+ const int *inelements;
+ int *outelements;
+ const float *intexcoord;
+ msurface_t *surface;
+
+ loadmodel->sortedmodelsurfaces[i] = i;
+ surface = loadmodel->data_surfaces + i;
+ surface->texture = loadmodel->data_textures + i;
+ surface->num_firsttriangle = meshtriangles;
+ surface->num_triangles = BigLong(dpmmesh->num_tris);
+ surface->num_firstvertex = meshvertices;
+ surface->num_vertices = BigLong(dpmmesh->num_verts);
+ meshvertices += surface->num_vertices;
+ meshtriangles += surface->num_triangles;
+
+ inelements = (int *) (pbase + BigLong(dpmmesh->ofs_indices));
+ outelements = loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3;
+ for (j = 0;j < surface->num_triangles;j++)
+ {
+ // swap element order to flip triangles, because Quake uses clockwise (rare) and dpm uses counterclockwise (standard)
+ outelements[0] = surface->num_firstvertex + BigLong(inelements[2]);
+ outelements[1] = surface->num_firstvertex + BigLong(inelements[1]);
+ outelements[2] = surface->num_firstvertex + BigLong(inelements[0]);
+ inelements += 3;
+ outelements += 3;
+ }
+
+ intexcoord = (float *) (pbase + BigLong(dpmmesh->ofs_texcoords));
+ for (j = 0;j < surface->num_vertices*2;j++)
+ loadmodel->surfmesh.data_texcoordtexture2f[j + surface->num_firstvertex * 2] = BigFloat(intexcoord[j]);
+
+ data = (unsigned char *) (pbase + BigLong(dpmmesh->ofs_verts));
+ for (j = surface->num_firstvertex;j < surface->num_firstvertex + surface->num_vertices;j++)
+ {
+ float sum;
+ int l;
+ int numweights = BigLong(((dpmvertex_t *)data)->numbones);
+ data += sizeof(dpmvertex_t);
+ for (k = 0;k < numweights;k++)
+ {
+ const dpmbonevert_t *vert = (dpmbonevert_t *) data;
+ int boneindex = BigLong(vert->bonenum);
+ const float *m = bonepose + 12 * boneindex;
+ float influence = BigFloat(vert->influence);
+ float relativeorigin[3], relativenormal[3];
+ relativeorigin[0] = BigFloat(vert->origin[0]);
+ relativeorigin[1] = BigFloat(vert->origin[1]);
+ relativeorigin[2] = BigFloat(vert->origin[2]);
+ relativenormal[0] = BigFloat(vert->normal[0]);
+ relativenormal[1] = BigFloat(vert->normal[1]);
+ relativenormal[2] = BigFloat(vert->normal[2]);
+ // blend the vertex bone weights into the base mesh
+ loadmodel->surfmesh.data_vertex3f[j*3+0] += relativeorigin[0] * m[0] + relativeorigin[1] * m[1] + relativeorigin[2] * m[ 2] + influence * m[ 3];
+ loadmodel->surfmesh.data_vertex3f[j*3+1] += relativeorigin[0] * m[4] + relativeorigin[1] * m[5] + relativeorigin[2] * m[ 6] + influence * m[ 7];
+ loadmodel->surfmesh.data_vertex3f[j*3+2] += relativeorigin[0] * m[8] + relativeorigin[1] * m[9] + relativeorigin[2] * m[10] + influence * m[11];
+ loadmodel->surfmesh.data_normal3f[j*3+0] += relativenormal[0] * m[0] + relativenormal[1] * m[1] + relativenormal[2] * m[ 2];
+ loadmodel->surfmesh.data_normal3f[j*3+1] += relativenormal[0] * m[4] + relativenormal[1] * m[5] + relativenormal[2] * m[ 6];
+ loadmodel->surfmesh.data_normal3f[j*3+2] += relativenormal[0] * m[8] + relativenormal[1] * m[9] + relativenormal[2] * m[10];
+ if (!k)
+ {
+ // store the first (and often only) weight
+ loadmodel->surfmesh.data_vertexweightinfluence4f[j*4+0] = influence;
+ loadmodel->surfmesh.data_vertexweightindex4i[j*4+0] = boneindex;
+ }
+ else
+ {
+ // sort the new weight into this vertex's weight table
+ // (which only accepts up to 4 bones per vertex)
+ for (l = 0;l < 4;l++)
+ {
+ if (loadmodel->surfmesh.data_vertexweightinfluence4f[j*4+l] < influence)
+ {
+ // move weaker influence weights out of the way first
+ int l2;
+ for (l2 = 3;l2 > l;l2--)
+ {
+ loadmodel->surfmesh.data_vertexweightinfluence4f[j*4+l2] = loadmodel->surfmesh.data_vertexweightinfluence4f[j*4+l2-1];
+ loadmodel->surfmesh.data_vertexweightindex4i[j*4+l2] = loadmodel->surfmesh.data_vertexweightindex4i[j*4+l2-1];
+ }
+ // store the new weight
+ loadmodel->surfmesh.data_vertexweightinfluence4f[j*4+l] = influence;
+ loadmodel->surfmesh.data_vertexweightindex4i[j*4+l] = boneindex;
+ break;
+ }
+ }
+ }
+ data += sizeof(dpmbonevert_t);
+ }
+ sum = 0;
+ for (l = 0;l < 4;l++)
+ sum += loadmodel->surfmesh.data_vertexweightinfluence4f[j*4+l];
+ if (sum && fabs(sum - 1) > (1.0f / 256.0f))
+ {
+ float f = 1.0f / sum;
+ for (l = 0;l < 4;l++)
+ loadmodel->surfmesh.data_vertexweightinfluence4f[j*4+l] *= f;
+ }
+ }
+
+ // since dpm models do not have named sections, reuse their shader name as the section name
+ Mod_BuildAliasSkinsFromSkinFiles(loadmodel->data_textures + i, skinfiles, dpmmesh->shadername, dpmmesh->shadername);
+
+ Mod_ValidateElements(loadmodel->surfmesh.data_element3i + surface->num_firsttriangle * 3, surface->num_triangles, surface->num_firstvertex, surface->num_vertices, __FILE__, __LINE__);
+ }
+ Z_Free(bonepose);
+ Mod_FreeSkinFiles(skinfiles);
+ Mod_MakeSortedSurfaces(loadmodel);
+
+ // compute all the mesh information that was not loaded from the file
+ if (loadmodel->surfmesh.data_element3s)
+ for (i = 0;i < loadmodel->surfmesh.num_triangles*3;i++)
+ loadmodel->surfmesh.data_element3s[i] = loadmodel->surfmesh.data_element3i[i];
+ Mod_BuildBaseBonePoses();
+ Mod_BuildTextureVectorsFromNormals(0, loadmodel->surfmesh.num_vertices, loadmodel->surfmesh.num_triangles, loadmodel->surfmesh.data_vertex3f, loadmodel->surfmesh.data_texcoordtexture2f, loadmodel->surfmesh.data_normal3f, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.data_svector3f, loadmodel->surfmesh.data_tvector3f, true);
+ Mod_BuildTriangleNeighbors(loadmodel->surfmesh.data_neighbor3i, loadmodel->surfmesh.data_element3i, loadmodel->surfmesh.num_triangles);
+
+ loadmodel->surfmesh.isanimated = loadmodel->numframes > 1 || loadmodel->animscenes[0].framecount > 1;
+}
+
+// no idea why PSK/PSA files contain weird quaternions but they do...
+#define PSKQUATNEGATIONS
+void Mod_PSKMODEL_Load(dp_model_t *mod, void *buffer, void *bufferend)
+{
+ int i, j, index, version, recordsize, numrecords, meshvertices, meshtriangles;
+ int numpnts, numvtxw, numfaces, nummatts, numbones, numrawweights, numanimbones, numanims, numanimkeys;
+ fs_offset_t filesize;
+ pskpnts_t *pnts;
+ pskvtxw_t *vtxw;
+ pskface_t *faces;
+ pskmatt_t *matts;
+ pskboneinfo_t *bones;
+ pskrawweights_t *rawweights;
+ pskboneinfo_t *animbones;
+ pskaniminfo_t *anims;
+ pskanimkeys_t *animkeys;
+ void *animfilebuffer, *animbuffer, *animbufferend;
+ unsigned char *data;
+ pskchunk_t *pchunk;
+ skinfile_t *skinfiles;
+ char animname[MAX_QPATH];
+ size_t size;
+
+ pchunk = (pskchunk_t *)buffer;
+ if (strcmp(pchunk->id, "ACTRHEAD"))
+ Host_Error ("Mod_PSKMODEL_Load: %s is not an Unreal Engine ActorX (.psk + .psa) model", loadmodel->name);
+
+ loadmodel->modeldatatypestring = "PSK";
+
+ loadmodel->type = mod_alias;
+ loadmodel->AnimateVertices = Mod_Skeletal_AnimateVertices;
+ loadmodel->DrawSky = NULL;
+ loadmodel->DrawAddWaterPlanes = NULL;
+ loadmodel->Draw = R_Q1BSP_Draw;
+ loadmodel->DrawDepth = R_Q1BSP_DrawDepth;
+ loadmodel->DrawDebug = R_Q1BSP_DrawDebug;
+ loadmodel->CompileShadowMap = R_Q1BSP_CompileShadowMap;
+ loadmodel->DrawShadowMap = R_Q1BSP_DrawShadowMap;
+ loadmodel->CompileShadowVolume = R_Q1BSP_CompileShadowVolume;
+ loadmodel->DrawShadowVolume = R_Q1BSP_DrawShadowVolume;
+ loadmodel->DrawLight = R_Q1BSP_DrawLight;
+ loadmodel->TraceBox = Mod_MDLMD2MD3_TraceBox;
+ loadmodel->TraceLine = Mod_MDLMD2MD3_TraceLine;
+ loadmodel->PointSuperContents = NULL;
+ loadmodel->synctype = ST_RAND;
+
+ FS_StripExtension(loadmodel->name, animname, sizeof(animname));
+ strlcat(animname, ".psa", sizeof(animname));
+ animbuffer = animfilebuffer = FS_LoadFile(animname, loadmodel->mempool, false, &filesize);
+ animbufferend = (void *)((unsigned char*)animbuffer + (int)filesize);
+ if (animbuffer == NULL)
+ Host_Error("%s: can't find .psa file (%s)", loadmodel->name, animname);
+
+ numpnts = 0;
+ pnts = NULL;
+ numvtxw = 0;
+ vtxw = NULL;
+ numfaces = 0;
+ faces = NULL;
+ nummatts = 0;
+ matts = NULL;
+ numbones = 0;
+ bones = NULL;
+ numrawweights = 0;
+ rawweights = NULL;
+ numanims = 0;
+ anims = NULL;
+ numanimkeys = 0;
+ animkeys = NULL;
+
+ while (buffer < bufferend)
+ {
+ pchunk = (pskchunk_t *)buffer;
+ buffer = (void *)((unsigned char *)buffer + sizeof(pskchunk_t));
+ version = LittleLong(pchunk->version);
+ recordsize = LittleLong(pchunk->recordsize);
+ numrecords = LittleLong(pchunk->numrecords);
+ if (developer.integer >= 100)
+ Con_Printf("%s: %s %x: %i * %i = %i\n", loadmodel->name, pchunk->id, version, recordsize, numrecords, recordsize * numrecords);
+ if (version != 0x1e83b9 && version != 0x1e9179 && version != 0x2e && version != 0x12f2bc && version != 0x12f2f0)
+ Con_Printf ("%s: chunk %s has unknown version %x (0x1e83b9, 0x1e9179, 0x2e, 0x12f2bc, 0x12f2f0 are currently supported), trying to load anyway!\n", loadmodel->name, pchunk->id, version);
+ if (!strcmp(pchunk->id, "ACTRHEAD"))
+ {
+ // nothing to do
+ }
+ else if (!strcmp(pchunk->id, "PNTS0000"))
+ {
+ pskpnts_t *p;
+ if (recordsize != sizeof(*p))
+ Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id);
+ // byteswap in place and keep the pointer
+ numpnts = numrecords;
+ pnts = (pskpnts_t *)buffer;
+ for (index = 0, p = (pskpnts_t *)buffer;index < numrecords;index++, p++)
+ {
+ p->origin[0] = LittleFloat(p->origin[0]);
+ p->origin[1] = LittleFloat(p->origin[1]);
+ p->origin[2] = LittleFloat(p->origin[2]);
+ }
+ buffer = p;
+ }
+ else if (!strcmp(pchunk->id, "VTXW0000"))
+ {
+ pskvtxw_t *p;
+ if (recordsize != sizeof(*p))
+ Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id);
+ // byteswap in place and keep the pointer
+ numvtxw = numrecords;
+ vtxw = (pskvtxw_t *)buffer;
+ for (index = 0, p = (pskvtxw_t *)buffer;index < numrecords;index++, p++)
+ {
+ p->pntsindex = LittleShort(p->pntsindex);
+ p->texcoord[0] = LittleFloat(p->texcoord[0]);
+ p->texcoord[1] = LittleFloat(p->texcoord[1]);
+ if (p->pntsindex >= numpnts)
+ {
+ Con_Printf("%s: vtxw->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts);
+ p->pntsindex = 0;
+ }
+ }
+ buffer = p;
+ }
+ else if (!strcmp(pchunk->id, "FACE0000"))
+ {
+ pskface_t *p;
+ if (recordsize != sizeof(*p))
+ Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id);
+ // byteswap in place and keep the pointer
+ numfaces = numrecords;
+ faces = (pskface_t *)buffer;
+ for (index = 0, p = (pskface_t *)buffer;index < numrecords;index++, p++)
+ {
+ p->vtxwindex[0] = LittleShort(p->vtxwindex[0]);
+ p->vtxwindex[1] = LittleShort(p->vtxwindex[1]);
+ p->vtxwindex[2] = LittleShort(p->vtxwindex[2]);
+ p->group = LittleLong(p->group);
+ if (p->vtxwindex[0] >= numvtxw)
+ {
+ Con_Printf("%s: face->vtxwindex[0] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[0], numvtxw);
+ p->vtxwindex[0] = 0;
+ }
+ if (p->vtxwindex[1] >= numvtxw)
+ {
+ Con_Printf("%s: face->vtxwindex[1] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[1], numvtxw);
+ p->vtxwindex[1] = 0;
+ }
+ if (p->vtxwindex[2] >= numvtxw)
+ {
+ Con_Printf("%s: face->vtxwindex[2] %i >= numvtxw %i\n", loadmodel->name, p->vtxwindex[2], numvtxw);
+ p->vtxwindex[2] = 0;
+ }
+ }
+ buffer = p;
+ }
+ else if (!strcmp(pchunk->id, "MATT0000"))
+ {
+ pskmatt_t *p;
+ if (recordsize != sizeof(*p))
+ Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id);
+ // byteswap in place and keep the pointer
+ nummatts = numrecords;
+ matts = (pskmatt_t *)buffer;
+ for (index = 0, p = (pskmatt_t *)buffer;index < numrecords;index++, p++)
+ {
+ // nothing to do
+ }
+ buffer = p;
+ }
+ else if (!strcmp(pchunk->id, "REFSKELT"))
+ {
+ pskboneinfo_t *p;
+ if (recordsize != sizeof(*p))
+ Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id);
+ // byteswap in place and keep the pointer
+ numbones = numrecords;
+ bones = (pskboneinfo_t *)buffer;
+ for (index = 0, p = (pskboneinfo_t *)buffer;index < numrecords;index++, p++)
+ {
+ p->numchildren = LittleLong(p->numchildren);
+ p->parent = LittleLong(p->parent);
+ p->basepose.quat[0] = LittleFloat(p->basepose.quat[0]);
+ p->basepose.quat[1] = LittleFloat(p->basepose.quat[1]);
+ p->basepose.quat[2] = LittleFloat(p->basepose.quat[2]);
+ p->basepose.quat[3] = LittleFloat(p->basepose.quat[3]);
+ p->basepose.origin[0] = LittleFloat(p->basepose.origin[0]);
+ p->basepose.origin[1] = LittleFloat(p->basepose.origin[1]);
+ p->basepose.origin[2] = LittleFloat(p->basepose.origin[2]);
+ p->basepose.unknown = LittleFloat(p->basepose.unknown);
+ p->basepose.size[0] = LittleFloat(p->basepose.size[0]);
+ p->basepose.size[1] = LittleFloat(p->basepose.size[1]);
+ p->basepose.size[2] = LittleFloat(p->basepose.size[2]);
+#ifdef PSKQUATNEGATIONS
+ if (index)
+ {
+ p->basepose.quat[0] *= -1;
+ p->basepose.quat[1] *= -1;
+ p->basepose.quat[2] *= -1;
+ }
+ else
+ {
+ p->basepose.quat[0] *= 1;
+ p->basepose.quat[1] *= -1;
+ p->basepose.quat[2] *= 1;
+ }
+#endif
+ if (p->parent < 0 || p->parent >= numbones)
+ {
+ Con_Printf("%s: bone->parent %i >= numbones %i\n", loadmodel->name, p->parent, numbones);
+ p->parent = 0;
+ }
+ }
+ buffer = p;
+ }
+ else if (!strcmp(pchunk->id, "RAWWEIGHTS"))
+ {
+ pskrawweights_t *p;
+ if (recordsize != sizeof(*p))
+ Host_Error("%s: %s has unsupported recordsize", loadmodel->name, pchunk->id);
+ // byteswap in place and keep the pointer
+ numrawweights = numrecords;
+ rawweights = (pskrawweights_t *)buffer;
+ for (index = 0, p = (pskrawweights_t *)buffer;index < numrecords;index++, p++)
+ {
+ p->weight = LittleFloat(p->weight);
+ p->pntsindex = LittleLong(p->pntsindex);
+ p->boneindex = LittleLong(p->boneindex);
+ if (p->pntsindex < 0 || p->pntsindex >= numpnts)
+ {
+ Con_Printf("%s: weight->pntsindex %i >= numpnts %i\n", loadmodel->name, p->pntsindex, numpnts);
+ p->pntsindex = 0;
+ }
+ if (p->boneindex < 0 || p->boneindex >= numbones)
+ {
+ Con_Printf("%s: weight->boneindex %i >= numbones %i\n", loadmodel->name, p->boneindex, numbones);
+ p->boneindex = 0;
+ }
+ }
+ buffer = p;
+ }