#include "../plugin.h" #include "../engine/common/com_mesh.h" /* Half-Life 2 / Source models store much of their data in other files. I'm only going to try loading simple static models, so we don't need much data from the .mdl itself. FIXME: multiple meshes are still buggy. FIXME: materials are not loaded properly. FIXME: no lod stuff. */ static plugfsfuncs_t *filefuncs; static plugmodfuncs_t *modfuncs; //Utility functions. silly plugins. float Length(const vec3_t v) {return sqrt(DotProduct(v,v));} float RadiusFromBounds (const vec3_t mins, const vec3_t maxs) { int i; vec3_t corner; for (i=0 ; i<3 ; i++) { corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]); } return Length (corner); } //blargh typedef struct { unsigned int magic; unsigned int version; unsigned int revisionid; char name[64]; unsigned int filesize; vec3_t _80; vec3_t _92; vec3_t mins; vec3_t maxs; vec3_t _128; vec3_t _140; unsigned int _152; unsigned int _156; unsigned int _160; unsigned int _164; unsigned int _168; unsigned int _172; unsigned int _176; unsigned int _180; unsigned int _184; unsigned int _188; unsigned int _192; unsigned int _196; unsigned int _200; unsigned int tex_count; unsigned int tex_ofs; unsigned int texpath_count; unsigned int texpath_ofs; unsigned int texbind_count; //N slots per skin unsigned int skin_count; unsigned int texbind_offset;//provides skin|slot->texture mappings unsigned int body_count; unsigned int body_ofs; //other stuff? } hl2mdlheader_t; typedef struct { unsigned int name_ofs; unsigned int surf_count; unsigned int base; unsigned int surf_ofs; } hl2mdlbody_t; typedef struct { char name[64]; unsigned int type; unsigned int radius; unsigned int mesh_count; unsigned int mesh_ofs; unsigned int vertex_count; unsigned int _84; unsigned int _88; unsigned int _92; unsigned int _96; unsigned int _100; unsigned int _104; unsigned int _108; unsigned int _112; unsigned int _116; unsigned int _120; unsigned int _124; unsigned int _128; unsigned int _132; unsigned int _136; unsigned int _140; unsigned int _144; } hl2mdlsurf_t; typedef struct { unsigned int mat_idx; unsigned int model_ofs; unsigned int vert_count; unsigned int vert_first; unsigned int _16; unsigned int _20; unsigned int _24; unsigned int _28; unsigned int _32; unsigned int _36; unsigned int _40; unsigned int _44; unsigned int _48; unsigned int _52; unsigned int _56; unsigned int _60; unsigned int _64; unsigned int _68; unsigned int _72; unsigned int _76; unsigned int _80; unsigned int _84; unsigned int _88; unsigned int _92; unsigned int _96; unsigned int _100; unsigned int _104; unsigned int _108; unsigned int _112; } hl2mdlmesh_t; typedef struct { unsigned int nameofs; unsigned int _4; unsigned int _8; unsigned int _12; unsigned int _16; unsigned int _20; unsigned int _24; unsigned int _28; unsigned int _32; unsigned int _36; unsigned int _40; unsigned int _44; unsigned int _48; unsigned int _52; unsigned int _56; unsigned int _60; } hl2mdltexture_t; typedef struct { unsigned int nameofs; } hl2mdltexturepath_t; #pragma pack(push,1) //urgh wtf is this bullshit typedef struct { unsigned int numskins; unsigned int offsetskin; } hl2vtxskins_t; typedef struct { unsigned short foo; //no padding unsigned int offsetskinname; } hl2vtxskin_t; typedef struct { unsigned int version; unsigned int vertcachesize; unsigned short bonesperstrip; unsigned short bonespertri; unsigned int bonespervert; unsigned int revisionid; unsigned int lod_count; unsigned int texreplacements_offset; unsigned int body_count; unsigned int body_ofs; } hl2vtxheader_t; typedef struct { unsigned int surf_count; unsigned int surf_ofs; } hl2vtxbody_t; typedef struct { unsigned int lod_count; unsigned int lod_ofs; } hl2vtxsurf_t; typedef struct { unsigned int mesh_count; unsigned int mesh_ofs; float dist; } hl2vtxlod_t; typedef struct { unsigned int stripg_count; unsigned int stripg_ofs; unsigned char flags; //no padding (3 bytes) } hl2vtxmesh_t; typedef struct { unsigned int vert_count; unsigned int vert_ofs; unsigned int idx_count; unsigned int idx_ofs; unsigned int strip_count; unsigned int strip_ofs; unsigned char flags; //no padding (3 bytes) } hl2vtxstripg_t; typedef struct { unsigned int idx_num; unsigned int idx_ofs; unsigned int vert_count; unsigned int vert_ofs; unsigned short bone_count; unsigned char flags; //no padding (1 byte) unsigned int bonestate_count; unsigned int bonestate_ofs; } hl2vtxstrip_t; typedef struct { qbyte bone[3]; qbyte bone_count; unsigned short vert; qbyte boneID[3]; //no padding } hl2vtxvert_t; typedef struct { unsigned int magic; unsigned int version; unsigned int revisionid; unsigned int lod_count; unsigned int lodverts_count[8]; unsigned int fixups_count; unsigned int fixups_offset; unsigned int verts_offset; unsigned int tangents_offset; } hl2vvdheader_t; typedef struct { unsigned int lod; unsigned int sourcevert; unsigned int numverts; } hl2vvdfixup_t; typedef struct { float weight[3]; qbyte bone[3]; qbyte numbones; vec3_t xyz; vec3_t norm; vec2_t st; } hl2vvdvert_t; #pragma pack(pop) /*seriously, how many structs do you need?*/ typedef struct { model_t *mod; unsigned numverts; vec2_t *ofs_st_array; vecV_t *ofs_skel_xyz; vec3_t *ofs_skel_norm; vec3_t *ofs_skel_svect; vec3_t *ofs_skel_tvect; // byte_vec4_t *ofs_skel_idx; // vec4_t *ofs_skel_weight; struct { unsigned int numfixups; index_t *fixup; } lod[1]; //must remain at 1 (instead of 8) until fixups are handled. } hl2parsecontext_t; static index_t *Mod_HL2_LoadIndexes(hl2parsecontext_t *ctx, unsigned int *idxcount, const hl2vtxmesh_t *vmesh, unsigned int lod, index_t firstindex) { size_t numidx = 0, g; const hl2vtxstripg_t *vg; index_t *idx, *ret = NULL; vg = (const void*)((const qbyte*)vmesh+vmesh->stripg_ofs); for (g = 0; g < vmesh->stripg_count; g++, vg++) { if (vg->idx_count%3) { *idxcount = 0; return NULL; } numidx += vg->idx_count; } ret = idx = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*idx)*numidx); vg = (const void*)((const qbyte*)vmesh+vmesh->stripg_ofs); for (g = 0; g < vmesh->stripg_count; g++, vg++) { const unsigned short *in = (const void*)((const qbyte*)vg+vg->idx_ofs); const unsigned short *e = in+vg->idx_count; const hl2vtxvert_t *v = (const void*)((const qbyte*)vg+vg->vert_ofs); if (ctx->lod[lod].numfixups) { index_t *fixup = ctx->lod[lod].fixup; for(;;) { if (in == e) break; *idx++ = fixup[v[*in++].vert+firstindex]; } } else { for(;;) { if (in == e) break; *idx++ = v[*in++].vert+firstindex; } } } *idxcount = idx-ret; return ret; } static qboolean Mod_HL2_LoadVTX(hl2parsecontext_t *ctx, const void *buffer, size_t fsize, unsigned int rev, const hl2mdlheader_t *mdl) { //horribly overcomplicated way to express this stuff. size_t totalsurfs = 0, b, s, l, m, t; const hl2vtxheader_t *header = buffer; const hl2vtxbody_t *vbody; const hl2vtxsurf_t *vsurf; const hl2vtxlod_t *vlod; const hl2vtxmesh_t *vmesh; // const hl2vtxskins_t *vskins; // const hl2vtxskin_t *vskin; const hl2mdlbody_t *mbody = (const hl2mdlbody_t*)((const qbyte*)mdl + mdl->body_ofs); const hl2mdltexture_t *mtex = (const hl2mdltexture_t*)((const qbyte*)mdl + mdl->tex_ofs); const unsigned short *skinbind; galiasinfo_t *surf=NULL; galiasskin_t *skin; skinframe_t *skinframe; size_t firstvert = 0; if (fsize < sizeof(*header) || header->version != 7 || header->revisionid != rev || header->body_count == 0) return false; vbody = (const void*)((const qbyte*)header + header->body_ofs); for (b = 0; b < header->body_count; b++, vbody++) { vsurf = (const void*)((const qbyte*)vbody + vbody->surf_ofs); for (s = 0; s < vbody->surf_count; s++, vsurf++) { vlod = (const void*)((const qbyte*)vsurf + vsurf->lod_ofs); for (l = 0; l < min(vsurf->lod_count, countof(ctx->lod)); l++, vlod++) totalsurfs += vlod->mesh_count; } } if (!totalsurfs) return false; ctx->mod->meshinfo = surf = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*surf)*totalsurfs); t = mdl->skin_count*mdl->texbind_count; skinbind = (const unsigned short*)((const qbyte*)mdl+mdl->texbind_offset); skin = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*skin)*t + sizeof(*skinframe)*t); skinframe = (skinframe_t*)(skin+t); for (s = 0; s < mdl->skin_count; s++) for (t = 0; t < mdl->texbind_count; t++) { galiasskin_t *ns = &skin[s + t*mdl->skin_count]; Q_snprintfz(ns->name, sizeof(ns->name), "skin%u %u", (unsigned)s, (unsigned)t); m = *skinbind++; if (mdl->texpath_count) { const hl2mdltexturepath_t *mpath = (const hl2mdltexturepath_t*)((const qbyte*)mdl + mdl->texpath_ofs); Q_strlcpy(skinframe->shadername, (const char*)mdl+mpath->nameofs, sizeof(skinframe->shadername)); } else { modfuncs->StripExtension((const char*)ctx->mod->name, skinframe->shadername, sizeof(skinframe->shadername)); Q_strlcat(skinframe->shadername, "/", sizeof(skinframe->shadername)); } Q_strlcat(skinframe->shadername, (const char*)&mtex[m]+mtex[m].nameofs, sizeof(skinframe->shadername)); Q_strlcat(skinframe->shadername, ".vmt", sizeof(skinframe->shadername)); ns->numframes = 1; //no skingroups... not that kind anyway. ns->skinspeed = 10; ns->frame = skinframe; skinframe++; } vbody = (const void*)((const qbyte*)header + header->body_ofs); for (b = 0; b < header->body_count; b++, vbody++, mbody++) { const hl2mdlsurf_t *msurf = (const hl2mdlsurf_t*)((const qbyte*)mbody + mbody->surf_ofs); vsurf = (const void*)((const qbyte*)vbody + vbody->surf_ofs); for (s = 0; s < vbody->surf_count; s++, vsurf++, msurf++) { vlod = (const void*)((const qbyte*)vsurf + vsurf->lod_ofs); // vskins = (const hl2vtxskins_t*)((const qbyte*)header + header->texreplacements_offset); for (l = 0, t = 0; l < min(vsurf->lod_count, countof(ctx->lod)); l++, vlod++/*, vskins++*/) { const hl2mdlmesh_t *mmesh = (const hl2mdlmesh_t*)((const qbyte*)msurf + msurf->mesh_ofs); vmesh = (const void*)((const qbyte*)vlod + vlod->mesh_ofs); for (m = 0; m < vlod->mesh_count; m++, vmesh++, mmesh++) { Q_snprintfz(surf->surfacename, sizeof(surf->surfacename), "%s:%s:l%u:m%u", (const char*)mbody+mbody->name_ofs, msurf->name, (unsigned)l, (unsigned)m); /*animation info*/ surf->shares_bones = 0; surf->numbones = 0; surf->ofsbones = NULL; //no support for animations... surf->numanimations = 0; surf->ofsanimations = NULL; //we have no animation data #ifndef SERVERONLY /*skin data*/ surf->numskins = mdl->skin_count; surf->ofsskins = skin+mmesh->mat_idx*mdl->skin_count; /*vertdata*/ surf->ofs_rgbaf = NULL; surf->ofs_rgbaub = NULL; surf->ofs_st_array = ctx->ofs_st_array; #endif surf->shares_verts = 0; surf->numverts = ctx->numverts; surf->ofs_skel_xyz = ctx->ofs_skel_xyz; surf->ofs_skel_norm = ctx->ofs_skel_norm; surf->ofs_skel_svect = ctx->ofs_skel_svect; surf->ofs_skel_tvect = ctx->ofs_skel_tvect; //surf->ofs_skel_idx = ctx->ofs_skel_idx; //surf->ofs_skel_weight = ctx->ofs_skel_weight; /*index data*/ if (mmesh->vert_first+mmesh->vert_count > surf->numverts) surf->ofs_indexes = NULL, surf->numindexes = 0; //erk? else surf->ofs_indexes = Mod_HL2_LoadIndexes(ctx, &surf->numindexes, vmesh, l, firstvert+mmesh->vert_first); /*misc data*/ surf->geomset = 0; surf->geomid = 0; surf->contents = FTECONTENTS_BODY; surf->csurface.flags = 0; surf->surfaceid = 0; surf->mindist = 0; surf->maxdist = 0; if (surf != ctx->mod->meshinfo) surf[-1].nextsurf = surf; surf->nextsurf = NULL; surf++; } } firstvert += msurf->vertex_count; } } if (surf == ctx->mod->meshinfo) return false; return true; } void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross) { cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; } static qboolean Mod_HL2_LoadVVD(hl2parsecontext_t *ctx, const void *buffer, size_t fsize, unsigned int rev) { const hl2vvdheader_t *header = buffer; size_t lod; const hl2vvdvert_t *in; const vec4_t *it; if (fsize < sizeof(*header) || header->magic != (('I'<<0)|('D'<<8)|('S'<<16)|('V'<<24)) || header->version != 4 || header->revisionid != rev || header->lodverts_count[0] == 0) return false; { size_t v, numverts = ctx->numverts = header->lodverts_count[0]; vec2_t *st = ctx->ofs_st_array = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*st)*numverts); vecV_t *xyz = ctx->ofs_skel_xyz = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*xyz)*numverts); vec3_t *norm = ctx->ofs_skel_norm = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*norm)*numverts); vec3_t *sdir = ctx->ofs_skel_svect = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*sdir)*numverts); vec3_t *tdir = ctx->ofs_skel_tvect = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*tdir)*numverts); // byte_vec4_t *bone = ctx->lod[lod].ofs_skel_idx = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*bone)*numverts); // vec4_t *weight = ctx->lod[lod].ofs_skel_weight = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(*weight)*numverts); in = (const void*)((const char*)buffer+header->verts_offset); it = (const void*)((const char*)buffer+header->tangents_offset); for(v = 0; v < numverts; v++, in++, it++) { Vector2Copy(in->st, st[v]); VectorCopy(in->xyz, xyz[v]); VectorCopy(in->norm, norm[v]); // VectorCopy(in->bone, bone[v]); // bone[v][3] = bone[v][2]; //make sure its valid, and in cache. // VectorCopy(in->weight, weight[v]); // weight[v][3] = 0; //missing influences cannot influence. //tangents are compacted, and for some reason in a different part of the file. VectorCopy((*it), sdir[v]); CrossProduct(in->norm, (*it), tdir[v]); VectorScale(tdir[v], (*it)[3], tdir[v]); } } if (header->fixups_count) { size_t fixups = header->fixups_count, f, v; const hl2vvdfixup_t *fixup = (const hl2vvdfixup_t*)((const qbyte*)header+header->fixups_offset); for (lod = 0; lod < countof(ctx->lod) && lod < header->lod_count; lod++) { size_t numverts; for (numverts=0, f = 0; f < fixups; f++) { if (fixup[f].lod >= lod) numverts += fixup[f].numverts; } if (numverts != header->lodverts_count[lod]) continue; ctx->lod[lod].numfixups = numverts; ctx->lod[lod].fixup = plugfuncs->GMalloc(&ctx->mod->memgroup, sizeof(index_t)*numverts); for (numverts=0, f = 0; f < fixups; f++) { if (fixup[f].lod >= lod) for (v = 0; v < fixup[f].numverts; v++) ctx->lod[lod].fixup[numverts++] = fixup[f].sourcevert+v; } } } return true; } qboolean QDECL Mod_LoadHL2Model (model_t *mod, void *buffer, size_t fsize) { hl2parsecontext_t ctx = {mod}; const hl2mdlheader_t *header = buffer; void *vtx = NULL, *vvd = NULL; size_t vtxsize = 0, vvdsize = 0; char base[MAX_QPATH]; qboolean result; size_t i; const char *vtxpostfixes[] = { ".dx90.vtx", ".dx80.vtx", ".vtx", ".sw.vtx" }; const char *vvdpostfixes[] = { ".vvd" }; for (i = 0; !vtx && i < countof(vtxpostfixes); i++) { modfuncs->StripExtension(mod->name, base, sizeof(base)); Q_strncatz(base, vtxpostfixes[i], sizeof(base)); vtx = filefuncs->LoadFile(base, &vtxsize); } for (i = 0; !vvd && i < countof(vvdpostfixes); i++) { modfuncs->StripExtension(mod->name, base, sizeof(base)); Q_strncatz(base, vvdpostfixes[i], sizeof(base)); vvd = filefuncs->LoadFile(base, &vvdsize); } result = Mod_HL2_LoadVVD(&ctx, vvd, vvdsize, header->revisionid); result &= Mod_HL2_LoadVTX(&ctx, vtx, vtxsize, header->revisionid, header); plugfuncs->Free(vvd); plugfuncs->Free(vtx); VectorCopy(header->mins, mod->mins); VectorCopy(header->maxs, mod->maxs); mod->type = mod_alias; mod->radius = RadiusFromBounds(mod->mins, mod->maxs); modfuncs->BIH_BuildAlias(mod, mod->meshinfo); return result; } qboolean MDL_Init(void) { filefuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*filefuncs)); modfuncs = plugfuncs->GetEngineInterface(plugmodfuncs_name, sizeof(*modfuncs)); if (modfuncs && modfuncs->version < MODPLUGFUNCS_VERSION) modfuncs = NULL; if (modfuncs && filefuncs) { modfuncs->RegisterModelFormatMagic("Source model (v44)", "IDST\x2c\0\0\0",8, Mod_LoadHL2Model); modfuncs->RegisterModelFormatMagic("Source model (v45)", "IDST\x2d\0\0\0",8, Mod_LoadHL2Model); modfuncs->RegisterModelFormatMagic("Source model (v46)", "IDST\x2e\0\0\0",8, Mod_LoadHL2Model); modfuncs->RegisterModelFormatMagic("Source model (v47)", "IDST\x2f\0\0\0",8, Mod_LoadHL2Model); modfuncs->RegisterModelFormatMagic("Source model (v48)", "IDST\x30\0\0\0",8, Mod_LoadHL2Model); modfuncs->RegisterModelFormatMagic("Source model (v49)", "IDST\x31\0\0\0",8, Mod_LoadHL2Model); return true; } return false; }