// ============================================================== // This file is part of MegaGlest (www.glest.org) // // Md5Model.h -- Copyright (c) 2006-2007 David Henry // changed for use with MegaGlest: Copyright (C) 2011- by Mark Vejvoda // // This code is licensed under the MIT license: // http://www.opensource.org/licenses/mit-license.php // // Open Source Initiative OSI - The MIT License (MIT):Licensing // // The MIT License (MIT) // Copyright (c) // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE.// // // Implementation of MD5 Model classes. // ///////////////////////////////////////////////////////////////////////////// #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif // _WIN32 #include #include #include #include #include #include "md5Texture.h" #include "ArbProgram.h" #include "ShaderManager.h" #include "Md5Model.h" namespace Shared { namespace Graphics { namespace md5 { using std::cout; using std::cerr; using std::endl; ///////////////////////////////////////////////////////////////////////////// // // global stuff // ///////////////////////////////////////////////////////////////////////////// render_path_e Md5Model::renderPath; ShaderProgram *Md5Model::shader=NULL; ArbVertexProgram *Md5Model::vp=NULL; ArbFragmentProgram *Md5Model::fp=NULL; // Tangent uniform's location GLint Md5Model::tangentLoc = -1; bool Md5Model::bDrawNormals = false; const int kMd5Version = 10; // Sort functor for joints struct SortByDepth : public std::binary_function { bool operator() (const Md5Joint_t &j1, const Md5Joint_t &j2) const { return (j1.pos._z < j2.pos._z); } }; ///////////////////////////////////////////////////////////////////////////// // // class Md5Skeleton implementation. // ///////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // Md5Skeleton::Md5Skeleton // // Constructor. Load skeleton data from a <.md5mesh> file. // -------------------------------------------------------------------------- Md5Skeleton::Md5Skeleton (std::ifstream &ifs, int numJoints) throw (Md5Exception) { string token, buffer; if (!ifs.is_open()) throw Md5Exception ("Input stream not opened!", "skeleton"); // Read all joints for (int i = 0; i < numJoints; ++i) { // NOTE: hope there isn't any comment between // two lines of joints data... Md5JointPtr j (new Md5Joint_t); ifs >> j->name; ifs >> j->parent; ifs >> token; // "(" ifs >> j->pos._x; ifs >> j->pos._y; ifs >> j->pos._z; ifs >> token; // ")" ifs >> token; // "(" ifs >> j->orient._x; ifs >> j->orient._y; ifs >> j->orient._z; ifs >> token; // ")" // Eat up rest of the line std::getline (ifs, buffer); // Compute orient quaternion's w value j->orient.computeW (); // Add joint to joints vector _joints.push_back (j); } } // -------------------------------------------------------------------------- // Md5Skeleton::~Md5Skeleton // // Destructor. Free all data allocated for skeleton's joints. // -------------------------------------------------------------------------- Md5Skeleton::~Md5Skeleton () { } // -------------------------------------------------------------------------- // Md5Skeleton::draw // // Draw skeleton's bones and joints. // -------------------------------------------------------------------------- void Md5Skeleton::draw (const Matrix4x4f &modelView, bool labelJoints) { // Draw each joint glPointSize (5.0f); glColor3f (1.0f, 0.0f, 0.0f); glBegin (GL_POINTS); for (unsigned int i = 0; i < _joints.size (); ++i) glVertex3fv (_joints[i]->pos); glEnd (); glPointSize (1.0f); // Draw each bone glColor3f (0.0f, 1.0f, 0.0f); glBegin (GL_LINES); for (unsigned int i = 0; i < _joints.size (); ++i) { if (_joints[i]->parent != -1) { glVertex3fv (_joints[ _joints[i]->parent ]->pos); glVertex3fv (_joints[i]->pos); } } glEnd (); // Label each joint /* if (labelJoints && font) { vector jointlist (_joints.size ()); // Copy joint's position and name for (unsigned int i = 0; i < _joints.size (); ++i) jointlist.push_back (*_joints[i]); // Sort joints about depth because of alpha blending std::sort (jointlist.begin (), jointlist.end (), SortByDepth ()); glActiveTexture (GL_TEXTURE0); glMatrixMode (GL_TEXTURE); glLoadIdentity (); GLfloat mat[16]; glMatrixMode (GL_MODELVIEW); glGetFloatv (GL_MODELVIEW_MATRIX, mat); glPushMatrix (); // Setup billboard matrix mat[0] = 1.0f; mat[1] = 0.0f; mat[2] = 0.0f; mat[4] = 0.0f; mat[5] = 1.0f; mat[6] = 0.0f; mat[8] = 0.0f; mat[9] = 0.0f; mat[10]= 1.0f; glLoadMatrixf (mat); glPushAttrib (GL_POLYGON_BIT); glFrontFace (GL_CCW); glPolygonMode (GL_FRONT, GL_FILL); glColor3f (1.0f, 1.0f, 1.0f); glLoadIdentity (); glScalef (0.1f, 0.1f, 0.1f); for (unsigned int i = 0; i < _joints.size (); ++i) { glPushMatrix (); // Move to joint's position glTranslatef (jointlist[i].pos._x * 10.0f, jointlist[i].pos._y * 10.0f, jointlist[i].pos._z * 10.0f); font->printText (jointlist[i].name.c_str ()); glPopMatrix(); } // GL_POLYGON_BIT glPopAttrib (); glPopMatrix (); } */ } // -------------------------------------------------------------------------- // Md5Skeleton::setNumJoints // // Reserve memory to hold numJoints joints. // -------------------------------------------------------------------------- void Md5Skeleton::setNumJoints (int numJoints) { _joints.reserve (numJoints); } // -------------------------------------------------------------------------- // Md5Skeleton::addJoint // // Add a joint to the skeleton. // -------------------------------------------------------------------------- void Md5Skeleton::addJoint (Md5Joint_t *thisJoint) { _joints.push_back (Md5JointPtr (thisJoint)); } // -------------------------------------------------------------------------- // Md5Skeleton::clone // // Dupplicate the skeleton. // -------------------------------------------------------------------------- Md5Skeleton* Md5Skeleton::clone () const { Md5Skeleton *cpy = new Md5Skeleton; cpy->setNumJoints (_joints.size ()); for (size_t i = 0; i < _joints.size (); ++i) cpy->addJoint (new Md5Joint_t (*_joints[i].get ())); return cpy; } ///////////////////////////////////////////////////////////////////////////// // // class Md5Mesh implementation. // ///////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // Md5Mesh::Md5Mesh // // Constructor. Load mesh data from a <.md5mesh> file. // -------------------------------------------------------------------------- Md5Mesh::Md5Mesh (std::ifstream &ifs) throw (Md5Exception) : _renderState (kShow), _numVerts (0), _numTris (0), _numWeights (0), _decal (NULL), _specMap (NULL), _normalMap (NULL), _heightMap (NULL) { string token, buffer; if (!ifs.is_open()) throw Md5Exception ("Input stream not opened!", "mesh"); do { // Read first token line from file ifs >> token; if (token == "shader") { ifs >> _shader; // Remove quote marks from the shader string _shader = _shader.substr (_shader.find_first_of ('\"') + 1, _shader.find_last_of ('\"') - 1); // Get mesh name from shader string _name = _shader.c_str() + _shader.find_last_of ('/') + 1; } else if (token == "numverts") { ifs >> _numVerts; _verts.reserve (_numVerts); } else if (token == "numtris") { ifs >> _numTris; _tris.reserve (_numTris); } else if (token == "numweights") { ifs >> _numWeights; _weights.reserve (_numWeights); } else if (token == "vert") { Md5VertexPtr vert (new Md5Vertex_t); // Read vertex data ifs >> token; // index ifs >> token; // "(" ifs >> vert->st[0]; ifs >> vert->st[1]; ifs >> token; // ")" ifs >> vert->startWeight; ifs >> vert->countWeight; _verts.push_back (vert); } else if (token == "tri") { Md5TrianglePtr tri (new Md5Triangle_t); // Read triangle data ifs >> token; // index ifs >> tri->index[0]; ifs >> tri->index[1]; ifs >> tri->index[2]; _tris.push_back (tri); } else if (token == "weight") { Md5WeightPtr weight (new Md5Weight_t); // Read weight data ifs >> token; // index ifs >> weight->joint; ifs >> weight->bias; ifs >> token; // "(" ifs >> weight->pos._x; ifs >> weight->pos._y; ifs >> weight->pos._z; ifs >> token; // ")" _weights.push_back (weight); } // Eat up rest of the line std::getline (ifs, buffer); } while ((token != "}") && !ifs.eof ()); // Memory allocation for vertex arrays allocVertexArrays (); // Setup texture coordinates array setupTexCoordArray (); } // -------------------------------------------------------------------------- // Md5Mesh::~Md5Mesh // // Destructor. Free all data allocated for the mesh, i.e. vertices, // triangles, weights and vertex arrays. // -------------------------------------------------------------------------- Md5Mesh::~Md5Mesh () { } // -------------------------------------------------------------------------- // Md5Mesh::setupVertexArray // // Compute vertices' position, normal and tangent. // -------------------------------------------------------------------------- void Md5Mesh::setupVertexArrays (Md5Skeleton *skel) { for (int i = 0; i < _numVerts; ++i) { Vector3f finalVertex = kZeroVectorf; Vector3f finalNormal = kZeroVectorf; Vector3f finalTangent = kZeroVectorf; // Calculate final vertex to draw with weights for (int j = 0; j < _verts[i]->countWeight; ++j) { const Md5Weight_t *pWeight = _weights[_verts[i]->startWeight + j].get (); const Md5Joint_t *pJoint = skel->joint (pWeight->joint); // Calculate transformed vertex for this weight Vector3f wv = pWeight->pos; pJoint->orient.rotate (wv); // The sum of all pWeight->bias should be 1.0 finalVertex += (pJoint->pos + wv) * pWeight->bias; // Calculate transformed normal for this weight Vector3f wn = pWeight->norm; pJoint->orient.rotate (wn); finalNormal += wn * pWeight->bias; // Calculate transformed tangent for this weight Vector3f wt = pWeight->tan; pJoint->orient.rotate (wt); finalTangent += wt * pWeight->bias; } // We can omit to normalize normal and tangent, // because they should have been already normalized // when they were computed. We can gain some time // avoiding some heavy calculus. //finalNormal.normalize (); //finalTangent.normalize (); { // Fill in the vertex arrays with the freshly vertex, normal // and tangent computed. GLfloat *vertexPointer = &_vertexArray[i * 3]; GLfloat *normalPointer = &_normalArray[i * 3]; GLfloat *tangentPointer = &_tangentArray[i * 3]; vertexPointer[0] = finalVertex._x; vertexPointer[1] = finalVertex._y; vertexPointer[2] = finalVertex._z; normalPointer[0] = finalNormal._x; normalPointer[1] = finalNormal._y; normalPointer[2] = finalNormal._z; tangentPointer[0] = finalTangent._x; tangentPointer[1] = finalTangent._y; tangentPointer[2] = finalTangent._z; } } } // -------------------------------------------------------------------------- // Md5Mesh::computeWeightNormals // // der_ton said: // // * First you have to get the bind-pose model-space normals by calculating // them from the model geometry in bind-pose. // // * Then you calculate the weight's normal (which is in bone-space) by // invert-transforming the normal by the bone-space matrix. // // * So afterwards when animating, you'll transform the weight normal with // the animated bone-space matrix and add them all up and you'll get // back your animated vertex normal. // -------------------------------------------------------------------------- void Md5Mesh::computeWeightNormals (Md5Skeleton *skel) { vector bindposeVerts (_numVerts); vector bindposeNorms (_numVerts); for (int i = 0; i < _numVerts; ++i) { // Zero out final vertex position and final vertex normal bindposeVerts[i] = kZeroVectorf; bindposeNorms[i] = kZeroVectorf; for (int j = 0; j < _verts[i]->countWeight; ++j) { const Md5Weight_t *pWeight = _weights[_verts[i]->startWeight + j].get (); const Md5Joint_t *pJoint = skel->joint (pWeight->joint); // Calculate transformed vertex for this weight Vector3f wv = pWeight->pos; pJoint->orient.rotate (wv); bindposeVerts[i] += (pJoint->pos + wv) * pWeight->bias; } } // Compute triangle normals for (int i = 0; i < _numTris; ++i) { const Md5Triangle_t *pTri = _tris[i].get (); Vector3f triNorm (-ComputeNormal (bindposeVerts[pTri->index[0]], bindposeVerts[pTri->index[1]], bindposeVerts[pTri->index[2]])); for (int j = 0; j < 3; ++j) bindposeNorms[pTri->index[j]] += triNorm; } // "Average" the surface normals, by normalizing them for (int i = 0; i < _numVerts; ++i) bindposeNorms[i].normalize (); // // At this stage we have all vertex normals computed // for the model geometry in bind-pos // // Zero out all weight normals for (int i = 0; i < _numWeights; ++i) _weights[i]->norm = kZeroVectorf; // Compute weight normals by invert-transforming the normal // by the bone-space matrix for (int i = 0; i < _numVerts; ++i) { for (int j = 0; j < _verts[i]->countWeight; ++j) { Md5Weight_t *pWeight = _weights[_verts[i]->startWeight + j].get (); const Md5Joint_t *pJoint = skel->joint (pWeight->joint); Vector3f wn = bindposeNorms[i]; // Compute inverse quaternion rotation Quaternionf invRot = Inverse (pJoint->orient); invRot.rotate (wn); pWeight->norm += wn; } } // Normalize all weight normals for (int i = 0; i < _numWeights; ++i) _weights[i]->norm.normalize (); } // -------------------------------------------------------------------------- // Md5Mesh::computeWeightTangents // // Compute per-vertex tangent vectors and then, calculate the weight // tangent. // -------------------------------------------------------------------------- void Md5Mesh::computeWeightTangents (Md5Skeleton *skel) { vector bindposeVerts (_numVerts); vector bindposeNorms (_numVerts); vector bindposeTans (_numVerts); vector sTan (_numVerts); vector tTan (_numVerts); // Zero out all weight tangents (thank you Valgrind) for (int i = 0; i < _numWeights; ++i) _weights[i]->tan = kZeroVectorf; // Compute bind-pose vertices and normals for (int i = 0; i < _numVerts; ++i) { // Zero out final vertex position, normal and tangent bindposeVerts[i] = kZeroVectorf; bindposeNorms[i] = kZeroVectorf; bindposeTans[i] = kZeroVectorf; // Zero s-tangents and t-tangents sTan[i] = kZeroVectorf; tTan[i] = kZeroVectorf; for (int j = 0; j < _verts[i]->countWeight; ++j) { const Md5Weight_t *pWeight = _weights[_verts[i]->startWeight + j].get (); const Md5Joint_t *pJoint = skel->joint (pWeight->joint); // Calculate transformed vertex for this weight Vector3f wv = pWeight->pos; pJoint->orient.rotate (wv); bindposeVerts[i] += (pJoint->pos + wv) * pWeight->bias; // Calculate transformed normal for this weight Vector3f wn = pWeight->norm; pJoint->orient.rotate (wn); bindposeNorms[i] += wn * pWeight->bias; } } // Calculate s-tangeants and t-tangeants at triangle level for (int i = 0; i < _numTris; ++i) { const Md5Triangle_t *pTri = _tris[i].get (); const Vector3f &v0 = bindposeVerts[pTri->index[0]]; const Vector3f &v1 = bindposeVerts[pTri->index[1]]; const Vector3f &v2 = bindposeVerts[pTri->index[2]]; const vec2_t &w0 = _verts[pTri->index[0]]->st; const vec2_t &w1 = _verts[pTri->index[1]]->st; const vec2_t &w2 = _verts[pTri->index[2]]->st; float x1 = v1._x - v0._x; float x2 = v2._x - v0._x; float y1 = v1._y - v0._y; float y2 = v2._y - v0._y; float z1 = v1._z - v0._z; float z2 = v2._z - v0._z; float s1 = w1[0] - w0[0]; float s2 = w2[0] - w0[0]; float t1 = w1[1] - w0[1]; float t2 = w2[1] - w0[1]; float r = (s1 * t2) - (s2 * t1); if (r == 0.0f) // Prevent division by zero r = 1.0f; float oneOverR = 1.0f / r; Vector3f sDir ((t2 * x1 - t1 * x2) * oneOverR, (t2 * y1 - t1 * y2) * oneOverR, (t2 * z1 - t1 * z2) * oneOverR); Vector3f tDir ((s1 * x2 - s2 * x1) * oneOverR, (s1 * y2 - s2 * y1) * oneOverR, (s1 * z2 - s2 * z1) * oneOverR); for (int j = 0; j < 3; ++j) { sTan[pTri->index[j]] += sDir; tTan[pTri->index[j]] += tDir; } } // Calculate vertex tangent for (int i = 0; i < _numVerts; ++i) { const Vector3f &n = bindposeNorms[i]; const Vector3f &t = sTan[i]; // Gram-Schmidt orthogonalize bindposeTans[i] = (t - n * DotProduct (n, t)); bindposeTans[i].normalize (); // Calculate handedness if (DotProduct (CrossProduct (n, t), tTan[i]) < 0.0f) bindposeTans[i] = -bindposeTans[i]; // Compute weight tangent for (int j = 0; j < _verts[i]->countWeight; ++j) { Md5Weight_t *pWeight = _weights[_verts[i]->startWeight + j].get (); const Md5Joint_t *pJoint = skel->joint (pWeight->joint); Vector3f wt = bindposeTans[i]; // Compute inverse quaternion rotation Quaternionf invRot = Inverse (pJoint->orient); invRot.rotate (wt); pWeight->tan += wt; } } // Normalize all weight tangents for (int i = 0; i < _numWeights; ++i) _weights[i]->tan.normalize (); } // -------------------------------------------------------------------------- // Md5Mesh::computeBoundingBox // // Compute mesh bounding box for a given skeleton. // -------------------------------------------------------------------------- void Md5Mesh::computeBoundingBox (Md5Skeleton *skel) { Vector3f maxValue(-99999.0f, -99999.0f, -99999.0f); Vector3f minValue( 99999.0f, 99999.0f, 99999.0f); for (int i = 0; i < _numVerts; ++i) { Vector3f finalVertex = kZeroVectorf; // Calculate final vertex to draw with weights for (int j = 0; j < _verts[i]->countWeight; ++j) { const Md5Weight_t *pWeight = _weights[_verts[i]->startWeight + j].get (); const Md5Joint_t *pJoint = skel->joint (pWeight->joint); // Calculate transformed vertex for this weight Vector3f wv = pWeight->pos; pJoint->orient.rotate (wv); // The sum of all pWeight->bias should be 1.0 finalVertex += (pJoint->pos + wv) * pWeight->bias; } if (finalVertex._x > maxValue._x) maxValue._x = finalVertex._x; if (finalVertex._x < minValue._x) minValue._x = finalVertex._x; if (finalVertex._y > maxValue._y) maxValue._y = finalVertex._y; if (finalVertex._y < minValue._y) minValue._y = finalVertex._y; if (finalVertex._z > maxValue._z) maxValue._z = finalVertex._z; if (finalVertex._z < minValue._z) minValue._z = finalVertex._z; } _boundingBox.min = minValue; _boundingBox.max = maxValue; } // -------------------------------------------------------------------------- // Md5Mesh::preRenderVertexArrays // // Pre-rendering preparation. Setup some render-path dependent stuff, // like tangent arrays for ARB programs and shaders. // -------------------------------------------------------------------------- void Md5Mesh::preRenderVertexArrays () const { switch (Md5Model::renderPath) { case R_normal: break; case R_ARBfp_diffuse: case R_ARBfp_diffuse_specular: case R_ARBfp_ds_parallax: { Md5Model::vp->use (); Md5Model::fp->use (); glEnableVertexAttribArrayARB (TANGENT_LOC); glVertexAttribPointerARB (TANGENT_LOC, 3, GL_FLOAT, GL_FALSE, 0, &_tangentArray.front ()); break; } case R_shader: { if (Md5Model::tangentLoc == -1) break; Md5Model::shader->use (); if (GLEW_VERSION_2_0) { glEnableVertexAttribArray (Md5Model::tangentLoc); glVertexAttribPointer (Md5Model::tangentLoc, 3, GL_FLOAT, GL_FALSE, 0, &_tangentArray.front ()); } else { glEnableVertexAttribArrayARB (Md5Model::tangentLoc); glVertexAttribPointerARB (Md5Model::tangentLoc, 3, GL_FLOAT, GL_FALSE, 0, &_tangentArray.front ()); } break; } } } // -------------------------------------------------------------------------- // Md5Mesh::postRenderVertexArrays // // Post-rendering operations. Render-path dependent cleaning up after // mesh rendering, like disabling tangent arrays for ARB programs and // shaders. // -------------------------------------------------------------------------- void Md5Mesh::postRenderVertexArrays () const { switch (Md5Model::renderPath) { case R_normal: break; case R_ARBfp_diffuse: case R_ARBfp_diffuse_specular: case R_ARBfp_ds_parallax: { Md5Model::vp->unuse (); Md5Model::fp->unuse (); glDisableVertexAttribArrayARB (TANGENT_LOC); break; } case R_shader: { if (Md5Model::tangentLoc == -1) break; Md5Model::shader->unuse (); if (GLEW_VERSION_2_0) glDisableVertexAttribArray (Md5Model::tangentLoc); else glDisableVertexAttribArrayARB (Md5Model::tangentLoc); break; } } } // -------------------------------------------------------------------------- // Md5Mesh::renderVertexArray // // Render mesh with vertex array. // -------------------------------------------------------------------------- void Md5Mesh::renderVertexArrays () const { // Ensable shader/program's stuff preRenderVertexArrays (); glEnableClientState (GL_VERTEX_ARRAY); glEnableClientState (GL_NORMAL_ARRAY); glEnableClientState (GL_TEXTURE_COORD_ARRAY); // Upload mesh data to OpenGL glVertexPointer (3, GL_FLOAT, 0, &_vertexArray.front ()); glNormalPointer (GL_FLOAT, 0, &_normalArray.front ()); glTexCoordPointer (2, GL_FLOAT, 0, &_texCoordArray.front ()); // Bind to mesh's textures setupTexture(_heightMap, GL_TEXTURE3); setupTexture(_normalMap, GL_TEXTURE2); setupTexture(_specMap, GL_TEXTURE1); setupTexture(_decal, GL_TEXTURE0); // Draw the mesh glDrawElements (GL_TRIANGLES, _numTris * 3, GL_UNSIGNED_INT, &_vertIndices.front ()); resetReversedTexture(_heightMap, GL_TEXTURE3); resetReversedTexture(_normalMap, GL_TEXTURE2); resetReversedTexture(_specMap, GL_TEXTURE1); resetReversedTexture(_decal, GL_TEXTURE0); glDisableClientState (GL_TEXTURE_COORD_ARRAY); glDisableClientState (GL_NORMAL_ARRAY); glDisableClientState (GL_VERTEX_ARRAY); // Disable shader/program's stuff postRenderVertexArrays (); } // -------------------------------------------------------------------------- // Md5Mesh::drawNormals // // Draw mesh's vertex normals and tangents. // -------------------------------------------------------------------------- void Md5Mesh::drawNormals () const { Vector3f thisVertex, thisNormal, thisTangent; glPushAttrib (GL_ENABLE_BIT); glDisable (GL_LIGHTING); glDisable (GL_TEXTURE_2D); // Blue glColor3f (0.0, 0.0, 1.0); // Draw normals glBegin (GL_LINES); for (int i = 0; i < _numVerts * 3; i += 3) { thisVertex._x = _vertexArray[i + 0]; thisVertex._y = _vertexArray[i + 1]; thisVertex._z = _vertexArray[i + 2]; thisNormal._x = _normalArray[i + 0]; thisNormal._y = _normalArray[i + 1]; thisNormal._z = _normalArray[i + 2]; Vector3f normVec (thisVertex + thisNormal); glVertex3fv (thisVertex); glVertex3fv (normVec); } glEnd (); // Magenta glColor3f (1.0, 0.0, 1.0); // Draw tangents glBegin (GL_LINES); for (int i = 0; i < _numVerts * 3; i += 3) { thisVertex._x = _vertexArray[i + 0]; thisVertex._y = _vertexArray[i + 1]; thisVertex._z = _vertexArray[i + 2]; thisTangent._x = _tangentArray[i + 0]; thisTangent._y = _tangentArray[i + 1]; thisTangent._z = _tangentArray[i + 2]; Vector3f tanVec (thisVertex + thisTangent); glVertex3fv (thisVertex); glVertex3fv (tanVec); } glEnd (); // GL_ENABLE_BIT glPopAttrib (); } // -------------------------------------------------------------------------- // Md5Mesh::allocVertexArrays // // Allocate memory for vertex arrays. NOTE: we need to have triangle // data first! (_tris) // -------------------------------------------------------------------------- void Md5Mesh::allocVertexArrays () { _vertexArray.reserve (_numVerts * 3); _normalArray.reserve (_numVerts * 3); _tangentArray.reserve (_numVerts * 3); _texCoordArray.reserve (_numVerts * 2); _vertIndices.reserve (_numTris * 3); // We can already initialize the vertex index array (we won't have // to do it each time we want to draw) for (int i = 0; i < _numTris; ++i) { for (int j = 0; j < 3; ++j) _vertIndices.push_back (_tris[i]->index[j]); } } // -------------------------------------------------------------------------- // Md5Mesh::setupTexCoordArray // // Compute texture coordinate array. // -------------------------------------------------------------------------- void Md5Mesh::setupTexCoordArray () { for (int i = 0, j = 0; i < _numVerts; ++i, j += 2) { // j = i * 2; _texCoordArray[j + 0] = _verts[i]->st[0]; _texCoordArray[j + 1] = _verts[i]->st[1]; } } // -------------------------------------------------------------------------- // Md5Mesh::setupTexture // // Setup texture 'tex' for the given texture unit. // -------------------------------------------------------------------------- void Md5Mesh::setupTexture(const Texture2D *tex, GLenum texUnit) const { if(!tex) { // Disable texture and return glActiveTexture (texUnit); glBindTexture (GL_TEXTURE_2D, 0); } else { tex->bind (texUnit); // Doom 3 doesn't use the OpenGL standard coordinate system // for images, i.e. image data "starts" at the upper-left // corner instead of the lower-left corner. // We must reverse the t component if the texture // has been built with OpenGL's coord. system if (tex->stdCoordSystem ()) { glMatrixMode(GL_TEXTURE); glLoadIdentity(); glScalef(1.0f, -1.0f, 1.0f); glTranslatef(0.0f, -1.0f, 0.0f); glMatrixMode(GL_MODELVIEW); } } } void Md5Mesh::resetReversedTexture(const Texture2D *tex, GLenum texUnit) const { if(tex) { tex->bind(texUnit); // Doom 3 doesn't use the OpenGL standard coordinate system // for images, i.e. image data "starts" at the upper-left // corner instead of the lower-left corner. // We must UNDO the reversing of the t component if the texture // has been built with OpenGL's coord. system if(tex->stdCoordSystem ()) { glMatrixMode(GL_TEXTURE); glLoadIdentity(); glTranslatef(0.0f, 0.0f, 0.0f); glScalef(1.0f, 1.0f, 1.0f); glMatrixMode(GL_MODELVIEW); } } } ///////////////////////////////////////////////////////////////////////////// // // class Md5Model implementation. // ///////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // Md5Model::Md5Model // // Constructor. Load MD5 mesh from file. // -------------------------------------------------------------------------- Md5Model::Md5Model (const string &filename) throw (Md5Exception) : _numJoints (0), _numMeshes (0) { // Open file std::ifstream ifs (filename.c_str(), std::ios::in); if (ifs.fail ()) throw Md5Exception ("Couldn't open md5model file: " + filename, filename); while (!ifs.eof ()) { string token, buffer; int version = 0; // Read next token ifs >> token; if (token == "//") { // This is the begining of a comment // Eat up rest of the line std::getline (ifs, buffer); } else if (token == "MD5Version") { ifs >> version; if (version != kMd5Version) throw Md5Exception ("Bad ifs version", filename); } else if (token == "numJoints") { ifs >> _numJoints; } else if (token == "numMeshes") { ifs >> _numMeshes; _meshes.reserve (_numMeshes); } else if (token == "joints") { // Base skeleton data ifs >> token; // "{" Md5Skeleton *skel = new Md5Skeleton (ifs, _numJoints); _baseSkeleton = Md5SkeletonPtr (skel); ifs >> token; // "}" } else if (token == "mesh") { ifs >> token; // "{" // Create and load a new model mesh Md5MeshPtr mesh = Md5MeshPtr (new Md5Mesh (ifs)); // Compute bounding box in bind-pose mesh->computeBoundingBox (_baseSkeleton.get ()); // Compute weight normals mesh->computeWeightNormals (_baseSkeleton.get ()); // Compute weight tangents mesh->computeWeightTangents (_baseSkeleton.get ()); _meshes.push_back (mesh); } } ifs.close (); // Compute the bounding box in bind-pose computeBindPoseBoundingBox (); } // -------------------------------------------------------------------------- // Md5Model::~Md5Model // // Destructor. Free all data allocated for the model. // -------------------------------------------------------------------------- Md5Model::~Md5Model () { } // -------------------------------------------------------------------------- // Md5Model::drawModel // // Draw each mesh of the model. // -------------------------------------------------------------------------- void Md5Model::drawModel () const { for (int i = 0; i < _numMeshes; ++i) { if (_meshes[i]->show ()) { _meshes[i]->renderVertexArrays (); if (Md5Model::bDrawNormals) _meshes[i]->drawNormals (); } } } // -------------------------------------------------------------------------- // Md5Model::prepare // // Prepare each mesh of the model for drawing, i.e. compute final vertex // positions, normals and other vertex's related data. // -------------------------------------------------------------------------- void Md5Model::prepare (Md5Skeleton *skel) { for (int i = 0; i < _numMeshes; ++i) { if (!_meshes[i]->hiden ()) { // Prepare for drawing with interpolated skeleton _meshes[i]->setupVertexArrays(skel); } } } // -------------------------------------------------------------------------- // Md5Model::addAnim // // Load a MD5 animation and add it to the animation list. Return true if // the animation has been loaded successfully. // -------------------------------------------------------------------------- bool Md5Model::addAnim (const string &filename) { Md5Animation *pAnim = new Md5Animation (filename); // Check for compatibility if (!validityCheck (pAnim)) { cerr << filename << " isn't a valid animation" << " for this model!" << endl; delete pAnim; return false; } const string name (pAnim->name ()); // If there is already an animation with same name, // delete it AnimMap::iterator itor = _animList.find (name); if (itor != _animList.end()) _animList.erase (itor); // Insert the new animation _animList.insert (AnimMap::value_type (name, Md5AnimationPtr (pAnim))); return true; } // -------------------------------------------------------------------------- // Md5Model::setMeshRenderState // Md5Model::setMeshDecalMap // Md5Model::setMeshSpecularMap // Md5Model::setMeshNormalMap // Md5Model::setMeshHeightMap // // Setup mesh's state or texture. // -------------------------------------------------------------------------- void Md5Model::setMeshRenderState (const string &name, int state) { if (Md5Mesh *mesh = getMeshByName (name)) mesh->setState (state); } void Md5Model::setMeshDecalMap (const string &name, const Texture2D *tex) { if (Md5Mesh *mesh = getMeshByName (name)) mesh->setDecalMap (tex); } void Md5Model::setMeshSpecularMap (const string &name, const Texture2D *tex) { if (Md5Mesh *mesh = getMeshByName (name)) mesh->setSpecularMap (tex); } void Md5Model::setMeshNormalMap (const string &name, const Texture2D *tex) { if (Md5Mesh *mesh = getMeshByName (name)) mesh->setNormalMap (tex); } void Md5Model::setMeshHeightMap (const string &name, const Texture2D *tex) { if (Md5Mesh *mesh = getMeshByName (name)) mesh->setHeightMap (tex); } // -------------------------------------------------------------------------- // Md5Model::anim // // Accessor. Return animation from list. // -------------------------------------------------------------------------- const Md5Animation* Md5Model::anim (const string &name) const { AnimMap::const_iterator itor = _animList.find (name); if (itor != _animList.end ()) return itor->second.get (); return NULL; } // -------------------------------------------------------------------------- // Md5Model::computeBindPoseBoundingBox // // Compute model's bounding box in bind-pose. // -------------------------------------------------------------------------- void Md5Model::computeBindPoseBoundingBox() { Vector3f maxValue(-99999.0f, -99999.0f, -99999.0f); Vector3f minValue( 99999.0f, 99999.0f, 99999.0f); // Get the min and the max from all mesh bounding boxes for (int i = 0; i < _numMeshes; ++i) { const BoundingBox_t &meshBox = _meshes[i]->boundingBox (); if (meshBox.max._x > maxValue._x) maxValue._x = meshBox.max._x; if (meshBox.min._x < minValue._x) minValue._x = meshBox.min._x; if (meshBox.max._y > maxValue._y) maxValue._y = meshBox.max._y; if (meshBox.min._y < minValue._y) minValue._y = meshBox.min._y; if (meshBox.max._z > maxValue._z) maxValue._z = meshBox.max._z; if (meshBox.min._z < minValue._z) minValue._z = meshBox.min._z; } _bindPoseBox.min = minValue; _bindPoseBox.max = maxValue; } // -------------------------------------------------------------------------- // Md5Model::validityCheck // // Check if an animation is valid for this model or not. A valid // animation must have a skeleton with the same number of joints with // model's skeleton and for each skeleton joint, name and parent Id must // match. // -------------------------------------------------------------------------- bool Md5Model::validityCheck (Md5Animation *anim) const { if (!anim) return false; if (_numJoints != anim->frame (0)->numJoints()) return false; for (int i = 0; i < _numJoints; ++i) { const Md5Joint_t *modelJoint = _baseSkeleton->joint (i); const Md5Joint_t *animJoint = anim->frame (0)->joint (i); if (modelJoint->name != animJoint->name) return false; if (modelJoint->parent != animJoint->parent) return false; } return true; } // -------------------------------------------------------------------------- // Md5Model::getMeshByName // // Get pointer to a mesh given its name. Return NULL if there is no mesh // with such a name. // -------------------------------------------------------------------------- Md5Mesh* Md5Model::getMeshByName (const string &name) const { for (int i = 0; i < _numMeshes; ++i) { if (_meshes[i]->name () == name) return _meshes[i].get (); } return NULL; } ///////////////////////////////////////////////////////////////////////////// // // class Md5Animation implementation. // ///////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // Md5Animation::Md5Animation // // Constructor. Load MD5 animation from <.md5anim> file. // -------------------------------------------------------------------------- Md5Animation::Md5Animation (const string &filename) throw (Md5Exception) : _numFrames (0), _frameRate (0) { vector jointInfos; vector baseFrame; vector animFrameData; int numJoints = 0; int numAnimatedComponents = 0; // Open file std::ifstream ifs (filename.c_str(), std::ios::in); if (ifs.fail()) throw Md5Exception ("Couldn't open md5anim file: " + filename, filename); while (!ifs.eof ()) { string token, buffer; int version = 0;; int i = 0; // Read next token ifs >> token; if (token == "//") { // This is the begining of a comment // Eat up rest of the line std::getline (ifs, buffer); } else if (token == "MD5Version") { ifs >> version; if (version != kMd5Version) throw Md5Exception ("Bad file version", filename); } else if (token == "numFrames") { ifs >> _numFrames; _skelframes.reserve (_numFrames); _bboxes.reserve (_numFrames); } else if (token == "numJoints") { ifs >> numJoints; jointInfos.reserve (numJoints); baseFrame.reserve (numJoints); } else if (token == "frameRate") { ifs >> _frameRate; } else if (token == "numAnimatedComponents") { ifs >> numAnimatedComponents; animFrameData.reserve (numAnimatedComponents); } else if (token == "hierarchy") { // Read all joint infos ifs >> token; // "{" // Read all joint infos for (i = 0; i < numJoints; ++i) { JointInfo jinfo; ifs >> jinfo.name; ifs >> jinfo.parent; ifs >> jinfo.flags.value; ifs >> jinfo.startIndex; jointInfos.push_back (jinfo); // Eat up rest of the line std::getline (ifs, buffer); } ifs >> token; // "}" } else if (token == "bounds") { ifs >> token; // "{" // Read frame bounds for (int i = 0; i < _numFrames; ++i) { BoundingBoxPtr bbox (new BoundingBox_t); ifs >> token; // "(" ifs >> bbox->min._x; ifs >> bbox->min._y; ifs >> bbox->min._z; ifs >> token; // ")" ifs >> token; // "(" ifs >> bbox->max._x; ifs >> bbox->max._y; ifs >> bbox->max._z; ifs >> token; // ")" _bboxes.push_back (bbox); } ifs >> token; // "}" } else if (token == "baseframe") { // We should have an opening bracket for the baseframe joint list ifs >> token; // "{" // Read baseframe data for (i = 0; i < numJoints; ++i) { BaseFrameJoint bfj; ifs >> token; // "(" ifs >> bfj.pos._x; ifs >> bfj.pos._y; ifs >> bfj.pos._z; ifs >> token; // ")" ifs >> token; // "(" ifs >> bfj.orient._x; ifs >> bfj.orient._y; ifs >> bfj.orient._z; ifs >> token; // ")" baseFrame.push_back (bfj); // Eat up rest of the line std::getline (ifs, buffer); } ifs >> token; // "}" } else if (token == "frame") { int frameIndex; ifs >> frameIndex; ifs >> token; // "{" animFrameData.clear (); // Read all frame data float afvalue; for (i = 0; i < numAnimatedComponents; ++i) { // NOTE about coding style: beeuuarg *vomit* ifs >> afvalue; animFrameData.push_back (afvalue); } ifs >> token; // "}" // Build skeleton for this frame buildFrameSkeleton (jointInfos, baseFrame, animFrameData); } } ifs.close (); // Extract animation's name string szfile = filename; string::size_type start = szfile.find_last_of ('/'); string::size_type end = szfile.find_last_of (".md5anim"); _name = szfile.substr (start + 1, end - start - 8); } // -------------------------------------------------------------------------- // Md5Animation::~Md5Animation // // Destructor. Free all data allocated for the animation. // -------------------------------------------------------------------------- Md5Animation::~Md5Animation () { } // -------------------------------------------------------------------------- // Md5Animation::buildFrameSkeleton // // Build a skeleton for a particular frame. The skeleton is transformed // by the given modelview matrix so that it is possible to obtain the // skeleton in absolute coordinates. // -------------------------------------------------------------------------- void Md5Animation::buildFrameSkeleton (vector &jointInfos, vector &baseFrame, vector &animFrameData) { // Allocate memory for this frame Md5SkeletonPtr skelframe (new Md5Skeleton); _skelframes.push_back (skelframe); skelframe->setNumJoints (jointInfos.size ()); // Setup all joints for this frame for (unsigned int i = 0; i < jointInfos.size (); ++i) { BaseFrameJoint *baseJoint = &baseFrame[i]; Vector3f animatedPos = baseJoint->pos; Quaternionf animatedOrient = baseJoint->orient; int j = 0; if (jointInfos[i].flags.tx) // Tx { animatedPos._x = animFrameData[jointInfos[i].startIndex + j]; ++j; } if (jointInfos[i].flags.ty) // Ty { animatedPos._y = animFrameData[jointInfos[i].startIndex + j]; ++j; } if (jointInfos[i].flags.tz) // Tz { animatedPos._z = animFrameData[jointInfos[i].startIndex + j]; ++j; } if (jointInfos[i].flags.qx) // Qx { animatedOrient._x = animFrameData[jointInfos[i].startIndex + j]; ++j; } if (jointInfos[i].flags.qy) // Qy { animatedOrient._y = animFrameData[jointInfos[i].startIndex + j]; ++j; } if (jointInfos[i].flags.qz) // Qz { animatedOrient._z = animFrameData[jointInfos[i].startIndex + j]; ++j; } // Compute orient quaternion's w value animatedOrient.computeW (); // NOTE: we assume that this joint's parent has // already been calculated, i.e. joint's ID should // never be smaller than its parent ID. Md5Joint_t *thisJoint = new Md5Joint_t; skelframe->addJoint (thisJoint); int parent = jointInfos[i].parent; thisJoint->parent = parent; thisJoint->name = jointInfos[i].name; // has parent? if (thisJoint->parent < 0) { thisJoint->pos = animatedPos; thisJoint->orient = animatedOrient; } else { const Md5Joint_t *parentJoint = skelframe->joint (parent); parentJoint->orient.rotate (animatedPos); thisJoint->pos = animatedPos + parentJoint->pos; thisJoint->orient = parentJoint->orient * animatedOrient; thisJoint->orient.normalize (); } } } // -------------------------------------------------------------------------- // Md5Animation::interpolate // // Build an interpolated skeleton given two frame indexes and an // interpolation percentage. 'out' must be non-null. // -------------------------------------------------------------------------- void Md5Animation::interpolate (int frameA, int frameB, float interp, Md5Skeleton *out) const { for (int i = 0; i < out->numJoints (); ++i) { const Md5Joint_t *pJointA = _skelframes[frameA]->joint (i); const Md5Joint_t *pJointB = _skelframes[frameB]->joint (i); Md5Joint_t *pFinalJoint = out->joint (i); pFinalJoint->parent = pJointA->parent; pFinalJoint->pos = pJointA->pos + interp * (pJointB->pos - pJointA->pos); pFinalJoint->orient = Slerp (pJointA->orient, pJointB->orient, interp); } } ///////////////////////////////////////////////////////////////////////////// // // class Md5Object implementation. // ///////////////////////////////////////////////////////////////////////////// // -------------------------------------------------------------------------- // Md5Object::Md5Object // // Constructor. // -------------------------------------------------------------------------- Md5Object::Md5Object (Md5Model *model) : _model (NULL), _animatedSkeleton (NULL), _softwareTransformation (false), _currAnim (NULL), _currFrame (0), _nextFrame (1), _last_time (0.0), _max_time (0.0), _renderFlags (kDrawModel) { setMd5Model (model); } // -------------------------------------------------------------------------- // Md5Object::~Md5Object // // Destructor. Free all data allocated for the object. // -------------------------------------------------------------------------- Md5Object::~Md5Object () { delete _animatedSkeleton; } // -------------------------------------------------------------------------- // Md5Object::setMd5Model // // Attach MD5 Model to Object. // -------------------------------------------------------------------------- void Md5Object::setMd5Model (Md5Model *model) { if (_model != model) { _model = model; // Link to the model // Delete previous skeletons because the new // model is different and its skeleton can hold // more joints. delete _animatedSkeleton; // Copy skeleton joints name _animatedSkeleton = _model->baseSkeleton ()->clone (); // Reset animation _currAnim = NULL; _currAnimName.clear (); } } // -------------------------------------------------------------------------- // Md5Object::setAnim // // Set the current animation to play. // -------------------------------------------------------------------------- void Md5Object::setAnim (const string &name) { if (_model) { // Retrieve animation from model's animation list if ((_currAnim = _model->anim (name))) { _currAnimName = _currAnim->name (); // Compute max frame time and reset _last_time _max_time = 1.0 / static_cast(_currAnim->frameRate ()); _last_time = 0.0; // Reset current and next frames _currFrame = 0; _nextFrame = (_currAnim->maxFrame () > 0) ? 1 : 0; } else { delete _animatedSkeleton; _currAnimName.clear (); // Rebuild animated skeleton with model's base skeleton _animatedSkeleton = _model->baseSkeleton ()->clone (); } } } // -------------------------------------------------------------------------- // Md5Object::animate // // Compute current and next frames for model's animation. // -------------------------------------------------------------------------- void Md5Object::animate (double dt) { // Animate only if there is an animation... if (_currAnim) { _last_time += dt; // Move to next frame? if (_last_time >= _max_time) { _currFrame++; _nextFrame++; _last_time = 0.0f; unsigned int maxFrame = _currAnim->maxFrame (); if (_currFrame > maxFrame) _currFrame = 0; if (_nextFrame > maxFrame) _nextFrame = 0; } } } // -------------------------------------------------------------------------- // Md5Object::computeBoundingBox // // Compute object's oriented bounding box. // -------------------------------------------------------------------------- void Md5Object::computeBoundingBox () { BoundingBox_t bbox; if (_currAnim) { // Interpolate frames' bounding box in order // to get animated AABB in object space const BoundingBox_t *boxA, *boxB; boxA = _currAnim->frameBounds (_currFrame); boxB = _currAnim->frameBounds (_nextFrame); float interp = _last_time * _currAnim->frameRate (); bbox.min = boxA->min + (boxB->min - boxA->min) * interp; bbox.max = boxA->max + (boxB->max - boxA->max) * interp; } else { // Get bind-pose model's bouding box bbox = _model->bindPoseBoundingBox (); } // Compute oriented bounding box _bbox.world = _modelView; _bbox.center = Vector3f ((bbox.max._x + bbox.min._x) * 0.5f, (bbox.max._y + bbox.min._y) * 0.5f, (bbox.max._z + bbox.min._z) * 0.5f); _bbox.extent = Vector3f (bbox.max._x - _bbox.center._x, bbox.max._y - _bbox.center._y, bbox.max._z - _bbox.center._z); } // -------------------------------------------------------------------------- // Md5Object::prepare // // Prepare object to be drawn. Interpolate skeleton frames if the // object is animated, or copy model's base skeleton to current // skeleton if not. // Apply model view transformation if asked. // -------------------------------------------------------------------------- void Md5Object::prepare(bool softwareTransformation) { _softwareTransformation = softwareTransformation; if (_renderFlags & kDrawModel) { if (_currAnim) { // Interpolate current and next frame skeletons float interp = _last_time * _currAnim->frameRate (); _currAnim->interpolate (_currFrame, _nextFrame, interp, _animatedSkeleton); } else { // If there is no animated skeleton, fall to // model's base skeleton delete _animatedSkeleton; _animatedSkeleton = _model->baseSkeleton()->clone (); } if (_softwareTransformation || _renderFlags & kDrawJointLabels) { // Force software transformation if joint labels have // to be drawn _softwareTransformation = true; Quaternionf rot; rot.fromMatrix (_modelView); // Applly Model-View transformation for each joint for (int i = 0; i < _animatedSkeleton->numJoints (); ++i) { Md5Joint_t *thisJoint = _animatedSkeleton->joint (i); thisJoint->pos = _modelView * thisJoint->pos; thisJoint->orient = rot * thisJoint->orient; } } // Setup vertex arrays _model->prepare (_animatedSkeleton); } } // -------------------------------------------------------------------------- // Md5Object::render // // Draw the object. // -------------------------------------------------------------------------- void Md5Object::render () const { glPushMatrix (); //glTranslatef( 0.0f, -60.0f, 0.0f ); //glRotatef( -90.0, 1.0, 0.0, 0.0 ); //glRotatef( -90.0, 0.0, 0.0, 1.0 ); //glTranslatef( 0.0f, -60.0f, 0.0f ); //!glRotatef( -20.0, 1.0, 0.0, 0.0 ); //!glRotatef( -20.0, 0.0, 1.0, 0.0 ); //glRotatef( 50.0, 1.0, 0.0, 0.0 ); //glTranslatef( 5.0f, -2.0f, -3.0f ); //!glTranslatef(-1.4f, -1.4f, -7.5f); //glRotatef( 90.0, 0.0, 0.0, 1.0 ); //glRotatef( -25.0, 0.0, 1.0, 0.0 ); //glRotatef( -20.0, 1.0, 0.0, 0.0 ); //glScalef(1.0/20.0f, 1.0/20.0f, 1.0f); if (!_softwareTransformation) { glMultMatrixf (_modelView._m); } glPushAttrib (GL_POLYGON_BIT | GL_ENABLE_BIT); glFrontFace (GL_CW); if (_renderFlags & kDrawModel) { _model->drawModel (); } if (_renderFlags & kDrawSkeleton) { glDisable (GL_TEXTURE_2D); glDisable (GL_LIGHTING); _animatedSkeleton->draw (_modelView,_renderFlags & kDrawJointLabels); } // GL_POLYGON_BIT | GL_ENABLE_BIT glPopAttrib (); glPopMatrix (); } }}} //end namespace