From c404212b669f0c14d57f76b0f58220e0e257927b Mon Sep 17 00:00:00 2001 From: havoc Date: Sun, 28 Aug 2005 07:23:51 +0000 Subject: [PATCH] added .dpm model support moved Mod_ZYMOTICMODEL_Load function prototype from model_zymotic.h to model_alias.h (where the others already are) removed an unnecessary call to Mod_BuildTriangleNeighbors in Mod_ZYMOTICMODEL_Load (it was being called twice) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@5665 d7cf8633-e32d-0410-b094-e92efae38249 --- model_alias.c | 213 +++++++++++++++++++++++++++++++++++++++++++++++- model_alias.h | 4 + model_dpmodel.h | 123 ++++++++++++++++++++++++++++ model_shared.c | 1 + model_zymotic.h | 2 - 5 files changed, 340 insertions(+), 3 deletions(-) create mode 100644 model_dpmodel.h diff --git a/model_alias.c b/model_alias.c index ca95999c..572f038b 100644 --- a/model_alias.c +++ b/model_alias.c @@ -1355,7 +1355,6 @@ void Mod_ZYMOTICMODEL_Load(model_t *mod, void *buffer) mesh->num_vertices = Mod_BuildVertexRemapTableFromElements(mesh->num_triangles * 3, mesh->data_element3i, pheader->numverts, remapvertices); for (j = 0;j < mesh->num_triangles * 3;j++) mesh->data_element3i[j] = remapvertices[mesh->data_element3i[j]]; - Mod_BuildTriangleNeighbors(mesh->data_neighbor3i, mesh->data_element3i, mesh->num_triangles); mesh->data_texcoordtexture2f = Mem_Alloc(loadmodel->mempool, mesh->num_vertices * sizeof(float[2])); for (j = 0;j < pheader->numverts;j++) { @@ -1420,3 +1419,215 @@ void Mod_ZYMOTICMODEL_Load(model_t *mod, void *buffer) Mem_Free(outtexcoord2f); } +void Mod_DARKPLACESMODEL_Load(model_t *mod, void *buffer) +{ + dpmheader_t *pheader; + dpmframe_t *frame; + dpmbone_t *bone; + dpmmesh_t *dpmmesh; + qbyte *pbase; + int i, j, k; + skinfile_t *skinfiles; + qbyte *data; + + pheader = (void *)buffer; + pbase = buffer; + if (memcmp(pheader->id, "DARKPLACESMODEL\0", 16)) + Host_Error ("Mod_DARKPLACESMODEL_Load: %s is not a zymotic model\n"); + if (BigLong(pheader->type) != 2) + Host_Error ("Mod_DARKPLACESMODEL_Load: only type 2 (hierarchical skeletal pose) models are currently supported (name = %s)\n", loadmodel->name); + + loadmodel->type = mod_alias; + loadmodel->DrawSky = NULL; + loadmodel->Draw = R_Q1BSP_Draw; + loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume; + loadmodel->DrawLight = R_Q1BSP_DrawLight; + loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox; + loadmodel->flags = 0; // there are no flags on zym models + loadmodel->synctype = ST_RAND; + + // byteswap header + pheader->type = BigLong(pheader->type); + pheader->filesize = BigLong(pheader->filesize); + pheader->mins[0] = BigFloat(pheader->mins[0]); + pheader->mins[1] = BigFloat(pheader->mins[1]); + pheader->mins[2] = BigFloat(pheader->mins[2]); + pheader->maxs[0] = BigFloat(pheader->maxs[0]); + pheader->maxs[1] = BigFloat(pheader->maxs[1]); + pheader->maxs[2] = BigFloat(pheader->maxs[2]); + pheader->yawradius = BigFloat(pheader->yawradius); + pheader->allradius = BigFloat(pheader->allradius); + pheader->num_bones = BigLong(pheader->num_bones); + pheader->num_meshs = BigLong(pheader->num_meshs); + pheader->num_frames = BigLong(pheader->num_frames); + pheader->ofs_bones = BigLong(pheader->ofs_bones); + pheader->ofs_meshs = BigLong(pheader->ofs_meshs); + pheader->ofs_frames = BigLong(pheader->ofs_frames); + + // model bbox + for (i = 0;i < 3;i++) + { + loadmodel->normalmins[i] = pheader->mins[i]; + loadmodel->normalmaxs[i] = pheader->maxs[i]; + loadmodel->yawmins[i] = i != 2 ? -pheader->yawradius : pheader->mins[i]; + loadmodel->yawmaxs[i] = i != 2 ? pheader->yawradius : pheader->maxs[i]; + loadmodel->rotatedmins[i] = -pheader->allradius; + loadmodel->rotatedmaxs[i] = pheader->allradius; + } + loadmodel->radius = pheader->allradius; + loadmodel->radius2 = pheader->allradius * pheader->allradius; + + // load external .skin files if present + skinfiles = Mod_LoadSkinFiles(); + if (loadmodel->numskins < 1) + loadmodel->numskins = 1; + + loadmodel->numframes = pheader->num_frames; + loadmodel->num_bones = pheader->num_bones; + loadmodel->num_poses = loadmodel->num_bones * loadmodel->numframes; + loadmodel->num_textures = loadmodel->nummeshes = loadmodel->nummodelsurfaces = loadmodel->num_surfaces = pheader->num_meshs; + + // do most allocations as one merged chunk + data = Mem_Alloc(loadmodel->mempool, loadmodel->num_surfaces * sizeof(msurface_t) + loadmodel->num_surfaces * sizeof(int) + loadmodel->nummeshes * sizeof(surfmesh_t *) + loadmodel->nummeshes * sizeof(surfmesh_t) + loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t) + loadmodel->numskins * sizeof(animscene_t) + loadmodel->num_bones * sizeof(aliasbone_t) + loadmodel->num_poses * sizeof(float[12]) + loadmodel->numframes * sizeof(animscene_t)); + loadmodel->data_surfaces = (void *)data;data += loadmodel->num_surfaces * sizeof(msurface_t); + loadmodel->surfacelist = (void *)data;data += loadmodel->num_surfaces * sizeof(int); + loadmodel->meshlist = (void *)data;data += loadmodel->num_surfaces * sizeof(surfmesh_t *); + loadmodel->data_textures = (void *)data;data += loadmodel->num_surfaces * loadmodel->numskins * sizeof(texture_t); + loadmodel->skinscenes = (void *)data;data += loadmodel->numskins * sizeof(animscene_t); + loadmodel->data_bones = (void *)data;data += loadmodel->num_bones * sizeof(aliasbone_t); + loadmodel->data_poses = (void *)data;data += loadmodel->num_poses * sizeof(float[12]); + loadmodel->animscenes = (void *)data;data += loadmodel->numframes * sizeof(animscene_t); + for (i = 0;i < loadmodel->numskins;i++) + { + loadmodel->skinscenes[i].firstframe = i; + loadmodel->skinscenes[i].framecount = 1; + loadmodel->skinscenes[i].loop = true; + loadmodel->skinscenes[i].framerate = 10; + } + for (i = 0;i < loadmodel->num_surfaces;i++) + { + loadmodel->surfacelist[i] = i; + loadmodel->meshlist[i] = (void *)data;data += sizeof(surfmesh_t); + } + + // load the bone info + bone = (void *) (pbase + pheader->ofs_bones); + for (i = 0;i < loadmodel->num_bones;i++) + { + memcpy(loadmodel->data_bones[i].name, bone[i].name, sizeof(bone[i].name)); + loadmodel->data_bones[i].flags = BigLong(bone[i].flags); + loadmodel->data_bones[i].parent = BigLong(bone[i].parent); + if (loadmodel->data_bones[i].parent >= i) + Host_Error("%s bone[%i].parent >= %i\n", loadmodel->name, i, i); + } + + // load the frames + frame = (void *) (pbase + pheader->ofs_frames); + for (i = 0;i < loadmodel->numframes;i++) + { + const float *poses; + memcpy(loadmodel->animscenes[i].name, frame->name, sizeof(frame->name)); + loadmodel->animscenes[i].firstframe = i; + loadmodel->animscenes[i].framecount = 1; + loadmodel->animscenes[i].loop = true; + loadmodel->animscenes[i].framerate = 10; + // load the bone poses for this frame + poses = (void *) (pbase + BigLong(frame->ofs_bonepositions)); + for (j = 0;j < loadmodel->num_bones*12;j++) + loadmodel->data_poses[i * loadmodel->num_bones*12 + j] = BigFloat(poses[j]); + // stuff not processed here: mins, maxs, yawradius, allradius + frame++; + } + + // load the meshes now + dpmmesh = (void *) (pbase + pheader->ofs_meshs); + for (i = 0;i < loadmodel->num_surfaces;i++) + { + const int *inelements; + int *outelements; + const float *intexcoord; + surfmesh_t *mesh; + msurface_t *surface; + + mesh = loadmodel->meshlist[i]; + mesh->num_triangles = BigLong(dpmmesh->num_tris); + mesh->num_vertices = BigLong(dpmmesh->num_verts); + + // to find out how many weights exist we two a two-stage load... + mesh->num_vertexboneweights = 0; + data = (void *) (pbase + BigLong(dpmmesh->ofs_verts)); + for (j = 0;j < mesh->num_vertices;j++) + { + int numweights = BigLong(((dpmvertex_t *)data)->numbones); + mesh->num_vertexboneweights += numweights; + data += sizeof(dpmvertex_t); + data += numweights * sizeof(dpmbonevert_t); + } + + // allocate things now that we know how many + mesh->data_vertexboneweights = Mem_Alloc(loadmodel->mempool, mesh->num_vertexboneweights * sizeof(surfmeshvertexboneweight_t)); + mesh->data_element3i = Mem_Alloc(loadmodel->mempool, mesh->num_triangles * sizeof(int[3])); + mesh->data_neighbor3i = Mem_Alloc(loadmodel->mempool, mesh->num_triangles * sizeof(int[3])); + mesh->data_texcoordtexture2f = Mem_Alloc(loadmodel->mempool, mesh->num_vertices * sizeof(float[2])); + + inelements = (void *) (pbase + BigLong(dpmmesh->ofs_indices)); + outelements = mesh->data_element3i; + for (j = 0;j < mesh->num_triangles;j++) + { + // swap element order to flip triangles, because Quake uses clockwise (rare) and dpm uses counterclockwise (standard) + outelements[0] = BigLong(inelements[2]); + outelements[1] = BigLong(inelements[1]); + outelements[2] = BigLong(inelements[0]); + inelements += 3; + outelements += 3; + } + + intexcoord = (void *) (pbase + BigLong(dpmmesh->ofs_texcoords)); + for (j = 0;j < mesh->num_vertices*2;j++) + mesh->data_texcoordtexture2f[j] = BigFloat(intexcoord[j]); + + // now load them for real + mesh->num_vertexboneweights = 0; + data = (void *) (pbase + BigLong(dpmmesh->ofs_verts)); + for (j = 0;j < mesh->num_vertices;j++) + { + int numweights = BigLong(((dpmvertex_t *)data)->numbones); + data += sizeof(dpmvertex_t); + for (k = 0;k < numweights;k++) + { + const dpmbonevert_t *vert = (void *) data; + // stuff not processed here: normal + mesh->data_vertexboneweights[mesh->num_vertexboneweights].vertexindex = j; + mesh->data_vertexboneweights[mesh->num_vertexboneweights].boneindex = BigLong(vert->bonenum); + mesh->data_vertexboneweights[mesh->num_vertexboneweights].origin[0] = BigFloat(vert->origin[0]); + mesh->data_vertexboneweights[mesh->num_vertexboneweights].origin[1] = BigFloat(vert->origin[1]); + mesh->data_vertexboneweights[mesh->num_vertexboneweights].origin[2] = BigFloat(vert->origin[2]); + mesh->data_vertexboneweights[mesh->num_vertexboneweights].origin[3] = BigFloat(vert->influence); + mesh->num_vertexboneweights++; + data += sizeof(dpmbonevert_t); + } + } + + // since dpm models do not have named sections, reuse their shader name as the section name + if (dpmmesh->shadername[0]) + Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, dpmmesh->shadername, dpmmesh->shadername); + else + for (j = 0;j < loadmodel->numskins;j++) + Mod_BuildAliasSkinFromSkinFrame(loadmodel->data_textures + i + j * loadmodel->num_surfaces, NULL); + + Mod_ValidateElements(mesh->data_element3i, mesh->num_triangles, mesh->num_vertices, __FILE__, __LINE__); + Mod_BuildTriangleNeighbors(mesh->data_neighbor3i, mesh->data_element3i, mesh->num_triangles); + Mod_Alias_Mesh_CompileFrameZero(mesh); + + surface = loadmodel->data_surfaces + i; + surface->groupmesh = mesh; + surface->texture = loadmodel->data_textures + i; + surface->num_firsttriangle = 0; + surface->num_triangles = mesh->num_triangles; + surface->num_firstvertex = 0; + surface->num_vertices = mesh->num_vertices; + + dpmmesh++; + } +} + diff --git a/model_alias.h b/model_alias.h index c3367f8d..2b69fac9 100644 --- a/model_alias.h +++ b/model_alias.h @@ -119,11 +119,15 @@ typedef struct extern void Mod_IDP0_Load(struct model_s *mod, void *buffer); extern void Mod_IDP2_Load(struct model_s *mod, void *buffer); extern void Mod_IDP3_Load(struct model_s *mod, void *buffer); +extern void Mod_ZYMOTICMODEL_Load(struct model_s *mod, void *buffer); +extern void Mod_DARKPLACESMODEL_Load(struct model_s *mod, void *buffer); extern void Mod_AliasInit(void); #include "model_zymotic.h" +#include "model_dpmodel.h" + // all md3 ints, floats, and shorts, are little endian, and thus need to be // passed through LittleLong/LittleFloat/LittleShort to avoid breaking on // bigendian machines (Macs for example) diff --git a/model_dpmodel.h b/model_dpmodel.h new file mode 100644 index 00000000..52ad0ce0 --- /dev/null +++ b/model_dpmodel.h @@ -0,0 +1,123 @@ + +#ifndef MODEL_DPMODEL_H +#define MODEL_DPMODEL_H + +/* +type 2 model (hierarchical skeletal pose) +within this specification, int is assumed to be 32bit, float is assumed to be 32bit, char is assumed to be 8bit, text is assumed to be an array of chars with NULL termination +all values are big endian (also known as network byte ordering), NOT x86 little endian +general notes: +a pose is a 3x4 matrix (rotation matrix, and translate vector) +parent bones must always be lower in number than their children, models will be rejected if this is not obeyed (can be fixed by modelling utilities) +utility notes: +if a hard edge is desired (faceted lighting, or a jump to another set of skin coordinates), vertices must be duplicated +ability to visually edit groupids of triangles is highly recommended +bones should be markable as 'attach' somehow (up to the utility) and thus protected from culling of unused resources +frame 0 is always the base pose (the one the skeleton was built for) +game notes: +the loader should be very thorough about error checking, all vertex and bone indices should be validated, etc +the gamecode can look up bone numbers by name using a builtin function, for use in attachment situations (the client should have the same model as the host of the gamecode in question - that is to say if the server gamecode is setting the bone number, the client and server must have vaguely compatible models so the client understands, and if the client gamecode is setting the bone number, the server could have a completely different model with no harm done) +the triangle groupid values are up to the gamecode, it is recommended that gamecode process this in an object-oriented fashion (I.E. bullet hits entity, call that entity's function for getting properties of that groupid) +frame 0 should be usable, not skipped +speed optimizations for the saver to do: +remove all unused data (unused bones, vertices, etc, be sure to check if bones are used for attachments however) +sort triangles into strips +sort vertices according to first use in a triangle (caching benefits) after sorting triangles +speed optimizations for the loader to do: +if the model only has one frame, process it at load time to create a simple static vertex mesh to render (this is a hassle, but it is rewarding to optimize all such models) +rendering process: +1*. one or two poses are looked up by number +2*. boneposes (matrices) are interpolated, building bone matrix array +3. bones are parsed sequentially, each bone's matrix is transformed by it's parent bone (which can be -1; the model to world matrix) +4. meshs are parsed sequentially, as follows: + 1. vertices are parsed sequentially and may be influenced by more than one bone (the results of the 3x4 matrix transform will be added together - weighting is already built into these) + 2. shader is looked up and called, passing vertex buffer (temporary) and triangle indices (which are stored in the mesh) +5. rendering is complete +* - these stages can be replaced with completely dynamic animation instead of pose animations. +*/ +// header for the entire file +typedef struct dpmheader_s +{ + char id[16]; // "DARKPLACESMODEL\0", length 16 + unsigned int type; // 2 (hierarchical skeletal pose) + unsigned int filesize; // size of entire model file + float mins[3], maxs[3], yawradius, allradius; // for clipping uses + // these offsets are relative to the file + unsigned int num_bones; + unsigned int num_meshs; + unsigned int num_frames; + unsigned int ofs_bones; // dpmbone_t bone[num_bones]; + unsigned int ofs_meshs; // dpmmesh_t mesh[num_meshs]; + unsigned int ofs_frames; // dpmframe_t frame[num_frames]; +} +dpmheader_t; +// there may be more than one of these +typedef struct dpmmesh_s +{ + // these offsets are relative to the file + char shadername[32]; // name of the shader to use + unsigned int num_verts; + unsigned int num_tris; + unsigned int ofs_verts; // dpmvertex_t vert[numvertices]; // see vertex struct + unsigned int ofs_texcoords; // float texcoords[numvertices][2]; + unsigned int ofs_indices; // unsigned int indices[numtris*3]; // designed for glDrawElements (each triangle is 3 unsigned int indices) + unsigned int ofs_groupids; // unsigned int groupids[numtris]; // the meaning of these values is entirely up to the gamecode and modeler +} +dpmmesh_t; +// if set on a bone, it must be protected from removal +#define DPMBONEFLAG_ATTACHMENT 1 +// one per bone +typedef struct dpmbone_s +{ + // name examples: upperleftarm leftfinger1 leftfinger2 hand, etc + char name[32]; + // parent bone number + signed int parent; + // flags for the bone + unsigned int flags; +} +dpmbone_t; +// a bonepose matrix is intended to be used like this: +// (n = output vertex, v = input vertex, m = matrix, f = influence) +// n[0] = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2] + f * m[0][3]; +// n[1] = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2] + f * m[1][3]; +// n[2] = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2] + f * m[2][3]; +typedef struct dpmbonepose_s +{ + float matrix[3][4]; +} +dpmbonepose_t; +// immediately followed by bone positions for the frame +typedef struct dpmframe_s +{ + // name examples: idle_1 idle_2 idle_3 shoot_1 shoot_2 shoot_3, etc + char name[32]; + float mins[3], maxs[3], yawradius, allradius; + int ofs_bonepositions; // dpmbonepose_t bonepositions[bones]; +} +dpmframe_t; +// one or more of these per vertex +typedef struct dpmbonevert_s +{ + // this pairing of origin and influence is intentional + // (in SSE or 3DNow! assembly it can be done as a quad vector op + // (or two dual vector ops) very easily) + float origin[3]; // vertex location (these blend) + float influence; // influence fraction (these must add up to 1) + // this pairing of normal and bonenum is intentional + // (in SSE or 3DNow! assembly it can be done as a quad vector op + // (or two dual vector ops) very easily, the bonenum is ignored) + float normal[3]; // surface normal (these blend) + unsigned int bonenum; // number of the bone +} +dpmbonevert_t; +// variable size, parsed sequentially +typedef struct dpmvertex_s +{ + unsigned int numbones; + // immediately followed by 1 or more dpmbonevert_t structures +} +dpmvertex_t; + +#endif + diff --git a/model_shared.c b/model_shared.c index 4e77c52b..b0de8563 100644 --- a/model_shared.c +++ b/model_shared.c @@ -199,6 +199,7 @@ model_t *Mod_LoadModel(model_t *mod, qboolean crash, qboolean checkdisk, qboolea else if (!memcmp(buf, "IDS2", 4)) Mod_IDS2_Load(mod, buf); else if (!memcmp(buf, "IBSP", 4)) Mod_IBSP_Load(mod, buf); else if (!memcmp(buf, "ZYMOTICMODEL", 12)) Mod_ZYMOTICMODEL_Load(mod, buf); + else if (!memcmp(buf, "DARKPLACESMODEL", 16)) Mod_DARKPLACESMODEL_Load(mod, buf); else if (strlen(mod->name) >= 4 && !strcmp(mod->name - 4, ".map")) Mod_MAP_Load(mod, buf); else if (num == BSPVERSION || num == 30) Mod_Q1BSP_Load(mod, buf); else Con_Printf("Mod_LoadModel: model \"%s\" is of unknown/unsupported type\n", mod->name); diff --git a/model_zymotic.h b/model_zymotic.h index 02fead12..d43f72c5 100644 --- a/model_zymotic.h +++ b/model_zymotic.h @@ -64,7 +64,5 @@ typedef struct zymvertex_s } zymvertex_t; -extern void Mod_ZYMOTICMODEL_Load(struct model_s *mod, void *buffer); - #endif -- 2.39.2