/* 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 ; i < strlen( string ); i++ ) toupper( string[i] ); return; } #endif //============================================================== /* =============== ClearModel =============== */ static void ClearModel( void ){ memset( &fmheader, 0, sizeof( fmheader ) ); modelname[0] = 0; scale_up = 1.0; VectorCopy( vec3_origin, adjust ); g_fixedwidth = g_fixedheight = 0; g_skipmodel = false; num_groups = 0; if ( pmnodes ) { free( pmnodes ); pmnodes = NULL; } ClearSkeletalModel(); } extern void H_printf( char *fmt, ... ); void WriteHeader( FILE *FH, char *Ident, int Version, int Size, void *Data ){ header_t header; static long pos = -1; long CurrentPos; if ( Size == 0 ) { // Don't write out empty packets return; } if ( pos != -1 ) { CurrentPos = ftell( FH ); Size = CurrentPos - pos + sizeof( header_t ); fseek( FH, pos, SEEK_SET ); pos = -2; } else if ( Size == -1 ) { pos = ftell( FH ); } memset( &header,0,sizeof( header ) ); strcpy( header.ident,Ident ); header.version = Version; header.size = Size; SafeWrite( FH, &header, sizeof( header ) ); if ( Data ) { SafeWrite( FH, Data, Size ); } if ( pos == -2 ) { pos = -1; fseek( FH, 0, SEEK_END ); } } /* ============ WriteModelFile ============ */ static void WriteModelFile( FILE *modelouthandle ){ int i; int j, k; fmframe_t *in; fmaliasframe_t *out; byte buffer[MAX_FM_VERTS * 4 + 128]; float v; int c_on, c_off; IntListNode_t *current, *toFree; qboolean framesWritten = false; size_t temp,size = 0; // probably should do this dynamically one of these days struct { float scale[3]; // multiply byte verts by this float translate[3]; // then add this } outFrames[MAX_FM_FRAMES]; #define DATA_SIZE 0x60000 // 384K had better be enough, particularly for the reference points byte data[DATA_SIZE]; byte data2[DATA_SIZE]; fmheader.num_glcmds = numcommands; fmheader.framesize = (int)&( (fmaliasframe_t *)0 )->verts[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 ; i < fmheader.num_st ; i++ ) { base_st[i].s = LittleShort( base_st[i].s ); base_st[i].t = LittleShort( base_st[i].t ); } WriteHeader( modelouthandle, FM_ST_NAME, FM_ST_VER, fmheader.num_st * sizeof( base_st[0] ), base_st ); // // write out the triangles // WriteHeader( modelouthandle, FM_TRI_NAME, FM_TRI_VER, fmheader.num_tris * sizeof( fmtriangle_t ), NULL ); for ( i = 0 ; i < fmheader.num_tris ; i++ ) { int j; fmtriangle_t tri; for ( j = 0 ; j < 3 ; j++ ) { tri.index_xyz[j] = LittleShort( triangles[i].index_xyz[j] ); tri.index_st[j] = LittleShort( triangles[i].index_st[j] ); } SafeWrite( modelouthandle, &tri, sizeof( tri ) ); } if ( !num_groups ) { // // write out the frames // WriteHeader( modelouthandle, FM_FRAME_NAME, FM_FRAME_VER, fmheader.num_frames * fmheader.framesize, NULL ); // WriteHeader(modelouthandle, FM_FRAME_NAME, FM_FRAME_VER, -1, NULL); for ( i = 0 ; i < fmheader.num_frames ; i++ ) { in = &g_frames[i]; out = (fmaliasframe_t *)buffer; strcpy( out->name, 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 ; j < fmheader.num_xyz ; j++ ) { // all of these are byte values, so no need to deal with endianness out->verts[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 ; i < fmheader.num_frames ; i++ ) { in = &g_frames[i]; SafeWrite( modelouthandle,in->name,FRAME_NAME_LEN ); } WriteHeader( modelouthandle, FM_NORMAL_NAME, FM_NORMAL_VER,fmheader.num_xyz, NULL ); in = &g_frames[0]; for ( j = 0 ; j < fmheader.num_xyz ; j++ ) SafeWrite( modelouthandle,&in->v[j].lightnormalindex,1 ); } // // write out glcmds // WriteHeader( modelouthandle, FM_GLCMDS_NAME, FM_GLCMDS_VER, numcommands * 4, commands ); // // write out mesh nodes // for ( i = 0; i < fmheader.num_mesh_nodes; i++ ) { memcpy( mesh_nodes[i].tris, pmnodes[i].tris, sizeof( mesh_nodes[i].tris ) ); memcpy( mesh_nodes[i].verts, pmnodes[i].verts, sizeof( mesh_nodes[i].verts ) ); mesh_nodes[i].start_glcmds = LittleShort( (short)pmnodes[i].start_glcmds ); mesh_nodes[i].num_glcmds = LittleShort( (short)pmnodes[i].num_glcmds ); } WriteHeader( modelouthandle, FM_MESH_NAME, FM_MESH_VER, sizeof( fmmeshnode_t ) * fmheader.num_mesh_nodes, mesh_nodes ); if ( num_groups ) { /* typedef struct { int start_frame; int num_frames; int degrees; char *mat; fmheader.num_xyz*3*g->degrees*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; k < num_groups; k++ ) { g = &groups[k]; size += sizeof( int ) * 3; size += fmheader.num_xyz * 3 * g->degrees * 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; k < num_groups; k++ ) { g = &groups[k]; tmp = LittleLong( g->start_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 < fmheader.num_frames; i++ ) { while ( i >= groups[j].start_frame + groups[j].num_frames && j < num_groups - 1 ) j++; frame_to_group[i] = j; } for ( k = 0; k < num_groups; k++ ) { g = &groups[k]; printf( "\nCompressing Frames for group %i...\n", k ); AnimCompressInit( g->num_frames,fmheader.num_xyz,g->degrees ); for ( i = 0; i < g->num_frames; i++ ) { in = &g_frames[i + g->start_frame]; for ( j = 0; j < fmheader.num_xyz; j++ ) AnimSetFrame( i,j,in->v[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 ; i < fmheader.num_frames ; i++ ) { in = &g_frames[i]; for ( j = 0; j < fmheader.num_xyz; j++ ) { for ( k = 0,Found = false; k < j; k++ ) { // starting from the beginning always ensures vert_replacement points to the first point in the array if ( in->v[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 ; i < fmheader.num_frames ; i++ ) { in = &g_frames[i]; for ( j = 0; j < fmheader.num_xyz; j++ ) { if ( !vert_used[j] ) { k = vert_replacement[j]; VectorAdd( in->v[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 ; j < fmheader.num_xyz ; j++ ) { vec3_t v; float maxdot; int maxdotindex; int c; c = in->v[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 < NUMVERTEXNORMALS ; k++ ) { float dot; dot = DotProduct( v, avertexnormals[k] ); if ( dot > maxdot ) { maxdot = dot; maxdotindex = k; } } in->v[j].lightnormalindex = maxdotindex; } } // create substitution list num_unique = 0; for ( i = 0; i < fmheader.num_xyz; i++ ) { if ( vert_used[i] ) { vert_replacement[i] = num_unique; num_unique++; } else { vert_replacement[i] = vert_replacement[vert_replacement[i]]; } // vert_replacement[i] is the new index, i is the old index // need to add the new index to the cluster list if old index was in it if ( g_skelModel.clustered == true ) { 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 ) { 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 ; i < fmheader.num_frames ; i++ ) { in = &g_frames[i]; for ( j = 0; j < fmheader.num_xyz; j++ ) { in->v[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 < fmheader.num_mesh_nodes; i++ ) { // reset the vert bits memset( pmnodes[i].verts,0,sizeof( pmnodes[i].verts ) ); } // repleace the master triangle list vertex indexes and update the vert bits for each mesh node for ( i = 0 ; i < fmheader.num_tris ; i++ ) { pos = 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; k < fmheader.num_mesh_nodes; k++ ) { if ( !( pmnodes[k].tris[pos] & bit ) ) { continue; } pmnodes[k].verts[set_pos] |= set_bit; } } } for ( i = 0; i < numcommands; i++ ) { j = commands[i]; if ( !j ) { continue; } j = abs( j ); for ( i++; j; j--,i += 3 ) { commands[i + 2] = vert_replacement[commands[i + 2]]; } i--; } printf( "Reduced by %d\n",fmheader.num_xyz - num_unique ); fmheader.num_xyz = num_unique; if ( num_groups ) { // tack on the reference verts to the regular verts if ( g_skelModel.references != REF_NULL ) { fmframe_t *in; int index; int refnum; if ( RefPointNum <= 0 ) { // Hard-coded labels refnum = numReferences[g_skelModel.references]; } else { // Labels indicated in QDT refnum = RefPointNum; } for ( i = 0; i < fmheader.num_frames; ++i ) { in = &g_frames[i]; index = fmheader.num_xyz; for ( j = 0 ; j < refnum; ++j ) { VectorCopy( in->references[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 < fmheader.num_skins ; i++ ) { ReleaseFile( g_skins[i] ); } fmheader.num_frames = 0; return; } printf( "\n" ); trans_count = 0; for ( i = 0; i < fmheader.num_tris; i++ ) if ( translucent[i] ) { trans_count++; } if ( !g_no_opimizations ) { OptimizeVertices(); } // // write the model output file // if ( modelname[0] ) { sprintf( name, "%s%s", g_outputDir, modelname ); } else{ sprintf( name, "%s/tris.fm", g_outputDir ); } printf( "saving to %s\n", name ); CreatePath( name ); modelouthandle = SafeOpenWrite( name ); WriteModelFile( modelouthandle ); printf( "%3dx%3d skin\n", fmheader.skinwidth, fmheader.skinheight ); printf( "First frame boundaries:\n" ); printf( " minimum x: %3f\n", g_frames[0].mins[0] ); printf( " maximum x: %3f\n", g_frames[0].maxs[0] ); printf( " minimum y: %3f\n", g_frames[0].mins[1] ); printf( " maximum y: %3f\n", g_frames[0].maxs[1] ); printf( " minimum z: %3f\n", g_frames[0].mins[2] ); printf( " maximum z: %3f\n", g_frames[0].maxs[2] ); printf( "%4d vertices\n", fmheader.num_xyz ); printf( "%4d triangles, %4d of them translucent\n", fmheader.num_tris, trans_count ); printf( "%4d frame\n", fmheader.num_frames ); printf( "%4d glverts\n", numglverts ); printf( "%4d glcmd\n", fmheader.num_glcmds ); printf( "%4d skins\n", fmheader.num_skins ); printf( "%4d mesh nodes\n", fmheader.num_mesh_nodes ); printf( "wasted pixels: %d / %d (%5.2f Percent)\n",total_skin_pixels - skin_pixels_used, total_skin_pixels, (double)( total_skin_pixels - skin_pixels_used ) / (double)total_skin_pixels * 100.0 ); printf( "file size: %d\n", (int)ftell( modelouthandle ) ); printf( "---------------------\n" ); if ( g_verbose ) { if ( fmheader.num_mesh_nodes ) { total_tris = total_verts = 0; printf( "Node Name Tris Verts\n" ); printf( "--------------------------------- ---- -----\n" ); for ( i = 0; i < fmheader.num_mesh_nodes; i++ ) { tris = 0; verts = 0; for ( j = 0; j < MAXTRIANGLES; j++ ) { pos = ( j ) >> 3; bit = 1 << ( ( j ) & 7 ); if ( pmnodes[i].tris[pos] & bit ) { tris++; } } for ( j = 0; j < MAX_FM_VERTS; j++ ) { pos = ( 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; i < fmheader.num_mesh_nodes; i++ ) { strcpy( name, pmnodes[i].name ); strupr( name ); length = strlen( name ); for ( j = 0; j < length; j++ ) { if ( name[j] == ' ' ) { name[j] = '_'; } } H_printf( "#define MESH_%s\t\t%d\n", name, i ); } } fclose( headerouthandle ); headerouthandle = NULL; free( pmnodes ); } /* ================================================================= ALIAS MODEL DISPLAY LIST GENERATION ================================================================= */ extern int strip_xyz[128]; extern int strip_st[128]; extern int strip_tris[128]; extern int stripcount; /* ================ StripLength ================ */ static int StripLength( int starttri, int startv, int num_tris, int node ){ int m1, m2; int st1, st2; int j; fmtriangle_t *last, *check; int k; int pos, bit; used[starttri] = 2; last = &triangles[starttri]; strip_xyz[0] = last->index_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 < num_tris ; j++, check++ ) { pos = 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 ; j < num_tris ; j++ ) if ( used[j] == 2 ) { used[j] = 0; } return stripcount; } /* =========== FanLength =========== */ static int FanLength( int starttri, int startv, int num_tris, int node ){ int m1, m2; int st1, st2; int j; fmtriangle_t *last, *check; int k; int pos, bit; used[starttri] = 2; last = &triangles[starttri]; strip_xyz[0] = last->index_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 < num_tris ; j++, check++ ) { pos = 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 < num_tris ; j++ ) if ( used[j] == 2 ) { used[j] = 0; } return stripcount; } /* ================ BuildGlCmds Generate a list of trifans or strips for the model, which holds for all frames ================ */ static void BuildGlCmds( void ){ int i, j, k, l; int startv; float s, t; int len, bestlen, besttype; int best_xyz[1024]; int best_st[1024]; int best_tris[1024]; int type; int trans_check; int bit,pos; // // build tristrips // numcommands = 0; numglverts = 0; for ( l = 0; l < fmheader.num_mesh_nodes; l++ ) { memset( used, 0, sizeof( used ) ); pmnodes[l].start_glcmds = numcommands; for ( trans_check = 0; trans_check < 2; trans_check++ ) { for ( i = 0 ; i < fmheader.num_tris ; i++ ) { pos = i >> 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 < bestlen + 2 ; j++ ) { best_st[j] = strip_st[j]; best_xyz[j] = strip_xyz[j]; } for ( j = 0 ; j < bestlen ; j++ ) best_tris[j] = strip_tris[j]; } } } // mark the tris on the best strip/fan as used for ( j = 0 ; j < bestlen ; j++ ) used[best_tris[j]] = 1; if ( besttype == 1 ) { commands[numcommands++] = ( bestlen + 2 ); } else{ commands[numcommands++] = -( bestlen + 2 ); } numglverts += bestlen + 2; for ( j = 0 ; j < bestlen + 2 ; j++ ) { // emit a vertex into the reorder buffer k = best_st[j]; // emit s/t coords into the commands stream s = base_st[k].s; t = base_st[k].t; s = ( s ) / fmheader.skinwidth; t = ( t ) / fmheader.skinheight; *(float *)&commands[numcommands++] = s; *(float *)&commands[numcommands++] = t; *(int *)&commands[numcommands++] = best_xyz[j]; } } } commands[numcommands++] = 0; // end of list marker pmnodes[l].num_glcmds = numcommands - pmnodes[l].start_glcmds; } } /* =============================================================== BASE FRAME SETUP =============================================================== */ #define LINE_NORMAL 1 #define LINE_FAT 2 #define LINE_DOTTED 3 #define ASCII_SPACE 32 int LineType = LINE_NORMAL; extern unsigned char pic[SKINPAGE_HEIGHT * SKINPAGE_WIDTH], pic_palette[768]; unsigned char LineColor = 255; int ScaleWidth, ScaleHeight; static char *CharDefs[] = { "-------------------------", "-------------------------", // ! "-------------------------", // " "-------------------------", // # "-------------------------", // $ "-------------------------", // % "-------------------------", // & "--*----*-----------------", // ' "-*---*----*----*-----*---", // ( "*-----*----*----*---*----", // ) "-----*--*--**---**--*--*-", // * "-------------------------", // + "----------------**--**---", // , "-------------------------", // - "----------------**---**--", // . "-------------------------", // / " *** * *** * *** * *** ", // 0 " * ** * * * ", "**** * *** * *****", "**** * *** ***** ", " ** * * * * ***** * ", "**** * **** ***** ", " *** * **** * * *** ", "***** * * * * ", " *** * * *** * * *** ", " *** * * **** * *** ", // 9 "-**---**--------**---**--", // : "-------------------------", // ; "-------------------------", // < "-------------------------", // = "-------------------------", // > "-------------------------", // ? "-------------------------", // @ "-***-*---*******---**---*", // 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 < numtri ; i++ ) for ( j = 0 ; j < 3 ; j++ ) AddPointToBounds( ptri[i].verts[j], mins, maxs ); for ( i = 0 ; i < 3 ; i++ ) { mins[i] = floor( mins[i] ); maxs[i] = ceil( maxs[i] ); } width = maxs[0] - mins[0]; height = maxs[2] - mins[2]; for ( i = 0 ; i < numtri ; i++ ) { VectorSubtract( ptri[i].verts[0], ptri[i].verts[1], vtemp1 ); VectorSubtract( ptri[i].verts[2], ptri[i].verts[1], vtemp2 ); CrossProduct( vtemp1, vtemp2, normal ); if ( normal[1] > 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 < numtri ; i++ ) { if ( ptri[i].HasUV ) { for ( j = 0 ; j < 3 ; j++ ) { triangle_st[i][j][0] = Q_rint( ptri[i].uv[j][0] * skinwidth ); triangle_st[i][j][1] = Q_rint( ( 1.0f - ptri[i].uv[j][1] ) * skinheight ); } } else { VectorSubtract( ptri[i].verts[0], ptri[i].verts[1], vtemp1 ); VectorSubtract( ptri[i].verts[2], ptri[i].verts[1], vtemp2 ); CrossProduct( vtemp1, vtemp2, normal ); if ( normal[1] > 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 < numtri ; i++ ) { if ( ptri[i].HasUV ) { for ( j = 0 ; j < 3 ; j++ ) { triangle_st[i][j][0] = Q_rint( ptri[i].uv[j][0] * ( ScaleWidth - 1 ) ); triangle_st[i][j][1] = Q_rint( ( 1.0f - ptri[i].uv[j][1] ) * ( ScaleHeight - 1 ) ); } } 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 fmheader.skinwidth = ( ScaleWidth + 3 ) & ~3; fmheader.skinheight = ScaleHeight; skin_width = ScaleWidth; skin_height = ScaleHeight; } byte *BasePalette; byte *BasePixels,*TransPixels; int BaseWidth, BaseHeight, TransWidth, TransHeight; qboolean BaseTrueColor; static qboolean SetPixel = false; int CheckTransRecursiveTri( int *lp1, int *lp2, int *lp3 ){ int *temp; int d; int new[2]; d = lp2[0] - lp1[0]; if ( d < -1 || d > 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 < fmheader.num_tris; i++ ) { ptri[i].HasUV = 0; } } GetScriptToken( false ); sprintf( file2, "%s/%s", cddir, token ); sprintf( trans_file, "%s/!%s_a.pcx", cddir, token ); ExtractFileExtension( file2, extension ); if ( extension[0] == 0 ) { strcat( file2, ".pcx" ); } printf( "skin: %s\n", file2 ); BaseTrueColor = LoadAnyImage( file2, &BasePixels, &BasePalette, &BaseWidth, &BaseHeight ); NewSkin = false; if ( BaseWidth != SKINPAGE_WIDTH || BaseHeight != SKINPAGE_HEIGHT ) { if ( g_allow_newskin ) { ScaleWidth = BaseWidth; ScaleHeight = BaseHeight; NewSkin = true; } else { Error( "Invalid skin page size: (%d,%d) should be (%d,%d)", BaseWidth,BaseHeight,SKINPAGE_WIDTH,SKINPAGE_HEIGHT ); } } else if ( !BaseTrueColor ) { ScaleWidth = (float)ExtractNumber( BasePixels, ENCODED_WIDTH_X, ENCODED_WIDTH_Y ); ScaleHeight = (float)ExtractNumber( BasePixels, ENCODED_HEIGHT_X, ENCODED_HEIGHT_Y ); } else { Error( "Texture coordinates not supported on true color image" ); } if ( GetST ) { GetScriptToken( false ); sprintf( stfile, "%s/%s.%s", cdarchive, token, trifileext ); printf( "ST: %s ", stfile ); sprintf( stfile, "%s/%s", cddir, token ); if ( do3ds ) { Load3DSTriangleList( stfile, &st_tri, &num_st_tris, NULL, NULL ); } else{ LoadTriangleList( stfile, &st_tri, &num_st_tris, NULL, NULL ); } if ( num_st_tris != fmheader.num_tris ) { Error( "num st tris mismatch: st %d / base %d", num_st_tris, fmheader.num_tris ); } printf( " matching triangles...\n" ); for ( i = 0; i < fmheader.num_tris; i++ ) { k = -1; for ( j = 0; j < num_st_tris; j++ ) { for ( x = 0; x < 3; x++ ) { for ( y = 0; y < 3; y++ ) { if ( x == y ) { continue; } for ( z = 0; z < 3; z++ ) { if ( z == x || z == y ) { continue; } if ( VectorFudgeCompare( ptri[i].verts[0], st_tri[j].verts[x] ) && VectorFudgeCompare( ptri[i].verts[1], st_tri[j].verts[y] ) && VectorFudgeCompare( ptri[i].verts[2], st_tri[j].verts[z] ) ) { if ( k == -1 ) { k = j; ptri[i].HasUV = st_tri[k].HasUV; ptri[i].uv[0][0] = st_tri[k].uv[x][0]; ptri[i].uv[0][1] = st_tri[k].uv[x][1]; ptri[i].uv[1][0] = st_tri[k].uv[y][0]; ptri[i].uv[1][1] = st_tri[k].uv[y][1]; ptri[i].uv[2][0] = st_tri[k].uv[z][0]; ptri[i].uv[2][1] = st_tri[k].uv[z][1]; x = y = z = 999; } else if ( k != j ) { printf( "Duplicate triangle %d found in st file: %d and %d\n",i,k,j ); printf( " (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f)\n", ptri[i].verts[0][0],ptri[i].verts[0][1],ptri[i].verts[0][2], ptri[i].verts[1][0],ptri[i].verts[1][1],ptri[i].verts[1][2], ptri[i].verts[2][0],ptri[i].verts[2][1],ptri[i].verts[2][2] ); printf( " (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f)\n", st_tri[k].verts[0][0],st_tri[k].verts[0][1],st_tri[k].verts[0][2], st_tri[k].verts[1][0],st_tri[k].verts[1][1],st_tri[k].verts[1][2], st_tri[k].verts[2][0],st_tri[k].verts[2][1],st_tri[k].verts[2][2] ); printf( " (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f) (%0.3f %0.3f %0.3f)\n", st_tri[j].verts[0][0],st_tri[j].verts[0][1],st_tri[j].verts[0][2], st_tri[j].verts[1][0],st_tri[j].verts[1][1],st_tri[j].verts[1][2], st_tri[j].verts[2][0],st_tri[j].verts[2][1],st_tri[j].verts[2][2] ); } } } } } } if ( k == -1 ) { printf( "No matching triangle %d\n",i ); } } free( st_tri ); } // // get the ST values // if ( ptri && ptri[0].HasUV ) { if ( !NewSkin ) { Error( "Base has UVs with old style skin page\nMaybe you want to use -ignoreUV" ); } else { BuildNewST( ptri, fmheader.num_tris, false ); } } else { if ( NewSkin ) { Error( "Base has new style skin without UVs" ); } else { BuildST( ptri, fmheader.num_tris, false ); } } TransPixels = NULL; if ( !BaseTrueColor ) { FH = fopen( trans_file,"rb" ); if ( FH ) { fclose( FH ); Load256Image( trans_file, &TransPixels, NULL, &TransWidth, &TransHeight ); if ( TransWidth != fmheader.skinwidth || TransHeight != fmheader.skinheight ) { Error( "source image %s dimensions (%d,%d) are not the same as alpha image (%d,%d)\n",file2,fmheader.skinwidth,fmheader.skinheight,TransWidth,TransHeight ); } } } // // run through all the base triangles, storing each unique vertex in the // base vertex list and setting the indirect triangles to point to the base // vertices // for ( l = 0; l < fmheader.num_mesh_nodes; l++ ) { for ( i = 0 ; i < fmheader.num_tris ; i++ ) { pos = 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 < fmheader.num_xyz ; k++ ) { if ( VectorCompare( ptri[i].verts[j], base_xyz[k] ) ) { break; // this vertex is already in the base vertex list } } if ( k == fmheader.num_xyz ) { // new index VectorCopy( ptri[i].verts[j], base_xyz[fmheader.num_xyz] ); if ( pmnodes[l].clustered == true ) { ReplaceClusterIndex( k, ptri[i].indicies[j], (int **)&pmnodes[l].clusters, (IntListNode_t **)&g_skelModel.vertLists, (int *)&pmnodes[l].num_verts, (int *)&g_skelModel.new_num_verts ); } fmheader.num_xyz++; } pos = 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_st ; k++ ) { if ( triangle_st[i][j][0] == base_st[k].s && triangle_st[i][j][1] == base_st[k].t ) { break; // this vertex is already in the base vertex list } } if ( k == fmheader.num_st ) { // new index base_st[fmheader.num_st].s = triangle_st[i][j][0]; base_st[fmheader.num_st].t = triangle_st[i][j][1]; fmheader.num_st++; } triangles[i].index_st[j] = k; } if ( TransPixels || BaseTrueColor ) { translucent[i] = CheckTransRecursiveTri( triangle_st[i][0], triangle_st[i][1], triangle_st[i][2] ); } else { translucent[i] = false; } } } if ( !BaseTrueColor ) { SetPixel = true; memset( BasePixels,0,BaseWidth * BaseHeight ); for ( i = 0 ; i < fmheader.num_tris ; i++ ) { CheckTransRecursiveTri( triangle_st[i][0], triangle_st[i][1], triangle_st[i][2] ); } SetPixel = false; skin_pixels_used = 0; for ( i = 0; i < fmheader.skinheight; i++ ) { for ( j = 0; j < fmheader.skinwidth; j++ ) { skin_pixels_used += BasePixels[( i * BaseWidth ) + j]; } } total_skin_pixels = fmheader.skinheight * fmheader.skinwidth; } else { SetPixel = true; memset( BasePixels,0,BaseWidth * BaseHeight * 4 ); for ( i = 0 ; i < fmheader.num_tris ; i++ ) { CheckTransRecursiveTri( triangle_st[i][0], triangle_st[i][1], triangle_st[i][2] ); } SetPixel = false; skin_pixels_used = 0; for ( i = 0; i < fmheader.skinheight; i++ ) { for ( j = 0; j < fmheader.skinwidth; j++ ) { skin_pixels_used += BasePixels[( ( i * BaseWidth ) + j ) * 4]; } } total_skin_pixels = fmheader.skinheight * fmheader.skinwidth; } // build triangle strips / fans BuildGlCmds(); if ( TransPixels ) { free( TransPixels ); } free( BasePixels ); if ( BasePalette ) { free( BasePalette ); } free( ptri ); } void Cmd_FMNodeOrder( void ){ mesh_node_t *newnodes, *pos; int i,j; if ( !pmnodes ) { Error( "Base has not been established yet" ); } pos = newnodes = malloc( sizeof( mesh_node_t ) * fmheader.num_mesh_nodes ); for ( i = 0; i < fmheader.num_mesh_nodes; i++ ) { GetScriptToken( false ); for ( j = 0; j < fmheader.num_mesh_nodes; j++ ) { if ( strcmpi( pmnodes[j].name, token ) == 0 ) { *pos = pmnodes[j]; pos++; break; } } if ( j >= 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 ; i < fmheader.num_xyz ; i++ ) { ptrivert[i].vnorm.numnormals = 0; VectorClear( ptrivert[i].vnorm.normalsum ); } ClearBounds( fr->mins, 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 ; i < num_tris ; i++ ) { vec3_t vtemp1, vtemp2, normal; float ftemp; VectorSubtract( ptri[i].verts[0], ptri[i].verts[1], vtemp1 ); VectorSubtract( ptri[i].verts[2], ptri[i].verts[1], vtemp2 ); CrossProduct( vtemp1, vtemp2, normal ); VectorNormalize( normal, normal ); // rotate the normal so the model faces down the positive x axis ftemp = normal[0]; normal[0] = -normal[1]; normal[1] = ftemp; for ( j = 0 ; j < 3 ; j++ ) { index_xyz = triangles[i].index_xyz[j]; // rotate the vertices so the model faces down the positive x axis // also adjust the vertices to the desired origin ptrivert[index_xyz].v[0] = ( ( -ptri[i].verts[j][1] ) * scale_up ) + adjust[0]; ptrivert[index_xyz].v[1] = ( ptri[i].verts[j][0] * scale_up ) + adjust[1]; ptrivert[index_xyz].v[2] = ( ptri[i].verts[j][2] * scale_up ) + adjust[2]; AddPointToBounds( ptrivert[index_xyz].v, fr->mins, 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 < fmheader.num_xyz ; i++ ) { int j; vec3_t v; float maxdot; int maxdotindex; int c; c = ptrivert[i].vnorm.numnormals; if ( !c ) { Error( "Vertex with no triangles attached" ); } VectorScale( ptrivert[i].vnorm.normalsum, 1.0 / c, v ); VectorNormalize( v, v ); maxdot = -999999.0; maxdotindex = -1; for ( j = 0 ; j < NUMVERTEXNORMALS ; j++ ) { float dot; dot = DotProduct( v, avertexnormals[j] ); if ( dot > 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 < fmheader.skinheight ; y++ ) { memcpy( cropped + y * fmheader.skinwidth, pixels + y * width, fmheader.skinwidth ); } TransPixels = NULL; FH = fopen( transname,"rb" ); if ( FH ) { fclose( FH ); strcat( g_skins[fmheader.num_skins - 1],".pcx" ); strcat( savename,".pcx" ); // save off the new image printf( "saving %s\n", savename ); CreatePath( savename ); WritePCXfile( savename, cropped, fmheader.skinwidth, fmheader.skinheight, palette ); } else { #if 1 miptex_t *qtex; qtex = CreateMip( cropped, fmheader.skinwidth, fmheader.skinheight, palette, &size, true ); strcat( g_skins[fmheader.num_skins - 1],".m8" ); strcat( savename,".m8" ); printf( "saving %s\n", savename ); CreatePath( savename ); SaveFile( savename, (byte *)qtex, size ); free( qtex ); #else strcat( g_skins[fmheader.num_skins - 1],".pcx" ); strcat( savename,".pcx" ); // save off the new image printf( "saving %s\n", savename ); CreatePath( savename ); WritePCXfile( savename, cropped, fmheader.skinwidth, fmheader.skinheight, palette ); #endif } } else { cropped = (byte *) SafeMalloc( fmheader.skinwidth * fmheader.skinheight * 4, "Cmd_FMSkin" ); for ( y = 0 ; y < fmheader.skinheight ; y++ ) { memcpy( cropped + ( ( y * fmheader.skinwidth ) * 4 ), pixels + ( y * width * 4 ), fmheader.skinwidth * 4 ); } qtex32 = CreateMip32( (unsigned *)cropped, fmheader.skinwidth, fmheader.skinheight, &size, true ); StripExtension( g_skins[fmheader.num_skins - 1] ); strcat( g_skins[fmheader.num_skins - 1],".m32" ); StripExtension( savename ); strcat( savename,".m32" ); printf( "saving %s\n", savename ); CreatePath( savename ); SaveFile( savename, (byte *)qtex32, size ); } free( pixels ); if ( palette ) { free( palette ); } free( cropped ); } /* =============== Cmd_Cd =============== */ void Cmd_FMCd( void ){ char temp[256]; FinishModel(); ClearModel(); GetScriptToken( false ); // this is a silly mess... sprintf( cdpartial, "models/%s", token ); sprintf( cdarchive, "%smodels/%s", gamedir + strlen( qdir ), token ); sprintf( cddir, "%s%s", gamedir, cdpartial ); // Since we also changed directories on the output side (for mirror) make sure the outputdir is set properly too. sprintf( temp, "%s%s", g_outputDir, cdpartial ); strcpy( g_outputDir, temp ); // if -only was specified and this cd doesn't match, // skip the model (you only need to match leading chars, // so you could regrab all monsters with -only monsters) if ( !g_only[0] ) { return; } if ( strncmp( token, g_only, strlen( g_only ) ) ) { g_skipmodel = true; printf( "skipping %s\n", cdpartial ); } } /* //======================= // NEW GEN //======================= void NewGen (char *ModelFile, char *OutputName, int width, int height) { trigroup_t *triangles; triangle_t *ptri; triangle_t *grouptris; mesh_node_t *pmnodes; vec3_t *vertices; vec3_t *uvs; vec3_t aveNorm, crossvect; vec3_t diffvect1, diffvect2; vec3_t v0, v1, v2; vec3_t n, u, v; vec3_t base, zaxis, yaxis; vec3_t uvwMin, uvwMax; vec3_t groupMin, groupMax; vec3_t uvw; float *uFinal, *vFinal; unsigned char *newpic; int finalstart = 0, finalcount = 0; int xbase = 0, xwidth = 0, ywidth = 0; int *todo, *done, finished; int i, j, k, l; //counters int groupnum, numtris, numverts, num; int count; FILE *grpfile; long datasize; for ( i = 0; i<3; i++) { aveNorm[i] = 0; uvwMin[i] = 1e30f; uvwMax[i] = -1e30f; } pmnodes = NULL; ptri = NULL; triangles = NULL; zaxis[0] = 0; zaxis[1] = 0; zaxis[2] = 1; yaxis[0] = 0; yaxis[1] = 1; yaxis[2] = 0; LoadTriangleList (ModelFile, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes); todo = (int*)SafeMalloc(fmheader.num_tris*sizeof(int), "NewGen"); done = (int*)SafeMalloc(fmheader.num_tris*sizeof(int), "NewGen"); triangles = (trigroup_t*)SafeMalloc(fmheader.num_tris*sizeof(trigroup_t), "NewGen"); for ( i=0; i < fmheader.num_tris; i++) { todo[i] = false; done[i] = false; triangles[i].triangle = ptri[i]; triangles[i].group = 0; } groupnum = 0; // transitive closure algorithm follows // put all triangles who transitively share vertices into separate groups while (1) { for ( i = 0; i < fmheader.num_tris; i++) { if (!done[i]) { break; } } if ( i == fmheader.num_tris) { break; } finished = false; todo[i] = true; while (!finished) { finished = true; for ( i = 0; i < fmheader.num_tris; i++) { if (todo[i]) { done[i] = true; triangles[i].group = groupnum; todo[i] = false; for ( j = 0; j < fmheader.num_tris; j++) { if ((!done[j]) && (ShareVertex(triangles[i],triangles[j]))) { todo[j] = true; finished = false; } } } } } groupnum++; } uFinal = (float*)SafeMalloc(3*fmheader.num_tris*sizeof(float), "NewGen"); vFinal = (float*)SafeMalloc(3*fmheader.num_tris*sizeof(float), "NewGen"); grpfile = fopen("grpdebug.txt","w"); for (i = 0; i < groupnum; i++) { fprintf(grpfile,"Group Number: %d\n", i); numtris = GetNumTris(triangles, i); // number of triangles in group i numverts = numtris * 3; fprintf(grpfile,"%d triangles.\n", numtris); vertices = (vec3_t*)SafeMalloc(numverts*sizeof(vec3_t), "NewGen"); uvs = (vec3_t*)SafeMalloc(numverts*sizeof(vec3_t), "NewGen"); grouptris = (triangle_t*)SafeMalloc(numtris*sizeof(triangle_t), "NewGen"); for (count = 0; count < fmheader.num_tris; count++) { if (triangles[count].group == i) { fprintf(grpfile,"Triangle %d\n", count); } } fprintf(grpfile,"\n"); GetOneGroup(triangles, i, grouptris); num = 0; for (j = 0; j < numtris; j++) { VectorCopy(grouptris[j].verts[0], v0); VectorCopy(grouptris[j].verts[1], v1); VectorCopy(grouptris[j].verts[2], v2); VectorSubtract(v1, v0, diffvect1); VectorSubtract(v2, v1, diffvect2); CrossProduct( diffvect1, diffvect2, crossvect); VectorAdd(aveNorm, crossvect, aveNorm); VectorCopy(v0,vertices[num]); num++; // FIXME VectorCopy(v1,vertices[num]); num++; // add routine to add only verts that VectorCopy(v2,vertices[num]); num++; // have not already been added } assert (num >= 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 < verts; i++ ) { u[i] *= scale; v[i] *= scale; } return; } void GetOneGroup( trigroup_t *tris, int grp, triangle_t* triangles ){ int i; int j; j = 0; for ( i = 0; i < fmheader.num_tris; i++ ) { if ( tris[i].group == grp ) { triangles[j++] = tris[i].triangle; } } return; } int GetNumTris( trigroup_t *tris, int grp ){ int i; int verts; verts = 0; for ( i = 0; i < fmheader.num_tris; i++ ) { if ( tris[i].group == grp ) { verts++; } } return verts; } int ShareVertex( trigroup_t trione, trigroup_t tritwo ){ int i; int j; i = 1; j = 1; for ( i = 0; i < 3; i++ ) { for ( j = 0; j < 3; j++ ) { if ( DistBetween( trione.triangle.verts[i],tritwo.triangle.verts[j] ) < TRIVERT_DIST ) { return true; } } } return false; } float DistBetween( vec3_t point1, vec3_t point2 ){ float dist; dist = ( point1[0] - point2[0] ); dist *= dist; dist += ( point1[1] - point2[1] ) * ( point1[1] - point2[1] ); dist += ( point1[2] - point2[2] ) * ( point1[2] - point2[2] ); dist = sqrt( dist ); return dist; } void GenSkin( char *ModelFile, char *OutputName, int Width, int Height ){ triangle_t *ptri; mesh_node_t *pmnodes; int i; pmnodes = NULL; ptri = NULL; LoadTriangleList( ModelFile, &ptri, &fmheader.num_tris, &pmnodes, &fmheader.num_mesh_nodes ); if ( g_ignoreTriUV ) { for ( i = 0; i < fmheader.num_tris; i++ ) { ptri[i].HasUV = 0; } } memset( pic,0,sizeof( pic ) ); memset( pic_palette,0,sizeof( pic_palette ) ); pic_palette[767] = pic_palette[766] = pic_palette[765] = 255; ScaleWidth = Width; ScaleHeight = Height; BuildST( ptri, fmheader.num_tris, true ); WritePCXfile( OutputName, pic, SKINPAGE_WIDTH, SKINPAGE_HEIGHT, pic_palette ); printf( "Gen Skin Stats:\n" ); printf( " Input Base: %s\n",ModelFile ); printf( " Input Dimensions: %d,%d\n",Width,Height ); printf( "\n" ); printf( " Output File: %s\n",OutputName ); printf( " Output Dimensions: %d,%d\n",ScaleWidth,ScaleHeight ); if ( fmheader.num_mesh_nodes ) { printf( "\nNodes:\n" ); for ( i = 0; i < fmheader.num_mesh_nodes; i++ ) { printf( " %s\n",pmnodes[i].name ); } } free( ptri ); free( pmnodes ); } void Cmd_FMBeginGroup( void ){ GetScriptToken( false ); g_no_opimizations = false; groups[num_groups].start_frame = fmheader.num_frames; groups[num_groups].num_frames = 0; groups[num_groups].degrees = atol( token ); if ( groups[num_groups].degrees < 1 || groups[num_groups].degrees > 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 ); } }