#ifndef GLQUAKE #define GLQUAKE //this is shit. #endif #include "quakedef.h" #include "../plugin.h" #include "com_mesh.h" extern plugmodfuncs_t *modfuncs; extern plugfsfuncs_t *filefuncs; #define IQM_MAGIC "INTERQUAKEMODEL" #define IQM_VERSION2 2 struct iqmheader { char magic[16]; unsigned int version; unsigned int filesize; unsigned int flags; unsigned int num_text, ofs_text; unsigned int num_meshes, ofs_meshes; unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays; unsigned int num_triangles, ofs_triangles, ofs_adjacency; unsigned int num_joints, ofs_joints; unsigned int num_poses, ofs_poses; unsigned int num_anims, ofs_anims; unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds; unsigned int num_comment, ofs_comment; unsigned int num_extensions, ofs_extensions; }; struct iqmmesh { unsigned int name; unsigned int material; unsigned int first_vertex, num_vertexes; unsigned int first_triangle, num_triangles; }; enum { IQM_POSITION = 0, IQM_TEXCOORD = 1, IQM_NORMAL = 2, IQM_TANGENT = 3, IQM_BLENDINDEXES = 4, IQM_BLENDWEIGHTS = 5, IQM_COLOR = 6, IQM_CUSTOM = 0x10 }; enum { IQM_BYTE = 0, IQM_UBYTE = 1, IQM_SHORT = 2, IQM_USHORT = 3, IQM_INT = 4, IQM_UINT = 5, IQM_HALF = 6, IQM_FLOAT = 7, IQM_DOUBLE = 8, }; struct iqmtriangle { unsigned int vertex[3]; }; struct iqmjoint2 { unsigned int name; int parent; float translate[3], rotate[4], scale[3]; }; struct iqmpose2 { int parent; unsigned int mask; float channeloffset[10]; float channelscale[10]; }; struct iqmanim { unsigned int name; unsigned int first_frame, num_frames; float framerate; unsigned int flags; }; enum { IQM_LOOP = 1<<0 }; struct iqmvertexarray { unsigned int type; unsigned int flags; unsigned int format; unsigned int size; unsigned int offset; }; struct iqmbounds { float bbmin[3], bbmax[3]; float xyradius, radius; }; struct iqmextension { unsigned int name; unsigned int num_data, ofs_data; unsigned int ofs_extensions; // pointer to next extension. wtf is up with this? how is this not redundant due to ofs_data? }; struct iqmext_fte_mesh { unsigned int contents; //default CONTENTS_BODY unsigned int surfaceflags; //propagates to trace_surfaceflags unsigned int surfaceid; //the body reported to qc via trace_surface unsigned int geomset; unsigned int geomid; float mindist; float maxdist; }; struct iqmext_fte_event { unsigned int anim; float timestamp; unsigned int evcode; unsigned int evdata_str; //stringtable }; static 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 void Bone_To_PosQuat4(const float *matrix, float *pos, float *quat4, float *scale) { //I originally ripped this function out of DP. tweaked slightly. //http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm float origininvscale = 1; float origin[3]; float quat[4]; float quatscale; float trace = matrix[0*4+0] + matrix[1*4+1] + matrix[2*4+2]; origin[0] = matrix[0*4+0]; origin[1] = matrix[1*4+0]; origin[2] = matrix[2*4+0]; scale [0] = sqrt(DotProduct(origin,origin)); origin[0] = matrix[0*4+1]; origin[1] = matrix[1*4+1]; origin[2] = matrix[2*4+1]; scale [1] = sqrt(DotProduct(origin,origin)); origin[1] = matrix[0*4+2]; origin[1] = matrix[1*4+2]; origin[2] = matrix[2*4+2]; scale [2] = sqrt(DotProduct(origin,origin)); origin[0] = matrix[0*4+3]; origin[1] = matrix[1*4+3]; origin[2] = matrix[2*4+3]; if(trace > 0) { float r = sqrt(1.0f + trace), inv = 0.5f / r; quat[0] = (matrix[2*4+1] - matrix[1*4+2]) * inv; quat[1] = (matrix[0*4+2] - matrix[2*4+0]) * inv; quat[2] = (matrix[1*4+0] - matrix[0*4+1]) * inv; quat[3] = 0.5f * r; } else if(matrix[0*4+0] > matrix[1*4+1] && matrix[0*4+0] > matrix[2*4+2]) { float r = sqrt(1.0f + matrix[0*4+0] - matrix[1*4+1] - matrix[2*4+2]), inv = 0.5f / r; quat[0] = 0.5f * r; quat[1] = (matrix[1*4+0] + matrix[0*4+1]) * inv; quat[2] = (matrix[0*4+2] + matrix[2*4+0]) * inv; quat[3] = (matrix[2*4+1] - matrix[1*4+2]) * inv; } else if(matrix[1*4+1] > matrix[2*4+2]) { float r = sqrt(1.0f + matrix[1*4+1] - matrix[0*4+0] - matrix[2*4+2]), inv = 0.5f / r; quat[0] = (matrix[1*4+0] + matrix[0*4+1]) * inv; quat[1] = 0.5f * r; quat[2] = (matrix[2*4+1] + matrix[1*4+2]) * inv; quat[3] = (matrix[0*4+2] - matrix[2*4+0]) * inv; } else { float r = sqrt(1.0f + matrix[2*4+2] - matrix[0*4+0] - matrix[1*4+1]), inv = 0.5f / r; quat[0] = (matrix[0*4+2] + matrix[2*4+0]) * inv; quat[1] = (matrix[2*4+1] + matrix[1*4+2]) * inv; quat[2] = 0.5f * r; quat[3] = (matrix[1*4+0] - matrix[0*4+1]) * inv; } // normalize quaternion so that it is unit length quatscale = quat[0]*quat[0]+quat[1]*quat[1]+quat[2]*quat[2]+quat[3]*quat[3]; if (quatscale) quatscale = (quat[3] >= 0 ? -1.0f : 1.0f) / sqrt(quatscale); // use a negative scale on the quat because the above function produces a // positive quat[3] and canonical quaternions have negative quat[3] VectorScale(origin, origininvscale, pos); Vector4Scale(quat, quatscale, quat4); } void Mod_ExportIQM(char *fname, int flags, galiasinfo_t *mesh) { int i, j, k; vfsfile_t *f; galiasinfo_t *m; qbyte *data = NULL; char *otext; struct iqmvertexarray *ovarr; struct iqmtriangle *otri; struct iqmmesh *omesh; struct iqmjoint2 *ojoint = NULL; struct iqmanim *oanim = NULL; struct iqmpose2 *opose = NULL; struct { float min[10], max[10], scale[10]; int flags; } *poseinfo = NULL, *pi; //per bone struct { //pos3, quat4, scale3 float posquatscale[10]; //raw values, used to calibrate ranges } *posedata = NULL, *pd; //per bone*joint avec4_t *ivert; vec2_t *ist; vec3_t *overt; vec3_t *onorm = NULL; vec4_t *otang = NULL; vec2_t *ost; bone_vec4_t *oboneidx = NULL; byte_vec4_t *oboneweight = NULL; unsigned short *oposedata = NULL; struct iqmheader hdr = {IQM_MAGIC, IQM_VERSION2}, *oh; hdr.flags = flags; hdr.num_vertexarrays = 4; hdr.num_triangles = 0; hdr.ofs_adjacency = 0; //noone actually uses this... hdr.num_poses = 0; // hdr.ofs_poses = 0; hdr.num_anims = 0; // hdr.ofs_anims = 0; hdr.num_frames = 0; hdr.num_framechannels = 0; // hdr.ofs_frames = 0; // hdr.ofs_bounds = 0; hdr.num_comment = 0; // hdr.ofs_comment = 0; hdr.num_extensions = 0; // hdr.ofs_extensions = 0; hdr.num_joints = mesh->numbones; if (hdr.num_joints) { float *matrix; hdr.num_vertexarrays+= 2; hdr.num_anims = mesh->numanimations; for (i = 0; i < hdr.num_anims; i++) { hdr.num_text += strlen(mesh->ofsanimations[i].name)+1; hdr.num_frames += mesh->ofsanimations[i].numposes; } if (hdr.num_frames) { poseinfo = malloc(sizeof(*poseinfo)*hdr.num_joints); hdr.num_poses = hdr.num_joints; posedata = malloc(sizeof(*posedata)*hdr.num_joints*hdr.num_poses); //pull out the raw data and convert to the quats that we need for (i = 0, pd = posedata; i < hdr.num_anims; i++) for (j = 0, matrix = mesh->ofsanimations[i].boneofs; j < mesh->ofsanimations[i].numposes; j++) for (k = 0; k < hdr.num_joints; k++) { Bone_To_PosQuat4(matrix, &pd->posquatscale[0], &pd->posquatscale[3], &pd->posquatscale[7]); pd++; matrix+=12; } //now figure out each poseinfo's min+max for (i = 0, pi = poseinfo; i < hdr.num_joints; i++, pi++) for (j = 0, pd = posedata+i; j < hdr.num_poses; j++, pd+=hdr.num_joints) for (k = 0; k < 10; k++) { if (!i || pd->posquatscale[k] < pi->min[k]) pi->min[k] = pd->posquatscale[k]; if (!i || pi[i].max[k] < pd->posquatscale[k]) pi->max[k] = pd->posquatscale[k]; } //figure out the offset+range+flags for (i = 0, pi = poseinfo; i < hdr.num_joints; i++, pi++) for (k = 0; k < 10; k++) { pi->scale[k] = pi->max[k]-pi->min[k]; if (pi->scale[k] < 1e-10f) ; //total range is tiny and won't make any real difference, drop this channel for a small saving. else { pi->scale[k] /= 0xffffu; //compensate for the datatype's max pi->flags |= 1u<nextsurf) { //can't handle the surface if its verts are weird. if (m->shares_verts && m->shares_verts != hdr.num_meshes) continue; //can only handle one set of bones. if (m->shares_bones != 0) continue; //and must have the same number of bones. if (hdr.num_joints != m->numbones) continue; hdr.num_text += strlen(m->surfacename)+1; if (m->ofsskins && m->ofsskins->frame) hdr.num_text += strlen(m->ofsskins->frame->shadername)+1; hdr.num_triangles += m->numindexes/3; hdr.num_vertexes += m->numverts; hdr.num_meshes++; } //allocate our output buffer #define ALLOCSPACE hdr.filesize = 0; \ ALLOC(oh, sizeof(*oh)); \ ALLOC(otext, sizeof(*otext)*hdr.num_text); \ ALLOC(ovarr, sizeof(*ovarr)*hdr.num_vertexarrays); \ ALLOC(otri, sizeof(*otri)*hdr.num_triangles); \ ALLOC(overt, sizeof(*overt)*hdr.num_vertexes); \ ALLOC(ost, sizeof(*ost)*hdr.num_vertexes); \ if (mesh->ofs_skel_norm) {ALLOC(onorm, sizeof(*onorm)*hdr.num_vertexes);} \ if (mesh->ofs_skel_svect && mesh->ofs_skel_tvect) {ALLOC(otang, sizeof(*otang)*hdr.num_vertexes);} \ if (hdr.num_joints) {ALLOC(oboneweight, sizeof(*oboneweight)*hdr.num_vertexes);} \ if (hdr.num_joints) {ALLOC(oboneidx, sizeof(*oboneidx)*hdr.num_vertexes);} \ if (hdr.num_joints) {ALLOC(ojoint, sizeof(*ojoint)*hdr.num_joints);} \ if (hdr.num_anims) {ALLOC(oanim, sizeof(*oanim)*hdr.num_anims);} \ if (hdr.num_poses) {ALLOC(opose, sizeof(*opose)*hdr.num_poses);} \ if (hdr.num_framechannels) {ALLOC(oposedata, sizeof(*oposedata)*hdr.num_framechannels);} \ ALLOC(omesh, sizeof(*omesh)*hdr.num_meshes); #define ALLOC(p,s) p=(void*)(data+hdr.filesize);hdr.filesize+=s; ALLOCSPACE; //figure out how much space we need data = malloc(hdr.filesize); memset(data, 0xFE, hdr.filesize); ALLOCSPACE; //and assign everything to the right offsets. #undef ALLOC #undef ALLOCSPACE //copy over the preliminary header *oh = hdr; #define hdr hdr if (omesh) oh->ofs_meshes = (qbyte*)omesh-data; if (otext) oh->ofs_text = (qbyte*)otext-data; if (ovarr) oh->ofs_vertexarrays = (qbyte*)ovarr-data; if (otri) oh->ofs_triangles = (qbyte*)otri-data; if (ojoint) oh->ofs_joints = (qbyte*)ojoint-data; if (opose) oh->ofs_poses = (qbyte*)opose-data; if (oposedata) oh->ofs_frames = (qbyte*)oposedata-data; if (oanim) oh->ofs_anims = (qbyte*)oanim-data; //set up vertex array data. we might add some padding here, in case the extra data isn't availble. memset(ovarr, 0, sizeof(*ovarr)*oh->num_vertexarrays); oh->num_vertexarrays=0; ovarr->type = IQM_POSITION; ovarr->flags = 0; ovarr->format = IQM_FLOAT; ovarr->size = 3; ovarr->offset = (qbyte*)overt - data; ovarr++; oh->num_vertexarrays++; ovarr->type = IQM_TEXCOORD; ovarr->flags = 0; ovarr->format = IQM_FLOAT; ovarr->size = 2; ovarr->offset = (qbyte*)ost - data; ovarr++; oh->num_vertexarrays++; if (onorm) { ovarr->type = IQM_NORMAL; ovarr->flags = 0; ovarr->format = IQM_FLOAT; ovarr->size = 3; ovarr->offset = (qbyte*)onorm - data; ovarr++; oh->num_vertexarrays++; } if (otang) { ovarr->type = IQM_TANGENT; ovarr->flags = 0; ovarr->format = IQM_FLOAT; ovarr->size = 4; ovarr->offset = (qbyte*)otang - data; ovarr++; oh->num_vertexarrays++; } if (oboneidx) { ovarr->type = IQM_BLENDINDEXES; ovarr->flags = 0; ovarr->format = (MAX_BONES>65536)?IQM_UINT:((MAX_BONES>256)?IQM_USHORT:IQM_UBYTE); ovarr->size = 4; ovarr->offset = (qbyte*)oboneidx - data; ovarr++; oh->num_vertexarrays++; } if (oboneweight) { ovarr->type = IQM_BLENDWEIGHTS; ovarr->flags = 0; ovarr->format = IQM_BYTE; ovarr->size = 4; ovarr->offset = (qbyte*)oboneweight - data; ovarr++; oh->num_vertexarrays++; } /*if (orgba) { ovarr->type = IQM_COLOR; ovarr->flags = 0; ovarr->format = IQM_FLOAT; ovarr->size = 4; ovarr->offset = (qbyte*)orgba - data; ovarr++; oh->num_vertexarrays++; }*/ if (ojoint) { for (i = 0; i < hdr.num_joints; i++) { ojoint[i].parent = mesh->ofsbones[i].parent; ojoint[i].name = (qbyte*)otext-(data+oh->ofs_text); strcpy(otext, mesh->ofsbones[i].name); otext += strlen(otext)+1; Bone_To_PosQuat4(mesh->ofsbones[i].inverse, ojoint[i].translate, ojoint[i].rotate, ojoint[i].scale); } } if (opose) { int c; for (i = 0, pi=poseinfo; i < hdr.num_joints; i++, pi++) { opose[i].parent = mesh->ofsbones[i].parent; opose[i].mask = pi->flags; for (k = 0; k < 10; k++) { opose[i].channeloffset[k] = pi->min[k]; opose[i].channelscale[k] = pi->scale[k]; } for (j = 0, pd = posedata+i; j < hdr.num_frames; j++, pd+=hdr.num_joints) { for (k = 0; k < 10; k++) { if (opose[i].mask & (1<posquatscale[k]-pi->min[k])/pi->scale[k]; c = bound(0, c, 0xffff); //clamp it just in case (floats can be annoying) *oposedata++ = c; } } } } } if (oposedata) { for (i = 0; i < hdr.num_joints; i++) { opose[i].parent = mesh->ofsbones[i].parent; opose[i].mask = poseinfo[i].flags; for (k = 0; k < 10; k++) { opose[i].channeloffset[k] = poseinfo[i].min[k]; opose[i].channelscale[k] = poseinfo[i].scale[k]; } } } hdr.num_frames = 0; for (i = 0; i < hdr.num_anims; i++, oanim++) { oanim->first_frame = hdr.num_frames; oanim->num_frames = mesh->ofsanimations[i].numposes; oanim->framerate = mesh->ofsanimations[i].rate; oanim->flags = mesh->ofsanimations[i].loop?IQM_LOOP:0; oanim->name = (qbyte*)otext-(data+oh->ofs_text); strcpy(otext, mesh->ofsanimations[i].name); otext += strlen(otext)+1; hdr.num_frames += mesh->ofsanimations[i].numposes; } oh->num_anims = i; oh->num_frames = hdr.num_frames; //count needed data hdr.num_triangles = 0; hdr.num_vertexes = 0; for (m = mesh; m; m = m->nextsurf) { //can't handle the surface if its verts are weird. if (m->shares_verts && m->shares_verts != hdr.num_meshes) continue; //can only handle one set of bones. if (m->shares_bones != 0) continue; if (hdr.num_joints != m->numbones) continue; omesh->name = (qbyte*)otext-(data+oh->ofs_text); strcpy(otext, m->surfacename); otext += strlen(otext)+1; omesh->material = (qbyte*)otext-(data+oh->ofs_text); if (m->ofsskins && m->ofsskins->frame) strcpy(otext, m->ofsskins->frame->shadername); else strcpy(otext, ""); otext += strlen(otext)+1; omesh->first_vertex = hdr.num_vertexes; omesh->num_vertexes = m->numverts; omesh->first_triangle = hdr.num_triangles; omesh->num_triangles = m->numindexes/3; if (m->ofs_skel_xyz) ivert = m->ofs_skel_xyz; #ifdef NONSKELETALMODELS else if (m->numanimations && m->ofsanimations->numposes) ivert = m->ofsanimations->poseofs->ofsverts; #endif else ivert = NULL; if (ivert) { for (i = 0; i < omesh->num_vertexes; i++) VectorCopy (ivert[i], overt[i]); } else { for (i = 0; i < omesh->num_vertexes; i++) VectorClear (overt[i]); } overt += i; if (oboneidx) { bone_vec4_t *iidx = m->ofs_skel_idx; vec4_t *iweight = m->ofs_skel_weight; for (i = 0; i < omesh->num_vertexes; i++) { Vector4Copy(iidx[i], oboneidx[i]); Vector4Scale(iweight[i], 255, oboneweight[i]); } oboneidx += i; oboneweight += i; } if (ost) { ist = m->ofs_st_array; for (i = 0; i < omesh->num_vertexes; i++) Vector2Copy(ist[i], ost[i]); ost += i; } if (onorm) { vec3_t *inorm, *isdir, *itdir, t; if (m->ofs_skel_norm) { inorm = m->ofs_skel_norm; isdir = m->ofs_skel_svect; itdir = m->ofs_skel_tvect; } #ifdef NONSKELETALMODELS else if (m->numanimations && m->ofsanimations->numposes) { inorm = m->ofsanimations->poseofs->ofsnormals; isdir = m->ofsanimations->poseofs->ofssvector; itdir = m->ofsanimations->poseofs->ofstvector; } #endif else { inorm = NULL; isdir = NULL; itdir = NULL; } if (otang) { if (inorm && isdir && itdir) { for (i = 0; i < omesh->num_vertexes; i++) { VectorCopy (isdir[i], otang[i]); CrossProduct_(isdir[i], inorm[i], t); otang[i][3] = DotProduct(itdir[i], t)<0; //fourth part is simply a flag that says which direction the bitangent is in, should otherwise be a nice crossproduct result. } } else { for (i = 0; i < omesh->num_vertexes; i++) { VectorClear (otang[i]); otang[i][3] = 0; } } otang += i; } if (inorm) { for (i = 0; i < omesh->num_vertexes; i++) VectorCopy (ivert[i], onorm[i]); } else { for (i = 0; i < omesh->num_vertexes; i++) VectorClear (onorm[i]); } otang += i; onorm += i; } for (i = 0; i < omesh->num_triangles; i++) { otri[i].vertex[0] = m->ofs_indexes[i*3+0]+hdr.num_vertexes; otri[i].vertex[1] = m->ofs_indexes[i*3+1]+hdr.num_vertexes; otri[i].vertex[2] = m->ofs_indexes[i*3+2]+hdr.num_vertexes; } otri += i; hdr.num_vertexes += omesh->num_vertexes; hdr.num_triangles += omesh->num_triangles; } //and write it out f = filefuncs->OpenVFS(fname, "wb", FS_GAMEONLY); if (f) { VFS_WRITE(f, oh, oh->filesize); VFS_CLOSE(f); } free(data); free(poseinfo); #undef hdr }