Paranoia2_original/utils/studiomdl/studiomdl.cpp

5524 lines
126 KiB
C++

/*
studiomodel.cpp - generates a studio .mdl file from a .qc script
Copyright (C) 2017 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include <windows.h>
#include "cmdlib.h"
#include "mathlib.h"
#include "stringlib.h"
#include "scriplib.h"
#include "filesystem.h"
#define EXTERN
#include "studio.h"
#include "studiomdl.h"
#include <activity.h>
#include <activitymap.h>
CUtlArray< char > g_KeyValueText;
char filename[1024];
char line[1024];
int linecount;
FILE *input;
//-----------------------------------------------------------------------------
// Parsed data from a .qc file
//-----------------------------------------------------------------------------
/*
=================
ClearModel
FIXME: finalize this if you want to compile models in cycle
=================
*/
void ClearModel( void )
{
int i, j, k;
// release memory occupied by animations
for( i = 0; i < g_real_numani; i++ )
{
// we never know how many frames are used
for( j = 0; j < MAXSTUDIOANIMATIONS; j++ )
{
if( g_panimation[i]->rawanim[j] )
Mem_Free( g_panimation[i]->rawanim[j] );
if( g_panimation[i]->sanim[j] )
Mem_Free( g_panimation[i]->sanim[j] );
}
for( j = 0; j < g_real_numbones; j++ )
{
for( k = 0; k < 6; k++ )
{
Mem_Free( g_panimation[i]->anim[j][k] );
}
}
for( j = 0; j < g_panimation[i]->numcmds; j++ )
{
s_animcmd_t *pcmd = &g_panimation[i]->cmds[j];
if( pcmd->cmd == CMD_IKRULE && pcmd->cmd_source == CMDSRC_LOCAL )
Mem_Free( pcmd->ikrule.pRule );
if( pcmd->cmd == CMD_IKFIXUP && pcmd->cmd_source == CMDSRC_LOCAL )
Mem_Free( pcmd->ikfixup.pRule );
}
for( j = 0; j < g_panimation[i]->numikrules; j++ )
{
s_ikrule_t *ikrule = &g_panimation[i]->ikrule[j];
Mem_Free( ikrule->errorData.pError );
for( k = 0; k < 6; k++ )
Mem_Free( ikrule->errorData.anim[k] );
}
Mem_Free( g_panimation[i] );
}
for( i = 0; i < g_numcmdlists; i++ )
{
s_cmdlist_t *cmdlist = &g_cmdlist[i];
for( j = 0; j < cmdlist->numcmds; j++ )
{
s_animcmd_t *pcmd = &cmdlist->cmds[j];
if( pcmd->cmd == CMD_IKRULE && pcmd->cmd_source == CMDSRC_GLOBAL )
Mem_Free( pcmd->ikrule.pRule );
if( pcmd->cmd == CMD_IKFIXUP && pcmd->cmd_source == CMDSRC_GLOBAL )
Mem_Free( pcmd->ikfixup.pRule );
}
}
memset( g_cmdlist, 0, sizeof( g_cmdlist ));
g_numcmdlists = 0;
for( i = 0; i < g_nummodels; i++ )
{
for( j = 0; j < g_model[i]->nummesh; j++ )
{
Mem_Free( g_model[i]->pmesh[j]->triangle );
Mem_Free( g_model[i]->pmesh[j] ); // iteslf
}
Mem_Free( g_model[i] );
}
for( i = 0; i < g_real_numseq; i++ )
{
g_sequence[i].KeyValue.Purge();
}
for( i = 1; i <= g_numxnodes; i++ )
free( g_xnodename[i] );
for( i = 0; i < g_numtextures; i++ )
Mem_Free( g_texture[i].pdata );
memset( g_xnode, 0, sizeof( g_xnode ));
memset( g_xnodename, 0, sizeof( g_xnodename ));
memset( g_xnodeskip, 0, sizeof( g_xnodeskip ));
g_numxnodes = g_numxnodeskips = 0;
memset( g_texture, 0, sizeof( g_texture ));
g_numtextures = 0;
memset( g_sequencegroup, 0, sizeof( g_sequencegroup ));
g_numseqgroups = 0;
memset( g_sequence, 0, sizeof( g_sequence ));
g_real_numseq = 0;
g_numseq = 0;
memset( g_model, 0, sizeof( g_model ));
g_nummodels = 0;
memset( g_panimation, 0, sizeof( g_panimation ));
g_real_numani = 0;
g_numani = 0;
memset( g_pose, 0, sizeof( g_pose ));
g_numposeparameters = 0;
memset( g_bonetable, 0, sizeof( g_bonetable ));
g_real_numbones = 0;
g_numbones = 0;
memset( g_renamedbone, 0, sizeof( g_renamedbone ));
g_numrenamedbones = 0;
memset( g_importbone, 0, sizeof( g_importbone ));
g_numimportbones = 0;
memset( g_hitgroup, 0, sizeof( g_hitgroup ));
g_numhitgroups = 0;
memset( g_bonecontroller, 0, sizeof( g_bonecontroller ));
g_numbonecontrollers = 0;
memset( g_screenalignedbone, 0, sizeof( g_screenalignedbone ));
g_numscreenalignedbones = 0;
memset( g_attachment, 0, sizeof( g_attachment ));
g_numattachments = 0;
g_hitboxsets.Purge();
g_BoneMerge.Purge();
}
bool GetLineInput( void )
{
while( fgets( line, sizeof( line ), input ) != NULL )
{
linecount++;
// skip comments
if( line[0] == '/' && line[1] == '/' )
continue;
return true;
}
return false;
}
bool IsEnd( char const *pLine )
{
if( !Q_strncmp( "end", pLine, 3 ))
return true;
return ( pLine[3] == '\0' ) || ( pLine[3] == '\n' );
}
int verify_atoi_dbg( const char *token, const int line )
{
if( token[0] != '-' && ( token[0] < '0' || token[0] > '9' ))
{
TokenError( "expecting number, got \"%s\"\ncalled at line %d", token, line );
}
return atoi( token );
}
float verify_atof_dbg( const char *token, const int line )
{
if( token[0] != '-' && token[0] != '.' && ( token[0] < '0' || token[0] > '9' ))
{
TokenError( "expecting number, got \"%s\"\ncalled at line %d", token, line );
}
return atof( token );
}
float verify_atof_with_null( const char *token )
{
if( !Q_strcmp( token, ".." ))
return -1;
return verify_atof( token );
}
//-----------------------------------------------------------------------------
// Key value block
//-----------------------------------------------------------------------------
static void AppendKeyValueText( CUtlArray< char > *pKeyValue, const char *pString )
{
int nLen = Q_strlen( pString );
int nFirst = pKeyValue->AddMultipleToTail( nLen );
memcpy( pKeyValue->Base() + nFirst, pString, nLen );
}
int KeyValueTextSize( CUtlArray< char > *pKeyValue )
{
return pKeyValue->Count();
}
const char *KeyValueText( CUtlArray< char > *pKeyValue )
{
return pKeyValue->Base();
}
const char *RenameBone( const char *pName )
{
for( int k = 0; k < g_numrenamedbones; k++ )
{
if( !Q_stricmp( pName, g_renamedbone[k].from ))
return g_renamedbone[k].to;
}
return pName;
}
bool IsGlobalBoneXSI( const char *name, const char *bonename )
{
name = RenameBone( name );
int len = Q_strlen( name );
int len2 = Q_strlen( bonename );
if( len2 == len && Q_strchr( bonename, '.' ) == NULL && !Q_stricmp( bonename, name ))
return true;
if( len2 > len )
{
if( bonename[len2-len-1] == '.' )
{
if( !Q_stricmp( &bonename[len2-len], name ))
{
return true;
}
}
}
return false;
}
int findGlobalBone( const char *name )
{
name = RenameBone( name );
for( int k = 0; k < g_numbones; k++ )
{
if( !Q_stricmp( g_bonetable[k].name, name ))
return k;
}
return -1;
}
int findLocalBone( const s_animation_t *panim, const char *name )
{
if( name )
{
int i;
for( i = 0; i < panim->numbones; i++ )
{
if( !Q_stricmp( name, panim->localBone[i].name ))
return i;
}
name = RenameBone( name );
for( i = 0; i < panim->numbones; i++ )
{
if( !Q_stricmp( name, panim->localBone[i].name ))
return i;
}
}
return -1;
}
int findGlobalBoneXSI( const char *name )
{
name = RenameBone( name );
for( int k = 0; k < g_numbones; k++ )
{
if( IsGlobalBoneXSI( name, g_bonetable[k].name ))
return k;
}
return -1;
}
int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] )
{
int i, bShouldSort;
float w, t;
// collapse duplicate bone weights
for( i = 0; i < iCount-1; i++ )
{
for( int j = i + 1; j < iCount; j++ )
{
if( bones[i] == bones[j] )
{
weights[i] += weights[j];
weights[j] = 0.0;
}
}
}
// do sleazy bubble sort
do {
bShouldSort = false;
for( i = 0; i < iCount-1; i++ )
{
if( weights[i+1] > weights[i] )
{
int j = bones[i+1];
bones[i+1] = bones[i];
bones[i] = j;
w = weights[i+1];
weights[i+1] = weights[i];
weights[i] = w;
bShouldSort = true;
}
}
} while( bShouldSort );
// throw away all weights less than 1/20th
while( iCount > 1 && weights[iCount-1] < 0.005 )
iCount--;
// clip to the top iMaxCount bones
if( iCount > iMaxCount )
iCount = iMaxCount;
t = 0.0f;
for( i = 0; i < iCount; i++ )
t += weights[i];
if( t <= 0.0f )
{
// missing weights?, go ahead and evenly share?
// FIXME: shouldn't this error out?
t = 1.0 / iCount;
for( i = 0; i < iCount; i++ )
weights[i] = t;
}
else
{
// scale to sum to 1.0
t = 1.0 / t;
for( i = 0; i < iCount; i++ )
weights[i] = weights[i] * t;
}
return iCount;
}
int LookupControl( const char *string )
{
if( !Q_stricmp( string, "X" )) return STUDIO_X;
if( !Q_stricmp( string, "Y" )) return STUDIO_Y;
if( !Q_stricmp( string, "Z" )) return STUDIO_Z;
if( !Q_stricmp( string, "XR" )) return STUDIO_XR;
if( !Q_stricmp( string, "YR" )) return STUDIO_YR;
if( !Q_stricmp( string, "ZR" )) return STUDIO_ZR;
if( !Q_stricmp( string, "LX" )) return STUDIO_LX;
if( !Q_stricmp( string, "LY" )) return STUDIO_LY;
if( !Q_stricmp( string, "LZ" )) return STUDIO_LZ;
if( !Q_stricmp( string, "LXR" )) return STUDIO_LXR;
if( !Q_stricmp( string, "LYR" )) return STUDIO_LYR;
if( !Q_stricmp( string, "LZR" )) return STUDIO_LZR;
if( !Q_stricmp( string, "LM" )) return STUDIO_LINEAR;
if( !Q_stricmp( string, "LQ" )) return STUDIO_QUADRATIC_MOTION;
return -1;
}
int LookupPoseParameter( const char *name )
{
int i;
for( i = 0; i < g_numposeparameters; i++)
{
if( !Q_stricmp( name, g_pose[i].name ))
return i;
}
Q_strncpy( g_pose[i].name, name, sizeof( g_pose[0].name ));
g_numposeparameters = i + 1;
if( g_numposeparameters > MAXSTUDIOPOSEPARAM )
TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM );
return i;
}
s_sequence_t *LookupSequence( const char *name )
{
for( int i = 0; i < g_numseq; i++ )
{
if ( !Q_stricmp( g_sequence[i].name, name ))
return &g_sequence[i];
}
return NULL;
}
s_animation_t *LookupAnimation( const char *name )
{
for( int i = 0; i < g_numani; i++ )
{
if( !Q_stricmp( g_panimation[i]->name, name ))
return g_panimation[i];
}
s_sequence_t *pseq = LookupSequence( name );
return pseq ? pseq->panim[0] : NULL;
}
int LookupAttachment( const char *name )
{
for( int i = 0; i < g_numattachments; i++ )
{
if( !Q_stricmp( g_attachment[i].name, name ))
return i;
}
return -1;
}
int LookupTexture( const char *texturename )
{
int i;
for( i = 0; i < g_numtextures; i++ )
{
if( !Q_stricmp( g_texture[i].name, texturename ))
return i;
}
Q_strncpy( g_texture[i].name, texturename, sizeof( g_texture[0].name ));
// XDM: allow such names as "tex_chrome_bright" - chrome and full brightness effects
if( Q_stristr( texturename, "chrome" ) != NULL )
g_texture[i].flags |= STUDIO_NF_FLATSHADE | STUDIO_NF_CHROME;
if( Q_stristr( texturename, "bright" ) != NULL )
g_texture[i].flags |= STUDIO_NF_FULLBRIGHT;
g_numtextures++;
return i;
}
s_mesh_t *LookupMesh( s_model_t *pmodel, char *texturename )
{
int i, j;
j = LookupTexture( texturename );
for( i = 0; i < pmodel->nummesh; i++ )
{
if( pmodel->pmesh[i]->skinref == j )
return pmodel->pmesh[i];
}
if( i >= MAXSTUDIOMESHES )
COM_FatalError( "too many meshes in model: \"%s\"\n", pmodel->name );
pmodel->nummesh = i + 1;
pmodel->pmesh[i] = (s_mesh_t *)Mem_Alloc( sizeof( s_mesh_t ));
pmodel->pmesh[i]->skinref = j;
return pmodel->pmesh[i];
}
s_trianglevert_t *LookupTriangle( s_mesh_t *pmesh, int index )
{
if( index >= MAXSTUDIOTRIANGLES )
COM_FatalError( "max studio triangles exceeds 65536\n" );
if( index >= pmesh->alloctris )
{
int start = pmesh->alloctris;
pmesh->alloctris = index + 256;
if( pmesh->triangle )
{
pmesh->triangle = (s_trianglevert_t (*)[3])Mem_Realloc( pmesh->triangle, pmesh->alloctris * sizeof( *pmesh->triangle ));
memset( &pmesh->triangle[start], 0, ( pmesh->alloctris - start ) * sizeof( *pmesh->triangle ));
}
else
{
pmesh->triangle = (s_trianglevert_t (*)[3])Mem_Alloc( pmesh->alloctris * sizeof( *pmesh->triangle ));
}
}
return pmesh->triangle[index];
}
int LookupNormal( s_model_t *pmodel, s_srcvertex_t *srcv )
{
int i;
for( i = 0; i < pmodel->numnorms; i++ )
{
if( DotProduct( pmodel->norm[i].org, srcv->norm ) > g_normal_blend && pmodel->norm[i].skinref == srcv->skinref
&& !memcmp( &pmodel->norm[i].globalWeight, &srcv->globalWeight, sizeof( s_boneweight_t ))) {
return i;
}
}
if( i >= MAXSTUDIOVERTS )
COM_FatalError( "too many normals in model: \"%s\"\n", pmodel->name );
pmodel->norm[i].org = srcv->norm;
pmodel->norm[i].globalWeight = srcv->globalWeight;
pmodel->norm[i].skinref = srcv->skinref;
pmodel->numnorms = i + 1;
return i;
}
int LookupVertex( s_model_t *pmodel, s_srcvertex_t *srcv )
{
int i;
// assume 3 digits of accuracy
srcv->vert.x = (int)(srcv->vert.x * 1000) / 1000.0;
srcv->vert.y = (int)(srcv->vert.y * 1000) / 1000.0;
srcv->vert.z = (int)(srcv->vert.z * 1000) / 1000.0;
for( i = 0; i < pmodel->numverts; i++ )
{
if( pmodel->vert[i].org == srcv->vert && !memcmp( &pmodel->vert[i].globalWeight, &srcv->globalWeight, sizeof( s_boneweight_t )))
return i;
}
if( i >= MAXSTUDIOVERTS )
COM_FatalError( "too many vertices in model: \"%s\"\n", pmodel->name );
pmodel->vert[i].org = srcv->vert;
pmodel->vert[i].globalWeight = srcv->globalWeight;
pmodel->numverts = i + 1;
return i;
}
int LookupActivity( const char *szActivity )
{
int i;
for( i = 0; activity_map[i].name; i++ )
{
if( !Q_stricmp( szActivity, activity_map[i].name ))
return activity_map[i].type;
}
// match ACT_#
if( !Q_strnicmp( szActivity, "ACT_", 4 ))
return verify_atoi( &szActivity[4] );
return 0;
}
int LookupXNode( const char *name )
{
int i;
// FIXME: check transitions in-game
if( Q_isdigit( name ))
return verify_atoi( name );
for( i = 1; i <= g_numxnodes; i++ )
{
if( !Q_stricmp( name, g_xnodename[i] ))
return i;
}
g_xnodename[i] = strdup( name );
g_numxnodes = i;
return i;
}
void clip_rotations( float &rot )
{
// clip everything to : -M_PI <= x < M_PI
while( rot >= M_PI ) rot -= M_PI * 2.0f;
while( rot < -M_PI ) rot += M_PI * 2.0f;
}
void clip_rotations( Radian &rot )
{
clip_rotations( rot.x );
clip_rotations( rot.y );
clip_rotations( rot.z );
}
void scale_animation( s_animation_t *panim )
{
for( int t = 0; t < panim->source.numframes; t++ )
{
for( int j = 0; j < panim->numbones; j++ )
panim->rawanim[t][j].pos *= panim->scale;
}
}
void Build_Reference( s_model_t *pmodel )
{
for( int i = 0; i < pmodel->numbones; i++ )
{
matrix3x4 bonematrix = matrix3x4( pmodel->skeleton[i].pos, pmodel->skeleton[i].rot );
int parent = pmodel->localBone[i].parent;
if( parent == -1 )
{
// scale the done pos.
// calc rotational matrices
pmodel->boneToPose[i] = bonematrix;
}
else
{
// calc compound rotational matrices
pmodel->boneToPose[i] = pmodel->boneToPose[parent].ConcatTransforms( bonematrix );
}
}
}
void Grab_Triangles( s_model_t *pmodel )
{
int i, j, k;
int ncount = 0;
float vmin = 9999.0f;
// load the base triangles
while( 1 )
{
if( !GetLineInput( ))
break;
// check for end
if( IsEnd( line ))
break;
char texturename[64];
s_mesh_t *pmesh;
s_trianglevert_t *ptriv;
int bone;
Vector vert[3];
Vector norm[3];
Q_strncpy( texturename, line, sizeof( texturename ));
// strip off trailing smag
for( i = Q_strlen( texturename ) - 1; i >= 0 && !isgraph( texturename[i] ); i-- );
texturename[i + 1] = '\0';
// funky texture overrides
for( i = 0; i < numrep; i++ )
{
if( sourcetexture[i][0] == '\0' )
{
Q_strncpy( texturename, defaulttexture[i], sizeof( texturename ));
break;
}
if( !Q_stricmp( texturename, sourcetexture[i] ))
{
Q_strncpy( texturename, defaulttexture[i], sizeof( texturename ));
break;
}
}
if( texturename[0] == '\0' )
{
// weird model problem, skip them
GetLineInput();
GetLineInput();
GetLineInput();
continue;
}
if( Q_stristr( texturename, "null.bmp" ) || Q_stristr( texturename, "null.tga" ))
{
// skip all faces with the null texture on them.
GetLineInput();
GetLineInput();
GetLineInput();
continue;
}
COM_DefaultExtension( texturename, ".tga" ); // Crowbar decompiler issues
pmesh = LookupMesh( pmodel, texturename );
for( j = 0; j < 3; j++ )
{
if( pmodel->flip_triangles )
ptriv = LookupTriangle( pmesh, pmesh->numtris ) + 2 - j; // quake wants them in the reverse order
else ptriv = LookupTriangle( pmesh, pmesh->numtris ) + j;
if( !GetLineInput( ))
{
COM_FatalError( "%s: error on line %d: %s", filename, linecount, line );
}
s_srcvertex_t *srcv = &pmodel->srcvert[pmodel->numsrcverts];
int iCount = 0, bones[MAXSTUDIOSRCBONES];
float weights[MAXSTUDIOSRCBONES];
s_boneweight_t boneWeight;
// get support for Source bone weights description
i = sscanf( line, "%d %f %f %f %f %f %f %f %f %d %d %f %d %f %d %f %d %f",
&bone,
&srcv->vert[0], &srcv->vert[1], &srcv->vert[2],
&srcv->norm[0], &srcv->norm[1], &srcv->norm[2],
&ptriv->u, &ptriv->v,
&iCount,
&bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] );
if( i < 9 ) continue;
if( bone < 0 || bone >= pmodel->numbones )
{
COM_FatalError( "bogus bone index\n%d %s :\n%s", linecount, filename, line );
}
// continue parsing more bones.
if( iCount > MAXSTUDIOBONEWEIGHTS )
{
char *token;
int ctr = 0;
for( k = 0; k < 18; k++ )
{
while( line[ctr] == ' ' )
{
ctr++;
}
token = strtok( &line[ctr], " " );
ctr += Q_strlen( token ) + 1;
}
for( k = 4; k < iCount && k < MAXSTUDIOSRCBONES; k++ )
{
while( line[ctr] == ' ' )
{
ctr++;
}
token = strtok( &line[ctr], " " );
ctr += Q_strlen( token ) + 1;
bones[k] = verify_atoi( token );
token = strtok( &line[ctr], " " );
ctr += strlen( token ) + 1;
weights[k] = verify_atof( token );
}
}
vmin = Q_min( srcv->vert.z, vmin );
srcv->skinref = pmesh->skinref;
srcv->vert *= pmodel->scale;
vert[j] = srcv->vert;
norm[j] = srcv->norm;
// initialize boneweigts
for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ )
{
boneWeight.weight[k] = 0.0f;
boneWeight.bone[k] = -1;
}
if( i == 9 || iCount == 0 )
{
boneWeight.weight[0] = 1.0f;
boneWeight.bone[0] = bone;
boneWeight.numbones = 1;
}
else
{
iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights );
if( allow_boneweights )
{
for( k = 0; k < iCount; k++ )
{
boneWeight.bone[k] = bound( 0, bones[k], MAXSTUDIOBONES - 1 );
boneWeight.weight[k] = weights[k];
}
boneWeight.numbones = iCount;
has_boneweights = true;
}
else
{
boneWeight.bone[0] = bones[0];
boneWeight.weight[0] = 1.0f;
boneWeight.numbones = 1;
}
}
srcv->localWeight = boneWeight;
ptriv->vertindex = ptriv->normindex = pmodel->numsrcverts++;
if( pmodel->numsrcverts >= MAXSRCSTUDIOVERTS )
COM_FatalError( "too many source vertices in model: \"%s\"\n", pmodel->name );
// tag bone as being used
// pmodel->bone[bone].ref = 1;
}
if( tag_reversed || tag_normals )
{
// check triangle direction
if( DotProduct( norm[0], norm[1] ) < 0 || DotProduct( norm[1], norm[2] ) < 0 || DotProduct( norm[2], norm[0] ) < 0 )
{
ncount++;
if( tag_normals )
{
// steal the triangle and make it white
s_trianglevert_t *ptriv2;
pmesh = LookupMesh( pmodel, "#white.bmp" );
ptriv2 = LookupTriangle( pmesh, pmesh->numtris );
ptriv2[0] = ptriv[0];
ptriv2[1] = ptriv[1];
ptriv2[2] = ptriv[2];
}
}
else
{
Vector a1, a2, sn;
float x, y, z;
a1 = vert[1] - vert[0];
a2 = vert[2] - vert[0];
sn = CrossProduct( a1, a2 ).Normalize();
x = DotProduct( sn, norm[0] );
y = DotProduct( sn, norm[1] );
z = DotProduct( sn, norm[2] );
if( x < 0.0 || y < 0.0 || z < 0.0 )
{
if( tag_reversed )
{
// steal the triangle and make it white
s_trianglevert_t *ptriv2;
MsgDev( D_INFO, "triangle reversed (%f %f %f)\n",
DotProduct( norm[0], norm[1] ),
DotProduct( norm[1], norm[2] ),
DotProduct( norm[2], norm[0] ));
pmesh = LookupMesh( pmodel, "#white.bmp" );
ptriv2 = LookupTriangle( pmesh, pmesh->numtris );
ptriv2[0] = ptriv[0];
ptriv2[1] = ptriv[1];
ptriv2[2] = ptriv[2];
}
}
}
}
pmesh->numtris++;
}
if( ncount ) MsgDev( D_WARN, "%d triangles with misdirected normals\n", ncount );
if( vmin != 0.0 ) MsgDev( D_REPORT, "lowest vector at %f\n", vmin );
}
bool Grab_AnimFrames( s_animation_t *panim )
{
Vector pos;
Radian rot;
char cmd[1024];
int index, size;
int t = -99999999;
size = panim->numbones * sizeof( s_bone_t );
panim->source.startframe = -1;
panim->source.endframe = 0;
while( GetLineInput( ))
{
if( sscanf( line, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7 )
{
if( panim->source.startframe < 0 )
COM_FatalError( "missing frame start(%d) : %s\n", linecount, line );
panim->rawanim[t][index].pos = pos;
panim->rawanim[t][index].rot = rot;
continue;
}
if( sscanf( line, "%1023s %d", cmd, &index ) == 0 )
{
COM_FatalError( "(%d) : %s", linecount, line );
continue;
}
if( !Q_stricmp( cmd, "time" ))
{
t = index;
if( panim->source.startframe == -1 )
panim->source.startframe = t;
if( t < panim->source.startframe )
COM_FatalError( "frame error(%d) : %s\n", linecount, line );
if( t > panim->source.endframe )
panim->source.endframe = t;
t -= panim->source.startframe;
if( t > MAXSTUDIOANIMFRAMES )
{
MsgDev( D_ERROR, "animation %s has too many frames. Cutted at %d\n", panim->name, MAXSTUDIOANIMFRAMES );
panim->source.numframes = MAXSTUDIOANIMFRAMES - 1;
panim->source.endframe = MAXSTUDIOANIMFRAMES - 1;
return false;
}
if( panim->rawanim[t] != NULL )
continue;
panim->rawanim[t] = (s_bone_t *)Mem_Alloc( size );
// duplicate previous frames keys
if( t > 0 && panim->rawanim[t-1] )
{
for( int j = 0; j < panim->numbones; j++ )
{
panim->rawanim[t][j].pos = panim->rawanim[t-1][j].pos;
panim->rawanim[t][j].rot = panim->rawanim[t-1][j].rot;
}
}
continue;
}
if( !Q_stricmp( cmd, "end" ))
{
panim->source.numframes = panim->source.endframe - panim->source.startframe + 1;
for( t = 0; t < panim->source.numframes; t++ )
{
if( panim->rawanim[t] == NULL )
COM_FatalError( "%s is missing frame %d\n", panim->name, t + panim->source.startframe );
}
return true;
}
COM_FatalError( "(%d) : %s", linecount, line );
}
COM_FatalError( "unexpected EOF: %s\n", panim->name );
return true;
}
void Grab_Skeleton( s_model_t *pmodel )
{
Vector pos;
Radian rot;
char cmd[1024];
int index;
while( GetLineInput( ))
{
if( sscanf( line, "%d %f %f %f %f %f %f", &index, &pos.x, &pos.y, &pos.z, &rot.x, &rot.y, &rot.z ) == 7 )
{
pos *= pmodel->scale;
pmodel->skeleton[index].pos = pos;
pmodel->skeleton[index].rot = rot;
}
else if( sscanf( line, "%s %d", cmd, &index ))
{
if( !Q_strcmp( cmd, "time" ))
{
// begin building skeleton
}
else if( !Q_strcmp( cmd, "end" ))
{
Build_Reference( pmodel );
return;
}
}
}
}
int Grab_Nodes( s_node_t *pnodes )
{
int index, parent;
int numbones = 0;
char name[1024];
for( index = 0; index < MAXSTUDIOSRCBONES; index++ )
pnodes[index].parent = -1;
while( GetLineInput( ))
{
if( sscanf( line, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3 )
{
Q_strncpy( pnodes[index].name, name, sizeof( pnodes[0].name ));
pnodes[index].parent = parent;
numbones = Q_max( numbones, index );
}
else
{
return numbones + 1;
}
}
COM_FatalError( "Unexpected EOF at line %d\n", linecount );
return 0;
}
void Grab_Studio( s_model_t *pmodel )
{
char cmd[1024];
int option;
Q_snprintf( filename, sizeof( filename ), "%s/%s", cddir[numdirs], pmodel->name );
COM_DefaultExtension( filename, ".smd" );
if( !COM_FileExists( filename ))
COM_FatalError( "%s doesn't exist\n", filename );
if(( input = fopen( filename, "r" )) == 0 )
COM_FatalError( "%s couldn't be open\n", filename );
MsgDev( D_INFO, "grabbing: %s.smd\t\t[^1mesh^7]\n", pmodel->name );
linecount = 0;
while( GetLineInput( ))
{
int numRead = sscanf( line, "%s %d", cmd, &option );
// blank line
if(( numRead == EOF ) || ( numRead == 0 ))
continue;
if( !Q_strcmp( cmd, "version" ))
{
if( option != 1 )
COM_FatalError( "%s version %i should be 1\n", filename, option );
}
else if( !Q_strcmp( cmd, "nodes" ))
{
pmodel->numbones = Grab_Nodes( pmodel->localBone );
}
else if( !Q_strcmp( cmd, "skeleton" ))
{
Grab_Skeleton( pmodel );
}
else if( !Q_strcmp( cmd, "triangles" ))
{
Grab_Triangles( pmodel );
}
else
{
MsgDev( D_WARN, "unknown studio command\n" );
}
}
fclose( input );
}
void Grab_Animation( const char *name, s_animation_t *panim )
{
char cmd[1024];
int option;
Q_snprintf( filename, sizeof( filename ), "%s/%s", cddir[numdirs], name );
COM_DefaultExtension( filename, ".smd" );
if( !COM_FileExists( filename ))
COM_FatalError ("%s doesn't exist\n", filename);
if(( input = fopen( filename, "r" )) == 0 )
COM_FatalError( "%s couldn't be open\n", filename );
linecount = 0;
while( GetLineInput( ))
{
sscanf( line, "%s %d", cmd, &option );
if( !Q_strcmp( cmd, "version" ))
{
if( option != 1 )
COM_FatalError( "%s version %i should be 1\n", filename, option );
}
else if( !Q_strcmp( cmd, "nodes" ))
{
panim->numbones = Grab_Nodes( panim->localBone );
}
else if( !Q_strcmp( cmd, "skeleton" ))
{
if( !Grab_AnimFrames( panim ))
break; // animation was cutted
}
else
{
// some artists use mesh reference as default animation
if( Q_strcmp( cmd, "triangles" ))
MsgDev( D_WARN, "unknown studio command\n" );
while( GetLineInput( ))
{
if( IsEnd( line ))
break;
}
}
}
fclose( input );
}
void Cmd_Eyeposition( void )
{
// rotate points into frame of reference
// so model points down the positive x axis
GetToken( false );
eyeposition.y = verify_atof( token );
GetToken( false );
eyeposition.x = -verify_atof( token );
GetToken( false );
eyeposition.z = verify_atof( token );
}
void Cmd_Flags( void )
{
GetToken( false );
gflags = verify_atoi( token );
}
void Cmd_Modelname( void )
{
GetToken( false );
Q_strncpy( outname, token, sizeof( outname ));
}
void Option_Studio( s_model_t *pmodel )
{
if( !GetToken( false ))
return;
Q_strncpy( pmodel->name, token, sizeof( pmodel->name ));
pmodel->flip_triangles = flip_triangles;
pmodel->scale = g_defaultscale;
while( TokenAvailable( ))
{
GetToken( false );
if( !Q_stricmp( "reverse", token ))
{
pmodel->flip_triangles = 0;
}
else if( !Q_stricmp( "scale", token ))
{
GetToken( false );
pmodel->scale = verify_atof( token );
}
else if( !Q_stricmp( "faces", token ))
{
GetToken( false );
GetToken( false );
continue;
}
else if( !Q_stricmp( "bias", token ))
{
GetToken( false );
continue;
}
else if( !Q_stricmp( "{", token ))
{
UnGetToken( );
break;
}
else
{
COM_FatalError( "unknown command \"%s\"\n", token );
}
}
Grab_Studio( pmodel );
}
int Option_Blank( void )
{
g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t ));
g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
Q_strncpy( g_model[g_nummodels]->name, "blank", sizeof( g_model[0]->name ));
g_bodypart[g_numbodyparts].nummodels++;
g_nummodels++;
return 0;
}
void Cmd_Bodygroup( void )
{
int depth = 0;
if( !GetToken( false ))
return;
if( g_numbodyparts == 0 ) g_bodypart[g_numbodyparts].base = 1;
else g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels;
Q_strncpy( g_bodypart[g_numbodyparts].name, token, sizeof( g_bodypart[g_numbodyparts].name ));
do
{
GetToken( true );
if( endofscript )
{
return;
}
else if( token[0] == '{' )
{
depth++;
}
else if( token[0] == '}' )
{
depth--;
break;
}
else if( !Q_stricmp( "studio", token ))
{
g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t ));
g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
g_bodypart[g_numbodyparts].nummodels++;
Option_Studio( g_model[g_nummodels] );
g_nummodels++;
}
else if( !Q_stricmp( "blank", token ))
{
Option_Blank( );
}
else
{
COM_FatalError( "unknown bodygroup option: \"%s\"\n", token );
}
} while( 1 );
g_numbodyparts++;
}
void Cmd_Body( void )
{
if( !GetToken( false ))
return;
if( g_numbodyparts == 0 ) g_bodypart[g_numbodyparts].base = 1;
else g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels;
Q_strncpy(g_bodypart[g_numbodyparts].name, token, sizeof( g_bodypart[0].name ));
g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t ));
g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
g_bodypart[g_numbodyparts].nummodels = 1;
Option_Studio( g_model[g_nummodels] );
g_numbodyparts++;
g_nummodels++;
}
int Cmd_Model( void )
{
int depth = 0;
g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t ));
// name
if( !GetToken( false ))
return 0;
Q_strncpy( g_model[g_nummodels]->name, token, sizeof( g_model[0]->name ));
// fake bodypart stuff
if( g_numbodyparts == 0 ) g_bodypart[g_numbodyparts].base = 1;
else g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels;
Q_strncpy(g_bodypart[g_numbodyparts].name, token, sizeof( g_bodypart[0].name ));
g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
g_bodypart[g_numbodyparts].nummodels = 1;
g_numbodyparts++;
Option_Studio( g_model[g_nummodels] );
while( 1 )
{
if( depth > 0 )
{
if( !GetToken( true ))
{
break;
}
}
else
{
if( !TokenAvailable( ))
{
break;
}
else
{
GetToken( false );
}
}
if( endofscript )
{
if( depth != 0 )
COM_FatalError( "missing }\n" );
return 1;
}
if( !Q_stricmp( "{", token ))
{
depth++;
}
else if( !Q_stricmp( "}", token ))
{
depth--;
}
else
{
// NOTE: cmd $model has many various params
// that completely ignored in goldsource
// so we just use it as equal of $body command
// and skip all this unknown commands
while( TryToken( ));
}
if( depth < 0 )
COM_FatalError( "missing {\n" );
}
g_nummodels++;
return 0;
}
int Option_Activity( s_sequence_t *psequence )
{
int i;
GetToken( false );
if(( i = LookupActivity( token )) != 0 )
{
psequence->activity = i;
GetToken( false );
psequence->actweight = verify_atoi( token );
return 0;
}
return 1;
}
int Option_Event( s_sequence_t *psequence )
{
if(( psequence->numevents + 1 ) >= MAXSTUDIOEVENTS )
COM_FatalError( "too many events\n" );
GetToken( false );
psequence->event[psequence->numevents].event = verify_atoi( token );
GetToken( false );
psequence->event[psequence->numevents].frame = verify_atoi( token );
psequence->numevents++;
// option token
if( TokenAvailable( ))
{
GetToken( false );
if( token[0] == '}' ) // opps, hit the end
return 1;
// found an option
Q_strncpy( psequence->event[psequence->numevents-1].options, token, sizeof( psequence->event[0].options ));
}
return 0;
}
void Option_IKRule( s_ikrule_t *pRule )
{
int i;
// chain
GetToken( false );
for( i = 0; i < g_numikchains; i++)
{
if( !Q_stricmp( token, g_ikchain[i].name ))
break;
}
if( i >= g_numikchains )
TokenError( "unknown chain \"%s\" in ikrule\n", token );
pRule->chain = i;
// default slot
pRule->slot = i;
// type
GetToken( false );
if( !Q_stricmp( token, "touch" ))
{
pRule->type = IK_SELF;
// bone
GetToken( false );
Q_strncpy( pRule->bonename, token, sizeof( pRule->bonename ));
}
else if( !Q_stricmp( token, "footstep" ))
{
pRule->type = IK_GROUND;
pRule->height = g_ikchain[pRule->chain].height;
pRule->floor = g_ikchain[pRule->chain].floor;
pRule->radius = g_ikchain[pRule->chain].radius;
}
else if( !Q_stricmp( token, "attachment" ))
{
pRule->type = IK_ATTACHMENT;
// name of attachment
GetToken( false );
Q_strncpy( pRule->attachment, token, sizeof( pRule->attachment ));
}
else if( !Q_stricmp( token, "release" ))
{
pRule->type = IK_RELEASE;
}
else if( !Q_stricmp( token, "unlatch" ))
{
pRule->type = IK_UNLATCH;
}
pRule->contact = -1;
while( TokenAvailable( ))
{
GetToken( false );
if( !Q_stricmp( token, "height" ))
{
GetToken( false );
pRule->height = verify_atof( token );
}
else if( !Q_stricmp( token, "target" ))
{
// slot
GetToken( false );
pRule->slot = verify_atoi( token );
}
else if( !Q_stricmp( token, "range" ))
{
// ramp
GetToken( false );
if( token[0] == '.' ) pRule->start = -1;
else pRule->start = verify_atoi( token );
GetToken( false );
if( token[0] == '.' ) pRule->peak = -1;
else pRule->peak = verify_atoi( token );
GetToken( false );
if( token[0] == '.' ) pRule->tail = -1;
else pRule->tail = verify_atoi( token );
GetToken( false );
if( token[0] == '.' ) pRule->end = -1;
else pRule->end = verify_atoi( token );
}
else if( !Q_stricmp( token, "floor" ))
{
GetToken( false );
pRule->floor = verify_atof( token );
}
else if( !Q_stricmp( token, "pad" ))
{
GetToken( false );
pRule->radius = verify_atof( token ) / 2.0f;
}
else if( !Q_stricmp( token, "radius" ))
{
GetToken( false );
pRule->radius = verify_atof( token );
}
else if( !Q_stricmp( token, "contact" ))
{
GetToken( false );
pRule->contact = verify_atoi( token );
}
else if( !Q_stricmp( token, "usesequence" ))
{
pRule->usesequence = true;
pRule->usesource = false;
}
else if( !Q_stricmp( token, "usesource" ))
{
pRule->usesequence = false;
pRule->usesource = true;
}
else if( !Q_stricmp( token, "fakeorigin" ))
{
GetToken( false );
pRule->pos.x = verify_atof( token );
GetToken( false );
pRule->pos.y = verify_atof( token );
GetToken( false );
pRule->pos.z = verify_atof( token );
pRule->bone = -1;
}
else if( !Q_stricmp( token, "fakerotate" ))
{
Vector ang;
GetToken( false );
ang.x = verify_atof( token );
GetToken( false );
ang.y = verify_atof( token );
GetToken( false );
ang.z = verify_atof( token );
AngleQuaternion( ang, pRule->q );
pRule->bone = -1;
}
else if( !Q_stricmp( token, "bone" ))
{
GetToken( false );
Q_strncpy( pRule->bonename, token, sizeof( pRule->bonename ));
}
else
{
UnGetToken();
return;
}
}
}
//-----------------------------------------------------------------------------
// Key value block!
//-----------------------------------------------------------------------------
void Option_KeyValues( CUtlArray< char > *pKeyValue )
{
// Simply read in the block between { }s as text
// and plop it out unchanged into the .mdl file.
// Make sure to respect the fact that we may have nested {}s
int nLevel = 1;
if( !GetToken( true ))
return;
if( token[0] != '{' )
return;
AppendKeyValueText( pKeyValue, "mdlkeyvalue\n{\n" );
while( GetToken( true ))
{
if( !Q_stricmp( token, "}" ))
{
if( --nLevel <= 0 )
break;
AppendKeyValueText( pKeyValue, " }\n" );
}
else if( !Q_stricmp( token, "{" ))
{
AppendKeyValueText( pKeyValue, "{\n" );
nLevel++;
}
else
{
// tokens inside braces are quoted
if( nLevel > 1 )
{
AppendKeyValueText( pKeyValue, "\"" );
AppendKeyValueText( pKeyValue, token );
AppendKeyValueText( pKeyValue, "\" " );
}
else
{
AppendKeyValueText( pKeyValue, token );
AppendKeyValueText( pKeyValue, " " );
}
}
}
if( nLevel >= 1 )
{
TokenError( "Keyvalue block missing matching braces.\n" );
}
AppendKeyValueText( pKeyValue, "}\n" );
}
void Cmd_PoseParameter( void )
{
if( g_numposeparameters >= MAXSTUDIOPOSEPARAM )
TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM );
int i = LookupPoseParameter( token );
// name
GetToken( false );
Q_strncpy( g_pose[i].name, token, sizeof( g_pose[0].name ));
if( TokenAvailable( ))
{
// min
GetToken( false );
g_pose[i].min = verify_atof( token );
}
if( TokenAvailable( ))
{
// max
GetToken( false );
g_pose[i].max = verify_atof( token );
}
while( TokenAvailable( ))
{
GetToken( false );
if( !Q_stricmp( token, "wrap" ))
{
g_pose[i].flags |= STUDIO_LOOPING;
g_pose[i].loop = g_pose[i].max - g_pose[i].min;
}
else if( !Q_stricmp( token, "loop" ))
{
g_pose[i].flags |= STUDIO_LOOPING;
GetToken( false );
g_pose[i].loop = verify_atof( token );
}
}
}
/*
=================
Cmd_Origin
=================
*/
void Cmd_Origin( void )
{
GetToken( false );
g_defaultadjust.x = verify_atof (token);
GetToken( false );
g_defaultadjust.y = verify_atof (token);
GetToken( false );
g_defaultadjust.z = verify_atof (token);
if( TokenAvailable( ))
{
GetToken( false );
g_defaultrotation.z = DEG2RAD( verify_atof( token ) + 90.0f );
}
}
//-----------------------------------------------------------------------------
// Purpose: Set the default root rotation so that the Y axis is up instead of the Z axis (for Maya)
//-----------------------------------------------------------------------------
void Cmd_UpAxis( void )
{
// We want to create a rotation that rotates from the art space
// (specified by the up direction) to a z up space
// Note: x, -x, -y are untested
GetToken( false );
if( !Q_stricmp( token, "x" ))
{
// rotate 90 degrees around y to move x into z
g_defaultrotation.x = 0.0f;
g_defaultrotation.y = M_PI / 2.0f;
}
else if( !Q_stricmp( token, "-x" ))
{
// untested
g_defaultrotation.x = 0.0f;
g_defaultrotation.y = -M_PI / 2.0f;
}
else if( !Q_stricmp( token, "y" ))
{
// rotate 90 degrees around x to move y into z
g_defaultrotation.x = M_PI / 2.0f;
g_defaultrotation.y = 0.0f;
}
else if( !Q_stricmp( token, "-y" ))
{
// untested
g_defaultrotation.x = -M_PI / 2.0f;
g_defaultrotation.y = 0.0f;
}
else if( !Q_stricmp( token, "z" ))
{
// there's still a built in 90 degree Z rotation :(
g_defaultrotation.x = 0.0f;
g_defaultrotation.y = 0.0f;
}
else if( !Q_stricmp( token, "-z" ))
{
// there's still a built in 90 degree Z rotation :(
g_defaultrotation.x = 0.0f;
g_defaultrotation.y = 0.0f;
}
else
{
TokenError( "unknown $upaxis option: \"%s\"\n", token );
return;
}
}
void Cmd_ScaleUp( void )
{
GetToken( false );
g_defaultscale = verify_atof( token );
}
void Cmd_ScaleAxis( int axis )
{
GetToken( false );
g_defaultscale[axis] = verify_atof( token );
}
int Cmd_SequenceGroup( void )
{
GetToken( false );
Q_strncpy( g_sequencegroup[g_numseqgroups].label, token, sizeof( g_sequencegroup[0].label ));
g_numseqgroups++;
return 0;
}
int Cmd_SequenceGroupSize( void )
{
GetToken( false );
maxseqgroupsize = 1024 * verify_atoi( token );
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: parse order dependant s_animcmd_t token for $animations
//-----------------------------------------------------------------------------
int ParseCmdlistToken( int &numcmds, s_animcmd_t *cmds, int cmd_source )
{
if( numcmds >= MAXSTUDIOCMDS )
return false;
s_animcmd_t *pcmd = &cmds[numcmds];
pcmd->cmd_source = cmd_source;
if( !Q_stricmp( "fixuploop", token ))
{
pcmd->cmd = CMD_FIXUP;
GetToken( false );
pcmd->fixuploop.start = verify_atoi( token );
GetToken( false );
pcmd->fixuploop.end = verify_atoi( token );
}
else if( !Q_strnicmp( "weightlist", token, 6 ))
{
GetToken( false );
int i;
for( i = 1; i < g_numweightlist; i++ )
{
if( !Q_stricmp( g_weightlist[i].name, token ))
break;
}
if( i == g_numweightlist )
{
TokenError( "unknown weightlist '%s\'\n", token );
}
pcmd->cmd = CMD_WEIGHTS;
pcmd->weightlist.index = i;
}
else if( !Q_stricmp( "subtract", token ))
{
pcmd->cmd = CMD_SUBTRACT;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown subtract animation '%s\'\n", token );
}
pcmd->subtract.ref = extanim;
GetToken( false );
pcmd->subtract.frame = verify_atoi( token );
pcmd->subtract.flags |= STUDIO_POST;
}
else if( !Q_stricmp( "presubtract", token )) // FIXME: rename this to something better
{
pcmd->cmd = CMD_SUBTRACT;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown presubtract animation '%s\'\n", token );
}
pcmd->subtract.ref = extanim;
GetToken( false );
pcmd->subtract.frame = verify_atoi( token );
}
else if( !Q_stricmp( "alignto", token ))
{
pcmd->cmd = CMD_AO;
pcmd->ao.pBonename = NULL;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown alignto animation '%s\'\n", token );
}
pcmd->ao.ref = extanim;
pcmd->ao.motiontype = STUDIO_X | STUDIO_Y;
pcmd->ao.srcframe = 0;
pcmd->ao.destframe = 0;
}
else if( !Q_stricmp( "align", token ))
{
pcmd->cmd = CMD_AO;
pcmd->ao.pBonename = NULL;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if (extanim == NULL)
{
TokenError( "unknown align animation '%s\'\n", token );
}
pcmd->ao.ref = extanim;
// motion type to match
pcmd->ao.motiontype = 0;
GetToken( false );
int ctrl;
while(( ctrl = LookupControl( token )) != -1 )
{
pcmd->ao.motiontype |= ctrl;
GetToken( false );
}
if( pcmd->ao.motiontype == 0 )
{
TokenError( "missing controls on align\n" );
}
// frame of reference animation to match
pcmd->ao.srcframe = verify_atoi( token );
// against what frame of the current animation
GetToken( false );
pcmd->ao.destframe = verify_atoi( token );
}
else if( !Q_stricmp( "alignboneto", token ))
{
pcmd->cmd = CMD_AO;
GetToken( false );
pcmd->ao.pBonename = strdup( token );
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown alignboneto animation '%s\'\n", token );
}
pcmd->ao.ref = extanim;
pcmd->ao.motiontype = STUDIO_X | STUDIO_Y;
pcmd->ao.srcframe = 0;
pcmd->ao.destframe = 0;
}
else if( !Q_stricmp( "alignbone", token ))
{
pcmd->cmd = CMD_AO;
GetToken( false );
pcmd->ao.pBonename = strdup( token );
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown alignboneto animation '%s\'\n", token );
}
pcmd->ao.ref = extanim;
// motion type to match
pcmd->ao.motiontype = 0;
GetToken( false );
int ctrl;
while(( ctrl = LookupControl( token )) != -1 )
{
pcmd->ao.motiontype |= ctrl;
GetToken( false );
}
if( pcmd->ao.motiontype == 0 )
{
TokenError( "missing controls on align\n" );
}
// frame of reference animation to match
pcmd->ao.srcframe = verify_atoi( token );
// against what frame of the current animation
GetToken( false );
pcmd->ao.destframe = verify_atoi( token );
}
else if( !Q_stricmp( "match", token ))
{
pcmd->cmd = CMD_MATCH;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown match animation '%s\'\n", token );
}
pcmd->match.ref = extanim;
}
else if( !Q_stricmp( "matchblend", token ))
{
pcmd->cmd = CMD_MATCHBLEND;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown match animation '%s\'\n", token );
}
pcmd->match.ref = extanim;
// frame of reference animation to match
GetToken( false );
pcmd->match.srcframe = verify_atoi( token );
// against what frame of the current animation
GetToken( false );
pcmd->match.destframe = verify_atoi( token );
// backup and starting match in here
GetToken( false );
pcmd->match.destpre = verify_atoi( token );
// continue blending match till here
GetToken( false );
pcmd->match.destpost = verify_atoi( token );
}
else if( !Q_stricmp( "worldspaceblend", token ))
{
pcmd->cmd = CMD_WORLDSPACEBLEND;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown worldspaceblend animation '%s\'\n", token );
}
pcmd->world.ref = extanim;
pcmd->world.startframe = 0;
pcmd->world.loops = false;
}
else if( !Q_stricmp( "worldspaceblendloop", token ))
{
pcmd->cmd = CMD_WORLDSPACEBLEND;
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if (extanim == NULL)
{
TokenError( "unknown worldspaceblend animation '%s\'\n", token );
}
pcmd->world.ref = extanim;
GetToken( false );
pcmd->world.startframe = atoi( token );
pcmd->world.loops = true;
}
else if( !Q_stricmp( "rotateto", token ))
{
pcmd->cmd = CMD_ANGLE;
GetToken( false );
pcmd->angle.angle = verify_atof( token );
}
else if( !Q_stricmp( "ikrule", token ))
{
pcmd->cmd = CMD_IKRULE;
pcmd->ikrule.pRule = (s_ikrule_t *)Mem_Alloc( sizeof( s_ikrule_t ));
Option_IKRule( pcmd->ikrule.pRule );
}
else if( !Q_stricmp( "ikfixup", token ))
{
pcmd->cmd = CMD_IKFIXUP;
pcmd->ikfixup.pRule = (s_ikrule_t *)Mem_Alloc( sizeof( s_ikrule_t ));
Option_IKRule( pcmd->ikrule.pRule );
}
else if( !Q_stricmp( "walkframe", token ))
{
pcmd->cmd = CMD_MOTION;
// frame
GetToken( false );
pcmd->motion.iEndFrame = verify_atoi( token );
// motion type to match
pcmd->motion.motiontype = 0;
while( TokenAvailable( ))
{
GetToken( false );
int ctrl = LookupControl( token );
if( ctrl != -1 )
{
pcmd->motion.motiontype |= ctrl;
}
else
{
UnGetToken();
break;
}
}
}
else if( !Q_stricmp( "walkalignto", token ))
{
pcmd->cmd = CMD_REFMOTION;
GetToken( false );
pcmd->motion.iEndFrame = verify_atoi( token );
pcmd->motion.iSrcFrame = pcmd->motion.iEndFrame;
GetToken( false ); // reference animation
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown alignto animation '%s\'\n", token );
}
pcmd->motion.pRefAnim = extanim;
pcmd->motion.iRefFrame = 0;
// motion type to match
pcmd->motion.motiontype = 0;
while( TokenAvailable( ))
{
GetToken( false );
int ctrl = LookupControl( token );
if( ctrl != -1 )
{
pcmd->motion.motiontype |= ctrl;
}
else
{
UnGetToken();
break;
}
}
}
else if( !Q_stricmp( "walkalign", token ))
{
pcmd->cmd = CMD_REFMOTION;
// end frame to apply motion over
GetToken( false );
pcmd->motion.iEndFrame = verify_atoi( token );
// reference animation
GetToken( false );
s_animation_t *extanim = LookupAnimation( token );
if( extanim == NULL )
{
TokenError( "unknown alignto animation '%s\'\n", token );
}
pcmd->motion.pRefAnim = extanim;
// motion type to match
pcmd->motion.motiontype = 0;
while( TokenAvailable( ))
{
GetToken( false );
int ctrl = LookupControl( token );
if( ctrl != -1 )
{
pcmd->motion.motiontype |= ctrl;
}
else
{
break;
}
}
if( pcmd->motion.motiontype == 0 )
{
TokenError( "missing controls on walkalign\n" );
}
// frame of reference animation to match
pcmd->motion.iRefFrame = verify_atoi( token );
// against what frame of the current animation
GetToken( false );
pcmd->motion.iSrcFrame = verify_atoi( token );
}
else if( !Q_stricmp( "derivative", token ))
{
pcmd->cmd = CMD_DERIVATIVE;
// get scale
GetToken( false );
pcmd->derivative.scale = verify_atof( token );
}
else if( !Q_stricmp( "noanimation", token ))
{
pcmd->cmd = CMD_NOANIMATION;
}
else if( !Q_stricmp( "lineardelta", token ))
{
pcmd->cmd = CMD_LINEARDELTA;
pcmd->linear.flags |= STUDIO_AL_POST;
}
else if( !Q_stricmp( "splinedelta", token ))
{
pcmd->cmd = CMD_LINEARDELTA;
pcmd->linear.flags |= STUDIO_AL_POST;
pcmd->linear.flags |= STUDIO_AL_SPLINE;
}
else if( !Q_stricmp( "compress", token ))
{
pcmd->cmd = CMD_COMPRESS;
// get frames to skip
GetToken( false );
pcmd->compress.frames = verify_atoi( token );
}
else if( !Q_stricmp( "numframes", token ))
{
pcmd->cmd = CMD_NUMFRAMES;
// get frames to force
GetToken( false );
pcmd->compress.frames = verify_atoi( token );
}
else if( !Q_stricmp( "counterrotate", token ))
{
pcmd->cmd = CMD_COUNTERROTATE;
// get bone name
GetToken( false );
pcmd->counterrotate.pBonename = strdup( token );
}
else if( !Q_stricmp( "counterrotateto", token ))
{
pcmd->cmd = CMD_COUNTERROTATE;
pcmd->counterrotate.bHasTarget = true;
// get pitch
GetToken( false );
pcmd->counterrotate.targetAngle[0] = verify_atof( token );
// get yaw
GetToken( false );
pcmd->counterrotate.targetAngle[1] = verify_atof( token );
// get roll
GetToken( false );
pcmd->counterrotate.targetAngle[2] = verify_atof( token );
// get bone name
GetToken( false );
pcmd->counterrotate.pBonename = strdup( token );
}
else if( !Q_stricmp( "localhierarchy", token ))
{
pcmd->cmd = CMD_LOCALHIERARCHY;
// get bone name
GetToken( false );
pcmd->localhierarchy.pBonename = strdup( token );
// get parent name
GetToken( false );
pcmd->localhierarchy.pParentname = strdup( token );
pcmd->localhierarchy.start = -1;
pcmd->localhierarchy.peak = -1;
pcmd->localhierarchy.tail = -1;
pcmd->localhierarchy.end = -1;
if( TokenAvailable( ))
{
GetToken( false );
if( !Q_stricmp( token, "range" ))
{
GetToken( false );
pcmd->localhierarchy.start = verify_atof_with_null( token );
GetToken( false );
pcmd->localhierarchy.peak = verify_atof_with_null( token );
GetToken( false );
pcmd->localhierarchy.tail = verify_atof_with_null( token );
GetToken( false );
pcmd->localhierarchy.end = verify_atof_with_null( token );
}
else
{
UnGetToken();
}
}
MsgDev( D_WARN, "^1localhierarchy^7: command not supported\n" );
return false;
}
else
{
return false;
}
numcmds++;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: create named list of boneweights
//-----------------------------------------------------------------------------
void Option_Weightlist( s_weightlist_t *pweightlist )
{
int i, depth = 0;
pweightlist->numbones = 0;
while( 1 )
{
if( depth > 0 )
{
if( !GetToken( true ))
{
break;
}
}
else
{
if( !TokenAvailable( ))
{
break;
}
else
{
GetToken( false );
}
}
if( endofscript )
{
if( depth != 0 )
TokenError("missing }\n" );
return;
}
if( !Q_stricmp( "{", token ))
{
depth++;
}
else if( !Q_stricmp( "}", token ))
{
depth--;
}
else if( !Q_stricmp( "posweight", token ))
{
i = pweightlist->numbones - 1;
if( i < 0 )
{
TokenError( "Error with specifing bone Position weight \'%s:%s\'\n",
pweightlist->name, pweightlist->bonename[i] );
}
GetToken( false );
pweightlist->boneposweight[i] = verify_atof( token );
if( pweightlist->boneweight[i] == 0 && pweightlist->boneposweight[i] > 0 )
{
TokenError( "Non-zero Position weight with zero Rotation weight not allowed \'%s:%s %f %f\'\n",
pweightlist->name, pweightlist->bonename[i], pweightlist->boneweight[i], pweightlist->boneposweight[i] );
}
}
else
{
i = pweightlist->numbones++;
if( i >= MAXWEIGHTSPERLIST )
{
TokenError( "Too many bones (%d) in weightlist '%s'\n", i, pweightlist->name );
}
pweightlist->bonename[i] = strdup( token );
GetToken( false );
pweightlist->boneweight[i] = verify_atof( token );
pweightlist->boneposweight[i] = pweightlist->boneweight[i];
}
if( depth < 0 )
{
TokenError( "missing {\n" );
}
}
}
int ParseEmpty( void )
{
int depth = 0;
while( 1 )
{
if( depth > 0 )
{
if( !GetToken( true ))
{
break;
}
}
else
{
if( !TokenAvailable( ))
{
break;
}
else
{
GetToken( false );
}
}
if( endofscript )
{
if( depth != 0 )
{
TokenError( "missing }\n" );
}
return 1;
}
if( !Q_stricmp( "{", token ))
{
depth++;
}
else if( !Q_stricmp( "}", token ))
{
depth--;
}
if( depth < 0 )
{
TokenError( "missing {\n" );
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: parse order independant s_animation_t token for $animations
//-----------------------------------------------------------------------------
bool ParseAnimationToken( s_animation_t *panim )
{
int i;
if( !Q_stricmp( "if", token ))
{
// fixme: add expression evaluation
GetToken( false );
if( atoi( token ) == 0 && Q_stricmp( token, "true" ))
{
GetToken( true );
if( token[0] == '{' )
{
int depth = 1;
while( TokenAvailable( ) && depth > 0 )
{
GetToken( true );
if( !Q_stricmp( "{", token ))
{
depth++;
}
else if( !Q_stricmp( "}", token ))
{
depth--;
}
}
}
}
return true;
}
if( !Q_stricmp( "fps", token ))
{
GetToken( false );
panim->fps = verify_atof( token );
if( panim->fps <= 0.0f )
{
TokenError( "ParseAnimationToken: fps (%f from '%s') <= 0.0\n", panim->fps, token );
}
return true;
}
if( !Q_stricmp( "origin", token ))
{
GetToken( false );
panim->adjust.x = verify_atof( token );
GetToken( false );
panim->adjust.y = verify_atof( token );
GetToken( false );
panim->adjust.z = verify_atof( token );
return true;
}
if( !Q_stricmp( "rotate", token ))
{
GetToken( false );
// FIXME: broken for Maya
panim->rotation.z = DEG2RAD( verify_atof( token ) + 90.0f );
return true;
}
if( !Q_stricmp( "angles", token ))
{
GetToken( false );
panim->rotation.x = DEG2RAD( verify_atof( token ));
GetToken( false );
panim->rotation.y = DEG2RAD( verify_atof( token ));
GetToken( false );
panim->rotation.z = DEG2RAD( verify_atof( token ) + 90.0f );
return true;
}
if( !Q_stricmp( "scale", token ))
{
GetToken( false );
panim->scale = verify_atof( token );
return true;
}
if( !Q_strnicmp( "loop", token, 4 ))
{
panim->flags |= STUDIO_LOOPING;
return true;
}
if( !Q_strnicmp( "rootlight", token, 4 ))
{
panim->flags |= STUDIO_LIGHT_FROM_ROOT;
return true;
}
if( !Q_strnicmp( "startloop", token, 5 ))
{
GetToken( false );
panim->looprestart = verify_atoi( token );
panim->flags |= STUDIO_LOOPING;
return true;
}
if( !Q_stricmp( "fudgeloop", token ))
{
panim->fudgeloop = true;
panim->flags |= STUDIO_LOOPING;
return true;
}
if( !Q_strnicmp( "snap", token, 4 ))
{
panim->flags |= STUDIO_SNAP;
return true;
}
if( !Q_strnicmp( "frame", token, 5 ))
{
GetToken( false );
panim->startframe = verify_atoi( token );
GetToken( false );
panim->endframe = verify_atoi( token );
if( panim->startframe < panim->source.startframe )
panim->startframe = panim->source.startframe;
if( panim->endframe > panim->source.endframe )
panim->endframe = panim->source.endframe;
if( panim->endframe < panim->startframe )
TokenError( "end frame before start frame in %s\n", panim->name );
panim->numframes = panim->endframe - panim->startframe + 1;
return true;
}
if( !Q_stricmp( "blockname", token ))
{
GetToken( false );
return true;
}
if( !Q_stricmp( "post", token ))
{
panim->flags |= STUDIO_POST;
return true;
}
if( !Q_stricmp( "noautoik", token ))
{
panim->noAutoIK = true;
return true;
}
if( !Q_stricmp( "autoik", token ))
{
panim->noAutoIK = false;
return true;
}
if( ParseCmdlistToken( panim->numcmds, panim->cmds, CMDSRC_LOCAL ))
return true;
if( !Q_stricmp( "cmdlist", token ))
{
GetToken( false ); // A
for( i = 0; i < g_numcmdlists; i++ )
{
if( !Q_stricmp( g_cmdlist[i].name, token ))
break;
}
if( i == g_numcmdlists )
TokenError( "unknown cmdlist %s\n", token );
for( int j = 0; j < g_cmdlist[i].numcmds; j++ )
{
if( panim->numcmds >= MAXSTUDIOCMDS )
TokenError( "Too many cmds in %s\n", panim->name );
panim->cmds[panim->numcmds++] = g_cmdlist[i].cmds[j];
}
return true;
}
if( !Q_stricmp( "motionrollback", token ))
{
GetToken( false );
panim->motionrollback = atof( token );
return true;
}
if( !Q_stricmp( "noanimblock", token ))
return true;
if( !Q_stricmp( "noanimblockstall", token ))
return true;
if( LookupControl( token ) != -1 )
{
panim->motiontype |= LookupControl( token );
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: create named order dependant s_animcmd_t blocks, used as replicated token list for $animations
//-----------------------------------------------------------------------------
void Cmd_Cmdlist( void )
{
int depth = 0;
// name
GetToken( false );
strncpy( g_cmdlist[g_numcmdlists].name, token, sizeof( g_cmdlist[0].name ));
while( 1 )
{
if( depth > 0 )
{
if( !GetToken( true ))
{
break;
}
}
else
{
if( !TokenAvailable( ))
{
break;
}
else
{
GetToken( false );
}
}
if( endofscript )
{
if( depth != 0 )
TokenError( "missing }\n" );
return;
}
if( !Q_stricmp( "{", token ))
{
depth++;
}
else if( !Q_stricmp( "}", token ))
{
depth--;
}
else if( !ParseCmdlistToken( g_cmdlist[g_numcmdlists].numcmds, g_cmdlist[g_numcmdlists].cmds, CMDSRC_GLOBAL ))
{
TokenError( "unknown command: %s\n", token );
}
if( depth < 0 )
{
TokenError( "missing {\n" );
}
}
g_numcmdlists++;
}
void Cmd_Weightlist( void )
{
int i;
if( !GetToken( false ))
return;
if( g_numweightlist >= MAXWEIGHTLISTS )
TokenError( "Too many weightlist commands (%d)\n", MAXWEIGHTLISTS );
for( i = 1; i < g_numweightlist; i++ )
{
if( !Q_stricmp( g_weightlist[i].name, token ))
{
TokenError( "Duplicate weightlist '%s'\n", token );
}
}
Q_strncpy( g_weightlist[i].name, token, sizeof( g_weightlist[i].name ));
Option_Weightlist( &g_weightlist[g_numweightlist] );
g_numweightlist++;
}
void Cmd_DefaultWeightlist( void )
{
Option_Weightlist( &g_weightlist[0] );
}
//-----------------------------------------------------------------------------
// Purpose: wrapper for parsing $animation tokens
//-----------------------------------------------------------------------------
int ParseAnimation( s_animation_t *panim )
{
int depth = 0;
while( 1 )
{
if( depth > 0 )
{
if( !GetToken( true ))
{
break;
}
}
else
{
if( !TokenAvailable( ))
{
break;
}
else
{
GetToken( false );
}
}
if( endofscript )
{
if( depth != 0 )
TokenError( "missing }\n" );
return 1;
}
if( !Q_stricmp( "{", token ))
{
depth++;
}
else if( !Q_stricmp( "}", token ))
{
depth--;
}
else if( !ParseAnimationToken( panim ))
{
TokenError( "Unknown animation option\'%s\'\n", token );
}
if( depth < 0 )
{
TokenError( "missing {\n" );
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose: allocate an entry for $animation
//-----------------------------------------------------------------------------
void Cmd_Animation( void )
{
// name
GetToken( false );
s_animation_t *panim = LookupAnimation( token );
if( panim != NULL )
TokenError( "Duplicate animation name \"%s\"\n", token );
// allocate animation entry
g_panimation[g_numani] = panim = (s_animation_t *)Mem_Alloc( sizeof( s_animation_t ));
Q_strncpy( panim->name, token, sizeof( panim->name ));
g_numani++;
// filename
GetToken( false );
Q_strncpy( panim->filename, token, sizeof( panim->filename ));
// grab animation frames
Grab_Animation( panim->filename, panim );
panim->startframe = panim->source.startframe;
panim->endframe = panim->source.endframe;
panim->adjust = g_defaultadjust;
panim->rotation = g_defaultrotation;
panim->scale = g_defaultscale;
panim->motionrollback = 0.3f;
panim->fps = 30.0f;
ParseAnimation( panim );
panim->numframes = panim->endframe - panim->startframe + 1;
MsgDev( D_INFO, "grabbing: %s.smd\t\t[^2anim^7][%d frames]\n", panim->name, panim->numframes );
// post-apply scale to the frames
scale_animation( panim );
}
//-----------------------------------------------------------------------------
// Purpose: create a virtual $animation command from a $sequence reference
//-----------------------------------------------------------------------------
s_animation_t *Cmd_ImpliedAnimation( s_sequence_t *psequence, const char *filename )
{
s_animation_t *panim;
// allocate animation entry
g_panimation[g_numani] = panim = (s_animation_t *)Mem_Alloc( sizeof( s_animation_t ));
Q_snprintf( panim->name, sizeof( panim->name ), "@%s", psequence->name );
g_numani++;
Q_strncpy( panim->filename, filename, sizeof( panim->filename ));
panim->startframe = 0;
panim->endframe = MAXSTUDIOANIMATIONS - 1;
panim->isImplied = true;
// grab animation frames
Grab_Animation( panim->filename, panim );
panim->adjust = g_defaultadjust;
panim->rotation = g_defaultrotation;
panim->scale = g_defaultscale;
panim->motionrollback = 0.3f;
panim->fps = 30.0f;
if( panim->startframe < panim->source.startframe )
panim->startframe = panim->source.startframe;
if( panim->endframe > panim->source.endframe )
panim->endframe = panim->source.endframe;
if( panim->endframe < panim->startframe )
TokenError( "end frame before start frame in %s", panim->name );
panim->numframes = panim->endframe - panim->startframe + 1;
MsgDev( D_INFO, "grabbing: %s.smd\t\t[^2anim^7][%d frames]\n", panim->name, panim->numframes );
// post-apply scale to the frames
scale_animation( panim );
return panim;
}
//-----------------------------------------------------------------------------
// Purpose: copy globally reavent $animation options from one $animation to another
//-----------------------------------------------------------------------------
void CopyAnimationSettings( s_animation_t *pdest, s_animation_t *psrc )
{
pdest->fps = psrc->fps;
pdest->adjust = psrc->adjust;
pdest->rotation = psrc->rotation;
pdest->scale = psrc->scale;
pdest->motiontype = psrc->motiontype;
for( int i = 0; i < psrc->numcmds; i++ )
{
if( pdest->numcmds >= MAXSTUDIOCMDS )
TokenError("Too many cmds in %s\n", pdest->name );
pdest->cmds[pdest->numcmds++] = psrc->cmds[i];
}
}
//-----------------------------------------------------------------------------
// Purpose: allocate an entry for $sequence
//-----------------------------------------------------------------------------
s_sequence_t *ProcessCmdSequence( const char *pSequenceName )
{
s_animation_t *panim = LookupAnimation( pSequenceName );
// allocate sequence
if( panim != NULL )
TokenError( "Duplicate sequence name \"%s\"\n", pSequenceName );
if( g_numseq >= MAXSTUDIOSEQUENCES )
TokenError( "Too many sequences (%d max)\n", MAXSTUDIOSEQUENCES );
s_sequence_t *pseq = &g_sequence[g_numseq];
// initialize sequence
Q_strncpy( pseq->name, pSequenceName, sizeof( pseq->name ));
pseq->seqgroup = g_numseqgroups - 1;
pseq->paramindex[0] = -1;
pseq->paramindex[1] = -1;
pseq->groupsize[0] = 0;
pseq->groupsize[1] = 0;
pseq->fadeintime = 0.2f;
pseq->fadeouttime = 0.2f;
pseq->fps = 30.0f;
pseq->flags = 0;
g_numseq++;
return pseq;
}
//-----------------------------------------------------------------------------
// Performs processing on a sequence
//-----------------------------------------------------------------------------
void ProcessSequence( s_sequence_t *pseq, int numblends, s_animation_t **animations, bool isAppend )
{
if( isAppend ) return;
if( numblends == 0 )
TokenError( "no animations found\n");
if( pseq->groupsize[0] == 0 )
{
// GoldSrc 9-way blending never filled second group
if( numblends < 4 || !FBitSet( pseq->flags, STUDIO_BLENDPOSE ))
{
pseq->groupsize[0] = numblends;
pseq->groupsize[1] = 1;
}
else
{
int i = sqrt( (float)numblends );
if(( i * i ) == numblends )
{
pseq->groupsize[0] = i;
pseq->groupsize[1] = i;
}
else
{
TokenError( "non-square (%d) number of blends without \"blendwidth\" set\n", numblends );
}
}
}
else
{
pseq->groupsize[1] = numblends / pseq->groupsize[0];
if( pseq->groupsize[0] * pseq->groupsize[1] != numblends )
TokenError( "missing animation blends. Expected %d, found %d\n", pseq->groupsize[0] * pseq->groupsize[1], numblends );
}
if( numblends == 0 )
COM_FatalError( "no animations found\n" );
for( int i = 0; i < numblends; i++ )
{
int j = i % pseq->groupsize[0];
int k = i / pseq->groupsize[0];
#if 0
// remap 2D array into 1D array
Msg( "i %i, group[%i x %i] = %i\n", i, j, k, j + pseq->groupsize[0] * k );
pseq->panim[j + pseq->groupsize[0] * k] = animations[i];
#else
pseq->panim[i] = animations[i];
#endif
if( i > 0 && animations[i]->isImplied )
CopyAnimationSettings( animations[i], animations[0] );
animations[i]->isImplied = false; // don't copy any more commands
pseq->motiontype |= animations[i]->motiontype;
pseq->flags |= animations[i]->flags;
}
// g-cont. backward compatibility
pseq->numframes = animations[0]->numframes;
pseq->fps = animations[0]->fps;
pseq->numblends = numblends;
}
//-----------------------------------------------------------------------------
// Purpose: parse options unique to $sequence
//-----------------------------------------------------------------------------
int ParseSequence( s_sequence_t *pseq, bool isAppend )
{
s_animation_t *animations[MAXSTUDIOBLENDS];
int i, j, n, depth = 0;
int numblends = 0;
// initialize first anim
if( isAppend ) animations[0] = pseq->panim[0];
while( 1 )
{
if( depth > 0 )
{
if( !GetToken( true ))
{
break;
}
}
else
{
if( !TokenAvailable( ))
{
break;
}
else
{
GetToken( false );
}
}
if( endofscript )
{
if( depth != 0 )
COM_FatalError( "missing }\n" );
return 1;
}
if( !Q_stricmp( "{", token ))
{
depth++;
}
else if( !Q_stricmp( "}", token ))
{
depth--;
}
else if( !Q_stricmp( "event", token ))
{
depth -= Option_Event( pseq );
}
else if( !Q_stricmp( "activity", token ))
{
Option_Activity( pseq );
}
else if( !Q_strnicmp( token, "ACT_", 4 ))
{
UnGetToken( );
Option_Activity( pseq );
}
else if( !Q_stricmp( "snap", token ))
{
pseq->flags |= STUDIO_SNAP;
}
else if( !Q_stricmp( "blendwidth", token ))
{
GetToken( false );
pseq->groupsize[0] = verify_atoi( token );
}
else if( !Q_stricmp( "blend", token ))
{
i = ( pseq->paramindex[0] != -1 ) ? 1 : 0;
GetToken( false );
// GoldSource style blending
if(( j = LookupControl( token )) == -1 )
{
// Source-style blending (pose parameters)
j = LookupPoseParameter( token );
SetBits( pseq->flags, STUDIO_BLENDPOSE );
}
pseq->paramindex[i] = j;
pseq->paramattachment[i] = -1;
GetToken( false );
pseq->paramstart[i] = verify_atof( token );
GetToken( false );
pseq->paramend[i] = verify_atof( token );
g_pose[j].min = Q_min( g_pose[j].min, pseq->paramstart[i] );
g_pose[j].min = Q_min( g_pose[j].min, pseq->paramend[i] );
g_pose[j].max = Q_max( g_pose[j].max, pseq->paramstart[i] );
g_pose[j].max = Q_max( g_pose[j].max, pseq->paramend[i] );
}
else if( !Q_stricmp( "calcblend", token ))
{
i = ( pseq->paramindex[0] != -1 ) ? 1 : 0;
GetToken( false );
j = LookupPoseParameter( token );
pseq->paramindex[i] = j;
GetToken( false );
pseq->paramattachment[i] = LookupAttachment( token );
if( pseq->paramattachment[i] == -1 )
{
TokenError( "Unknown calcblend attachment \"%s\"\n", token );
}
GetToken( false );
pseq->paramcontrol[i] = LookupControl( token );
SetBits( pseq->flags, STUDIO_BLENDPOSE );
}
else if( !Q_stricmp( "blendref", token ))
{
GetToken( false );
pseq->paramanim = LookupAnimation( token );
if( pseq->paramanim == NULL )
{
TokenError( "Unknown blendref animation \"%s\"\n", token );
}
}
else if( !Q_stricmp( "blendcomp", token ))
{
GetToken( false );
pseq->paramcompanim = LookupAnimation( token );
if( pseq->paramcompanim == NULL )
{
TokenError( "Unknown blendcomp animation \"%s\"\n", token );
}
}
else if( !Q_stricmp( "blendcenter", token ))
{
GetToken( false );
pseq->paramcenter = LookupAnimation( token );
if( pseq->paramcenter == NULL )
{
TokenError( "Unknown blendcenter animation \"%s\"\n", token );
}
}
else if( !Q_strnicmp( "node", token, 4 ))
{
GetToken( false );
pseq->entrynode = pseq->exitnode = LookupXNode( token );
}
else if( !Q_stricmp( "transition", token ))
{
GetToken( false );
pseq->entrynode = LookupXNode( token );
GetToken( false );
pseq->exitnode = LookupXNode( token );
}
else if( !Q_stricmp( "rtransition", token ))
{
GetToken( false );
pseq->entrynode = LookupXNode( token );
GetToken( false );
pseq->exitnode = LookupXNode( token );
pseq->nodeflags |= 1;
}
else if( !Q_stricmp( "exitphase", token ))
{
GetToken( false );
}
else if( !Q_stricmp( "delta", token ))
{
pseq->flags |= STUDIO_DELTA;
pseq->flags |= STUDIO_POST;
}
else if( !Q_stricmp( "worldspace", token ))
{
pseq->flags |= STUDIO_WORLD;
pseq->flags |= STUDIO_POST;
}
else if( !Q_stricmp( "post", token )) // remove
{
pseq->flags |= STUDIO_POST;
}
else if( !Q_stricmp( "predelta", token ))
{
pseq->flags |= STUDIO_DELTA;
}
else if( !Q_stricmp( "autoplay", token ))
{
pseq->flags |= STUDIO_AUTOPLAY;
}
else if( !Q_stricmp( "fadein", token ))
{
GetToken( false );
pseq->fadeintime = verify_atof( token );
}
else if( !Q_stricmp( "fadeout", token ))
{
GetToken( false );
pseq->fadeouttime = verify_atof( token );
}
else if( !Q_stricmp( "realtime", token ))
{
pseq->flags |= STUDIO_REALTIME;
}
else if( !Q_stricmp( "posecycle", token ))
{
pseq->flags |= STUDIO_CYCLEPOSE;
GetToken( false );
pseq->cycleposeindex = LookupPoseParameter( token );
}
else if( !Q_stricmp( "hidden", token ))
{
pseq->flags |= STUDIO_HIDDEN;
}
else if( !Q_stricmp( "addlayer", token ))
{
GetToken( false );
Q_strncpy( pseq->autolayer[pseq->numautolayers].name, token, sizeof( pseq->autolayer[0].name ));
pseq->numautolayers++;
}
else if( !Q_stricmp( "iklock", token ))
{
GetToken( false );
Q_strncpy( pseq->iklock[pseq->numiklocks].name, token, sizeof( pseq->iklock[0].name ));
GetToken( false );
pseq->iklock[pseq->numiklocks].flPosWeight = verify_atof( token );
GetToken( false );
pseq->iklock[pseq->numiklocks].flLocalQWeight = verify_atof( token );
pseq->numiklocks++;
}
else if( !Q_stricmp( "keyvalues", token ))
{
Option_KeyValues( &pseq->KeyValue );
}
else if( !Q_stricmp( "blendlayer", token ))
{
pseq->autolayer[pseq->numautolayers].flags = 0;
GetToken( false );
Q_strncpy( pseq->autolayer[pseq->numautolayers].name, token, sizeof( pseq->autolayer[0].name ));
GetToken( false );
pseq->autolayer[pseq->numautolayers].start = verify_atof( token );
GetToken( false );
pseq->autolayer[pseq->numautolayers].peak = verify_atof( token );
GetToken( false );
pseq->autolayer[pseq->numautolayers].tail = verify_atof( token );
GetToken( false );
pseq->autolayer[pseq->numautolayers].end = verify_atof( token );
while( TokenAvailable( ))
{
GetToken( false );
if( !Q_stricmp( "xfade", token ))
{
pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_XFADE;
}
else if( !Q_stricmp( "spline", token ))
{
pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_SPLINE;
}
else if( !Q_stricmp( "noblend", token ))
{
pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_NOBLEND;
}
else if( !Q_stricmp( "poseparameter", token ))
{
pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_POSE;
GetToken( false );
pseq->autolayer[pseq->numautolayers].pose = LookupPoseParameter( token );
}
else if( !Q_stricmp( "local", token ))
{
pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_LOCAL;
pseq->flags |= STUDIO_LOCAL;
}
else
{
UnGetToken();
break;
}
}
pseq->numautolayers++;
}
else if(( numblends || isAppend ) && ParseAnimationToken( animations[0] ))
{
}
else if( !isAppend )
{
// assume it's an animation reference
// first look up an existing animation
for( n = 0; n < g_numani; n++ )
{
if( !Q_stricmp( token, g_panimation[n]->name ))
{
animations[numblends++] = g_panimation[n];
break;
}
}
if( n >= g_numani )
{
// assume it's an implied animation
animations[numblends++] = Cmd_ImpliedAnimation( pseq, token );
}
// hack to allow animation commands to refer to same sequence
if( numblends == 1 )
{
pseq->panim[0] = animations[0];
}
}
else
{
TokenError( "unknown command \"%s\"\n", token );
}
if( depth < 0 )
{
TokenError( "missing {\n" );
}
}
ProcessSequence( pseq, numblends, animations, isAppend );
return 0;
}
//-----------------------------------------------------------------------------
// Process the sequence command
//-----------------------------------------------------------------------------
void Cmd_Sequence( void )
{
if( !GetToken( false ))
return;
s_sequence_t *pseq = ProcessCmdSequence( token );
if( pseq ) ParseSequence( pseq, false );
}
//-----------------------------------------------------------------------------
// Purpose: append commands to either a sequence or an animation
//-----------------------------------------------------------------------------
void Cmd_Append( void )
{
GetToken( false );
s_sequence_t *pseq = LookupSequence( token );
if( pseq )
{
ParseSequence( pseq, true );
return;
}
s_animation_t *panim = LookupAnimation( token );
if( panim )
{
ParseAnimation( panim );
return;
}
TokenError( "unknown append animation %s\n", token );
}
void Cmd_Prepend( void )
{
GetToken( false );
s_sequence_t *pseq = LookupSequence( token );
s_animation_t *panim = NULL;
int iRet = false;
int count = 0;
if( pseq )
{
panim = pseq->panim[0];
count = panim->numcmds;
iRet = ParseSequence( pseq, true );
}
else
{
panim = LookupAnimation( token );
if( panim )
{
count = panim->numcmds;
iRet = ParseAnimation( panim );
}
}
if( panim && count != panim->numcmds )
{
s_animcmd_t tmp;
tmp = panim->cmds[panim->numcmds - 1];
for( int i = panim->numcmds - 1; i > 0; i-- )
panim->cmds[i] = panim->cmds[i-1];
panim->cmds[0] = tmp;
return;
}
TokenError( "unknown prepend animation \"%s\"\n", token );
}
void Cmd_Continue( void )
{
GetToken( false );
s_sequence_t *pseq = LookupSequence( token );
if( pseq )
{
GetToken( true );
UnGetToken();
if( token[0] != '$' )
ParseSequence( pseq, true );
return;
}
s_animation_t *panim = LookupAnimation( token );
if( panim )
{
GetToken( true );
UnGetToken();
if( token[0] != '$' )
ParseAnimation( panim );
return;
}
TokenError( "unknown continue animation %s\n", token );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Cmd_IKChain( void )
{
if( !GetToken( false ))
return;
int i;
for( i = 0; i < g_numikchains; i++ )
{
if( !Q_stricmp( token, g_ikchain[i].name ))
break;
}
if( i < g_numikchains )
{
MsgDev( D_WARN, "duplicate ikchain \"%s\" ignored\n", token );
while( TryToken( ));
return;
}
Q_strncpy( g_ikchain[g_numikchains].name, token, sizeof( g_ikchain[0].name ));
GetToken( false );
Q_strncpy( g_ikchain[g_numikchains].bonename, token, sizeof( g_ikchain[0].bonename ));
g_ikchain[g_numikchains].axis = STUDIO_Z;
g_ikchain[g_numikchains].value = 0.0;
g_ikchain[g_numikchains].height = 18.0; // sv_stepheight
g_ikchain[g_numikchains].floor = 0.0;
g_ikchain[g_numikchains].radius = 0.0;
while( TokenAvailable( ))
{
GetToken( false );
if( LookupControl( token ) != -1 )
{
g_ikchain[g_numikchains].axis = LookupControl( token );
GetToken( false );
g_ikchain[g_numikchains].value = verify_atof( token );
}
else if( !Q_stricmp( "height", token ))
{
GetToken( false );
g_ikchain[g_numikchains].height = verify_atof( token );
}
else if( !Q_stricmp( "pad", token ))
{
GetToken( false );
g_ikchain[g_numikchains].radius = verify_atof( token ) / 2.0;
}
else if( !Q_stricmp( "floor", token ))
{
GetToken( false );
g_ikchain[g_numikchains].floor = verify_atof( token );
}
else if( !Q_stricmp( "knee", token ))
{
GetToken( false );
g_ikchain[g_numikchains].link[0].kneeDir.x = verify_atof( token );
GetToken( false );
g_ikchain[g_numikchains].link[0].kneeDir.y = verify_atof( token );
GetToken( false );
g_ikchain[g_numikchains].link[0].kneeDir.z = verify_atof( token );
}
else if( !Q_stricmp( "center", token ))
{
GetToken( false );
g_ikchain[g_numikchains].center.x = verify_atof( token );
GetToken( false );
g_ikchain[g_numikchains].center.y = verify_atof( token );
GetToken( false );
g_ikchain[g_numikchains].center.z = verify_atof( token );
}
}
g_numikchains++;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Cmd_IKAutoplayLock( void )
{
GetToken( false );
Q_strncpy( g_ikautoplaylock[g_numikautoplaylocks].name, token, sizeof( g_ikautoplaylock[0].name ));
GetToken( false );
g_ikautoplaylock[g_numikautoplaylocks].flPosWeight = verify_atof( token );
GetToken( false );
g_ikautoplaylock[g_numikautoplaylocks].flLocalQWeight = verify_atof( token );
g_numikautoplaylocks++;
}
int Cmd_Root( void )
{
if( GetToken( false ))
{
Q_strncpy( rootname, token, sizeof( rootname ));
return 0;
}
return 1;
}
int Cmd_Controller( void )
{
if( !GetToken( false ))
return 0;
if( g_numbonecontrollers >= MAXSTUDIOCONTROLLERS )
COM_FatalError( "too many bone controllers (max %d)\n", MAXSTUDIOCONTROLLERS );
if( !Q_stricmp( "mouth", token ))
g_bonecontroller[g_numbonecontrollers].index = STUDIO_MOUTH;
else g_bonecontroller[g_numbonecontrollers].index = verify_atoi( token );
if( g_bonecontroller[g_numbonecontrollers].index < 0 || g_bonecontroller[g_numbonecontrollers].index > STUDIO_MOUTH )
COM_FatalError( "$controller: invalid input index %d (allowed in range 0-4)\n", g_bonecontroller[g_numbonecontrollers].index );
if( GetToken( false ))
{
Q_strncpy( g_bonecontroller[g_numbonecontrollers].name, token, sizeof( g_bonecontroller[0].name ));
GetToken( false );
if(( g_bonecontroller[g_numbonecontrollers].type = LookupControl( token )) == -1 )
{
MsgDev( D_WARN, "unknown bonecontroller type '%s'\n", token );
return 0;
}
GetToken( false );
g_bonecontroller[g_numbonecontrollers].start = verify_atof( token );
GetToken( false );
g_bonecontroller[g_numbonecontrollers].end = verify_atof( token );
float start = g_bonecontroller[g_numbonecontrollers].start;
float end = g_bonecontroller[g_numbonecontrollers].end;
if( g_bonecontroller[g_numbonecontrollers].type & ( STUDIO_XR|STUDIO_YR|STUDIO_ZR ))
{
if(((int)( start + 360 ) % 360 ) == ((int)( end + 360 ) % 360 ))
g_bonecontroller[g_numbonecontrollers].type |= STUDIO_RLOOP;
}
g_numbonecontrollers++;
}
return 1;
}
void Cmd_ScreenAlign( void )
{
if( GetToken( false ))
{
Q_strncpy( g_screenalignedbone[g_numscreenalignedbones].name, token, sizeof( g_screenalignedbone[0].name ));
g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_SPHERE;
if( GetToken( false ) )
{
if( !Q_stricmp( "sphere", token ))
{
g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_SPHERE;
}
else if( !stricmp( "cylinder", token ))
{
g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_CYLINDER;
}
}
g_numscreenalignedbones++;
}
else
{
COM_FatalError( "$screenalign: expected bone name\n" );
}
}
void Cmd_BBox( void )
{
GetToken( false );
bbox[0][0] = verify_atof( token );
GetToken( false );
bbox[0][1] = verify_atof( token );
GetToken( false );
bbox[0][2] = verify_atof( token );
GetToken( false );
bbox[1][0] = verify_atof( token );
GetToken( false );
bbox[1][1] = verify_atof( token );
GetToken( false );
bbox[1][2] = verify_atof( token );
g_wrotebbox = true;
}
void Cmd_CBox( void )
{
GetToken( false );
cbox[0][0] = verify_atof( token );
GetToken( false );
cbox[0][1] = verify_atof( token );
GetToken( false );
cbox[0][2] = verify_atof( token );
GetToken( false );
cbox[1][0] = verify_atof( token );
GetToken( false );
cbox[1][1] = verify_atof( token );
GetToken( false );
cbox[1][2] = verify_atof( token );
g_wrotecbox = true;
}
void Cmd_Gamma( void )
{
GetToken( false );
g_gamma = verify_atof( token );
}
int Cmd_TextureGroup( void )
{
int depth = 0;
int index = 0;
int group = 0;
if( g_numtextures == 0 )
COM_FatalError( "texturegroups must follow model loading\n" );
if( !GetToken( false ))
return 0;
if( g_numskinref == 0 )
g_numskinref = g_numtextures;
while( 1 )
{
if( !GetToken( true ))
break;
if( endofscript )
{
if( depth != 0 )
COM_FatalError( "missing }\n" );
return 1;
}
if( token[0] == '{' )
{
depth++;
}
else if( token[0] == '}' )
{
depth--;
if( depth == 0 )
break;
group++;
index = 0;
}
else if( depth == 2 )
{
int i = LookupTexture( token );
g_texturegroup[g_numtexturegroups][group][index] = i;
if( group != 0 ) g_texture[i].parent = g_texturegroup[g_numtexturegroups][0][index];
index++;
g_numtexturereps[g_numtexturegroups] = index;
g_numtexturelayers[g_numtexturegroups] = group + 1;
}
}
g_numtexturegroups++;
return 0;
}
int Cmd_Hitgroup( void )
{
GetToken( false );
g_hitgroup[g_numhitgroups].group = verify_atoi( token );
GetToken( false );
Q_strncpy( g_hitgroup[g_numhitgroups].name, token, sizeof( g_hitgroup[0].name ));
g_numhitgroups++;
return 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Cmd_Hitbox( void )
{
bool autogenerated = false;
if( g_hitboxsets.Size() == 0 )
{
g_hitboxsets.AddToTail();
autogenerated = true;
}
// last one
s_hitboxset_t *set = &g_hitboxsets[ g_hitboxsets.Size() - 1 ];
if( autogenerated )
{
memset( set, 0, sizeof( *set ) );
// fill in name if it wasn't specified in the .qc
Q_strncpy( set->hitboxsetname, "default", sizeof( set->hitboxsetname ));
}
GetToken( false );
set->hitbox[set->numhitboxes].group = verify_atoi( token );
// grab the bone name:
GetToken( false );
Q_strncpy( set->hitbox[set->numhitboxes].name, token, sizeof( set->hitbox[0].name ));
GetToken( false );
set->hitbox[set->numhitboxes].bmin[0] = verify_atof( token );
GetToken( false );
set->hitbox[set->numhitboxes].bmin[1] = verify_atof( token );
GetToken( false );
set->hitbox[set->numhitboxes].bmin[2] = verify_atof( token );
GetToken( false );
set->hitbox[set->numhitboxes].bmax[0] = verify_atof( token );
GetToken( false );
set->hitbox[set->numhitboxes].bmax[1] = verify_atof( token );
GetToken( false );
set->hitbox[set->numhitboxes].bmax[2] = verify_atof( token );
// scale hitboxes
set->hitbox[set->numhitboxes].bmin *= g_defaultscale;
set->hitbox[set->numhitboxes].bmax *= g_defaultscale;
TryToken(); // skip the hit box name if present:
set->numhitboxes++;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Cmd_HitboxSet( void )
{
// add a new hitboxset
s_hitboxset_t *set = &g_hitboxsets[ g_hitboxsets.AddToTail() ];
GetToken( false );
memset( set, 0, sizeof( *set ) );
Q_strncpy( set->hitboxsetname, token, sizeof( set->hitboxsetname ));
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void Cmd_BoneMerge( void )
{
int nIndex = g_BoneMerge.AddToTail();
// bone name
GetToken( false );
Q_strncpy( g_BoneMerge[nIndex].bonename, token, sizeof( g_BoneMerge[0].bonename ));
}
int Cmd_Attachment( void )
{
Vector tmp;
int i;
if( g_numattachments >= MAXSTUDIOATTACHMENTS )
COM_FatalError( "too many attachments (max %d)\n", MAXSTUDIOATTACHMENTS );
// attachment name
GetToken( false );
Q_strncpy( g_attachment[g_numattachments].name, token, sizeof( g_attachment[0].name ));
// bone name
GetToken( false );
Q_strncpy( g_attachment[g_numattachments].bonename, token, sizeof( g_attachment[0].bonename ));
// position
GetToken( false );
tmp.x = verify_atof( token );
GetToken( false );
tmp.y = verify_atof( token );
GetToken( false );
tmp.z = verify_atof( token );
tmp *= g_defaultscale;
g_attachment[g_numattachments].local.Identity();
while( TokenAvailable( ))
{
GetToken( false );
if( !Q_stricmp( token, "absolute" ))
{
g_attachment[g_numattachments].local = matrix3x4( g_vecZero, g_defaultrotation ).Invert();
g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL;
g_attachment[g_numattachments].type |= IS_ABSOLUTE;
}
else if( !Q_stricmp( token, "rigid" ))
{
g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL;
g_attachment[g_numattachments].type |= IS_RIGID;
}
else if( !Q_stricmp( token, "world_align" ))
{
}
else if( !Q_stricmp( token, "rotate" ))
{
Vector angles = g_vecZero;
for( i = 0; i < 3; i++ )
{
if( !TokenAvailable( ))
break;
GetToken( false );
angles[i] = verify_atof( token );
}
g_attachment[g_numattachments].local = matrix3x4( g_vecZero, angles );
g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL;
}
else if( !Q_stricmp( token, "x_and_z_axes" ))
{
Vector xaxis, yaxis, zaxis;
for( i = 0; i < 3; i++ )
{
if( !TokenAvailable( ))
break;
GetToken( false );
xaxis[i] = verify_atof( token );
}
for( i = 0; i < 3; i++ )
{
if (!TokenAvailable())
break;
GetToken( false );
zaxis[i] = verify_atof( token );
}
xaxis = xaxis.Normalize();
zaxis += xaxis * -DotProduct( zaxis, xaxis );
zaxis = zaxis.Normalize();
yaxis = CrossProduct( zaxis, xaxis );
g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL;
g_attachment[g_numattachments].local.SetForward( xaxis );
g_attachment[g_numattachments].local.SetRight( yaxis );
g_attachment[g_numattachments].local.SetUp( zaxis );
}
else
{
MsgDev( D_WARN, "unknown attachment (%s) option\n", g_attachment[g_numattachments].name );
while( TryToken( ));
}
}
g_attachment[g_numattachments].local.SetOrigin( tmp );
g_numattachments++;
return 0;
}
void Cmd_Renamebone( void )
{
// from
GetToken( false );
Q_strncpy( g_renamedbone[g_numrenamedbones].from, token, sizeof( g_renamedbone[0].from ));
// to
GetToken( false );
Q_strncpy( g_renamedbone[g_numrenamedbones].to, token, sizeof( g_renamedbone[0].to ));
g_numrenamedbones++;
}
void Cmd_SkipTransition( void )
{
int nskips = 0;
int list[10];
while( TokenAvailable( ))
{
GetToken( false );
list[nskips++] = LookupXNode( token );
}
for( int i = 0; i < nskips; i++ )
{
for( int j = 0; j < nskips; j++ )
{
if( list[i] != list[j] )
{
g_xnodeskip[g_numxnodeskips][0] = list[i];
g_xnodeskip[g_numxnodeskips][1] = list[j];
g_numxnodeskips++;
}
}
}
}
void Cmd_TexRenderMode( void )
{
GetToken( false );
int i = LookupTexture( token );
GetToken( false );
if( !Q_stricmp( token, "additive" ))
{
g_texture[i].flags |= STUDIO_NF_ADDITIVE;
}
else if( !Q_stricmp( token, "masked" ))
{
g_texture[i].flags |= STUDIO_NF_MASKED;
}
else if( !Q_stricmp( token, "masked_solid" ))
{
g_texture[i].flags |= (STUDIO_NF_MASKED|STUDIO_NF_ALPHASOLID);
}
else if( !Q_stricmp( token, "fullbright" ))
{
g_texture[i].flags |= STUDIO_NF_FULLBRIGHT;
}
else if( !Q_stricmp( token, "smooth" ))
{
g_texture[i].flags |= STUDIO_NF_SMOOTH;
}
else if( !Q_stricmp( token, "nosmooth" ))
{
g_texture[i].flags &= ~STUDIO_NF_SMOOTH;
}
else if( !Q_stricmp( token, "twoside" ))
{
g_texture[i].flags |= STUDIO_NF_TWOSIDE;
}
else MsgDev( D_WARN, "Texture '%s' has unknown render mode '%s'!\n", g_texture[i].name, token );
}
//-----------------------------------------------------------------------------
// Purpose: force a specific parent child relationship
//-----------------------------------------------------------------------------
void Cmd_ForcedHierarchy( void )
{
// child name
GetToken( false );
Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].childname, token, sizeof( g_forcedhierarchy[0].childname ));
// parent name
GetToken( false );
Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].parentname, token, sizeof( g_forcedhierarchy[0].parentname ));
g_numforcedhierarchy++;
}
//-----------------------------------------------------------------------------
// Purpose: insert a virtual bone between a child and parent (currently unsupported)
//-----------------------------------------------------------------------------
void Cmd_InsertHierarchy( void )
{
// child name
GetToken( false );
Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].childname, token, sizeof( g_forcedhierarchy[0].childname ));
// subparent name
GetToken( false );
Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].subparentname, token, sizeof( g_forcedhierarchy[0].subparentname ));
// parent name
GetToken( false );
Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].parentname, token, sizeof( g_forcedhierarchy[0].parentname ));
g_numforcedhierarchy++;
}
//-----------------------------------------------------------------------------
// Purpose: rotate a specific bone
//-----------------------------------------------------------------------------
void Cmd_ForceRealign( void )
{
// bone name
GetToken( false );
Q_strncpy( g_forcedrealign[g_numforcedrealign].name, token, sizeof( g_forcedrealign[0].name ));
// skip
GetToken( false );
// X axis
GetToken( false );
g_forcedrealign[g_numforcedrealign].rot.x = DEG2RAD( verify_atof( token ));
// Y axis
GetToken( false );
g_forcedrealign[g_numforcedrealign].rot.y = DEG2RAD( verify_atof( token ));
// Z axis
GetToken( false );
g_forcedrealign[g_numforcedrealign].rot.z = DEG2RAD( verify_atof( token ));
g_numforcedrealign++;
}
//-----------------------------------------------------------------------------
// Purpose: specify a bone to allow > 180 but < 360 rotation (forces a calculated "mid point" to rotation)
//-----------------------------------------------------------------------------
void Cmd_LimitRotation( )
{
// bone name
GetToken( false );
Q_strncpy( g_limitrotation[g_numlimitrotation].name, token, sizeof( g_limitrotation[0].name ));
while( TokenAvailable( ))
{
// sequence name
GetToken( false );
g_limitrotation[g_numlimitrotation].sequencename[g_limitrotation[g_numlimitrotation].numseq++] = strdup( token );
}
g_numlimitrotation++;
}
//-----------------------------------------------------------------------------
// Purpose: specify bones to store, even if nothing references them
//-----------------------------------------------------------------------------
void Cmd_DefineBone( void )
{
// bone name
GetToken( false );
Q_strncpy( g_importbone[g_numimportbones].name, token, sizeof( g_importbone[0].name ));
// parent name
GetToken( false );
Q_strncpy( g_importbone[g_numimportbones].parent, token, sizeof( g_importbone[0].parent ));
Vector pos, ang;
// default pos
GetToken( false );
pos.x = verify_atof( token );
GetToken( false );
pos.y = verify_atof( token );
GetToken( false );
pos.z = verify_atof( token );
GetToken( false );
ang.x = verify_atof( token );
GetToken( false );
ang.y = verify_atof( token );
GetToken( false );
ang.z = verify_atof( token );
g_importbone[g_numimportbones].rawLocal = matrix3x4( pos, ang );
if( TokenAvailable( ))
{
g_importbone[g_numimportbones].bPreAligned = true;
// realign pos
GetToken( false );
pos.x = verify_atof( token );
GetToken( false );
pos.y = verify_atof( token );
GetToken(false );
pos.z = verify_atof( token );
GetToken( false );
ang.x = verify_atof( token );
GetToken( false );
ang.y = verify_atof( token );
GetToken( false );
ang.z = verify_atof( token );
g_importbone[g_numimportbones].srcRealign = matrix3x4( pos, ang );
}
else
{
g_importbone[g_numimportbones].srcRealign.Identity();
}
g_numimportbones++;
}
//----------------------------------------------------------------------------------------------
float ParseJiggleStiffness( void )
{
float stiffness;
float minStiffness; // const float
float maxStiffness; // const float
if ( !GetToken( false ) )
{
COM_FatalError( "$jigglebone(%d): expecting stiffness value\n", linecount, line );
return 0.0f;
}
stiffness = verify_atof( token );
minStiffness = 0.0f;
maxStiffness = 1000.0f;
return bound( minStiffness, stiffness, maxStiffness );
}
//----------------------------------------------------------------------------------------------
float ParseJiggleDamping( void )
{
float damping;
float minDamping; // const float
float maxDamping; // const float
if ( !GetToken( false ) )
{
COM_FatalError( "$jigglebone(%d): expecting damping value\n", linecount, line );
return 0.0f;
}
damping = verify_atof( token );
minDamping = 0.0f;
maxDamping = 10.0f;
return bound( minDamping, damping, maxDamping );
}
//----------------------------------------------------------------------------------------------
int ParseJiggleAngleConstraint( s_jigglebone_t *jiggleInfo )
{
jiggleInfo->data.flags |= JIGGLE_HAS_ANGLE_CONSTRAINT;
if ( !GetToken( false ) )
{
COM_FatalError( "$jigglebone(%d): expecting angle value\n", linecount, line );
return false;
}
jiggleInfo->data.angleLimit = verify_atof( token ) * M_PI / 180.0f;
return true;
}
//----------------------------------------------------------------------------------------------
int ParseJiggleYawConstraint( s_jigglebone_t *jiggleInfo )
{
jiggleInfo->data.flags |= JIGGLE_HAS_YAW_CONSTRAINT;
if ( !GetToken( false ) )
{
COM_FatalError( "$jigglebone(%d): expecting minimum yaw value\n", linecount, line );
return false;
}
jiggleInfo->data.minYaw = verify_atof( token ) * M_PI / 180.0f;
if ( !GetToken( false ) )
{
COM_FatalError( "$jigglebone(%d): expecting maximum yaw value\n", linecount, line );
return false;
}
jiggleInfo->data.maxYaw = verify_atof( token ) * M_PI / 180.0f;
return true;
}
//----------------------------------------------------------------------------------------------
int ParseJigglePitchConstraint( s_jigglebone_t *jiggleInfo )
{
jiggleInfo->data.flags |= JIGGLE_HAS_PITCH_CONSTRAINT;
if ( !GetToken( false ) )
{
COM_FatalError( "$jigglebone(%d): expecting minimum pitch value\n", linecount, line );
return false;
}
jiggleInfo->data.minPitch = verify_atof( token ) * M_PI / 180.0f;
if ( !GetToken( false ) )
{
COM_FatalError( "$jigglebone(%d): expecting maximum pitch value\n", linecount, line );
return false;
}
jiggleInfo->data.maxPitch = verify_atof( token ) * M_PI / 180.0f;
return true;
}
//----------------------------------------------------------------------------------------------
/**
* Parse common parameters.
* This assumes a token has already been read, and returns true if
* the token is recognized and parsed.
*/
int ParseCommonJiggle( s_jigglebone_t *jiggleInfo )
{
if (!stricmp( token, "tip_mass" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.tipMass = verify_atof( token );
}
else if (!stricmp( token, "length" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.length = verify_atof( token );
}
else if (!stricmp( token, "angle_constraint" ))
{
if (ParseJiggleAngleConstraint( jiggleInfo ) == false)
{
return false;
}
}
else if (!stricmp( token, "yaw_constraint" ))
{
if (ParseJiggleYawConstraint( jiggleInfo ) == false)
{
return false;
}
}
else if (!stricmp( token, "yaw_friction" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.yawFriction = verify_atof( token );
}
else if (!stricmp( token, "yaw_bounce" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.yawBounce = verify_atof( token );
}
else if (!stricmp( token, "pitch_constraint" ))
{
if (ParseJigglePitchConstraint( jiggleInfo ) == false)
{
return false;
}
}
else if (!stricmp( token, "pitch_friction" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.pitchFriction = verify_atof( token );
}
else if (!stricmp( token, "pitch_bounce" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.pitchBounce = verify_atof( token );
}
else
{
// unknown token
COM_FatalError( "$jigglebone: invalid syntax '%s'\n", token );
return false;
}
return true;
}
//----------------------------------------------------------------------------------------------
/**
* Parse parameters for is_flexible subsection
*/
int ParseFlexibleJiggle( s_jigglebone_t *jiggleInfo )
{
int gotOpenBracket = false;
jiggleInfo->data.flags |= (JIGGLE_IS_FLEXIBLE | JIGGLE_HAS_LENGTH_CONSTRAINT);
while (true)
{
if (GetToken( true ) == false)
{
COM_FatalError( "$jigglebone(%d): is_flexible: parse error\n", linecount, line );
return false;
}
if (!stricmp( token, "{" ))
{
gotOpenBracket = true;
}
else if (!gotOpenBracket)
{
COM_FatalError( "$jigglebone(%d): is_flexible: missing '{'\n", linecount, line );
return false;
}
else if (!stricmp( token, "}" ))
{
// definition complete
break;
}
else if (!stricmp( token, "yaw_stiffness" ))
{
jiggleInfo->data.yawStiffness = ParseJiggleStiffness();
}
else if (!stricmp( token, "yaw_damping" ))
{
jiggleInfo->data.yawDamping = ParseJiggleStiffness();
}
else if (!stricmp( token, "pitch_stiffness" ))
{
jiggleInfo->data.pitchStiffness = ParseJiggleStiffness();
}
else if (!stricmp( token, "pitch_damping" ))
{
jiggleInfo->data.pitchDamping = ParseJiggleStiffness();
}
else if (!stricmp( token, "along_stiffness" ))
{
jiggleInfo->data.alongStiffness = ParseJiggleStiffness();
}
else if (!stricmp( token, "along_damping" ))
{
jiggleInfo->data.alongDamping = ParseJiggleStiffness();
}
else if (!stricmp( token, "allow_length_flex" ))
{
jiggleInfo->data.flags &= ~JIGGLE_HAS_LENGTH_CONSTRAINT;
}
else if (ParseCommonJiggle( jiggleInfo ) == false)
{
COM_FatalError( "$jigglebone:is_flexible: invalid syntax '%s'\n", token );
return false;
}
}
return true;
}
//----------------------------------------------------------------------------------------------
/**
* Parse parameters for is_rigid subsection
*/
int ParseRigidJiggle( s_jigglebone_t *jiggleInfo )
{
int gotOpenBracket = false;
jiggleInfo->data.flags |= (JIGGLE_IS_RIGID | JIGGLE_HAS_LENGTH_CONSTRAINT);
while (true)
{
if (GetToken( true ) == false)
{
COM_FatalError( "$jigglebone(%d):is_rigid: parse error\n", linecount, line );
return false;
}
if (!stricmp( token, "{" ))
{
gotOpenBracket = true;
}
else if (!gotOpenBracket)
{
COM_FatalError( "$jigglebone(%d):is_rigid: missing '{'\n", linecount, line );
return false;
}
else if (!stricmp( token, "}" ))
{
// definition complete
break;
}
else if (ParseCommonJiggle( jiggleInfo ) == false)
{
COM_FatalError( "$jigglebone:is_rigid: invalid syntax '%s'\n", token );
return false;
}
}
return true;
}
//----------------------------------------------------------------------------------------------
/**
* Parse parameters for has_base_spring subsection
*/
bool ParseBaseSpringJiggle( s_jigglebone_t *jiggleInfo )
{
int gotOpenBracket = false;
jiggleInfo->data.flags |= JIGGLE_HAS_BASE_SPRING;
while (true)
{
if (GetToken( true ) == false)
{
COM_FatalError( "$jigglebone(%d):is_rigid: parse error\n", linecount, line );
return false;
}
if (!stricmp( token, "{" ))
{
gotOpenBracket = true;
}
else if (!gotOpenBracket)
{
COM_FatalError( "$jigglebone(%d):is_rigid: missing '{'\n", linecount, line );
return false;
}
else if (!stricmp( token, "}" ))
{
// definition complete
break;
}
else if (!stricmp( token, "stiffness" ))
{
jiggleInfo->data.baseStiffness = ParseJiggleStiffness();
}
else if (!stricmp( token, "damping" ))
{
jiggleInfo->data.baseDamping = ParseJiggleStiffness();
}
else if (!stricmp( token, "left_constraint" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseMinLeft = verify_atof( token );
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseMaxLeft = verify_atof( token );
}
else if (!stricmp( token, "left_friction" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseLeftFriction = verify_atof( token );
}
else if (!stricmp( token, "up_constraint" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseMinUp = verify_atof( token );
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseMaxUp = verify_atof( token );
}
else if (!stricmp( token, "up_friction" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseUpFriction = verify_atof( token );
}
else if (!stricmp( token, "forward_constraint" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseMinForward = verify_atof( token );
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseMaxForward = verify_atof( token );
}
else if (!stricmp( token, "forward_friction" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseForwardFriction = verify_atof( token );
}
else if (!stricmp( token, "base_mass" ))
{
if ( !GetToken( false ) )
{
return false;
}
jiggleInfo->data.baseMass = verify_atof( token );
}
else if (ParseCommonJiggle( jiggleInfo ) == false)
{
COM_FatalError( "$jigglebone:has_base_spring: invalid syntax '%s'\n", token );
return false;
}
}
return true;
}
/*
=================
=================
*/
void Cmd_JiggleBone( void )
{
s_jigglebone_t *jiggleInfo = &g_jigglebones[g_numjigglebones];
int gotOpenBracket = false;
// bone name
GetToken( false );
Q_strncpy( jiggleInfo->bonename, token, sizeof( jiggleInfo->bonename ));
// default values
memset( &jiggleInfo->data, 0, sizeof( mstudiojigglebone_t ) );
jiggleInfo->data.length = 10.0f;
jiggleInfo->data.yawStiffness = 100.0f;
jiggleInfo->data.pitchStiffness = 100.0f;
jiggleInfo->data.alongStiffness = 100.0f;
jiggleInfo->data.baseStiffness = 100.0f;
jiggleInfo->data.baseMinUp = -100.0f;
jiggleInfo->data.baseMaxUp = 100.0f;
jiggleInfo->data.baseMinLeft = -100.0f;
jiggleInfo->data.baseMaxLeft = 100.0f;
jiggleInfo->data.baseMinForward = -100.0f;
jiggleInfo->data.baseMaxForward = 100.0f;
while( 1 )
{
if( !GetToken( true ))
{
COM_FatalError( "$jigglebone(%d): parse error\n", linecount, line );
return;
}
if (!stricmp( token, "{" ))
{
gotOpenBracket = true;
}
else if (!gotOpenBracket)
{
COM_FatalError( "$jigglebone(%d): missing '{'\n", linecount, line );
return;
}
else if (!stricmp( token, "}" ))
{
// definition complete
break;
}
else if (!stricmp( token, "is_flexible" ))
{
if (ParseFlexibleJiggle( jiggleInfo ) == false)
{
return;
}
}
else if (!stricmp( token, "is_rigid" ))
{
if (ParseRigidJiggle( jiggleInfo ) == false)
{
return;
}
}
else if (!stricmp( token, "has_base_spring" ))
{
if( ParseBaseSpringJiggle( jiggleInfo ) == false)
{
return;
}
}
else
{
COM_FatalError( "$jigglebone: invalid syntax '%s'\n", token );
return;
}
}
MsgDev( D_INFO, "Marking bone %s as a jiggle bone\n", jiggleInfo->bonename );
g_numjigglebones++;
}
void Grab_AxisInterpBones( void )
{
char cmd[1024], tmp[1025];
s_axisinterpbone_t *pBone = &g_axisinterpbones[g_numaxisinterpbones];
s_axisinterpbone_t *pAxis = NULL;
Vector basepos;
while( GetLineInput( ))
{
if( IsEnd( line ))
return;
int i = sscanf( line, "%1023s \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" %d",
cmd, pBone->bonename, tmp, pBone->controlname, tmp, &pBone->axis );
if( i == 6 && !Q_stricmp( cmd, "bone" ))
{
pAxis = pBone;
pBone->axis = pBone->axis - 1; // MAX uses 1..3, engine 0..2
g_numaxisinterpbones++;
pBone = &g_axisinterpbones[g_numaxisinterpbones];
}
else if( !Q_stricmp( cmd, "display" ))
{
// skip all display info
}
else if( !Q_stricmp( cmd, "type" ))
{
// skip all type info
}
else if( !Q_stricmp( cmd, "basepos" ))
{
i = sscanf( line, "basepos %f %f %f", &basepos.x, &basepos.y, &basepos.z );
// skip all type info
}
else if( !Q_stricmp( cmd, "axis" ))
{
Vector pos, rot;
int j;
i = sscanf( line, "axis %d %f %f %f %f %f %f", &j, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] );
if( i == 7 )
{
pAxis->pos[j] = basepos + pos;
AngleQuaternion( rot, pAxis->quat[j] );
}
}
}
}
bool Grab_AimAtBones( void )
{
s_aimatbone_t *pAimAtBone = &g_aimatbones[g_numaimatbones];
// already know it's <aimconstraint> in the first string, otherwise wouldn't be here
if( sscanf( line, "%*s %127s %127s %127s", pAimAtBone->bonename, pAimAtBone->parentname, pAimAtBone->aimname ) == 3 )
{
g_numaimatbones++;
char cmd[1024];
Vector vector;
while( GetLineInput( ))
{
if( IsEnd( line ))
return false;
if( sscanf( line, "%1024s %f %f %f", cmd, &vector[0], &vector[1], &vector[2] ) != 4 )
{
bool allSpace = true;
// Allow blank lines to be skipped without error
for( const char *pC = line; *pC != '\0' && pC < ( line + 4096 ); ++pC )
{
if( !isspace( *pC ))
{
allSpace = false;
break;
}
}
if( allSpace )
{
continue;
}
return true;
}
if( !Q_stricmp( cmd, "<aimvector>" ))
{
// make sure these are unit length on read
pAimAtBone->aimvector = vector.Normalize();
}
else if( !Q_stricmp( cmd, "<upvector>" ))
{
// make sure these are unit length on read
pAimAtBone->upvector = vector.Normalize();
}
else if( !Q_stricmp( cmd, "<basepos>" ))
{
pAimAtBone->basepos = vector;
}
else
{
return true;
}
}
}
// If we get here, we're at EOF
return false;
}
void Grab_QuatInterpBones( void )
{
char cmd[1024];
Radian rotateaxis( 0.0f, 0.0f, 0.0f );
Radian jointorient( 0.0f, 0.0f, 0.0f );
s_quatinterpbone_t *pBone = &g_quatinterpbones[g_numquatinterpbones];
s_quatinterpbone_t *pAxis = NULL;
Vector basepos;
while( GetLineInput( ))
{
if( IsEnd( line ))
return;
int i = sscanf( line, "%s %s %s %s %s", cmd, pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname );
while( i == 4 && !Q_stricmp( cmd, "<aimconstraint>" ))
{
// if Grab_AimAtBones() returns false, there file is at EOF
if( !Grab_AimAtBones( ))
{
return;
}
// Grab_AimAtBones will read input into line same as here until it gets a line it doesn't understand, at which point
// it will exit leaving that line in line, so check for the end and scan the current buffer again and continue on with
// the normal QuatInterpBones process
i = sscanf( line, "%s %s %s %s %s", cmd,
pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname );
}
if( i == 5 && !Q_stricmp( cmd, "<helper>" ))
{
pAxis = pBone;
g_numquatinterpbones++;
pBone = &g_quatinterpbones[g_numquatinterpbones];
}
else if( i > 0 )
{
// There was a bug before which could cause the same command to be parsed twice
// because if the sscanf above completely fails, it will return 0 and not
// change the contents of cmd, so i should be greater than 0 in order for
// any of these checks to be valid... Still kind of buggy as these checks
// do case insensitive stricmp but then the sscanf does case sensitive
// matching afterwards... Should probably change those to
// sscanf( line, "%*s %f ... ) etc...
if( !Q_stricmp( cmd, "<display>" ))
{
// skip all display info
Vector size;
float distance;
i = sscanf( line, "<display> %f %f %f %f", &size[0], &size[1], &size[2], &distance );
if( i == 4 )
{
pAxis->percentage = distance / 100.0f;
pAxis->size = size;
}
else
{
COM_FatalError( "Line %d: Unable to parse procedual <display> bone: %s", linecount, line );
}
}
else if( !Q_stricmp( cmd, "<basepos>" ))
{
i = sscanf( line, "<basepos> %f %f %f", &basepos.x, &basepos.y, &basepos.z );
// skip all type info
}
else if( !Q_stricmp( cmd, "<rotateaxis>" ))
{
i = sscanf( line, "%*s %f %f %f", &rotateaxis.x, &rotateaxis.y, &rotateaxis.z );
rotateaxis.x = DEG2RAD( rotateaxis.x );
rotateaxis.y = DEG2RAD( rotateaxis.y );
rotateaxis.z = DEG2RAD( rotateaxis.z );
}
else if( !Q_stricmp( cmd, "<jointorient>" ))
{
i = sscanf( line, "%*s %f %f %f", &jointorient.x, &jointorient.y, &jointorient.z );
jointorient.x = DEG2RAD( jointorient.x );
jointorient.y = DEG2RAD( jointorient.y );
jointorient.z = DEG2RAD( jointorient.z );
}
else if( !Q_stricmp( cmd, "<trigger>" ))
{
float tolerance;
Radian trigger;
Vector pos, rot;
Radian ang;
int j;
i = sscanf( line, "<trigger> %f %f %f %f %f %f %f %f %f %f",
&tolerance,
&trigger.x, &trigger.y, &trigger.z,
&ang.x, &ang.y, &ang.z,
&pos.x, &pos.y, &pos.z );
if( i == 10 )
{
trigger.x = DEG2RAD( trigger.x );
trigger.y = DEG2RAD( trigger.y );
trigger.z = DEG2RAD( trigger.z );
ang.x = DEG2RAD( ang.x );
ang.y = DEG2RAD( ang.y );
ang.z = DEG2RAD( ang.z );
Vector4D q, q1, q2;
AngleQuaternion( ang, q );
if( rotateaxis.x != 0.0 || rotateaxis.y != 0.0 || rotateaxis.z != 0.0 )
{
AngleQuaternion( rotateaxis, q1 );
QuaternionMult( q1, q, q2 );
q = q2;
}
if( jointorient != g_vecZero )
{
AngleQuaternion( jointorient, q1 );
QuaternionMult( q, q1, q2 );
q = q2;
}
j = pAxis->numtriggers++;
pAxis->tolerance[j] = DEG2RAD( tolerance );
AngleQuaternion( trigger, pAxis->trigger[j] );
pAxis->pos[j] = basepos + pos;
pAxis->quat[j] = q;
}
else
{
COM_FatalError( "Line %d: Unable to parse procedual <trigger> bone: %s", linecount, line );
}
}
else
{
COM_FatalError( "Line %d: Unable to parse procedual bone data: %s", linecount, line );
}
}
else
{
// Allow blank lines to be skipped without error
bool allSpace = true;
for( const char *pC = line; *pC != '\0' && pC < ( line + 4096 ); ++pC )
{
if( !isspace( *pC ))
{
allSpace = false;
break;
}
}
if( !allSpace )
{
COM_FatalError( "Line %d: Unable to parse procedual bone data: %s", linecount, line );
}
}
}
}
void Load_ProceduralBones( void )
{
char filename[256];
char shortname[64];
char cmd[1024];
int option;
GetToken( false );
Q_snprintf( filename, sizeof( filename ), "%s/%s", cddir[numdirs], token );
COM_DefaultExtension( filename, ".vrd" );
if( !COM_FileExists( filename ))
{
MsgDev( D_ERROR, "%s doesn't exist\n", filename );
return;
}
if(( input = fopen( filename, "r" )) == 0 )
{
MsgDev( D_ERROR, "%s couldn't be open\n", filename );
return;
}
COM_FileBase( filename, shortname );
MsgDev( D_INFO, "grabbing: %s.%s\t\t[^3bones^7]\n", shortname, COM_FileExtension( filename ));
linecount = 0;
if( !Q_stricmp( COM_FileExtension( filename ), "vrd" ))
{
Grab_QuatInterpBones( );
}
else
{
while( GetLineInput( ))
{
sscanf( line, "%s", cmd, &option );
if( !Q_stricmp( cmd, "version" ))
{
if( option != 1 )
COM_FatalError( "%s version %i should be 1\n", filename, option );
}
else if( !Q_stricmp( cmd, "proceduralbones" ))
{
Grab_AxisInterpBones( );
}
}
}
fclose( input );
}
void Cmd_KeyValues( void )
{
Option_KeyValues( &g_KeyValueText );
}
void Cmd_AlwaysCollapse( void )
{
GetToken( false );
int nIndex = g_collapse.AddToTail();
Q_strncpy( g_collapse[nIndex].bonename, token, sizeof( g_collapse[0].bonename ));
g_collapse_bones = true;
}
void Cmd_Pushd( void )
{
GetToken( false );
Q_strncpy( cddir[numdirs+1], cddir[numdirs], sizeof( cddir[0] ));
Q_strncat( cddir[numdirs+1], token, sizeof( cddir[0] ));
Q_strncat( cddir[numdirs+1], "/", sizeof( cddir[0] ));
numdirs++;
}
void Cmd_Popd( void )
{
if( numdirs > 0 )
numdirs--;
}
void Cmd_Lod( void )
{
ParseEmpty();
}
void Cmd_ShadowLod( void )
{
ParseEmpty();
}
/*
===============
ParseScript
===============
*/
void ParseScript( void )
{
while( 1 )
{
do
{ // look for a line starting with a $ command
GetToken( true );
if( endofscript )
return;
if( token[0] == '$' )
break;
while( TryToken( ));
} while( 1 );
if( !Q_stricmp( token, "$modelname" ))
{
Cmd_Modelname ();
}
else if( !Q_stricmp( token, "$cd" ))
{
if( cdset ) COM_FatalError ("Two $cd in one model");
GetToken( false );
Q_strncpy( cddir[0], COM_ExpandArg( token ), sizeof( cddir[0] ));
cdset = true;
}
else if( !Q_stricmp( token, "$cdtexture" ) || !Q_stricmp( token, "$cdmaterials" ))
{
while( TokenAvailable( ))
{
GetToken( false );
Q_strncpy( cdtexture[cdtextureset], COM_ExpandArg( token ), sizeof( cdtexture[0] ));
cdtextureset++;
}
}
else if( !Q_stricmp( token, "$scale" ))
{
Cmd_ScaleUp ();
}
else if( !Q_stricmp( token, "$scale_x" ))
{
Cmd_ScaleAxis( 1 ); // x&y swapped in studio
}
else if( !Q_stricmp( token, "$scale_y" ))
{
Cmd_ScaleAxis( 0 ); // x&y swapped in studio
}
else if( !Q_stricmp( token, "$scale_z" ))
{
Cmd_ScaleAxis( 2 );
}
else if( !Q_stricmp( token, "$root" ) || !Q_stricmp( token, "$rootbone" ))
{
Cmd_Root ();
}
else if( !Q_stricmp( token, "$pushd" ))
{
Cmd_Pushd();
}
else if( !Q_stricmp( token, "$popd" ))
{
Cmd_Popd();
}
else if( !Q_stricmp( token, "$alwayscollapse" ))
{
Cmd_AlwaysCollapse ();
}
else if( !Q_stricmp( token, "$controller" ))
{
Cmd_Controller ();
}
else if (!stricmp( token, "$screenalign" ))
{
Cmd_ScreenAlign( );
}
else if (!stricmp (token, "$model"))
{
Cmd_Model();
}
else if( !Q_stricmp( token, "$body" ))
{
Cmd_Body();
}
else if( !Q_stricmp( token, "$bodygroup" ))
{
Cmd_Bodygroup();
}
else if (!stricmp (token, "$animation" ))
{
Cmd_Animation();
}
else if( !Q_stricmp( token, "$cmdlist" ))
{
Cmd_Cmdlist ();
}
else if( !Q_stricmp( token, "$sequence" ))
{
Cmd_Sequence ();
}
else if( !Q_stricmp( token, "$append" ))
{
Cmd_Append ();
}
else if( !Q_stricmp( token, "$prepend" ))
{
Cmd_Append ();
}
else if( !Q_stricmp( token, "$continue" ))
{
Cmd_Append ();
}
else if( !Q_stricmp( token, "$sequencegroup" ))
{
Cmd_SequenceGroup ();
}
else if( !Q_stricmp( token, "$sequencegroupsize" ))
{
Cmd_SequenceGroupSize ();
}
else if( !Q_stricmp( token, "$weightlist" ))
{
Cmd_Weightlist ();
}
else if( !Q_stricmp( token, "$defaultweightlist" ))
{
Cmd_DefaultWeightlist ();
}
else if( !Q_stricmp( token, "$ikchain" ))
{
Cmd_IKChain ();
}
else if( !Q_stricmp( token, "$ikautoplaylock" ))
{
Cmd_IKAutoplayLock ();
}
else if( !Q_stricmp( token, "$eyeposition" ))
{
Cmd_Eyeposition ();
}
else if( !Q_stricmp( token, "$heirarchy" ))
{
Cmd_ForcedHierarchy ();
}
else if( !Q_stricmp( token, "$hierarchy" ))
{
Cmd_ForcedHierarchy ();
}
else if( !Q_stricmp( token, "$insertbone" ))
{
Cmd_InsertHierarchy ();
}
else if( !Q_stricmp( token, "$limitrotation" ))
{
Cmd_LimitRotation ();
}
else if( !Q_stricmp( token, "$forcerealign" ))
{
Cmd_ForceRealign ();
}
else if( !Q_stricmp( token, "$origin" ))
{
Cmd_Origin ();
}
else if (!stricmp (token, "$upaxis"))
{
Cmd_UpAxis( );
}
else if( !Q_stricmp( token, "$bbox" ))
{
Cmd_BBox ();
}
else if( !Q_stricmp( token, "$cbox" ))
{
Cmd_CBox ();
}
else if( !Q_stricmp( token, "$gamma" ))
{
Cmd_Gamma ();
}
else if( !Q_stricmp( token, "$flags" ))
{
Cmd_Flags ();
}
else if( !Q_stricmp( token, "$texturegroup" ))
{
Cmd_TextureGroup ();
}
else if( !Q_stricmp( token, "$skiptransition" ))
{
Cmd_SkipTransition ();
}
else if( !Q_stricmp( token, "$calctransitions" ))
{
g_multistagegraph = 1;
}
else if( !Q_stricmp( token, "$staticprop" ))
{
gflags |= STUDIO_STATIC_PROP;
g_staticprop = true;
}
else if( !Q_stricmp( token, "$autocenter" ))
{
g_centerstaticprop = true;
}
else if( !Q_stricmp( token, "$hgroup" ))
{
Cmd_Hitgroup ();
}
else if( !Q_stricmp( token, "$hbox" ))
{
Cmd_Hitbox ();
}
else if( !Q_stricmp( token, "$hboxset" ))
{
Cmd_HitboxSet ();
}
else if( !Q_stricmp( token, "$attachment" ))
{
Cmd_Attachment ();
}
else if( !Q_stricmp( token, "$bonemerge" ))
{
Cmd_BoneMerge ();
}
else if( !Q_stricmp( token, "$lod" ))
{
Cmd_Lod ();
}
else if( !Q_stricmp( token, "$shadowlod" ))
{
Cmd_ShadowLod ();
}
else if( !Q_stricmp( token, "$externaltextures" ))
{
split_textures = 1;
}
else if( !Q_stricmp( token, "$cliptotextures" ))
{
clip_texcoords = 1;
}
else if( !Q_stricmp( token, "$mergecontrollers" ))
{
g_mergebonecontrollers = 1;
}
else if( !Q_stricmp( token, "$fixedcoords" ))
{
store_uv_coords = 1;
}
else if( !Q_stricmp( token, "$freecords" ))
{
allow_tileing = 1;
store_uv_coords = 1;
}
else if( !Q_stricmp( token, "$freecoords" ))
{
allow_tileing = 1;
store_uv_coords = 1;
}
else if( !Q_stricmp( token, "$boneweights" ))
{
allow_boneweights = 1;
}
else if( !Q_stricmp( token, "$lockbonelengths" ))
{
g_lockbonelengths = 1;
}
else if( !Q_stricmp( token, "$renamebone" ))
{
Cmd_Renamebone ();
}
else if( !Q_stricmp( token, "$definebone" ))
{
Cmd_DefineBone ();
}
else if( !Q_stricmp( token, "$realignbones" ))
{
g_realignbones = 1;
}
else if( !Q_stricmp( token, "$unlockdefinebones" ))
{
g_overridebones = 1;
}
else if( !Q_stricmp( token, "$texrendermode" ))
{
Cmd_TexRenderMode();
}
else if( !Q_stricmp( token, "$jigglebone" ))
{
Cmd_JiggleBone ();
}
else if( !Q_stricmp( token, "$proceduralbones" ))
{
Load_ProceduralBones( );
}
else if( !Q_stricmp( token, "$poseparameter" ))
{
Cmd_PoseParameter( );
}
else if( !Q_stricmp( token, "$keyvalues" ))
{
Cmd_KeyValues( );
}
else if( !Q_stricmp( token, "$collapsebones" ))
{
g_collapse_bones = true;
}
else if( !Q_stricmp( token, "$collapsebonesaggressive" ))
{
g_collapse_bones = true;
g_collapse_bones_aggressive = true;
}
else
{
if( token[0] == '$' )
MsgDev( D_REPORT, "^2Warning:^7 unknown command %s\n", token );
else MsgDev( D_WARN, "unknown command %s\n", token );
while( TryToken( )); // skip at rest of the line
}
}
}
int main( int argc, char **argv )
{
int i;
char path[1024];
atexit( Sys_CloseLog );
COM_InitCmdlib( argv, argc );
// impicit path
Q_strncpy( cddir[0], ".\\", sizeof( cddir[0] ));
g_numweightlist = 1; // skip weightlist 0
g_defaultrotation = Radian( 0.0f, 0.0f, M_PI / 2 );
g_normal_blend = cos( DEG2RAD( 2.0 ));
g_defaultscale = 1.0f;
numrep = 0;
tag_reversed = 0;
tag_normals = 0;
flip_triangles = 1;
g_lockbonelengths = 0;
g_overridebones = 0;
maxseqgroupsize = 1024 * 1024;
g_multistagegraph = 0;
allow_boneweights = 0;
has_boneweights = 0;
g_gamma = 1.8f;
if( argc == 1 )
{
Msg( " P2:Savior Studio Model Compiler\n" );
Msg( " XashXT Group 2018(^1c^7)\n\n\n" );
Msg( "usage: studiomdl <options> file.qc\n"
"\nlist options:\n"
"^2-t^7 - replace all model textures with specified\n"
"^2-r^7 - tag reversed\n"
"^2-n^7 - tag bad normals\n"
"^2-f^7 - flip all triangles\n"
"^2-a^7 - normal blend angle\n"
"^2-h^7 - dump hitboxes\n"
"^2-g^7 - dump transition graph\n"
"^2-dev^7 - shows developer messages\n"
"\n\t\tPress any key to exit" );
system( "pause>nul" );
return 1;
}
Sys_InitLog( "studiomdl.log" );
Msg( " P2:Savior Studio Model Compiler\n" );
Msg( " XashXT Group 2018(^1c^7)\n\n\n" );
for( i = 1; i < argc - 1; i++ )
{
if( argv[i][0] == '-' )
{
if( !Q_stricmp( argv[i], "-dev" ))
{
SetDeveloperLevel( verify_atoi( argv[i+1] ));
i++;
continue;
}
switch( argv[i][1] )
{
case 't':
i++;
Q_strncpy( defaulttexture[numrep], argv[i], sizeof( defaulttexture[0] ));
if( i < argc - 2 && argv[i + 1][0] != '-' )
{
i++;
Q_strncpy( sourcetexture[numrep], argv[i], sizeof( sourcetexture[0] ));
MsgDev( D_INFO, "Replacing %s with %s\n", sourcetexture[numrep], sizeof( defaulttexture[0] ));
}
MsgDev( D_INFO, "Using default texture: %s\n", defaulttexture );
numrep++;
break;
case 'r':
tag_reversed = 1;
break;
case 'n':
tag_normals = 1;
break;
case 'f':
flip_triangles = 0;
break;
case 'a':
i++;
g_normal_blend = cos( DEG2RAD( verify_atof( argv[i] )));
break;
case 'h':
g_dump_hboxes = true;
break;
case 'g':
g_dump_graph = true;
break;
}
}
}
if( !argv[i] )
{
MsgDev( D_ERROR, "No inputfile specified\n" );
return 1;
}
Q_strcpy( g_sequencegroup[g_numseqgroups].label, "default" );
g_numseqgroups = 1;
// load the script
Q_strncpy( path, argv[i], sizeof( path ));
Q_strncpy( outname, path, sizeof( outname ));
COM_DefaultExtension( path, ".qc" );
COM_StripExtension( outname );
LoadScriptFile( path );
// parse it
ParseScript ();
SetSkinValues ();
SimplifyModel ();
WriteFile ();
ClearModel ();
SetDeveloperLevel( D_REPORT );
Mem_Check(); // report leaks
return 0;
}