• fullscreen
  • MapMesh.pde
  • MapParticule.pde
  • Mapface.pde
  • Mappoint.pde
  • ParticulesOnMesh.pde
  • class MapMesh {
    
      Mappoint[] points;
      Mapface[] faces;
      Mappoint[] centers;
    
      float width;
      float height;
      float depth;
      int sampling;
      int[] grid;
    
      public MapMesh() {
        points = null;
        faces = null;
        centers = null;
        sampling = 0;
        grid = new int[2];
      }
    
      public void process( PImage img, float elevation, int sampling ) {
    
        this.sampling = sampling;
        width = img.width;
        height = img.height;
        depth = elevation;
    
        points = new Mappoint[ ceil( img.width / (float) sampling ) * ceil( img.height / (float) sampling ) ];
    
        // retrieving brightness of each pixels
        img.loadPixels();
        float[] pixels_brightness = new float[ img.pixels.length ];
        for ( int p=0; p < img.pixels.length; p++ )
          pixels_brightness[ p ] = brightness( img.pixels[ p ] ) / 255.f;
    
        // creating the map points
        grid[0] = (int) ceil( img.width / (float) sampling );
        grid[1] = (int) ceil( img.height / (float) sampling );
        int i = 0;
        int gc = 0;
        int gr = 0;
        for ( int r=0; r < img.height; r += sampling ) {
          gc = 0;
          for ( int c=0; c < img.width; c += sampling ) {
            int pix = c + ( r * img.width );
            // if sampling si > 1, than we average the surrounding pixels
            int np = 0;
            float totalb = 0;
            int halfs = (int) floor( sampling * 0.5f );
            for ( int sy = r-halfs; sy <= r+halfs; sy++ ) {
              if ( sy < 0 )
                continue;
              if ( sy >= img.height )
                break;
              for ( int sx = c-halfs; sx <= c+halfs; sx++ ) {
                if ( sx < 0 )
                  continue;
                if ( sx >= img.width )
                  break;
                totalb += pixels_brightness[ sx + ( sy * img.width ) ];
                np++;
              }
            }
            totalb /= np;
            points[ i ] = new Mappoint( i, gc, gr );
            points[ i ].position.set( ( c + 0.5f ), ( r + 0.5f ), totalb * depth );
            i++;
            gc++;
          }
          gr++;
        }
    
        // creating map faces
        int count = ( grid[0] - 1 ) * ( grid[1] - 1 ) * 2;
        faces = new Mapface[ count ];
        centers = new Mappoint[ count ];
        int facescount = 0;
        i = 0;
        for ( int r=0; r < grid[1]-1; r++ ) {
          for ( int c=0; c < grid[0]-1; c++ ) {
            i = c + r * grid[0];
            Mappoint topleft = points[ i ];
            Mappoint topright = points[ i + 1 ];
            Mappoint bottomleft = points[ i + grid[0] ];
            Mappoint bottomright = points[ i + grid[0] + 1 ];
            centers[ facescount ] = new Mappoint( facescount, 0, 0 );
            faces[ facescount ] = new Mapface( topleft, topright, bottomright, centers[ facescount ] );
            facescount++;
            centers[ facescount ] = new Mappoint( facescount, 0, 0 );
            faces[ facescount ] = new Mapface( topleft, bottomright, bottomleft, centers[ facescount ] );
            facescount++;
          }
        }
      }
    
      // this method return z value of the point and the value of the slope + normale values
      // output[ 0 ] : z value of the point x,y projected on the mesh
      // output[ 1 ] : slope, between -1 & 1 if the dirx & diry comes from a normalised 2D vector
      // output[ 2 -> 4 ] : normale
      public float[] getDepth( float x, float y, float dirx, float diry ) {
        if ( x < 0 || x >= width || y < 0 || y >= height )
          return new float[] { 0,0 };
        int[] top = new int[ 2 ];
        int[] bottom = new int[ 2 ];
        // locate the closest point:
        int c = (int) ( x / sampling );
        int r = (int) ( y / sampling );
    
        top[ 0 ] = c; 
        top[ 1 ] = r;
        if ( c < grid[0]-1 && r < grid[1]-1 ) {
          bottom[ 0 ] = c+1; 
          bottom[ 1 ] = r+1;
        } else if ( c < grid[0]-1 ) {
          bottom[ 0 ] = c+1; 
          bottom[ 1 ] = r; 
          top[ 1 ] = r-1;
        } else if ( r < grid[1]-1 ) {
          bottom[ 0 ] = c; 
          bottom[ 1 ] = r+1; 
          top[ 0 ] = c-1;
        } else {
          bottom[ 0 ] = c; 
          bottom[ 1 ] = r; 
          top[ 0 ] = c-1; 
          top[ 1 ] = r-1;
        }
    
        int p1 = top[ 0 ] + top[ 1 ] * grid[ 0 ];
        int p2 = bottom[ 0 ] + bottom[ 1 ] * grid[ 0 ];
    
        // get top face
        int fid = ( top[ 0 ] + ( grid[0] - 1 ) * top[ 1 ] ) * 2;
        int tmpf = fid + 1;
        if ( 
          dist( centers[ fid ].position.x, centers[ fid ].position.y, x, y ) >
          dist( centers[ tmpf ].position.x, centers[ tmpf ].position.y, x, y )
          ) {
          fid = tmpf;
        }
        Mapface f = faces[ fid ];
        Mappoint ce = centers[ fid ];
      
        float[] output = new float[ 5 ];
        output[ 0 ] = f.getZprojection( x,y );
        output[ 1 ] = f.normale.x * dirx + f.normale.y * diry;
        output[ 2 ] = f.normale.x;
        output[ 3 ] = f.normale.y;
        output[ 4 ] = f.normale.z;
        return output;
        
      }
    }
    
    
    class MapParticule {
    
      public final PVector gravity = new PVector( 0,0,-0.01 );
      public static final float MT_SPEED = 2;
      public static final float MT_DEVIATION = 0.03f;
    
      PVector direction;
      float[] positions;
    
      public MapParticule( float x, float y, float z ) {
        
        positions = new float[ 4 ];
        reset( x,y,z );
        
      }
      
      public void reset( float x, float y, float z ) {
        
        positions[ 0 ] = x;
        positions[ 1 ] = y;
        positions[ 2 ] = z;
        positions[ 3 ] = 0; // slope, pente en anglais
    
        direction = new PVector();
        direction.x = random( -1, 1 );
        direction.y = random( -1, 1 );
        direction.z = 0;
        direction.normalize();
        
      }
    
      public void update( MapMesh mesh ) {
        
        direction.add( gravity );
    
        positions[ 0 ] += direction.x * MT_SPEED;
        positions[ 1 ] += direction.y * MT_SPEED;
        positions[ 2 ] += direction.z * MT_SPEED;
        if ( positions[ 0 ] < 0 )
          positions[ 0 ] = 0;
        if ( positions[ 0 ] >= mesh.width )
          positions[ 0 ] = mesh.width-1;
        if ( positions[ 1 ] < 0 )
          positions[ 1 ] = 0;
        if ( positions[ 1 ] >= mesh.height )
          positions[ 1 ] = mesh.height-1;
    
        float[] proj = mesh.getDepth( positions[ 0 ], positions[ 1 ], direction.x, direction.y );
        if ( positions[ 2 ] < proj[ 0 ] ) {
          positions[ 2 ] = proj[ 0 ];
          positions[ 3 ] = proj[ 1 ];
          direction.x += proj[ 2 ] * 0.2f;
          direction.y += proj[ 3 ] * 0.2f;
        } else {
          positions[ 3 ] = 0;
        }
    
        if ( positions[ 0 ] + direction.x < 0 || positions[ 0 ] + direction.x * MT_SPEED > mesh.width )
          direction.x *= -1;
        if ( positions[ 1 ] + direction.y < 0 || positions[ 1 ] + direction.y * MT_SPEED > mesh.height )
          direction.y *= -1;
    
        direction.x += random( -MT_DEVIATION, MT_DEVIATION );
        direction.y += random( -MT_DEVIATION, MT_DEVIATION );
        direction.normalize();
      }
    }
    
    
    class Mapface {
    
      PVector normale;
      float kc, ac, bc;
      
      Mappoint center;
      Mappoint[] pts;
      
      public Mapface( Mappoint p1, Mappoint p2, Mappoint p3, Mappoint center ) {
      
        pts = new Mappoint[3];
        pts[ 0 ] = p1;
        pts[ 1 ] = p2;
        pts[ 2 ] = p3;
        
        this.center = center;
        
        renderFace();
      
      }
      
      // points may change => update of center and normale is required
      public void renderFace() {
        
        center.position.set(
          ( pts[0].position.x + pts[1].position.x + pts[2].position.x ) / 3,
          ( pts[0].position.y + pts[1].position.y + pts[2].position.y ) / 3,
          ( pts[0].position.z + pts[1].position.z + pts[2].position.z ) / 3
        );
        // via thierry ravet
        PVector u = new PVector( 
          pts[1].position.x - pts[0].position.x,
          pts[1].position.y - pts[0].position.y,
          pts[1].position.z - pts[0].position.z );
        PVector v = new PVector( 
          pts[2].position.x - pts[0].position.x,
          pts[2].position.y - pts[0].position.y,
          pts[2].position.z - pts[0].position.z );
        u.normalize();
        v.normalize();
        // cross product
        normale = u.cross( v );
      
        // preparing plane values
        // based on equation k = a*x0 + b*y0 + c*z0
        float k = normale.x * center.position.x + normale.y * center.position.y + normale.z * center.position.z;
        kc = k / normale.z;
        ac = normale.x / normale.z;
        bc = normale.y / normale.z;
        
      }
      
      public float dist( float x, float y ) {
        return PApplet.dist( x, y, center.position.x, center.position.y );
      }
      
      public float getZprojection( float x, float y ) {
        return kc - ac * x - bc * y;
      }
    }
    
    class Mappoint {
    
      int ID;
      int column, row;
      PVector position;
      
      public Mappoint( int ID, int column, int row ) {
        this.ID = ID;
        this.column = column;
        this.row = row;
        position = new PVector();
      }
    
    }
    
    PImage levels;
    
    MapMesh mesh;
    
    MapParticule[] parts;
    int pnum = 5000;
    
    int sampling = 7;
    float elevation = 280;
    
    boolean showimage = false;
    boolean drawedges = false;
    boolean drawfaces = false;
    
    void setup() {
    
      size( 1024, 768, P3D );
      levels = loadImage( "levelmap2.png" );
      
      mesh = new MapMesh();
      mesh.process( levels, elevation, sampling );
    
      parts = new MapParticule[ pnum ];
      for ( int i=0; i < pnum; i++ )
        parts[i] = new MapParticule( 
          random( 0.1f * mesh.width, 0.9f * mesh.width ), 
          random( 0.1f * mesh.height, 0.9f * mesh.height ),
          elevation );
          
      background( 0 );
    
    }
    
    void draw() {
    
      for ( int i=0; i < parts.length; i++ )
        parts[i].update( mesh );
    
      background( 0 );
      translate( width * 0.5f, height * 0.5f, -300 );
      rotateX( -mouseY * 0.03f );
      rotateZ( mouseX * 0.03f );
      translate( -levels.width * 0.5f, -levels.height * 0.5f, 0  );
      if ( showimage )
        image( levels, 0, 0 );
      noFill();
      
      stroke( 0, 255, 249 );
      for ( int i=0; i < parts.length; i++ ) {
        strokeWeight( 2 );
        point( parts[i].positions[ 0 ], parts[i].positions[ 1 ], parts[i].positions[ 2 ] );
        /*
        // rendering slope
        pushMatrix();
        translate( parts[i].positions[ 0 ], parts[i].positions[ 1 ], parts[i].positions[ 2 ] );
        rotateZ( -mouseX * 0.03f );
        rotateX( mouseY * 0.03f );
        noStroke();
        fill( 255 );
        if ( parts[i].positions[ 3 ] < -0.001f ) {
          translate( 0,-10,0 );
          beginShape();
          vertex( 0,-7,0 );
          vertex( -6,7,0 );
          vertex( 6,7,0 );
          endShape( CLOSE );
        } else if ( parts[i].positions[ 3 ] > 0.001f ) {
          translate( 0,10,0 );
          beginShape();
          vertex( 0,7,0 );
          vertex( -6,-7,0 );
          vertex( 6,-7,0 );
          endShape( CLOSE );
        }
        popMatrix();
        */
      }
      
      if ( drawedges && !drawfaces ) {
        strokeWeight(  1 );
        stroke( 127 );
        for ( int r = 0; r < mesh.grid[ 1 ]; r++ ) {
        for ( int c = 0; c < mesh.grid[ 0 ]; c++ ) {
          int p = c + r * mesh.grid[ 0 ];
          if ( c < mesh.grid[ 0 ] - 1 ) {
            line( 
              mesh.points[ p ].position.x,
              mesh.points[ p ].position.y,
              mesh.points[ p ].position.z,
              mesh.points[ p + 1 ].position.x,
              mesh.points[ p + 1 ].position.y,
              mesh.points[ p + 1 ].position.z
            );
          }
          if ( r < mesh.grid[ 1 ] - 1 ) {
            line( 
              mesh.points[ p ].position.x,
              mesh.points[ p ].position.y,
              mesh.points[ p ].position.z,
              mesh.points[ p + mesh.grid[ 0 ] ].position.x,
              mesh.points[ p + mesh.grid[ 0 ] ].position.y,
              mesh.points[ p + mesh.grid[ 0 ] ].position.z
            );
          }
        }
        }
      }
      if ( drawfaces ) {
        strokeWeight( 1 );
        for ( int f = 0; f < mesh.faces.length; f++ ) {
          Mappoint[] pts = mesh.faces[ f ].pts;
          noStroke();
          beginShape();
          fill( mesh.centers[ f ].position.z / elevation * 255.f );
          for ( int p = 0; p < pts.length; p++ )
            vertex( pts[ p ].position.x, pts[ p ].position.y, pts[ p ].position.z );
          endShape( CLOSE );
          stroke( 255,0,0 );
          pushMatrix();
          translate( mesh.centers[ f ].position.x, mesh.centers[ f ].position.y, mesh.centers[ f ].position.z );
          line( 0,0,0, mesh.faces[ f ].normale.x * 20, mesh.faces[ f ].normale.y * 20, mesh.faces[ f ].normale.z * 20 );
          popMatrix();
        }
      }
      
      // bundaries
      stroke( 255 );
      strokeWeight( 2 );
      noFill();
      pushMatrix();
      rect( 0, 0, levels.width, levels.height );
      translate( 0, 0, elevation );
      stroke( 80 );
      rect( 0, 0, levels.width, levels.height );
      popMatrix();
    
    }
    
    void keyPressed( ) {
      
      if ( key == 'e' ) {
        drawedges = !drawedges;
      } else if ( key == 'f' ) {
        drawfaces = !drawfaces;
      } else if ( key == 'i' ) {
        showimage = !showimage;
      } else if ( key == 'r' ) {
        for ( int i=0; i < pnum; i++ )
          parts[i].reset( random( 0.1f * mesh.width, 0.9f * mesh.width ), random( 0.1f * mesh.height, 0.9f * mesh.height ), elevation );
      } else {
        println( keyCode );
      }
    
    }
    
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    frankie zafe

    ParticulesOnMesh

    Add to Faves Me Likey@! 7
    You must login/register to add this sketch to your favorites.

    Compared to previous version:
    - refactoring : everything happens into mesh object
    - more effective localisation method (see MapMesh.getDepth).
    'r': reset the particules location
    'e': display edges
    'f': display faces ( very heavy! )
    'i': display base image
    Move the mouse to rotate the model
    Note: the white rectangle is the floor, the grey one is the roof :)

    You need to login/register to comment.