Original Post
I've been playing with MD5 models for the last couple days and finally have them somewhat working (minus animations). I'm having a problem with texturing though. I'm only using the _d.tga texture (working with the Doom3 imp model for testing) and it's coming out very odd. Things like the eyes and the teeth ending up on the model's arms. This is the code I've got for loading the model: MD5Model.h MD5Model.cc I know it's very unomptimized and ugly, I just want to get it working right now before I got and make a real mess. I'm pretty sure my .tga loader is working because if I skin a quad I get exactly the same image as if I opened it up in gimp. I'm sure it's something with how I'm loading the models or rendering them. I'm new to loading models and such, so I'm not sure where to start looking. Thanks in advance for any suggestions.
/*
====================
File: MD5Model.h
Author: Shane Lillie
Description: MD5 mesh module header.
====================
*/
#if !defined __MD5MODEL_H__
#define __MD5MODEL_H__
#if _MSC_VER >= 1000
#pragma once
#endif
#include <string>
#include "EnVector.h"
#include "EnQuaternion.h"
/*
struct MD5Joint
*/
struct MD5Joint
{
std::string name;
MD5Joint* parent;
EnVector3<GLdouble> position;
EnQuaternion<GLdouble> orientation;
MD5Joint(const std::string& n, MD5Joint* parent, const EnVector3<GLdouble>& pv, const EnVector3<GLdouble>& ov);
};
/*
struct MD5Weight
*/
struct MD5Weight
{
MD5Joint* joint;
GLdouble weight;
EnVector3<GLdouble> position;
MD5Weight(MD5Joint* j, GLdouble w, const EnVector3<GLdouble>& pv);
};
/*
struct MD5Vertex
*/
struct MD5Vertex
{
GLdouble texture_u, texture_v;
int weight_start, weight_count;
EnVector3<GLdouble> position;
MD5Vertex(GLdouble u, GLdouble v, int ws, int wc);
};
/*
struct MD5Triangle
*/
struct MD5Triangle
{
MD5Vertex *v1, *v2, *v3;
MD5Triangle(MD5Vertex* a, MD5Vertex* b, MD5Vertex* c);
};
/*
class MD5Mesh
*/
class MD5Mesh
{
public:
MD5Mesh();
~MD5Mesh();
public:
void render() const;
void calculate_vertex_positions();
//private:
public:
std::string m_shader;
size_t m_vcount;
MD5Vertex** m_vertices;
size_t m_tcount;
MD5Triangle** m_triangles;
size_t m_wcount;
MD5Weight** m_weights;
};
/*
class MD5Model
*/
class MD5Model
{
private:
enum Token
{
// keywords
VERSION,
COMMANDLINE,
NUM_JOINTS,
NUM_MESHES,
JOINTS,
MESH,
SHADER,
NUM_VERTS,
VERT,
NUM_TRIS,
TRI,
NUM_WEIGHTS,
WEIGHT,
// delimiters
OPEN_PAREN,
CLOSE_PAREN,
OPEN_BRACE,
CLOSE_BRACE,
// end of file
END,
// scan error
ERROR
};
public:
MD5Model();
~MD5Model();
public:
bool load(const std::string& filename);
void unload();
void render() const;
public:
bool loaded() const
{
return !m_filename.empty();
}
int version() const
{
return m_version;
}
const std::string& commandline() const
{
return m_commandline;
}
const std::string& filename() const
{
return m_filename;
}
private:
char advance(char** ptr) const;
Token lex(char** ptr) const;
void skip_whitespace(char** ptr) const;
void skip_single_comment(char** ptr) const;
bool number_literal(char** ptr, double& value) const;
Token keyword(char** ptr) const;
bool string_literal(char** ptr, std::string& value) const;
bool scan_version(char** ptr);
bool scan_commandline(char** ptr);
int scan_num_joints(char** ptr);
int scan_num_meshes(char** ptr);
bool scan_joints(char** ptr, int count);
bool scan_meshes(char** ptr, int count);
private:
int m_version;
std::string m_commandline;
size_t m_jcount;
MD5Joint** m_joints;
size_t m_mcount;
MD5Mesh** m_meshes;
std::string m_filename;
private:
MD5Model(const MD5Model& md5model);
MD5Model& operator=(const MD5Model& rhs);
};
#endif
/*
====================
File: MD5Model.cc
Author: Shane Lillie
Description: MD5 mesh module source.
====================
*/
#include "EnUtilities.h"
#include <cassert>
#include <cstdlib>
#include <memory>
#include <fstream>
#include <iostream>
#include <GL/gl.h>
#include "EnAutoArray.h"
#include "MD5Model.h"
/*
Format description from:
http://www.doom3world.org/phpbb2/viewtopic.php?t=2884
http://www.trevorwilkin.com/gamesprogramming/files/direct3d/MD5%20Spec.txt
*/
/*
* MD5Joint methods
*
*/
MD5Joint::MD5Joint(const std::string& n, MD5Joint* p, const EnVector3<GLdouble>& pv, const EnVector3<GLdouble>& ov)
: name(n), parent(p), position(pv), orientation(ov)
{
}
/*
* MD5Weight methods
*
*/
MD5Weight::MD5Weight(MD5Joint* j, GLdouble w, const EnVector3<GLdouble>& pv)
: joint(j), weight(w), position(pv)
{
}
/*
* MD5Vertex methods
*
*/
MD5Vertex::MD5Vertex(GLdouble u, GLdouble v, int ws, int wc)
: texture_u(u), texture_v(v), weight_start(ws), weight_count(wc)
{
}
/*
* MD5Triangle methods
*
*/
MD5Triangle::MD5Triangle(MD5Vertex* a, MD5Vertex* b, MD5Vertex* c)
: v1(a), v2(b), v3(c)
{
}
/*
* MD5Model methods
*
*/
MD5Mesh::MD5Mesh()
: m_vcount(0), m_vertices(NULL),
m_tcount(0), m_triangles(NULL),
m_wcount(0), m_weights(NULL)
{
}
void MD5Mesh::render() const
{
MD5Triangle* triangle;
MD5Vertex* vertex;
glBegin(GL_TRIANGLES);
for(size_t i=0; i<m_tcount; ++i) {
triangle = *(m_triangles + i);
vertex = triangle->v1;
glTexCoord2f(vertex->texture_u, vertex->texture_v);
glVertex3f(vertex->position.x(), vertex->position.y(), vertex->position.z());
vertex = triangle->v2;
glTexCoord2f(vertex->texture_u, vertex->texture_v);
glVertex3f(vertex->position.x(), vertex->position.y(), vertex->position.z());
vertex = triangle->v3;
glTexCoord2f(vertex->texture_u, vertex->texture_v);
glVertex3f(vertex->position.x(), vertex->position.y(), vertex->position.z());
}
glEnd();
}
void MD5Mesh::calculate_vertex_positions()
{
EnVector3<GLdouble> position;
MD5Vertex* vertex;
MD5Weight* weight;
MD5Joint* joint;
for(size_t i=0; i<m_vcount; ++i) {
vertex = *(m_vertices + i);
vertex->position.clear();
for(int i=0; i<vertex->weight_count; ++i) {
weight = m_weights[vertex->weight_start + i];
joint = weight->joint;
position = joint->orientation.rotate(weight->position);
vertex->position += (position + joint->position) * weight->weight;
}
}
}
MD5Mesh::~MD5Mesh()
{
if(m_vertices) {
for(size_t i=0; i<m_vcount; ++i)
if(m_vertices) delete m_vertices;
delete[] m_vertices;
}
if(m_triangles) {
for(size_t i=0; i<m_tcount; ++i)
if(m_triangles) delete m_triangles;
delete[] m_triangles;
}
if(m_weights) {
for(size_t i=0; i<m_wcount; ++i)
if(m_weights) delete m_weights;
delete[] m_weights;
}
}
/*
* MD5Model methods
*
*/
MD5Model::MD5Model()
: m_version(0), m_jcount(0), m_joints(NULL),
m_mcount(0), m_meshes(NULL)
{
}
MD5Model::~MD5Model()
{
unload();
}
bool MD5Model::load(const std::string& filename)
{
unload();
// open the file
std::ifstream infile(filename.c_str());
if(!infile) return false;
// get the file length
infile.seekg(0, std::ios::end);
size_t filelen = infile.tellg();
infile.seekg(0, std::ios::beg);
// read in the file data
char *buffer = new char[filelen+1], *ptr = buffer;
infile.read(buffer, filelen);
buffer[filelen] = '\0';
infile.close();
// put the buffer into an auto array
EnAutoArray<char> abuffer(buffer);
// version first
if(!scan_version(&ptr))
return false;
// then commandline
if(!scan_commandline(&ptr))
return false;
// then num joints
m_jcount = scan_num_joints(&ptr);
if(m_jcount < 0) return false;
m_joints = new MD5Joint*[m_jcount];
ZeroMemory(m_joints, m_jcount * sizeof(MD5Joint*));
// then num meshes
m_mcount = scan_num_meshes(&ptr);
if(m_mcount < 0) return false;
m_meshes = new MD5Mesh*[m_mcount];
ZeroMemory(m_meshes, m_mcount * sizeof(MD5Mesh*));
// then joints
if(!scan_joints(&ptr, m_jcount))
return false;
// finally, the meshes
if(!scan_meshes(&ptr, m_mcount))
return false;
m_filename = filename;
return true;
}
void MD5Model::unload()
{
m_version = 0;
m_commandline.erase();
if(m_joints) {
for(size_t i=0; i<m_jcount; ++i)
if(m_joints) delete m_joints;
delete[] m_joints;
}
m_joints = NULL;
m_jcount = 0;
if(m_meshes) {
for(size_t i=0; i<m_mcount; ++i)
if(m_meshes) delete m_meshes;
delete[] m_meshes;
}
m_meshes = NULL;
m_mcount = 0;
m_filename.erase();
}
void MD5Model::render() const
{
if(!loaded())
return;
#if 0
// draw the skeleton
MD5Joint* joint;
glBegin(GL_LINES);
for(size_t i=0; i<m_jcount; ++i) {
joint = *(m_joints + i);
// need a parent
if(!joint->parent)
continue;
glVertex3f(joint->parent->position.x(), joint->parent->position.y(), joint->parent->position.z());
glVertex3f(joint->position.x(), joint->position.y(), joint->position.z());
}
glEnd();
#endif
#if 1
// draw the meshes
MD5Mesh* mesh;
for(size_t i=0; i<m_mcount; ++i) {
mesh = *(m_meshes + i);
mesh->render();
}
#endif
}
char MD5Model::advance(char** ptr) const
{
char ret = **ptr;
++(*ptr);
return ret;
}
MD5Model::Token MD5Model::lex(char** ptr) const
{
skip_whitespace(ptr);
char ch = advance(ptr);
switch(ch)
{
case '\0': return END;
case '(': return OPEN_PAREN;
case ')': return CLOSE_PAREN;
case '{': return OPEN_BRACE;
case '}': return CLOSE_BRACE;
default:
if(isalpha(ch)) {
--(*ptr);
return keyword(ptr);
}
}
return ERROR;
}
void MD5Model::skip_whitespace(char** ptr) const
{
// skip whitespace
char ch = advance(ptr);
while(ch && isspace(ch))
ch = advance(ptr);
// skip comments
if(ch == '/') {
ch = advance(ptr);
if(ch == '/') {
skip_single_comment(ptr);
// could be more whitespace
return skip_whitespace(ptr);
}
--(*ptr);
}
--(*ptr);
}
void MD5Model::skip_single_comment(char** ptr) const
{
// skip to newline
// this leaves the newline
// as the current pointer
char ch = advance(ptr);
while(ch && ch != '\n')
ch = advance(ptr);
}
bool MD5Model::number_literal(char** ptr, double& value) const
{
skip_whitespace(ptr);
std::string scratch;
char ch = advance(ptr);
while(ch && (isdigit(ch) || ch == '-' || ch == '.')) {
scratch += ch;
ch = advance(ptr);
}
--(*ptr);
char* end;
value = strtod(scratch.c_str(), &end);
return !(*end);
}
MD5Model::Token MD5Model::keyword(char** ptr) const
{
std::string scratch;
char ch = advance(ptr);
while(ch && isalpha(ch) || isdigit(ch)) {
scratch += ch;
ch = advance(ptr);
}
--(*ptr);
if(scratch == "MD5Version")
return VERSION;
else if(scratch == "commandline")
return COMMANDLINE;
else if(scratch == "numJoints")
return NUM_JOINTS;
else if(scratch == "numMeshes")
return NUM_MESHES;
else if(scratch == "joints")
return JOINTS;
else if(scratch == "mesh")
return MESH;
else if(scratch == "shader")
return SHADER;
else if(scratch == "numverts")
return NUM_VERTS;
else if(scratch == "vert")
return VERT;
else if(scratch == "numtris")
return NUM_TRIS;
else if(scratch == "tri")
return TRI;
else if(scratch == "numweights")
return NUM_WEIGHTS;
else if(scratch == "weight")
return WEIGHT;
return ERROR;
}
bool MD5Model::string_literal(char** ptr, std::string& value) const
{
skip_whitespace(ptr);
char ch = advance(ptr);
if(ch != '"') return false;
value.erase();
ch = advance(ptr);
while(ch && ch != '"' && ch != '\n') {
value += ch;
ch = advance(ptr);
}
return ch && ch != '\n';
}
bool MD5Model::scan_version(char** ptr)
{
Token token = lex(ptr);
if(token != VERSION)
return false;
double dvalue;
if(!number_literal(ptr, dvalue))
return false;
// only version 10 is supported
m_version = static_cast<int>(dvalue);
return m_version == 10;
}
bool MD5Model::scan_commandline(char** ptr)
{
Token token = lex(ptr);
if(token != COMMANDLINE)
return false;
return string_literal(ptr, m_commandline);
}
int MD5Model::scan_num_joints(char** ptr)
{
Token token = lex(ptr);
if(token != NUM_JOINTS)
return -1;
double dvalue;
if(!number_literal(ptr, dvalue))
return -1;
return static_cast<int>(dvalue);
}
int MD5Model::scan_num_meshes(char** ptr)
{
Token token = lex(ptr);
if(token != NUM_MESHES)
return -1;
double dvalue;
if(!number_literal(ptr, dvalue))
return -1;
return static_cast<int>(dvalue);
}
bool MD5Model::scan_joints(char** ptr, int count)
{
Token token = lex(ptr);
if(token != JOINTS)
return false;
token = lex(ptr);
if(token != OPEN_BRACE)
return false;
std::string name;
int parent;
GLdouble dvalue;
EnVector3<GLdouble> position, orientation;
for(int i=0; i<count; ++i) {
// joint name
if(!string_literal(ptr, name))
return false;
// joint parent
if(!number_literal(ptr, dvalue))
return false;
parent = static_cast<int>(dvalue);
token = lex(ptr);
if(token != OPEN_PAREN)
return false;
// position.x
if(!number_literal(ptr, dvalue))
return false;
position.x(dvalue);
// position.y
if(!number_literal(ptr, dvalue))
return false;
position.y(dvalue);
// position.z
if(!number_literal(ptr, dvalue))
return false;
position.z(dvalue);
token = lex(ptr);
if(token != CLOSE_PAREN)
return false;
token = lex(ptr);
if(token != OPEN_PAREN)
return false;
// orientation.x
if(!number_literal(ptr, dvalue))
return false;
orientation.x(dvalue);
// orientation.y
if(!number_literal(ptr, dvalue))
return false;
orientation.y(dvalue);
// orientation.z
if(!number_literal(ptr, dvalue))
return false;
orientation.z(dvalue);
token = lex(ptr);
if(token != CLOSE_PAREN)
return false;
// add the joint
m_joints = new MD5Joint(name, parent < 0 ? 0 : *(m_joints + parent), position, orientation);
}
token = lex(ptr);
return token == CLOSE_BRACE;
}
bool MD5Model::scan_meshes(char** ptr, int count)
{
MD5Mesh* mesh;
std::string shader;
size_t vcount, tcount, wcount;
MD5Vertex** vertices;
MD5Triangle** triangles;
MD5Weight** weights;
double dvalue;
EnVector3<GLdouble> pv;
GLdouble u, v, w;
int ws, wc, a, b, c, ji;
int index;
for(int i=0; i<count; ++i) {
Token token = lex(ptr);
if(token != MESH)
return false;
token = lex(ptr);
if(token != OPEN_BRACE)
return false;
token = lex(ptr);
if(token != SHADER)
return false;
if(!string_literal(ptr, shader))
return false;
token = lex(ptr);
if(token != NUM_VERTS)
return false;
if(!number_literal(ptr, dvalue))
return false;
vcount = static_cast<int>(dvalue);
vertices = new MD5Vertex*[vcount];
ZeroMemory(vertices, vcount * sizeof(MD5Vertex*));
// read the vertices
for(size_t j=0; j<vcount; ++j) {
token = lex(ptr);
if(token != VERT)
return false;
// vertex index
if(!number_literal(ptr, dvalue))
return false;
index = static_cast<int>(dvalue);
token = lex(ptr);
if(token != OPEN_PAREN)
return false;
// texture u
if(!number_literal(ptr, u))
return false;
// texture v
if(!number_literal(ptr, v))
return false;
token = lex(ptr);
if(token != CLOSE_PAREN)
return false;
// start weight
if(!number_literal(ptr, dvalue))
return false;
ws = static_cast<int>(dvalue);
// weight count
if(!number_literal(ptr, dvalue))
return false;
wc = static_cast<int>(dvalue);
// add the vertex
vertices[index] = new MD5Vertex(u, v, ws, wc);
}
token = lex(ptr);
if(token != NUM_TRIS)
return false;
if(!number_literal(ptr, dvalue))
return false;
tcount = static_cast<int>(dvalue);
triangles = new MD5Triangle*[tcount];
ZeroMemory(triangles, tcount * sizeof(MD5Triangle*));
// read the triangles
for(size_t j=0; j<tcount; ++j) {
token = lex(ptr);
if(token != TRI)
return false;
// triangle index
if(!number_literal(ptr, dvalue))
return false;
index = static_cast<int>(dvalue);
// vertex index a
if(!number_literal(ptr, dvalue))
return false;
a = static_cast<int>(dvalue);
// vertex index b
if(!number_literal(ptr, dvalue))
return false;
b = static_cast<int>(dvalue);
// vertex index c
if(!number_literal(ptr, dvalue))
return false;
c = static_cast<int>(dvalue);
triangles[index] = new MD5Triangle(*(vertices + a), *(vertices + b), *(vertices + c));
}
token = lex(ptr);
if(token != NUM_WEIGHTS)
return false;
if(!number_literal(ptr, dvalue))
return false;
wcount = static_cast<int>(dvalue);
weights = new MD5Weight*[wcount];
ZeroMemory(weights, wcount * sizeof(MD5Weight*));
// read the weights
for(size_t j=0; j<wcount; ++j) {
token = lex(ptr);
if(token != WEIGHT)
return false;
// weight index
if(!number_literal(ptr, dvalue))
return false;
index = static_cast<int>(dvalue);
// joint index
if(!number_literal(ptr, dvalue))
return false;
ji = static_cast<int>(dvalue);
// weight
if(!number_literal(ptr, w))
return false;
token = lex(ptr);
if(token != OPEN_PAREN)
return false;
// position.x
if(!number_literal(ptr, dvalue))
return false;
pv.x(dvalue);
// position.y
if(!number_literal(ptr, dvalue))
return false;
pv.y(dvalue);
// position.z
if(!number_literal(ptr, dvalue))
return false;
pv.z(dvalue);
token = lex(ptr);
if(token != CLOSE_PAREN)
return false;
weights[index] = new MD5Weight(*(m_joints + ji), w, pv);
}
token = lex(ptr);
if(token != CLOSE_BRACE)
return false;
// add the mesh
mesh = new MD5Mesh;
mesh->m_shader = shader;
mesh->m_vcount = vcount;
mesh->m_vertices = vertices;
mesh->m_tcount = tcount;
mesh->m_triangles = triangles;
mesh->m_wcount = wcount;
mesh->m_weights = weights;
mesh->calculate_vertex_positions();
m_meshes = mesh;
}
return true;
}