1075 lines
30 KiB
C
1075 lines
30 KiB
C
#include <assert.h>
|
|
#include "cmdlib.h"
|
|
#include "mathlib.h"
|
|
#include "qfiles.h"
|
|
#include "models.h"
|
|
#include "tikidata.h"
|
|
#include "calclod.h"
|
|
|
|
//=================================================================
|
|
|
|
void OrderMesh( int input[][3], int output[][3], int numTris );
|
|
static void OrderSurfaces( void );
|
|
static void LoadBase( const char *filename );
|
|
|
|
q3data g_data;
|
|
|
|
qboolean g_verbose;
|
|
qboolean g_optimize;
|
|
qboolean g_lod = qtrue;
|
|
qboolean g_clampz;
|
|
qboolean g_noorigin;
|
|
qboolean g_zeroz;
|
|
qboolean g_clearz;
|
|
qboolean g_clearxy;
|
|
|
|
// the command list holds counts, the count * 3 xyz, st, normal indexes
|
|
// that are valid for every frame
|
|
char g_cddir[1024];
|
|
char g_modelname[1024];
|
|
|
|
|
|
/*
|
|
** TIKI_ComputeTagFromTri
|
|
*/
|
|
void TIKI_ComputeTagFromTri( tikiTagData_t *pTag, const float pTri[3][3], const float pbaseTri[3][3] )
|
|
{
|
|
float len[3];
|
|
float origlen[3];
|
|
vec3_t axes[3], sides[3];
|
|
int longestSide, shortestSide, hypotSide;
|
|
int origin;
|
|
int j;
|
|
float d;
|
|
|
|
longestSide = 0;
|
|
shortestSide = 0;
|
|
hypotSide = 0;
|
|
origin = 0;
|
|
|
|
memset( axes, 0, sizeof( axes ) );
|
|
memset( sides, 0, sizeof( sides ) );
|
|
|
|
//
|
|
// compute sides
|
|
//
|
|
for ( j = 0; j < 3; j++ )
|
|
{
|
|
#if 1
|
|
sides[j][0] = pbaseTri[(j+1)%3][0] - pbaseTri[j][0];
|
|
sides[j][1] = pbaseTri[(j+1)%3][1] - pbaseTri[j][1];
|
|
sides[j][2] = pbaseTri[(j+1)%3][2] - pbaseTri[j][2];
|
|
#else
|
|
sides[j][0] = pTri[(j+1)%3][0] - pTri[j][0];
|
|
sides[j][1] = pTri[(j+1)%3][1] - pTri[j][1];
|
|
sides[j][2] = pTri[(j+1)%3][2] - pTri[j][2];
|
|
#endif
|
|
|
|
origlen[ j ] = len[ j ] = ( float ) sqrt( DotProduct( sides[j], sides[j] ) );
|
|
}
|
|
|
|
#if 0
|
|
if ( len[0] > len[1] && len[0] > len[2] )
|
|
{
|
|
longestSide = 0; shortestSide = 1; origin = 2;
|
|
}
|
|
else if ( len[1] > len[0] && len[1] > len[2] )
|
|
{
|
|
longestSide = 1; shortestSide = 2; origin = 0;
|
|
}
|
|
else if ( len[2] > len[0] && len[2] > len[1] )
|
|
{
|
|
longestSide = 2; shortestSide = 0; origin = 1;
|
|
}
|
|
else
|
|
{
|
|
Error( "invalid tag triangle, must be a right triangle with unequal length sides" );
|
|
}
|
|
#endif
|
|
if ( len[0] > len[1] && len[0] > len[2] ) {
|
|
hypotSide = 0;
|
|
origin = 2;
|
|
} else if ( len[1] > len[0] && len[1] > len[2] ) {
|
|
hypotSide = 1;
|
|
origin = 0;
|
|
} else if ( len[2] > len[0] && len[2] > len[1] ) {
|
|
hypotSide = 2;
|
|
origin = 1;
|
|
}
|
|
len[hypotSide] = -1;
|
|
|
|
if ( len[0] > len[1] && len[0] > len[2] ) {
|
|
longestSide = 0;
|
|
} else if ( len[1] > len[0] && len[1] > len[2] ) {
|
|
longestSide = 1;
|
|
} else if ( len[2] > len[0] && len[2] > len[1] ) {
|
|
longestSide = 2;
|
|
}
|
|
len[longestSide] = -1;
|
|
|
|
if ( len[0] > len[1] && len[0] > len[2] ) {
|
|
shortestSide = 0;
|
|
} else if ( len[1] > len[0] && len[1] > len[2] ) {
|
|
shortestSide = 1;
|
|
} else if ( len[2] > len[0] && len[2] > len[1] ) {
|
|
shortestSide = 2;
|
|
}
|
|
len[shortestSide] = -1;
|
|
|
|
//
|
|
// compute sides
|
|
//
|
|
for ( j = 0; j < 3; j++ )
|
|
{
|
|
sides[j][0] = pTri[(j+1)%3][0] - pTri[j][0];
|
|
sides[j][1] = pTri[(j+1)%3][1] - pTri[j][1];
|
|
sides[j][2] = pTri[(j+1)%3][2] - pTri[j][2];
|
|
}
|
|
|
|
if ( longestSide == 0 && shortestSide == 1 )
|
|
{
|
|
VectorNegate( sides[ longestSide ] );
|
|
VectorNegate( sides[ shortestSide ] );
|
|
}
|
|
else if ( longestSide == 0 && shortestSide == 2 )
|
|
{
|
|
}
|
|
else if ( longestSide == 1 && shortestSide == 0 )
|
|
{
|
|
}
|
|
else if ( longestSide == 1 && shortestSide == 2 )
|
|
{
|
|
VectorNegate( sides[ longestSide ] );
|
|
VectorNegate( sides[ shortestSide ] );
|
|
}
|
|
else if ( longestSide == 2 && shortestSide == 0 )
|
|
{
|
|
VectorNegate( sides[ longestSide ] );
|
|
VectorNegate( sides[ shortestSide ] );
|
|
}
|
|
else if ( longestSide == 2 && shortestSide == 1 )
|
|
{
|
|
}
|
|
|
|
|
|
VectorNormalize( sides[longestSide], axes[0] );
|
|
VectorNormalize( sides[shortestSide], axes[1] );
|
|
|
|
// project shortest side so that it is exactly 90 degrees to the longer side
|
|
d = DotProduct( axes[1], axes[0] );
|
|
VectorMA( axes[1], -d, axes[0], axes[1] );
|
|
VectorNormalize( axes[1], axes[1] );
|
|
|
|
CrossProduct( sides[longestSide], sides[shortestSide], axes[2] );
|
|
VectorNormalize( axes[2], axes[2] );
|
|
|
|
pTag->origin[0] = pTri[origin][0];
|
|
pTag->origin[1] = pTri[origin][1];
|
|
pTag->origin[2] = pTri[origin][2];
|
|
|
|
VectorCopy( axes[0], pTag->axis[0] );
|
|
VectorCopy( axes[1], pTag->axis[1] );
|
|
VectorCopy( axes[2], pTag->axis[2] );
|
|
}
|
|
|
|
//==============================================================
|
|
|
|
/*
|
|
** MyNormalToLatLong
|
|
**
|
|
** Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format
|
|
** Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format
|
|
**
|
|
** Latitude is encoded in high 8 bits
|
|
*/
|
|
static unsigned short MyNormalToLatLong( vec3_t normal )
|
|
{
|
|
unsigned short ll;
|
|
|
|
// check for singularities
|
|
if ( normal[0] == 0 && normal[1] == 0 )
|
|
{
|
|
if ( normal[2] > 0 )
|
|
{
|
|
ll = 0; // lat = 0, long = 0
|
|
}
|
|
else
|
|
{
|
|
ll = 128; // lat = 0, long = 128
|
|
}
|
|
}
|
|
else
|
|
{
|
|
unsigned short a, b;
|
|
|
|
a = RAD2DEG( atan2( normal[1], normal[0] ) ) * (255.0f / 360.0f );
|
|
a &= 0xff;
|
|
|
|
b = RAD2DEG( acos( normal[2] ) ) * ( 255.0f / 360.0f );
|
|
b &= 0xff;
|
|
|
|
ll = ( a << 8 ) | b;
|
|
}
|
|
|
|
return ll;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ClearModel
|
|
===============
|
|
*/
|
|
void ClearModel (void)
|
|
{
|
|
int i;
|
|
|
|
g_data.type = 0;
|
|
|
|
for ( i = 0; i < TIKI_MAX_SURFACES * 2; i++ )
|
|
{
|
|
memset( &g_data.surfData[i].header, 0, sizeof( g_data.surfData[i].header ) );
|
|
memset( &g_data.surfData[i].shaders, 0, sizeof( g_data.surfData[i].shaders ) );
|
|
memset( &g_data.surfData[i].verts, 0, sizeof( g_data.surfData[i].verts ) );
|
|
}
|
|
|
|
memset( g_data.tags, 0, sizeof( g_data.tags ) );
|
|
memset( g_data.tagdata, 0, sizeof( g_data.tagdata ) );
|
|
memset( g_data.origins, 0, sizeof( g_data.origins ) );
|
|
|
|
for ( i = 0; i < g_data.model.numSurfaces; i++ )
|
|
{
|
|
int j;
|
|
|
|
for ( j = 0; j < g_data.model.numFrames; j++ )
|
|
{
|
|
if ( g_data.surfData[i].verts[j] )
|
|
{
|
|
free( g_data.surfData[i].verts[j] );
|
|
g_data.surfData[i].verts[j] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
memset (&g_data.model, 0, sizeof(g_data.model));
|
|
memset (g_cddir, 0, sizeof(g_cddir));
|
|
|
|
g_modelname[0] = 0;
|
|
g_data.scale_up = 1.0;
|
|
memset( &g_data.model, 0, sizeof( g_data.model ) );
|
|
VectorCopy (vec3_origin, g_data.adjust);
|
|
g_data.fixedwidth = g_data.fixedheight = 0;
|
|
g_data.originname[ 0 ] = 0;
|
|
}
|
|
|
|
/*
|
|
** void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData )
|
|
**
|
|
** This routine assumes that the file position has been adjusted
|
|
** properly prior to entry to point at the beginning of the surface.
|
|
**
|
|
** Since surface header information is completely relative, we can't
|
|
** just randomly seek to an arbitrary surface location right now. Is
|
|
** this something we should add?
|
|
*/
|
|
void WriteModelSurface( FILE *modelouthandle, md3SurfaceData_t *pSurfData )
|
|
{
|
|
tikiSurface_t *pSurf = &pSurfData->header;
|
|
md3Shader_t *pShader = pSurfData->shaders;
|
|
baseVertex_t *pBaseVertex = pSurfData->baseVertexes;
|
|
float **verts = pSurfData->verts;
|
|
|
|
short xyznormals[TIKI_MAX_VERTS][4];
|
|
|
|
float base_st[TIKI_MAX_VERTS][2];
|
|
tikiSurface_t surftemp;
|
|
|
|
int f, i, j, k, index;
|
|
|
|
if (
|
|
( strstr( pSurf->name, "tag_" ) == pSurf->name ) ||
|
|
( !strcmpi( g_data.originname, pSurf->name ) )
|
|
)
|
|
return;
|
|
|
|
//
|
|
// write out the header
|
|
//
|
|
surftemp = *pSurf;
|
|
surftemp.ident = LittleLong( TIKI_ANIM_IDENT );
|
|
surftemp.numFrames = LittleLong( pSurf->numFrames );
|
|
|
|
surftemp.minLod = LittleLong( pSurf->minLod );
|
|
|
|
surftemp.ofsTriangles = LittleLong( pSurf->ofsTriangles );
|
|
surftemp.numTriangles = LittleLong( pSurf->numTriangles );
|
|
|
|
surftemp.ofsCollapseMap = LittleLong( pSurf->ofsCollapseMap );
|
|
|
|
surftemp.ofsSt = LittleLong( pSurf->ofsSt );
|
|
surftemp.ofsXyzNormals = LittleLong( pSurf->ofsXyzNormals );
|
|
surftemp.ofsEnd = LittleLong( pSurf->ofsEnd );
|
|
|
|
SafeWrite( modelouthandle, &surftemp, sizeof( surftemp ) );
|
|
|
|
if ( g_verbose )
|
|
{
|
|
printf( "surface '%s'\n", pSurf->name );
|
|
}
|
|
|
|
//
|
|
// write out the triangles
|
|
//
|
|
for ( i = 0 ; i < pSurf->numTriangles ; i++ )
|
|
{
|
|
for (j = 0 ; j < 3 ; j++)
|
|
{
|
|
if ( g_lod )
|
|
{
|
|
int ivalue = LittleLong( pSurfData->permutationMap[ pSurfData->orderedTriangles[i][j] ] );
|
|
pSurfData->orderedTriangles[i][j] = ivalue;
|
|
}
|
|
else
|
|
{
|
|
int ivalue = LittleLong( pSurfData->orderedTriangles[i][j] );
|
|
pSurfData->orderedTriangles[i][j] = ivalue;
|
|
}
|
|
}
|
|
}
|
|
|
|
SafeWrite( modelouthandle, pSurfData->orderedTriangles, pSurf->numTriangles * sizeof( g_data.surfData[0].orderedTriangles[0] ) );
|
|
|
|
if ( g_verbose )
|
|
{
|
|
printf( "\n...num verts: %d\n", pSurf->numVerts );
|
|
printf( "...COLLAPSE MAP\n" );
|
|
}
|
|
|
|
//
|
|
// write out the collapse map
|
|
//
|
|
for ( i = 0 ; i < pSurf->numVerts; i++ )
|
|
{
|
|
{
|
|
if ( g_lod )
|
|
{
|
|
int ivalue = LittleLong( pSurfData->collapseMap[i] );
|
|
pSurfData->collapseMap[i] = ivalue;
|
|
}
|
|
else
|
|
{
|
|
pSurfData->collapseMap[i] = LittleLong( i );;
|
|
}
|
|
}
|
|
}
|
|
|
|
SafeWrite( modelouthandle, pSurfData->collapseMap, pSurf->numVerts * sizeof( g_data.surfData[0].collapseMap[0] ) );
|
|
|
|
if ( g_verbose )
|
|
{
|
|
printf( "...TEX COORDINATES\n" );
|
|
}
|
|
|
|
//
|
|
// write out the texture coordinates
|
|
//
|
|
for ( i = 0; i < pSurf->numVerts ; i++) {
|
|
if ( g_lod )
|
|
{
|
|
// reorder the verts for lod as we write them out
|
|
index = pSurfData->permutationMap[ i ];
|
|
base_st[index][0] = LittleFloat( pBaseVertex[i].st[0] );
|
|
base_st[index][1] = LittleFloat( pBaseVertex[i].st[1] );
|
|
if ( g_verbose )
|
|
printf( "......%d: %f,%f\n", i, base_st[index][0], base_st[index][1] );
|
|
}
|
|
else
|
|
{
|
|
base_st[i][0] = LittleFloat( pBaseVertex[i].st[0] );
|
|
base_st[i][1] = LittleFloat( pBaseVertex[i].st[1] );
|
|
if ( g_verbose )
|
|
printf( "......%d: %f,%f\n", i, base_st[i][0], base_st[i][1] );
|
|
}
|
|
}
|
|
SafeWrite( modelouthandle, base_st, pSurf->numVerts * sizeof(base_st[0]));
|
|
|
|
//
|
|
// write the xyz_normal
|
|
//
|
|
if ( g_verbose )
|
|
printf( "...XYZNORMALS\n" );
|
|
for ( f = 0; f < g_data.model.numFrames; f++ )
|
|
{
|
|
for (j=0 ; j< pSurf->numVerts; j++)
|
|
{
|
|
float fvalue;
|
|
short value;
|
|
|
|
if ( g_lod )
|
|
{
|
|
// reorder the verts for lod as we write them out
|
|
index = pSurfData->permutationMap[ j ];
|
|
}
|
|
else
|
|
{
|
|
index = j;
|
|
}
|
|
for (k=0 ; k < 3 ; k++)
|
|
{
|
|
fvalue = verts[f][j*6+k] - g_data.frames[ f ].offset[ k ];
|
|
value = ( short ) ( fvalue / g_data.frames[ f ].scale[ k ] );
|
|
xyznormals[index][k] = LittleShort( value );
|
|
}
|
|
value = MyNormalToLatLong( &verts[f][j*6+3] );
|
|
xyznormals[index][3] = LittleShort( value );
|
|
}
|
|
SafeWrite( modelouthandle, xyznormals, pSurf->numVerts * sizeof( short ) * 4 );
|
|
}
|
|
}
|
|
|
|
int numRealSurfaces;
|
|
/*
|
|
** void WriteModelFile( FILE *modelouthandle )
|
|
**
|
|
** CHUNK SIZE
|
|
** header sizeof( md3Header_t )
|
|
** frames sizeof( tikiFrame_t ) * numFrames
|
|
** tags sizeof( tikiTag_t ) * numTags + sizeof( tikiTagData_t ) * numFrames * numTags
|
|
** surfaces surfaceSum
|
|
*/
|
|
void WriteModelFile( FILE *modelouthandle )
|
|
{
|
|
int f, t;
|
|
int i, j;
|
|
tikiHeader_t modeltemp;
|
|
long surfaceSum = 0;
|
|
int numFrames = g_data.model.numFrames;
|
|
int ofstemp;
|
|
vec3_t totaldelta;
|
|
|
|
numRealSurfaces = 0 ;
|
|
// compute offsets for all surfaces, sum their total size
|
|
for ( i = 0; i < g_data.model.numSurfaces; i++ )
|
|
{
|
|
if (
|
|
( strstr( g_data.surfData[i].header.name, "tag_" ) != g_data.surfData[i].header.name ) &&
|
|
( strcmpi( g_data.originname, g_data.surfData[i].header.name ) )
|
|
)
|
|
{
|
|
tikiSurface_t *psurf = &g_data.surfData[i].header;
|
|
|
|
if ( psurf->numTriangles == 0 || psurf->numVerts == 0 )
|
|
continue;
|
|
|
|
//
|
|
// the triangle and vertex split threshold is controlled by a parameter
|
|
// to $base, a la $base blah.3ds 1900, where "1900" determines the number
|
|
// of triangles to split on
|
|
//
|
|
else if ( psurf->numVerts > MAX_SURFACE_VERTS )
|
|
{
|
|
Error( "too many vertices\n" );
|
|
}
|
|
|
|
psurf->numFrames = numFrames;
|
|
|
|
if ( psurf->numTriangles > MAX_SURFACE_TRIS )
|
|
{
|
|
Error( "too many faces\n" );
|
|
}
|
|
|
|
psurf->ofsTriangles = sizeof( tikiSurface_t );
|
|
|
|
psurf->ofsCollapseMap = psurf->ofsTriangles + psurf->numTriangles * sizeof( md3Triangle_t );
|
|
|
|
psurf->ofsSt = psurf->ofsCollapseMap + psurf->numVerts * sizeof( int );
|
|
psurf->ofsXyzNormals = psurf->ofsSt + psurf->numVerts * sizeof( md3St_t );
|
|
psurf->ofsEnd = psurf->ofsXyzNormals + psurf->numFrames * psurf->numVerts * ( sizeof( short ) * 4 );
|
|
|
|
surfaceSum += psurf->ofsEnd;
|
|
|
|
numRealSurfaces++;
|
|
}
|
|
}
|
|
|
|
g_data.model.ident = TIKI_ANIM_IDENT;
|
|
g_data.model.version = TIKI_ANIM_VERSION;
|
|
|
|
g_data.model.ofsFrames = sizeof(tikiHeader_t);
|
|
ofstemp = g_data.model.ofsFrames + numFrames*sizeof(tikiFrame_t);
|
|
for ( t = 0; t < g_data.model.numTags; t++ )
|
|
{
|
|
g_data.model.ofsTags[ t ] = ofstemp;
|
|
ofstemp += sizeof(tikiTag_t);
|
|
ofstemp += numFrames*sizeof(tikiTagData_t);
|
|
}
|
|
g_data.model.ofsSurfaces = ofstemp;
|
|
g_data.model.ofsEnd = g_data.model.ofsSurfaces + surfaceSum;
|
|
|
|
// was it optimized
|
|
//g_data.model.optimized = g_optimize;
|
|
|
|
// calculate total delta
|
|
VectorSubtract( g_data.origins[ numFrames - 1 ], g_data.origins[ 0 ], g_data.model.totaldelta );
|
|
|
|
// calculate total time
|
|
g_data.model.totaltime = ( float )numFrames / g_data.framerate;
|
|
|
|
//
|
|
// write out the model header
|
|
//
|
|
modeltemp = g_data.model;
|
|
modeltemp.ident = LittleLong( modeltemp.ident );
|
|
modeltemp.version = LittleLong( modeltemp.version );
|
|
modeltemp.numFrames = LittleLong( modeltemp.numFrames );
|
|
modeltemp.numTags = LittleLong( modeltemp.numTags );
|
|
modeltemp.numSurfaces = LittleLong( numRealSurfaces );
|
|
modeltemp.ofsFrames = LittleLong( modeltemp.ofsFrames );
|
|
for ( t = 0; t < g_data.model.numTags; t++ )
|
|
{
|
|
modeltemp.ofsTags[ t ] = LittleLong( modeltemp.ofsTags[ t ] );
|
|
}
|
|
modeltemp.ofsSurfaces = LittleLong( modeltemp.ofsSurfaces );
|
|
modeltemp.ofsEnd = LittleLong( modeltemp.ofsEnd );
|
|
modeltemp.totaltime = LittleFloat( modeltemp.totaltime );
|
|
for ( i = 0; i < 3; i++ )
|
|
{
|
|
modeltemp.totaldelta[ i ] = LittleFloat( modeltemp.totaldelta[ i ] );
|
|
}
|
|
|
|
SafeWrite (modelouthandle, &modeltemp, sizeof(modeltemp));
|
|
|
|
// clear out totaldelta
|
|
VectorClear( totaldelta );
|
|
|
|
//
|
|
// write out the frames
|
|
//
|
|
for (i=0 ; i < numFrames ; i++)
|
|
{
|
|
vec3_t tmpVec;
|
|
float maxRadius = 0;
|
|
|
|
//
|
|
// compute localOrigin and radius
|
|
//
|
|
for ( j = 0; j < 8; j++ )
|
|
{
|
|
tmpVec[0] = g_data.frames[i].bounds[(j&1)!=0][0];
|
|
tmpVec[1] = g_data.frames[i].bounds[(j&2)!=0][1];
|
|
tmpVec[2] = g_data.frames[i].bounds[(j&4)!=0][2];
|
|
|
|
if ( VectorLength( tmpVec ) > maxRadius )
|
|
maxRadius = VectorLength( tmpVec );
|
|
}
|
|
|
|
g_data.frames[i].radius = LittleFloat( maxRadius );
|
|
g_data.frames[i].frametime = LittleFloat( g_data.frames[i].frametime );
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
g_data.frames[i].scale[j] = ( g_data.frames[i].bounds[1][j] - g_data.frames[i].bounds[0][j] ) / 65535.0f;
|
|
g_data.frames[i].offset[j] = g_data.frames[i].bounds[0][j];
|
|
}
|
|
|
|
// calculate deltas
|
|
if ( i < ( numFrames - 1 ) )
|
|
{
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
g_data.frames[i].delta[j] = g_data.origins[ i + 1 ][ j ] - g_data.origins[ i ][ j ];
|
|
}
|
|
VectorAdd( totaldelta, g_data.frames[ i ].delta, totaldelta );
|
|
}
|
|
else
|
|
{
|
|
// fudge the last frame
|
|
if ( numFrames > 1 )
|
|
{
|
|
VectorScale( totaldelta, 1.0f / ( numFrames - 1 ), g_data.frames[i].delta );
|
|
}
|
|
else
|
|
{
|
|
VectorClear( totaldelta );
|
|
}
|
|
}
|
|
|
|
// swap
|
|
for (j=0 ; j<3 ; j++) {
|
|
g_data.frames[i].scale[j] = LittleFloat( g_data.frames[i].scale[j] );
|
|
g_data.frames[i].offset[j] = LittleFloat( g_data.frames[i].offset[j] );
|
|
g_data.frames[i].delta[j] = LittleFloat( g_data.frames[i].delta[j] );
|
|
g_data.frames[i].bounds[0][j] = LittleFloat( g_data.frames[i].bounds[0][j] );
|
|
g_data.frames[i].bounds[1][j] = LittleFloat( g_data.frames[i].bounds[1][j] );
|
|
}
|
|
}
|
|
fseek (modelouthandle, g_data.model.ofsFrames, SEEK_SET);
|
|
SafeWrite( modelouthandle, g_data.frames, numFrames * sizeof(g_data.frames[0]) );
|
|
|
|
//
|
|
// write out the tags
|
|
//
|
|
for ( t = 0; t < g_data.model.numTags; t++ )
|
|
{
|
|
for (f=0 ; f<g_data.model.numFrames; f++)
|
|
{
|
|
g_data.tagdata[t][f].origin[0] = LittleFloat(g_data.tagdata[t][f].origin[0]);
|
|
g_data.tagdata[t][f].origin[1] = LittleFloat(g_data.tagdata[t][f].origin[1]);
|
|
g_data.tagdata[t][f].origin[2] = LittleFloat(g_data.tagdata[t][f].origin[2]);
|
|
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
g_data.tagdata[t][f].axis[0][j] = LittleFloat(g_data.tagdata[t][f].axis[0][j]);
|
|
g_data.tagdata[t][f].axis[1][j] = LittleFloat(g_data.tagdata[t][f].axis[1][j]);
|
|
g_data.tagdata[t][f].axis[2][j] = LittleFloat(g_data.tagdata[t][f].axis[2][j]);
|
|
}
|
|
}
|
|
fseek( modelouthandle, g_data.model.ofsTags[ t ], SEEK_SET );
|
|
SafeWrite( modelouthandle, &g_data.tags[t], sizeof(tikiTag_t) );
|
|
SafeWrite( modelouthandle, g_data.tagdata[t], g_data.model.numFrames * sizeof(tikiTagData_t) );
|
|
}
|
|
|
|
//
|
|
// fixup the scale and offset parameters
|
|
//
|
|
for (i=0 ; i < numFrames ; i++)
|
|
{
|
|
// swap
|
|
for (j=0 ; j<3 ; j++)
|
|
{
|
|
g_data.frames[i].scale[j] = LittleFloat( g_data.frames[i].scale[j] );
|
|
g_data.frames[i].offset[j] = LittleFloat( g_data.frames[i].offset[j] );
|
|
}
|
|
}
|
|
|
|
//
|
|
// write out the surfaces
|
|
//
|
|
fseek( modelouthandle, g_data.model.ofsSurfaces, SEEK_SET );
|
|
for ( i = 0; i < g_data.model.numSurfaces; i++ )
|
|
{
|
|
WriteModelSurface( modelouthandle, &g_data.surfData[i] );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============
|
|
FinishModel
|
|
===============
|
|
*/
|
|
void FinishModel ( void )
|
|
{
|
|
FILE *modelouthandle;
|
|
char name[1024];
|
|
|
|
if (!g_data.model.numFrames)
|
|
return;
|
|
|
|
//
|
|
// build generalized triangle strips
|
|
//
|
|
OrderSurfaces();
|
|
|
|
sprintf (name, "%s%s", writedir, g_modelname);
|
|
|
|
//
|
|
// write the model output file
|
|
//
|
|
if ( g_verbose )
|
|
printf ("saving to %s\n", name);
|
|
CreatePath (name);
|
|
modelouthandle = SafeOpenWrite (name);
|
|
|
|
WriteModelFile (modelouthandle);
|
|
|
|
if ( g_verbose )
|
|
{
|
|
printf ("%4d surfaces\n", numRealSurfaces);
|
|
printf ("%4d frames\n", g_data.model.numFrames);
|
|
printf ("%4d tags\n", g_data.model.numTags);
|
|
printf ("file size: %d\n", (int)ftell (modelouthandle) );
|
|
printf ("---------------------\n");
|
|
}
|
|
|
|
fclose (modelouthandle);
|
|
}
|
|
|
|
/*
|
|
** OrderSurfaces
|
|
**
|
|
** Reorders triangles in all the surfaces.
|
|
*/
|
|
static void OrderSurfaces( void )
|
|
{
|
|
int s;
|
|
|
|
// go through each surface and find best strip/fans possible
|
|
for ( s = 0; s < g_data.model.numSurfaces; s++ )
|
|
{
|
|
int mesh[TIKI_MAX_TRIANGLES][3];
|
|
int i;
|
|
|
|
if (
|
|
( strstr( g_data.surfData[ s ].header.name, "tag_" ) == g_data.surfData[ s ].header.name ) ||
|
|
( !strcmpi( g_data.originname, g_data.surfData[ s ].header.name ) )
|
|
)
|
|
continue;
|
|
|
|
if ( g_verbose )
|
|
{
|
|
printf( "\nstripifying surface %16s %d/%d with %d tris\n", g_data.surfData[ s ].header.name, s+1, g_data.model.numSurfaces, g_data.surfData[s].header.numTriangles );
|
|
}
|
|
|
|
for ( i = 0; i < g_data.surfData[s].header.numTriangles; i++ )
|
|
{
|
|
mesh[i][0] = g_data.surfData[s].lodTriangles[i][0];
|
|
mesh[i][1] = g_data.surfData[s].lodTriangles[i][1];
|
|
mesh[i][2] = g_data.surfData[s].lodTriangles[i][2];
|
|
}
|
|
if ( g_optimize )
|
|
{
|
|
OrderMesh( mesh, // input
|
|
g_data.surfData[s].orderedTriangles, // output
|
|
g_data.surfData[s].header.numTriangles );
|
|
}
|
|
else
|
|
{
|
|
memcpy( g_data.surfData[s].orderedTriangles, mesh, sizeof( int ) * 3 * g_data.surfData[s].header.numTriangles );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================
|
|
|
|
BASE FRAME SETUP
|
|
|
|
===============================================================
|
|
*/
|
|
|
|
static void BuildBaseFrame( const char *filename, baseframe_t * base )
|
|
{
|
|
int i, j, t;
|
|
|
|
// calculate the base triangles
|
|
for ( i = 0; i < g_data.model.numSurfaces; i++ )
|
|
{
|
|
for ( t = 0; t < base->surfaces[ i ].numtriangles; t++ )
|
|
{
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
g_data.surfData[ i ].baseTriangles[ t ].v[ j ].index = base->surfaces[ i ].triangles[ t ].verts[ j ];
|
|
g_data.surfData[ i ].lodTriangles[ t ][ j ] = base->surfaces[ i ].triangles[ t ].verts[ j ];
|
|
}
|
|
}
|
|
strcpy( g_data.surfData[i].header.name, base->surfaces[ i ].name );
|
|
|
|
g_data.surfData[i].header.numTriangles = base->surfaces[ i ].numtriangles;
|
|
g_data.surfData[i].header.numVerts = base->surfaces[ i ].numvertices;
|
|
for( j = 0; j < base->surfaces[ i ].numvertices; j++ )
|
|
{
|
|
g_data.surfData[ i ].baseVertexes[ j ].index = base->surfaces[ i ].triverts[ j ].map;
|
|
g_data.surfData[ i ].baseVertexes[ j ].st[ 0 ] = base->surfaces[ i ].triverts[ j ].tex[ 0 ];
|
|
g_data.surfData[ i ].baseVertexes[ j ].st[ 1 ] = base->surfaces[ i ].triverts[ j ].tex[ 1 ];
|
|
VectorCopy( base->surfaces[ i ].triverts[ j ].xyz.xyz, g_data.surfData[ i ].baseVertexes[ j ].xyz );
|
|
VectorCopy( base->surfaces[ i ].triverts[ j ].xyz.normal, g_data.surfData[ i ].baseVertexes[ j ].normal );
|
|
}
|
|
CalculateLOD( &g_data.surfData[i] );
|
|
}
|
|
|
|
//
|
|
// find tags
|
|
//
|
|
for ( i = 0; i < g_data.model.numSurfaces; i++ )
|
|
{
|
|
if (
|
|
( strcmpi( g_data.originname, g_data.surfData[ i ].header.name ) ) &&
|
|
( strstr( g_data.surfData[ i ].header.name, "tag_" ) == g_data.surfData[ i ].header.name )
|
|
)
|
|
{
|
|
if ( g_data.surfData[ i ].header.numTriangles != 1 )
|
|
{
|
|
Error( "tag polysets must consist of only one triangle, tag %s", g_data.surfData[ i ].header.name );
|
|
}
|
|
if ( g_verbose )
|
|
{
|
|
printf( "found tag '%s'\n", g_data.surfData[ i ].header.name );
|
|
}
|
|
g_data.model.numTags++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
static void BuildAnimation
|
|
(
|
|
const char *filename,
|
|
baseframe_t * base,
|
|
animframe_t * anim,
|
|
int numFrames,
|
|
float framerate
|
|
)
|
|
{
|
|
int f, i, j, t, tagcount, index;
|
|
float *frameXyz;
|
|
float *frameNormals;
|
|
|
|
g_data.model.numSurfaces = base->numSurfaces;
|
|
g_data.model.numFrames = numFrames;
|
|
if ( g_data.model.numFrames >= TIKI_MAX_FRAMES)
|
|
Error ("model.numFrames >= TIKI_MAX_FRAMES");
|
|
|
|
// build base frame
|
|
BuildBaseFrame( filename, base );
|
|
|
|
// build animation frames
|
|
for ( f = 0; f < numFrames; f++ )
|
|
{
|
|
animframe_t *panim = &anim[f];
|
|
qboolean parentTagExists = qfalse;
|
|
tikiTagData_t tagParent;
|
|
int numtags = 0;
|
|
tikiFrame_t *fr;
|
|
|
|
fr = &g_data.frames[f];
|
|
|
|
// set frame time
|
|
fr->frametime = 1.0f / framerate;
|
|
|
|
VectorClear( g_data.origins[ f ] );
|
|
|
|
//
|
|
// find and count tags, locate parent tag
|
|
//
|
|
for ( i = 0; i < g_data.model.numSurfaces; i++ )
|
|
{
|
|
if (
|
|
( !strcmpi( g_data.originname, g_data.surfData[ i ].header.name ) ) ||
|
|
( strstr( g_data.surfData[ i ].header.name, "tag_" ) == g_data.surfData[ i ].header.name )
|
|
)
|
|
|
|
{
|
|
// store off origin info
|
|
if ( !strcmpi( g_data.originname, g_data.surfData[ i ].header.name ) )
|
|
{
|
|
float basetri[3][3];
|
|
float tri[3][3];
|
|
|
|
if ( parentTagExists )
|
|
Error( "Multiple parent tags not allowed" );
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
index = g_data.surfData[ i ].baseVertexes[ g_data.surfData[ i ].baseTriangles[ 0 ].v[ j ].index ].index;
|
|
VectorCopy( panim->verts[ index ].xyz, tri[ j ] );
|
|
}
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
VectorCopy( g_data.surfData[ i ].baseVertexes[ g_data.surfData[ i ].baseTriangles[ 0 ].v[ j ].index ].xyz, basetri[ j ] );
|
|
}
|
|
|
|
TIKI_ComputeTagFromTri( &tagParent, tri, basetri );
|
|
//strcpy( tagParent.name, "origin" );
|
|
//g_data.tags[f][numtags] = tagParent;
|
|
parentTagExists = qtrue;
|
|
if ( g_clampz && tagParent.origin[ 2 ] < 0 )
|
|
{
|
|
tagParent.origin[ 2 ] = 0;
|
|
}
|
|
if ( g_zeroz )
|
|
{
|
|
tagParent.origin[ 2 ] = 0;
|
|
}
|
|
if ( g_noorigin )
|
|
{
|
|
parentTagExists = qfalse;
|
|
VectorClear( tagParent.origin );
|
|
}
|
|
|
|
VectorCopy( tagParent.origin, g_data.origins[ f ] );
|
|
if ( g_clearxy )
|
|
{
|
|
// subtract forward and side movement from deltas
|
|
g_data.origins[ f ][ 0 ] = 0;
|
|
g_data.origins[ f ][ 1 ] = 0;
|
|
}
|
|
if ( g_clearz )
|
|
{
|
|
g_data.origins[ f ][ 2 ] = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
float basetri[3][3];
|
|
float tri[3][3];
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
index = g_data.surfData[ i ].baseVertexes[ g_data.surfData[ i ].baseTriangles[ 0 ].v[ j ].index ].index;
|
|
VectorCopy( panim->verts[ index ].xyz, tri[ j ] );
|
|
}
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
VectorCopy( g_data.surfData[ i ].baseVertexes[ g_data.surfData[ i ].baseTriangles[ 0 ].v[ j ].index ].xyz, basetri[ j ] );
|
|
}
|
|
|
|
TIKI_ComputeTagFromTri( &g_data.tagdata[numtags][f], tri, basetri );
|
|
strcpy( g_data.tags[numtags].name, g_data.surfData[ i ].header.name );
|
|
numtags++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( numtags != g_data.model.numTags )
|
|
{
|
|
Error( "mismatched number of tags in frame(%d) vs. base(%d)", numtags, g_data.model.numTags );
|
|
}
|
|
|
|
//
|
|
// prepare to accumulate bounds and normals
|
|
//
|
|
ClearBounds( fr->bounds[0], fr->bounds[1] );
|
|
|
|
//
|
|
// 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
|
|
//
|
|
|
|
//
|
|
// parent tag adjust
|
|
//
|
|
if ( parentTagExists )
|
|
{
|
|
for ( t = 0; t < panim->numverts; t++ )
|
|
{
|
|
vec3_t tmp;
|
|
|
|
#if 1
|
|
VectorSubtract( panim->verts[ t ].xyz, tagParent.origin, tmp );
|
|
|
|
panim->verts[ t ].xyz[0] = DotProduct( tmp, tagParent.axis[0] );
|
|
panim->verts[ t ].xyz[1] = DotProduct( tmp, tagParent.axis[1] );
|
|
panim->verts[ t ].xyz[2] = DotProduct( tmp, tagParent.axis[2] );
|
|
#else
|
|
VectorCopy( panim->verts[ t ].xyz, tmp );
|
|
|
|
panim->verts[ t ].xyz[0] = DotProduct( tmp, tagParent.axis[0] );
|
|
panim->verts[ t ].xyz[1] = DotProduct( tmp, tagParent.axis[1] );
|
|
panim->verts[ t ].xyz[2] = DotProduct( tmp, tagParent.axis[2] );
|
|
VectorSubtract( panim->verts[ t ].xyz, tagParent.origin, panim->verts[ t ].xyz );
|
|
#endif
|
|
|
|
VectorCopy( panim->verts[ t ].normal, tmp );
|
|
panim->verts[ t ].normal[0] = DotProduct( tmp, tagParent.axis[0] );
|
|
panim->verts[ t ].normal[1] = DotProduct( tmp, tagParent.axis[1] );
|
|
panim->verts[ t ].normal[2] = DotProduct( tmp, tagParent.axis[2] );
|
|
}
|
|
}
|
|
|
|
for ( i = 0, tagcount = 0; i < g_data.model.numSurfaces; i++ )
|
|
{
|
|
int t;
|
|
|
|
//
|
|
// compute tag data
|
|
//
|
|
if (
|
|
( strcmpi( g_data.originname, g_data.surfData[ i ].header.name ) ) &&
|
|
( strstr( g_data.surfData[ i ].header.name, "tag_" ) == g_data.surfData[ i ].header.name )
|
|
)
|
|
{
|
|
tikiTag_t *pTag = &g_data.tags[tagcount];
|
|
tikiTagData_t *pTagData = &g_data.tagdata[tagcount][f];
|
|
float tri[3][3];
|
|
float basetri[3][3];
|
|
|
|
strcpy( pTag->name, g_data.surfData[ i ].header.name );
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
index = g_data.surfData[ i ].baseVertexes[ g_data.surfData[ i ].baseTriangles[ 0 ].v[ j ].index ].index;
|
|
VectorCopy( panim->verts[ index ].xyz, tri[ j ] );
|
|
}
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
VectorCopy( g_data.surfData[ i ].baseVertexes[ g_data.surfData[ i ].baseTriangles[ 0 ].v[ j ].index ].xyz, basetri[ j ] );
|
|
}
|
|
|
|
TIKI_ComputeTagFromTri( pTagData, tri, basetri );
|
|
tagcount++;
|
|
}
|
|
else
|
|
{
|
|
if ( g_data.surfData[i].verts[f] )
|
|
free( g_data.surfData[i].verts[f] );
|
|
frameXyz = g_data.surfData[i].verts[f] = calloc( 1, sizeof( float ) * 6 * g_data.surfData[i].header.numVerts );
|
|
frameNormals = frameXyz + 3;
|
|
|
|
for ( t = 0; t < g_data.surfData[ i ].header.numVerts; t++ )
|
|
{
|
|
int index;
|
|
|
|
index = g_data.surfData[ i ].baseVertexes[ t ].index;
|
|
frameXyz[t*6+0] = panim->verts[ index ].xyz[0];
|
|
frameXyz[t*6+1] = panim->verts[ index ].xyz[1];
|
|
frameXyz[t*6+2] = panim->verts[ index ].xyz[2];
|
|
frameNormals[t*6+0] = panim->verts[ index ].normal[0];
|
|
frameNormals[t*6+1] = panim->verts[ index ].normal[1];
|
|
frameNormals[t*6+2] = panim->verts[ index ].normal[2];
|
|
AddPointToBounds (&frameXyz[t*6], fr->bounds[0], fr->bounds[1] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// set name
|
|
strcpy (g_modelname, filename);
|
|
strcpy (g_data.model.name, g_modelname);
|
|
|
|
FinishModel();
|
|
ClearModel();
|
|
}
|
|
|
|
|
|
void ConvertAnim
|
|
(
|
|
const char *filename,
|
|
int numFrames,
|
|
float framerate,
|
|
qboolean optimize,
|
|
baseframe_t * base,
|
|
animframe_t * anim,
|
|
const char *originname,
|
|
qboolean dolod,
|
|
qboolean clampz,
|
|
qboolean zeroz,
|
|
qboolean clearz,
|
|
qboolean clearxy,
|
|
qboolean noorigin,
|
|
qboolean verbose
|
|
)
|
|
{
|
|
char outfilename[1024];
|
|
|
|
ClearModel();
|
|
|
|
strcpy( g_data.originname, originname );
|
|
g_lod = dolod;
|
|
g_optimize = optimize;
|
|
g_verbose = verbose;
|
|
g_clampz = clampz;
|
|
g_zeroz = zeroz;
|
|
g_clearz = clearz;
|
|
g_clearxy = clearxy;
|
|
g_noorigin = noorigin;
|
|
g_data.framerate = framerate;
|
|
strcpy( outfilename, filename );
|
|
// create destination filename
|
|
StripExtension( outfilename );
|
|
strcat( outfilename, ".tan" );
|
|
BuildAnimation( outfilename, base, anim, numFrames, framerate );
|
|
}
|