The oscillating plane example

This example demonstrates how to create a flat plane from triangles and then oscillate its vertices to obtain a dynamic surface. Also, the color of vertices will depend on the oscillation amplitude.

Note

This is example 07-3D/06-OscillatingPlane.

The example is based on the emptyExample project in openFrameworks. Begin with adding the declaration and definition of the setNormals() function, as described in the Computing normals using the setNormals() function section. Then in the testApp.h file, in the testApp class declaration, add definitions of mesh and light:

ofMesh mesh;          //Mesh
ofLight light;        //Light

In the beginning of the testApp.cpp file, add constants with vertex grid size:

int W = 100;          //Grid size
int H = 100;

The setup() function adds vertices and triangles to the mesh and enables lighting:

void testApp::setup(){
  //Set up vertices and colors
  for (int y=0; y<H; y++) {
      for (int x=0; x<W; x++) {
          mesh.addVertex(
                  ofPoint( (x - W/2) * 6, (y - H/2) * 6, 0 ) );
          mesh.addColor( ofColor( 0, 0, 0 ) );
      }
  }
  //Set up triangles' indices
  for (int y=0; y<H-1; y++) {
      for (int x=0; x<W-1; x++) {
          int i1 = x + W * y;
          int i2 = x+1 + W * y;
          int i3 = x + W * (y+1);
          int i4 = x+1 + W * (y+1);
          mesh.addTriangle( i1, i2, i3 );
          mesh.addTriangle( i2, i4, i3 );
      }
  }
  setNormals( mesh );  //Set normals
  light.enable();      //Enable lighting
}

The update() function changes the z coordinate of each vertex using Perlin noise (refer to Appendix B, Perlin Noise) and also sets its color between the range blue to white:

void testApp::update(){
  float time = ofGetElapsedTimef();      //Get time
  //Change vertices
  for (int y=0; y<H; y++) {
      for (int x=0; x<W; x++) {
          int i = x + W * y;       //Vertex index
          ofPoint p = mesh.getVertex( i );
          //Get Perlin noise value
          float value =
              ofNoise( x * 0.05, y * 0.05, time * 0.5 );
          //Change z-coordinate of vertex
          p.z = value * 100;
          mesh.setVertex( i, p );
          //Change color of vertex
          mesh.setColor( i,
                        ofColor( value*255, value * 255, 255 ) );
      }
  }
  setNormals( mesh );  //Update the normals
}

The draw() function draws the surface and slowly rotates it:

void testApp::draw(){
  ofEnableDepthTest();    //Enable z-buffering

  //Set a gradient background from white to gray
  ofBackgroundGradient( ofColor( 255 ), ofColor( 128 ) );

  ofPushMatrix();          //Store the coordinate system

  //Move the coordinate center to screen's center
  ofTranslate( ofGetWidth()/2, ofGetHeight()/2, 0 );

  //Calculate the rotation angle
  float time = ofGetElapsedTimef();   //Get time in seconds
  float angle = time * 20;   //Compute angle. We rotate at speed
                             //20 degrees per second
  ofRotate( 30, 1, 0, 0 );            //Rotate coordinate system
  ofRotate( angle, 0, 0, 1 );

  //Draw mesh
  //Here ofSetColor() does not affects the result of drawing,
  //because the mesh has its own vertices' colors
  mesh.draw();

  ofPopMatrix();      //Restore the coordinate system
}

Run the example and you will see a pulsating surface that slowly rotates on the screen:

The oscillating plane example

Now replace in the testApp::draw() function in the line mesh.draw(); by the following line:

mesh.drawWireframe();

Now, run the project and you will see the wireframe structure of the surface.

Until now you knew how to create simple animated smooth surfaces and disconnected clouds of primitives. Let's consider an advanced example of constructing a smooth surface that grows and twists in space.

The twisting knot example

In this example we will create a tube-like surface, that is formed from a number of deformed circles. At each update() call, we will generate one circle and connect it with the previous circle by adding triangles to the surface. At each step the circle will slowly move, rotate, and deform in space. As result, we will see a growing and twisting 3D knot.

Note

This is example 07-3D/07-TwistingKnot.

The example is based on the emptyExample project in openFrameworks. Begin with adding declaration and definition of the setNormals() function, as is described in the Computing normals using the setNormals() function section. Then in the testApp.h file, in the testApp class declaration, add definitions of the mesh, light, and addRandomCircle() function:

ofMesh mesh;          //Mesh
ofLight light;        //Light
void addRandomCircle( ofMesh &mesh ); //Main function which
        //moves circle and adds triangles to the object

In the beginning of the testApp.cpp file, add the constants and the variables for the circle that will be used for knot generation:

//The circle parameters
float Rad = 25;              //Radius of circle
float circleStep = 3;        //Step size for circle motion
int circleN = 40;            //Number of points on the circle

//Current circle state
ofPoint pos;                //Circle center
ofPoint axeX, axyY, axyZ;   //Circle's coordinate system

