/* Copyright (C) 1999-2006 Id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant 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 General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "qd_fmodel.h" #include "animcomp.h" #include "qd_skeletons.h" #include "skeletons.h" #include "qdata.h" #include "flex.h" #include "reference.h" #include /* ======================================================================== .FM triangle flexible model file format ======================================================================== */ //================================================================= #define NUMVERTEXNORMALS 162 extern float avertexnormals[NUMVERTEXNORMALS][3]; #define MAX_GROUPS 128 typedef struct { triangle_t triangle; int group; } trigroup_t; #define TRIVERT_DIST .1 typedef struct { int start_frame; int num_frames; int degrees; char *mat; char *ccomp; char *cbase; float *cscale; float *coffset; float trans[3]; float scale[3]; float bmin[3]; float bmax[3]; } fmgroup_t; //================================================================ // Initial fmheader_t fmheader; // Skin extern char g_skins[MAX_FM_SKINS][64]; // ST Coord extern fmstvert_t base_st[MAX_FM_VERTS]; // Triangles extern fmtriangle_t triangles[MAX_FM_TRIANGLES]; // Frames fmframe_t g_frames[MAX_FM_FRAMES]; //fmframe_t *g_FMframes; // GL Commands extern int commands[16384]; extern int numcommands; // // varibles set by commands // extern float scale_up; // set by $scale extern vec3_t adjust; // set by $origin extern int g_fixedwidth, g_fixedheight; // set by $skinsize extern char modelname[64]; // set by $modelname extern char *g_outputDir; // Mesh Nodes mesh_node_t *pmnodes = NULL; fmmeshnode_t mesh_nodes[MAX_FM_MESH_NODES]; fmgroup_t groups[MAX_GROUPS]; int num_groups; int frame_to_group[MAX_FM_FRAMES]; // // variables set by command line arguments // qboolean g_no_opimizations = false; // // base frame info // static int triangle_st[MAX_FM_TRIANGLES][3][2]; // number of gl vertices extern int numglverts; // indicates if a triangle has already been used in a glcmd extern int used[MAX_FM_TRIANGLES]; // indicates if a triangle has translucency in it or not static qboolean translucent[MAX_FM_TRIANGLES]; // main output file handle extern FILE *headerouthandle; // output sizes of buildst() static int skin_width, skin_height; // statistics static int total_skin_pixels; static int skin_pixels_used; int ShareVertex( trigroup_t trione, trigroup_t tritwo); float DistBetween(vec3_t point1, vec3_t point2); int GetNumTris( trigroup_t *tris, int group); void GetOneGroup(trigroup_t *tris, int grp, triangle_t* triangles); void ScaleTris( vec3_t min, vec3_t max, int Width, int Height, float* u, float* v, int verts); void NewDrawLine(int x1, int y1, int x2, int y2, unsigned char* picture, int width, int height); #ifndef _WIN32 void strupr(char *string) { int i; for (i=0 ; iverts[fmheader.num_xyz]; WriteHeader(modelouthandle, FM_HEADER_NAME, FM_HEADER_VER, sizeof(fmheader), &fmheader); // // write out the skin names // WriteHeader(modelouthandle, FM_SKIN_NAME, FM_SKIN_VER, fmheader.num_skins * MAX_FM_SKINNAME, g_skins); // // write out the texture coordinates // c_on = c_off = 0; for (i=0 ; iname, in->name); for (j=0 ; j<3 ; j++) { out->scale[j] = (in->maxs[j] - in->mins[j])/255; out->translate[j] = in->mins[j]; outFrames[i].scale[j] = out->scale[j]; outFrames[i].translate[j] = out->translate[j]; } for (j=0 ; jverts[j].lightnormalindex = in->v[j].lightnormalindex; for (k=0 ; k<3 ; k++) { // scale to byte values & min/max check v = Q_rint ( (in->v[j].v[k] - out->translate[k]) / out->scale[k] ); // clamp, so rounding doesn't wrap from 255.6 to 0 if (v > 255.0) v = 255.0; if (v < 0) v = 0; out->verts[j].v[k] = v; } } for (j=0 ; j<3 ; j++) { out->scale[j] = LittleFloat (out->scale[j]); out->translate[j] = LittleFloat (out->translate[j]); } SafeWrite (modelouthandle, out, fmheader.framesize); } // Go back and finish the header // WriteHeader(modelouthandle, FM_FRAME_NAME, FM_FRAME_VER, -1, NULL); } else { WriteHeader(modelouthandle, FM_SHORT_FRAME_NAME, FM_SHORT_FRAME_VER,FRAME_NAME_LEN*fmheader.num_frames, NULL); for (i=0 ; iname,FRAME_NAME_LEN); } WriteHeader(modelouthandle, FM_NORMAL_NAME, FM_NORMAL_VER,fmheader.num_xyz, NULL); in = &g_frames[0]; for (j=0 ; jv[j].lightnormalindex,1); } // // write out glcmds // WriteHeader(modelouthandle, FM_GLCMDS_NAME, FM_GLCMDS_VER, numcommands*4, commands); // // write out mesh nodes // for(i=0;idegrees*sizeof(char) char *ccomp; g->num_frames*g->degrees*sizeof(char) char *cbase; fmheader.num_xyz*3*sizeof(unsigned char) float *cscale; g->degrees*sizeof(float) float *coffset; g->degrees*sizeof(float) float trans[3]; 3*sizeof(float) float scale[3]; 3*sizeof(float) } fmgroup_t; */ int tmp,k; fmgroup_t *g; size=sizeof(int)+fmheader.num_frames*sizeof(int); for (k=0;kdegrees*sizeof(char); size+=g->num_frames*g->degrees*sizeof(char); size+=fmheader.num_xyz*3*sizeof(unsigned char); size+=g->degrees*sizeof(float); size+=g->degrees*sizeof(float); size+=12*sizeof(float); } WriteHeader(modelouthandle, FM_COMP_NAME, FM_COMP_VER,size, NULL); SafeWrite (modelouthandle,&num_groups,sizeof(int)); SafeWrite (modelouthandle,frame_to_group,sizeof(int)*fmheader.num_frames); for (k=0;kstart_frame); SafeWrite (modelouthandle,&tmp,sizeof(int)); tmp=LittleLong(g->num_frames); SafeWrite (modelouthandle,&tmp,sizeof(int)); tmp=LittleLong(g->degrees); SafeWrite (modelouthandle,&tmp,sizeof(int)); SafeWrite (modelouthandle,g->mat,fmheader.num_xyz*3*g->degrees*sizeof(char)); SafeWrite (modelouthandle,g->ccomp,g->num_frames*g->degrees*sizeof(char)); SafeWrite (modelouthandle,g->cbase,fmheader.num_xyz*3*sizeof(unsigned char)); SafeWrite (modelouthandle,g->cscale,g->degrees*sizeof(float)); SafeWrite (modelouthandle,g->coffset,g->degrees*sizeof(float)); SafeWrite (modelouthandle,g->trans,3*sizeof(float)); SafeWrite (modelouthandle,g->scale,3*sizeof(float)); SafeWrite (modelouthandle,g->bmin,3*sizeof(float)); SafeWrite (modelouthandle,g->bmax,3*sizeof(float)); free(g->mat); free(g->ccomp); free(g->cbase); free(g->cscale); free(g->coffset); } } // write the skeletal info if(g_skelModel.type != SKEL_NULL) { size = 0; temp = sizeof(int); // change this to a byte memcpy(data + size, &g_skelModel.type, temp); size += temp; // number of joints temp = sizeof(int); // change this to a byte memcpy(data + size, &numJointsInSkeleton[g_skelModel.type], temp); size += temp; // number of verts in each joint cluster temp = sizeof(int)*numJointsInSkeleton[g_skelModel.type]; // change this to shorts memcpy(data + size, &g_skelModel.new_num_verts[1], temp); size += temp; // cluster verts for(i = 0; i < numJointsInSkeleton[g_skelModel.type]; ++i) { current = g_skelModel.vertLists[i]; while(current) { temp = sizeof(int); // change this to a short memcpy(data + size, ¤t->data, temp); size += temp; toFree = current; current = current->next; free(toFree); // freeing of memory allocated in ReplaceClusterIndex called in Cmd_Base } } if(!num_groups) // joints are stored with regular verts for compressed models { framesWritten = true; temp = sizeof(int); // change this to a byte memcpy(data + size, &framesWritten, temp); size += temp; for (i = 0; i < fmheader.num_frames; ++i) { in = &g_frames[i]; for (j = 0 ; j < numJointsInSkeleton[g_skelModel.type]; ++j) { for (k=0 ; k<3 ; k++) { // scale to byte values & min/max check v = Q_rint ( (in->joints[j].placement.origin[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); // write out origin as a float since they arn't clamped temp = sizeof(float); // change this to a short assert(size+temp < DATA_SIZE); memcpy(data + size, &v, temp); size += temp; } for (k=0 ; k<3 ; k++) { v = Q_rint ( (in->joints[j].placement.direction[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); // write out origin as a float since they arn't clamped temp = sizeof(float); // change this to a short assert(size+temp < DATA_SIZE); memcpy(data + size, &v, temp); size += temp; } for (k=0 ; k<3 ; k++) { v = Q_rint ( (in->joints[j].placement.up[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); // write out origin as a float since they arn't clamped temp = sizeof(float); // change this to a short assert(size+temp < DATA_SIZE); memcpy(data + size, &v, temp); size += temp; } } } } else { temp = sizeof(int); // change this to a byte memcpy(data + size, &framesWritten, temp); size += temp; } WriteHeader(modelouthandle, FM_SKELETON_NAME, FM_SKELETON_VER, size, data); } if(g_skelModel.references != REF_NULL) { int refnum; size = 0; if (RefPointNum <= 0) { // Hard-coded labels refnum = numReferences[g_skelModel.references]; } else { // Labels indicated in QDT refnum = RefPointNum; } temp = sizeof(int); // change this to a byte memcpy(data2 + size, &g_skelModel.references, temp); size += temp; if(!num_groups) { framesWritten = true; temp = sizeof(int); // change this to a byte memcpy(data2 + size, &framesWritten, temp); size += temp; for (i = 0; i < fmheader.num_frames; ++i) { in = &g_frames[i]; for (j = 0 ; j < refnum; ++j) { for (k=0 ; k<3 ; k++) { // scale to byte values & min/max check v = Q_rint ( (in->references[j].placement.origin[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); // write out origin as a float since they arn't clamped temp = sizeof(float); // change this to a short assert(size+temp < DATA_SIZE); memcpy(data2 + size, &v, temp); size += temp; } for (k=0 ; k<3 ; k++) { v = Q_rint ( (in->references[j].placement.direction[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); // write out origin as a float since they arn't clamped temp = sizeof(float); // change this to a short assert(size+temp < DATA_SIZE); memcpy(data2 + size, &v, temp); size += temp; } for (k=0 ; k<3 ; k++) { v = Q_rint ( (in->references[j].placement.up[k] - outFrames[i].translate[k]) / outFrames[i].scale[k] ); // write out origin as a float since they arn't clamped temp = sizeof(float); // change this to a short assert(size+temp < DATA_SIZE); memcpy(data2 + size, &v, temp); size += temp; } } } } else // FINISH ME: references need to be stored with regular verts for compressed models { framesWritten = false; temp = sizeof(int); // change this to a byte memcpy(data2 + size, &framesWritten, temp); size += temp; } WriteHeader(modelouthandle, FM_REFERENCES_NAME, FM_REFERENCES_VER, size, data2); } } static void CompressFrames() { fmgroup_t *g; int i,j,k; fmframe_t *in; j=0; for (i=0;i=groups[j].start_frame+groups[j].num_frames&&jnum_frames,fmheader.num_xyz,g->degrees); for (i=0;inum_frames;i++) { in = &g_frames[i+g->start_frame]; for (j=0;jv[j].v[0],in->v[j].v[1],in->v[j].v[2]); } AnimCompressDoit(); g->mat= (char *) SafeMalloc(fmheader.num_xyz*3*g->degrees*sizeof(char), "CompressFrames"); g->ccomp=(char *) SafeMalloc(g->num_frames*g->degrees*sizeof(char), "CompressFrames"); g->cbase=(char *) SafeMalloc(fmheader.num_xyz*3*sizeof(unsigned char), "CompressFrames"); g->cscale=(float *) SafeMalloc(g->degrees*sizeof(float), "CompressFrames"); g->coffset=(float *) SafeMalloc(g->degrees*sizeof(float), "CompressFrames"); AnimCompressToBytes(g->trans,g->scale,g->mat,g->ccomp,g->cbase,g->cscale,g->coffset,g->bmin,g->bmax); AnimCompressEnd(); } } static void OptimizeVertices(void) { qboolean vert_used[MAX_FM_VERTS]; short vert_replacement[MAX_FM_VERTS]; int i,j,k,l,pos,bit,set_pos,set_bit; fmframe_t *in; qboolean Found; int num_unique; static IntListNode_t *newVertLists[NUM_CLUSTERS]; static int newNum_verts[NUM_CLUSTERS]; IntListNode_t *current, *next; printf("Optimizing vertices..."); memset(vert_used, 0, sizeof(vert_used)); if(g_skelModel.clustered == true) { memset(newNum_verts, 0, sizeof(newNum_verts)); memset(newVertLists, 0, sizeof(newVertLists)); } num_unique = 0; // search for common points among all the frames for (i=0 ; iv[j].v[0] == in->v[k].v[0] && in->v[j].v[1] == in->v[k].v[1] && in->v[j].v[2] == in->v[k].v[2]) { Found = true; vert_replacement[j] = k; break; } } if (!Found) { if (!vert_used[j]) { num_unique++; } vert_used[j] = true; } } } // recompute the light normals for (i=0 ; iv[j].vnorm.normalsum, in->v[k].vnorm.normalsum, in->v[k].vnorm.normalsum); in->v[k].vnorm.numnormals += in->v[j].vnorm.numnormals++; } } for (j=0 ; jv[j].vnorm.numnormals; if (!c) Error ("Vertex with no triangles attached"); VectorScale (in->v[j].vnorm.normalsum, 1.0/c, v); VectorNormalize (v, v); maxdot = -999999.0; maxdotindex = -1; for (k=0 ; k maxdot) { maxdot = dot; maxdotindex = k; } } in->v[j].lightnormalindex = maxdotindex; } } // create substitution list num_unique = 0; for(i=0;inext) { if(current->data == i) { IntListNode_t *current2; int m; qboolean added = false; for(m = 0, current2 = newVertLists[k]; m < newNum_verts[k+1]; ++m, current2 = current2->next) { if(current2->data == vert_replacement[i]) { added = true; break; } } if(!added) { ++newNum_verts[k+1]; next = newVertLists[k]; newVertLists[k] = (IntListNode_t *) SafeMalloc(sizeof(IntListNode_t), "OptimizeVertices"); // freed after model write out newVertLists[k]->data = vert_replacement[i]; newVertLists[k]->next = next; } break; } } } } } // substitute for (i=0 ; iv[vert_replacement[j]] = in->v[j]; } } for(i = 0; i < numJointsInSkeleton[g_skelModel.type]; ++i) { IntListNode_t *toFree; current = g_skelModel.vertLists[i]; while(current) { toFree = current; current = current->next; free(toFree); // freeing of memory allocated in ReplaceClusterIndex called in Cmd_Base } g_skelModel.vertLists[i] = newVertLists[i]; g_skelModel.new_num_verts[i+1] = newNum_verts[i+1]; } #ifndef NDEBUG for(k = 0; k < numJointsInSkeleton[g_skelModel.type]; ++k) { for(l = 0, current = g_skelModel.vertLists[k]; l < g_skelModel.new_num_verts[k+1]; ++l, current = current->next) { IntListNode_t *current2; int m; for(m = l+1, current2 = current->next; m < newNum_verts[k+1]; ++m, current2 = current2->next) { if(current->data == current2->data) { printf("Warning duplicate vertex: %d\n", current->data); break; } } } } #endif for(i=0;i> 3; bit = 1 << (i & 7 ); for (j=0 ; j<3 ; j++) { set_bit = set_pos = triangles[i].index_xyz[j] = vert_replacement[triangles[i].index_xyz[j]]; set_pos >>= 3; set_bit = 1 << (set_bit & 7); for(k=0;kreferences[j].placement.origin, in->v[index].v); index++; VectorCopy(in->references[j].placement.direction, in->v[index].v); index++; VectorCopy(in->references[j].placement.up, in->v[index].v); index++; } } fmheader.num_xyz += refnum*3; } // tack on the skeletal joint verts to the regular verts if(g_skelModel.type != SKEL_NULL) { fmframe_t *in; int index; for (i = 0; i < fmheader.num_frames; ++i) { in = &g_frames[i]; index = fmheader.num_xyz; for (j = 0 ; j < numJointsInSkeleton[g_skelModel.type]; ++j) { VectorCopy(in->joints[j].placement.origin, in->v[index].v); index++; VectorCopy(in->joints[j].placement.direction, in->v[index].v); index++; VectorCopy(in->joints[j].placement.up, in->v[index].v); index++; } } fmheader.num_xyz += numJointsInSkeleton[g_skelModel.type]*3; } CompressFrames(); } } /* =============== FinishModel =============== */ void FMFinishModel (void) { FILE *modelouthandle; int i,j,length,tris,verts,bit,pos,total_tris,total_verts; char name[1024]; int trans_count; if (!fmheader.num_frames) return; // // copy to release directory tree if doing a release build // if (g_release) { if (modelname[0]) sprintf (name, "%s", modelname); else sprintf (name, "%s/tris.fm", cdpartial); ReleaseFile (name); for (i=0 ; i> 3; bit = 1 << ((j) & 7 ); if (pmnodes[i].tris[pos] & bit) { tris++; } } for(j=0;j> 3; bit = 1 << ((j) & 7 ); if (pmnodes[i].verts[pos] & bit) { verts++; } } printf("%-33s %4d %5d\n",pmnodes[i].name,tris,verts); total_tris += tris; total_verts += verts; } printf("--------------------------------- ---- -----\n"); printf("%-33s %4d %5d\n","TOTALS",total_tris,total_verts); } } fclose (modelouthandle); // finish writing header file H_printf("\n"); // scale_up is usefull to allow step distances to be adjusted H_printf("#define MODEL_SCALE\t\t%f\n", scale_up); // mesh nodes if (fmheader.num_mesh_nodes) { H_printf("\n"); H_printf("#define NUM_MESH_NODES\t\t%d\n\n",fmheader.num_mesh_nodes); for(i=0;iindex_xyz[(startv)%3]; strip_xyz[1] = last->index_xyz[(startv+1)%3]; strip_xyz[2] = last->index_xyz[(startv+2)%3]; strip_st[0] = last->index_st[(startv)%3]; strip_st[1] = last->index_st[(startv+1)%3]; strip_st[2] = last->index_st[(startv+2)%3]; strip_tris[0] = starttri; stripcount = 1; m1 = last->index_xyz[(startv+2)%3]; st1 = last->index_st[(startv+2)%3]; m2 = last->index_xyz[(startv+1)%3]; st2 = last->index_st[(startv+1)%3]; // look for a matching triangle nexttri: for (j=starttri+1, check=&triangles[starttri+1] ; j> 3; bit = 1 << (j & 7 ); if (!(pmnodes[node].tris[pos] & bit)) { continue; } for (k=0 ; k<3 ; k++) { if (check->index_xyz[k] != m1) continue; if (check->index_st[k] != st1) continue; if (check->index_xyz[ (k+1)%3 ] != m2) continue; if (check->index_st[ (k+1)%3 ] != st2) continue; // this is the next part of the fan // if we can't use this triangle, this tristrip is done if (used[j] || translucent[j] != translucent[starttri]) goto done; // the new edge if (stripcount & 1) { m2 = check->index_xyz[ (k+2)%3 ]; st2 = check->index_st[ (k+2)%3 ]; } else { m1 = check->index_xyz[ (k+2)%3 ]; st1 = check->index_st[ (k+2)%3 ]; } strip_xyz[stripcount+2] = check->index_xyz[ (k+2)%3 ]; strip_st[stripcount+2] = check->index_st[ (k+2)%3 ]; strip_tris[stripcount] = j; stripcount++; used[j] = 2; goto nexttri; } } done: // clear the temp used flags for (j=starttri+1 ; jindex_xyz[(startv)%3]; strip_xyz[1] = last->index_xyz[(startv+1)%3]; strip_xyz[2] = last->index_xyz[(startv+2)%3]; strip_st[0] = last->index_st[(startv)%3]; strip_st[1] = last->index_st[(startv+1)%3]; strip_st[2] = last->index_st[(startv+2)%3]; strip_tris[0] = starttri; stripcount = 1; m1 = last->index_xyz[(startv+0)%3]; st1 = last->index_st[(startv+0)%3]; m2 = last->index_xyz[(startv+2)%3]; st2 = last->index_st[(startv+2)%3]; // look for a matching triangle nexttri: for (j=starttri+1, check=&triangles[starttri+1] ; j> 3; bit = 1 << (j & 7 ); if (!(pmnodes[node].tris[pos] & bit)) { continue; } for (k=0 ; k<3 ; k++) { if (check->index_xyz[k] != m1) continue; if (check->index_st[k] != st1) continue; if (check->index_xyz[ (k+1)%3 ] != m2) continue; if (check->index_st[ (k+1)%3 ] != st2) continue; // this is the next part of the fan // if we can't use this triangle, this tristrip is done if (used[j] || translucent[j] != translucent[starttri]) goto done; // the new edge m2 = check->index_xyz[ (k+2)%3 ]; st2 = check->index_st[ (k+2)%3 ]; strip_xyz[stripcount+2] = m2; strip_st[stripcount+2] = st2; strip_tris[stripcount] = j; stripcount++; used[j] = 2; goto nexttri; } } done: // clear the temp used flags for (j=starttri+1 ; j> 3; bit = 1 << (i & 7 ); if (!(pmnodes[l].tris[pos] & bit)) { continue; } // pick an unused triangle and start the trifan if (used[i] || trans_check != translucent[i]) { continue; } bestlen = 0; for (type = 0 ; type < 2 ; type++) // type = 1; { for (startv =0 ; startv < 3 ; startv++) { if (type == 1) len = StripLength (i, startv, fmheader.num_tris, l); else len = FanLength (i, startv, fmheader.num_tris, l); if (len > bestlen) { besttype = type; bestlen = len; for (j=0 ; j "-------------------------", // ? "-------------------------", // @ "-***-*---*******---**---*", // A "****-*---*****-*---*****-", "-*****----*----*-----****", "****-*---**---**---*****-", "******----****-*----*****", "******----****-*----*----", "-*****----*--***---*-****", "*---**---*******---**---*", "-***---*----*----*---***-", "----*----*----**---*-***-", "-*--*-*-*--**---*-*--*--*", "-*----*----*----*----****", "*---***-***-*-**---**---*", "*---***--**-*-**--***---*", "-***-*---**---**---*-***-", "****-*---*****-*----*----", "-***-*---**---*-***----**", "****-*---*****-*-*--*--**", "-*****-----***-----*****-", "*****--*----*----*----*--", "*---**---**---**---******", "*---**---**---*-*-*---*--", "*---**---**-*-***-***---*", "*---*-*-*---*---*-*-*---*", "*---**---*-*-*---*----*--", "*****---*---*---*---*****" // Z }; void DrawLine(int x1, int y1, int x2, int y2) { int dx, dy; int adx, ady; int count; float xfrac, yfrac, xstep, ystep; unsigned sx, sy; float u, v; dx = x2 - x1; dy = y2 - y1; adx = abs(dx); ady = abs(dy); count = adx > ady ? adx : ady; count++; if(count > 300) { printf("Bad count\n"); return; // don't ever hang up on bad data } xfrac = x1; yfrac = y1; xstep = (float)dx/count; ystep = (float)dy/count; switch(LineType) { case LINE_NORMAL: do { if(xfrac < SKINPAGE_WIDTH && yfrac < SKINPAGE_HEIGHT) { pic[(int)yfrac*SKINPAGE_WIDTH+(int)xfrac] = LineColor; } xfrac += xstep; yfrac += ystep; count--; } while (count > 0); break; case LINE_FAT: do { for (u=-0.1 ; u<=0.9 ; u+=0.999) { for (v=-0.1 ; v<=0.9 ; v+=0.999) { sx = xfrac+u; sy = yfrac+v; if(sx < SKINPAGE_WIDTH && sy < SKINPAGE_HEIGHT) { pic[sy*SKINPAGE_WIDTH+sx] = LineColor; } } } xfrac += xstep; yfrac += ystep; count--; } while (count > 0); break; case LINE_DOTTED: do { if(count&1 && xfrac < SKINPAGE_WIDTH && yfrac < SKINPAGE_HEIGHT) { pic[(int)yfrac*SKINPAGE_WIDTH+(int)xfrac] = LineColor; } xfrac += xstep; yfrac += ystep; count--; } while (count > 0); break; default: Error("Unknown %d.\n", LineType); } } //========================================================================== // // DrawCharacter // //========================================================================== static void DrawCharacter(int x, int y, int character) { int r, c; char *def; character = toupper(character); if(character < ASCII_SPACE || character > 'Z') { character = ASCII_SPACE; } character -= ASCII_SPACE; for(def = CharDefs[character], r = 0; r < 5; r++) { for(c = 0; c < 5; c++) { pic[(y+r)*SKINPAGE_WIDTH+x+c] = *def++ == '*' ? 255 : 0; } } } //========================================================================== // // DrawTextChar // //========================================================================== void DrawTextChar(int x, int y, char *text) { int c; while((c = *text++) != '\0') { DrawCharacter(x, y, c); x += 6; } } extern void DrawScreen(float s_scale, float t_scale, float iwidth, float iheight); //========================================================================== // ExtractDigit static int ExtractDigit(byte *pic, int x, int y) { int i; int r, c; char digString[32]; char *buffer; byte backColor; char **DigitDefs; backColor = pic[(SKINPAGE_HEIGHT - 1) * SKINPAGE_WIDTH]; DigitDefs = &CharDefs['0' - ASCII_SPACE]; buffer = digString; for(r = 0; r < 5; r++) { for(c = 0; c < 5; c++) { *buffer++ = (pic[(y + r) * SKINPAGE_WIDTH + x + c] == backColor) ? ' ' : '*'; } } *buffer = '\0'; for(i = 0; i < 10; i++) { if(strcmp(DigitDefs[i], digString) == 0) { return i; } } Error("Unable to extract scaling info from skin PCX."); return 0; } //========================================================================== // ExtractNumber int ExtractNumber(byte *pic, int x, int y) { return ExtractDigit(pic, x, y) * 100 + ExtractDigit(pic, x + 6, y) * 10 + ExtractDigit(pic, x + 12, y); } /* ============ BuildST Builds the triangle_st array for the base frame and fmheader.skinwidth / fmheader.skinheight FIXME: allow this to be loaded from a file for arbitrary mappings ============ */ static void BuildST (triangle_t *ptri, int numtri, qboolean DrawSkin) { int backface_flag; int i, j; int width, height, iwidth, iheight, swidth; float basex, basey; float scale; vec3_t mins, maxs; float *pbasevert; vec3_t vtemp1, vtemp2, normal; float s_scale, t_scale; float scWidth; float scHeight; int skinwidth; int skinheight; // // find bounds of all the verts on the base frame // ClearBounds (mins, maxs); backface_flag = false; if (ptri[0].HasUV) // if we have the uv already, we don't want to double up or scale { iwidth = ScaleWidth; iheight = ScaleHeight; t_scale = s_scale = 1.0; } else { for (i=0 ; i 0) { backface_flag = true; break; } } scWidth = ScaleWidth*SCALE_ADJUST_FACTOR; if (backface_flag) //we are doubling scWidth /= 2; scHeight = ScaleHeight*SCALE_ADJUST_FACTOR; scale = scWidth/width; if(height*scale >= scHeight) { scale = scHeight/height; } iwidth = ceil(width*scale)+4; iheight = ceil(height*scale)+4; s_scale = (float)(iwidth-4) / width; t_scale = (float)(iheight-4) / height; t_scale = s_scale; } if (DrawSkin) { if(backface_flag) DrawScreen(s_scale, t_scale, iwidth*2, iheight); else DrawScreen(s_scale, t_scale, iwidth, iheight); } if (backface_flag) skinwidth=iwidth*2; else skinwidth=iwidth; skinheight=iheight; /* if (!g_fixedwidth) { // old style scale = 8; if (width*scale >= 150) scale = 150.0 / width; if (height*scale >= 190) scale = 190.0 / height; s_scale = t_scale = scale; iwidth = ceil(width*s_scale); iheight = ceil(height*t_scale); iwidth += 4; iheight += 4; } else { // new style iwidth = g_fixedwidth / 2; iheight = g_fixedheight; s_scale = (float)(iwidth-4) / width; t_scale = (float)(iheight-4) / height; }*/ // // determine which side of each triangle to map the texture to // basey = 2; for (i=0 ; i 0) { basex = iwidth + 2; } else { basex = 2; } for (j=0 ; j<3 ; j++) { pbasevert = ptri[i].verts[j]; triangle_st[i][j][0] = Q_rint((pbasevert[0] - mins[0]) * s_scale + basex); triangle_st[i][j][1] = Q_rint((maxs[2] - pbasevert[2]) * t_scale + basey); } } if (DrawSkin) { DrawLine(triangle_st[i][0][0], triangle_st[i][0][1], triangle_st[i][1][0], triangle_st[i][1][1]); DrawLine(triangle_st[i][1][0], triangle_st[i][1][1], triangle_st[i][2][0], triangle_st[i][2][1]); DrawLine(triangle_st[i][2][0], triangle_st[i][2][1], triangle_st[i][0][0], triangle_st[i][0][1]); } } // make the width a multiple of 4; some hardware requires this, and it ensures // dword alignment for each scan swidth = iwidth; if(backface_flag) swidth *= 2; fmheader.skinwidth = (swidth + 3) & ~3; fmheader.skinheight = iheight; skin_width = iwidth; skin_height = iheight; } static void BuildNewST (triangle_t *ptri, int numtri, qboolean DrawSkin) { int i, j; for (i=0 ; i 1) goto split; d = lp2[1] - lp1[1]; if (d < -1 || d > 1) goto split; d = lp3[0] - lp2[0]; if (d < -1 || d > 1) goto split2; d = lp3[1] - lp2[1]; if (d < -1 || d > 1) goto split2; d = lp1[0] - lp3[0]; if (d < -1 || d > 1) goto split3; d = lp1[1] - lp3[1]; if (d < -1 || d > 1) { split3: temp = lp1; lp1 = lp3; lp3 = lp2; lp2 = temp; goto split; } return 0; // entire tri is filled split2: temp = lp1; lp1 = lp2; lp2 = lp3; lp3 = temp; split: // split this edge new[0] = (lp1[0] + lp2[0]) >> 1; new[1] = (lp1[1] + lp2[1]) >> 1; // draw the point if splitting a leading edge if (lp2[1] > lp1[1]) goto nodraw; if ((lp2[1] == lp1[1]) && (lp2[0] < lp1[0])) goto nodraw; if (SetPixel) { assert ((new[1]*BaseWidth) + new[0] < BaseWidth*BaseHeight); if (BaseTrueColor) { BasePixels[((new[1]*BaseWidth) + new[0]) * 4] = 1; } else { BasePixels[(new[1]*BaseWidth) + new[0]] = 1; } } else { if (TransPixels) { if (TransPixels[(new[1]*TransWidth) + new[0]] != 255) return 1; } else if (BaseTrueColor) { if (BasePixels[(((new[1]*BaseWidth) + new[0]) * 4) + 3] != 255) return 1; } else { // pixel = BasePixels[(new[1]*BaseWidth) + new[0]]; } } nodraw: // recursively continue if (CheckTransRecursiveTri(lp3, lp1, new)) return 1; return CheckTransRecursiveTri(lp3, new, lp2); } static void ReplaceClusterIndex(int newIndex, int oldindex, int **clusters, IntListNode_t **vertLists, int *num_verts, int *new_num_verts) { int i, j; IntListNode_t *next; for(j = 0; j < numJointsInSkeleton[g_skelModel.type]; ++j) { if(!clusters[j]) { continue; } for(i = 0; i < num_verts[j+1]; ++i) { if(clusters[j][i] == oldindex) { ++new_num_verts[j+1]; next = vertLists[j]; vertLists[j] = (IntListNode_t *) SafeMalloc(sizeof(IntListNode_t), "ReplaceClusterIndex"); // Currently freed in WriteJointedModelFile only vertLists[j]->data = newIndex; vertLists[j]->next = next; } } } } #define FUDGE_EPSILON 0.002 qboolean VectorFudgeCompare (vec3_t v1, vec3_t v2) { int i; for (i=0 ; i<3 ; i++) if (fabs(v1[i]-v2[i]) > FUDGE_EPSILON) return false; return true; } /* ================= Cmd_Base ================= */ void Cmd_FMBase (qboolean GetST) { triangle_t *ptri, *st_tri; int num_st_tris; int i, j, k, l; int x,y,z; // int time1; char file1[1024],file2[1024],trans_file[1024], stfile[1024], extension[256]; vec3_t base_xyz[MAX_FM_VERTS]; FILE *FH; int pos,bit; qboolean NewSkin; GetScriptToken (false); if (g_skipmodel || g_release || g_archive) return; printf ("---------------------\n"); sprintf (file1, "%s/%s.%s", cdarchive, token, trifileext); printf ("%s ", file1); ExpandPathAndArchive (file1); // Use the input filepath for this one. sprintf (file1, "%s/%s", cddir, token); // time1 = FileTime (file1); // if (time1 == -1) // Error ("%s doesn't exist", file1); // // load the base triangles // if (do3ds) Load3DSTriangleList (file1, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes); else LoadTriangleList (file1, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes); if (g_ignoreTriUV) { for (i=0;i> 3; bit = 1 << (i & 7 ); if (!(pmnodes[l].tris[pos] & bit)) { continue; } for (j=0 ; j<3 ; j++) { // get the xyz index for (k=0 ; k> 3; bit = 1 << (k & 7); pmnodes[l].verts[pos] |= bit; triangles[i].index_xyz[j] = k; // get the st index for (k=0 ; k= fmheader.num_mesh_nodes) { Error("Node '%s' not in base list!\n", token); } } free(pmnodes); pmnodes = newnodes; } //=============================================================== extern char *FindFrameFile (char *frame); /* =============== GrabFrame =============== */ void GrabFrame (char *frame) { triangle_t *ptri; int i, j; fmtrivert_t *ptrivert; int num_tris; char file1[1024]; fmframe_t *fr; int index_xyz; char *framefile; // the frame 'run1' will be looked for as either // run.1 or run1.tri, so the new alias sequence save // feature an be used framefile = FindFrameFile (frame); sprintf (file1, "%s/%s", cdarchive, framefile); ExpandPathAndArchive (file1); sprintf (file1, "%s/%s",cddir, framefile); printf ("grabbing %s ", file1); if (fmheader.num_frames >= MAX_FM_FRAMES) Error ("fmheader.num_frames >= MAX_FM_FRAMES"); fr = &g_frames[fmheader.num_frames]; fmheader.num_frames++; strcpy (fr->name, frame); // // load the frame // if (do3ds) Load3DSTriangleList (file1, &ptri, &num_tris, NULL, NULL); else LoadTriangleList (file1, &ptri, &num_tris, NULL, NULL); if (num_tris != fmheader.num_tris) Error ("%s: number of triangles (%d) doesn't match base frame (%d)\n", file1, num_tris, fmheader.num_tris); // // allocate storage for the frame's vertices // ptrivert = fr->v; for (i=0 ; imins, fr->maxs); // // store the frame's vertices in the same order as the base. This assumes the // triangles and vertices in this frame are in exactly the same order as in the // base // for (i=0 ; imins, fr->maxs); VectorAdd (ptrivert[index_xyz].vnorm.normalsum, normal, ptrivert[index_xyz].vnorm.normalsum); ptrivert[index_xyz].vnorm.numnormals++; } } // // calculate the vertex normals, match them to the template list, and store the // index of the best match // for (i=0 ; i maxdot) { maxdot = dot; maxdotindex = j; } } ptrivert[i].lightnormalindex = maxdotindex; } free (ptri); } /* =============== Cmd_Frame =============== */ void Cmd_FMFrame (void) { while (ScriptTokenAvailable()) { GetScriptToken (false); if (g_skipmodel) continue; if (g_release || g_archive) { fmheader.num_frames = 1; // don't skip the writeout continue; } H_printf("#define FRAME_%-16s\t%i\n", token, fmheader.num_frames); if((g_skelModel.type != SKEL_NULL) || (g_skelModel.references != REF_NULL)) { GrabModelTransform(token); } GrabFrame (token); if(g_skelModel.type != SKEL_NULL) { GrabSkeletalFrame(token); } if(g_skelModel.references != REF_NULL) { GrabReferencedFrame(token); } // need to add the up and dir points to the frame bounds here // using AddPointToBounds (ptrivert[index_xyz].v, fr->mins, fr->maxs); // then remove fudge in determining scale on frame write out } } /* =============== Cmd_Skin Skins aren't actually stored in the file, only a reference is saved out to the header file. =============== */ void Cmd_FMSkin (void) { byte *palette; byte *pixels; int width, height; byte *cropped; int y; char name[1024], savename[1024], transname[1024], extension[256]; miptex32_t *qtex32; int size; FILE *FH; qboolean TrueColor; GetScriptToken (false); if (fmheader.num_skins == MAX_FM_SKINS) Error ("fmheader.num_skins == MAX_FM_SKINS"); if (g_skipmodel) return; sprintf (name, "%s/%s", cdarchive, token); strcpy (name, ExpandPathAndArchive( name ) ); // sprintf (name, "%s/%s.lbm", cddir, token); if (ScriptTokenAvailable()) { GetScriptToken (false); sprintf (g_skins[fmheader.num_skins], "!%s", token); sprintf (savename, "%s!%s", g_outputDir, token); sprintf (transname, "%s!%s_a.pcx", gamedir, token); } else { sprintf (g_skins[fmheader.num_skins], "%s/!%s", cdpartial, token); sprintf (savename, "%s/!%s", g_outputDir, token); sprintf (transname, "%s/!%s_a.pcx", cddir, token); } fmheader.num_skins++; if (g_skipmodel || g_release || g_archive) return; // load the image printf ("loading %s\n", name); ExtractFileExtension (name, extension); if (extension[0] == 0) { strcat(name, ".pcx"); } TrueColor = LoadAnyImage (name, &pixels, &palette, &width, &height); // RemapZero (pixels, palette, width, height); // crop it to the proper size if (!TrueColor) { cropped = (byte *) SafeMalloc (fmheader.skinwidth*fmheader.skinheight, "Cmd_FMSkin"); for (y=0 ; y= 3); // figure out the best plane projections DOsvdPlane ((float*)vertices, num, (float *)&n, (float *)&base); if (DotProduct(aveNorm,n) < 0.0f) { VectorScale(n, -1.0f, n); } VectorNormalize(n,n); if (fabs(n[2]) < .57) { CrossProduct( zaxis, n, crossvect); VectorCopy(crossvect, u); } else { CrossProduct( yaxis, n, crossvect); VectorCopy(crossvect, u); } VectorNormalize(u,u); CrossProduct( n, u, crossvect); VectorCopy(crossvect, v); VectorNormalize(v,v); num = 0; for ( j = 0; j < 3; j++) { groupMin[j] = 1e30f; groupMax[j] = -1e30f; } for ( j = 0; j < numtris; j++) { for ( k = 0; k < 3; k++) { VectorCopy(grouptris[j].verts[k],v0); VectorSubtract(v0, base, v0); uvw[0] = DotProduct(v0, u); uvw[1] = DotProduct(v0, v); uvw[2] = DotProduct(v0, n); VectorCopy(uvw,uvs[num]); num++; for ( l = 0; l < 3; l++) { if (uvw[l] < groupMin[l]) { groupMin[l] = uvw[l]; } if (uvw[l] > groupMax[l]) { groupMax[l] = uvw[l]; } } } } xwidth = ceil(0 - groupMin[0]) + 2; // move right of origin and avoid overlap ywidth = ceil(0 - groupMin[1]) + 2; // move "above" origin for ( j=0; j < numverts; j++) { uFinal[finalcount] = uvs[j][0] + xwidth + xbase; vFinal[finalcount] = uvs[j][1] + ywidth; if (uFinal[finalcount] < uvwMin[0]) { uvwMin[0] = uFinal[finalcount]; } if (uFinal[finalcount] > uvwMax[0]) { uvwMax[0] = uFinal[finalcount]; } if (vFinal[finalcount] < uvwMin[1]) { uvwMin[1] = vFinal[finalcount]; } if (vFinal[finalcount] > uvwMax[1]) { uvwMax[1] = vFinal[finalcount]; } finalcount++; } fprintf(grpfile,"svdPlaned Group min: ( %f , %f )\n",groupMin[0] + xwidth + xbase, groupMin[1] + ywidth); fprintf(grpfile,"svdPlaned Group max: ( %f , %f )\n",groupMax[0] + xwidth + xbase, groupMax[1] + ywidth); finalcount = finalstart; for ( count = 0; count < numverts; count++) { fprintf(grpfile,"Vertex %d: ( %f , %f , %f )\n",count,vertices[count][0],vertices[count][1],vertices[count][2]); fprintf(grpfile,"svdPlaned: ( %f , %f )\n",uFinal[finalcount],vFinal[finalcount++]); } finalstart = finalcount; fprintf(grpfile,"\n"); free(vertices); free(uvs); free(grouptris); xbase += ceil(groupMax[0] - groupMin[0]) + 2; } fprintf(grpfile,"Global Min ( %f , %f )\n",uvwMin[0],uvwMin[1]); fprintf(grpfile,"Global Max ( %f , %f )\n",uvwMax[0],uvwMax[1]); ScaleTris(uvwMin, uvwMax, width, height, uFinal, vFinal, finalcount); for (k = 0; k < finalcount; k++) { fprintf(grpfile, "scaled vertex %d: ( %f , %f )\n",k,uFinal[k],vFinal[k]); } // i've got the array of vertices in uFinal and vFinal. Now I need to write them and draw lines datasize = width * height*sizeof(unsigned char); newpic = (unsigned char*)SafeMalloc(datasize, "NewGen"); memset(newpic,0,datasize); memset(pic_palette,0,sizeof(pic_palette)); pic_palette[767] = pic_palette[766] = pic_palette[765] = 255; k = 0; while (k < finalcount) { NewDrawLine(uFinal[k], vFinal[k], uFinal[k+1], vFinal[k+1], newpic, width, height); k++; NewDrawLine(uFinal[k], vFinal[k], uFinal[k+1], vFinal[k+1], newpic, width, height); k++; NewDrawLine(uFinal[k], vFinal[k], uFinal[k-2], vFinal[k-2], newpic, width, height); k++; fprintf(grpfile, "output tri with verts %d, %d, %d", k-2, k-1, k); } WritePCXfile (OutputName, newpic, width, height, pic_palette); fclose(grpfile); free(todo); free(done); free(triangles); free(newpic); return; } void NewDrawLine(int x1, int y1, int x2, int y2, unsigned char* picture, int width, int height) { long dx, dy; long adx, ady; long count; float xfrac, yfrac, xstep, ystep; unsigned long sx, sy; float u, v; dx = x2 - x1; dy = y2 - y1; adx = abs(dx); ady = abs(dy); count = adx > ady ? adx : ady; count++; if(count > 300) { printf("Bad count\n"); return; // don't ever hang up on bad data } xfrac = x1; yfrac = y1; xstep = (float)dx/count; ystep = (float)dy/count; switch(LineType) { case LINE_NORMAL: do { if(xfrac < width && yfrac < height) { picture[(long)yfrac*width+(long)xfrac] = LineColor; } xfrac += xstep; yfrac += ystep; count--; } while (count > 0); break; case LINE_FAT: do { for (u=-0.1 ; u<=0.9 ; u+=0.999) { for (v=-0.1 ; v<=0.9 ; v+=0.999) { sx = xfrac+u; sy = yfrac+v; if(sx < width && sy < height) { picture[sy*width+sx] = LineColor; } } } xfrac += xstep; yfrac += ystep; count--; } while (count > 0); break; case LINE_DOTTED: do { if(count&1 && xfrac < width && yfrac < height) { picture[(long)yfrac*width+(long)xfrac] = LineColor; } xfrac += xstep; yfrac += ystep; count--; } while (count > 0); break; default: Error("Unknown %d.\n", LineType); } } */ void ScaleTris( vec3_t min, vec3_t max, int Width, int Height, float* u, float* v, int verts) { int i; float hscale, vscale; float scale; hscale = max[0]; vscale = max[1]; hscale = (Width-2) / max[0]; vscale = (Height-2) / max[1]; scale = hscale; if (scale > vscale) { scale = vscale; } for ( i = 0; i 32) { Error ("Degrees of freedom out of range: %d",groups[num_groups].degrees); } } void Cmd_FMEndGroup (void) { groups[num_groups].num_frames = fmheader.num_frames - groups[num_groups].start_frame; if(num_groups < MAX_GROUPS - 1) { num_groups++; } else { Error("Number of compression groups exceded: %i\n", MAX_GROUPS); } }