forked from FWGS/Paranoia2
5524 lines
121 KiB
C++
5524 lines
121 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;
|
|
}
|