// //--------------------------------------------------------------------------- // // Copyright(C) 2006-2016 Christoph Oelckers // All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see http://www.gnu.org/licenses/ // //-------------------------------------------------------------------------- // #include "filesystem.h" #include "cmdlib.h" #include "model_md3.h" #include "texturemanager.h" #include "modelrenderer.h" #define MAX_QPATH 64 #ifdef _MSC_VER #pragma warning(disable:4244) // warning C4244: conversion from 'double' to 'float', possible loss of data #endif //=========================================================================== // // decode the lat/lng normal to a 3 float normal // //=========================================================================== static void UnpackVector(unsigned short packed, float & nx, float & ny, float & nz) { double lat = ( packed >> 8 ) & 0xff; double lng = ( packed & 0xff ); lat *= M_PI/128; lng *= M_PI/128; nx = cos(lat) * sin(lng); ny = sin(lat) * sin(lng); nz = cos(lng); } //=========================================================================== // // MD3 File structure // //=========================================================================== #pragma pack(4) struct md3_header_t { uint32_t Magic; uint32_t Version; char Name[MAX_QPATH]; uint32_t Flags; uint32_t Num_Frames; uint32_t Num_Tags; uint32_t Num_Surfaces; uint32_t Num_Skins; uint32_t Ofs_Frames; uint32_t Ofs_Tags; uint32_t Ofs_Surfaces; uint32_t Ofs_Eof; }; struct md3_surface_t { uint32_t Magic; char Name[MAX_QPATH]; uint32_t Flags; uint32_t Num_Frames; uint32_t Num_Shaders; uint32_t Num_Verts; uint32_t Num_Triangles; uint32_t Ofs_Triangles; uint32_t Ofs_Shaders; uint32_t Ofs_Texcoord; uint32_t Ofs_XYZNormal; uint32_t Ofs_End; }; struct md3_triangle_t { uint32_t vt_index[3]; }; struct md3_shader_t { char Name[MAX_QPATH]; uint32_t index; }; struct md3_texcoord_t { float s, t; }; struct md3_vertex_t { short x, y, z, n; }; struct md3_frame_t { float min_Bounds[3]; float max_Bounds[3]; float localorigin[3]; float radius; char Name[16]; }; #pragma pack() //=========================================================================== // // // //=========================================================================== bool FMD3Model::Load(const char * path, int lumpnum, const char * buffer, int length) { md3_header_t * hdr = (md3_header_t *)buffer; auto numFrames = LittleLong(hdr->Num_Frames); auto numSurfaces = LittleLong(hdr->Num_Surfaces); numTags = LittleLong(hdr->Num_Tags); md3_frame_t * frm = (md3_frame_t*)(buffer + LittleLong(hdr->Ofs_Frames)); Frames.Resize(numFrames); for (unsigned i = 0; i < numFrames; i++) { strncpy(Frames[i].Name, frm[i].Name, 16); for (int j = 0; j < 3; j++) Frames[i].origin[j] = frm[i].localorigin[j]; } md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces)); Surfaces.Resize(numSurfaces); for (unsigned i = 0; i < numSurfaces; i++) { MD3Surface * s = &Surfaces[i]; md3_surface_t * ss = surf; surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End)); s->numSkins = LittleLong(ss->Num_Shaders); s->numTriangles = LittleLong(ss->Num_Triangles); s->numVertices = LittleLong(ss->Num_Verts); // copy shaders (skins) md3_shader_t * shader = (md3_shader_t*)(((char*)ss) + LittleLong(ss->Ofs_Shaders)); s->Skins.Resize(s->numSkins); for (unsigned i = 0; i < s->numSkins; i++) { // [BB] According to the MD3 spec, Name is supposed to include the full path. // ... and since some tools seem to output backslashes, these need to be replaced with forward slashes to work. FixPathSeperator(shader[i].Name); s->Skins[i] = LoadSkin("", shader[i].Name); // [BB] Fall back and check if Name is relative. if (!s->Skins[i].isValid()) s->Skins[i] = LoadSkin(path, shader[i].Name); } } mLumpNum = lumpnum; return true; } //=========================================================================== // // // //=========================================================================== void FMD3Model::LoadGeometry() { FileData lumpdata = fileSystem.ReadFile(mLumpNum); const char *buffer = (const char *)lumpdata.GetMem(); md3_header_t * hdr = (md3_header_t *)buffer; md3_surface_t * surf = (md3_surface_t*)(buffer + LittleLong(hdr->Ofs_Surfaces)); for (unsigned i = 0; i < Surfaces.Size(); i++) { MD3Surface * s = &Surfaces[i]; md3_surface_t * ss = surf; surf = (md3_surface_t *)(((char*)surf) + LittleLong(surf->Ofs_End)); // copy triangle indices md3_triangle_t * tris = (md3_triangle_t*)(((char*)ss) + LittleLong(ss->Ofs_Triangles)); s->Tris.Resize(s->numTriangles); for (unsigned i = 0; i < s->numTriangles; i++) for (int j = 0; j < 3; j++) { s->Tris[i].VertIndex[j] = LittleLong(tris[i].vt_index[j]); } // Load texture coordinates md3_texcoord_t * tc = (md3_texcoord_t*)(((char*)ss) + LittleLong(ss->Ofs_Texcoord)); s->Texcoords.Resize(s->numVertices); for (unsigned i = 0; i < s->numVertices; i++) { s->Texcoords[i].s = tc[i].s; s->Texcoords[i].t = tc[i].t; } // Load vertices and texture coordinates md3_vertex_t * vt = (md3_vertex_t*)(((char*)ss) + LittleLong(ss->Ofs_XYZNormal)); s->Vertices.Resize(s->numVertices * Frames.Size()); for (unsigned i = 0; i < s->numVertices * Frames.Size(); i++) { s->Vertices[i].x = LittleShort(vt[i].x) / 64.f; s->Vertices[i].y = LittleShort(vt[i].y) / 64.f; s->Vertices[i].z = LittleShort(vt[i].z) / 64.f; UnpackVector(LittleShort(vt[i].n), s->Vertices[i].nx, s->Vertices[i].ny, s->Vertices[i].nz); } } } //=========================================================================== // // // //=========================================================================== void FMD3Model::BuildVertexBuffer(FModelRenderer *renderer) { if (!GetVertexBuffer(renderer->GetType())) { LoadGeometry(); unsigned int vbufsize = 0; unsigned int ibufsize = 0; for (unsigned i = 0; i < Surfaces.Size(); i++) { MD3Surface * surf = &Surfaces[i]; vbufsize += Frames.Size() * surf->numVertices; ibufsize += 3 * surf->numTriangles; } auto vbuf = renderer->CreateVertexBuffer(true, Frames.Size() == 1); SetVertexBuffer(renderer->GetType(), vbuf); FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize); unsigned int *indxptr = vbuf->LockIndexBuffer(ibufsize); assert(vertptr != nullptr && indxptr != nullptr); unsigned int vindex = 0, iindex = 0; for (unsigned i = 0; i < Surfaces.Size(); i++) { MD3Surface * surf = &Surfaces[i]; surf->vindex = vindex; surf->iindex = iindex; for (unsigned j = 0; j < Frames.Size() * surf->numVertices; j++) { MD3Vertex* vert = &surf->Vertices[j]; FModelVertex *bvert = &vertptr[vindex++]; int tc = j % surf->numVertices; bvert->Set(vert->x, vert->z, vert->y, surf->Texcoords[tc].s, surf->Texcoords[tc].t); bvert->SetNormal(vert->nx, vert->nz, vert->ny); } for (unsigned k = 0; k < surf->numTriangles; k++) { for (int l = 0; l < 3; l++) { indxptr[iindex++] = surf->Tris[k].VertIndex[l]; } } surf->UnloadGeometry(); } vbuf->UnlockVertexBuffer(); vbuf->UnlockIndexBuffer(); } } //=========================================================================== // // for skin precaching // //=========================================================================== void FMD3Model::AddSkins(uint8_t *hitlist) { for (unsigned i = 0; i < Surfaces.Size(); i++) { int ssIndex = i + curMDLIndex * MD3_MAX_SURFACES; if (curSpriteMDLFrame && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid()) { hitlist[curSpriteMDLFrame->surfaceskinIDs[ssIndex].GetIndex()] |= FTextureManager::HIT_Flat; } MD3Surface * surf = &Surfaces[i]; for (unsigned j = 0; j < surf->numSkins; j++) { if (surf->Skins[j].isValid()) { hitlist[surf->Skins[j].GetIndex()] |= FTextureManager::HIT_Flat; } } } } //=========================================================================== // // // //=========================================================================== int FMD3Model::FindFrame(const char * name) { for (unsigned i = 0; i < Frames.Size(); i++) { if (!stricmp(name, Frames[i].Name)) return i; } return -1; } //=========================================================================== // // // //=========================================================================== void FMD3Model::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation) { if ((unsigned)frameno >= Frames.Size() || (unsigned)frameno2 >= Frames.Size()) return; renderer->SetInterpolation(inter); for (unsigned i = 0; i < Surfaces.Size(); i++) { MD3Surface * surf = &Surfaces[i]; // [BB] In case no skin is specified via MODELDEF, check if the MD3 has a skin for the current surface. // Note: Each surface may have a different skin. FGameTexture *surfaceSkin = skin; if (!surfaceSkin) { if (curSpriteMDLFrame) { int ssIndex = i + curMDLIndex * MD3_MAX_SURFACES; if (curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid()) { surfaceSkin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[ssIndex], true); } else if (surf->numSkins > 0 && surf->Skins[0].isValid()) { surfaceSkin = TexMan.GetGameTexture(surf->Skins[0], true); } } if (!surfaceSkin) { continue; } } renderer->SetMaterial(surfaceSkin, false, translation); renderer->SetupFrame(this, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices); renderer->DrawElements(surf->numTriangles * 3, surf->iindex * sizeof(unsigned int)); } renderer->SetInterpolation(0.f); }