Esempio n. 1
0
// Specifying shell geometries based on g_tipPos, g_furHeight, and g_numShells.
// You need to call this function whenver the shell needs to be updated
static void updateShellGeometry() {
  // TASK 1 and 3 TODO: finish this function as part of Task 1 and Task 3
  int numVertices = g_bunnyMesh.getNumVertices();
  
  g_bunnyMeshCopy = Mesh(g_bunnyMesh);

  createSmoothNormals(g_bunnyMeshCopy);
  RigTForm path = getPathAccumRbt(g_world, g_bunnyNode);

  for(int i = 0; i < g_numShells; i++) {

    // iterate through each vertex in the mesh
    for(int j = 0; j < numVertices; j++) {

      Cvec3 normal = g_bunnyMesh.getVertex(j).getNormal();
      Cvec3 n = normal * (((double) g_furHeight) / g_numShells);

      Cvec3 s = (path * RigTForm(g_tipAtRest[j])).getTranslation();
      Cvec3 d = (g_tipPos[j] - s) * (2.0 / (g_numShells * (g_numShells - 1)));

      Mesh::Vertex v = g_bunnyMeshCopy.getVertex(j);
      Cvec3 vPos = v.getPosition();
      Cvec3 newPos = vPos + (n + (d * (i+1)));

      v.setPosition(newPos);
      v.setNormal(newPos - vPos);
    }
    uploadMeshToSimpleGeometryPNX(g_bunnyMeshCopy, *(g_bunnyShellGeometries[i]), g_hairyness);
  }

}
Esempio n. 2
0
static void applySubdivs(Mesh &mesh, int subdivLevel) {
  for (int i = 0; i < subdivLevel; i++) {
    // subdivide faces
    for (int j = 0, n = mesh.getNumFaces(); j < n; j++) {
      Mesh::Face face = mesh.getFace(j);
      int verticesAroundFace = face.getNumVertices();
      Cvec3 vertexSum = Cvec3();
      for (int k = 0; k < verticesAroundFace; k++) {
        vertexSum += face.getVertex(k).getPosition();
      }
      vertexSum = vertexSum * (1.0 / verticesAroundFace); 
      mesh.setNewFaceVertex(face, vertexSum);
    }

    // subdivide edges
    for (int j = 0, n = mesh.getNumEdges(); j < n; j++) {
      Mesh::Edge edge = mesh.getEdge(j);
      Cvec3 vertexSum = (edge.getVertex(0).getPosition() +
        edge.getVertex(1).getPosition() +
        mesh.getNewFaceVertex(edge.getFace(0)) +
        mesh.getNewFaceVertex(edge.getFace(1))) * 0.25;
      mesh.setNewEdgeVertex(edge, vertexSum); 
    }

    // subdivide vertices
    for (int j = 0, n = mesh.getNumVertices(); j < n; j++) {
      Mesh::Vertex v = mesh.getVertex(j);
      int numOfVertices = 0;
      
      Mesh::VertexIterator vertexIter(v.getIterator()), iterOrigin(vertexIter);
      Cvec3 accumVertices = Cvec3();
      Cvec3 accumFaceVertices = Cvec3();

      do {
        accumVertices += vertexIter.getVertex().getPosition();
        accumFaceVertices += mesh.getNewFaceVertex(vertexIter.getFace());
        
        numOfVertices++;
      } while (++vertexIter != iterOrigin);
      
      double factor = 1.0 / (numOfVertices * numOfVertices);

      Cvec3 vertexVertex = 
        v.getPosition() * ((numOfVertices - 2.0) / numOfVertices) +
        accumVertices * factor +
        accumFaceVertices * factor; 
      mesh.setNewVertexVertex(mesh.getVertex(j), vertexVertex);
    }
    
    // subdivide for each level of subdivision we need 
    mesh.subdivide();
  }
}
Esempio n. 3
0
static void simpleShadeCube(Mesh& mesh) {
  Cvec3 normal = Cvec3(0, 1, 0);
  for (int i = 0; i < mesh.getNumFaces(); ++i) {
    const Mesh::Face f = mesh.getFace(i);
    Cvec3 facenorm = f.getNormal();

    for (int j = 0; j < f.getNumVertices(); ++j) {
      const Mesh::Vertex v = f.getVertex(j);
      v.setNormal(facenorm);
    }
  }
}
Esempio n. 4
0
void collectVertexVertices(Mesh& m) {
  vector<vector<Cvec3> > vertexVertices;
  for (int i = 0; i < m.getNumVertices(); ++i) {
    const Mesh::Vertex v = m.getVertex(i);
    Mesh::VertexIterator it(v.getIterator()), it0(it);
    vector<Cvec3> vertices;
    vector<Cvec3> faces;
    do {
      vertices.push_back(it.getVertex().getPosition());
      faces.push_back(m.getNewFaceVertex(it.getFace()));
    }
    while (++it != it0);                                  // go around once the 1ring
    Cvec3 vertex = getVertexVertex(v.getPosition(), vertices, faces);
    m.setNewVertexVertex(v, vertex);
  }
}
Esempio n. 5
0
// New function that initialize the dynamics simulation
static void initSimulation() {
  g_tipPos.resize(g_bunnyMesh.getNumVertices(), Cvec3(0));
  g_tipVelocity = g_tipPos;
  g_tipAtRest = g_tipPos;

  // TASK 1 TODO: initialize g_tipPos to "at-rest" hair tips in world coordinates
  int numVertices = g_bunnyMesh.getNumVertices();
  for(int i = 0; i < numVertices; i++) {
    Mesh::Vertex v = g_bunnyMesh.getVertex(i);
    g_tipAtRest[i] = v.getPosition() + (v.getNormal() * g_furHeight);
    g_tipPos[i] = (getPathAccumRbt(g_world, g_bunnyNode) * RigTForm(g_tipAtRest[i])).getTranslation();
  }

  // Starts hair tip simulation
  hairsSimulationCallback(0);
}
Esempio n. 6
0
static void updateMeshNormals(Mesh &mesh) {
  for (int i = 0, n = mesh.getNumVertices(); i < n; i++) {
    Cvec3 vectorSum = Cvec3(); 
    Mesh::Vertex v = mesh.getVertex(i);

    Mesh::VertexIterator vertexIter(v.getIterator()), iterOrigin(vertexIter);

    // walk around the vertex
    do {
      vectorSum += vertexIter.getFace().getNormal();
    } while (++vertexIter != iterOrigin);

    if (dot(vectorSum, vectorSum) > CS175_EPS2) {
      vectorSum.normalize();
    }
 
    v.setNormal(vectorSum);
  } 
}
Esempio n. 7
0
static void updateShellGeometry() {
  float xs[] = {0, g_hairyness, 0};
  float ys[] = {0, 0, g_hairyness};


  vector<Cvec3> prevPos;
  prevPos.resize(g_bunnyMesh.getNumFaces() * 3);

  for (int level = 0; level < g_numShells; ++level) {
    int counter = 0;
    vector<VertexPNX> verts;
    for (int i = 0; i < g_bunnyMesh.getNumFaces(); ++i) {
      const Mesh::Face f = g_bunnyMesh.getFace(i);
      for (int j = 0; j < f.getNumVertices(); ++j) {
        const Mesh::Vertex v = f.getVertex(j);
        int index = v.getIndex();
        Cvec3 pos = v.getPosition();
        Cvec3 normal = v.getNormal();
        Cvec2 c = Cvec2(xs[j], ys[j]);

        Cvec3 n = normal * g_furHeight / g_numShells;
        Cvec3 s = pos + (n * g_numShells);
        Cvec3 t = world2bunny(g_tipPos[index]);
        Cvec3 d = (t - s) / ((g_numShells + 1) * g_numShells / 2);
        /* Cvec3 d = (world2bunny(g_tipPos[index]) - (normal * g_furHeight)) / (g_numShells - 1); */

        if (level == 0) {
          prevPos[counter] = pos;
          verts.push_back(VertexPNX(pos, n, c));
        }
        else {
          Cvec3 new_position = prevPos[counter] + n + (d * level);
          verts.push_back(VertexPNX(new_position, new_position - prevPos[counter], c));
          prevPos[counter] = new_position;
        }
        ++counter;
      }
    }
    int numVertices = verts.size();
    g_bunnyShellGeometries[level]->upload(&verts[0], numVertices);
    verts.clear();
  }
}
Esempio n. 8
0
//animation timer for mesh
static void animateMeshCallback(int whocares) {

  g_meshObject = Mesh(g_meshObjectCopy);

  //update animation data, geometry
  for(int i = 0; i < 8; ++i){

    float ang = g_meshAnimationAngle[i];
    ang += (g_meshAnimationSpeed * g_meshAngleSpeed[i]);

    //between 0 and PI
    if(ang >= M_PI)
      ang = 0;
    g_meshAnimationAngle[i] = ang;

    //between 0 and 1
    float scale = sin(ang);

    //between 0.5 and 3
    float lowerBound = 0.5, upperBound = 3.0;
    scale = lowerBound + (scale * (upperBound - lowerBound));

    //update geometry
    Mesh::Vertex v = g_meshObject.getVertex(i);
    Cvec3 vPos = v.getPosition();

    //multiply by scale
    v.setPosition(vPos * scale);
  }

  //subdivide modified g_meshObject
  subdivideMesh(g_meshObject, g_meshSubdivisionLvl);

  //upload geometry
  uploadMeshGeometry();

  //redraw!
  glutPostRedisplay();

  glutTimerFunc(1000/g_animateFramesPerSecond,
    animateMeshCallback, 0);
}
Esempio n. 9
0
// New function that initialize the dynamics simulation
static void initSimulation() {
  bunnyTransformSet = true;
  bunnyTransform = (getPathAccumRbt(g_world, g_bunnyNode));
  g_tipPos.resize(g_bunnyMesh.getNumVertices(), Cvec3(0));
  g_tipStartPos.resize(g_bunnyMesh.getNumVertices(), Cvec3(0));
  g_tipVelocity = g_tipPos;

  // TASK 1 TODO: initialize g_tipPos to "at-rest" hair tips in world coordinates

  for (int i = 0; i < g_bunnyMesh.getNumVertices(); ++i) {
    const Mesh::Vertex v = g_bunnyMesh.getVertex(i);
    Cvec3 pos = v.getPosition();
    Cvec3 normal = v.getNormal();
    g_tipPos[i] = bunny2world(pos + normal * g_furHeight);
    g_tipStartPos[i] = bunny2world(pos + normal * g_furHeight);
  }

  // Starts hair tip simulation
  hairsSimulationCallback(0);
}
Esempio n. 10
0
static void initBunnyMeshes() {
  g_bunnyMesh.load("bunny.mesh");

  // TODO: Init the per vertex normal of g_bunnyMesh, using codes from asst7
  // ...
  shadeCube(g_bunnyMesh);
  // cout << "Finished shading bunny" << endl;
  // TODO: Initialize g_bunnyGeometry from g_bunnyMesh, similar to
  vector<VertexPN> verts;
  for (int i = 0; i < g_bunnyMesh.getNumFaces(); ++i) {
    const Mesh::Face f = g_bunnyMesh.getFace(i);
    Cvec3 pos;
    Cvec3 normal;

    if (g_flat)
      normal = f.getNormal();

    for (int j = 0; j < f.getNumVertices(); ++j) {
      const Mesh::Vertex v = f.getVertex(j);
      pos = v.getPosition();

      if (!g_flat)
        normal = v.getNormal();

      verts.push_back(VertexPN(pos, normal));
    }
  }

  // add vertices to bunny geometry
  int numVertices = verts.size();

  g_bunnyGeometry.reset(new SimpleGeometryPN());
  g_bunnyGeometry->upload(&verts[0], numVertices);

  // Now allocate array of SimpleGeometryPNX to for shells, one per layer
  g_bunnyShellGeometries.resize(g_numShells);
  for (int i = 0; i < g_numShells; ++i) {
    g_bunnyShellGeometries[i].reset(new SimpleGeometryPNX());
  }
}
Esempio n. 11
0
static void loadMeshGeometry(Mesh& m, GeometryPX& g) {
	vector<GLfloat> pos, tex;
	for (int i = 0; i < m.getNumFaces(); ++i) {
		const Mesh::Face f = m.getFace(i);
		for (int j = 0; j < f.getNumVertices(); ++j) {
			const Mesh::Vertex v = f.getVertex(j);
			pos.push_back((GLfloat)(v.getPosition()[0]));
			pos.push_back((GLfloat)(v.getPosition()[1]));
			tex.push_back((GLfloat)v.getTexCoords()[0]);
			tex.push_back((GLfloat)v.getTexCoords()[1]);
		}
	}

	const unsigned int size = pos.size() * sizeof(GLfloat);
	glBindBuffer(GL_ARRAY_BUFFER, g.posVbo);
	glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
	glBufferSubData(GL_ARRAY_BUFFER, 0, size, &pos[0]);
	checkGlErrors();

	glBindBuffer(GL_ARRAY_BUFFER, g.texVbo);
	glBufferData(GL_ARRAY_BUFFER, size, NULL, GL_DYNAMIC_DRAW);
	glBufferSubData(GL_ARRAY_BUFFER, 0, size, &tex[0]);
	checkGlErrors();
}
Esempio n. 12
0
static void initCubeMesh() {
  if (!meshLoaded) {
    cubeMesh.load("./cube.mesh");
    meshLoaded = true;
  }

  // set normals
  shadeCube(cubeMesh);

  // collect vertices from each face and map quads to triangles
  vector<VertexPN> verts;
  for (int i = 0; i < cubeMesh.getNumFaces(); ++i) {
    const Mesh::Face f = cubeMesh.getFace(i);
    Cvec3 pos;
    Cvec3 normal;

    if (g_flat)
      normal = f.getNormal();

    for (int j = 0; j < f.getNumVertices(); ++j) {
      const Mesh::Vertex v = f.getVertex(j);
      pos = v.getPosition();

      if (!g_flat)
        normal = v.getNormal();

      verts.push_back(VertexPN(pos, normal));
      if (j == 2) {
        verts.push_back(VertexPN(pos, normal));
      }
    }
    const Mesh::Vertex v = f.getVertex(0);
    pos = v.getPosition();

    if (!g_flat)
      normal = v.getNormal();

    verts.push_back(VertexPN(pos, normal));
  }

  // add vertices to cube geometry
  int numVertices = verts.size();
  if (!g_cubeGeometryPN) {
    g_cubeGeometryPN.reset(new SimpleGeometryPN());
  }
  g_cubeGeometryPN->upload(&verts[0], numVertices);
}
Esempio n. 13
0
static void shadeCube(Mesh& mesh) {
  Cvec3 normal = Cvec3(0, 0, 0);
  for (int i = 0; i < mesh.getNumVertices(); ++i) {
    mesh.getVertex(i).setNormal(normal);
  }

  for (int i = 0; i < mesh.getNumFaces(); ++i) {
    const Mesh::Face f = mesh.getFace(i);
    Cvec3 facenorm = f.getNormal();

    for (int j = 0; j < f.getNumVertices(); ++j) {
      const Mesh::Vertex v = f.getVertex(j);
      v.setNormal(facenorm + v.getNormal());
    }
  }

  for (int i = 0; i < mesh.getNumVertices(); ++i) {
    const Mesh::Vertex v = mesh.getVertex(i);
    if (norm2(v.getNormal()) > .001) {
          v.setNormal(normalize(v.getNormal()));
    }
  }
}
Esempio n. 14
0
void tesselateMesh(Mesh& obj, int rec=1, bool onSphere=false)
{
    BBox bb = obj.calcBBox();
    std::cout << "Mesh bbox="<<bb<<std::endl;

    std::cout << "Flipping mesh..."<<std::endl;
    obj.calcFlip();
    obj.calcEdges();
    bool closed = obj.isClosed();
    std::cout << "Mesh is "<<(closed?"":"NOT ")<<"closed."<<std::endl;

    Vec3f center; Vec3f radius;
    if (onSphere)
    {
        // HACK: keep center at (0,0,0), so that we can use translation to concentrate vertices on one side of the sphere
        //center = (bb.b+bb.a)/2;
        radius = (bb.b-bb.a)/2;
    }

    if (rec == 0 && onSphere)
    {
        for(int i=0; i<obj.nbp(); i++)
            projectOnSphere(obj.PP(i),obj.PN(i),center,radius[0]);
        obj.calcNormals();
        return;
    }

    bool groups = obj.getAttrib(Mesh::MESH_POINTS_GROUP);
    if (!groups)
    {
        std::cout << "Creating artificial groups."<<std::endl;
        // create artificial groups
        for(int i=0; i<obj.nbp(); i++)
        {
            obj.PG(i) = i;
            obj.GP0(i) = i;
        }
        obj.setAttrib(Mesh::MESH_POINTS_GROUP,true);
    }

    std::cout << "Input mesh: "<<obj.nbp()<<" points, "<<obj.nbf()<<" faces."<<std::endl;

    for (int r=0; r<rec; r++)
    {
        std::cout << "Tesselation level "<<r+1<<"..."<<std::endl;
        obj.calcEdges();

        std::cout << "Creating new points..."<<std::endl;
        // first create a new point on each edge
        for(int e1 = 0 ; e1 < (int)obj.edges.size(); ++e1)
        {
            int g1 = obj.getPG(e1);
            for(std::map< int,Mesh::Edge >::iterator it = obj.edges[e1].begin(), itend = obj.edges[e1].end(); it != itend; ++it)
            {
                int g2 = obj.getPG(it->first);

                int f1p1 = -1, f1p2 = -1;
                int f2p1 = -1, f2p2 = -1;
                int i1 = -1;
                if (it->second.f1 >= 0)
                {
                    Vec3i fp = obj.getFP(it->second.f1);
                    f1p1 = fp[0], f1p2 = fp[1];
                    if (obj.getPG(f1p1) != g1)
                    {
                        f1p1 = fp[1]; f1p2 = fp[2];
                        if (obj.getPG(f1p1) != g1)
                        {
                            f1p1 = fp[2]; f1p2 = fp[0]; // this is the last possible edge
                        }
                    }
                    if (obj.getPG(f1p1) != g1 || (obj.getPG(f1p2) != g2))
                    {
                        std::cerr << "ERROR: Edge "<<g1<<" - "<<g2<<" not found on face 1 ( "<<it->second.f1<<" = "<<fp<<" = "<<Vec3i(obj.getPG(fp[0]),obj.getPG(fp[1]),obj.getPG(fp[2]))<<" )"<<std::endl;
                        it->second.f1 = -1;
                        continue;
                    }
                    Mesh::Vertex v;
                    v = obj.getP(f1p1);
                    v += obj.getP(f1p2);
                    v.mean(2);
                    //if (onSphere) v.p = projectOnSphere(v.p, center, radius[0]);
                    i1 = obj.addP(v);
                    // replace the face index with the new point index
                    it->second.f1 = i1;
                }

                if (it->second.f2 >= 0)
                {
                    Vec3i fp = obj.getFP(it->second.f2);
                    f2p1 = fp[0], f2p2 = fp[1];
                    if (obj.getPG(f2p1) != g2)
                    {
                        f2p1 = fp[1]; f2p2 = fp[2];
                        if (obj.getPG(f2p1) != g2)
                        {
                            f2p1 = fp[2]; f2p2 = fp[0]; // this is the last possible edge
                        }
                    }
                    if (obj.getPG(f2p1) != g2 || (obj.getPG(f2p2) != g1))
                    {
                        std::cerr << "ERROR: Edge "<<g1<<" - "<<g2<<" not found on face 2 ( "<<it->second.f2<<" = "<<fp<<" = "<<Vec3i(obj.getPG(fp[0]),obj.getPG(fp[1]),obj.getPG(fp[2]))<<" )"<<std::endl;
                        it->second.f2 = -1;
                        continue;
                    }
                    if (f1p1 == f2p2 && f1p2 == f2p1)
                    {
                        // same point as other face
                        it->second.f2 = i1;
                    }
                    else
                    {
                        Mesh::Vertex v;
                        v = obj.getP(f2p1);
                        v += obj.getP(f2p2);
                        v.mean(2);
                        //if (onSphere) v.p = projectOnSphere(v.p, center, radius[0]);
                        int i2;
                        if (i1 == -1) // no other edge, create a new group
                            i2 = obj.addP(v);
                        else
                        {
                            // see of all points are on the same subgroup
                            int gf1p1 = g1;
                            while (obj.getGP0(gf1p1+1) <= -f1p1) ++gf1p1;
                            int gf1p2 = g2;
                            while (obj.getGP0(gf1p2+1) <= -f1p2) ++gf1p2;
                            int gf2p1 = g2;
                            while (obj.getGP0(gf2p1+1) <= -f2p1) ++gf2p1;
                            int gf2p2 = g1;
                            while (obj.getGP0(gf2p2+1) <= -f2p2) ++gf2p2;
                            int g = obj.getPG(i1);
                            i2 = obj.addP(v,g);
                            if (gf1p1 != gf2p2 || gf1p2 != gf2p1)
                            {
                                // create a subgroup
                                obj.GP0(obj.nbg()) = -i2;
                            }
                        }
                        // replace the face index with the new point index
                        it->second.f2 = i2;
                    }
                }
            }
        }

        // then create new faces
        std::cout << "Creating new faces..."<<std::endl;
        std::vector<Vec3i> faces_p;
        int nbf0 = obj.nbf();
        faces_p.reserve(nbf0*4);
        for(int i=0; i<nbf0; ++i)
        {
            Vec3i points = obj.getFP(i);
            Vec3i edges;
            edges[0] = obj.getEdgeFace(points[0],points[1]);
            edges[1] = obj.getEdgeFace(points[1],points[2]);
            edges[2] = obj.getEdgeFace(points[2],points[0]);
            if ((unsigned)edges[0] >= (unsigned)obj.nbp() || (unsigned)edges[1] >= (unsigned)obj.nbp() || (unsigned)edges[2] >= (unsigned)obj.nbp())
            {
                std::cerr << "ERROR: invalid edge points " << edges << " in face " <<i<<" = "<<points<<std::endl;
                continue;
            }
            faces_p.push_back(Vec3i(edges[2],points[0],edges[0]));
            faces_p.push_back(Vec3i(edges[0],points[1],edges[1]));
            faces_p.push_back(Vec3i(edges[2],edges[0],edges[1]));
            faces_p.push_back(Vec3i(edges[1],points[2],edges[2]));
        }
        obj.faces_p = faces_p;

        // finally we update the material groups
        std::cout << "Updating materials..."<<std::endl;
        for(unsigned int i=0; i<obj.mat_groups.size(); ++i)
        {
            obj.mat_groups[i].f0 *= 4;
            obj.mat_groups[i].nbf *= 4;
        }
        // and we recompute the edges
        if (r < rec-1)
        {
            std::cout << "Updating edges..."<<std::endl;
            obj.calcEdges();
            bool closed = obj.isClosed();
            std::cout << "Mesh is "<<(closed?"":"NOT ")<<"closed."<<std::endl;
        }
        if (onSphere)
        {
            for(int i=0; i<obj.nbp(); i++)
                projectOnSphere(obj.PP(i),obj.PN(i),center,radius[0]);
            obj.calcNormals();
        }
        std::cout << "Tesselation level "<<r<<" DONE: "<<obj.nbp()<<" points, "<<obj.nbf()<<" faces."<<std::endl;
    }
    if (!groups)
    {
        // remove artificial groups
        obj.setAttrib(Mesh::MESH_POINTS_GROUP,false);
    }
    obj.calcNormals();
}
Esempio n. 15
0
static void animateCube(int ms) {
  float t = (float) ms / (float) g_msBetweenKeyFrames;

  // scale all vertices in cube
  for (int i = 0; i < cubeMesh.getNumVertices(); ++i) {
    const Mesh::Vertex v = cubeMesh.getVertex(i);
    Cvec3 pos = v.getPosition();
    double factor = (1 + (float(g_div_level)/10)) * ((-1 * sin((double) (g_horiz_scale * ms) / (1000 * (vertex_speeds[i] + .5))) + 1) / 2 + .5);
    pos[0] = vertex_signs[i][0] * (factor / sqrt(3));
    pos[1] = vertex_signs[i][1] * (factor / sqrt(3));
    pos[2] = vertex_signs[i][2] * (factor / sqrt(3));
    v.setPosition(pos);

  }

  // copy mesh to temporary mesh for rendering
  Mesh renderMesh = cubeMesh;

  // subdivision
  for (int i = 0; i < g_div_level; ++i) {
    collectFaceVertices(renderMesh);
    collectEdgeVertices(renderMesh);
    collectVertexVertices(renderMesh);
    renderMesh.subdivide();

  }

  // set normals
  shadeCube(renderMesh);

  // collect vertices for each face
  vector<VertexPN> verts;
  int q = 0;
  for (int i = 0; i < renderMesh.getNumFaces(); ++i) {
    const Mesh::Face f = renderMesh.getFace(i);
    Cvec3 pos;
    Cvec3 normal;
    for (int j = 0; j < f.getNumVertices(); ++j) {
      const Mesh::Vertex v = f.getVertex(j);
      pos = v.getPosition();

      if (!g_flat)
        normal = v.getNormal();
      else
        normal = f.getNormal();

      verts.push_back(VertexPN(pos, normal));
      if (j == 2) {
        verts.push_back(VertexPN(pos, normal));
      }
    }
    const Mesh::Vertex v = f.getVertex(0);
    pos = v.getPosition();
    if (!g_flat)
      normal = v.getNormal();
    else
      normal = f.getNormal();
    verts.push_back(VertexPN(pos, normal));
  }

  // dump into geometry
  int numVertices = verts.size();
  g_cubeGeometryPN->upload(&verts[0], numVertices);
  glutPostRedisplay();
  glutTimerFunc(1000/g_animateFramesPerSecond,
      animateCube,
      ms + 1000/g_animateFramesPerSecond);
}