The setup() function sets the initial values of the circle's position and also enables lighting with light, using its default settings:

void testApp::setup(){
  pos = ofPoint( 0, 0, 0 );  //Start from center of coordinate
  axeX = ofPoint( 1, 0, 0 ); //Set initial coordinate system
  axyY = ofPoint( 0, 1, 0 );
  axyZ = ofPoint( 0, 0, 1 );
  light.enable();            //Enable lighting
  ofSetFrameRate( 60 );      //Set the rate of screen redrawing
}

The update() function just calls the addRandomCircle() function, which adds one more circle to the knot:

void testApp::update(){
  addRandomCircle( mesh );
}

The draw() function draws the mesh on the screen. Note that we use the mesh.getCentroid() function, which returns the center of mass of mesh's vertex array. In other words, we apply it for the shift coordinate system ofTranslate( -mesh.getCentroid() ), which helps us to draw our object positioned in the center :

void testApp::draw(){
  ofEnableDepthTest();    //Enable z-buffering

  //Set a gradient background from white to gray
  ofBackgroundGradient( ofColor( 255 ), ofColor( 128 ) );

  ofPushMatrix();  //Store the coordinate system
  //Move the coordinate center to screen's center
  ofTranslate( ofGetWidth()/2, ofGetHeight()/2, 0 );

  //Calculate the rotation angle
  float time = ofGetElapsedTimef(); //Get time in seconds
  float angle = time * 20;          //Compute the angle.
                     //We rotate at speed 20 degrees per second
  ofRotate( angle, 0, 1, 0 );       //Rotate the coordinate system
                                    //along y-axe
  //Shift the coordinate center so the mesh
  //will be drawn in the screen center
  ofTranslate( -mesh.getCentroid() );
  
  //Draw the mesh
  //Here ofSetColor() does not affects the result of drawing,
  //because the mesh has its own vertices' colors
  mesh.draw();

  ofPopMatrix();  //Restore the coordinate system
}

The most important function in the example is addRandomCircle(). It pseudorandomly moves the circle, adds new vertices from the circle to the object's vertex array, and adds corresponding triangles to the object. It also sets colors for the new vertices.

void testApp::addRandomCircle( ofMesh &mesh ){
  float time = ofGetElapsedTimef();    //Time

  //Parameters – twisting and rotating angles and color
  float twistAngle = 5.0 * ofSignedNoise( time * 0.3 + 332.4 );
  float rotateAngle = 1.5;
  ofFloatColor color( ofNoise( time * 0.05 ),
                      ofNoise( time * 0.1 ),
                      ofNoise( time * 0.15 ));
  color.setSaturation( 1.0 );  //Make the color maximally
                               //colorful

  //Rotate the coordinate system of the circle
  axeX.rotate( twistAngle, axyZ );
  axyY.rotate( twistAngle, axyZ );

  axeX.rotate( rotateAngle, axyY );
  axyZ.rotate( rotateAngle, axyY );

  //Move the circle on a step
  ofPoint move = axyZ * circleStep;
  pos += move;

  //Add vertices
  for (int i=0; i<circleN; i++) {
      float angle = float(i) / circleN * TWO_PI;
      float x = Rad * cos( angle );
      float y = Rad * sin( angle );
      //We would like to distort this point
      //to make the knot's surface embossed
      float distort = ofNoise( x * 0.2, y * 0.2,
                              time * 0.2 + 30 );
      distort = ofMap( distort, 0.2, 0.8, 0.8, 1.2 );
      x *= distort;
      y *= distort;
    
      ofPoint p = axeX * x + axyY * y + pos;
      mesh.addVertex( p );
      mesh.addColor( color );
  }

  //Add the triangles
  int base = mesh.getNumVertices() - 2 * circleN;
  if ( base >= 0 ) {  //Check if it is not the first step
                      //and we really need to add the triangles
      for (int i=0; i<circleN; i++) {
          int a = base + i;
          int b = base + (i + 1) % circleN;
          int c = circleN  + a;
          int d = circleN  + b;
          mesh.addTriangle( a, b, d ); //Clock-wise
          mesh.addTriangle( a, d, c );  
      }
      //Update the normals
      setNormals( mesh );
  }
}

Run the example and you will see a growing and twisting knot, as shown in the following screenshot:

The twisting knot example

Note that we control the rate of testApp::update() callings (and hence the addRandomCircle() rate) using the ofSetFrameRate( 60 ) call in testApp::setup(). If you change the rate, say to ofSetFrameRate( 30 ), you will obtain a differently shaped knot. To make the resultant shape independent of frame rate, you should make the circleStep parameter dependent on the time between current and previous frames.

Note

At each update() call, the application constantly adds new vertices and triangles to the object. Then it recalculates all the normals, though many of the triangles did not change. So application performance will degrade with time because the setNormals() function will take more and more computing power. To solve this problem, you can optimize the setNormals() function so it does not recalculate the unchanged normals and does not check the old triangles at all.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset