mirror of
https://github.com/glest/glest-source.git
synced 2025-09-25 15:09:03 +02:00
1955 lines
50 KiB
C++
1955 lines
50 KiB
C++
// ==============================================================
|
|
// 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) <year> <copyright holders>
|
|
//
|
|
// 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 <windows.h>
|
|
#endif // _WIN32
|
|
|
|
#include <GL/glew.h>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
#include <algorithm>
|
|
|
|
#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<Md5Joint_t, Md5Joint_t, bool>
|
|
{
|
|
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<Md5Joint_t> 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<Vector3f> bindposeVerts (_numVerts);
|
|
vector<Vector3f> 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<Vector3f> bindposeVerts (_numVerts);
|
|
vector<Vector3f> bindposeNorms (_numVerts);
|
|
vector<Vector3f> bindposeTans (_numVerts);
|
|
|
|
vector<Vector3f> sTan (_numVerts);
|
|
vector<Vector3f> 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<JointInfo> jointInfos;
|
|
vector<BaseFrameJoint> baseFrame;
|
|
vector<float> 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<JointInfo> &jointInfos,
|
|
vector<BaseFrameJoint> &baseFrame,
|
|
vector<float> &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<double>(_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
|