3183 lines
86 KiB
C++
3183 lines
86 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /fakk2_code/Utils/max2skl/max2skl.cpp $
|
|
// $Revision:: 38 $
|
|
// $Date:: 7/24/00 12:08p $
|
|
//
|
|
// Copyright (C) 1999 by Ritual Entertainment, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// This source is may not be distributed and/or modified without
|
|
// expressly written permission by Ritual Entertainment, Inc.
|
|
//
|
|
// $Log:: /fakk2_code/Utils/max2skl/max2skl.cpp $
|
|
//
|
|
// 38 7/24/00 12:08p Markd
|
|
// remove skipping origin code
|
|
//
|
|
// 37 7/20/00 10:53p Markd
|
|
// added short offset format
|
|
//
|
|
// 36 6/21/00 6:27p Markd
|
|
// added variable framerate support to max2skl
|
|
//
|
|
// 35 6/16/00 4:15p Markd
|
|
// Added more error checking for exceeding max verts
|
|
//
|
|
// 34 6/15/00 4:22p Markd
|
|
// reverted to original code
|
|
//
|
|
// 33 6/15/00 2:58p Markd
|
|
// added ignoreflags by default
|
|
//
|
|
// 32 6/12/00 4:26p Markd
|
|
// Added debugging messages
|
|
//
|
|
// 31 6/09/00 6:22p Markd
|
|
// Added better error reporting
|
|
//
|
|
// 30 5/05/00 5:10p Jimdose
|
|
// x and y relevant commands had to be swapped because of
|
|
// ConvertToQuake3Worldspace
|
|
//
|
|
// 29 5/05/00 3:58p Markd
|
|
// added debugging info
|
|
//
|
|
// 28 5/05/00 3:37p Jimdose
|
|
// merged code
|
|
// added -ignoreflags for disabling keeping of parent bone when no other bone
|
|
// with the same flags is available
|
|
//
|
|
// 6 5/02/00 5:47p Jimdose
|
|
// fixed bug in RemoveUnusedBones
|
|
//
|
|
// 5 5/01/00 8:15p Pmack
|
|
// catch a crash and added some verbose info on bone removal.
|
|
//
|
|
// 27 4/25/00 12:16p Jimdose
|
|
// merged changes
|
|
// added code to test for verts with no wieghts and set them to an average
|
|
// weight
|
|
// added -destdir and -uvb
|
|
//
|
|
// 26 3/27/00 5:09p Jimdose
|
|
// changed check for MAX_BLENDBONES exceeded to MAX_LOADBLENDBONES exceeded
|
|
//
|
|
// 3 4/17/00 2:55p Jimdose
|
|
// added ConvertToQuake3Worldspace
|
|
// added FixMirroredBones
|
|
//
|
|
// 25 3/15/00 7:15p Jimdose
|
|
// added bounds tests for when adding verts and triangles to surfaces
|
|
//
|
|
// 24 3/04/00 12:06p Jimdose
|
|
// added zerox and zeroy
|
|
//
|
|
// 23 3/03/00 6:07p Jimdose
|
|
// added clearx, cleary, and loop commands
|
|
//
|
|
// 22 3/03/00 11:21a Jimdose
|
|
// added -offset command
|
|
//
|
|
// 21 2/26/00 1:31p Jimdose
|
|
// added -bones to the list of command line options
|
|
//
|
|
// 20 2/01/00 6:37p Jimdose
|
|
// Tags were being taken into account when generating bounding boxes for frames
|
|
//
|
|
// 19 1/19/00 7:36p Jimdose
|
|
// changed default non verbose output
|
|
//
|
|
// 18 1/19/00 5:17p Jimdose
|
|
// CreateSurfaces now adds a new bone when it finds a tag that is named
|
|
// different than a preexisting bone
|
|
// ReduceBonesPerVertex now combines blends with same bone number into one on
|
|
// each vertex
|
|
//
|
|
// 17 1/13/00 6:35p Jimdose
|
|
// made RemoveUnusedBones not remove bones with names that begin with "tag_"
|
|
//
|
|
// 16 1/13/00 4:19p Jimdose
|
|
// fixed RemoveUnusedBones for when bone 0 is not scene root
|
|
//
|
|
// 15 1/11/00 7:30p Jimdose
|
|
// added boneflags
|
|
//
|
|
// 14 1/05/00 11:55a Jimdose
|
|
// nolod was incorrectly initialized to true
|
|
// no longer set triangle id to be the index of the surface it's on. This
|
|
// caused triangles to sometimes be added to mutilple surfaces.
|
|
//
|
|
// 13 11/03/99 3:15p Jimdose
|
|
// fixed problems with delta calculation
|
|
//
|
|
// 12 10/20/99 2:50p Jimdose
|
|
// added CopyBones and -bones parm for animations where the heirarchy is
|
|
// different from the base model
|
|
//
|
|
// 11 10/20/99 2:02p Jimdose
|
|
// fixed bug in SaveAnimation where converting to Q3 worldspace caused a
|
|
// roll-pitch swap in the game
|
|
//
|
|
// 10 10/18/99 6:07p Jimdose
|
|
// changed error message to include filename
|
|
// made SaveBaseFrame allow for twice as many surfaces in base model, but only
|
|
// allow the normal amount in saved model
|
|
//
|
|
// 9 10/14/99 6:37p Jimdose
|
|
// added the ability to export MD4 (Quake 3 skeletal) files
|
|
//
|
|
// 8 10/11/99 5:50p Jimdose
|
|
// added ScaleModel, ReverseAnimation, ReduceBonesPerVertex, and
|
|
// RemoveUnusedBones
|
|
//
|
|
// 7 10/08/99 7:19p Jimdose
|
|
// got tags and deltas working
|
|
//
|
|
// 6 10/06/99 12:46p Jimdose
|
|
// added ComputeVertexNormals
|
|
//
|
|
// 5 10/04/99 7:06p Jimdose
|
|
// moved lod to surfaces
|
|
// added radius to frames
|
|
// read texture coordinates from UV file
|
|
//
|
|
// 4 9/27/99 5:01p Jimdose
|
|
// fixed some errors in saving animations
|
|
//
|
|
// 3 9/27/99 11:39a Jimdose
|
|
// changed TIKI_ANIM_IDENT to TIKI_SKEL_ANIM_IDENT
|
|
//
|
|
// 2 9/24/99 4:39p Jimdose
|
|
// first working version
|
|
//
|
|
// DESCRIPTION:
|
|
//
|
|
|
|
#include "max2skl.h"
|
|
#include "str.h"
|
|
#include "progmesh.h"
|
|
#include "uvector3.h"
|
|
#include "umatrix.h"
|
|
#include "uquat.h"
|
|
|
|
double scale_factor;
|
|
bool nolod;
|
|
bool zerox;
|
|
bool zeroy;
|
|
bool zeroz;
|
|
bool noclampz;
|
|
bool clearx;
|
|
bool cleary;
|
|
bool clearz;
|
|
bool loop;
|
|
bool clearxy;
|
|
bool noorigin;
|
|
bool reverse;
|
|
bool force;
|
|
bool baseframe;
|
|
bool md4;
|
|
bool ignoreflags;
|
|
bool adjustfps;
|
|
float fps;
|
|
UVector3 modeloffset;
|
|
|
|
str originname( "origin" );
|
|
|
|
extern "C" void OrderMesh( int input[][3], int output[][3], int numTris );
|
|
extern "C" int main ( int argc, char * argv[] );
|
|
|
|
SkelModel::SkelModel()
|
|
{
|
|
memset( &model, 0, sizeof( model ) );
|
|
}
|
|
|
|
SkelModel::~SkelModel()
|
|
{
|
|
FreeModel();
|
|
}
|
|
|
|
void SkelModel::FreeModel
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < model.numframes; i++ )
|
|
{
|
|
delete[] model.anim[ i ].bones;
|
|
}
|
|
|
|
delete[] model.verts;
|
|
delete[] model.faces;
|
|
delete[] model.anim;
|
|
delete[] model.bones;
|
|
delete[] model.surfaces;
|
|
|
|
memset( &model, 0, sizeof( model ) );
|
|
}
|
|
|
|
void SkelModel::LoadIgnoreFile
|
|
(
|
|
const char *filename
|
|
)
|
|
|
|
{
|
|
Script script;
|
|
|
|
printf(" reading ignore file %s.\n", filename );
|
|
|
|
script.LoadFile( filename );
|
|
while( script.TokenAvailable( true ) )
|
|
{
|
|
ignorelist.AddObject( str( script.GetToken( true ) ) );
|
|
}
|
|
}
|
|
|
|
bool SkelModel::IgnoreSurface
|
|
(
|
|
const char *name
|
|
)
|
|
|
|
{
|
|
int i;
|
|
|
|
if ( !name || !strlen( name ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for( i = 1; i <= ignorelist.NumObjects(); i++ )
|
|
{
|
|
if ( !ignorelist.ObjectAt( i ).icmp( name ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void SkelModel::LoadSKL
|
|
(
|
|
const char *filename
|
|
)
|
|
|
|
{
|
|
Script script;
|
|
int version;
|
|
|
|
FreeModel();
|
|
|
|
memset( model.name, 0, sizeof( model.name ) );
|
|
strncpy( model.name, filename, sizeof( model.name ) - 1 );
|
|
|
|
VectorClear( model.totaldelta );
|
|
|
|
script.LoadFile( filename );
|
|
|
|
script.Skip( "SKELETON" );
|
|
script.Skip( "VERSION" );
|
|
|
|
version = script.GetInteger( false );
|
|
if ( version != SKL_VERSION )
|
|
{
|
|
script.Error( "Expecting version %d but found version %d.\n", SKL_VERSION, version );
|
|
}
|
|
|
|
LoadSurfaces( script );
|
|
LoadBones( script );
|
|
LoadVerts( script );
|
|
LoadFaces( script );
|
|
LoadFrames( script );
|
|
|
|
script.Skip( "END" );
|
|
}
|
|
|
|
void SkelModel::LoadSurfaces
|
|
(
|
|
Script &script
|
|
)
|
|
|
|
{
|
|
int i;
|
|
surface_t *surf;
|
|
int num;
|
|
|
|
// skip over nummaterials
|
|
script.Skip( "NUMMATERIALS" );
|
|
|
|
// get number of materials
|
|
num = script.GetInteger( false );
|
|
|
|
delete[] model.surfaces;
|
|
model.surfaces = new surface_t[ num ];
|
|
memset( model.surfaces, 0, sizeof( surface_t ) * num );
|
|
|
|
// get the surfaces
|
|
model.numsurfaces = 0;
|
|
for( i = 0; i < num; i++ )
|
|
{
|
|
// skip over "MATERIAL"
|
|
script.Skip( "MATERIAL" );
|
|
|
|
surf = &model.surfaces[ model.numsurfaces ];
|
|
|
|
// get the surface id
|
|
surf->id = script.GetInteger( false );
|
|
|
|
// get the surface name
|
|
memset( surf->name, 0, MAX_QPATH );
|
|
strncpy( surf->name, script.GetToken( false ), MAX_QPATH - 1 );
|
|
|
|
if ( !IgnoreSurface( surf->name ) )
|
|
{
|
|
// add this surface
|
|
model.numsurfaces++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::LoadBones
|
|
(
|
|
Script &script
|
|
)
|
|
|
|
{
|
|
skelBoneName_t *bone;
|
|
int i;
|
|
int bonenum;
|
|
str bonetype;
|
|
|
|
script.Skip( "NUMBONES" );
|
|
model.numbones = script.GetInteger( false );
|
|
|
|
if ( model.numbones > MAX_BONES )
|
|
{
|
|
script.Error( "Too many bones in model. Numbones cannot exceed %d.", MAX_BONES );
|
|
}
|
|
|
|
delete[] model.bones;
|
|
model.bones = new skelBoneName_t[ model.numbones ];
|
|
|
|
bone = model.bones;
|
|
for( i = 0; i < model.numbones; i++, bone++ )
|
|
{
|
|
bonetype = script.GetToken( true );
|
|
if ( bonetype == "BONE" )
|
|
{
|
|
bone->flags = 0;
|
|
}
|
|
else if ( bonetype == "LEGBONE" )
|
|
{
|
|
bone->flags = TIKI_BONEFLAG_LEG;
|
|
}
|
|
else
|
|
{
|
|
script.Error( "Expecting 'BONE' or 'LEGBONE', but found ", bonetype.c_str() );
|
|
}
|
|
|
|
bonenum = script.GetInteger( false );
|
|
if ( bonenum != i )
|
|
{
|
|
script.Error( "Bone number out of sync" );
|
|
}
|
|
|
|
bone->parent = script.GetInteger( false );
|
|
if ( ( bone->parent < -1 ) || ( bone->parent >= model.numbones ) )
|
|
{
|
|
script.Error( "Parent bone out of range" );
|
|
}
|
|
|
|
memset( bone->name, 0, sizeof( bone->name ) );
|
|
strncpy( bone->name, script.GetRaw(), MAX_QPATH - 1 );
|
|
}
|
|
}
|
|
|
|
void SkelModel::LoadVerts
|
|
(
|
|
Script &script
|
|
)
|
|
|
|
{
|
|
blendvert_t *blend;
|
|
loadvertx_t *vert;
|
|
int j;
|
|
int i;
|
|
int v;
|
|
|
|
script.Skip( "NUMVERTS" );
|
|
model.numverts = script.GetInteger( false );
|
|
|
|
delete[] model.verts;
|
|
model.verts = new loadvertx_t[ model.numverts ];
|
|
memset( model.verts, 0, sizeof( loadvertx_t ) * model.numverts );
|
|
|
|
vert = model.verts;
|
|
for( i = 0; i < model.numverts; i++, vert++ )
|
|
{
|
|
script.Skip( "VERT" );
|
|
v = script.GetInteger( false );
|
|
if ( v != i )
|
|
{
|
|
script.Error( "Vertex number out of sync" );
|
|
}
|
|
|
|
vert->index = i;
|
|
vert->texCoords[ 0 ] = 0;
|
|
vert->texCoords[ 1 ] = 0;
|
|
|
|
script.Skip( "NORMAL" );
|
|
script.GetVector( model.verts[ i ].normal, false );
|
|
|
|
script.Skip( "BONES" );
|
|
vert->numbones = script.GetInteger( false );
|
|
if ( vert->numbones >= MAX_LOADBLENDBONES )
|
|
{
|
|
Error( "Exceeded %d bones on vertex %d\n", MAX_LOADBLENDBONES, i );
|
|
}
|
|
|
|
blend = vert->blend;
|
|
for( j = 0; j < vert->numbones; j++, blend++ )
|
|
{
|
|
script.Skip( "BONE" );
|
|
|
|
blend->bone = script.GetInteger( false );
|
|
blend->weight = script.GetFloat( false );
|
|
script.GetVector( blend->offset, false );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::LoadFaces
|
|
(
|
|
Script &script
|
|
)
|
|
|
|
{
|
|
loadfacevertx_t *facevert;
|
|
loadtriangle_t *face;
|
|
int j;
|
|
int i;
|
|
|
|
script.Skip( "NUMFACES" );
|
|
model.numfaces = script.GetInteger( false );
|
|
|
|
delete[] model.faces;
|
|
model.faces = new loadtriangle_t[ model.numfaces ];
|
|
|
|
face = model.faces;
|
|
for( i = 0; i < model.numfaces; i++, face++ )
|
|
{
|
|
script.Skip( "TRI" );
|
|
face->id = script.GetInteger( false );
|
|
|
|
facevert = face->verts;
|
|
for( j = 0; j < 3; j++, facevert++ )
|
|
{
|
|
facevert->vertindex = script.GetInteger( false );
|
|
if ( ( facevert->vertindex < 0 ) || ( facevert->vertindex >= model.numverts ) )
|
|
{
|
|
script.Error( "Vertex index out of range" );
|
|
}
|
|
|
|
facevert->texCoords[ 0 ] = script.GetFloat( false );
|
|
facevert->texCoords[ 1 ] = script.GetFloat( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkipFrame
|
|
(
|
|
Script &script,
|
|
int numbones
|
|
)
|
|
{
|
|
int j;
|
|
|
|
// get frame header
|
|
script.Skip( "FRAME" );
|
|
// skip frame number
|
|
script.GetInteger( false );
|
|
for( j = 0; j < numbones; j++ )
|
|
{
|
|
vec3_t vec;
|
|
script.Skip( "BONE" );
|
|
// skip bone number
|
|
script.GetInteger( false );
|
|
script.Skip( "OFFSET" );
|
|
// skip offset
|
|
script.GetVector( vec, false );
|
|
script.Skip( "X" );
|
|
// skip x
|
|
script.GetVector( vec, false );
|
|
script.Skip( "Y" );
|
|
// skip y
|
|
script.GetVector( vec, false );
|
|
script.Skip( "Z" );
|
|
// skip z
|
|
script.GetVector( vec, false );
|
|
}
|
|
}
|
|
|
|
|
|
void SkelModel::LoadFrames
|
|
(
|
|
Script &script
|
|
)
|
|
|
|
{
|
|
loadbone_t *bone;
|
|
int k;
|
|
int j;
|
|
int i;
|
|
int frame;
|
|
int b;
|
|
int factor;
|
|
int realframe;
|
|
int orig_numframes;
|
|
|
|
for( i = 0; i < model.numframes; i++ )
|
|
{
|
|
delete[] model.anim[ i ].bones;
|
|
}
|
|
delete[] model.anim;
|
|
|
|
script.Skip( "FRAMERATE" );
|
|
model.framerate = script.GetFloat( false );
|
|
|
|
script.Skip( "NUMFRAMES" );
|
|
model.numframes = script.GetInteger( false );
|
|
|
|
orig_numframes = model.numframes;
|
|
|
|
if ( adjustfps )
|
|
{
|
|
factor = ( int )( ( model.framerate / fps ) + 0.5f );
|
|
if ( factor < 1 )
|
|
factor = 1;
|
|
model.framerate /= factor;
|
|
if ( model.framerate < 1 )
|
|
{
|
|
model.framerate = 1;
|
|
}
|
|
model.numframes /= factor;
|
|
if ( model.numframes < 1 )
|
|
{
|
|
model.numframes = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
factor = 1;
|
|
}
|
|
|
|
model.anim = new loadframe_t[ model.numframes ];
|
|
|
|
realframe = 0;
|
|
|
|
for( i = 0; i < model.numframes; i++ )
|
|
{
|
|
script.Skip( "FRAME" );
|
|
frame = script.GetInteger( false ) / factor;
|
|
if ( frame != i )
|
|
{
|
|
script.Error( "Frame number out of sync" );
|
|
}
|
|
|
|
model.anim[ i ].bones = new loadbone_t[ model.numbones ];
|
|
bone = model.anim[ i ].bones;
|
|
|
|
ClearBounds( model.anim[ i ].bounds[ 0 ], model.anim[ i ].bounds[ 1 ] );
|
|
|
|
VectorClear( model.anim[ i ].delta );
|
|
VectorClear( model.anim[ i ].origin );
|
|
|
|
for( j = 0; j < model.numbones; j++, bone++ )
|
|
{
|
|
script.Skip( "BONE" );
|
|
b = script.GetInteger( false );
|
|
if ( b != j )
|
|
{
|
|
script.Error( "Bone number out of sync" );
|
|
}
|
|
|
|
script.Skip( "OFFSET" );
|
|
script.GetVector( bone->offset, false );
|
|
|
|
script.Skip( "X" );
|
|
script.GetVector( bone->matrix[ 0 ], false );
|
|
|
|
script.Skip( "Y" );
|
|
script.GetVector( bone->matrix[ 1 ], false );
|
|
|
|
script.Skip( "Z" );
|
|
script.GetVector( bone->matrix[ 2 ], false );
|
|
}
|
|
|
|
// increment our realframe
|
|
realframe++;
|
|
|
|
//
|
|
// skip over any frames we might be skipping
|
|
//
|
|
for( k = 1; k < factor; k++ )
|
|
{
|
|
// make sure we don't read past the end of the data
|
|
if ( realframe == orig_numframes )
|
|
break;
|
|
|
|
SkipFrame( script, model.numbones );
|
|
|
|
// increment our realframe
|
|
realframe++;
|
|
|
|
}
|
|
}
|
|
for( ; realframe < orig_numframes; realframe++ )
|
|
{
|
|
SkipFrame( script, model.numbones );
|
|
}
|
|
}
|
|
|
|
void SkelModel::CopyBaseModel
|
|
(
|
|
SkelModel &base
|
|
)
|
|
|
|
{
|
|
int i;
|
|
|
|
// copy the materials
|
|
delete[] model.surfaces;
|
|
model.numsurfaces = base.model.numsurfaces;
|
|
model.surfaces = new surface_t[ model.numsurfaces ];
|
|
memcpy( model.surfaces, base.model.surfaces, sizeof( surface_t ) * model.numsurfaces );
|
|
|
|
// get the faces
|
|
if ( model.numfaces != base.model.numfaces )
|
|
{
|
|
Error( "Base model has different number of faces than model %s, re-export.\n", model.name );
|
|
}
|
|
|
|
for( i = 0; i < model.numfaces; i++ )
|
|
{
|
|
model.faces[ i ] = base.model.faces[ i ];
|
|
}
|
|
|
|
// get the verts
|
|
if ( model.numverts != base.model.numverts )
|
|
{
|
|
Error( "Base model has different number of verts than model %s, re-export.\n", model.name );
|
|
}
|
|
}
|
|
|
|
void SkelModel::CopyBones
|
|
(
|
|
SkelModel &base
|
|
)
|
|
|
|
{
|
|
loadvertx_t *vert;
|
|
loadframe_t *frame;
|
|
loadbone_t framebones[ MAX_BONES ];
|
|
int bonemap[ MAX_BONES ];
|
|
int i;
|
|
int j;
|
|
|
|
// get the bones
|
|
if ( model.numbones != base.model.numbones )
|
|
{
|
|
Error( "%s has different number of bones than %s, re-export.\n", base.model.name, model.name );
|
|
}
|
|
|
|
// create a mapping to the new bone order
|
|
for( i = 0; i < model.numbones; i++ )
|
|
{
|
|
for( j = 0; j < model.numbones; j++ )
|
|
{
|
|
if ( !str::icmp( model.bones[ i ].name, base.model.bones[ j ].name ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( j == model.numbones )
|
|
{
|
|
Error( "Bone '%s' not found in source model.\n", model.bones[ i ].name );
|
|
}
|
|
|
|
bonemap[ i ] = j;
|
|
}
|
|
|
|
// copy the bone heirarchy
|
|
memcpy( model.bones, base.model.bones, sizeof( skelBoneName_t ) * model.numbones );
|
|
|
|
// fixup the bone indices on the vertices
|
|
vert = model.verts;
|
|
for( i = 0; i < model.numverts; i++, vert++ )
|
|
{
|
|
for( j = 0; j < vert->numbones; j++ )
|
|
{
|
|
vert->blend[ j ].bone = bonemap[ vert->blend[ j ].bone ];
|
|
}
|
|
}
|
|
|
|
// fixup the bone order on the animation
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes; i++, frame++ )
|
|
{
|
|
// save off the original frame
|
|
memcpy( framebones, frame->bones, sizeof( loadbone_t ) * model.numbones );
|
|
|
|
// store the bones off in the proper order
|
|
for( j = 0; j < model.numbones; j++ )
|
|
{
|
|
frame->bones[ bonemap[ j ] ] = framebones[ j ];
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::LoadUVFile
|
|
(
|
|
const char *filename
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
int version;
|
|
Script script;
|
|
surface_t *surf;
|
|
int num;
|
|
|
|
script.LoadFile( filename );
|
|
|
|
// skip over version
|
|
script.Skip( "version" );
|
|
|
|
// get version number
|
|
version = script.GetInteger( false );
|
|
if ( version != UV_FILE_VERSION )
|
|
{
|
|
script.Error( "UV file is out of date, re-export.\n" );
|
|
}
|
|
|
|
// skip over nummaterials
|
|
script.Skip( "nummaterials" );
|
|
|
|
// get number of materials
|
|
num = script.GetInteger( false );
|
|
|
|
delete[] model.surfaces;
|
|
model.surfaces = new surface_t[ num ];
|
|
memset( model.surfaces, 0, sizeof( surface_t ) * num );
|
|
|
|
// get the surfaces
|
|
model.numsurfaces = 0;
|
|
for( i = 0; i < num; i++ )
|
|
{
|
|
// skip over "material"
|
|
script.Skip( "material" );
|
|
|
|
surf = &model.surfaces[ model.numsurfaces ];
|
|
|
|
// get the surface id
|
|
surf->id = script.GetInteger( false );
|
|
|
|
// get the surface name
|
|
memset( surf->name, 0, MAX_QPATH );
|
|
strncpy( surf->name, script.GetToken( false ), MAX_QPATH - 1 );
|
|
|
|
if ( !IgnoreSurface( surf->name ) )
|
|
{
|
|
// add this surface
|
|
model.numsurfaces++;
|
|
}
|
|
}
|
|
|
|
// skip over numverts
|
|
script.Skip( "numverts" );
|
|
|
|
// get the number of verts
|
|
num = script.GetInteger( false );
|
|
if ( model.numverts != num )
|
|
{
|
|
script.Error( "UV file has different number of vertices than model %s, re-export.\n", model.name );
|
|
}
|
|
|
|
// skip the verts
|
|
for( i = 0; i < model.numverts; i++ )
|
|
{
|
|
// skip the vertex
|
|
script.GetFloat( true );
|
|
script.GetFloat( false );
|
|
script.GetFloat( false );
|
|
}
|
|
|
|
// skip over numfaces
|
|
script.Skip( "numfaces" );
|
|
|
|
// get the number of faces
|
|
num = script.GetInteger( false );
|
|
if ( model.numfaces != num )
|
|
{
|
|
script.Error( "UV file has different number of faces than model %s, re-export.\n", model.name );
|
|
}
|
|
|
|
// get the faces
|
|
for( i = 0; i < model.numfaces; i++ )
|
|
{
|
|
// get the material id
|
|
model.faces[ i ].id = script.GetInteger( true );
|
|
|
|
// get the verts
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
// get the vertindex
|
|
model.faces[ i ].verts[ j ].vertindex = script.GetInteger( false );
|
|
|
|
// get texture coordinates
|
|
model.faces[ i ].verts[ j ].texCoords[ 0 ] = script.GetFloat( false );
|
|
model.faces[ i ].verts[ j ].texCoords[ 1 ] = script.GetFloat( false );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::CreateSurfaces
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
int k;
|
|
int l;
|
|
surface_t *surf;
|
|
loadvertx_t *facevert;
|
|
loadvertx_t *vert;
|
|
loadtriangle_t *face;
|
|
int index;
|
|
vec2_t texCoords;
|
|
|
|
// add the unique verts to each surface
|
|
surf = model.surfaces;
|
|
for( i = 0; i < model.numsurfaces; i++, surf++ )
|
|
{
|
|
surf->numtris = 0;
|
|
surf->numverts = 0;
|
|
|
|
// find all the faces that match this surface id
|
|
face = model.faces;
|
|
for( j = 0; j < model.numfaces; j++, face++ )
|
|
{
|
|
if ( surf->id == face->id )
|
|
{
|
|
if ( surf->numtris >= TIKI_MAX_TRIANGLES )
|
|
{
|
|
Error( "Exceeded %d triangles on surface '%s'\n", TIKI_MAX_TRIANGLES, surf->name );
|
|
}
|
|
|
|
// go through triangle and add each unique vertex
|
|
for( l = 0; l < 3; l++ )
|
|
{
|
|
index = face->verts[ l ].vertindex;
|
|
|
|
facevert = &model.verts[ index ];
|
|
texCoords[ 0 ] = face->verts[ l ].texCoords[ 0 ];
|
|
texCoords[ 1 ] = face->verts[ l ].texCoords[ 1 ];
|
|
|
|
// see if it already exists
|
|
vert = surf->verts;
|
|
for( k = 0; k < surf->numverts; k++, vert++ )
|
|
{
|
|
if ( ( vert->index == index ) &&
|
|
( vert->texCoords[ 0 ] == texCoords[ 0 ] ) &&
|
|
( vert->texCoords[ 1 ] == texCoords[ 1 ] ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if it wasn't found, add it in
|
|
if ( k == surf->numverts )
|
|
{
|
|
if ( surf->numverts >= TIKI_MAX_VERTS )
|
|
{
|
|
Error( "Exceeded %d vertices on surface '%s'\n", TIKI_MAX_VERTS, surf->name );
|
|
}
|
|
|
|
*vert = model.verts[ index ];
|
|
vert->index = index;
|
|
vert->texCoords[ 0 ] = texCoords[ 0 ];
|
|
vert->texCoords[ 1 ] = texCoords[ 1 ];
|
|
|
|
surf->numverts++;
|
|
}
|
|
|
|
surf->tris[ surf->numtris ][ l ] = k;
|
|
}
|
|
|
|
surf->numtris++;
|
|
}
|
|
}
|
|
|
|
// initialize the collapse map for this surface
|
|
surf->minLod = surf->numverts;
|
|
for( j = 0; j < surf->numverts; j++ )
|
|
{
|
|
surf->collapseMap[ i ] = i;
|
|
}
|
|
}
|
|
|
|
// faces are no longer valid, so delete them
|
|
delete[] model.faces;
|
|
model.faces = NULL;
|
|
}
|
|
|
|
void SkelModel::CalcFrameBones
|
|
(
|
|
int framenum,
|
|
UMat3 bones[ MAX_BONES ],
|
|
UVector3 offsets[ MAX_BONES ]
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int parent;
|
|
loadframe_t *frame;
|
|
loadbone_t *bone;
|
|
UMat3 mat;
|
|
UVector3 offset;
|
|
|
|
// do sanity check on the frame number
|
|
if ( ( framenum < 0 ) || ( framenum >= model.numframes ) )
|
|
{
|
|
Error( "Frame %d out of range\n" );
|
|
}
|
|
|
|
// get the specified frame
|
|
frame = &model.anim[ framenum ];
|
|
|
|
// propogate the bone transformations
|
|
bone = frame->bones;
|
|
for( i = 0; i < model.numbones; i++, bone++ )
|
|
{
|
|
mat.vecs[ 0 ] = bone->matrix[ 0 ];
|
|
mat.vecs[ 1 ] = bone->matrix[ 1 ];
|
|
mat.vecs[ 2 ] = bone->matrix[ 2 ];
|
|
|
|
offset = bone->offset;
|
|
|
|
parent = model.bones[ i ].parent;
|
|
if ( 1 )//parent < 0 )
|
|
{
|
|
bones[ i ] = mat;
|
|
offsets[ i ] = offset;
|
|
}
|
|
else
|
|
{
|
|
assert( parent < i );
|
|
bones[ i ].MultiplyMatrix( mat, bones[ parent ] );
|
|
bones[ parent ].UnprojectVector( offset, offsets[ i ] );
|
|
offsets[ i ] += offsets[ parent ];
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::CalcVerts
|
|
(
|
|
vec3_t *outverts,
|
|
UMat3 bones[ MAX_BONES ],
|
|
UVector3 offsets[ MAX_BONES ]
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
loadvertx_t *vert;
|
|
blendvert_t *blend;
|
|
UVector3 offset;
|
|
UVector3 temp;
|
|
|
|
if ( model.numverts > TIKI_MAX_VERTS )
|
|
{
|
|
Error( "Too many vertices %d out of %d in model %s\n", model.numverts, TIKI_MAX_VERTS, model.name );
|
|
}
|
|
|
|
// blend the bones for each vertex to generate the final vertex position
|
|
vert = model.verts;
|
|
for( i = 0; i < model.numverts; i++, vert++ )
|
|
{
|
|
blend = vert->blend;
|
|
|
|
// blend all the bones together for this vertex
|
|
offset = UVector3::zero;
|
|
for( j = 0; j < vert->numbones; j++, blend++ )
|
|
{
|
|
bones[ blend->bone ].UnprojectVector( blend->offset, temp );
|
|
offset += ( offsets[ blend->bone ] + temp ) * blend->weight;
|
|
}
|
|
|
|
// store off the result in the buffer
|
|
outverts[ i ][ 0 ] = offset[ 0 ];
|
|
outverts[ i ][ 1 ] = offset[ 1 ];
|
|
outverts[ i ][ 2 ] = offset[ 2 ];
|
|
}
|
|
}
|
|
|
|
void SkelModel::CalcFrame
|
|
(
|
|
int framenum,
|
|
vec3_t *outverts
|
|
)
|
|
|
|
{
|
|
UMat3 bones[ MAX_BONES ];
|
|
UVector3 offsets[ MAX_BONES ];
|
|
|
|
CalcFrameBones( framenum, bones, offsets );
|
|
CalcVerts( outverts, bones, offsets );
|
|
}
|
|
|
|
void SkelModel::ComputeVertexNormals
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
UMat3 bones[ MAX_BONES ];
|
|
UVector3 offsets[ MAX_BONES ];
|
|
surface_t *surf;
|
|
UVector3 p;
|
|
int i;
|
|
int j;
|
|
|
|
CalcFrameBones( 0, bones, offsets );
|
|
|
|
surf = model.surfaces;
|
|
for( i = 0; i < model.numsurfaces; i++, surf++ )
|
|
{
|
|
// convert vertices
|
|
for( j = 0; j < surf->numverts; j++ )
|
|
{
|
|
bones[ surf->verts[ j ].blend[ 0 ].bone ].ProjectVector( surf->verts[ j ].normal, p );
|
|
|
|
surf->verts[ j ].normal[ 0 ] = p.x;
|
|
surf->verts[ j ].normal[ 1 ] = p.y;
|
|
surf->verts[ j ].normal[ 2 ] = p.z;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::CalculateLOD
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
int minresolution;
|
|
float *vp;
|
|
tridata td;
|
|
vec3_t baseVerts[ TIKI_MAX_VERTS ];
|
|
loadvertx_t newverts[ TIKI_MAX_VERTS ];
|
|
surface_t *surf;
|
|
|
|
CalcFrame( 0, baseVerts );
|
|
|
|
surf = model.surfaces;
|
|
for( j = 0; j < model.numsurfaces; j++, surf++ )
|
|
{
|
|
List<Vector> verts;
|
|
List<tridata> tris;
|
|
List<int> permutation;
|
|
List<int> collapse_map;
|
|
|
|
if ( surf->numverts > TIKI_MAX_VERTS )
|
|
{
|
|
Error( "Too many vertices %d out of %d on surface %s in model %s\n", surf->numverts, TIKI_MAX_VERTS, surf->name, model.name );
|
|
}
|
|
// convert vertices
|
|
for( i = 0; i < surf->numverts; i++ )
|
|
{
|
|
vp = baseVerts[ surf->verts[ i ].index ];
|
|
verts.Add( Vector( vp[ 0 ], vp[ 1 ], vp[ 2 ] ) );
|
|
}
|
|
|
|
// convert triangles
|
|
for( i = 0; i < surf->numtris; i++ )
|
|
{
|
|
td.v[ 0 ] = surf->tris[ i ][ 0 ];
|
|
td.v[ 1 ] = surf->tris[ i ][ 1 ];
|
|
td.v[ 2 ] = surf->tris[ i ][ 2 ];
|
|
|
|
tris.Add( td );
|
|
}
|
|
|
|
ProgressiveMesh( verts, tris, collapse_map, permutation, &minresolution );
|
|
|
|
assert( permutation.num == verts.num );
|
|
assert( verts.num == surf->numverts );
|
|
|
|
// copy the collapse map
|
|
for( i = 0; i < surf->numverts; i++ )
|
|
{
|
|
surf->collapseMap[ i ] = collapse_map[ i ];
|
|
}
|
|
|
|
// reorder the vertices
|
|
for( i = 0; i < surf->numverts; i++ )
|
|
{
|
|
newverts[ permutation[ i ] ] = surf->verts[ i ];
|
|
}
|
|
memcpy( surf->verts, newverts, surf->numverts * sizeof( surf->verts[ 0 ] ) );
|
|
|
|
// reorder the vertex indices on the triangles
|
|
for( i = 0; i < surf->numtris; i++ )
|
|
{
|
|
surf->tris[ i ][ 0 ] = permutation[ surf->tris[ i ][ 0 ] ];
|
|
surf->tris[ i ][ 1 ] = permutation[ surf->tris[ i ][ 1 ] ];
|
|
surf->tris[ i ][ 2 ] = permutation[ surf->tris[ i ][ 2 ] ];
|
|
}
|
|
|
|
surf->minLod = minresolution;
|
|
}
|
|
}
|
|
|
|
void SkelModel::CalculateTriStrips
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
int s;
|
|
surface_t *surf;
|
|
int mesh[ TIKI_MAX_TRIANGLES ][ 3 ];
|
|
|
|
// go through each surface and find best strip/fans possible
|
|
surf = model.surfaces;
|
|
for ( s = 0; s < model.numsurfaces; s++, surf++ )
|
|
{
|
|
if ( surf->numtris )
|
|
{
|
|
memcpy( mesh, surf->tris, sizeof( mesh[ 0 ] ) * surf->numtris );
|
|
qprintf( "%s(%d):\n", surf->name, surf->id );
|
|
OrderMesh( mesh, surf->tris, surf->numtris );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::CalculateBounds
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
int k;
|
|
int l;
|
|
int index;
|
|
vec3_t baseVerts[ TIKI_MAX_VERTS ];
|
|
vec3_t tmpVec;
|
|
loadframe_t *frame;
|
|
float maxRadius;
|
|
float radius;
|
|
surface_t *surf;
|
|
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes; i++, frame++ )
|
|
{
|
|
CalcFrame( i, baseVerts );
|
|
ClearBounds( frame->bounds[ 0 ], frame->bounds[ 1 ] );
|
|
|
|
surf = model.surfaces;
|
|
for( j = 0; j < model.numsurfaces; j++, surf++ )
|
|
{
|
|
if ( str::icmpn( surf->name, "tag_", 4 ) && originname.icmp( surf->name ) )
|
|
{
|
|
for( l = 0; l < surf->numtris; l++ )
|
|
{
|
|
for( k = 0; k < 3; k++ )
|
|
{
|
|
index = surf->verts[ surf->tris[ l ][ k ] ].index;
|
|
AddPointToBounds( baseVerts[ index ], frame->bounds[ 0 ], frame->bounds[ 1 ] );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// compute localOrigin and radius
|
|
//
|
|
maxRadius = 0;
|
|
for( j = 0; j < 8; j++ )
|
|
{
|
|
tmpVec[ 0 ] = frame->bounds[ ( j & 1 ) != 0 ][ 0 ];
|
|
tmpVec[ 1 ] = frame->bounds[ ( j & 2 ) != 0 ][ 1 ];
|
|
tmpVec[ 2 ] = frame->bounds[ ( j & 4 ) != 0 ][ 2 ];
|
|
|
|
radius = VectorLength( tmpVec );
|
|
if ( radius > maxRadius )
|
|
{
|
|
maxRadius = radius;
|
|
}
|
|
}
|
|
|
|
frame->radius = maxRadius;
|
|
}
|
|
}
|
|
|
|
void SkelModel::ComputeTagFromTri
|
|
(
|
|
loadbone_t *bone,
|
|
const float pTri[ 3 ][ 3 ]
|
|
)
|
|
|
|
{
|
|
float len[ 3 ];
|
|
vec3_t axis[ 3 ];
|
|
vec3_t sides[ 3 ];
|
|
int longestSide;
|
|
int shortestSide;
|
|
int hypotSide;
|
|
int origin;
|
|
int j;
|
|
float d;
|
|
|
|
longestSide = 0;
|
|
shortestSide = 0;
|
|
hypotSide = 0;
|
|
origin = 0;
|
|
|
|
memset( axis, 0, sizeof( axis ) );
|
|
memset( sides, 0, sizeof( sides ) );
|
|
|
|
// 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 ];
|
|
|
|
len[ j ] = VectorLength( sides[ j ] );
|
|
}
|
|
|
|
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;
|
|
|
|
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 ], axis[ 0 ] );
|
|
VectorNormalize( sides[ shortestSide ], axis[ 1 ] );
|
|
|
|
// project shortest side so that it is exactly 90 degrees to the longer side
|
|
d = DotProduct( axis[ 1 ], axis[ 0 ] );
|
|
VectorMA( axis[ 1 ], -d, axis[ 0 ], axis[ 1 ] );
|
|
|
|
VectorNormalize( axis[ 1 ], axis[ 1 ] );
|
|
|
|
CrossProduct( sides[ longestSide ], sides[ shortestSide ], axis[ 2 ] );
|
|
VectorNormalize( axis[ 2 ], axis[ 2 ] );
|
|
|
|
VectorCopy( pTri[ origin ], bone->offset );
|
|
VectorCopy( axis[ 0 ], bone->matrix[ 0 ] );
|
|
VectorCopy( axis[ 1 ], bone->matrix[ 1 ] );
|
|
VectorCopy( axis[ 2 ], bone->matrix[ 2 ] );
|
|
}
|
|
|
|
void SkelModel::CalculateTags
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
loadframe_t *frame;
|
|
surface_t *surf;
|
|
float tri[ 3 ][ 3 ];
|
|
int i;
|
|
int j;
|
|
int k;
|
|
int l;
|
|
vec3_t baseVerts[ TIKI_MAX_VERTS ];
|
|
int index;
|
|
int bestbone;
|
|
float bestboneweight;
|
|
loadbone_t parentBone[ TIKI_MAX_FRAMES ];
|
|
UMat3 mat;
|
|
UVector3 off;
|
|
UVector3 t;
|
|
|
|
// build animation frames
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes; i++, frame++ )
|
|
{
|
|
CalcFrame( i, baseVerts );
|
|
|
|
surf = model.surfaces;
|
|
for( j = 0; j < model.numsurfaces; j++, surf++ )
|
|
{
|
|
if ( !str::icmpn( surf->name, "tag_", 4 ) )
|
|
{
|
|
for( k = 0; k < 3; k++ )
|
|
{
|
|
index = surf->verts[ surf->tris[ 0 ][ k ] ].index;
|
|
VectorCopy( baseVerts[ index ], tri[ k ] );
|
|
}
|
|
|
|
for( k = 0; k < model.numbones; k++ )
|
|
{
|
|
if ( !str::icmp( model.bones[ k ].name, surf->name ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( k >= model.numbones )
|
|
{
|
|
// no bone with same name found, so create a new one
|
|
// first, find the bone that has the greatest influence over
|
|
// the triangle. We'll make that one our parent.
|
|
bestbone = -1; // Default to a root node
|
|
bestboneweight = -1;
|
|
for( k = 0; k < 3; k++ )
|
|
{
|
|
index = surf->tris[ 0 ][ k ];
|
|
for( l = 0; l < surf->verts[ index ].numbones; l++ )
|
|
{
|
|
if ( surf->verts[ index ].blend[ l ].weight > bestboneweight )
|
|
{
|
|
bestboneweight = surf->verts[ index ].blend[ l ].weight;
|
|
bestbone = surf->verts[ index ].blend[ l ].bone;
|
|
}
|
|
}
|
|
}
|
|
|
|
// create the bone
|
|
k = AddBone( bestbone, surf->name );
|
|
}
|
|
|
|
ComputeTagFromTri( &frame->bones[ k ], tri );
|
|
|
|
if ( i == ( model.numframes - 1 ) )
|
|
{
|
|
for( l = 0; l < 3; l++ )
|
|
{
|
|
mat = *( UMat3 * )frame->bones[ k ].matrix;
|
|
off = *( UVector3 * )frame->bones[ k ].offset;
|
|
|
|
index = surf->verts[ surf->tris[ 0 ][ l ] ].index;
|
|
t = baseVerts[ index ] - off;
|
|
|
|
mat.ProjectVector( t, off );
|
|
*( UVector3 * )surf->verts[ surf->tris[ 0 ][ l ] ].blend[ 0 ].offset = off;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !originname.icmp( surf->name ) )
|
|
{
|
|
for( k = 0; k < 3; k++ )
|
|
{
|
|
index = surf->verts[ surf->tris[ 0 ][ k ] ].index;
|
|
VectorCopy( baseVerts[ index ], tri[ k ] );
|
|
}
|
|
|
|
ComputeTagFromTri( &parentBone[ i ], tri );
|
|
|
|
// store off origin info
|
|
if ( noorigin )
|
|
{
|
|
VectorClear( parentBone[ i ].offset );
|
|
}
|
|
|
|
if ( !noclampz && parentBone[ i ].offset[ 2 ] < 0 )
|
|
{
|
|
parentBone[ i ].offset[ 2 ] = 0;
|
|
}
|
|
|
|
if ( zerox )
|
|
{
|
|
parentBone[ i ].offset[ 0 ] = 0;
|
|
}
|
|
|
|
if ( zeroy )
|
|
{
|
|
parentBone[ i ].offset[ 1 ] = 0;
|
|
}
|
|
|
|
if ( zeroz )
|
|
{
|
|
parentBone[ i ].offset[ 2 ] = 0;
|
|
}
|
|
|
|
VectorCopy( parentBone[ i ].offset, frame->origin );
|
|
|
|
if ( clearxy )
|
|
{
|
|
// subtract forward and side movement from deltas
|
|
frame->origin[ 0 ] = 0;
|
|
frame->origin[ 1 ] = 0;
|
|
}
|
|
|
|
if ( clearx )
|
|
{
|
|
frame->origin[ 0 ] = 0;
|
|
}
|
|
|
|
if ( cleary )
|
|
{
|
|
frame->origin[ 1 ] = 0;
|
|
}
|
|
|
|
if ( clearz )
|
|
{
|
|
frame->origin[ 2 ] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorClear( model.totaldelta );
|
|
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes - 1; i++, frame++ )
|
|
{
|
|
for( k = 0; k < model.numbones; k++ )
|
|
{
|
|
VectorSubtract( frame->bones[ k ].offset, parentBone[ i ].offset, frame->bones[ k ].offset );
|
|
VectorSubtract( frame->bones[ k ].offset, modeloffset, frame->bones[ k ].offset );
|
|
}
|
|
|
|
// calculate deltas
|
|
for( j = 0; j < 3; j++ )
|
|
{
|
|
model.anim[ i ].delta[ j ] = model.anim[ i + 1 ].origin[ j ] - model.anim[ i ].origin[ j ];
|
|
}
|
|
|
|
VectorAdd( model.totaldelta, model.anim[ i ].delta, model.totaldelta );
|
|
}
|
|
|
|
// handle last frame
|
|
for( k = 0; k < model.numbones; k++ )
|
|
{
|
|
VectorSubtract( frame->bones[ k ].offset, parentBone[ i ].offset, frame->bones[ k ].offset );
|
|
VectorSubtract( frame->bones[ k ].offset, modeloffset, frame->bones[ k ].offset );
|
|
}
|
|
|
|
// fudge the last frame
|
|
VectorClear( model.anim[ model.numframes - 1 ].delta );
|
|
if ( model.numframes > 1 )
|
|
{
|
|
VectorScale( model.totaldelta, 1.0f / ( model.numframes - 1 ), model.anim[ model.numframes - 1 ].delta );
|
|
}
|
|
else
|
|
{
|
|
VectorClear( model.totaldelta );
|
|
}
|
|
}
|
|
|
|
void SkelModel::ScaleModel
|
|
(
|
|
float scale
|
|
)
|
|
|
|
{
|
|
loadframe_t *frame;
|
|
loadvertx_t *vert;
|
|
loadbone_t *bone;
|
|
int i;
|
|
int j;
|
|
|
|
if ( scale == 1.0f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// scale the verts
|
|
vert = model.verts;
|
|
for( i = 0; i < model.numverts; i++, vert++ )
|
|
{
|
|
for( j = 0; j < vert->numbones; j++ )
|
|
{
|
|
VectorScale( vert->blend[ j ].offset, scale, vert->blend[ j ].offset );
|
|
}
|
|
}
|
|
|
|
// scale the bone frames
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes; i++, frame++ )
|
|
{
|
|
bone = frame->bones;
|
|
for( j = 0; j < model.numbones; j++, bone++ )
|
|
{
|
|
VectorScale( bone->offset, scale, bone->offset );
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::ReverseAnimation
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
loadframe_t *framelo;
|
|
loadframe_t *framehi;
|
|
loadbone_t *bone;
|
|
int num;
|
|
int i;
|
|
|
|
// scale the bone frames
|
|
framelo = model.anim;
|
|
framehi = &model.anim[ model.numframes - 1 ];
|
|
num = model.numframes / 2;
|
|
for( i = 0; i < num; i++, framelo++, framehi-- )
|
|
{
|
|
bone = framelo->bones;
|
|
framelo->bones = framehi->bones;
|
|
framehi->bones = bone;
|
|
}
|
|
}
|
|
|
|
void SkelModel::ReduceBonesPerVertex
|
|
(
|
|
int maxbones,
|
|
float minweight
|
|
)
|
|
|
|
{
|
|
loadvertx_t *vert;
|
|
loadvertx_t v;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
float totalweight;
|
|
float singleweight;
|
|
UVector3 singleoffset;
|
|
int numbones;
|
|
int numleft;
|
|
int merge;
|
|
int numzero;
|
|
|
|
numleft = 0;
|
|
numbones = 0;
|
|
merge = 0;
|
|
|
|
vert = model.verts;
|
|
for( i = 0; i < model.numverts; i++, vert++ )
|
|
{
|
|
numzero = 0;
|
|
for( j = 0; j < vert->numbones; j++ )
|
|
{
|
|
if ( vert->blend[ j ].weight <= 0 )
|
|
{
|
|
numzero++;
|
|
}
|
|
}
|
|
|
|
if ( numzero == vert->numbones )
|
|
{
|
|
printf( "** No valid weights on vertex %d. Setting to average weight.\n", i );
|
|
for( j = 0; j < vert->numbones; j++ )
|
|
{
|
|
vert->blend[ j ].weight = 1.0f / ( float )vert->numbones;
|
|
}
|
|
}
|
|
|
|
numbones += vert->numbones;
|
|
v = *vert;
|
|
vert->numbones = 0;
|
|
totalweight = 0;
|
|
for( j = 0; j < v.numbones; j++ )
|
|
{
|
|
if ( v.blend[ j ].bone == UNUSED_BONE )
|
|
{
|
|
// skip blends with unused bones since we zeroed it out ourselves
|
|
continue;
|
|
}
|
|
|
|
// For some reason, Max may report the same bone more than once, so we
|
|
// add up all the blends that have the same bone and combine them into
|
|
// one offset and weight.
|
|
singleweight = v.blend[ j ].weight;
|
|
singleoffset = UVector3( v.blend[ j ].offset ) * v.blend[ j ].weight;
|
|
for( k = j + 1; k < v.numbones; k++ )
|
|
{
|
|
if ( v.blend[ j ].bone == v.blend[ k ].bone )
|
|
{
|
|
singleweight += v.blend[ k ].weight;
|
|
singleoffset += UVector3( v.blend[ k ].offset ) * v.blend[ k ].weight;
|
|
|
|
// zero it out so we never use it again
|
|
v.blend[ k ].weight = 0;
|
|
v.blend[ k ].bone = UNUSED_BONE;
|
|
VectorClear( v.blend[ k ].offset );
|
|
|
|
merge++;
|
|
}
|
|
}
|
|
|
|
// don't allow zero weight bones, even if the user allows it.
|
|
if ( ( singleweight != 0 ) && ( singleweight > minweight ) )
|
|
{
|
|
singleoffset *= 1 / singleweight;
|
|
|
|
vert->blend[ vert->numbones ].bone = v.blend[ j ].bone;
|
|
vert->blend[ vert->numbones ].weight = singleweight;
|
|
VectorCopy( singleoffset, vert->blend[ vert->numbones ].offset );
|
|
|
|
totalweight += singleweight;
|
|
numleft++;
|
|
|
|
vert->numbones++;
|
|
if ( vert->numbones >= maxbones )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//printf( "degenerate weight %.2f for bone %s on vertex %d\n", singleweight, model.bones[ v.blend[ j ].bone ].name, i );
|
|
}
|
|
}
|
|
|
|
if ( totalweight <= 0 )
|
|
{
|
|
Error( "Invalid totalweight %.2f on vertex %d when reducing number of blended bones, on model %s\n", totalweight, i, model.name );
|
|
}
|
|
|
|
if ( vert->numbones < 1 )
|
|
{
|
|
Error( "No bones on vertex %d when reducing number of blended bones, on model %s.\n", i, model.name );
|
|
}
|
|
|
|
for( j = 0; j < vert->numbones; j++ )
|
|
{
|
|
vert->blend[ j ].weight /= totalweight;
|
|
}
|
|
}
|
|
|
|
if ( verbose )
|
|
{
|
|
printf( "removed %d weights, %d remain. merged %d weights.\n", numbones - numleft, numbones, merge );
|
|
}
|
|
}
|
|
|
|
int SkelModel::AddBone
|
|
(
|
|
int parent,
|
|
const char *name
|
|
)
|
|
|
|
{
|
|
skelBoneName_t *oldbonenames;
|
|
skelBoneName_t *bonename;
|
|
loadbone_t *oldbones;
|
|
loadframe_t *frame;
|
|
loadbone_t *bone;
|
|
int oldbonenum;
|
|
int i;
|
|
int j;
|
|
|
|
// reallocate the bones
|
|
oldbonenum = model.numbones;
|
|
oldbonenames = model.bones;
|
|
|
|
model.numbones++;
|
|
model.bones = new skelBoneName_t[ model.numbones ];
|
|
|
|
bonename = model.bones;
|
|
for( i = 0; i < oldbonenum; i++, bonename++ )
|
|
{
|
|
bonename->parent = oldbonenames[ i ].parent;
|
|
bonename->flags = oldbonenames[ i ].flags;
|
|
strcpy( bonename->name, oldbonenames[ i ].name );
|
|
}
|
|
|
|
model.bones[ oldbonenum ].parent = parent;
|
|
model.bones[ oldbonenum ].flags = 0;
|
|
strcpy( model.bones[ oldbonenum ].name, name );
|
|
|
|
delete[] oldbonenames;
|
|
|
|
// reallocate the bone frames
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes; i++, frame++ )
|
|
{
|
|
oldbones = frame->bones;
|
|
frame->bones = new loadbone_t[ model.numbones ];
|
|
bone = frame->bones;
|
|
for( j = 0; j < oldbonenum; j++, bone++ )
|
|
{
|
|
VectorCopy( oldbones[ j ].matrix[ 0 ], bone->matrix[ 0 ] );
|
|
VectorCopy( oldbones[ j ].matrix[ 1 ], bone->matrix[ 1 ] );
|
|
VectorCopy( oldbones[ j ].matrix[ 2 ], bone->matrix[ 2 ] );
|
|
VectorCopy( oldbones[ j ].offset, bone->offset );
|
|
}
|
|
|
|
VectorSet( frame->bones[ oldbonenum ].matrix[ 0 ], 1, 0, 0 );
|
|
VectorSet( frame->bones[ oldbonenum ].matrix[ 1 ], 0, 1, 0 );
|
|
VectorSet( frame->bones[ oldbonenum ].matrix[ 2 ], 0, 0, 1 );
|
|
VectorSet( frame->bones[ oldbonenum ].offset, 0, 0, 0 );
|
|
|
|
delete[] oldbones;
|
|
}
|
|
|
|
if ( verbose )
|
|
{
|
|
printf( "Created new bone %s. Total bones %d.\n", name, model.numbones );
|
|
}
|
|
|
|
return oldbonenum;
|
|
}
|
|
|
|
void SkelModel::RemoveUnusedBones
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
surface_t *surf;
|
|
loadvertx_t *vert;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
int parent;
|
|
int parent2;
|
|
bool used[ MAX_BONES ];
|
|
int newboneindex[ MAX_BONES ];
|
|
int oldboneindex[ MAX_BONES ];
|
|
int newparent[ MAX_BONES ];
|
|
int numused;
|
|
skelBoneName_t *oldbonenames;
|
|
skelBoneName_t *bonename;
|
|
loadbone_t *oldbones;
|
|
loadbone_t *bone;
|
|
loadframe_t *frame;
|
|
|
|
memset( used, 0, sizeof( used ) );
|
|
memset( oldboneindex, 0, sizeof( oldboneindex ) );
|
|
memset( newboneindex, 0, sizeof( newboneindex ) );
|
|
memset( newparent, 0, sizeof( newparent ) );
|
|
|
|
// find all the bones that are used
|
|
surf = model.surfaces;
|
|
for( i = 0; i < model.numsurfaces; i++, surf++ )
|
|
{
|
|
// if ( !originname.icmp( surf->name ) )
|
|
// {
|
|
// // we don't care about the origin once we have grabbed it
|
|
// continue;
|
|
// }
|
|
vert = surf->verts;
|
|
for( j = 0; j < surf->numverts; j++, vert++ )
|
|
{
|
|
for( k = 0; k < vert->numbones; k++ )
|
|
{
|
|
used[ vert->blend[ k ].bone ] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !ignoreflags )
|
|
{
|
|
// go through the bones and mark any bones that have to be kept
|
|
// so that we don't mistakenly remap any children. Normally this
|
|
// wouldn't be an issue since we go through the bones parent first,
|
|
// but since we have to keep the parent's flags the same, we have
|
|
// to scan back up the hierarchy, which means we could be remapping
|
|
// a bone index that we shouldn't by mistake.
|
|
for( i = 0; i < model.numbones; i++ )
|
|
{
|
|
if ( !originname.icmp( model.bones[ i ].name ) || !str::icmpn( model.bones[ i ].name, "tag_", 4 ) )
|
|
// if ( !str::icmpn( model.bones[ i ].name, "tag_", 4 ) )
|
|
{
|
|
used[ i ] = true;
|
|
}
|
|
|
|
if ( used[ i ] )
|
|
{
|
|
parent = model.bones[ i ].parent;
|
|
while( ( parent != -1 ) && !used[ parent ] )
|
|
{
|
|
parent2 = model.bones[ parent ].parent;
|
|
|
|
// when we're collapsing bones, if we remove a parent bone, we can
|
|
// only connect the child to a bone with the same flags as the parent,
|
|
// so if there isn't one available, mark the parent as used.
|
|
if ( ( parent2 == -1 ) || ( model.bones[ parent2 ].flags != model.bones[ parent ].flags ) )
|
|
{
|
|
used[ parent ] = true;
|
|
break;
|
|
}
|
|
|
|
parent = parent2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// create the indices to be remapped
|
|
numused = 0;
|
|
for( i = 0; i < model.numbones; i++ )
|
|
{
|
|
if ( used[ i ] )
|
|
{
|
|
parent = model.bones[ i ].parent;
|
|
while( ( parent != -1 ) && !used[ parent ] )
|
|
{
|
|
parent = model.bones[ parent ].parent;
|
|
}
|
|
|
|
if ( parent != -1 )
|
|
{
|
|
newparent[ numused ] = newboneindex[ parent ];
|
|
}
|
|
else
|
|
{
|
|
newparent[ numused ] = -1;
|
|
}
|
|
|
|
oldboneindex[ numused ] = i;
|
|
newboneindex[ i ] = numused++;
|
|
}
|
|
else
|
|
{
|
|
// if verbose, show which bones are being removed.
|
|
if ( verbose )
|
|
{
|
|
printf( "Bone #%d (%s) unused.\n", i, model.bones[i].name );
|
|
}
|
|
}
|
|
}
|
|
|
|
// remap all the bone numbers
|
|
vert = model.verts;
|
|
for( j = 0; j < model.numverts; j++, vert++ )
|
|
{
|
|
for( k = 0; k < vert->numbones; k++ )
|
|
{
|
|
vert->blend[ k ].bone = newboneindex[ vert->blend[ k ].bone ];
|
|
}
|
|
}
|
|
|
|
// remap all the bone numbers in the surfaces
|
|
surf = model.surfaces;
|
|
for( i = 0; i < model.numsurfaces; i++, surf++ )
|
|
{
|
|
vert = surf->verts;
|
|
for( j = 0; j < surf->numverts; j++, vert++ )
|
|
{
|
|
for( k = 0; k < vert->numbones; k++ )
|
|
{
|
|
vert->blend[ k ].bone = newboneindex[ vert->blend[ k ].bone ];
|
|
}
|
|
}
|
|
}
|
|
|
|
// reallocate the bones
|
|
oldbonenames = model.bones;
|
|
model.bones = new skelBoneName_t[ numused ];
|
|
bonename = model.bones;
|
|
for( i = 0; i < numused; i++, bonename++ )
|
|
{
|
|
bonename->parent = newparent[ i ];
|
|
bonename->flags = oldbonenames[ oldboneindex[ i ] ].flags;
|
|
strcpy( bonename->name, oldbonenames[ oldboneindex[ i ] ].name );
|
|
}
|
|
|
|
delete[] oldbonenames;
|
|
|
|
// reallocate the bone frames
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes; i++, frame++ )
|
|
{
|
|
oldbones = frame->bones;
|
|
frame->bones = new loadbone_t[ numused ];
|
|
bone = frame->bones;
|
|
for( j = 0; j < numused; j++, bone++ )
|
|
{
|
|
VectorCopy( oldbones[ oldboneindex[ j ] ].matrix[ 0 ], bone->matrix[ 0 ] );
|
|
VectorCopy( oldbones[ oldboneindex[ j ] ].matrix[ 1 ], bone->matrix[ 1 ] );
|
|
VectorCopy( oldbones[ oldboneindex[ j ] ].matrix[ 2 ], bone->matrix[ 2 ] );
|
|
VectorCopy( oldbones[ oldboneindex[ j ] ].offset, bone->offset );
|
|
}
|
|
|
|
delete[] oldbones;
|
|
}
|
|
|
|
if ( verbose )
|
|
{
|
|
printf( "removed %d bones, %d remain\n", model.numbones - numused, numused );
|
|
}
|
|
|
|
model.numbones = numused;
|
|
#if 0
|
|
for( i = 0; i < model.numbones; i++ )
|
|
{
|
|
char temp[ 1024 ];
|
|
sprintf( temp, "%i %s\n", i, model.bones[ i ].name );
|
|
OutputDebugString( temp );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SkelModel::ConvertToQuake3Worldspace
|
|
(
|
|
void
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
loadframe_t *frame;
|
|
loadbone_t *bone;
|
|
UMat3 mat;
|
|
UVector3 offset;
|
|
|
|
frame = model.anim;
|
|
for( i = 0; i < model.numframes; i++, frame++ )
|
|
{
|
|
bone = frame->bones;
|
|
for( j = 0; j < model.numbones; j++, bone++ )
|
|
{
|
|
mat = *( UMat3 * )bone->matrix;
|
|
|
|
// convert the orientation to Quake 3 worldspace
|
|
( *( UMat3 * )bone->matrix ).vecs[ 0 ].x = -mat.vecs[ 0 ][ 1 ];
|
|
( *( UMat3 * )bone->matrix ).vecs[ 0 ].y = mat.vecs[ 0 ][ 0 ];
|
|
( *( UMat3 * )bone->matrix ).vecs[ 0 ].z = mat.vecs[ 0 ][ 2 ];
|
|
|
|
( *( UMat3 * )bone->matrix ).vecs[ 1 ].x = -mat.vecs[ 1 ][ 1 ];
|
|
( *( UMat3 * )bone->matrix ).vecs[ 1 ].y = mat.vecs[ 1 ][ 0 ];
|
|
( *( UMat3 * )bone->matrix ).vecs[ 1 ].z = mat.vecs[ 1 ][ 2 ];
|
|
|
|
( *( UMat3 * )bone->matrix ).vecs[ 2 ].x = -mat.vecs[ 2 ][ 1 ];
|
|
( *( UMat3 * )bone->matrix ).vecs[ 2 ].y = mat.vecs[ 2 ][ 0 ];
|
|
( *( UMat3 * )bone->matrix ).vecs[ 2 ].z = mat.vecs[ 2 ][ 2 ];
|
|
|
|
// convert the offset to Quake 3 worldspace
|
|
offset = bone->offset;
|
|
( *( UVector3 * )bone->offset ).x = -offset.y;
|
|
( *( UVector3 * )bone->offset ).y = offset.x;
|
|
( *( UVector3 * )bone->offset ).z = offset.z;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SkelModel::SaveBaseFrame
|
|
(
|
|
const char *name
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
int k;
|
|
skelHeader_t modeltemp;
|
|
skelSurface_t surftemp[ TIKI_MAX_SURFACES * 2 ];
|
|
skelTriangle_t tritemp[ TIKI_MAX_TRIANGLES ];
|
|
int collapseMap[ TIKI_MAX_VERTS ];
|
|
skelVertex_t tempvert;
|
|
skelWeight_t tempweight;
|
|
long surfaceSum;
|
|
long vertSum;
|
|
int numsurfs;
|
|
FILE *modelouthandle;
|
|
surface_t *insurf;
|
|
skelSurface_t *outsurf;
|
|
loadvertx_t *vert;
|
|
skelBoneName_t bonename;
|
|
|
|
if ( !model.numframes )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( model.numsurfaces > TIKI_MAX_SURFACES * 2 )
|
|
{
|
|
Error( "too many surfaces\n" );
|
|
}
|
|
|
|
//
|
|
// write the model output file
|
|
//
|
|
printf( "Saving baseframe to %s\n", name );
|
|
|
|
strncpy( model.name, name, sizeof( model.name ) - 1 );
|
|
|
|
surfaceSum = 0;
|
|
numsurfs = 0;
|
|
|
|
memset( surftemp, 0, sizeof( surftemp ) );
|
|
|
|
// compute offsets for all surfaces, sum their total size
|
|
insurf = model.surfaces;
|
|
outsurf = surftemp;
|
|
for( i = 0; i < model.numsurfaces; i++, insurf++, outsurf++ )
|
|
{
|
|
if ( ( insurf->numtris == 0 ) || !str::icmpn( insurf->name , "tag_", 4 ) || !originname.icmp( insurf->name ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( numsurfs >= TIKI_MAX_SURFACES )
|
|
{
|
|
Error( "too many surfaces\n" );
|
|
}
|
|
|
|
if ( insurf->numtris > MAX_SURFACE_TRIS )
|
|
{
|
|
Error( "too many faces\n" );
|
|
}
|
|
|
|
if ( insurf->numverts > TIKI_MAX_VERTS )
|
|
{
|
|
Error( "Too many vertices %d out of %d on surface %s in model %s\n", insurf->numverts, TIKI_MAX_VERTS, insurf->name, model.name );
|
|
}
|
|
|
|
// sum the total size of the vertices
|
|
vertSum = 0;
|
|
for( j = 0; j < insurf->numverts; j++ )
|
|
{
|
|
vertSum += sizeof( skelVertex_t ) + sizeof( skelWeight_t ) * ( insurf->verts[ j ].numbones - 1 );
|
|
}
|
|
|
|
strcpy( outsurf->name, insurf->name );
|
|
|
|
outsurf->ident = LittleLong( TIKI_SKEL_IDENT );
|
|
outsurf->numTriangles = LittleLong( insurf->numtris );
|
|
outsurf->numVerts = LittleLong( insurf->numverts );
|
|
outsurf->minLod = LittleLong( insurf->minLod );
|
|
outsurf->ofsTriangles = sizeof( skelSurface_t );
|
|
outsurf->ofsVerts = outsurf->ofsTriangles + insurf->numtris * sizeof( skelTriangle_t );
|
|
outsurf->ofsCollapse = outsurf->ofsVerts + vertSum;
|
|
outsurf->ofsEnd = outsurf->ofsCollapse + insurf->numverts * sizeof( insurf->collapseMap[ 0 ] );
|
|
|
|
surfaceSum += outsurf->ofsEnd;
|
|
outsurf->ofsVerts = LittleLong( outsurf->ofsVerts );
|
|
outsurf->ofsTriangles = LittleLong( outsurf->ofsTriangles );
|
|
outsurf->ofsCollapse = LittleLong( outsurf->ofsCollapse );
|
|
outsurf->ofsEnd = LittleLong( outsurf->ofsEnd );
|
|
|
|
numsurfs++;
|
|
}
|
|
|
|
CreatePath( name );
|
|
modelouthandle = SafeOpenWrite( name );
|
|
|
|
// create the header
|
|
strcpy( modeltemp.name, model.name );
|
|
|
|
modeltemp.ident = LittleLong( TIKI_SKEL_IDENT );
|
|
modeltemp.version = LittleLong( TIKI_SKEL_VERSION );
|
|
modeltemp.numsurfaces = LittleLong( numsurfs );
|
|
modeltemp.numbones = LittleLong( model.numbones );
|
|
|
|
modeltemp.ofsBones = sizeof( skelHeader_t );
|
|
modeltemp.ofsSurfaces = modeltemp.ofsBones + model.numbones * sizeof( skelBoneName_t );
|
|
modeltemp.ofsEnd = modeltemp.ofsSurfaces + surfaceSum;
|
|
|
|
modeltemp.ofsBones = LittleLong( modeltemp.ofsBones );
|
|
modeltemp.ofsSurfaces = LittleLong( modeltemp.ofsSurfaces );
|
|
modeltemp.ofsEnd = LittleLong( modeltemp.ofsEnd );
|
|
|
|
//
|
|
// write out the model header
|
|
//
|
|
SafeWrite( modelouthandle, &modeltemp, sizeof( modeltemp ) );
|
|
|
|
//
|
|
// write out the bone names
|
|
//
|
|
for( i = 0; i < model.numbones; i++ )
|
|
{
|
|
memset( &bonename, 0, sizeof( bonename ) );
|
|
|
|
bonename.flags = LittleLong( model.bones[ i ].flags );
|
|
bonename.parent = LittleLong( model.bones[ i ].parent );
|
|
|
|
strcpy( bonename.name, model.bones[ i ].name );
|
|
|
|
SafeWrite( modelouthandle, &bonename, sizeof( bonename ) );
|
|
}
|
|
|
|
//
|
|
// write out the surfaces
|
|
//
|
|
insurf = model.surfaces;
|
|
outsurf = surftemp;
|
|
for( i = 0; i < model.numsurfaces; i++, insurf++, outsurf++ )
|
|
{
|
|
if ( ( insurf->numtris == 0 ) || !str::icmpn( insurf->name , "tag_", 4 ) || !originname.icmp( insurf->name ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
SafeWrite( modelouthandle, outsurf, sizeof( skelSurface_t ) );
|
|
|
|
for( j = 0; j < insurf->numtris; j++ )
|
|
{
|
|
tritemp[ j ].indexes[ 0 ] = LittleLong( insurf->tris[ j ][ 0 ] );
|
|
tritemp[ j ].indexes[ 1 ] = LittleLong( insurf->tris[ j ][ 1 ] );
|
|
tritemp[ j ].indexes[ 2 ] = LittleLong( insurf->tris[ j ][ 2 ] );
|
|
}
|
|
|
|
SafeWrite( modelouthandle, tritemp, insurf->numtris * sizeof( skelTriangle_t ) );
|
|
|
|
//
|
|
// write out the vertices
|
|
//
|
|
vert = insurf->verts;
|
|
for( j = 0; j < insurf->numverts; j++, vert++ )
|
|
{
|
|
tempvert.normal[ 0 ] = LittleFloat( vert->normal[ 0 ] );
|
|
tempvert.normal[ 1 ] = LittleFloat( vert->normal[ 1 ] );
|
|
tempvert.normal[ 2 ] = LittleFloat( vert->normal[ 2 ] );
|
|
|
|
tempvert.texCoords[ 0 ] = LittleFloat( vert->texCoords[ 0 ] );
|
|
tempvert.texCoords[ 1 ] = LittleFloat( vert->texCoords[ 1 ] );
|
|
|
|
tempvert.numWeights = LittleLong( vert->numbones );
|
|
|
|
SafeWrite( modelouthandle, &tempvert, sizeof( tempvert ) - sizeof( tempvert.weights ) );
|
|
|
|
if ( !vert->numbones )
|
|
{
|
|
Error( "Vertex with no bones found in surface '%s'(%d).\n", insurf->name, insurf->id );
|
|
}
|
|
|
|
for( k = 0; k < vert->numbones; k++ )
|
|
{
|
|
tempweight.boneIndex = LittleLong( vert->blend[ k ].bone );
|
|
tempweight.boneWeight = LittleFloat( vert->blend[ k ].weight );
|
|
tempweight.offset[ 0 ] = LittleFloat( vert->blend[ k ].offset[ 0 ] );
|
|
tempweight.offset[ 1 ] = LittleFloat( vert->blend[ k ].offset[ 1 ] );
|
|
tempweight.offset[ 2 ] = LittleFloat( vert->blend[ k ].offset[ 2 ] );
|
|
|
|
SafeWrite( modelouthandle, &tempweight, sizeof( tempweight ) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// write out the collapse map
|
|
//
|
|
for( j = 0; j < insurf->numverts; j++ )
|
|
{
|
|
collapseMap[ j ] = LittleLong( insurf->collapseMap[ j ] );
|
|
}
|
|
|
|
SafeWrite( modelouthandle, collapseMap, insurf->numverts * sizeof( collapseMap[ 0 ] ) );
|
|
}
|
|
|
|
if ( verbose )
|
|
{
|
|
printf( "%4d surfaces\n", model.numsurfaces );
|
|
printf( "%4d bones\n", model.numbones );
|
|
printf( "%4d verts\n", model.numverts );
|
|
printf( "file size: %d\n", ( int )ftell( modelouthandle ) );
|
|
printf( "---------------------\n" );
|
|
}
|
|
|
|
fclose( modelouthandle );
|
|
}
|
|
|
|
void SkelModel::SaveAnimation
|
|
(
|
|
const char *name
|
|
)
|
|
|
|
{
|
|
int i, j, k;
|
|
FILE *modelouthandle;
|
|
loadframe_t *frame;
|
|
loadframe_t outframe;
|
|
loadbone_t *bone;
|
|
skelBone_t outbone;
|
|
skelAnimHeader_t animheader;
|
|
UMat3 mat;
|
|
UMat3 pmat;
|
|
UMat3 outm;
|
|
UVector3 offset;
|
|
UVector3 poffset;
|
|
UVector3 outv;
|
|
int numframes;
|
|
|
|
if ( !model.numframes )
|
|
{
|
|
return;
|
|
}
|
|
|
|
numframes = model.numframes;
|
|
if ( loop && ( numframes > 1 ) )
|
|
{
|
|
numframes--;
|
|
}
|
|
|
|
//
|
|
// write the model output file
|
|
//
|
|
printf( "Saving animation to %s\n", name );
|
|
|
|
strncpy( model.name, name, sizeof( model.name ) - 1 );
|
|
|
|
animheader.ident = TIKI_SKEL_ANIM_IDENT;
|
|
animheader.version = TIKI_SKEL_VERSION;
|
|
|
|
memset( animheader.name, 0, sizeof( animheader.name ) );
|
|
strncpy( animheader.name, name, sizeof( animheader.name ) - 1 );
|
|
|
|
// calculate total time
|
|
animheader.totaltime = ( float )numframes / model.framerate;
|
|
animheader.frametime = 1.0f / model.framerate;
|
|
animheader.numFrames = numframes;
|
|
animheader.numbones = model.numbones;
|
|
|
|
// animheader.framesize = ( sizeof( model.anim[ 0 ] ) - sizeof( model.anim[ 0 ].bones ) + model.numbones * sizeof( loadbone_t ) );
|
|
animheader.ofsFrames = sizeof( animheader );
|
|
// animheader.ofsEnd = animheader.ofsFrames + numframes * animheader.framesize;
|
|
|
|
//
|
|
// figure out if any of our bone offsets change during this animation
|
|
//
|
|
|
|
CreatePath( name );
|
|
modelouthandle = SafeOpenWrite( name );
|
|
|
|
//
|
|
// write out the model header
|
|
//
|
|
animheader.ident = LittleLong( animheader.ident );
|
|
animheader.version = LittleLong( animheader.version );
|
|
animheader.numFrames = LittleLong( animheader.numFrames );
|
|
animheader.numbones = LittleLong( model.numbones );
|
|
animheader.totaltime = LittleFloat( animheader.totaltime );
|
|
animheader.frametime = LittleFloat( animheader.frametime );
|
|
// animheader.framesize = LittleLong( animheader.framesize );
|
|
animheader.ofsFrames = LittleLong( animheader.ofsFrames );
|
|
// animheader.ofsEnd = LittleLong( animheader.ofsEnd );
|
|
|
|
// copy the totaldelta
|
|
animheader.totaldelta[ 0 ] = LittleFloat( model.totaldelta[ 0 ] );
|
|
animheader.totaldelta[ 1 ] = LittleFloat( model.totaldelta[ 1 ] );
|
|
animheader.totaldelta[ 2 ] = LittleFloat( model.totaldelta[ 2 ] );
|
|
|
|
SafeWrite( modelouthandle, &animheader, sizeof( animheader ) );
|
|
|
|
//
|
|
// write out the frames
|
|
//
|
|
frame = model.anim;
|
|
for( i = 0; i < numframes; i++, frame++ )
|
|
{
|
|
// swap
|
|
for( j = 0; j < 2; j++ )
|
|
{
|
|
outframe.bounds[ j ][ 0 ] = LittleFloat( frame->bounds[ j ][ 0 ] );
|
|
outframe.bounds[ j ][ 1 ] = LittleFloat( frame->bounds[ j ][ 1 ] );
|
|
outframe.bounds[ j ][ 2 ] = LittleFloat( frame->bounds[ j ][ 2 ] );
|
|
}
|
|
|
|
outframe.radius = LittleFloat( frame->radius );
|
|
|
|
outframe.delta[ 0 ] = LittleFloat( frame->delta[ 0 ] );
|
|
outframe.delta[ 1 ] = LittleFloat( frame->delta[ 1 ] );
|
|
outframe.delta[ 2 ] = LittleFloat( frame->delta[ 2 ] );
|
|
|
|
SafeWrite( modelouthandle, outframe.bounds, sizeof( outframe.bounds ) );
|
|
SafeWrite( modelouthandle, &outframe.radius, sizeof( outframe.radius ) );
|
|
SafeWrite( modelouthandle, outframe.delta, sizeof( outframe.delta ) );
|
|
|
|
bone = frame->bones;
|
|
for( j = 0; j < model.numbones; j++, bone++ )
|
|
{
|
|
UQuat outQuat;
|
|
UVector3 outVect3;
|
|
|
|
if ( model.bones[ j ].parent == -1 )
|
|
{
|
|
outm = *( UMat3 * )bone->matrix;
|
|
outv = bone->offset;
|
|
}
|
|
else
|
|
{
|
|
mat = *( UMat3 * )bone->matrix;
|
|
offset = bone->offset;
|
|
|
|
pmat = *( UMat3 * )frame->bones[ model.bones[ j ].parent ].matrix;
|
|
poffset = frame->bones[ model.bones[ j ].parent ].offset;
|
|
|
|
offset -= poffset;
|
|
pmat.ProjectVector( offset, outv );
|
|
|
|
pmat.Transpose();
|
|
outm = mat * pmat;
|
|
}
|
|
|
|
outQuat = outm;
|
|
outVect3 = outv;
|
|
|
|
for( k = 0; k < 4; k++ )
|
|
{
|
|
short tempShort;
|
|
|
|
assert( ( outQuat[ k ] <= 1.0f ) && ( outQuat[ k ] >= -1.0f ) );
|
|
tempShort = outQuat[ k ] * TIKI_BONE_QUAT_MULTIPLIER;
|
|
outbone.shortQuat[ k ] = LittleShort( tempShort );
|
|
}
|
|
|
|
for( k = 0; k < 3; k++ )
|
|
{
|
|
short tempShort;
|
|
|
|
if ( fabs( outVect3[ k ] ) > TIKI_BONE_OFFSET_MAX_SIGNED_VALUE )
|
|
{
|
|
Error( "Bone offset is too large for data format\n" );
|
|
}
|
|
tempShort = outVect3[ k ] * TIKI_BONE_OFFSET_MULTIPLIER;
|
|
outbone.shortOffset[ k ] = LittleShort( tempShort );
|
|
}
|
|
|
|
SafeWrite( modelouthandle, &outbone, sizeof( outbone ) );
|
|
}
|
|
}
|
|
|
|
if ( verbose )
|
|
{
|
|
printf( "%4d frames\n", numframes );
|
|
printf( "%4d bones\n", model.numbones );
|
|
printf( "file size: %d\n", ( int )ftell( modelouthandle ) );
|
|
printf( "---------------------\n" );
|
|
}
|
|
|
|
fclose( modelouthandle );
|
|
}
|
|
|
|
void SkelModel::SaveMD4
|
|
(
|
|
const char *name
|
|
)
|
|
|
|
{
|
|
int i;
|
|
int j;
|
|
int k;
|
|
int l;
|
|
md4Header_t modeltemp;
|
|
md4LOD_t lodtemp;
|
|
md4Surface_t surftemp[ MD3_MAX_SURFACES ];
|
|
md4Triangle_t tritemp[ MD3_MAX_TRIANGLES ];
|
|
md4Vertex_t tempvert;
|
|
md4Weight_t tempweight;
|
|
int bonerefs[ MD3_MAX_SURFACES ][ MD4_MAX_BONES ];
|
|
int numbonerefs[ MD3_MAX_SURFACES ];
|
|
long surfaceSum;
|
|
long vertSum;
|
|
int numsurfs;
|
|
FILE *modelouthandle;
|
|
surface_t *insurf;
|
|
md4Surface_t *outsurf;
|
|
loadvertx_t *vert;
|
|
loadframe_t *frame;
|
|
loadbone_t *bone;
|
|
md4Bone_t outbone;
|
|
md4Frame_t outframe;
|
|
int numframes;
|
|
|
|
if ( !model.numframes )
|
|
{
|
|
return;
|
|
}
|
|
|
|
numframes = model.numframes;
|
|
if ( loop && ( numframes > 1 ) )
|
|
{
|
|
numframes--;
|
|
}
|
|
|
|
//
|
|
// write the model output file
|
|
//
|
|
printf( "Saving MD4 to %s\n", name );
|
|
|
|
strncpy( model.name, name, sizeof( model.name ) - 1 );
|
|
|
|
surfaceSum = 0;
|
|
numsurfs = 0;
|
|
|
|
memset( surftemp, 0, sizeof( surftemp ) );
|
|
|
|
// compute offsets for all surfaces, sum their total size
|
|
insurf = model.surfaces;
|
|
outsurf = surftemp;
|
|
for( i = 0; i < model.numsurfaces; i++, insurf++, outsurf++ )
|
|
{
|
|
if ( insurf->numtris == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( numsurfs >= MD3_MAX_SURFACES )
|
|
{
|
|
Error( "too many surfaces\n" );
|
|
}
|
|
|
|
if ( insurf->numtris > MD3_MAX_TRIANGLES )
|
|
{
|
|
Error( "too many faces\n" );
|
|
}
|
|
|
|
numbonerefs[ i ] = 0;
|
|
|
|
// sum the total size of the vertices
|
|
vertSum = 0;
|
|
vert = insurf->verts;
|
|
for( j = 0; j < insurf->numverts; j++, vert++ )
|
|
{
|
|
vertSum += sizeof( md4Vertex_t ) + sizeof( md4Weight_t ) * ( vert->numbones - 1 );
|
|
|
|
for( k = 0; k < vert->numbones; k++ )
|
|
{
|
|
for( l = 0; l < numbonerefs[ i ]; l++ )
|
|
{
|
|
if ( bonerefs[ i ][ l ] == vert->blend[ k ].bone )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( l == numbonerefs[ i ] )
|
|
{
|
|
bonerefs[ i ][ numbonerefs[ i ]++ ] = vert->blend[ k ].bone;
|
|
}
|
|
}
|
|
}
|
|
|
|
strcpy( outsurf->name, insurf->name );
|
|
strcpy( outsurf->shader, "" );
|
|
|
|
outsurf->ident = LittleLong( MD4_IDENT );
|
|
outsurf->shaderIndex = 0;
|
|
outsurf->ofsHeader = -1; // this is calculated from the file position when we save it out
|
|
|
|
outsurf->numVerts = LittleLong( insurf->numverts );
|
|
outsurf->ofsVerts = sizeof( md4Surface_t );
|
|
|
|
outsurf->numTriangles = LittleLong( insurf->numtris );
|
|
outsurf->ofsTriangles = outsurf->ofsVerts + vertSum;
|
|
|
|
outsurf->numBoneReferences = LittleLong( numbonerefs[ i ] );
|
|
outsurf->ofsBoneReferences = outsurf->ofsTriangles + insurf->numtris * sizeof( md4Triangle_t );
|
|
|
|
outsurf->ofsEnd = outsurf->ofsBoneReferences + numbonerefs[ i ] * sizeof( bonerefs[ i ][ 0 ] );
|
|
|
|
surfaceSum += outsurf->ofsEnd;
|
|
outsurf->ofsHeader = LittleLong( outsurf->ofsHeader );
|
|
outsurf->ofsVerts = LittleLong( outsurf->ofsVerts );
|
|
outsurf->ofsTriangles = LittleLong( outsurf->ofsTriangles );
|
|
outsurf->ofsBoneReferences = LittleLong( outsurf->ofsBoneReferences );
|
|
outsurf->ofsEnd = LittleLong( outsurf->ofsEnd );
|
|
|
|
numsurfs++;
|
|
}
|
|
|
|
CreatePath( name );
|
|
modelouthandle = SafeOpenWrite( name );
|
|
|
|
// create the header
|
|
strcpy( modeltemp.name, model.name );
|
|
|
|
modeltemp.ident = LittleLong( MD4_IDENT );
|
|
modeltemp.version = LittleLong( MD4_VERSION );
|
|
|
|
modeltemp.numFrames = LittleLong( numframes );
|
|
modeltemp.numBones = LittleLong( model.numbones );
|
|
modeltemp.numLODs = LittleLong( 1 ); //FIXME -- add support for more than 1 lod
|
|
|
|
modeltemp.ofsFrames = sizeof( md4Header_t );
|
|
modeltemp.ofsLODs = modeltemp.ofsFrames + ( sizeof( md4Frame_t ) - sizeof( md4Bone_t ) + model.numbones * sizeof( md4Bone_t ) ) * numframes;
|
|
modeltemp.ofsEnd = modeltemp.ofsLODs + sizeof( md4LOD_t ) + surfaceSum; //FIXME -- add support for more than 1 lod
|
|
|
|
modeltemp.ofsFrames = LittleLong( modeltemp.ofsFrames );
|
|
modeltemp.ofsLODs = LittleLong( modeltemp.ofsLODs );
|
|
modeltemp.ofsEnd = LittleLong( modeltemp.ofsEnd );
|
|
|
|
//
|
|
// write out the model header
|
|
//
|
|
SafeWrite( modelouthandle, &modeltemp, sizeof( modeltemp ) );
|
|
|
|
//
|
|
// write out the frames
|
|
//
|
|
frame = model.anim;
|
|
for( i = 0; i < numframes; i++, frame++ )
|
|
{
|
|
strcpy( outframe.name, model.name );
|
|
|
|
// swap
|
|
for( j = 0; j < 2; j++ )
|
|
{
|
|
outframe.bounds[ j ][ 0 ] = LittleFloat( frame->bounds[ j ][ 0 ] );
|
|
outframe.bounds[ j ][ 1 ] = LittleFloat( frame->bounds[ j ][ 1 ] );
|
|
outframe.bounds[ j ][ 2 ] = LittleFloat( frame->bounds[ j ][ 2 ] );
|
|
}
|
|
|
|
outframe.localOrigin[ 0 ] = LittleFloat( ( frame->bounds[ 0 ][ 0 ] + frame->bounds[ 1 ][ 0 ] ) * 0.5 );
|
|
outframe.localOrigin[ 1 ] = LittleFloat( ( frame->bounds[ 0 ][ 1 ] + frame->bounds[ 1 ][ 1 ] ) * 0.5 );
|
|
outframe.localOrigin[ 2 ] = LittleFloat( ( frame->bounds[ 0 ][ 2 ] + frame->bounds[ 1 ][ 2 ] ) * 0.5 );
|
|
|
|
outframe.radius = LittleFloat( frame->radius );
|
|
|
|
SafeWrite( modelouthandle, &outframe, sizeof( outframe ) - sizeof( md4Bone_t ) );
|
|
|
|
bone = frame->bones;
|
|
for( j = 0; j < model.numbones; j++, bone++ )
|
|
{
|
|
outbone.matrix[ 0 ][ 0 ] = LittleFloat( bone->matrix[ 0 ][ 0 ] );
|
|
outbone.matrix[ 1 ][ 0 ] = LittleFloat( bone->matrix[ 0 ][ 1 ] );
|
|
outbone.matrix[ 2 ][ 0 ] = LittleFloat( bone->matrix[ 0 ][ 2 ] );
|
|
|
|
outbone.matrix[ 0 ][ 1 ] = LittleFloat( bone->matrix[ 1 ][ 0 ] );
|
|
outbone.matrix[ 1 ][ 1 ] = LittleFloat( bone->matrix[ 1 ][ 1 ] );
|
|
outbone.matrix[ 2 ][ 1 ] = LittleFloat( bone->matrix[ 1 ][ 2 ] );
|
|
|
|
outbone.matrix[ 0 ][ 2 ] = LittleFloat( bone->matrix[ 2 ][ 0 ] );
|
|
outbone.matrix[ 1 ][ 2 ] = LittleFloat( bone->matrix[ 2 ][ 1 ] );
|
|
outbone.matrix[ 2 ][ 2 ] = LittleFloat( bone->matrix[ 2 ][ 2 ] );
|
|
|
|
outbone.matrix[ 0 ][ 3 ] = LittleFloat( bone->offset[ 0 ] );
|
|
outbone.matrix[ 1 ][ 3 ] = LittleFloat( bone->offset[ 1 ] );
|
|
outbone.matrix[ 2 ][ 3 ] = LittleFloat( bone->offset[ 2 ] );
|
|
|
|
SafeWrite( modelouthandle, &outbone, sizeof( outbone ) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// write out the LODs
|
|
//
|
|
for( l = 0; l < 1; l++ ) //FIXME -- add support for more than 1 lod
|
|
{
|
|
lodtemp.numSurfaces = LittleLong( numsurfs );
|
|
lodtemp.ofsSurfaces = sizeof( lodtemp );
|
|
lodtemp.ofsEnd = lodtemp.ofsSurfaces + surfaceSum;
|
|
|
|
lodtemp.ofsSurfaces = LittleLong( lodtemp.ofsSurfaces );
|
|
lodtemp.ofsEnd = LittleLong( lodtemp.ofsEnd );
|
|
|
|
SafeWrite( modelouthandle, &lodtemp, sizeof( lodtemp ) );
|
|
|
|
//
|
|
// write out the surfaces
|
|
//
|
|
insurf = model.surfaces;
|
|
outsurf = surftemp;
|
|
for( i = 0; i < model.numsurfaces; i++, insurf++, outsurf++ )
|
|
{
|
|
if ( insurf->numtris == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// calculate the offset to the header
|
|
outsurf->ofsHeader = LittleLong( -ftell( modelouthandle ) );
|
|
|
|
SafeWrite( modelouthandle, outsurf, sizeof( md4Surface_t ) );
|
|
|
|
//
|
|
// write out the vertices
|
|
//
|
|
vert = insurf->verts;
|
|
for( j = 0; j < insurf->numverts; j++, vert++ )
|
|
{
|
|
tempvert.normal[ 0 ] = LittleFloat( vert->normal[ 0 ] );
|
|
tempvert.normal[ 1 ] = LittleFloat( vert->normal[ 1 ] );
|
|
tempvert.normal[ 2 ] = LittleFloat( vert->normal[ 2 ] );
|
|
|
|
tempvert.texCoords[ 0 ] = LittleFloat( vert->texCoords[ 0 ] );
|
|
tempvert.texCoords[ 1 ] = LittleFloat( vert->texCoords[ 1 ] );
|
|
|
|
tempvert.numWeights = LittleLong( vert->numbones );
|
|
|
|
SafeWrite( modelouthandle, &tempvert, sizeof( tempvert ) - sizeof( tempvert.weights ) );
|
|
|
|
if ( !vert->numbones )
|
|
{
|
|
Error( "Vertex with no bones found in surface '%s'(%d).\n", insurf->name, insurf->id );
|
|
}
|
|
|
|
for( k = 0; k < vert->numbones; k++ )
|
|
{
|
|
tempweight.boneIndex = LittleLong( vert->blend[ k ].bone );
|
|
tempweight.boneWeight = LittleFloat( vert->blend[ k ].weight );
|
|
tempweight.offset[ 0 ] = LittleFloat( vert->blend[ k ].offset[ 0 ] );
|
|
tempweight.offset[ 1 ] = LittleFloat( vert->blend[ k ].offset[ 1 ] );
|
|
tempweight.offset[ 2 ] = LittleFloat( vert->blend[ k ].offset[ 2 ] );
|
|
|
|
SafeWrite( modelouthandle, &tempweight, sizeof( tempweight ) );
|
|
}
|
|
}
|
|
|
|
//
|
|
// write out the triangles
|
|
//
|
|
for( j = 0; j < insurf->numtris; j++ )
|
|
{
|
|
tritemp[ j ].indexes[ 0 ] = LittleLong( insurf->tris[ j ][ 0 ] );
|
|
tritemp[ j ].indexes[ 1 ] = LittleLong( insurf->tris[ j ][ 1 ] );
|
|
tritemp[ j ].indexes[ 2 ] = LittleLong( insurf->tris[ j ][ 2 ] );
|
|
}
|
|
|
|
SafeWrite( modelouthandle, tritemp, insurf->numtris * sizeof( skelTriangle_t ) );
|
|
|
|
//
|
|
// write out the bone references
|
|
//
|
|
for( j = 0; j < numbonerefs[ i ]; j++ )
|
|
{
|
|
bonerefs[ i ][ j ] = LittleLong( bonerefs[ i ][ j ] );
|
|
}
|
|
|
|
SafeWrite( modelouthandle, bonerefs[ i ], numbonerefs[ i ] * sizeof( bonerefs[ i ][ 0 ] ) );
|
|
}
|
|
}
|
|
|
|
if ( verbose )
|
|
{
|
|
printf( "%4d surfaces\n", model.numsurfaces );
|
|
printf( "%4d bones\n", model.numbones );
|
|
printf( "%4d verts\n", model.numverts );
|
|
printf( "file size: %d\n", ( int )ftell( modelouthandle ) );
|
|
printf( "---------------------\n" );
|
|
}
|
|
|
|
fclose( modelouthandle );
|
|
}
|
|
|
|
bool is_file_valid
|
|
(
|
|
char *filename
|
|
)
|
|
|
|
{
|
|
FILE *temp_file;
|
|
bool valid;
|
|
|
|
valid = false;
|
|
|
|
temp_file = fopen( filename, "rb" );
|
|
if ( temp_file != NULL )
|
|
{
|
|
fclose( temp_file );
|
|
valid = true;
|
|
}
|
|
|
|
return valid;
|
|
}
|
|
|
|
int main
|
|
(
|
|
int argc,
|
|
char * argv[]
|
|
)
|
|
|
|
{
|
|
SkelModel skel;
|
|
SkelModel baseskel;
|
|
char dest_filename[ 128 ];
|
|
char src_filename[ 128 ];
|
|
char uv_filename[ 128 ];
|
|
char bone_filename[ 128 ];
|
|
bool use_uv;
|
|
bool use_bone;
|
|
FILE *f;
|
|
int srctime;
|
|
int desttime;
|
|
int i;
|
|
int maxbones;
|
|
float weightthreshold;
|
|
|
|
myargc = argc;
|
|
myargv = argv;
|
|
|
|
if ( argc < 2 )
|
|
{
|
|
Error( "max2skl animname [-uv filename] [-force] [-scale num] [-dest name]\n"
|
|
" [-ignore filename] [-origin originname] [-reverse]\n"
|
|
" [-verbose] [-nolod] [-noclampz] [-zeroz] [-noorigin]\n"
|
|
" [-clearz] [-clearxy] [-baseframe] [-maxbones num]\n"
|
|
" [-weightthreshold num] [-md4] [-bones filename]\n"
|
|
" [-offset x y z] [-clearx] [-cleary] [-loop]\n"
|
|
" [-zerox] [-zeroy] [-uvb filename] [-destdir dirname]\n"
|
|
" [-ignoreflags] [-fps num]\n" );
|
|
}
|
|
|
|
verbose = qfalse;
|
|
if ( CheckParm( "-verbose" ) )
|
|
{
|
|
verbose = qtrue;
|
|
printf(" Verbose mode enabled.\n" );
|
|
}
|
|
|
|
reverse = false;
|
|
if ( CheckParm( "-reverse" ) )
|
|
{
|
|
reverse = true;
|
|
if ( verbose )
|
|
printf(" Animation will be reversed.\n" );
|
|
}
|
|
|
|
nolod = false;
|
|
if ( CheckParm( "-nolod" ) )
|
|
{
|
|
nolod = true;
|
|
if ( verbose )
|
|
printf(" No LOD map created.\n" );
|
|
}
|
|
|
|
zerox = false;
|
|
if ( CheckParm( "-zeroy" ) ) // X & Y axis are swapped in Q3
|
|
{
|
|
zerox = true;
|
|
if ( verbose )
|
|
printf(" Delta Y movement will be zeroed.\n" );
|
|
}
|
|
|
|
zeroy = false;
|
|
if ( CheckParm( "-zerox" ) ) // X & Y axis are swapped in Q3
|
|
{
|
|
zeroy = true;
|
|
if ( verbose )
|
|
printf(" Delta X movement will be zeroed.\n" );
|
|
}
|
|
|
|
zeroz = false;
|
|
if ( CheckParm( "-zeroz" ) )
|
|
{
|
|
zeroz = true;
|
|
if ( verbose )
|
|
printf(" Delta Z movement will be zeroed.\n" );
|
|
}
|
|
|
|
noclampz = false;
|
|
if ( CheckParm( "-noclampz" ) )
|
|
{
|
|
noclampz = true;
|
|
if ( verbose )
|
|
printf(" Delta Z movement will not be clamped.\n" );
|
|
}
|
|
|
|
loop = false;
|
|
if ( CheckParm( "-loop" ) )
|
|
{
|
|
loop = true;
|
|
if ( verbose )
|
|
printf(" Looping enabled. Last frame of animation will not be exported.\n" );
|
|
}
|
|
|
|
clearz = false;
|
|
if ( CheckParm( "-clearz" ) )
|
|
{
|
|
clearz = true;
|
|
if ( verbose )
|
|
printf(" Delta Z movement will be cleared after vertices are moved.\n" );
|
|
}
|
|
|
|
clearx = false;
|
|
if ( CheckParm( "-cleary" ) ) // X & Y axis are swapped in Q3
|
|
{
|
|
clearx = true;
|
|
if ( verbose )
|
|
printf(" Delta Y movement will be cleared after vertices are moved.\n" );
|
|
}
|
|
|
|
cleary = false;
|
|
if ( CheckParm( "-clearx" ) ) // X & Y axis are swapped in Q3
|
|
{
|
|
cleary = true;
|
|
if ( verbose )
|
|
printf(" Delta X movement will be cleared after vertices are moved.\n" );
|
|
}
|
|
|
|
clearxy = false;
|
|
if ( CheckParm( "-clearxy" ) )
|
|
{
|
|
clearxy = true;
|
|
if ( verbose )
|
|
printf(" Delta XY movement will be cleared after vertices are moved.\n" );
|
|
}
|
|
|
|
noorigin = false;
|
|
if ( CheckParm( "-noorigin" ) )
|
|
{
|
|
noorigin = true;
|
|
if ( verbose )
|
|
printf(" Origin is being ignored.\n" );
|
|
}
|
|
|
|
force = false;
|
|
if ( CheckParm( "-force" ) )
|
|
{
|
|
force = true;
|
|
printf(" forced no timecheck performed.\n" );
|
|
}
|
|
|
|
baseframe = false;
|
|
if ( CheckParm( "-baseframe" ) )
|
|
{
|
|
baseframe = true;
|
|
if ( verbose )
|
|
printf(" Baseframe export.\n" );
|
|
}
|
|
|
|
md4 = false;
|
|
if ( CheckParm( "-md4" ) )
|
|
{
|
|
md4 = true;
|
|
if ( verbose )
|
|
printf(" MD4 export.\n" );
|
|
}
|
|
|
|
ignoreflags = false;
|
|
if ( CheckParm( "-ignoreflags" ) )
|
|
{
|
|
ignoreflags = true;
|
|
if ( verbose )
|
|
printf(" Ignoring flags when removing bones.\n" );
|
|
}
|
|
|
|
// set the maxbones if necessary
|
|
maxbones = MAX_BLENDBONES;
|
|
if ( i = CheckParm( "-maxbones" ) )
|
|
{
|
|
maxbones = atoi( argv[ i + 1 ] );
|
|
if ( verbose )
|
|
printf(" maxbones set at %d.\n", maxbones );
|
|
}
|
|
|
|
// set the weightthreshold if necessary
|
|
weightthreshold = 0.01;
|
|
if ( i = CheckParm( "-weightthreshold" ) )
|
|
{
|
|
weightthreshold = atof( argv[ i + 1 ] );
|
|
if ( verbose )
|
|
printf(" weightthreshold set at %f.\n", weightthreshold );
|
|
}
|
|
|
|
// set the fps adjustment if necessary
|
|
adjustfps = false;
|
|
if ( i = CheckParm( "-fps" ) )
|
|
{
|
|
adjustfps = true;
|
|
fps = atof( argv[ i + 1 ] );
|
|
if ( verbose )
|
|
printf(" frames per second set at %f.\n", fps );
|
|
}
|
|
|
|
// set the scale if necessary
|
|
scale_factor = 1;
|
|
if ( i = CheckParm( "-scale" ) )
|
|
{
|
|
scale_factor = atof( argv[ i + 1 ] );
|
|
if ( verbose )
|
|
printf(" scale set at %f.\n", scale_factor );
|
|
}
|
|
|
|
// set the offset if necessary
|
|
modeloffset = Vector( 0, 0, 0 );
|
|
if ( i = CheckParm( "-offset" ) )
|
|
{
|
|
if ( i + 3 >= argc )
|
|
{
|
|
Error( "Not enough arguments for -offset. Usage: -offset x y z\n" );
|
|
}
|
|
|
|
// X & Y axis are swapped in Q3
|
|
modeloffset.x = -atof( argv[ i + 2 ] );
|
|
modeloffset.y = atof( argv[ i + 1 ] );
|
|
modeloffset.z = atof( argv[ i + 3 ] );
|
|
if ( verbose )
|
|
{
|
|
printf(" offset set to (%f %f %f).\n", modeloffset.x, modeloffset.y, modeloffset.z );
|
|
}
|
|
}
|
|
|
|
// see if a new origin was specified
|
|
if ( i = CheckParm( "-origin" ) )
|
|
{
|
|
originname = argv[ i + 1 ];
|
|
if ( verbose )
|
|
printf(" origin set to %s.\n", originname.c_str() );
|
|
}
|
|
|
|
// see if an ignore list is specified
|
|
if ( i = CheckParm( "-ignore" ) )
|
|
{
|
|
skel.LoadIgnoreFile( argv[ i + 1 ] );
|
|
baseskel.LoadIgnoreFile( argv[ i + 1 ] );
|
|
}
|
|
|
|
strcpy( src_filename, argv[ 1 ] );
|
|
strcat( src_filename, ".skl" );
|
|
|
|
// check for destination directory
|
|
if ( i = CheckParm( "-destdir" ) )
|
|
{
|
|
sprintf( writedir, "%s", argv[ i + 1 ] );
|
|
qprintf( "Destination directory changed to %s.\n", writedir );
|
|
}
|
|
|
|
// create destination filename
|
|
// check for dest file
|
|
if ( i = CheckParm( "-dest" ) )
|
|
{
|
|
sprintf( dest_filename, "%s%s", writedir, argv[ i + 1 ] );
|
|
}
|
|
else
|
|
{
|
|
sprintf( dest_filename, "%s%s", writedir, src_filename );
|
|
}
|
|
|
|
StripExtension( dest_filename );
|
|
if ( baseframe )
|
|
{
|
|
strcat( dest_filename, ".skb" );
|
|
}
|
|
else if ( md4 )
|
|
{
|
|
strcat( dest_filename, ".md4" );
|
|
}
|
|
else
|
|
{
|
|
strcat( dest_filename, ".ska" );
|
|
}
|
|
|
|
// Make sure the SKL file is really there
|
|
if ( !is_file_valid( src_filename ) )
|
|
{
|
|
Error( "File '%s' not found", src_filename );
|
|
}
|
|
|
|
// check the time stamps
|
|
srctime = FileTime( src_filename );
|
|
desttime = FileTime( dest_filename );
|
|
|
|
// check for version number difference
|
|
if ( !force && ( desttime > 0 ) )
|
|
{
|
|
f = SafeOpenRead( dest_filename );
|
|
|
|
// read header
|
|
SafeRead( f, &i, sizeof( i ) );
|
|
if ( ( baseframe && ( i != TIKI_SKEL_IDENT ) ) ||
|
|
( !baseframe && ( i != TIKI_SKEL_ANIM_IDENT ) ) )
|
|
{
|
|
printf( " file identitifier differs, regrabbing.\n" );
|
|
force = true;
|
|
}
|
|
|
|
// read version
|
|
SafeRead( f, &i, sizeof( i ) );
|
|
i = LittleLong( i );
|
|
if ( i != TIKI_SKEL_VERSION )
|
|
{
|
|
printf( " version numbers differ, regrabbing.\n" );
|
|
force = true;
|
|
}
|
|
|
|
fclose( f );
|
|
}
|
|
|
|
if ( srctime > desttime )
|
|
{
|
|
force = true;
|
|
}
|
|
|
|
// check for uv file
|
|
use_uv = false;
|
|
if ( ( i = CheckParm( "-uv" ) ) || ( i = CheckParm( "-uvb" ) ) )
|
|
{
|
|
// check the date and time of the uv file
|
|
strcpy( uv_filename, argv[ i + 1 ] );
|
|
if ( !strstr( uv_filename, "." ) )
|
|
{
|
|
StripExtension( uv_filename );
|
|
strcat( uv_filename, ".skl" );
|
|
}
|
|
|
|
srctime = FileTime( uv_filename );
|
|
if ( srctime > desttime )
|
|
{
|
|
force = true;
|
|
}
|
|
|
|
use_uv = true;
|
|
}
|
|
|
|
// check for bone file
|
|
use_bone = false;
|
|
if ( ( i = CheckParm( "-bones" ) ) || ( i = CheckParm( "-uvb" ) ) )
|
|
{
|
|
// check the date and time of the bones file
|
|
strcpy( bone_filename, argv[ i + 1 ] );
|
|
if ( !strstr( bone_filename, "." ) )
|
|
{
|
|
StripExtension( bone_filename );
|
|
strcat( bone_filename, ".skl" );
|
|
}
|
|
|
|
srctime = FileTime( bone_filename );
|
|
if ( srctime > desttime )
|
|
{
|
|
force = true;
|
|
}
|
|
|
|
use_bone = true;
|
|
}
|
|
|
|
if ( !force )
|
|
{
|
|
if ( -verbose )
|
|
{
|
|
printf(" destination is newer than source, skipping.\n" );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
skel.LoadSKL( src_filename );
|
|
|
|
if ( use_uv )
|
|
{
|
|
// Load the uv coordinates from the uv file
|
|
qprintf( "Grabbing surfaces from %s.\n", uv_filename );
|
|
baseskel.LoadSKL( uv_filename );
|
|
skel.CopyBaseModel( baseskel );
|
|
}
|
|
|
|
if ( use_bone )
|
|
{
|
|
// Load the bone names from the bones file
|
|
qprintf( "Merging bones from %s.\n", bone_filename );
|
|
baseskel.LoadSKL( bone_filename );
|
|
skel.CopyBones( baseskel );
|
|
}
|
|
|
|
skel.ConvertToQuake3Worldspace();
|
|
|
|
skel.ScaleModel( scale_factor );
|
|
if ( reverse )
|
|
{
|
|
skel.ReverseAnimation();
|
|
}
|
|
|
|
skel.ReduceBonesPerVertex( maxbones, weightthreshold );
|
|
|
|
skel.FixMirroredBones();
|
|
skel.CreateSurfaces();
|
|
skel.CalculateTags();
|
|
skel.RemoveUnusedBones();
|
|
|
|
// check if we should create a baseframe or an animation
|
|
if ( baseframe )
|
|
{
|
|
skel.ComputeVertexNormals();
|
|
|
|
if ( !nolod )
|
|
{
|
|
skel.CalculateLOD();
|
|
}
|
|
|
|
skel.CalculateTriStrips();
|
|
skel.SaveBaseFrame( dest_filename );
|
|
}
|
|
else if ( !md4 )
|
|
{
|
|
skel.CalculateBounds();
|
|
skel.SaveAnimation( dest_filename );
|
|
}
|
|
else
|
|
{
|
|
skel.ComputeVertexNormals();
|
|
skel.CalculateTriStrips();
|
|
skel.CalculateBounds();
|
|
skel.SaveMD4( dest_filename );
|
|
}
|
|
|
|
return 0;
|
|
}
|