gzdoom/src/gl/models/gl_models_md2.cpp

546 lines
15 KiB
C++
Raw Normal View History

/*
** gl_models.cpp
**
** MD2/DMD model format code
**
**---------------------------------------------------------------------------
** Copyright 2005 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
** 4. When not used as part of GZDoom or a GZDoom derivative, this code will be
** covered by the terms of the GNU Lesser General Public License as published
** by the Free Software Foundation; either version 2.1 of the License, or (at
** your option) any later version.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "gl/system/gl_system.h"
#include "w_wad.h"
#include "cmdlib.h"
#include "sc_man.h"
#include "m_crc32.h"
#include "gl/renderer/gl_renderstate.h"
#include "gl/scene/gl_drawinfo.h"
#include "gl/models/gl_models.h"
#include "gl/textures/gl_material.h"
#include "gl/shaders/gl_shader.h"
static float avertexnormals[NUMVERTEXNORMALS][3] = {
#include "tab_anorms.h"
};
//===========================================================================
//
// UnpackVector
// Packed: pppppppy yyyyyyyy. Yaw is on the XY plane.
//
//===========================================================================
static void UnpackVector(unsigned short packed, float vec[3])
{
float yaw = (packed & 511) / 512.0f * 2 * PI;
float pitch = ((packed >> 9) / 127.0f - 0.5f) * PI;
float cosp = (float) cos(pitch);
vec[VX] = (float) cos(yaw) * cosp;
vec[VY] = (float) sin(yaw) * cosp;
vec[VZ] = (float) sin(pitch);
}
//===========================================================================
//
// FDMDModel::Load
//
//===========================================================================
bool FDMDModel::Load(const char * path, int, const char * buffer, int length)
{
struct dmd_chunk_t
{
int type;
int length; // Next chunk follows...
};
#pragma pack(1)
struct dmd_packedVertex_t
{
byte vertex[3];
unsigned short normal; // Yaw and pitch.
};
struct dmd_packedFrame_t
{
float scale[3];
float translate[3];
char name[16];
dmd_packedVertex_t vertices[1];
} ;
#pragma pack()
// Chunk types.
enum
{
DMC_END, // Must be the last chunk.
DMC_INFO // Required; will be expected to exist.
};
dmd_chunk_t * chunk = (dmd_chunk_t*)(buffer+12);
char *temp;
ModelFrame *frame;
int i, k, c;
FTriangle *triangles[MAX_LODS];
int axis[3] = { VX, VY, VZ };
int fileoffset=12+sizeof(dmd_chunk_t);
chunk->type = LittleLong(chunk->type);
while(chunk->type != DMC_END)
{
switch (chunk->type)
{
case DMC_INFO: // Standard DMD information chunk.
memcpy(&info, buffer + fileoffset, LittleLong(chunk->length));
info.skinWidth = LittleLong(info.skinWidth);
info.skinHeight = LittleLong(info.skinHeight);
info.frameSize = LittleLong(info.frameSize);
info.numSkins = LittleLong(info.numSkins);
info.numVertices = LittleLong(info.numVertices);
info.numTexCoords = LittleLong(info.numTexCoords);
info.numFrames = LittleLong(info.numFrames);
info.numLODs = LittleLong(info.numLODs);
info.offsetSkins = LittleLong(info.offsetSkins);
info.offsetTexCoords = LittleLong(info.offsetTexCoords);
info.offsetFrames = LittleLong(info.offsetFrames);
info.offsetLODs = LittleLong(info.offsetLODs);
info.offsetEnd = LittleLong(info.offsetEnd);
fileoffset += chunk->length;
break;
default:
// Just skip all unknown chunks.
fileoffset += chunk->length;
break;
}
// Read the next chunk header.
chunk = (dmd_chunk_t*)(buffer+fileoffset);
chunk->type = LittleLong(chunk->type);
fileoffset += sizeof(dmd_chunk_t);
}
// Allocate and load in the data.
skins = new FTexture *[info.numSkins];
for(i = 0; i < info.numSkins; i++)
{
skins[i] = LoadSkin(path, buffer + info.offsetSkins + i*64);
}
temp = (char*)buffer + info.offsetFrames;
frames = new ModelFrame[info.numFrames];
ib_count = 0;
for(i = 0, frame = frames; i < info.numFrames; i++, frame++)
{
dmd_packedFrame_t *pfr = (dmd_packedFrame_t *) (temp + info.frameSize * i);
dmd_packedVertex_t *pVtx;
memcpy(frame->name, pfr->name, sizeof(pfr->name));
frame->vertices = new DMDModelVertex[info.numVertices];
frame->normals = new DMDModelVertex[info.numVertices];
frame->vindex = UINT_MAX;
// Translate each vertex.
for(k = 0, pVtx = pfr->vertices; k < info.numVertices; k++, pVtx++)
{
UnpackVector((unsigned short)(pVtx->normal), frame->normals[k].xyz);
for(c = 0; c < 3; c++)
{
frame->vertices[k].xyz[axis[c]] =
(pVtx->vertex[c] * FLOAT(pfr->scale[c]) + FLOAT(pfr->translate[c]));
}
// Aspect undo.
frame->vertices[k].xyz[VZ] *= rModelAspectMod;
}
}
memcpy(lodInfo, buffer+info.offsetLODs, info.numLODs * sizeof(DMDLoDInfo));
for(i = 0; i < info.numLODs; i++)
{
lodInfo[i].numTriangles = LittleLong(lodInfo[i].numTriangles);
lodInfo[i].numGlCommands = LittleLong(lodInfo[i].numGlCommands);
lodInfo[i].offsetTriangles = LittleLong(lodInfo[i].offsetTriangles);
lodInfo[i].offsetGlCommands = LittleLong(lodInfo[i].offsetGlCommands);
triangles[i] = (FTriangle*)(buffer + lodInfo[i].offsetTriangles);
lods[i].glCommands = new int[lodInfo[i].numGlCommands];
memcpy(lods[i].glCommands, buffer + lodInfo[i].offsetGlCommands, sizeof(int) * lodInfo[i].numGlCommands);
}
// Determine vertex usage at each LOD level.
vertexUsage = new char[info.numVertices];
memset(vertexUsage, 0, info.numVertices);
for(i = 0; i < info.numLODs; i++)
for(k = 0; k < lodInfo[i].numTriangles; k++)
for(c = 0; c < 3; c++)
vertexUsage[short(triangles[i][k].vertexIndices[c])] |= 1 << i;
loaded=true;
return true;
}
FDMDModel::~FDMDModel()
{
int i;
// clean up
if (skins != NULL)
{
// skins are managed by the texture manager so they must not be deleted here.
delete [] skins;
}
if (frames != NULL)
{
for (i=0;i<info.numFrames;i++)
{
delete [] frames[i].vertices;
delete [] frames[i].normals;
}
delete [] frames;
}
for(i = 0; i < info.numLODs; i++)
{
delete [] lods[i].glCommands;
}
if (vertexUsage != NULL) delete [] vertexUsage;
}
void FDMDModel::BuildVertexBuffer(FModelVertexBuffer *buf)
{
for (int i = 0; i < info.numFrames; i++)
{
ModelFrame *frame = &frames[i];
DMDModelVertex *vert = frame->vertices;
DMDModelVertex *norm = frame->normals;
void *glCommands = lods[0].glCommands;
frame->vindex = buf->vbo_shadowdata.Size();
for (char *pos = (char*)glCommands; *pos;)
{
int count = *(int *)pos;
pos += 4;
// The type of primitive depends on the sign.
int primtype = count > 0 ? GL_TRIANGLE_STRIP : GL_TRIANGLE_FAN;
count = abs(count);
if (i == 0)
{
// build the index buffer - we'll use the same buffer for all frames so only create it once
unsigned int bufindex = buf->vbo_shadowdata.Size() - frame->vindex;
unsigned int bufp = bufindex;
if (primtype == GL_TRIANGLE_STRIP)
{
for (int t = 0; t < count - 2; t++)
{
unsigned int *p = &buf->ibo_shadowdata[buf->ibo_shadowdata.Reserve(3)];
if ((t & 1) == 0)
{
p[0] = bufp;
p[1] = bufp + 1;
p[2] = bufp + 2;
}
else
{
p[0] = bufp;
p[2] = bufp + 2;
p[1] = bufp + 1;
}
bufp++;
}
}
else
{
bufp++;
for (int t = 0; t < count - 2; t++)
{
unsigned int *p = &buf->ibo_shadowdata[buf->ibo_shadowdata.Reserve(3)];
p[0] = bufindex;
p[1] = bufp;
p[2] = bufp + 1;
bufp++;
}
}
}
while (count--)
{
FModelVertex bvert;
FGLCommandVertex * v = (FGLCommandVertex *)pos;
pos += sizeof(FGLCommandVertex);
bvert.Set(vert[v->index].xyz[0], vert[v->index].xyz[1], vert[v->index].xyz[2], v->s, v->t);
bvert.SetNormal(norm[v->index].xyz[0], norm[v->index].xyz[1], norm[v->index].xyz[2]);
buf->vbo_shadowdata.Push(bvert);
}
}
}
}
//===========================================================================
//
// FDMDModel::FindFrame
//
//===========================================================================
int FDMDModel::FindFrame(const char * name)
{
for (int i=0;i<info.numFrames;i++)
{
if (!stricmp(name, frames[i].name)) return i;
}
return -1;
}
//===========================================================================
//
// Render a set of GL commands using the given data.
//
//===========================================================================
void FDMDModel::RenderGLCommands(void *glCommands, unsigned int numVertices, DMDModelVertex * vertices, DMDModelVertex *vertices2, double inter)
{
char *pos;
FGLCommandVertex * v;
int count;
const bool interpolate = (vertices2 != NULL && inter != 0. && vertices != vertices2);
gl_RenderState.Apply();
for(pos = (char*)glCommands; *pos;)
{
count = *(int *) pos;
pos += 4;
// The type of primitive depends on the sign.
glBegin(count > 0 ? GL_TRIANGLE_STRIP : GL_TRIANGLE_FAN);
count = abs(count);
while (count--)
{
v = (FGLCommandVertex *)pos;
pos += sizeof(FGLCommandVertex);
glTexCoord2fv(&v->s);
if (!interpolate)
{
glVertex3fv(vertices[v->index].xyz);
}
else
{
float interp[3];
for (int i = 0; i < 3; i++)
interp[i] = inter * vertices[v->index].xyz[i] + (1. - inter) * vertices2[v->index].xyz[i];
glVertex3fv(interp);
}
}
glEnd();
}
}
void FDMDModel::RenderFrame(FTexture * skin, int frameno, int frameno2, double inter, int translation)
{
if (frameno >= info.numFrames || frameno2 >= info.numFrames) return;
if (!skin)
{
if (info.numSkins == 0) return;
skin = skins[0];
if (!skin) return;
}
FMaterial * tex = FMaterial::ValidateTexture(skin);
tex->Bind(0, translation);
RenderGLCommands(lods[0].glCommands, info.numVertices, frames[frameno].vertices, frames[frameno2].vertices, inter);
}
//===========================================================================
//
// FMD2Model::Load
//
//===========================================================================
bool FMD2Model::Load(const char * path, int, const char * buffer, int length)
{
// Internal data structures of MD2 files - only used during loading!
struct md2_header_t
{
int magic;
int version;
int skinWidth;
int skinHeight;
int frameSize;
int numSkins;
int numVertices;
int numTexCoords;
int numTriangles;
int numGlCommands;
int numFrames;
int offsetSkins;
int offsetTexCoords;
int offsetTriangles;
int offsetFrames;
int offsetGlCommands;
int offsetEnd;
} ;
struct md2_triangleVertex_t
{
byte vertex[3];
byte lightNormalIndex;
};
struct md2_packedFrame_t
{
float scale[3];
float translate[3];
char name[16];
md2_triangleVertex_t vertices[1];
};
md2_header_t * md2header = (md2_header_t *)buffer;
ModelFrame *frame;
byte *md2_frames;
int i, k, c;
int axis[3] = { VX, VY, VZ };
// Convert it to DMD.
header.magic = MD2_MAGIC;
header.version = 8;
header.flags = 0;
vertexUsage = NULL;
info.skinWidth = LittleLong(md2header->skinWidth);
info.skinHeight = LittleLong(md2header->skinHeight);
info.frameSize = LittleLong(md2header->frameSize);
info.numLODs = 1;
info.numSkins = LittleLong(md2header->numSkins);
info.numTexCoords = LittleLong(md2header->numTexCoords);
info.numVertices = LittleLong(md2header->numVertices);
info.numFrames = LittleLong(md2header->numFrames);
info.offsetSkins = LittleLong(md2header->offsetSkins);
info.offsetTexCoords = LittleLong(md2header->offsetTexCoords);
info.offsetFrames = LittleLong(md2header->offsetFrames);
info.offsetLODs = LittleLong(md2header->offsetEnd); // Doesn't exist.
lodInfo[0].numTriangles = LittleLong(md2header->numTriangles);
lodInfo[0].numGlCommands = LittleLong(md2header->numGlCommands);
lodInfo[0].offsetTriangles = LittleLong(md2header->offsetTriangles);
lodInfo[0].offsetGlCommands = LittleLong(md2header->offsetGlCommands);
info.offsetEnd = LittleLong(md2header->offsetEnd);
if (info.offsetFrames + info.frameSize * info.numFrames > length)
{
Printf("LoadModel: Model '%s' file too short\n", path);
return false;
}
if (lodInfo[0].numGlCommands <= 0)
{
Printf("LoadModel: Model '%s' invalid NumGLCommands\n", path);
return false;
}
// The frames need to be unpacked.
md2_frames = (byte*)buffer + info.offsetFrames;
frames = new ModelFrame[info.numFrames];
for(i = 0, frame = frames; i < info.numFrames; i++, frame++)
{
md2_packedFrame_t *pfr = (md2_packedFrame_t *) (md2_frames + info.frameSize * i);
md2_triangleVertex_t *pVtx;
memcpy(frame->name, pfr->name, sizeof(pfr->name));
frame->vertices = new DMDModelVertex[info.numVertices];
frame->normals = new DMDModelVertex[info.numVertices];
frame->vindex = UINT_MAX;
// Translate each vertex.
for(k = 0, pVtx = pfr->vertices; k < info.numVertices; k++, pVtx++)
{
memcpy(frame->normals[k].xyz,
avertexnormals[pVtx->lightNormalIndex], sizeof(float) * 3);
for(c = 0; c < 3; c++)
{
frame->vertices[k].xyz[axis[c]] =
(pVtx->vertex[c] * pfr->scale[c] + pfr->translate[c]);
}
// Aspect ratio adjustment (1.33 -> 1.6.)
frame->vertices[k].xyz[VZ] *= rModelAspectMod;
}
}
lods[0].glCommands = new int[lodInfo[0].numGlCommands];
memcpy(lods[0].glCommands, buffer + lodInfo[0].offsetGlCommands, sizeof(int) * lodInfo[0].numGlCommands);
skins = new FTexture *[info.numSkins];
for(i = 0; i < info.numSkins; i++)
{
skins[i] = LoadSkin(path, buffer + info.offsetSkins + i*64);
}
loaded=true;
return true;
}
FMD2Model::~FMD2Model()
{
}