//Load the texture coordinates
input.seekg(texCoordOffset, ios_base::beg);
model->texCoords = new MD2TexCoord[numTexCoords];
for(int i = 0; i < numTexCoords; i++) {
MD2TexCoord* texCoord = model->texCoords + i;
texCoord->texCoordX = (float)readShort(input) / textureWidth;
texCoord->texCoordY = 1 - (float)readShort(input) / textureHeight;
}
接下来,我们载入纹理坐标。每个纹理坐标用两个short表示。为了能从每个short获得合适的float,我们需要除以在文件开头找到的纹理的 长和宽。对于y轴坐标,需要在纹理前面加一个负号,因为md2文件的y轴是从纹理的顶部向下计算的,而OpenGL是从纹理的底部开始向上计算的。
//Load the triangles
input.seekg(triangleOffset, ios_base::beg);
model->triangles = new MD2Triangle[numTriangles];
model->numTriangles = numTriangles;
for(int i = 0; i < numTriangles; i++) {
MD2Triangle* triangle = model->triangles + i;
for(int j = 0; j < 3; j++) {
triangle->vertices[j] = readUShort(input);
}
for(int j = 0; j < 3; j++) {
triangle->texCoords[j] = readUShort(input);
}
}
现在,我们载入三角形,这些其实只是一堆顶点和纹理坐标的索引号。
//Load the frames
input.seekg(frameOffset, ios_base::beg);
model->frames = new MD2Frame[numFrames];
model->numFrames = numFrames;
for(int i = 0; i < numFrames; i++) {
MD2Frame* frame = model->frames + i;
frame->vertices = new MD2Vertex[numVertices];
Vec3f scale = readVec3f(input);
Vec3f translation = readVec3f(input);
input.read(frame->name, 16);
for(int j = 0; j < numVertices; j++) {
MD2Vertex* vertex = frame->vertices + j;
input.read(buffer, 3);
Vec3f v((unsigned char)buffer[0],
(unsigned char)buffer[1],
(unsigned char)buffer[2]);
vertex->pos = translation + Vec3f(scale[0] * v[0],
scale[1] * v[1],
scale[2] * v[2]);
input.read(buffer, 1);
int normalIndex = (int)((unsigned char)buffer[0]);
vertex->normal = Vec3f(NORMALS[3 * normalIndex],
NORMALS[3 * normalIndex + 1],
NORMALS[3 * normalIndex + 2]);
}
}
现在我们载入帧。每个帧以6个浮点数开始,表明了变换和伸缩顶点的数组。然后的16个字节表示帧的名字。接下来是顶点,对于每个顶点,有两个无符号 型char表示位置,我们可以通过伸缩和变换将其变为浮点数。然后有一个无符号char表示法向量,这是我们先前看到的NORMALS数组的索引。
model->startFrame = 0;
model->endFrame = numFrames - 1;
return model;
}
最后,设置开始和结束帧,并返回模型。
void MD2Model::setAnimation(const char* name) {
/* The names of frames normally begin with the name of the animation in
* which they are, e.g. "run", and are followed by a non-alphabetical
* character. Normally, they indicate their frame number in the animation,
* e.g. "run_1", "run_2", etc.
*/
bool found = false;
for(int i = 0; i < numFrames; i++) {
MD2Frame* frame = frames + i;
if (strlen(frame->name) > strlen(name) &&
strncmp(frame->name, name, strlen(name)) == 0 &&
!isalpha(frame->name[strlen(name)])) {
if (!found) {
found = true;
startFrame = i;
}
else {
endFrame = i;
}
}
else if (found) {
break;
}
}
}
这个函数找到开始和结束帧,以使用不同帧的名字来指示动画,这注释里有说明。
void MD2Model::advance(float dt) {
if (dt < 0) {
return;
}
time += dt;
if (time < 1000000000) {
time -= (int)time;
}
else {
time = 0;
}
}
现在,我们有一个播放动画的方法,就是增大我们的time值。为了保证这个值介于0到1,我们使用-=(int)time(除非time真的非常大,这样将time转换为int就会出现问题。)
void MD2Model::draw() {
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
这里是我们绘制3D模型的代码。从告诉OpenGL纹理和想使用的纹理映射开始。
//Figure out the two frames between which we are interpolating
int frameIndex1 = (int)(time * (endFrame - startFrame + 1)) + startFrame;
if (frameIndex1 > endFrame) {
frameIndex1 = startFrame;
}
int frameIndex2;
if (frameIndex1 < endFrame) {
frameIndex2 = frameIndex1 + 1;
}
else {
frameIndex2 = startFrame;
}
MD2Frame* frame1 = frames + frameIndex1;
MD2Frame* frame2 = frames + frameIndex2;
现在使用time值,计算出那两个帧中间需要插值。
//Figure out the two frames between which we are interpolating
int frameIndex1 = (int)(time * (endFrame - startFrame + 1)) + startFrame;
if (frameIndex1 > endFrame) {
frameIndex1 = startFrame;
}
int frameIndex2;
if (frameIndex1 < endFrame) {
frameIndex2 = frameIndex1 + 1;
}
else {
frameIndex2 = startFrame;
}
MD2Frame* frame1 = frames + frameIndex1;
MD2Frame* frame2 = frames + frameIndex2;
现在计算出我们在两个帧中的那个部分。0表示我们在第一个帧,1表示在第二个帧,0.5表示在中间。
//Draw the model as an interpolation between the two frames
glBegin(GL_TRIANGLES);
for(int i = 0; i < numTriangles; i++) {
MD2Triangle* triangle = triangles + i;
for(int j = 0; j < 3; j++) {
MD2Vertex* v1 = frame1->vertices + triangle->vertices[j];
MD2Vertex* v2 = frame2->vertices + triangle->vertices[j];
Vec3f pos = v1->pos * (1 - frac) + v2->pos * frac;
现在我们遍历每个三角形,每个顶点,他们的位置取这两个帧的插值。
Vec3f normal = v1->normal * (1 - frac) + v2->normal * frac;
if (normal[0] == 0 && normal[1] == 0 && normal[2] == 0) {
normal = Vec3f(0, 0, 1);
}
glNormal3f(normal[0], normal[1], normal[2]);
对于每个法向量做相同事情。如果平均下来为0向量,我们将之改为一个任意的向量,因为0向量不表示任何方向,因此不能作为一个法向量。实际上有一个更好的办法去两个方向的军制,但是我们采用线性平均,因为这比较简单。
MD2TexCoord* texCoord = texCoords + triangle->texCoords[j];
glTexCoord2f(texCoord->texCoordX, texCoord->texCoordY);
glVertex3f(pos[0], pos[1], pos[2]);
}
}
glEnd();
现在只需要找到合适的纹理坐标,并调用glTexCoord2f和glVertex3f函数。