summaryrefslogtreecommitdiff
path: root/utils/studiomdl/studiomdl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/studiomdl/studiomdl.cpp')
-rw-r--r--utils/studiomdl/studiomdl.cpp10448
1 files changed, 10448 insertions, 0 deletions
diff --git a/utils/studiomdl/studiomdl.cpp b/utils/studiomdl/studiomdl.cpp
new file mode 100644
index 0000000..4441617
--- /dev/null
+++ b/utils/studiomdl/studiomdl.cpp
@@ -0,0 +1,10448 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//===========================================================================//
+
+
+//
+// studiomdl.c: generates a studio .mdl file from a .qc script
+// models/<scriptname>.mdl.
+//
+
+
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4237 )
+#pragma warning( disable : 4305 )
+
+#include <windows.h>
+#undef GetCurrentDirectory
+
+#include <Shlwapi.h> // PathCanonicalize
+#pragma comment( lib, "shlwapi" )
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+#include <direct.h>
+#include "istudiorender.h"
+#include "filesystem_tools.h"
+#include "tier2/fileutils.h"
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#define EXTERN
+#include "studio.h"
+#include "studiomdl.h"
+#include "collisionmodel.h"
+#include "optimize.h"
+#include "byteswap.h"
+#include "studiobyteswap.h"
+#include "tier1/strtools.h"
+#include "bspflags.h"
+#include "tier0/icommandline.h"
+#include "utldict.h"
+#include "tier1/utlsortvector.h"
+#include "bitvec.h"
+#include "appframework/appframework.h"
+#include "datamodel/idatamodel.h"
+#include "materialsystem/materialsystem_config.h"
+#include "vstdlib/cvar.h"
+#include "tier1/tier1.h"
+#include "tier2/tier2.h"
+#include "tier3/tier3.h"
+#include "datamodel/dmelementfactoryhelper.h"
+#include "mdlobjects/dmeboneflexdriver.h"
+#include "movieobjects/dmeanimationset.h"
+#include "movieobjects/dmemdlmakefile.h"
+#include "movieobjects/dmevertexdata.h"
+#include "movieobjects/dmecombinationoperator.h"
+#include "dmserializers/idmserializers.h"
+#include "tier2/p4helpers.h"
+#include "p4lib/ip4.h"
+#include "mdllib/mdllib.h"
+#include "perfstats.h"
+#include "worldsize.h"
+
+bool g_collapse_bones = false;
+bool g_collapse_bones_aggressive = false;
+bool g_quiet = false;
+bool g_badCollide = false;
+bool g_IHVTest = false;
+bool g_bCheckLengths = false;
+bool g_bPrintBones = false;
+bool g_bPerf = false;
+bool g_bDumpGraph = false;
+bool g_bMultistageGraph = false;
+bool g_verbose = false;
+bool g_bCreateMakefile = false;
+bool g_bHasModelName = false;
+bool g_bZBrush = false;
+bool g_bVerifyOnly = false;
+bool g_bUseBoneInBBox = true;
+bool g_bLockBoneLengths = false;
+bool g_bOverridePreDefinedBones = false;
+int g_minLod = 0;
+int g_numAllowedRootLODs = 0;
+bool g_bNoWarnings = false;
+int g_maxWarnings = -1;
+bool g_bX360 = false;
+bool g_bBuildPreview = false;
+bool g_bCenterBonesOnVerts = false;
+bool g_bDumpMaterials = false;
+bool g_bStripLods = false;
+bool g_bMakeVsi = false;
+float g_flDefaultMotionRollback = 0.3f;
+int g_minSectionFrameLimit = 120;
+int g_sectionFrames = 30;
+bool g_bNoAnimblockStall = false;
+
+char g_path[MAX_PATH];
+Vector g_vecMinWorldspace = Vector( MIN_COORD_INTEGER, MIN_COORD_INTEGER, MIN_COORD_INTEGER );
+Vector g_vecMaxWorldspace = Vector( MAX_COORD_INTEGER, MAX_COORD_INTEGER, MAX_COORD_INTEGER );
+DmElementHandle_t g_hDmeBoneFlexDriverList = DMELEMENT_HANDLE_INVALID;
+
+enum RunMode
+{
+ RUN_MODE_BUILD,
+ RUN_MODE_STRIP_MODEL,
+ RUN_MODE_STRIP_VHV
+} g_eRunMode = RUN_MODE_BUILD;
+
+bool g_bNoP4 = false;
+
+
+CUtlVector< s_hitboxset > g_hitboxsets;
+CUtlVector< char > g_KeyValueText;
+CUtlVector<s_flexcontrollerremap_t> g_FlexControllerRemap;
+CCheckUVCmd g_StudioMdlCheckUVCmd;
+
+
+//-----------------------------------------------------------------------------
+// Parsed data from a .qc or .dmx file
+//-----------------------------------------------------------------------------
+struct IKLock_t
+{
+ CUtlString m_Name;
+ float m_flPosWeight;
+ float m_flLocalQWeight;
+};
+
+struct SequenceOption_t
+{
+ bool m_bSnap : 1;
+ bool m_bIsDelta : 1;
+ bool m_bIsWorldSpace : 1;
+ bool m_bIsPost : 1;
+ bool m_bIsPreDelta : 1;
+ bool m_bIsAutoplay : 1;
+ bool m_bIsRealTime : 1;
+ bool m_bIsHidden : 1;
+ float m_flFadeInTime;
+ float m_flFadeOutTime;
+ int m_nBlendWidth;
+ CUtlVector< CUtlString > m_AutoLayers;
+ CUtlVector< IKLock_t > m_IKLocks;
+};
+
+struct CmdSequence_t
+{
+ CUtlString m_Name;
+ CUtlString m_FileName;
+ SequenceOption_t m_Options;
+};
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+void AddBodyFlexData( s_source_t *pSource, int imodel );
+void AddBodyAttachments( s_source_t *pSource );
+void AddBodyFlexRules( s_source_t *pSource );
+
+//-----------------------------------------------------------------------------
+// Stuff for writing a makefile to build models incrementally.
+//-----------------------------------------------------------------------------
+CUtlVector<CUtlSymbol> m_CreateMakefileDependencies;
+
+void CreateMakefile_AddDependency( const char *pFileName )
+{
+ EnsureDependencyFileCheckedIn( pFileName );
+
+ if( !g_bCreateMakefile )
+ {
+ return;
+ }
+
+ CUtlSymbol sym( pFileName );
+ int i;
+ for( i = 0; i < m_CreateMakefileDependencies.Count(); i++ )
+ {
+ if( m_CreateMakefileDependencies[i] == sym )
+ {
+ return;
+ }
+ }
+ m_CreateMakefileDependencies.AddToTail( sym );
+}
+
+void EnsureDependencyFileCheckedIn( const char *pFileName )
+{
+ // Early out: if no p4
+ if ( g_bNoP4 )
+ return;
+
+ char pFullPath[MAX_PATH];
+ if ( !GetGlobalFilePath( pFileName, pFullPath, sizeof(pFullPath) ) )
+ {
+ MdlWarning( "Model dependency file '%s' is missing.\n", pFileName );
+ return;
+ }
+
+ Q_FixSlashes( pFullPath );
+ char bufCanonicalPath[ MAX_PATH ] = {0};
+ PathCanonicalize( bufCanonicalPath, pFullPath );
+ CP4AutoAddFile p4_add_dep_file( bufCanonicalPath );
+}
+
+void StudioMdl_ScriptLoadedCallback( char const *pFilenameLoaded, char const *pIncludedFromFileName, int nIncludeLineNumber )
+{
+ EnsureDependencyFileCheckedIn( pFilenameLoaded );
+}
+
+void CreateMakefile_OutputMakefile( void )
+{
+ if( !g_bHasModelName )
+ {
+ MdlError( "Can't write makefile since a target mdl hasn't been specified!" );
+ }
+ FILE *fp = fopen( "makefile.tmp", "a" );
+ if( !fp )
+ {
+ MdlError( "can't open makefile.tmp!\n" );
+ }
+ char mdlname[MAX_PATH];
+ V_strcpy_safe( mdlname, gamedir );
+// if( *g_pPlatformName )
+// {
+// V_strcat_safe( mdlname, "platform_" );
+// V_strcat_safe( mdlname, g_pPlatformName );
+// V_strcat_safe( mdlname, "/" );
+// }
+ V_strcat_safe( mdlname, "models/" );
+ V_strcat_safe( mdlname, outname );
+ Q_StripExtension( mdlname, mdlname, sizeof( mdlname ) );
+ V_strcat_safe( mdlname, ".mdl" );
+ Q_FixSlashes( mdlname );
+
+ fprintf( fp, "%s:", mdlname );
+ int i;
+ for( i = 0; i < m_CreateMakefileDependencies.Count(); i++ )
+ {
+ fprintf( fp, " %s", m_CreateMakefileDependencies[i].String() );
+ }
+ fprintf( fp, "\n" );
+ char mkdirpath[MAX_PATH];
+ V_strcpy_safe( mkdirpath, mdlname );
+ Q_StripFilename( mkdirpath );
+ fprintf( fp, "\tmkdir \"%s\"\n", mkdirpath );
+ fprintf( fp, "\t%s -quiet %s\n\n", CommandLine()->GetParm( 0 ), fullpath );
+ fclose( fp );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+
+static bool g_bFirstWarning = true;
+
+void TokenError( const char *fmt, ... )
+{
+ static char output[1024];
+ va_list args;
+
+ char *pFilename;
+ int iLineNumber;
+
+ if (GetTokenizerStatus( &pFilename, &iLineNumber ))
+ {
+ va_start( args, fmt );
+ vsprintf( output, fmt, args );
+
+ MdlError( "%s(%d): - %s", pFilename, iLineNumber, output );
+ }
+ else
+ {
+ va_start( args, fmt );
+ vsprintf( output, fmt, args );
+ MdlError( "%s", output );
+ }
+}
+
+void MdlError( const char *fmt, ... )
+{
+ static char output[1024];
+ static char *knownExtensions[] = {".mdl", ".ani", ".phy", ".sw.vtx", ".dx80.vtx", ".dx90.vtx", ".vvd"};
+ char fileName[MAX_PATH];
+ char baseName[MAX_PATH];
+ va_list args;
+
+ Assert( 0 );
+ if (g_quiet)
+ {
+ if (g_bFirstWarning)
+ {
+ printf("%s :\n", fullpath );
+ g_bFirstWarning = false;
+ }
+ printf("\t");
+ }
+
+ printf("ERROR: ");
+ va_start( args, fmt );
+ vprintf( fmt, args );
+
+ // delete premature files
+ // unforunately, content is built without verification
+ // ensuring that targets are not available, prevents check-in
+ if (g_bHasModelName)
+ {
+ // undescriptive errors in batch processes could be anonymous
+ printf("ERROR: Aborted Processing on '%s'\n", outname);
+
+ V_strcpy_safe( fileName, gamedir );
+ V_strcat_safe( fileName, "models/" );
+ V_strcat_safe( fileName, outname );
+ Q_FixSlashes( fileName );
+ Q_StripExtension( fileName, baseName, sizeof( baseName ) );
+
+ for (int i=0; i<ARRAYSIZE(knownExtensions); i++)
+ {
+ V_strcpy_safe( fileName, baseName);
+ V_strcat_safe( fileName, knownExtensions[i] );
+
+ // really need filesystem concept here
+// g_pFileSystem->RemoveFile( fileName );
+ unlink( fileName );
+ }
+ }
+
+ exit( -1 );
+}
+
+
+void MdlWarning( const char *fmt, ... )
+{
+ va_list args;
+ static char output[1024];
+
+ if (g_bNoWarnings || g_maxWarnings == 0)
+ return;
+
+ WORD old = SetConsoleTextColor( 1, 1, 0, 1 );
+
+ if (g_quiet)
+ {
+ if (g_bFirstWarning)
+ {
+ printf("%s :\n", fullpath );
+ g_bFirstWarning = false;
+ }
+ printf("\t");
+ }
+
+ Assert( 0 );
+
+ printf("WARNING: ");
+ va_start( args, fmt );
+ vprintf( fmt, args );
+
+ if (g_maxWarnings > 0)
+ g_maxWarnings--;
+
+ if (g_maxWarnings == 0)
+ {
+ if (g_quiet)
+ {
+ printf("\t");
+ }
+ printf("suppressing further warnings...\n");
+ }
+
+ RestoreConsoleTextColor( old );
+}
+
+SpewRetval_t MdlSpewOutputFunc( SpewType_t type, char const *pMsg )
+{
+ if ((( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) && g_quiet)
+ {
+ // suppress
+ }
+ else if (type == SPEW_WARNING)
+ {
+ MdlWarning( "%s", pMsg );
+ }
+ else
+ {
+ return CmdLib_SpewOutputFunc( type, pMsg );
+ }
+
+ return SPEW_CONTINUE;
+}
+
+
+#ifndef _DEBUG
+
+void MdlHandleCrash( const char *pMessage, bool bAssert )
+{
+ static LONG crashHandlerCount = 0;
+ if ( InterlockedIncrement( &crashHandlerCount ) == 1 )
+ {
+ MdlError( "'%s' (assert: %d)\n", pMessage, bAssert );
+ }
+
+ InterlockedDecrement( &crashHandlerCount );
+}
+
+
+// This is called if we crash inside our crash handler. It just terminates the process immediately.
+LONG __stdcall MdlSecondExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo )
+{
+ TerminateProcess( GetCurrentProcess(), 2 );
+ return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway)
+}
+
+
+void MdlExceptionFilter( unsigned long code )
+{
+ // This is called if we crash inside our crash handler. It just terminates the process immediately.
+ SetUnhandledExceptionFilter( MdlSecondExceptionFilter );
+
+ //DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode;
+
+ #define ERR_RECORD( name ) { name, #name }
+ struct
+ {
+ int code;
+ char *pReason;
+ } errors[] =
+ {
+ ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ),
+ ERR_RECORD( EXCEPTION_ARRAY_BOUNDS_EXCEEDED ),
+ ERR_RECORD( EXCEPTION_BREAKPOINT ),
+ ERR_RECORD( EXCEPTION_DATATYPE_MISALIGNMENT ),
+ ERR_RECORD( EXCEPTION_FLT_DENORMAL_OPERAND ),
+ ERR_RECORD( EXCEPTION_FLT_DIVIDE_BY_ZERO ),
+ ERR_RECORD( EXCEPTION_FLT_INEXACT_RESULT ),
+ ERR_RECORD( EXCEPTION_FLT_INVALID_OPERATION ),
+ ERR_RECORD( EXCEPTION_FLT_OVERFLOW ),
+ ERR_RECORD( EXCEPTION_FLT_STACK_CHECK ),
+ ERR_RECORD( EXCEPTION_FLT_UNDERFLOW ),
+ ERR_RECORD( EXCEPTION_ILLEGAL_INSTRUCTION ),
+ ERR_RECORD( EXCEPTION_IN_PAGE_ERROR ),
+ ERR_RECORD( EXCEPTION_INT_DIVIDE_BY_ZERO ),
+ ERR_RECORD( EXCEPTION_INT_OVERFLOW ),
+ ERR_RECORD( EXCEPTION_INVALID_DISPOSITION ),
+ ERR_RECORD( EXCEPTION_NONCONTINUABLE_EXCEPTION ),
+ ERR_RECORD( EXCEPTION_PRIV_INSTRUCTION ),
+ ERR_RECORD( EXCEPTION_SINGLE_STEP ),
+ ERR_RECORD( EXCEPTION_STACK_OVERFLOW ),
+ ERR_RECORD( EXCEPTION_ACCESS_VIOLATION ),
+ };
+
+ int nErrors = sizeof( errors ) / sizeof( errors[0] );
+ {
+ int i;
+ for ( i=0; i < nErrors; i++ )
+ {
+ if ( errors[i].code == code )
+ MdlHandleCrash( errors[i].pReason, true );
+ }
+
+ if ( i == nErrors )
+ {
+ MdlHandleCrash( "Unknown reason", true );
+ }
+ }
+
+ TerminateProcess( GetCurrentProcess(), 1 );
+}
+
+#endif
+
+/*
+=================
+=================
+*/
+
+int k_memtotal;
+void *kalloc( int num, int size )
+{
+ // printf( "calloc( %d, %d )\n", num, size );
+ // printf( "%d ", num * size );
+ int nMemSize = num * size;
+ k_memtotal += nMemSize;
+
+ // ensure memory alignment on maximum of ALIGN
+ nMemSize += 511;
+ void *ptr = malloc( nMemSize );
+ memset( ptr, 0, nMemSize );
+ ptr = (byte *)((int)((byte *)ptr + 511) & ~511);
+ return ptr;
+}
+
+void kmemset( void *ptr, int value, int size )
+{
+ // printf( "kmemset( %x, %d, %d )\n", ptr, value, size );
+ memset( ptr, value, size );
+ return;
+}
+
+
+int verify_atoi( const char *token )
+{
+ if (token[0] != '-' && (token[0] < '0' || token[0] > '9'))
+ {
+ TokenError( "expecting number, got \"%s\"\n", token );
+ }
+ return atoi( token );
+}
+
+float verify_atof( const char *token )
+{
+ if (token[0] != '-' && token[0] != '.' && (token[0] < '0' || token[0] > '9'))
+ {
+ TokenError( "expecting number, got \"%s\"\n", token );
+ }
+ return atof( token );
+}
+
+float verify_atof_with_null( const char *token )
+{
+ if (strcmp( token, ".." ) == 0)
+ return -1;
+
+ if (token[0] != '-' && token[0] != '.' && (token[0] < '0' || token[0] > '9'))
+ {
+ TokenError( "expecting number, got \"%s\"\n", token );
+ }
+ return atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// Key value block
+//-----------------------------------------------------------------------------
+static void AppendKeyValueText( CUtlVector< char > *pKeyValue, const char *pString )
+{
+ int nLen = strlen(pString);
+ int nFirst = pKeyValue->AddMultipleToTail( nLen );
+ memcpy( pKeyValue->Base() + nFirst, pString, nLen );
+}
+
+int KeyValueTextSize( CUtlVector< char > *pKeyValue )
+{
+ return pKeyValue->Count();
+}
+
+const char *KeyValueText( CUtlVector< char > *pKeyValue )
+{
+ return pKeyValue->Base();
+}
+
+void Option_KeyValues( CUtlVector< char > *pKeyValue );
+
+//-----------------------------------------------------------------------------
+// Read global input into common string
+//-----------------------------------------------------------------------------
+
+bool GetLineInput( void )
+{
+ while (fgets( g_szLine, sizeof( g_szLine ), g_fpInput ) != NULL)
+ {
+ g_iLinecount++;
+ // skip comments
+ if (g_szLine[0] == '/' && g_szLine[1] == '/')
+ continue;
+
+ return true;
+ }
+ return false;
+}
+
+
+/*
+=================
+=================
+*/
+
+
+
+
+int lookupControl( char *string )
+{
+ if (stricmp(string,"X")==0) return STUDIO_X;
+ if (stricmp(string,"Y")==0) return STUDIO_Y;
+ if (stricmp(string,"Z")==0) return STUDIO_Z;
+ if (stricmp(string,"XR")==0) return STUDIO_XR;
+ if (stricmp(string,"YR")==0) return STUDIO_YR;
+ if (stricmp(string,"ZR")==0) return STUDIO_ZR;
+
+ if (stricmp(string,"LX")==0) return STUDIO_LX;
+ if (stricmp(string,"LY")==0) return STUDIO_LY;
+ if (stricmp(string,"LZ")==0) return STUDIO_LZ;
+ if (stricmp(string,"LXR")==0) return STUDIO_LXR;
+ if (stricmp(string,"LYR")==0) return STUDIO_LYR;
+ if (stricmp(string,"LZR")==0) return STUDIO_LZR;
+
+ if (stricmp(string,"LM")==0) return STUDIO_LINEAR;
+ if (stricmp(string,"LQ")==0) return STUDIO_QUADRATIC_MOTION;
+
+ return -1;
+}
+
+
+
+/*
+=================
+=================
+*/
+
+int LookupPoseParameter( char *name )
+{
+ int i;
+ for ( i = 0; i < g_numposeparameters; i++)
+ {
+ if (!stricmp( name, g_pose[i].name))
+ {
+ return i;
+ }
+ }
+ V_strcpy_safe( g_pose[i].name, name );
+ g_numposeparameters = i + 1;
+
+ if (g_numposeparameters > MAXSTUDIOPOSEPARAM)
+ {
+ TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM );
+ }
+
+ return i;
+}
+
+
+//-----------------------------------------------------------------------------
+// Stuff for writing a makefile to build models incrementally.
+//-----------------------------------------------------------------------------
+s_sourceanim_t *FindSourceAnim( s_source_t *pSource, const char *pAnimName )
+{
+ int nCount = pSource->m_Animations.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_sourceanim_t *pAnim = &pSource->m_Animations[i];
+ if ( !Q_stricmp( pAnimName, pAnim->animationname ) )
+ return pAnim;
+ }
+ return NULL;
+}
+
+const s_sourceanim_t *FindSourceAnim( const s_source_t *pSource, const char *pAnimName )
+{
+ if ( !pAnimName[0] )
+ return NULL;
+
+ int nCount = pSource->m_Animations.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ const s_sourceanim_t *pAnim = &pSource->m_Animations[i];
+ if ( !Q_stricmp( pAnimName, pAnim->animationname ) )
+ return pAnim;
+ }
+ return NULL;
+}
+
+s_sourceanim_t *FindOrAddSourceAnim( s_source_t *pSource, const char *pAnimName )
+{
+ if ( !pAnimName[0] )
+ return NULL;
+
+ int nCount = pSource->m_Animations.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_sourceanim_t *pAnim = &pSource->m_Animations[i];
+ if ( !Q_stricmp( pAnimName, pAnim->animationname ) )
+ return pAnim;
+ }
+
+ int nIndex = pSource->m_Animations.AddToTail();
+ s_sourceanim_t *pAnim = &pSource->m_Animations[nIndex];
+ memset( pAnim, 0, sizeof(s_sourceanim_t) );
+ Q_strncpy( pAnim->animationname, pAnimName, sizeof(pAnim->animationname) );
+ return pAnim;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle the $boneflexdriver command
+// QC: $boneflexdriver <bone name> <tx|ty|tz> <flex controller name> <min> <max>
+//-----------------------------------------------------------------------------
+void Cmd_BoneFlexDriver()
+{
+ CDisableUndoScopeGuard undoDisable; // Turn of Dme undo
+
+ // Find or create the DmeBoneFlexDriverList
+ CDmeBoneFlexDriverList *pDmeBoneFlexDriverList = GetElement< CDmeBoneFlexDriverList >( g_hDmeBoneFlexDriverList );
+ if ( !pDmeBoneFlexDriverList )
+ {
+ pDmeBoneFlexDriverList = CreateElement< CDmeBoneFlexDriverList >( "boneDriverFlexList", DMFILEID_INVALID );
+ if ( pDmeBoneFlexDriverList )
+ {
+ g_hDmeBoneFlexDriverList = pDmeBoneFlexDriverList->GetHandle();
+ }
+ }
+
+ if ( !pDmeBoneFlexDriverList )
+ {
+ MdlError( "%s: Couldn't find or create DmeBoneDriverFlexList\n", "$boneflexdriver" );
+ return;
+ }
+
+ // <bone name>
+ GetToken( false );
+ CDmeBoneFlexDriver *pDmeBoneFlexDriver = pDmeBoneFlexDriverList->FindOrCreateBoneFlexDriver( token );
+ if ( !pDmeBoneFlexDriver )
+ {
+ MdlError( "%s: Couldn't find or create DmeBoneFlexDriver for bone \"%s\"\n", "$boneflexdriver", token );
+ return;
+ }
+
+ // <tx|ty|tz|rx|ry|rz>
+ GetToken( false );
+ const char *ppszComponentTypeList[] = { "tx", "ty", "tz" };
+ int nBoneComponent = -1;
+ for ( int i = 0; i < ARRAYSIZE( ppszComponentTypeList ); ++i )
+ {
+ if ( StringHasPrefix( token, ppszComponentTypeList[i] ) )
+ {
+ nBoneComponent = i;
+ break;
+ }
+ }
+
+ if ( nBoneComponent < STUDIO_BONE_FLEX_TX || nBoneComponent > STUDIO_BONE_FLEX_TZ )
+ {
+ TokenError( "%s: Invalid bone component, must be one of <tx|ty|tz>\n", "$boneflexdriver" );
+ return;
+ }
+
+ // <flex controller name>
+ GetToken( false );
+ CDmeBoneFlexDriverControl *pDmeBoneFlexDriverControl = pDmeBoneFlexDriver->FindOrCreateControl( token );
+ if ( !pDmeBoneFlexDriverControl )
+ {
+ MdlError( "%s: Couldn't find or create DmeBoneFlexDriverControl for bone \"%s\"\n", "$boneflexdriver", token );
+ return;
+ }
+
+ pDmeBoneFlexDriverControl->m_nBoneComponent = nBoneComponent;
+
+ // <min>
+ GetToken( false );
+ pDmeBoneFlexDriverControl->m_flMin = verify_atof( token );
+
+ // <max>
+ GetToken( false );
+ pDmeBoneFlexDriverControl->m_flMax = verify_atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle the $checkuv command
+// QC: $checkuv [0to1] [overlap] [inverse] [gutter <res> <min>]
+//-----------------------------------------------------------------------------
+void Cmd_CheckUV()
+{
+ g_StudioMdlCheckUVCmd.ClearCheck( CCheckUVCmd::CHECK_UV_ALL_FLAGS );
+
+ while ( TokenAvailable() && GetToken( false ) )
+ {
+ if ( !V_stricmp( token, "0to1" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_NORMALIZED );
+ }
+ else if ( !V_stricmp( token, "overlap" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_OVERLAP );
+ }
+ else if ( !V_stricmp( token, "inverse" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_INVERSE );
+ }
+ else if ( !V_stricmp( token, "gutter" ) )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_FLAG_GUTTER );
+ if ( TokenAvailable() && GetToken( false ) )
+ {
+ if ( V_isdigit( *token ) )
+ {
+ const int nOptRes = V_atoi( token );
+ if ( nOptRes <= 0 )
+ {
+ MdlError( "$checkuv: Invalid resolution, \"%s\", for gutter check specified, must be > 0\n", token );
+ return;
+ }
+
+ g_StudioMdlCheckUVCmd.m_nOptGutterTexWidth = nOptRes;
+ g_StudioMdlCheckUVCmd.m_nOptGutterTexHeight = nOptRes;
+
+ if ( TokenAvailable() && GetToken( false ) )
+ {
+ if ( V_isdigit( *token ) )
+ {
+ const int nOptMin = V_atoi( token );
+ if ( nOptMin <= 0 )
+ {
+ MdlError( "$checkuv: Invalid minimum, \"%s\", for gutter check specified, must be > 0\n", token );
+ return;
+ }
+
+ g_StudioMdlCheckUVCmd.m_nOptGutterMin = nOptMin;
+ }
+ else
+ {
+ UnGetToken();
+ }
+ }
+ }
+ else
+ {
+ UnGetToken();
+ }
+ }
+ }
+ else
+ {
+ MdlError( "$checkuv: Unknown argument \"%s\", expected one of [ 0to1, overlap, inverse, gutter ]\n", token );
+ return;
+ }
+ }
+
+ if ( !g_StudioMdlCheckUVCmd.DoAnyCheck() )
+ {
+ g_StudioMdlCheckUVCmd.SetCheck( CCheckUVCmd::CHECK_UV_ALL_FLAGS );
+ }
+}
+
+
+void Cmd_PoseParameter( )
+{
+ if ( g_numposeparameters >= MAXSTUDIOPOSEPARAM )
+ {
+ TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM );
+ }
+
+ int i = LookupPoseParameter( token );
+
+ // name
+ GetToken (false);
+ V_strcpy_safe( g_pose[i].name, token );
+
+ 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 );
+ }
+ }
+}
+
+
+/*
+=================
+=================
+*/
+
+int LookupTexture( const char *pTextureName, bool bRelativePath )
+{
+ char pTextureNoExt[MAX_PATH];
+ char pTextureBase[MAX_PATH];
+ char pTextureBase2[MAX_PATH];
+ Q_StripExtension( pTextureName, pTextureNoExt, sizeof(pTextureNoExt) );
+ Q_FileBase( pTextureName, pTextureBase, sizeof(pTextureBase) );
+
+ int nFlags = bRelativePath ? RELATIVE_TEXTURE_PATH_SPECIFIED : 0;
+ int i;
+ for ( i = 0; i < g_numtextures; i++ )
+ {
+ if ( g_texture[i].flags == nFlags )
+ {
+ if ( !Q_stricmp( pTextureNoExt, g_texture[i].name ) )
+ return i;
+ continue;
+ }
+
+ // Comparing relative vs non-relative
+ if ( bRelativePath )
+ {
+ if ( !Q_stricmp( pTextureBase, g_texture[i].name ) )
+ return i;
+ continue;
+ }
+
+ // Comparing non-relative vs relative
+ Q_FileBase( g_texture[i].name, pTextureBase2, sizeof(pTextureBase2) );
+ if ( !Q_stricmp( pTextureNoExt, pTextureBase2 ) )
+ return i;
+ }
+
+ if ( i >= MAXSTUDIOSKINS )
+ {
+ MdlError("Too many materials used, max %d\n", ( int )MAXSTUDIOSKINS );
+ }
+
+ Q_strncpy( g_texture[i].name, pTextureNoExt, sizeof(g_texture[i].name) );
+ g_texture[i].material = -1;
+ g_texture[i].flags = nFlags;
+ g_numtextures++;
+ return i;
+}
+
+
+void Cmd_RenameMaterial( void )
+{
+ char from[256];
+ char to[256];
+
+ GetToken( false );
+ V_strcpy_safe( from, token );
+
+ GetToken( false );
+ V_strcpy_safe( to, token );
+
+ int i;
+ for (i = 0; i < g_numtextures; i++)
+ {
+ if (stricmp( g_texture[i].name, from ) == 0)
+ {
+ V_strcpy_safe( g_texture[i].name, to );
+ return;
+ }
+ }
+ MdlError( "unknown material \"%s\" in rename\n", from );
+}
+
+
+int UseTextureAsMaterial( int textureindex )
+{
+ if ( g_texture[textureindex].material == -1 )
+ {
+ if (g_bDumpMaterials)
+ {
+ printf("material %d %d %s\n", textureindex, g_nummaterials, g_texture[textureindex].name );
+ }
+ g_material[g_nummaterials] = textureindex;
+ g_texture[textureindex].material = g_nummaterials++;
+ }
+
+ return g_texture[textureindex].material;
+}
+
+int MaterialToTexture( int material )
+{
+ int i;
+ for (i = 0; i < g_numtextures; i++)
+ {
+ if (g_texture[i].material == material)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+//Wrong name for the use of it.
+void scale_vertex( Vector &org )
+{
+ org[0] = org[0] * g_currentscale;
+ org[1] = org[1] * g_currentscale;
+ org[2] = org[2] * g_currentscale;
+}
+
+
+
+void SetSkinValues( )
+{
+ int i, j;
+ int index;
+
+ // Check all textures to see if we have relative paths specified
+ for (i = 0; i < g_numtextures; i++)
+ {
+ if ( g_texture[i].flags & RELATIVE_TEXTURE_PATH_SPECIFIED )
+ {
+ // Add an empty path to prepend if anything specifies a relative path
+ cdtextures[numcdtextures] = 0;
+ ++numcdtextures;
+ break;
+ }
+ }
+
+ if ( numcdtextures == 0 )
+ {
+ char szName[MAX_PATH];
+
+ // strip down till it finds "models"
+ V_strcpy_safe( szName, fullpath );
+ while (szName[0] != '\0' && strnicmp( "models", szName, 6 ) != 0)
+ {
+ strcpy( &szName[0], &szName[1] );
+ }
+ if (szName[0] != '\0')
+ {
+ Q_StripFilename( szName );
+ V_strcat_safe( szName, "/" );
+ }
+ else
+ {
+// if( *g_pPlatformName )
+// {
+// V_strcat_safe( szName, "platform_" );
+// V_strcat_safe( szName, g_pPlatformName );
+// V_strcat_safe( szName, "/" );
+// }
+ V_strcpy_safe( szName, "models/" );
+ V_strcat_safe( szName, outname );
+ Q_StripExtension( szName, szName, sizeof( szName ) );
+ V_strcat_safe( szName, "/" );
+ }
+ cdtextures[0] = strdup( szName );
+ numcdtextures = 1;
+ }
+
+ for (i = 0; i < g_numtextures; i++)
+ {
+ char szName[256];
+ Q_StripExtension( g_texture[i].name, szName, sizeof( szName ) );
+ Q_strncpy( g_texture[i].name, szName, sizeof( g_texture[i].name ) );
+ }
+
+ // build texture groups
+ for (i = 0; i < MAXSTUDIOSKINS; i++)
+ {
+ for (j = 0; j < MAXSTUDIOSKINS; j++)
+ {
+ g_skinref[i][j] = j;
+ }
+ }
+ index = 0;
+ for (i = 0; i < g_numtexturelayers[0]; i++)
+ {
+ for (j = 0; j < g_numtexturereps[0]; j++)
+ {
+ g_skinref[i][g_texturegroup[0][0][j]] = g_texturegroup[0][i][j];
+ }
+ }
+
+ if (i != 0)
+ {
+ g_numskinfamilies = i;
+ }
+ else
+ {
+ g_numskinfamilies = 1;
+ }
+ g_numskinref = g_numtextures;
+
+ // printf ("width: %i height: %i\n",width, height);
+ /*
+ printf ("adjusted width: %i height: %i top : %i left: %i\n",
+ pmesh->skinwidth, pmesh->skinheight, pmesh->skintop, pmesh->skinleft );
+ */
+}
+
+/*
+=================
+=================
+*/
+
+
+int LookupXNode( char *name )
+{
+ int i;
+ for ( i = 1; i <= g_numxnodes; i++)
+ {
+ if (stricmp( name, g_xnodename[i] ) == 0)
+ {
+ return i;
+ }
+ }
+ g_xnodename[i] = strdup( name );
+ g_numxnodes = i;
+ return i;
+}
+
+
+/*
+=================
+=================
+*/
+
+char g_szFilename[1024];
+FILE *g_fpInput;
+char g_szLine[4096];
+int g_iLinecount;
+
+
+void Build_Reference( s_source_t *pSource, const char *pAnimName )
+{
+ int i, parent;
+ Vector angle;
+
+ s_sourceanim_t *pReferenceAnim = FindSourceAnim( pSource, pAnimName );
+ for (i = 0; i < pSource->numbones; i++)
+ {
+ matrix3x4_t m;
+ if ( pReferenceAnim )
+ {
+ AngleMatrix( pReferenceAnim->rawanim[0][i].rot, m );
+ m[0][3] = pReferenceAnim->rawanim[0][i].pos[0];
+ m[1][3] = pReferenceAnim->rawanim[0][i].pos[1];
+ m[2][3] = pReferenceAnim->rawanim[0][i].pos[2];
+ }
+ else
+ {
+ SetIdentityMatrix( m );
+ }
+
+ parent = pSource->localBone[i].parent;
+ if (parent == -1)
+ {
+ // scale the done pos.
+ // calc rotational matrices
+ MatrixCopy( m, pSource->boneToPose[i] );
+ }
+ else
+ {
+ // calc compound rotational matrices
+ // FIXME : Hey, it's orthogical so inv(A) == transpose(A)
+ Assert( parent < i );
+ ConcatTransforms( pSource->boneToPose[parent], m, pSource->boneToPose[i] );
+ }
+ // printf("%3d %f %f %f\n", i, psource->bonefixup[i].worldorg[0], psource->bonefixup[i].worldorg[1], psource->bonefixup[i].worldorg[2] );
+ /*
+ AngleMatrix( angle, m );
+ printf("%8.4f %8.4f %8.4f\n", m[0][0], m[1][0], m[2][0] );
+ printf("%8.4f %8.4f %8.4f\n", m[0][1], m[1][1], m[2][1] );
+ printf("%8.4f %8.4f %8.4f\n", m[0][2], m[1][2], m[2][2] );
+ */
+ }
+}
+
+
+
+
+int Grab_Nodes( s_node_t *pnodes )
+{
+ int index;
+ char name[1024];
+ int parent;
+ int numbones = 0;
+
+ for (index = 0; index < MAXSTUDIOSRCBONES; index++)
+ {
+ pnodes[index].parent = -1;
+ }
+
+ while (GetLineInput())
+ {
+ if (sscanf( g_szLine, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3)
+ {
+ // check for duplicated bones
+ /*
+ if (strlen(pnodes[index].name) != 0)
+ {
+ MdlError( "bone \"%s\" exists more than once\n", name );
+ }
+ */
+
+ V_strcpy_safe( pnodes[index].name, name );
+ pnodes[index].parent = parent;
+ if (index > numbones)
+ {
+ numbones = index;
+ }
+ }
+ else
+ {
+ return numbones + 1;
+ }
+ }
+ MdlError( "Unexpected EOF at line %d\n", g_iLinecount );
+ return 0;
+}
+
+
+
+
+void clip_rotations( RadianEuler& rot )
+{
+ int j;
+ // clip everything to : -M_PI <= x < M_PI
+
+ for (j = 0; j < 3; j++) {
+ while (rot[j] >= M_PI)
+ rot[j] -= M_PI*2;
+ while (rot[j] < -M_PI)
+ rot[j] += M_PI*2;
+ }
+}
+
+
+void clip_rotations( Vector& rot )
+{
+ int j;
+ // clip everything to : -180 <= x < 180
+
+ for (j = 0; j < 3; j++) {
+ while (rot[j] >= 180)
+ rot[j] -= 180*2;
+ while (rot[j] < -180)
+ rot[j] += 180*2;
+ }
+}
+
+
+
+/*
+=================
+Cmd_Eyeposition
+=================
+*/
+void Cmd_Eyeposition (void)
+{
+// rotate points into frame of reference so g_model points down the positive x
+// axis
+ // FIXME: these coords are bogus
+ GetToken (false);
+ eyeposition[1] = verify_atof (token);
+
+ GetToken (false);
+ eyeposition[0] = -verify_atof (token);
+
+ GetToken (false);
+ eyeposition[2] = verify_atof (token);
+}
+
+
+//-----------------------------------------------------------------------------
+// Cmd_MaxEyeDeflection
+//-----------------------------------------------------------------------------
+void Cmd_MaxEyeDeflection()
+{
+ GetToken( false );
+ g_flMaxEyeDeflection = cosf( verify_atof( token ) * M_PI / 180.0f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Cmd_Illumposition
+//-----------------------------------------------------------------------------
+void Cmd_Illumposition( void )
+{
+ GetToken( false );
+ illumposition[0] = verify_atof( token );
+
+ GetToken( false );
+ illumposition[1] = verify_atof( token );
+
+ GetToken( false );
+ illumposition[2] = verify_atof( token );
+
+ if ( TokenAvailable() )
+ {
+ GetToken( false );
+
+ Q_strncpy( g_attachment[g_numattachments].name, "__illumPosition", sizeof(g_attachment[g_numattachments].name) );
+ Q_strncpy( g_attachment[g_numattachments].bonename, token, sizeof(g_attachment[g_numattachments].bonename) );
+ AngleMatrix( QAngle( 0, 0, 0 ), illumposition, g_attachment[g_numattachments].local );
+ g_attachment[g_numattachments].type |= IS_RIGID;
+
+ g_illumpositionattachment = g_numattachments + 1;
+ ++g_numattachments;
+ }
+ else
+ {
+ g_illumpositionattachment = 0;
+
+ // rotate points into frame of reference so
+ // g_model points down the positive x axis
+ // FIXME: these coords are bogus
+ float flTemp = illumposition[0];
+ illumposition[0] = -illumposition[1];
+ illumposition[1] = flTemp;
+ }
+
+ illumpositionset = true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process Cmd_Modelname
+//-----------------------------------------------------------------------------
+void ProcessModelName( const char *pModelName )
+{
+ // Abort early if modelname is too big
+ // - actually that's okay, it's just an identifier and can be truncated
+
+ g_bHasModelName = true;
+ Q_strncpy( outname, pModelName, sizeof( outname ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse Cmd_Modelname
+//-----------------------------------------------------------------------------
+void Cmd_Modelname (void)
+{
+ GetToken (false);
+ if ( token[0] == '/' || token[0] == '\\' )
+ {
+ MdlWarning( "$modelname key has slash as first character. Removing.\n" );
+ ProcessModelName( &token[1] );
+ }
+ else
+ {
+ ProcessModelName( token );
+ }
+}
+
+void Cmd_Autocenter()
+{
+ g_centerstaticprop = true;
+}
+
+/*
+===============
+===============
+*/
+
+
+//-----------------------------------------------------------------------------
+// Parse the body command from a .qc file
+//-----------------------------------------------------------------------------
+void ProcessOptionStudio( s_model_t *pmodel, const char *pFullPath, CDmeSourceSkin *pSkin )
+{
+ Q_strncpy( pmodel->filename, pFullPath, sizeof(pmodel->filename) );
+
+ if ( pSkin->m_flScale != 0.0f )
+ {
+ pmodel->scale = g_currentscale = pSkin->m_flScale;
+ }
+ else
+ {
+ pmodel->scale = g_currentscale = g_defaultscale;
+ }
+
+ // load source
+ pmodel->source = Load_Source( pmodel->filename, "", pSkin->m_bFlipTriangles, true );
+
+ // Reset currentscale to whatever global we currently have set
+ // g_defaultscale gets set in Cmd_ScaleUp everytime the $scale command is used.
+ g_currentscale = g_defaultscale;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse the studio options from a .qc file
+//-----------------------------------------------------------------------------
+bool ParseOptionStudio( CDmeSourceSkin *pSkin )
+{
+ if ( !GetToken( false ) )
+ return false;
+
+ pSkin->SetRelativeFileName( token );
+ while ( TokenAvailable() )
+ {
+ GetToken(false);
+ if ( !Q_stricmp( "reverse", token ) )
+ {
+ pSkin->m_bFlipTriangles = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( "scale", token ) )
+ {
+ GetToken(false);
+ pSkin->m_flScale = verify_atof( token );
+ continue;
+ }
+
+ if ( !Q_stricmp( "faces", token ) )
+ {
+ GetToken( false );
+ GetToken( false );
+ continue;
+ }
+
+ if ( !Q_stricmp( "bias", token ) )
+ {
+ GetToken( false );
+ continue;
+ }
+
+ if ( !Q_stricmp( "{", token ) )
+ {
+ UnGetToken( );
+ break;
+ }
+
+ MdlError("unknown command \"%s\"\n", token );
+ return false;
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse + process the studio options from a .qc file
+//-----------------------------------------------------------------------------
+void Option_Studio( s_model_t *pmodel )
+{
+ CDmeSourceSkin *pSourceSkin = CreateElement< CDmeSourceSkin >( "" );
+
+ // Set defaults
+ pSourceSkin->m_flScale = g_defaultscale;
+
+ if ( ParseOptionStudio( pSourceSkin ) )
+ {
+ ProcessOptionStudio( pmodel, pSourceSkin->GetRelativeFileName(), pSourceSkin );
+ }
+ DestroyElement( pSourceSkin );
+}
+
+
+int Option_Blank( )
+{
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, sizeof( s_model_t ) );
+
+ g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) );
+ g_model[g_nummodels]->source = g_source[g_numsources];
+ g_numsources++;
+
+ g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
+
+ V_strcpy_safe( g_model[g_nummodels]->name, "blank" );
+
+ g_bodypart[g_numbodyparts].nummodels++;
+ g_nummodels++;
+ return 0;
+}
+
+
+void Cmd_Bodygroup( )
+{
+ int is_started = 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;
+ }
+ V_strcpy_safe( g_bodypart[g_numbodyparts].name, token );
+
+ do
+ {
+ GetToken (true);
+ if (endofscript)
+ return;
+ else if (token[0] == '{')
+ {
+ is_started = 1;
+ }
+ else if (token[0] == '}')
+ {
+ break;
+ }
+ else if (stricmp("studio", token ) == 0)
+ {
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, 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] );
+
+ // Body command should add any flex commands in the source loaded
+ if ( g_model[g_nummodels]->source )
+ {
+ AddBodyFlexData( g_model[g_nummodels]->source, g_nummodels );
+ AddBodyAttachments( g_model[g_nummodels]->source );
+ }
+
+ g_nummodels++;
+ }
+ else if (stricmp("blank", token ) == 0)
+ {
+ Option_Blank( );
+ }
+ else
+ {
+ MdlError("unknown bodygroup option: \"%s\"\n", token );
+ }
+ } while (1);
+
+ g_numbodyparts++;
+ return;
+}
+
+
+//-----------------------------------------------------------------------------
+// Add A Body Flex Rule
+//-----------------------------------------------------------------------------
+void AddBodyFlexFetchRule(
+ s_source_t *pSource,
+ s_flexrule_t *pRule,
+ int rawIndex,
+ const CUtlVector< int > &pRawIndexToRemapSourceIndex,
+ const CUtlVector< int > &pRawIndexToRemapLocalIndex,
+ const CUtlVector< int > &pRemapSourceIndexToGlobalFlexControllerIndex )
+{
+ // Lookup the various indices of the requested input to fetch
+ // Relative to the remapped controls in the current s_source_t
+ const int remapSourceIndex = pRawIndexToRemapSourceIndex[ rawIndex ];
+ // Relative to the specific remapped control
+ const int remapLocalIndex = pRawIndexToRemapLocalIndex[ rawIndex ];
+ // The global flex controller index that the user ultimately twiddles
+ const int globalFlexControllerIndex = pRemapSourceIndexToGlobalFlexControllerIndex[ remapSourceIndex ];
+
+ // Get the Remap record
+ s_flexcontrollerremap_t &remap = pSource->m_FlexControllerRemaps[ remapSourceIndex ];
+ switch ( remap.m_RemapType )
+ {
+ case FLEXCONTROLLER_REMAP_PASSTHRU:
+ // Easy As!
+ pRule->op[ pRule->numops ].op = STUDIO_FETCH1;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ break;
+
+ case FLEXCONTROLLER_REMAP_EYELID:
+ if ( remapLocalIndex == 0 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_EyesUpDownFlexController >= 0 ? remap.m_EyesUpDownFlexController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_BlinkController >= 0 ? remap.m_BlinkController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = globalFlexControllerIndex; // CloseLid
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_DME_LOWER_EYELID;
+ pRule->op[ pRule->numops ].d.index = remap.m_MultiIndex; // CloseLidV
+ pRule->numops++;
+ }
+ else
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_EyesUpDownFlexController >= 0 ? remap.m_EyesUpDownFlexController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_BlinkController >= 0 ? remap.m_BlinkController : -1;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = globalFlexControllerIndex; // CloseLid
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_DME_UPPER_EYELID;
+ pRule->op[ pRule->numops ].d.index = remap.m_MultiIndex; // CloseLidV
+ pRule->numops++;
+ }
+ break;
+
+ case FLEXCONTROLLER_REMAP_2WAY:
+ // A little trickier... local index 0 is on the left, local index 1 is on the right
+ // Left Equivalent RemapVal( -1.0, 0.0, 0.0, 1.0 )
+ // Right Equivalent RemapVal( 0.0, 1.0, 0.0, 1.0 )
+ if ( remapLocalIndex == 0 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_2WAY_0;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ }
+ else
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_2WAY_1;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ }
+ break;
+
+ case FLEXCONTROLLER_REMAP_NWAY:
+ {
+ int nRemapCount = remap.m_RawControls.Count();
+ float flStep = ( nRemapCount > 2 ) ? 2.0f / ( nRemapCount - 1 ) : 0.0f;
+
+ if ( remapLocalIndex == 0 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -11.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -10.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -1.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = -1.0f + flStep;
+ pRule->numops++;
+ }
+ else if ( remapLocalIndex == nRemapCount - 1 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 1.0f - flStep;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 1.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 10.0f;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 11.0f;
+ pRule->numops++;
+ }
+ else
+ {
+ float flPeak = remapLocalIndex * flStep - 1.0f;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak - flStep;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = flPeak + flStep;
+ pRule->numops++;
+ }
+
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = remap.m_MultiIndex;
+ pRule->numops++;
+
+ pRule->op[ pRule->numops ].op = STUDIO_NWAY;
+ pRule->op[ pRule->numops ].d.index = globalFlexControllerIndex;
+ pRule->numops++;
+ }
+ break;
+ default:
+ Assert( 0 );
+ // This is an error condition
+ pRule->op[ pRule->numops ].op = STUDIO_CONST;
+ pRule->op[ pRule->numops ].d.value = 1.0f;
+ pRule->numops++;
+ break;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Add A Body Flex Rule
+//-----------------------------------------------------------------------------
+void AddBodyFlexRule(
+ s_source_t *pSource,
+ s_combinationrule_t &rule,
+ int nFlexDesc,
+ const CUtlVector< int > &pRawIndexToRemapSourceIndex,
+ const CUtlVector< int > &pRawIndexToRemapLocalIndex,
+ const CUtlVector< int > &pRemapSourceIndexToGlobalFlexControllerIndex )
+{
+ if ( g_numflexrules >= MAXSTUDIOFLEXRULES )
+ MdlError( "Line %d: Too many flex rules, max %d",
+ g_iLinecount, MAXSTUDIOFLEXRULES );
+
+ s_flexrule_t *pRule = &g_flexrule[g_numflexrules++];
+ pRule->flex = nFlexDesc;
+
+ // This will multiply the combination together
+ const int nCombinationCount = rule.m_Combination.Count();
+ if ( nCombinationCount )
+ {
+ for ( int j = 0; j < nCombinationCount; ++j )
+ {
+ // Handle any controller remapping
+ AddBodyFlexFetchRule( pSource, pRule, rule.m_Combination[ j ],
+ pRawIndexToRemapSourceIndex, pRawIndexToRemapLocalIndex,
+ pRemapSourceIndexToGlobalFlexControllerIndex );
+ }
+
+ if ( nCombinationCount > 1 )
+ {
+ pRule->op[ pRule->numops ].op = STUDIO_COMBO;
+ pRule->op[ pRule->numops ].d.index = nCombinationCount;
+ pRule->numops++;
+ }
+ }
+
+ // This will multiply in the suppressors
+ int nDominators = rule.m_Dominators.Count();
+ for ( int j = 0; j < nDominators; ++j )
+ {
+ const int nFactorCount = rule.m_Dominators[j].Count();
+ if ( nFactorCount )
+ {
+ for ( int k = 0; k < nFactorCount; ++k )
+ {
+ AddBodyFlexFetchRule( pSource, pRule, rule.m_Dominators[ j ][ k ],
+ pRawIndexToRemapSourceIndex, pRawIndexToRemapLocalIndex,
+ pRemapSourceIndexToGlobalFlexControllerIndex );
+ }
+
+ pRule->op[ pRule->numops ].op = STUDIO_DOMINATE;
+ pRule->op[ pRule->numops ].d.index = nFactorCount;
+ pRule->numops++;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds flex controller data to a particular source
+//-----------------------------------------------------------------------------
+void AddFlexControllers(
+ s_source_t *pSource )
+{
+ CUtlVector< int > &r2s = pSource->m_rawIndexToRemapSourceIndex;
+ CUtlVector< int > &r2l = pSource->m_rawIndexToRemapLocalIndex;
+ CUtlVector< int > &l2i = pSource->m_leftRemapIndexToGlobalFlexControllIndex;
+ CUtlVector< int > &r2i = pSource->m_rightRemapIndexToGlobalFlexControllIndex;
+
+ // Number of Raw controls in this source
+ const int nRawControlCount = pSource->m_CombinationControls.Count();
+ // Initialize rawToRemapIndices
+ r2s.SetSize( nRawControlCount );
+ r2l.SetSize( nRawControlCount );
+ for ( int i = 0; i < nRawControlCount; ++i )
+ {
+ r2s[ i ] = -1;
+ r2l[ i ] = -1;
+ }
+
+ // Number of Remapped Controls in this source
+ const int nRemappedControlCount = pSource->m_FlexControllerRemaps.Count();
+ l2i.SetSize( nRemappedControlCount );
+ r2i.SetSize( nRemappedControlCount );
+
+ for ( int i = 0; i < nRemappedControlCount; ++i )
+ {
+ s_flexcontrollerremap_t &remapControl = pSource->m_FlexControllerRemaps[ i ];
+
+ // Number of Raw Controls In This Remapped Control
+ const int nRemappedRawControlCount = remapControl.m_RawControls.Count();
+
+ // Figure out the mapping from raw to remapped
+ for ( int j = 0; j < nRemappedRawControlCount; ++j )
+ {
+ for ( int k = 0; k < nRawControlCount; ++k )
+ {
+ if ( remapControl.m_RawControls[ j ] == pSource->m_CombinationControls[ k ].name )
+ {
+ Assert( r2s[ k ] == -1 );
+ Assert( r2l[ k ] == -1 );
+ r2s[ k ] = i; // The index of the remapped control
+ r2l[ k ] = j; // The index of which control this is in the remap
+ break;
+ }
+ }
+ }
+
+ if ( remapControl.m_bIsStereo )
+ {
+ // The controls have to be named 'right_' and 'left_' and right has to be first for
+ // hlfaceposer to recognize them
+
+ // See if we can add two more flex controllers
+ if ( ( g_numflexcontrollers + 1 ) >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add split control %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ s_flexcontroller_t *pController;
+
+ int nLen = remapControl.m_Name.Length();
+ char *pTemp = (char*)_alloca( nLen + 7 ); // 'left_' && 'right_'
+
+ memcpy( pTemp + 6, remapControl.m_Name.Get(), nLen + 1 );
+ memcpy( pTemp, "right_", 6 );
+ pTemp[nLen + 6] = '\0';
+
+ remapControl.m_RightIndex = g_numflexcontrollers;
+ r2i[ i ] = g_numflexcontrollers;
+ pController = &g_flexcontroller[g_numflexcontrollers++];
+ Q_strncpy( pController->name, pTemp, sizeof( pController->name ) );
+ Q_strncpy( pController->type, pTemp, sizeof( pController->type ) );
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_2WAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+ else
+ {
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+
+ memcpy( pTemp + 5, remapControl.m_Name.Get(), nLen + 1 );
+ memcpy( pTemp, "left_", 5 );
+ pTemp[nLen + 5] = '\0';
+
+ remapControl.m_LeftIndex = g_numflexcontrollers;
+ l2i[ i ] = g_numflexcontrollers;
+ pController = &g_flexcontroller[g_numflexcontrollers++];
+ Q_strncpy( pController->name, pTemp, sizeof( pController->name ) );
+ Q_strncpy( pController->type, pTemp, sizeof( pController->type ) );
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_2WAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+ else
+ {
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+ }
+ else
+ {
+ // See if we can add one more flex controller
+ if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add control %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ remapControl.m_Index = g_numflexcontrollers;
+ r2i[ i ] = g_numflexcontrollers;
+ l2i[ i ] = g_numflexcontrollers;
+ s_flexcontroller_t *pController = &g_flexcontroller[g_numflexcontrollers++];
+ Q_strncpy( pController->name, remapControl.m_Name.Get(), sizeof( pController->name ) );
+ Q_strncpy( pController->type, remapControl.m_Name.Get(), sizeof( pController->type ) );
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_2WAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+ else
+ {
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+ }
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_NWAY || remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add value control for nWay %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ remapControl.m_MultiIndex = g_numflexcontrollers;
+ s_flexcontroller_t *pController = &g_flexcontroller[g_numflexcontrollers++];
+ const int nLen = remapControl.m_Name.Length();
+ char *pTemp = ( char * )_alloca( nLen + 6 + 1 ); // 'multi_' + 1 for the NULL
+
+ memcpy( pTemp, "multi_", 6 );
+ memcpy( pTemp + 6, remapControl.m_Name.Get(), nLen + 1 );
+ pTemp[nLen+6] = '\0';
+ Q_strncpy( pController->name, pTemp, sizeof( pController->name ) );
+ Q_strncpy( pController->type, pTemp, sizeof( pController->type ) );
+
+ pController->min = -1.0f;
+ pController->max = 1.0f;
+ }
+
+ if ( remapControl.m_RemapType == FLEXCONTROLLER_REMAP_EYELID )
+ {
+ // Make a blink controller
+
+ if ( g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ MdlError( "Line %d: Too many flex controllers, max %d, cannot add value control for nWay %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXCTRL, remapControl.m_Name.Get(), pSource->filename );
+
+ remapControl.m_BlinkController = g_numflexcontrollers;
+ s_flexcontroller_t *pController = &g_flexcontroller[g_numflexcontrollers++];
+
+ Q_strncpy( pController->name, "blink", sizeof( pController->name ) );
+ Q_strncpy( pController->type, "blink", sizeof( pController->type ) );
+
+ pController->min = 0.0f;
+ pController->max = 1.0f;
+ }
+ }
+
+#ifdef _DEBUG
+ for ( int j = 0; j != nRawControlCount; ++j )
+ {
+ Assert( r2s[ j ] != -1 );
+ Assert( r2l[ j ] != -1 );
+ }
+#endif // def _DEBUG
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds flex controller remappers
+//-----------------------------------------------------------------------------
+void AddBodyFlexRemaps( s_source_t *pSource )
+{
+ int nCount = pSource->m_FlexControllerRemaps.Count();
+ for( int i = 0; i < nCount; ++i )
+ {
+ int k = g_FlexControllerRemap.AddToTail();
+ s_flexcontrollerremap_t &remap = g_FlexControllerRemap[k];
+ remap = pSource->m_FlexControllerRemaps[i];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void AddBodyFlexRules( s_source_t *pSource )
+{
+ const int nRemapCount = pSource->m_FlexControllerRemaps.Count();
+ for ( int i = 0; i < nRemapCount; ++i )
+ {
+ s_flexcontrollerremap_t &remap = pSource->m_FlexControllerRemaps[ i ];
+ if ( remap.m_RemapType == FLEXCONTROLLER_REMAP_EYELID && !remap.m_EyesUpDownFlexName.IsEmpty() )
+ {
+ for ( int j = 0; j < g_numflexcontrollers; ++j )
+ {
+ if ( !Q_strcmp( g_flexcontroller[ j ].name, remap.m_EyesUpDownFlexName.Get() ) )
+ {
+ Assert( remap.m_EyesUpDownFlexController == -1 );
+ remap.m_EyesUpDownFlexController = j;
+ break;
+ }
+ }
+ }
+ }
+
+ const int nCount = pSource->m_CombinationRules.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_combinationrule_t &rule = pSource->m_CombinationRules[i];
+ s_flexkey_t &flexKey = g_flexkey[ pSource->m_nKeyStartIndex + rule.m_nFlex ];
+ AddBodyFlexRule( pSource, rule, flexKey.flexdesc,
+ pSource->m_rawIndexToRemapSourceIndex, pSource->m_rawIndexToRemapLocalIndex, pSource->m_leftRemapIndexToGlobalFlexControllIndex );
+ if ( flexKey.flexpair != 0 )
+ {
+ AddBodyFlexRule( pSource, rule, flexKey.flexpair,
+ pSource->m_rawIndexToRemapSourceIndex, pSource->m_rawIndexToRemapLocalIndex, pSource->m_rightRemapIndexToGlobalFlexControllIndex );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Process a body command
+//-----------------------------------------------------------------------------
+void AddBodyFlexData( s_source_t *pSource, int imodel )
+{
+ pSource->m_nKeyStartIndex = g_numflexkeys;
+
+ // Add flex keys
+ int nCount = pSource->m_FlexKeys.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ s_flexkey_t &key = pSource->m_FlexKeys[i];
+
+ if ( g_numflexkeys >= MAXSTUDIOFLEXKEYS )
+ MdlError( "Line %d: Too many flex keys, max %d, cannot add flexKey %s from source %s",
+ g_iLinecount, MAXSTUDIOFLEXKEYS, key.animationname, pSource->filename );
+
+ memcpy( &g_flexkey[g_numflexkeys], &key, sizeof(s_flexkey_t) );
+ g_flexkey[g_numflexkeys].imodel = imodel;
+
+ // flexpair was set up in AddFlexKey
+ if ( key.flexpair )
+ {
+ char mod[512];
+ Q_snprintf( mod, sizeof(mod), "%sL", key.animationname );
+ g_flexkey[g_numflexkeys].flexdesc = Add_Flexdesc( mod );
+ Q_snprintf( mod, sizeof(mod), "%sR", key.animationname );
+ g_flexkey[g_numflexkeys].flexpair = Add_Flexdesc( mod );
+ }
+ else
+ {
+ g_flexkey[g_numflexkeys].flexdesc = Add_Flexdesc( key.animationname );
+ g_flexkey[g_numflexkeys].flexpair = 0;
+ }
+
+ ++g_numflexkeys;
+ }
+
+ AddFlexControllers( pSource );
+
+ AddBodyFlexRemaps( pSource );
+}
+
+//-----------------------------------------------------------------------------
+// Comparison operator for s_attachment_t
+//-----------------------------------------------------------------------------
+bool s_attachment_t::operator==( const s_attachment_t &rhs ) const
+{
+ if ( Q_strcmp( name, rhs.name ) )
+ return false;
+
+ if ( Q_stricmp( bonename, rhs.bonename ) ||
+ bone != rhs.bone ||
+ type != rhs.type ||
+ flags != rhs.flags ||
+ Q_memcmp( local.Base(), rhs.local.Base(), sizeof( local ) ) )
+ {
+ RadianEuler iEuler, jEuler;
+ Vector iPos, jPos;
+ MatrixAngles( local, iEuler, iPos );
+ MatrixAngles( rhs.local, jEuler, jPos );
+ MdlWarning(
+ "Attachments with the same name but different parameters found\n"
+ " %s: ParentBone: %s Type: %d Flags: 0x%08x P: %6.2f %6.2f %6.2f R: %6.2f %6.2f %6.2f\n"
+ " %s: ParentBone: %s Type: %d Flags: 0x%08x P: %6.2f %6.2f %6.2f R: %6.2f %6.2f %6.2f\n",
+ name, bonename, type, flags,
+ iPos.x, iPos.y, iPos.z, RAD2DEG( iEuler.x ), RAD2DEG( iEuler.y ), RAD2DEG( iEuler.z ),
+ rhs.name, rhs.bonename, rhs.type, rhs.flags,
+ jPos.x, jPos.y, jPos.z, RAD2DEG( jEuler.x ), RAD2DEG( jEuler.y ), RAD2DEG( jEuler.z ) );
+
+ return false;
+ }
+
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Add attachments from the s_source_t that aren't already present in the
+// global attachment list. At this point, the attachments aren't linked
+// to the bone, but since that is done by string matching on the bone name
+// the test for an attachment being a duplicate is still valid this early.
+//-----------------------------------------------------------------------------
+void AddBodyAttachments( s_source_t *pSource )
+{
+ for ( int i = 0; i < pSource->m_Attachments.Count(); ++i )
+ {
+ const s_attachment_t &sourceAtt = pSource->m_Attachments[i];
+
+ bool bDuplicate = false;
+
+ for ( int j = 0; j < g_numattachments; ++j )
+ {
+ if ( sourceAtt == g_attachment[j] )
+ {
+ bDuplicate = true;
+ break;
+ }
+ }
+
+ if ( bDuplicate )
+ continue;
+
+ if ( g_numattachments >= ARRAYSIZE( g_attachment ) )
+ {
+ MdlWarning( "Too Many Attachments (Max %d), Ignoring Attachment %s:%s\n",
+ ARRAYSIZE( g_attachment ), pSource->filename, pSource->m_Attachments[i].name );
+ continue;;
+ }
+
+ memcpy( &g_attachment[g_numattachments], &( pSource->m_Attachments[i] ), sizeof( s_attachment_t ) );
+ ++g_numattachments;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Process a body command
+//-----------------------------------------------------------------------------
+void ProcessCmdBody( const char *pFullPath, CDmeSourceSkin *pSkin )
+{
+ 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, pSkin->m_SkinName.Get(), sizeof(g_bodypart[g_numbodyparts].name) );
+
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, sizeof( s_model_t ) );
+ g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels];
+ g_bodypart[g_numbodyparts].nummodels = 1;
+
+ ProcessOptionStudio( g_model[g_nummodels], pFullPath, pSkin );
+
+ // Body command should add any flex commands in the source loaded
+ if ( g_model[g_nummodels]->source )
+ {
+ AddBodyFlexData( g_model[g_nummodels]->source, g_nummodels );
+ AddBodyAttachments( g_model[g_nummodels]->source );
+ AddBodyFlexRules( g_model[ g_nummodels ]->source );
+ }
+
+ g_nummodels++;
+ g_numbodyparts++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse the body command from a .qc file
+//-----------------------------------------------------------------------------
+void Cmd_Body( )
+{
+ if ( !GetToken(false) )
+ return;
+
+ CDmeSourceSkin *pSourceSkin = CreateElement< CDmeSourceSkin >( "" );
+
+ // Set defaults
+ pSourceSkin->m_flScale = g_defaultscale;
+
+ pSourceSkin->m_SkinName = token;
+ if ( ParseOptionStudio( pSourceSkin ) )
+ {
+ ProcessCmdBody( pSourceSkin->GetRelativeFileName(), pSourceSkin );
+ }
+ DestroyElement( pSourceSkin );
+}
+
+
+
+/*
+===============
+===============
+*/
+
+void Grab_Animation( s_source_t *pSource, const char *pAnimName )
+{
+ Vector pos;
+ RadianEuler rot;
+ char cmd[1024];
+ int index;
+ int t = -99999999;
+ int size;
+
+ s_sourceanim_t *pAnim = FindOrAddSourceAnim( pSource, pAnimName );
+ pAnim->startframe = -1;
+
+ size = pSource->numbones * sizeof( s_bone_t );
+
+ while ( GetLineInput() )
+ {
+ if ( sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7 )
+ {
+ if ( pAnim->startframe < 0 )
+ {
+ MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ scale_vertex( pos );
+ VectorCopy( pos, pAnim->rawanim[t][index].pos );
+ VectorCopy( rot, pAnim->rawanim[t][index].rot );
+
+ clip_rotations( rot ); // !!!
+ continue;
+ }
+
+ if ( sscanf( g_szLine, "%1023s %d", cmd, &index ) == 0 )
+ {
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ continue;
+ }
+
+ if ( !Q_stricmp( cmd, "time" ) )
+ {
+ t = index;
+ if ( pAnim->startframe == -1 )
+ {
+ pAnim->startframe = t;
+ }
+ if ( t < pAnim->startframe )
+ {
+ MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ if ( t > pAnim->endframe )
+ {
+ pAnim->endframe = t;
+ }
+ t -= pAnim->startframe;
+
+ if ( t >= pAnim->rawanim.Count())
+ {
+ s_bone_t *ptr = NULL;
+ pAnim->rawanim.AddMultipleToTail( t - pAnim->rawanim.Count() + 1, &ptr );
+ }
+
+ if ( pAnim->rawanim[t] != NULL )
+ {
+ continue;
+ }
+
+ pAnim->rawanim[t] = (s_bone_t *)kalloc( 1, size );
+
+ // duplicate previous frames keys
+ if ( t > 0 && pAnim->rawanim[t-1] )
+ {
+ for ( int j = 0; j < pSource->numbones; j++ )
+ {
+ VectorCopy( pAnim->rawanim[t-1][j].pos, pAnim->rawanim[t][j].pos );
+ VectorCopy( pAnim->rawanim[t-1][j].rot, pAnim->rawanim[t][j].rot );
+ }
+ }
+ continue;
+ }
+
+ if ( !Q_stricmp( cmd, "end" ) )
+ {
+ pAnim->numframes = pAnim->endframe - pAnim->startframe + 1;
+
+ for ( t = 0; t < pAnim->numframes; t++ )
+ {
+ if ( pAnim->rawanim[t] == NULL)
+ {
+ MdlError( "%s is missing frame %d\n", pSource->filename, t + pAnim->startframe );
+ }
+ }
+
+ Build_Reference( pSource, pAnimName );
+ return;
+ }
+
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ MdlError( "unexpected EOF: %s\n", pSource->filename );
+}
+
+
+
+
+
+int Option_Activity( s_sequence_t *psequence )
+{
+ qboolean found;
+
+ found = false;
+
+ GetToken(false);
+ V_strcpy_safe( psequence->activityname, token );
+
+ GetToken(false);
+ psequence->actweight = verify_atoi(token);
+
+ if ( psequence->actweight == 0 )
+ {
+ TokenError( "Activity %s has a zero weight (weights must be integers > 0)\n", psequence->activityname );
+ }
+
+ return 0;
+}
+
+
+int Option_ActivityModifier( s_sequence_t *psequence )
+{
+ GetToken(false);
+ V_strcpy_safe( psequence->activitymodifier[ psequence->numactivitymodifiers++ ].name, token );
+
+ return 0;
+}
+
+
+/*
+===============
+===============
+*/
+
+
+int Option_Event ( s_sequence_t *psequence )
+{
+ if (psequence->numevents + 1 >= MAXSTUDIOEVENTS)
+ {
+ TokenError("too many events\n");
+ }
+
+ GetToken (false);
+
+ V_strcpy_safe( psequence->event[psequence->numevents].eventname, 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
+ V_strcpy_safe( psequence->event[psequence->numevents-1].options, token );
+ }
+
+ return 0;
+}
+
+
+
+void Option_IKRule( s_ikrule_t *pRule )
+{
+ // chain
+ GetToken( false );
+
+ int i;
+ for ( i = 0; i < g_numikchains; i++)
+ {
+ if (stricmp( token, g_ikchain[i].name ) == 0)
+ {
+ 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 (stricmp( token, "touch" ) == 0)
+ {
+ pRule->type = IK_SELF;
+
+ // bone
+ GetToken( false );
+ V_strcpy_safe( pRule->bonename, token );
+ }
+ else if (stricmp( token, "footstep" ) == 0)
+ {
+ 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 (stricmp( token, "attachment" ) == 0)
+ {
+ pRule->type = IK_ATTACHMENT;
+
+ // name of attachment
+ GetToken( false );
+ V_strcpy_safe( pRule->attachment, token );
+ }
+ else if (stricmp( token, "release" ) == 0)
+ {
+ pRule->type = IK_RELEASE;
+ }
+ else if (stricmp( token, "unlatch" ) == 0)
+ {
+ pRule->type = IK_UNLATCH;
+ }
+
+ pRule->contact = -1;
+
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ if (stricmp( token, "height" ) == 0)
+ {
+ GetToken( false );
+ pRule->height = verify_atof( token );
+ }
+ else if (stricmp( token, "target" ) == 0)
+ {
+ // slot
+ GetToken( false );
+ pRule->slot = verify_atoi( token );
+ }
+ else if (stricmp( token, "range" ) == 0)
+ {
+ // 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 (stricmp( token, "floor" ) == 0)
+ {
+ GetToken( false );
+ pRule->floor = verify_atof( token );
+ }
+ else if (stricmp( token, "pad" ) == 0)
+ {
+ GetToken( false );
+ pRule->radius = verify_atof( token ) / 2.0f;
+ }
+ else if (stricmp( token, "radius" ) == 0)
+ {
+ GetToken( false );
+ pRule->radius = verify_atof( token );
+ }
+ else if (stricmp( token, "contact" ) == 0)
+ {
+ GetToken( false );
+ pRule->contact = verify_atoi( token );
+ }
+ else if (stricmp( token, "usesequence" ) == 0)
+ {
+ pRule->usesequence = true;
+ pRule->usesource = false;
+ }
+ else if (stricmp( token, "usesource" ) == 0)
+ {
+ pRule->usesequence = false;
+ pRule->usesource = true;
+ }
+ else if (stricmp( token, "fakeorigin" ) == 0)
+ {
+ 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 (stricmp( token, "fakerotate" ) == 0)
+ {
+ QAngle 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 (stricmp( token, "bone" ) == 0)
+ {
+ V_strcpy_safe( pRule->bonename, token );
+ }
+ else
+ {
+ UnGetToken();
+ return;
+ }
+ }
+}
+
+
+/*
+=================
+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);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the default root rotation so that the Y axis is up instead of the Z axis (for Maya)
+//-----------------------------------------------------------------------------
+void ProcessUpAxis( const RadianEuler &angles )
+{
+ g_defaultrotation = angles;
+}
+
+
+//-----------------------------------------------------------------------------
+// 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
+ RadianEuler angles( 0.0f, 0.0f, M_PI / 2.0f );
+ GetToken (false);
+ if (!Q_stricmp( token, "x" ))
+ {
+ // rotate 90 degrees around y to move x into z
+ angles.x = 0.0f;
+ angles.y = M_PI / 2.0f;
+ }
+ else if (!Q_stricmp( token, "-x" ))
+ {
+ // untested
+ angles.x = 0.0f;
+ angles.y = -M_PI / 2.0f;
+ }
+ else if (!Q_stricmp( token, "y" ))
+ {
+ // rotate 90 degrees around x to move y into z
+ angles.x = M_PI / 2.0f;
+ angles.y = 0.0f;
+ }
+ else if (!Q_stricmp( token, "-y" ))
+ {
+ // untested
+ angles.x = -M_PI / 2.0f;
+ angles.y = 0.0f;
+ }
+ else if (!Q_stricmp( token, "z" ))
+ {
+ // there's still a built in 90 degree Z rotation :(
+ angles.x = 0.0f;
+ angles.y = 0.0f;
+ }
+ else if (!Q_stricmp( token, "-z" ))
+ {
+ // there's still a built in 90 degree Z rotation :(
+ angles.x = 0.0f;
+ angles.y = 0.0f;
+ }
+ else
+ {
+ TokenError( "unknown $upaxis option: \"%s\"\n", token );
+ return;
+ }
+
+ ProcessUpAxis( angles );
+}
+
+
+/*
+=================
+=================
+*/
+void Cmd_ScaleUp (void)
+{
+ GetToken (false);
+ g_defaultscale = verify_atof (token);
+
+ g_currentscale = g_defaultscale;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets how what size chunks to cut the animations into
+//-----------------------------------------------------------------------------
+void Cmd_AnimBlockSize( void )
+{
+ GetToken( false );
+ g_animblocksize = verify_atoi( token );
+ if (g_animblocksize < 1024)
+ {
+ g_animblocksize *= 1024;
+ }
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ if (!Q_stricmp( token, "nostall" ))
+ {
+ g_bNoAnimblockStall = true;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+static void FlipFacing( s_source_t *pSrc )
+{
+ unsigned short tmp;
+
+ int i, j;
+ for( i = 0; i < pSrc->nummeshes; i++ )
+ {
+ s_mesh_t *pMesh = &pSrc->mesh[i];
+ for( j = 0; j < pMesh->numfaces; j++ )
+ {
+ s_face_t &f = pSrc->face[pMesh->faceoffset + j];
+ tmp = f.b; f.b = f.c; f.c = tmp;
+ }
+ }
+}
+
+
+// Processes source comment line and extracts information about the data file
+void ProcessSourceComment( s_source_t *psource, const char *pCommentString )
+{
+ if ( char const *szSceneComment = StringAfterPrefix( pCommentString, "// SCENE=" ) )
+ {
+ char szScene[1024];
+ Q_strncpy( szScene, szSceneComment, ARRAYSIZE( szScene ) );
+
+ Q_FixSlashes( szScene );
+
+ ProcessOriginalContentFile( psource->filename, szScene );
+ }
+}
+
+// Processes original content file "szOriginalContentFile" that was used to generate
+// data file "szDataFile"
+void ProcessOriginalContentFile( char const *szDataFile, char const *szOriginalContentFile )
+{
+ // Early out: if no p4
+ if ( g_bNoP4 )
+ return;
+
+ char const *szContentDirRootEnd = strstr( szDataFile, "\\content\\" );
+ char const *szSceneName = strstr( szOriginalContentFile, "\\content\\" );
+ if ( szContentDirRootEnd && szSceneName )
+ {
+ char chScenePath[ MAX_PATH ] = {0};
+ Q_snprintf( chScenePath, sizeof( chScenePath ) - 1, "%.*s%s",
+ szContentDirRootEnd - szDataFile, szDataFile, szSceneName );
+ EnsureDependencyFileCheckedIn( chScenePath );
+ }
+ else if ( szContentDirRootEnd && !szSceneName )
+ {
+ // Assume relative path
+ char chScenePath[ MAX_PATH ] = {0};
+ Q_snprintf( chScenePath, sizeof( chScenePath ) - 1, "%.*s%s",
+ max( strrchr( szDataFile, '\\' ), strrchr( szDataFile, '/' ) ) + 1 - szDataFile,
+ szDataFile, szOriginalContentFile );
+ EnsureDependencyFileCheckedIn( chScenePath );
+ }
+ else
+ {
+ MdlWarning( "ProcessOriginalContentFile for '%s' cannot detect scene source file from '%s'!\n", szDataFile, szOriginalContentFile );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks to see if the model source was already loaded
+//-----------------------------------------------------------------------------
+static s_source_t *FindCachedSource( const char* name, const char* xext )
+{
+ int i;
+
+ if( xext[0] )
+ {
+ // we know what extension is necessary. . look for it.
+ Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.%s", cddir[numdirs], name, xext );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ }
+ else
+ {
+ // we don't know what extension to use, so look for all of 'em.
+ Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.vrm", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.smd", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.dmx", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.xml", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ Q_snprintf (g_szFilename, sizeof(g_szFilename), "%s%s.obj", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if ( !Q_stricmp( g_szFilename, g_source[i]->filename ) )
+ return g_source[i];
+ }
+ /*
+ sprintf (g_szFilename, "%s%s.vta", cddir[numdirs], name );
+ for (i = 0; i < g_numsources; i++)
+ {
+ if (stricmp( g_szFilename, g_source[i]->filename ) == 0)
+ return g_source[i];
+ }
+ */
+ }
+
+ // Not found
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads an animation/model source
+//-----------------------------------------------------------------------------
+s_source_t *Load_Source( const char *name, const char *ext, bool reverse, bool isActiveModel )
+{
+ if ( g_numsources >= MAXSTUDIOSEQUENCES )
+ TokenError( "Load_Source( %s ) - overflowed g_numsources.", name );
+
+ Assert(name);
+ int namelen = strlen(name) + 1;
+ char* pTempName = (char*)_alloca( namelen );
+ char xext[32];
+ int result = false;
+
+ V_strncpy( pTempName, name, namelen );
+ Q_ExtractFileExtension( pTempName, xext, sizeof( xext ) );
+
+ if (xext[0] == '\0')
+ {
+ V_strcpy_safe( xext, ext );
+ }
+ else
+ {
+ Q_StripExtension( pTempName, pTempName, namelen );
+ }
+
+ s_source_t* pSource = FindCachedSource( pTempName, xext );
+ if (pSource)
+ {
+ if (isActiveModel)
+ pSource->isActiveModel = true;
+
+ return pSource;
+ }
+
+ g_source[g_numsources] = (s_source_t *)kalloc( 1, sizeof( s_source_t ) );
+ V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename );
+
+
+ if (isActiveModel)
+ {
+ g_source[g_numsources]->isActiveModel = true;
+ }
+
+ char const * load_extensions[] = { "vrm", "smd", "sma", "phys", "vta", "obj", "dmx", "xml" };
+ int ( *load_procs[] )( s_source_t * ) = { Load_VRM, Load_SMD, Load_SMD, Load_SMD, Load_VTA, Load_OBJ, Load_DMX, Load_DMX };
+
+ Assert( ARRAYSIZE(load_extensions) == ARRAYSIZE(load_procs) );
+ for ( int kk = 0; kk < ARRAYSIZE( load_extensions ); ++ kk )
+ {
+ if ( ( !result && xext[0] == '\0' ) || Q_stricmp( xext, load_extensions[kk] ) == 0)
+ {
+ Q_snprintf( g_szFilename, sizeof(g_szFilename), "%s%s.%s", cddir[numdirs], pTempName, load_extensions[kk] );
+ V_strcpy_safe( g_source[g_numsources]->filename, g_szFilename );
+ result = (load_procs[kk])( g_source[g_numsources] );
+
+ if ( result )
+ EnsureDependencyFileCheckedIn( g_source[g_numsources]->filename );
+ }
+ }
+
+ if (!g_bCreateMakefile && !result)
+ {
+ if (xext[0] == '\0')
+ TokenError( "could not load file '%s%s'\n", cddir[numdirs], pTempName );
+ else
+ TokenError( "could not load file '%s%s.%s'\n", cddir[numdirs], pTempName, xext );
+ }
+
+ if ( g_source[g_numsources]->numbones == 0 )
+ {
+ TokenError( "missing all bones in file '%s'\n", g_source[g_numsources]->filename );
+ }
+
+ // copy over default settings of when the model was loaded (since there's no actual animation for some of the systems)
+ VectorCopy( g_defaultadjust, g_source[g_numsources]->adjust );
+ g_source[g_numsources]->scale = 1.0f;
+ g_source[g_numsources]->rotation = g_defaultrotation;
+
+
+ g_numsources++;
+ if( reverse )
+ {
+ FlipFacing( g_source[g_numsources-1] );
+ }
+
+ return g_source[g_numsources-1];
+}
+
+
+s_sequence_t *LookupSequence( const char *name )
+{
+ int i;
+ for ( i = 0; i < g_sequence.Count(); ++i )
+ {
+ if ( !Q_stricmp( g_sequence[i].name, name ) )
+ return &g_sequence[i];
+ }
+ return NULL;
+}
+
+
+s_animation_t *LookupAnimation( const char *name )
+{
+ int i;
+ for ( 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][0] : NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: parse order dependant s_animcmd_t token for $animations
+//-----------------------------------------------------------------------------
+int ParseCmdlistToken( int &numcmds, s_animcmd_t *cmds )
+{
+ if (numcmds >= MAXSTUDIOCMDS)
+ {
+ return false;
+ }
+ s_animcmd_t *pcmd = &cmds[numcmds];
+ if (stricmp("fixuploop", token ) == 0)
+ {
+ pcmd->cmd = CMD_FIXUP;
+
+ GetToken( false );
+ pcmd->u.fixuploop.start = verify_atoi( token );
+ GetToken( false );
+ pcmd->u.fixuploop.end = verify_atoi( token );
+ }
+ else if (strnicmp("weightlist", token, 6 ) == 0)
+ {
+ GetToken( false );
+
+ int i;
+ for ( i = 1; i < g_numweightlist; i++)
+ {
+ if (stricmp( g_weightlist[i].name, token ) == 0)
+ {
+ break;
+ }
+ }
+ if (i == g_numweightlist)
+ {
+ TokenError( "unknown weightlist '%s\'\n", token );
+ }
+ pcmd->cmd = CMD_WEIGHTS;
+ pcmd->u.weightlist.index = i;
+ }
+ else if (stricmp("subtract", token ) == 0)
+ {
+ pcmd->cmd = CMD_SUBTRACT;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown subtract animation '%s\'\n", token );
+ }
+
+ pcmd->u.subtract.ref = extanim;
+
+ GetToken( false );
+ pcmd->u.subtract.frame = verify_atoi( token );
+
+ pcmd->u.subtract.flags |= STUDIO_POST;
+ }
+ else if (stricmp("presubtract", token ) == 0) // 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->u.subtract.ref = extanim;
+
+ GetToken( false );
+ pcmd->u.subtract.frame = verify_atoi( token );
+ }
+ else if (stricmp( "alignto", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ pcmd->u.ao.pBonename = NULL;
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignto animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+ pcmd->u.ao.motiontype = STUDIO_X | STUDIO_Y;
+ pcmd->u.ao.srcframe = 0;
+ pcmd->u.ao.destframe = 0;
+ }
+ else if (stricmp( "align", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ pcmd->u.ao.pBonename = NULL;
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown align animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+
+ // motion type to match
+ pcmd->u.ao.motiontype = 0;
+ GetToken( false );
+ int ctrl;
+ while ((ctrl = lookupControl( token )) != -1)
+ {
+ pcmd->u.ao.motiontype |= ctrl;
+ GetToken( false );
+ }
+ if (pcmd->u.ao.motiontype == 0)
+ {
+ TokenError( "missing controls on align\n" );
+ }
+
+ // frame of reference animation to match
+ pcmd->u.ao.srcframe = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.ao.destframe = verify_atoi( token );
+ }
+ else if (stricmp( "alignboneto", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ GetToken( false );
+ pcmd->u.ao.pBonename = strdup( token );
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignboneto animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+ pcmd->u.ao.motiontype = STUDIO_X | STUDIO_Y;
+ pcmd->u.ao.srcframe = 0;
+ pcmd->u.ao.destframe = 0;
+ }
+ else if (stricmp( "alignbone", token ) == 0)
+ {
+ pcmd->cmd = CMD_AO;
+
+ GetToken( false );
+ pcmd->u.ao.pBonename = strdup( token );
+
+ GetToken( false );
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignboneto animation '%s\'\n", token );
+ }
+
+ pcmd->u.ao.ref = extanim;
+
+ // motion type to match
+ pcmd->u.ao.motiontype = 0;
+ GetToken( false );
+ int ctrl;
+ while ((ctrl = lookupControl( token )) != -1)
+ {
+ pcmd->u.ao.motiontype |= ctrl;
+ GetToken( false );
+ }
+ if (pcmd->u.ao.motiontype == 0)
+ {
+ TokenError( "missing controls on align\n" );
+ }
+
+ // frame of reference animation to match
+ pcmd->u.ao.srcframe = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.ao.destframe = verify_atoi( token );
+ }
+ else if (stricmp( "match", token ) == 0)
+ {
+ pcmd->cmd = CMD_MATCH;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown match animation '%s\'\n", token );
+ }
+
+ pcmd->u.match.ref = extanim;
+ }
+ else if (stricmp( "matchblend", token ) == 0)
+ {
+ pcmd->cmd = CMD_MATCHBLEND;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ MdlError( "unknown match animation '%s\'\n", token );
+ }
+
+ pcmd->u.match.ref = extanim;
+
+ // frame of reference animation to match
+ GetToken( false );
+ pcmd->u.match.srcframe = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.match.destframe = verify_atoi( token );
+
+ // backup and starting match in here
+ GetToken( false );
+ pcmd->u.match.destpre = verify_atoi( token );
+
+ // continue blending match till here
+ GetToken( false );
+ pcmd->u.match.destpost = verify_atoi( token );
+
+ }
+ else if (stricmp( "worldspaceblend", token ) == 0)
+ {
+ pcmd->cmd = CMD_WORLDSPACEBLEND;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown worldspaceblend animation '%s\'\n", token );
+ }
+
+ pcmd->u.world.ref = extanim;
+ pcmd->u.world.startframe = 0;
+ pcmd->u.world.loops = false;
+ }
+ else if (stricmp( "worldspaceblendloop", token ) == 0)
+ {
+ pcmd->cmd = CMD_WORLDSPACEBLEND;
+
+ GetToken( false );
+
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown worldspaceblend animation '%s\'\n", token );
+ }
+
+ pcmd->u.world.ref = extanim;
+
+ GetToken( false );
+ pcmd->u.world.startframe = atoi( token );
+
+ pcmd->u.world.loops = true;
+ }
+ else if (stricmp( "rotateto", token ) == 0)
+ {
+ pcmd->cmd = CMD_ANGLE;
+
+ GetToken( false );
+ pcmd->u.angle.angle = verify_atof( token );
+ }
+ else if (stricmp( "ikrule", token ) == 0)
+ {
+ pcmd->cmd = CMD_IKRULE;
+
+ pcmd->u.ikrule.pRule = (s_ikrule_t *)kalloc( 1, sizeof( s_ikrule_t ) );
+
+ Option_IKRule( pcmd->u.ikrule.pRule );
+ }
+ else if (stricmp( "ikfixup", token ) == 0)
+ {
+ pcmd->cmd = CMD_IKFIXUP;
+
+ pcmd->u.ikfixup.pRule = (s_ikrule_t *)kalloc( 1, sizeof( s_ikrule_t ) );
+
+ Option_IKRule( pcmd->u.ikrule.pRule );
+ }
+ else if (stricmp( "walkframe", token ) == 0)
+ {
+ pcmd->cmd = CMD_MOTION;
+
+ // frame
+ GetToken( false );
+ pcmd->u.motion.iEndFrame = verify_atoi( token );
+
+ // motion type to match
+ pcmd->u.motion.motiontype = 0;
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ int ctrl = lookupControl( token );
+ if (ctrl != -1)
+ {
+ pcmd->u.motion.motiontype |= ctrl;
+ }
+ else
+ {
+ UnGetToken();
+ break;
+ }
+ }
+
+ /*
+ GetToken( false ); // X
+ pcmd->u.motion.x = verify_atof( token );
+
+ GetToken( false ); // Y
+ pcmd->u.motion.y = verify_atof( token );
+
+ GetToken( false ); // A
+ pcmd->u.motion.zr = verify_atof( token );
+ */
+ }
+ else if (stricmp( "walkalignto", token ) == 0)
+ {
+ pcmd->cmd = CMD_REFMOTION;
+
+ GetToken( false );
+ pcmd->u.motion.iEndFrame = verify_atoi( token );
+
+ pcmd->u.motion.iSrcFrame = pcmd->u.motion.iEndFrame;
+
+ GetToken( false ); // reference animation
+ s_animation_t *extanim = LookupAnimation( token );
+ if (extanim == NULL)
+ {
+ TokenError( "unknown alignto animation '%s\'\n", token );
+ }
+ pcmd->u.motion.pRefAnim = extanim;
+
+ pcmd->u.motion.iRefFrame = 0;
+
+ // motion type to match
+ pcmd->u.motion.motiontype = 0;
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ int ctrl = lookupControl( token );
+ if (ctrl != -1)
+ {
+ pcmd->u.motion.motiontype |= ctrl;
+ }
+ else
+ {
+ UnGetToken();
+ break;
+ }
+ }
+
+
+ /*
+ GetToken( false ); // X
+ pcmd->u.motion.x = verify_atof( token );
+
+ GetToken( false ); // Y
+ pcmd->u.motion.y = verify_atof( token );
+
+ GetToken( false ); // A
+ pcmd->u.motion.zr = verify_atof( token );
+ */
+ }
+ else if (stricmp( "walkalign", token ) == 0)
+ {
+ pcmd->cmd = CMD_REFMOTION;
+
+ // end frame to apply motion over
+ GetToken( false );
+ pcmd->u.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->u.motion.pRefAnim = extanim;
+
+ // motion type to match
+ pcmd->u.motion.motiontype = 0;
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ int ctrl = lookupControl( token );
+ if (ctrl != -1)
+ {
+ pcmd->u.motion.motiontype |= ctrl;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (pcmd->u.motion.motiontype == 0)
+ {
+ TokenError( "missing controls on walkalign\n" );
+ }
+
+ // frame of reference animation to match
+ pcmd->u.motion.iRefFrame = verify_atoi( token );
+
+ // against what frame of the current animation
+ GetToken( false );
+ pcmd->u.motion.iSrcFrame = verify_atoi( token );
+ }
+ else if (stricmp("derivative", token ) == 0)
+ {
+ pcmd->cmd = CMD_DERIVATIVE;
+
+ // get scale
+ GetToken( false );
+ pcmd->u.derivative.scale = verify_atof( token );
+ }
+ else if (stricmp("noanimation", token ) == 0)
+ {
+ pcmd->cmd = CMD_NOANIMATION;
+ }
+ else if (stricmp("lineardelta", token ) == 0)
+ {
+ pcmd->cmd = CMD_LINEARDELTA;
+ pcmd->u.linear.flags |= STUDIO_AL_POST;
+ }
+ else if (stricmp("splinedelta", token ) == 0)
+ {
+ pcmd->cmd = CMD_LINEARDELTA;
+ pcmd->u.linear.flags |= STUDIO_AL_POST;
+ pcmd->u.linear.flags |= STUDIO_AL_SPLINE;
+ }
+ else if (stricmp("compress", token ) == 0)
+ {
+ pcmd->cmd = CMD_COMPRESS;
+
+ // get frames to skip
+ GetToken( false );
+ pcmd->u.compress.frames = verify_atoi( token );
+ }
+ else if (stricmp("numframes", token ) == 0)
+ {
+ pcmd->cmd = CMD_NUMFRAMES;
+
+ // get frames to force
+ GetToken( false );
+ pcmd->u.compress.frames = verify_atoi( token );
+ }
+ else if (stricmp("counterrotate", token ) == 0)
+ {
+ pcmd->cmd = CMD_COUNTERROTATE;
+
+ // get bone name
+ GetToken( false );
+ pcmd->u.counterrotate.pBonename = strdup( token );
+ }
+ else if (stricmp("counterrotateto", token ) == 0)
+ {
+ pcmd->cmd = CMD_COUNTERROTATE;
+
+ pcmd->u.counterrotate.bHasTarget = true;
+
+ // get pitch
+ GetToken( false );
+ pcmd->u.counterrotate.targetAngle[0] = verify_atof( token );
+
+ // get yaw
+ GetToken( false );
+ pcmd->u.counterrotate.targetAngle[1] = verify_atof( token );
+
+ // get roll
+ GetToken( false );
+ pcmd->u.counterrotate.targetAngle[2] = verify_atof( token );
+
+ // get bone name
+ GetToken( false );
+ pcmd->u.counterrotate.pBonename = strdup( token );
+ }
+ else if (stricmp("localhierarchy", token ) == 0)
+ {
+ pcmd->cmd = CMD_LOCALHIERARCHY;
+
+ // get bone name
+ GetToken( false );
+ pcmd->u.localhierarchy.pBonename = strdup( token );
+
+ // get parent name
+ GetToken( false );
+ pcmd->u.localhierarchy.pParentname = strdup( token );
+
+ pcmd->u.localhierarchy.start = -1;
+ pcmd->u.localhierarchy.peak = -1;
+ pcmd->u.localhierarchy.tail = -1;
+ pcmd->u.localhierarchy.end = -1;
+
+ if (TokenAvailable())
+ {
+ GetToken( false );
+ if (stricmp( token, "range" ) == 0)
+ {
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.start = verify_atof_with_null( token );
+
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.peak = verify_atof_with_null( token );
+
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.tail = verify_atof_with_null( token );
+
+ //
+ GetToken( false );
+ pcmd->u.localhierarchy.end = verify_atof_with_null( token );
+ }
+ else
+ {
+ UnGetToken();
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+ numcmds++;
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: parse order independant s_animation_t token for $animations
+//-----------------------------------------------------------------------------
+bool ParseAnimationToken( s_animation_t *panim )
+{
+ if ( !Q_stricmp( "if", token ) )
+ {
+ // fixme: add expression evaluation
+ GetToken( false );
+ if (atoi( token ) == 0 && stricmp( token, "true" ) != 0)
+ {
+ GetToken(true);
+ if (token[0] == '{')
+ {
+ int depth = 1;
+ while (TokenAvailable() && depth > 0)
+ {
+ GetToken( true );
+ if (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ 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 );
+ 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( "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 );
+
+ // NOTE: This always affects the first source anim read in
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( panim->source, panim->animationname );
+ if ( pSourceAnim )
+ {
+ if ( panim->startframe < pSourceAnim->startframe )
+ {
+ panim->startframe = pSourceAnim->startframe;
+ }
+
+ if ( panim->endframe > pSourceAnim->endframe )
+ {
+ panim->endframe = pSourceAnim->endframe;
+ }
+ }
+
+ if ( !g_bCreateMakefile && panim->endframe < panim->startframe )
+ {
+ TokenError( "end frame before start frame in %s", panim->name );
+ }
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+
+ return true;
+ }
+
+ if ( !Q_stricmp( "blockname", token ) )
+ {
+ GetToken( false );
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( panim->source, token );
+
+ // NOTE: This always affects the first source anim read in
+ if ( pSourceAnim )
+ {
+ panim->startframe = pSourceAnim->startframe;
+ panim->endframe = pSourceAnim->endframe;
+
+ if ( !g_bCreateMakefile && panim->endframe < panim->startframe )
+ {
+ TokenError( "end frame before start frame in %s", panim->name );
+ }
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+ Q_strncpy( panim->animationname, token, sizeof(panim->animationname) );
+ }
+ else
+ {
+ MdlError( "Requested unknown animation block name %s\n", token );
+ }
+ 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 ) )
+ return true;
+
+ if ( !Q_stricmp( "cmdlist", token ) )
+ {
+ GetToken( false ); // A
+
+ int i;
+ for ( i = 0; i < g_numcmdlists; i++)
+ {
+ if (stricmp( g_cmdlist[i].name, token) == 0)
+ {
+ 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 ) )
+ {
+ panim->disableAnimblocks = true;
+ return true;
+ }
+
+ if ( !Q_stricmp( "noanimblockstall", token ) )
+ {
+ panim->isFirstSectionLocal = true;
+ 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( )
+{
+ int depth = 0;
+
+ // name
+ GetToken(false);
+ V_strcpy_safe( g_cmdlist[g_numcmdlists].name, token );
+
+ 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 (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (ParseCmdlistToken( g_cmdlist[g_numcmdlists].numcmds, g_cmdlist[g_numcmdlists].cmds ))
+ {
+
+ }
+ else
+ {
+ TokenError( "unknown command: %s\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ };
+
+ g_numcmdlists++;
+}
+
+int ParseAnimation( s_animation_t *panim, bool isAppend );
+int ParseEmpty( void );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: allocate an entry for $animation
+//-----------------------------------------------------------------------------
+void Cmd_Animation( )
+{
+ // name
+ GetToken(false);
+
+ s_animation_t *panim = LookupAnimation( token );
+
+ if (panim != NULL)
+ {
+ if (!panim->isOverride)
+ {
+ TokenError( "Duplicate animation name \"%s\"\n", token );
+ }
+ else
+ {
+ panim->doesOverride = true;
+ ParseEmpty();
+ return;
+ }
+ }
+
+ // allocate animation entry
+ g_panimation[g_numani] = (s_animation_t *)kalloc( 1, sizeof( s_animation_t ) );
+ g_panimation[g_numani]->index = g_numani;
+ panim = g_panimation[g_numani];
+ V_strcpy_safe( panim->name, token );
+ g_numani++;
+
+ // filename
+ GetToken(false);
+ V_strcpy_safe( panim->filename, token );
+
+ panim->source = Load_Source( panim->filename, "" );
+ if ( panim->source->m_Animations.Count() )
+ {
+ s_sourceanim_t *pSourceAnim = &panim->source->m_Animations[0];
+ panim->startframe = pSourceAnim->startframe;
+ panim->endframe = pSourceAnim->endframe;
+ Q_strncpy( panim->animationname, pSourceAnim->animationname, sizeof(panim->animationname) );
+ }
+ else
+ {
+ panim->startframe = 0;
+ panim->endframe = 0;
+ Q_strncpy( panim->animationname, "", sizeof(panim->animationname) );
+ }
+ VectorCopy( g_defaultadjust, panim->adjust );
+ panim->rotation = g_defaultrotation;
+ panim->scale = 1.0f;
+ panim->fps = 30.0;
+ panim->motionrollback = g_flDefaultMotionRollback;
+
+ ParseAnimation( panim, false );
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+
+ //CheckAutoShareAnimationGroup( panim->name );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: wrapper for parsing $animation tokens
+//-----------------------------------------------------------------------------
+
+int ParseAnimation( s_animation_t *panim, bool isAppend )
+{
+ 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 (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (ParseAnimationToken( panim ))
+ {
+
+ }
+ else
+ {
+ TokenError( "Unknown animation option\'%s\'\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ };
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: create a virtual $animation command from a $sequence reference
+//-----------------------------------------------------------------------------
+s_animation_t *Cmd_ImpliedAnimation( s_sequence_t *psequence, const char *filename )
+{
+ // allocate animation entry
+ g_panimation[g_numani] = (s_animation_t *)kalloc( 1, sizeof( s_animation_t ) );
+ g_panimation[g_numani]->index = g_numani;
+ s_animation_t *panim = g_panimation[g_numani];
+ g_numani++;
+
+ panim->isImplied = true;
+
+ panim->startframe = 0;
+ panim->endframe = MAXSTUDIOANIMFRAMES - 1;
+
+ V_strcpy_safe( panim->name, "@" );
+ V_strcat_safe( panim->name, psequence->name );
+ V_strcpy_safe( panim->filename, filename );
+
+ VectorCopy( g_defaultadjust, panim->adjust );
+ panim->scale = 1.0f;
+ panim->rotation = g_defaultrotation;
+ panim->fps = 30;
+ panim->motionrollback = g_flDefaultMotionRollback;
+
+ //panim->source = Load_Source( panim->filename, "smd" );
+ panim->source = Load_Source( panim->filename, "" );
+ if ( panim->source->m_Animations.Count() )
+ {
+ s_sourceanim_t *pSourceAnim = &panim->source->m_Animations[0];
+ Q_strncpy( panim->animationname, panim->source->m_Animations[0].animationname, sizeof(panim->animationname) );
+ if ( panim->startframe < pSourceAnim->startframe )
+ {
+ panim->startframe = pSourceAnim->startframe;
+ }
+
+ if ( panim->endframe > pSourceAnim->endframe )
+ {
+ panim->endframe = pSourceAnim->endframe;
+ }
+ }
+ else
+ {
+ Q_strncpy( panim->animationname, "", sizeof( panim->animationname ) );
+ }
+
+ if ( !g_bCreateMakefile && panim->endframe < panim->startframe )
+ {
+ TokenError( "end frame before start frame in %s", panim->name );
+ }
+
+ panim->numframes = panim->endframe - panim->startframe + 1;
+
+ //CheckAutoShareAnimationGroup( panim->name );
+
+ 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;
+
+ VectorCopy( psrc->adjust, pdest->adjust );
+ pdest->scale = psrc->scale;
+ pdest->rotation = psrc->rotation;
+
+ pdest->motiontype = psrc->motiontype;
+
+ //Adrian - Hey! Revisit me later.
+ /*if (pdest->startframe < psrc->startframe)
+ pdest->startframe = psrc->startframe;
+
+ if (pdest->endframe > psrc->endframe)
+ pdest->endframe = psrc->endframe;
+
+ if (pdest->endframe < pdest->startframe)
+ TokenError( "fixedup end frame before start frame in %s", pdest->name );
+
+ pdest->numframes = pdest->endframe - pdest->startframe + 1;*/
+
+ 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];
+ }
+}
+
+int ParseSequence( s_sequence_t *pseq, bool isAppend );
+
+
+//-----------------------------------------------------------------------------
+// Purpose: allocate an entry for $sequence
+//-----------------------------------------------------------------------------
+s_sequence_t *ProcessCmdSequence( const char *pSequenceName )
+{
+ s_animation_t *panim = LookupAnimation( pSequenceName );
+
+ // allocate sequence
+ if ( panim != NULL )
+ {
+ if ( !panim->isOverride )
+ {
+ TokenError( "Duplicate sequence name \"%s\"\n", pSequenceName );
+ }
+ else
+ {
+ panim->doesOverride = true;
+ return NULL;
+ }
+ }
+
+ if ( g_sequence.Count() >= MAXSTUDIOSEQUENCES )
+ {
+ TokenError("Too many sequences (%d max)\n", MAXSTUDIOSEQUENCES );
+ }
+
+ s_sequence_t *pseq = &g_sequence[ g_sequence.AddToTail() ];
+ memset( pseq, 0, sizeof( s_sequence_t ) );
+
+ // initialize sequence
+ Q_strncpy( pseq->name, pSequenceName, sizeof(pseq->name) );
+
+ pseq->actweight = 0;
+ pseq->activityname[0] = '\0';
+ pseq->activity = -1; // -1 is the default for 'no activity'
+
+ pseq->paramindex[0] = -1;
+ pseq->paramindex[1] = -1;
+
+ pseq->groupsize[0] = 0;
+ pseq->groupsize[1] = 0;
+
+ pseq->fadeintime = 0.2;
+ pseq->fadeouttime = 0.2;
+ return pseq;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process the sequence command
+//-----------------------------------------------------------------------------
+void Cmd_Sequence( )
+{
+ if ( !GetToken(false) )
+ return;
+
+ // Find existing sequences
+ const char *pSequenceName = token;
+ s_animation_t *panim = LookupAnimation( pSequenceName );
+ if ( panim != NULL && panim->isOverride )
+ {
+ ParseEmpty( );
+ }
+
+ s_sequence_t *pseq = ProcessCmdSequence( pSequenceName );
+ if ( pseq )
+ {
+ ParseSequence( pseq, false );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 )
+ {
+ if (numblends < 4)
+ {
+ 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 );
+ }
+ }
+
+ for (int i = 0; i < numblends; i++)
+ {
+ int j = i % pseq->groupsize[0];
+ int k = i / pseq->groupsize[0];
+
+ pseq->panim[j][k] = animations[i];
+
+ if (i > 0 && animations[i]->isImplied)
+ {
+ CopyAnimationSettings( animations[i], animations[0] );
+ }
+ animations[i]->isImplied = false; // don't copy any more commands
+ pseq->flags |= animations[i]->flags;
+ }
+
+ pseq->numblends = numblends;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: parse options unique to $sequence
+//-----------------------------------------------------------------------------
+int ParseSequence( s_sequence_t *pseq, bool isAppend )
+{
+ int depth = 0;
+ s_animation_t *animations[64];
+ int i, j, n;
+ int numblends = 0;
+
+ if (isAppend)
+ {
+ animations[0] = pseq->panim[0][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 (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ /*
+ else if (stricmp("deform", token ) == 0)
+ {
+ Option_Deform( pseq );
+ }
+ */
+
+ else if (stricmp("event", token ) == 0)
+ {
+ depth -= Option_Event( pseq );
+ }
+ else if (stricmp("activity", token ) == 0)
+ {
+ Option_Activity( pseq );
+ }
+ else if (stricmp("activitymodifier", token ) == 0)
+ {
+ Option_ActivityModifier( pseq );
+ }
+ else if (strnicmp( token, "ACT_", 4 ) == 0)
+ {
+ UnGetToken( );
+ Option_Activity( pseq );
+ }
+
+ else if (stricmp("snap", token ) == 0)
+ {
+ pseq->flags |= STUDIO_SNAP;
+ }
+
+ else if (stricmp("blendwidth", token ) == 0)
+ {
+ GetToken( false );
+ pseq->groupsize[0] = verify_atoi( token );
+ }
+
+ else if (stricmp("blend", token ) == 0)
+ {
+ i = 0;
+ if (pseq->paramindex[0] != -1)
+ {
+ i = 1;
+ }
+
+ GetToken( false );
+ j = LookupPoseParameter( token );
+ 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 = min( g_pose[j].min, pseq->paramstart[i] );
+ g_pose[j].min = min( g_pose[j].min, pseq->paramend[i] );
+ g_pose[j].max = max( g_pose[j].max, pseq->paramstart[i] );
+ g_pose[j].max = max( g_pose[j].max, pseq->paramend[i] );
+ }
+ else if (stricmp("calcblend", token ) == 0)
+ {
+ i = 0;
+ if (pseq->paramindex[0] != -1)
+ {
+ i = 1;
+ }
+
+ 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 );
+ }
+ else if (stricmp("blendref", token ) == 0)
+ {
+ GetToken( false );
+ pseq->paramanim = LookupAnimation( token );
+ if (pseq->paramanim == NULL)
+ {
+ TokenError( "Unknown blendref animation \"%s\"\n", token );
+ }
+ }
+ else if (stricmp("blendcomp", token ) == 0)
+ {
+ GetToken( false );
+ pseq->paramcompanim = LookupAnimation( token );
+ if (pseq->paramcompanim == NULL)
+ {
+ TokenError( "Unknown blendcomp animation \"%s\"\n", token );
+ }
+ }
+ else if (stricmp("blendcenter", token ) == 0)
+ {
+ GetToken( false );
+ pseq->paramcenter = LookupAnimation( token );
+ if (pseq->paramcenter == NULL)
+ {
+ TokenError( "Unknown blendcenter animation \"%s\"\n", token );
+ }
+ }
+ else if (stricmp("node", token ) == 0)
+ {
+ GetToken( false );
+ pseq->entrynode = pseq->exitnode = LookupXNode( token );
+ }
+ else if (stricmp("transition", token ) == 0)
+ {
+ GetToken( false );
+ pseq->entrynode = LookupXNode( token );
+ GetToken( false );
+ pseq->exitnode = LookupXNode( token );
+ }
+ else if (stricmp("rtransition", token ) == 0)
+ {
+ GetToken( false );
+ pseq->entrynode = LookupXNode( token );
+ GetToken( false );
+ pseq->exitnode = LookupXNode( token );
+ pseq->nodeflags |= 1;
+ }
+ else if (stricmp("exitphase", token ) == 0)
+ {
+ GetToken( false );
+ pseq->exitphase = verify_atof( token );
+ }
+ else if (stricmp("delta", token) == 0)
+ {
+ pseq->flags |= STUDIO_DELTA;
+ pseq->flags |= STUDIO_POST;
+ }
+ else if (stricmp("worldspace", token) == 0)
+ {
+ pseq->flags |= STUDIO_WORLD;
+ pseq->flags |= STUDIO_POST;
+ }
+ else if (stricmp("post", token) == 0) // remove
+ {
+ pseq->flags |= STUDIO_POST;
+ }
+ else if (stricmp("predelta", token) == 0)
+ {
+ pseq->flags |= STUDIO_DELTA;
+ }
+ else if (stricmp("autoplay", token) == 0)
+ {
+ pseq->flags |= STUDIO_AUTOPLAY;
+ }
+ else if (stricmp( "fadein", token ) == 0)
+ {
+ GetToken( false );
+ pseq->fadeintime = verify_atof( token );
+ }
+ else if (stricmp( "fadeout", token ) == 0)
+ {
+ GetToken( false );
+ pseq->fadeouttime = verify_atof( token );
+ }
+ else if (stricmp( "realtime", token ) == 0)
+ {
+ pseq->flags |= STUDIO_REALTIME;
+ }
+ else if (stricmp( "posecycle", token ) == 0)
+ {
+ pseq->flags |= STUDIO_CYCLEPOSE;
+
+ GetToken( false );
+ pseq->cycleposeindex = LookupPoseParameter( token );
+ }
+ else if (stricmp( "hidden", token ) == 0)
+ {
+ pseq->flags |= STUDIO_HIDDEN;
+ }
+ else if (stricmp( "addlayer", token ) == 0)
+ {
+ GetToken( false );
+ V_strcpy_safe( pseq->autolayer[pseq->numautolayers].name, token );
+ pseq->numautolayers++;
+ }
+ else if (stricmp( "iklock", token ) == 0)
+ {
+ GetToken(false);
+ V_strcpy_safe( pseq->iklock[pseq->numiklocks].name, token );
+
+ GetToken(false);
+ pseq->iklock[pseq->numiklocks].flPosWeight = verify_atof( token );
+
+ GetToken(false);
+ pseq->iklock[pseq->numiklocks].flLocalQWeight = verify_atof( token );
+
+ pseq->numiklocks++;
+ }
+ else if (stricmp( "keyvalues", token ) == 0)
+ {
+ Option_KeyValues( &pseq->KeyValue );
+ }
+ else if (stricmp( "blendlayer", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags = 0;
+
+ GetToken( false );
+ V_strcpy_safe( pseq->autolayer[pseq->numautolayers].name, token );
+
+ 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 (stricmp( "xfade", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_XFADE;
+ }
+ else if (stricmp( "spline", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_SPLINE;
+ }
+ else if (stricmp( "noblend", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_NOBLEND;
+ }
+ else if (stricmp( "poseparameter", token ) == 0)
+ {
+ pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_POSE;
+ GetToken( false );
+ pseq->autolayer[pseq->numautolayers].pose = LookupPoseParameter( token );
+ }
+ else if (stricmp( "local", token ) == 0)
+ {
+ 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 (stricmp( token, g_panimation[n]->name ) == 0)
+ {
+ 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][0] = animations[0];
+ }
+
+ }
+ else
+ {
+ TokenError( "unknown command \"%s\"\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ }
+
+ ProcessSequence( pseq, numblends, animations, isAppend );
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: throw away all the options for a specific sequence or animation
+//-----------------------------------------------------------------------------
+
+int ParseEmpty( )
+{
+ 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 (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: append commands to either a sequence or an animation
+//-----------------------------------------------------------------------------
+void Cmd_Append( )
+{
+ GetToken(false);
+
+
+ s_sequence_t *pseq = LookupSequence( token );
+
+ if (pseq)
+ {
+ ParseSequence( pseq, true );
+ return;
+ }
+ else
+ {
+ s_animation_t *panim = LookupAnimation( token );
+
+ if (panim)
+ {
+ ParseAnimation( panim, true );
+ return;
+ }
+ }
+ TokenError( "unknown append animation %s\n", token );
+}
+
+
+
+void Cmd_Prepend( )
+{
+ GetToken(false);
+
+ s_sequence_t *pseq = LookupSequence( token );
+ int count = 0;
+ s_animation_t *panim = NULL;
+ int iRet = false;
+
+ if (pseq)
+ {
+ panim = pseq->panim[0][0];
+ count = panim->numcmds;
+ iRet = ParseSequence( pseq, true );
+ }
+ else
+ {
+ panim = LookupAnimation( token );
+ if (panim)
+ {
+ count = panim->numcmds;
+ iRet = ParseAnimation( panim, true );
+ }
+ }
+ if (panim && count != panim->numcmds)
+ {
+ s_animcmd_t tmp;
+ tmp = panim->cmds[panim->numcmds - 1];
+ int i;
+ for (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( )
+{
+ GetToken(false);
+
+ s_sequence_t *pseq = LookupSequence( token );
+
+ if (pseq)
+ {
+ GetToken(true);
+ UnGetToken();
+ if (token[0] != '$')
+ ParseSequence( pseq, true );
+ return;
+ }
+ else
+ {
+ s_animation_t *panim = LookupAnimation( token );
+
+ if (panim)
+ {
+ GetToken(true);
+ UnGetToken();
+ if (token[0] != '$')
+ ParseAnimation( panim, true );
+ return;
+ }
+ }
+ TokenError( "unknown continue animation %s\n", token );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: foward declare an empty sequence
+//-----------------------------------------------------------------------------
+
+void Cmd_DeclareSequence( void )
+{
+ if (g_sequence.Count() >= MAXSTUDIOSEQUENCES)
+ {
+ TokenError("Too many sequences (%d max)\n", MAXSTUDIOSEQUENCES );
+ }
+
+ s_sequence_t *pseq = &g_sequence[ g_sequence.AddToTail() ];
+ memset( pseq, 0, sizeof( s_sequence_t ) );
+ pseq->flags = STUDIO_OVERRIDE;
+
+ // initialize sequence
+ GetToken( false );
+ V_strcpy_safe( pseq->name, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: foward declare an empty sequence
+//-----------------------------------------------------------------------------
+void Cmd_DeclareAnimation( void )
+{
+ if (g_numani >= MAXSTUDIOANIMS)
+ {
+ TokenError("Too many animations (%d max)\n", MAXSTUDIOANIMS );
+ }
+
+ // allocate animation entry
+ s_animation_t *panim = (s_animation_t *)kalloc( 1, sizeof( s_animation_t ) );
+ g_panimation[g_numani] = panim;
+ panim->index = g_numani;
+ panim->flags = STUDIO_OVERRIDE;
+ g_numani++;
+
+ // initialize animation
+ GetToken( false );
+ V_strcpy_safe( panim->name, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: create named list of boneweights
+//-----------------------------------------------------------------------------
+void Option_Weightlist( s_weightlist_t *pweightlist )
+{
+ int depth = 0;
+ int i;
+
+ 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 (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (stricmp("posweight", token ) == 0)
+ {
+ i = pweightlist->numbones - 1;
+ if (i < 0)
+ {
+ MdlError( "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)
+ {
+ MdlError( "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");
+ }
+ };
+}
+
+
+void Cmd_Weightlist( )
+{
+ 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 (stricmp( g_weightlist[i].name, token ) == 0)
+ {
+ TokenError( "Duplicate weightlist '%s'\n", token );
+ }
+ }
+
+ V_strcpy_safe( g_weightlist[i].name, token );
+
+ Option_Weightlist( &g_weightlist[g_numweightlist] );
+
+ g_numweightlist++;
+}
+
+void Cmd_DefaultWeightlist( )
+{
+ Option_Weightlist( &g_weightlist[0] );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Eyeball( s_model_t *pmodel )
+{
+ Vector tmp;
+ int i, j;
+ int mesh_material;
+ char szMeshMaterial[256];
+
+ s_eyeball_t *eyeball = &(pmodel->eyeball[pmodel->numeyeballs++]);
+
+ // name
+ GetToken (false);
+ V_strcpy_safe( eyeball->name, token );
+
+ // bone name
+ GetToken (false);
+ for (i = 0; i < pmodel->source->numbones; i++)
+ {
+ if ( !Q_stricmp( pmodel->source->localBone[i].name, token ) )
+ {
+ eyeball->bone = i;
+ break;
+ }
+ }
+ if (!g_bCreateMakefile && i >= pmodel->source->numbones)
+ {
+ TokenError( "unknown eyeball bone \"%s\"\n", token );
+ }
+
+ // X
+ GetToken (false);
+ tmp[0] = verify_atof (token);
+
+ // Y
+ GetToken (false);
+ tmp[1] = verify_atof (token);
+
+ // Z
+ GetToken (false);
+ tmp[2] = verify_atof (token);
+
+ // mesh material
+ GetToken (false);
+ Q_strncpy( szMeshMaterial, token, sizeof(szMeshMaterial) );
+ mesh_material = UseTextureAsMaterial( LookupTexture( token ) );
+
+ // diameter
+ GetToken (false);
+ eyeball->radius = verify_atof (token) / 2.0;
+
+ // Z angle offset
+ GetToken (false);
+ eyeball->zoffset = tan( DEG2RAD( verify_atof (token) ) );
+
+ // iris material (no longer used, but we need to remove the token)
+ GetToken (false);
+
+ // pupil scale
+ GetToken (false);
+ eyeball->iris_scale = 1.0 / verify_atof( token );
+
+ VectorCopy( tmp, eyeball->org );
+
+ for (i = 0; i < pmodel->source->nummeshes; i++)
+ {
+ j = pmodel->source->meshindex[i]; // meshes are internally stored by material index
+
+ if (j == mesh_material)
+ {
+ eyeball->mesh = i; // FIXME: should this be pre-adjusted?
+ break;
+ }
+ }
+
+ if (!g_bCreateMakefile && i >= pmodel->source->nummeshes)
+ {
+ TokenError("can't find eyeball texture \"%s\" on model\n", szMeshMaterial );
+ }
+
+ // translate eyeball into bone space
+ VectorITransform( tmp, pmodel->source->boneToPose[eyeball->bone], eyeball->org );
+
+ matrix3x4_t vtmp;
+ AngleMatrix( g_defaultrotation, vtmp );
+
+ VectorIRotate( Vector( 0, 0, 1 ), vtmp, tmp );
+ VectorIRotate( tmp, pmodel->source->boneToPose[eyeball->bone], eyeball->up );
+
+ VectorIRotate( Vector( 1, 0, 0 ), vtmp, tmp );
+ VectorIRotate( tmp, pmodel->source->boneToPose[eyeball->bone], eyeball->forward );
+
+ // these get overwritten by "eyelid" data
+ eyeball->upperlidflexdesc = -1;
+ eyeball->lowerlidflexdesc = -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Spherenormals( s_source_t *psource )
+{
+ Vector pos;
+ int i, j;
+ int mesh_material;
+ char szMeshMaterial[256];
+
+ // mesh material
+ GetToken (false);
+ V_strcpy_safe( szMeshMaterial, token );
+ mesh_material = UseTextureAsMaterial( LookupTexture( token ) );
+
+ // X
+ GetToken (false);
+ pos[0] = verify_atof (token);
+
+ // Y
+ GetToken (false);
+ pos[1] = verify_atof (token);
+
+ // Z
+ GetToken (false);
+ pos[2] = verify_atof (token);
+
+ for (i = 0; i < psource->nummeshes; i++)
+ {
+ j = psource->meshindex[i]; // meshes are internally stored by material index
+
+ if (j == mesh_material)
+ {
+ s_vertexinfo_t *vertex = &psource->vertex[psource->mesh[i].vertexoffset];
+
+ for (int k = 0; k < psource->mesh[i].numvertices; k++)
+ {
+ Vector n = vertex[k].position - pos;
+ VectorNormalize( n );
+ if (DotProduct( n, vertex[k].normal ) < 0.0)
+ {
+ vertex[k].normal = -1 * n;
+ }
+ else
+ {
+ vertex[k].normal = n;
+ }
+#if 0
+ vertex[k].normal[0] += 0.5f * ( 2.0f * ( ( float )rand() ) / ( float )VALVE_RAND_MAX ) - 1.0f;
+ vertex[k].normal[1] += 0.5f * ( 2.0f * ( ( float )rand() ) / ( float )VALVE_RAND_MAX ) - 1.0f;
+ vertex[k].normal[2] += 0.5f * ( 2.0f * ( ( float )rand() ) / ( float )VALVE_RAND_MAX ) - 1.0f;
+ VectorNormalize( vertex[k].normal );
+#endif
+ }
+ break;
+ }
+ }
+
+ if (i >= psource->nummeshes)
+ {
+ TokenError("can't find spherenormal texture \"%s\" on model\n", szMeshMaterial );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int Add_Flexdesc( const char *name )
+{
+ int flexdesc;
+ for ( flexdesc = 0; flexdesc < g_numflexdesc; flexdesc++)
+ {
+ if (stricmp( name, g_flexdesc[flexdesc].FACS ) == 0)
+ {
+ break;
+ }
+ }
+
+ if (flexdesc >= MAXSTUDIOFLEXDESC)
+ {
+ TokenError( "Too many flex types, max %d\n", MAXSTUDIOFLEXDESC );
+ }
+
+ if (flexdesc == g_numflexdesc)
+ {
+ V_strcpy_safe( g_flexdesc[flexdesc].FACS, name );
+
+ g_numflexdesc++;
+ }
+ return flexdesc;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Flex( char *name, char *vtafile, int imodel, float pairsplit )
+{
+ if (g_numflexkeys >= MAXSTUDIOFLEXKEYS)
+ {
+ TokenError( "Too many flexes, max %d\n", MAXSTUDIOFLEXKEYS );
+ }
+
+ int flexdesc, flexpair;
+
+ if (pairsplit != 0)
+ {
+ char mod[256];
+ sprintf( mod, "%sR", name );
+ flexdesc = Add_Flexdesc( mod );
+
+ sprintf( mod, "%sL", name );
+ flexpair = Add_Flexdesc( mod );
+ }
+ else
+ {
+ flexdesc = Add_Flexdesc( name );
+ flexpair = 0;
+ }
+
+ // initialize
+ g_flexkey[g_numflexkeys].imodel = imodel;
+ g_flexkey[g_numflexkeys].flexdesc = flexdesc;
+ g_flexkey[g_numflexkeys].target0 = 0.0;
+ g_flexkey[g_numflexkeys].target1 = 1.0;
+ g_flexkey[g_numflexkeys].target2 = 10;
+ g_flexkey[g_numflexkeys].target3 = 11;
+ g_flexkey[g_numflexkeys].split = pairsplit;
+ g_flexkey[g_numflexkeys].flexpair = flexpair;
+ g_flexkey[g_numflexkeys].decay = 1.0;
+
+ while (TokenAvailable())
+ {
+ GetToken(false);
+
+ if (stricmp( token, "frame") == 0)
+ {
+ GetToken (false);
+
+ g_flexkey[g_numflexkeys].frame = verify_atoi( token );
+ }
+ else if (stricmp( token, "position") == 0)
+ {
+ GetToken (false);
+ g_flexkey[g_numflexkeys].target1 = verify_atof( token );
+ }
+ else if (stricmp( token, "split") == 0)
+ {
+ GetToken (false);
+ g_flexkey[g_numflexkeys].split = verify_atof( token );
+ }
+ else if (stricmp( token, "decay") == 0)
+ {
+ GetToken (false);
+ g_flexkey[g_numflexkeys].decay = verify_atof( token );
+ }
+ else
+ {
+ TokenError( "unknown option: %s", token );
+ }
+
+ }
+
+ if (g_numflexkeys > 1)
+ {
+ if (g_flexkey[g_numflexkeys-1].flexdesc == g_flexkey[g_numflexkeys].flexdesc)
+ {
+ g_flexkey[g_numflexkeys-1].target2 = g_flexkey[g_numflexkeys-1].target1;
+ g_flexkey[g_numflexkeys-1].target3 = g_flexkey[g_numflexkeys].target1;
+ g_flexkey[g_numflexkeys].target0 = g_flexkey[g_numflexkeys-1].target1;
+ }
+ }
+
+ // link to source
+ s_source_t *pSource = Load_Source( vtafile, "vta" );
+ g_flexkey[g_numflexkeys].source = pSource;
+ if ( pSource->m_Animations.Count() )
+ {
+ Q_strncpy( g_flexkey[g_numflexkeys].animationname, pSource->m_Animations[0].animationname, sizeof( g_flexkey[g_numflexkeys].animationname ) );
+ }
+ else
+ {
+ g_flexkey[g_numflexkeys].animationname[0] = 0;
+ }
+ g_numflexkeys++;
+ // this needs to be per model.
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds combination data to the source
+//-----------------------------------------------------------------------------
+int FindSourceFlexKey( s_source_t *pSource, const char *pName )
+{
+ int nCount = pSource->m_FlexKeys.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !Q_stricmp( pSource->m_FlexKeys[i].animationname, pName ) )
+ return i;
+ }
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds flexkey data to a particular source
+//-----------------------------------------------------------------------------
+void AddFlexKey( s_source_t *pSource, CDmeCombinationOperator *pComboOp, const char *pFlexKeyName )
+{
+ // See if the delta state is already accounted for
+ if ( FindSourceFlexKey( pSource, pFlexKeyName ) >= 0 )
+ return;
+
+ int i = pSource->m_FlexKeys.AddToTail();
+
+ s_flexkey_t &key = pSource->m_FlexKeys[i];
+ memset( &key, 0, sizeof(key) );
+
+ key.target0 = 0.0f;
+ key.target1 = 1.0f;
+ key.target2 = 10.0f;
+ key.target3 = 11.0f;
+ key.decay = 1.0f;
+ key.source = pSource;
+
+ Q_strncpy( key.animationname, pFlexKeyName, sizeof(key.animationname) );
+ key.flexpair = pComboOp->IsDeltaStateStereo( pFlexKeyName ); // Signal used by AddBodyFlexData
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds combination data to the source
+//-----------------------------------------------------------------------------
+void AddCombination( s_source_t *pSource, CDmeCombinationOperator *pCombination )
+{
+ // Define the remapped controls
+ int nControlCount = pCombination->GetRawControlCount();
+ for ( int i = 0; i < nControlCount; ++i )
+ {
+ int m = pSource->m_CombinationControls.AddToTail();
+ s_combinationcontrol_t &control = pSource->m_CombinationControls[m];
+ Q_strncpy( control.name, pCombination->GetRawControlName( i ), sizeof(control.name) );
+ }
+
+ // Define the combination + domination rules
+ int nTargetCount = pCombination->GetOperationTargetCount();
+ for ( int i = 0; i < nTargetCount; ++i )
+ {
+ int nOpCount = pCombination->GetOperationCount( i );
+ for ( int j = 0; j < nOpCount; ++j )
+ {
+ CDmElement *pDeltaState = pCombination->GetOperationDeltaState( i, j );
+ if ( !pDeltaState )
+ continue;
+
+ int nFlex = FindSourceFlexKey( pSource, pDeltaState->GetName() );
+ if ( nFlex < 0 )
+ continue;
+
+ int k = pSource->m_CombinationRules.AddToTail();
+ s_combinationrule_t &rule = pSource->m_CombinationRules[k];
+ rule.m_nFlex = nFlex;
+ rule.m_Combination = pCombination->GetOperationControls( i, j );
+ int nDominatorCount = pCombination->GetOperationDominatorCount( i, j );
+ for ( int l = 0; l < nDominatorCount; ++l )
+ {
+ int m = rule.m_Dominators.AddToTail();
+ rule.m_Dominators[m] = pCombination->GetOperationDominator( i, j, l );
+ }
+ }
+ }
+
+ // Define the remapping controls
+ nControlCount = pCombination->GetControlCount();
+ for ( int i = 0; i < nControlCount; ++i )
+ {
+ int k = pSource->m_FlexControllerRemaps.AddToTail();
+ s_flexcontrollerremap_t &remap = pSource->m_FlexControllerRemaps[k];
+ remap.m_Name = pCombination->GetControlName( i );
+ remap.m_bIsStereo = pCombination->IsStereoControl( i );
+ remap.m_Index = -1; // Don't know this right now
+ remap.m_LeftIndex = -1; // Don't know this right now
+ remap.m_RightIndex = -1; // Don't know this right now
+ remap.m_MultiIndex = -1; // Don't know this right now
+ remap.m_EyesUpDownFlexController = -1;
+ remap.m_BlinkController = -1;
+
+ int nRemapCount = pCombination->GetRawControlCount( i );
+ if ( pCombination->IsEyelidControl( i ) )
+ {
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_EYELID;
+
+ // Save the eyes_updown flex for later
+ const char *pEyesUpDownFlexName = pCombination->GetEyesUpDownFlexName( i );
+ remap.m_EyesUpDownFlexName = pEyesUpDownFlexName ? pEyesUpDownFlexName : "eyes_updown";
+ }
+ else
+ {
+ switch( nRemapCount )
+ {
+ case 0:
+ case 1:
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_PASSTHRU;
+ break;
+ case 2:
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_2WAY;
+ break;
+ default:
+ remap.m_RemapType = FLEXCONTROLLER_REMAP_NWAY;
+ break;
+ }
+ }
+
+ Assert( nRemapCount != 0 );
+ for ( int j = 0; j < nRemapCount; ++j )
+ {
+ const char *pRemapName = pCombination->GetRawControlName( i, j );
+ remap.m_RawControls.AddToTail( pRemapName );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Option_Eyelid( int imodel )
+{
+ char type[256];
+ char vtafile[256];
+
+ // type
+ GetToken (false);
+ V_strcpy_safe( type, token );
+
+ // source
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+
+ int lowererframe = 0;
+ int neutralframe = 0;
+ int raiserframe = 0;
+ float lowerertarget = 0.0f;
+ float neutraltarget = 0.0f;
+ float raisertarget = 0.0f;
+ int lowererdesc = 0;
+ int neutraldesc = 0;
+ int raiserdesc = 0;
+ int basedesc;
+ float split = 0;
+ char szEyeball[64] = {""};
+
+ basedesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, type );
+
+ while (TokenAvailable())
+ {
+ GetToken(false);
+
+ char localdesc[256];
+ V_strcpy_safe( localdesc, type );
+ V_strcat_safe( localdesc, "_" );
+ V_strcat_safe( localdesc, token );
+
+ if (stricmp( token, "lowerer") == 0)
+ {
+ GetToken (false);
+ lowererframe = verify_atoi( token );
+ GetToken (false);
+ lowerertarget = verify_atof( token );
+ lowererdesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, localdesc );
+ }
+ else if (stricmp( token, "neutral") == 0)
+ {
+ GetToken (false);
+ neutralframe = verify_atoi( token );
+ GetToken (false);
+ neutraltarget = verify_atof( token );
+ neutraldesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, localdesc );
+ }
+ else if (stricmp( token, "raiser") == 0)
+ {
+ GetToken (false);
+ raiserframe = verify_atoi( token );
+ GetToken (false);
+ raisertarget = verify_atof( token );
+ raiserdesc = g_numflexdesc;
+ V_strcpy_safe( g_flexdesc[g_numflexdesc++].FACS, localdesc );
+ }
+ else if (stricmp( token, "split") == 0)
+ {
+ GetToken (false);
+ split = verify_atof( token );
+ }
+ else if (stricmp( token, "eyeball") == 0)
+ {
+ GetToken (false);
+ V_strcpy_safe( szEyeball, token );
+ }
+ else
+ {
+ TokenError( "unknown option: %s", token );
+ }
+ }
+
+ s_source_t *pSource = Load_Source( vtafile, "vta" );
+ g_flexkey[g_numflexkeys+0].source = pSource;
+ g_flexkey[g_numflexkeys+0].frame = lowererframe;
+ g_flexkey[g_numflexkeys+0].flexdesc = basedesc;
+ g_flexkey[g_numflexkeys+0].imodel = imodel;
+ g_flexkey[g_numflexkeys+0].split = split;
+ g_flexkey[g_numflexkeys+0].target0 = -11;
+ g_flexkey[g_numflexkeys+0].target1 = -10;
+ g_flexkey[g_numflexkeys+0].target2 = lowerertarget;
+ g_flexkey[g_numflexkeys+0].target3 = neutraltarget;
+ g_flexkey[g_numflexkeys+0].decay = 0.0;
+ if ( pSource->m_Animations.Count() > 0 )
+ {
+ Q_strncpy( g_flexkey[g_numflexkeys+0].animationname, pSource->m_Animations[0].animationname, sizeof(g_flexkey[g_numflexkeys+0].animationname) );
+ }
+ else
+ {
+ g_flexkey[g_numflexkeys+0].animationname[0] = 0;
+ }
+
+ g_flexkey[g_numflexkeys+1].source = g_flexkey[g_numflexkeys+0].source;
+ Q_strncpy( g_flexkey[g_numflexkeys+1].animationname, g_flexkey[g_numflexkeys+0].animationname, sizeof(g_flexkey[g_numflexkeys+1].animationname) );
+ g_flexkey[g_numflexkeys+1].frame = neutralframe;
+ g_flexkey[g_numflexkeys+1].flexdesc = basedesc;
+ g_flexkey[g_numflexkeys+1].imodel = imodel;
+ g_flexkey[g_numflexkeys+1].split = split;
+ g_flexkey[g_numflexkeys+1].target0 = lowerertarget;
+ g_flexkey[g_numflexkeys+1].target1 = neutraltarget;
+ g_flexkey[g_numflexkeys+1].target2 = neutraltarget;
+ g_flexkey[g_numflexkeys+1].target3 = raisertarget;
+ g_flexkey[g_numflexkeys+1].decay = 0.0;
+
+ g_flexkey[g_numflexkeys+2].source = g_flexkey[g_numflexkeys+0].source;
+ Q_strncpy( g_flexkey[g_numflexkeys+2].animationname, g_flexkey[g_numflexkeys+0].animationname, sizeof(g_flexkey[g_numflexkeys+2].animationname) );
+ g_flexkey[g_numflexkeys+2].frame = raiserframe;
+ g_flexkey[g_numflexkeys+2].flexdesc = basedesc;
+ g_flexkey[g_numflexkeys+2].imodel = imodel;
+ g_flexkey[g_numflexkeys+2].split = split;
+ g_flexkey[g_numflexkeys+2].target0 = neutraltarget;
+ g_flexkey[g_numflexkeys+2].target1 = raisertarget;
+ g_flexkey[g_numflexkeys+2].target2 = 10;
+ g_flexkey[g_numflexkeys+2].target3 = 11;
+ g_flexkey[g_numflexkeys+2].decay = 0.0;
+ g_numflexkeys += 3;
+
+ s_model_t *pmodel = g_model[imodel];
+ for (int i = 0; i < pmodel->numeyeballs; i++)
+ {
+ s_eyeball_t *peyeball = &(pmodel->eyeball[i]);
+
+ if (szEyeball[0] != '\0')
+ {
+ if (stricmp( peyeball->name, szEyeball ) != 0)
+ continue;
+ }
+
+ if (fabs( lowerertarget ) > peyeball->radius)
+ {
+ TokenError( "Eyelid \"%s\" lowerer out of range (+-%.1f)\n", type, peyeball->radius );
+ }
+ if (fabs( neutraltarget ) > peyeball->radius)
+ {
+ TokenError( "Eyelid \"%s\" neutral out of range (+-%.1f)\n", type, peyeball->radius );
+ }
+ if (fabs( raisertarget ) > peyeball->radius)
+ {
+ TokenError( "Eyelid \"%s\" raiser out of range (+-%.1f)\n", type, peyeball->radius );
+ }
+
+ switch( type[0] )
+ {
+ case 'u':
+ peyeball->upperlidflexdesc = basedesc;
+ peyeball->upperflexdesc[0] = lowererdesc;
+ peyeball->uppertarget[0] = lowerertarget;
+ peyeball->upperflexdesc[1] = neutraldesc;
+ peyeball->uppertarget[1] = neutraltarget;
+ peyeball->upperflexdesc[2] = raiserdesc;
+ peyeball->uppertarget[2] = raisertarget;
+ break;
+ case 'l':
+ peyeball->lowerlidflexdesc = basedesc;
+ peyeball->lowerflexdesc[0] = lowererdesc;
+ peyeball->lowertarget[0] = lowerertarget;
+ peyeball->lowerflexdesc[1] = neutraldesc;
+ peyeball->lowertarget[1] = neutraltarget;
+ peyeball->lowerflexdesc[2] = raiserdesc;
+ peyeball->lowertarget[2] = raisertarget;
+ break;
+ }
+ }
+}
+
+/*
+=================
+=================
+*/
+int Option_Mouth( s_model_t *pmodel )
+{
+ // index
+ GetToken (false);
+ int index = verify_atoi( token );
+ if (index >= g_nummouths)
+ g_nummouths = index + 1;
+
+ // flex controller name
+ GetToken (false);
+ g_mouth[index].flexdesc = Add_Flexdesc( token );
+
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_mouth[index].bonename, token );
+
+ // vector
+ GetToken (false);
+ g_mouth[index].forward[0] = verify_atof( token );
+ GetToken (false);
+ g_mouth[index].forward[1] = verify_atof( token );
+ GetToken (false);
+ g_mouth[index].forward[2] = verify_atof( token );
+ return 0;
+}
+
+
+
+void Option_Flexcontroller( s_model_t *pmodel )
+{
+ char type[256];
+ float range_min = 0.0f;
+ float range_max = 1.0f;
+
+ // g_flex
+ GetToken (false);
+ V_strcpy_safe( type, token );
+
+ while (TokenAvailable())
+ {
+ GetToken(false);
+
+ if (stricmp( token, "range") == 0)
+ {
+ GetToken(false);
+ range_min = verify_atof( token );
+
+ GetToken(false);
+ range_max = verify_atof( token );
+ }
+ else
+ {
+ if (g_numflexcontrollers >= MAXSTUDIOFLEXCTRL)
+ {
+ TokenError( "Too many flex controllers, max %d\n", MAXSTUDIOFLEXCTRL );
+ }
+
+ V_strcpy_safe( g_flexcontroller[g_numflexcontrollers].name, token );
+ V_strcpy_safe( g_flexcontroller[g_numflexcontrollers].type, type );
+ g_flexcontroller[g_numflexcontrollers].min = range_min;
+ g_flexcontroller[g_numflexcontrollers].max = range_max;
+ g_numflexcontrollers++;
+ }
+ }
+
+ // this needs to be per model.
+}
+
+void Option_Flexrule( s_model_t *pmodel, char *name )
+{
+ int precedence[32];
+ precedence[ STUDIO_CONST ] = 0;
+ precedence[ STUDIO_FETCH1 ] = 0;
+ precedence[ STUDIO_FETCH2 ] = 0;
+ precedence[ STUDIO_ADD ] = 1;
+ precedence[ STUDIO_SUB ] = 1;
+ precedence[ STUDIO_MUL ] = 2;
+ precedence[ STUDIO_DIV ] = 2;
+ precedence[ STUDIO_NEG ] = 4;
+ precedence[ STUDIO_EXP ] = 3;
+ precedence[ STUDIO_OPEN ] = 0; // only used in token parsing
+ precedence[ STUDIO_CLOSE ] = 0;
+ precedence[ STUDIO_COMMA ] = 0;
+ precedence[ STUDIO_MAX ] = 5;
+ precedence[ STUDIO_MIN ] = 5;
+
+ s_flexop_t stream[MAX_OPS];
+ int i = 0;
+ s_flexop_t stack[MAX_OPS];
+ int j = 0;
+ int k = 0;
+
+ s_flexrule_t *pRule = &g_flexrule[g_numflexrules++];
+
+ if (g_numflexrules > MAXSTUDIOFLEXRULES)
+ {
+ TokenError( "Too many flex rules (max %d)\n", MAXSTUDIOFLEXRULES );
+ }
+
+ int flexdesc;
+ for ( flexdesc = 0; flexdesc < g_numflexdesc; flexdesc++)
+ {
+ if (stricmp( name, g_flexdesc[flexdesc].FACS ) == 0)
+ {
+ break;
+ }
+ }
+
+ if (flexdesc >= g_numflexdesc)
+ {
+ TokenError( "Rule for unknown flex %s\n", name );
+ }
+
+ pRule->flex = flexdesc;
+ pRule->numops = 0;
+
+ // =
+ GetToken(false);
+
+ // parse all the tokens
+ bool linecontinue = false;
+ while ( linecontinue || TokenAvailable())
+ {
+ GetExprToken(linecontinue);
+
+ linecontinue = false;
+
+ if ( token[0] == '\\' )
+ {
+ if (!GetToken(false) || token[0] != '\\')
+ {
+ TokenError( "unknown expression token '\\%s\n", token );
+ }
+ linecontinue = true;
+ }
+ else if ( token[0] == '(' )
+ {
+ stream[i++].op = STUDIO_OPEN;
+ }
+ else if ( token[0] == ')' )
+ {
+ stream[i++].op = STUDIO_CLOSE;
+ }
+ else if ( token[0] == '+' )
+ {
+ stream[i++].op = STUDIO_ADD;
+ }
+ else if ( token[0] == '-' )
+ {
+ stream[i].op = STUDIO_SUB;
+ if (i > 0)
+ {
+ switch( stream[i-1].op )
+ {
+ case STUDIO_OPEN:
+ case STUDIO_ADD:
+ case STUDIO_SUB:
+ case STUDIO_MUL:
+ case STUDIO_DIV:
+ case STUDIO_COMMA:
+ // it's a unary if it's preceded by a "(+-*/,"?
+ stream[i].op = STUDIO_NEG;
+ break;
+ }
+ }
+ i++;
+ }
+ else if ( token[0] == '*' )
+ {
+ stream[i++].op = STUDIO_MUL;
+ }
+ else if ( token[0] == '/' )
+ {
+ stream[i++].op = STUDIO_DIV;
+ }
+ else if ( V_isdigit( token[0] ))
+ {
+ stream[i].op = STUDIO_CONST;
+ stream[i++].d.value = verify_atof( token );
+ }
+ else if ( token[0] == ',' )
+ {
+ stream[i++].op = STUDIO_COMMA;
+ }
+ else if ( stricmp( token, "max" ) == 0)
+ {
+ stream[i++].op = STUDIO_MAX;
+ }
+ else if ( stricmp( token, "min" ) == 0)
+ {
+ stream[i++].op = STUDIO_MIN;
+ }
+ else
+ {
+ if (token[0] == '%')
+ {
+ GetExprToken(false);
+
+ for (k = 0; k < g_numflexdesc; k++)
+ {
+ if (stricmp( token, g_flexdesc[k].FACS ) == 0)
+ {
+ stream[i].op = STUDIO_FETCH2;
+ stream[i++].d.index = k;
+ break;
+ }
+ }
+ if (k >= g_numflexdesc)
+ {
+ TokenError( "unknown flex %s\n", token );
+ }
+ }
+ else
+ {
+ for (k = 0; k < g_numflexcontrollers; k++)
+ {
+ if (stricmp( token, g_flexcontroller[k].name ) == 0)
+ {
+ stream[i].op = STUDIO_FETCH1;
+ stream[i++].d.index = k;
+ break;
+ }
+ }
+ if (k >= g_numflexcontrollers)
+ {
+ TokenError( "unknown controller %s\n", token );
+ }
+ }
+ }
+ }
+
+ if (i > MAX_OPS)
+ {
+ TokenError("expression %s too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+
+ if (0)
+ {
+ printf("%s = ", g_flexdesc[pRule->flex].FACS );
+ for ( k = 0; k < i; k++)
+ {
+ switch( stream[k].op )
+ {
+ case STUDIO_CONST: printf("%f ", stream[k].d.value ); break;
+ case STUDIO_FETCH1: printf("%s ", g_flexcontroller[stream[k].d.index].name ); break;
+ case STUDIO_FETCH2: printf("[%d] ", stream[k].d.index ); break;
+ case STUDIO_ADD: printf("+ "); break;
+ case STUDIO_SUB: printf("- "); break;
+ case STUDIO_MUL: printf("* "); break;
+ case STUDIO_DIV: printf("/ "); break;
+ case STUDIO_NEG: printf("neg "); break;
+ case STUDIO_MAX: printf("max "); break;
+ case STUDIO_MIN: printf("min "); break;
+ case STUDIO_COMMA: printf(", "); break; // error
+ case STUDIO_OPEN: printf("( " ); break; // error
+ case STUDIO_CLOSE: printf(") " ); break; // error
+ default:
+ printf("err%d ", stream[k].op ); break;
+ }
+ }
+ printf("\n");
+ // exit(1);
+ }
+
+ j = 0;
+ for (k = 0; k < i; k++)
+ {
+ if (j >= MAX_OPS)
+ {
+ TokenError("expression %s too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+ switch( stream[k].op )
+ {
+ case STUDIO_CONST:
+ case STUDIO_FETCH1:
+ case STUDIO_FETCH2:
+ pRule->op[pRule->numops++] = stream[k];
+ break;
+ case STUDIO_OPEN:
+ stack[j++] = stream[k];
+ break;
+ case STUDIO_CLOSE:
+ // pop all operators off of the stack until an open paren
+ while (j > 0 && stack[j-1].op != STUDIO_OPEN)
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ }
+ if (j == 0)
+ {
+ TokenError( "unmatched closed parentheses\n" );
+ }
+ if (j > 0)
+ j--;
+ break;
+ case STUDIO_COMMA:
+ // pop all operators off of the stack until an open paren
+ while (j > 0 && stack[j-1].op != STUDIO_OPEN)
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ }
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ break;
+ case STUDIO_ADD:
+ case STUDIO_SUB:
+ case STUDIO_MUL:
+ case STUDIO_DIV:
+ // pop all operators off of the stack that have equal or higher precedence
+ while (j > 0 && precedence[stream[k].op] <= precedence[stack[j-1].op])
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ }
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ break;
+ case STUDIO_NEG:
+ if (stream[k+1].op == STUDIO_CONST)
+ {
+ // change sign of constant, skip op
+ stream[k+1].d.value = -stream[k+1].d.value;
+ }
+ else
+ {
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ }
+ break;
+ case STUDIO_MAX:
+ case STUDIO_MIN:
+ // push operator onto the stack
+ stack[j++] = stream[k];
+ break;
+ }
+ if (pRule->numops >= MAX_OPS)
+ TokenError("expression for \"%s\" too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+ // pop all operators off of the stack
+ while (j > 0)
+ {
+ pRule->op[pRule->numops++] = stack[j-1];
+ j--;
+ if (pRule->numops >= MAX_OPS)
+ TokenError("expression for \"%s\" too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+
+ // reprocess the operands, eating commas for all functions
+ int numCommas = 0;
+ j = 0;
+ for (k = 0; k < pRule->numops; k++)
+ {
+ switch( pRule->op[k].op )
+ {
+ case STUDIO_MAX:
+ case STUDIO_MIN:
+ if (pRule->op[j-1].op != STUDIO_COMMA)
+ {
+ TokenError( "missing comma\n");
+ }
+ // eat the comma operator
+ numCommas--;
+ pRule->op[j-1] = pRule->op[k];
+ break;
+ case STUDIO_COMMA:
+ numCommas++;
+ pRule->op[j++] = pRule->op[k];
+ break;
+ default:
+ pRule->op[j++] = pRule->op[k];
+ break;
+ }
+ }
+ pRule->numops = j;
+ if (numCommas != 0)
+ {
+ TokenError( "too many comma's\n" );
+ }
+
+ if (pRule->numops > MAX_OPS)
+ {
+ TokenError("expression %s too complicated\n", g_flexdesc[pRule->flex].FACS );
+ }
+
+ if (0)
+ {
+ printf("%s = ", g_flexdesc[pRule->flex].FACS );
+ for ( i = 0; i < pRule->numops; i++)
+ {
+ switch( pRule->op[i].op )
+ {
+ case STUDIO_CONST: printf("%f ", pRule->op[i].d.value ); break;
+ case STUDIO_FETCH1: printf("%s ", g_flexcontroller[pRule->op[i].d.index].name ); break;
+ case STUDIO_FETCH2: printf("[%d] ", pRule->op[i].d.index ); break;
+ case STUDIO_ADD: printf("+ "); break;
+ case STUDIO_SUB: printf("- "); break;
+ case STUDIO_MUL: printf("* "); break;
+ case STUDIO_DIV: printf("/ "); break;
+ case STUDIO_NEG: printf("neg "); break;
+ case STUDIO_MAX: printf("max "); break;
+ case STUDIO_MIN: printf("min "); break;
+ case STUDIO_COMMA: printf(", "); break; // error
+ case STUDIO_OPEN: printf("( " ); break; // error
+ case STUDIO_CLOSE: printf(") " ); break; // error
+ default:
+ printf("err%d ", pRule->op[i].op ); break;
+ }
+ }
+ printf("\n");
+ // exit(1);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Model( )
+{
+ g_model[g_nummodels] = (s_model_t *)kalloc( 1, sizeof( s_model_t ) );
+
+ // name
+ if (!GetToken(false))
+ return;
+ V_strcpy_safe( g_model[g_nummodels]->name, token );
+
+ // fake g_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;
+ }
+ V_strcpy_safe( g_bodypart[g_numbodyparts].name, token );
+
+ 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] );
+
+ if ( g_model[g_nummodels]->source )
+ {
+ // Body command should add any flex commands in the source loaded
+ AddBodyFlexData( g_model[g_nummodels]->source, g_nummodels );
+ AddBodyAttachments( g_model[g_nummodels]->source );
+ }
+
+ int depth = 0;
+ while (1)
+ {
+ char FAC[256], vtafile[256];
+ 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( "eyeball", token ) )
+ {
+ Option_Eyeball( g_model[g_nummodels] );
+ }
+ else if ( !Q_stricmp( "eyelid", token ) )
+ {
+ Option_Eyelid( g_nummodels );
+ }
+ else if ( !Q_stricmp( "flex", token ) )
+ {
+ // g_flex
+ GetToken (false);
+ V_strcpy_safe( FAC, token );
+ if (depth == 0)
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+ Option_Flex( FAC, vtafile, g_nummodels, 0.0 ); // FIXME: this needs to point to a model used, not loaded!!!
+ }
+ else if ( !Q_stricmp( "flexpair", token ) )
+ {
+ // g_flex
+ GetToken (false);
+ V_strcpy_safe( FAC, token );
+
+ GetToken( false );
+ float split = atof( token );
+
+ if (depth == 0)
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+ Option_Flex( FAC, vtafile, g_nummodels, split ); // FIXME: this needs to point to a model used, not loaded!!!
+ }
+ else if ( !Q_stricmp( "defaultflex", token ) )
+ {
+ if (depth == 0)
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+
+ // g_flex
+ Option_Flex( "default", vtafile, g_nummodels, 0.0 ); // FIXME: this needs to point to a model used, not loaded!!!
+ g_defaultflexkey = &g_flexkey[g_numflexkeys-1];
+ }
+ else if ( !Q_stricmp( "flexfile", token ) )
+ {
+ // file
+ GetToken (false);
+ V_strcpy_safe( vtafile, token );
+ }
+ else if ( !Q_stricmp( "localvar", token ) )
+ {
+ while (TokenAvailable())
+ {
+ GetToken( false );
+ Add_Flexdesc( token );
+ }
+ }
+ else if ( !Q_stricmp( "mouth", token ) )
+ {
+ Option_Mouth( g_model[g_nummodels] );
+ }
+ else if ( !Q_stricmp( "flexcontroller", token ) )
+ {
+ Option_Flexcontroller( g_model[g_nummodels] );
+ }
+ else if ( token[0] == '%' )
+ {
+ Option_Flexrule( g_model[g_nummodels], &token[1] );
+ }
+ else if ( !Q_stricmp("attachment", token ) )
+ {
+ // Option_Attachment( g_model[g_nummodels] );
+ }
+ else if ( !Q_stricmp( token, "spherenormals" ) )
+ {
+ Option_Spherenormals( g_model[g_nummodels]->source );
+ }
+ else
+ {
+ TokenError( "unknown model option \"%s\"\n", token );
+ }
+
+ if (depth < 0)
+ {
+ TokenError("missing {\n");
+ }
+ };
+
+ // Actually connect up the expressions between the Dme Flex Controllers & Flex Descriptors
+ // In case there was data added by some other eyeball command (like eyelid)
+ AddBodyFlexRules( g_model[ g_nummodels ]->source );
+
+ g_nummodels++;
+}
+
+
+void Cmd_FakeVTA( void )
+{
+ int depth = 0;
+
+ GetToken( false );
+
+ s_source_t *psource = (s_source_t *)kalloc( 1, sizeof( s_source_t ) );
+ g_source[g_numsources] = psource;
+ V_strcpy_safe( g_source[g_numsources]->filename, token );
+ g_numsources++;
+
+ 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 (stricmp("{", token ) == 0)
+ {
+ depth++;
+ }
+ else if (stricmp("}", token ) == 0)
+ {
+ depth--;
+ }
+ else if (stricmp("appendvta", token ) == 0)
+ {
+ char filename[256];
+ // file
+ GetToken (false);
+ V_strcpy_safe( filename, token );
+
+ GetToken( false );
+ int frame = verify_atoi( token );
+
+ AppendVTAtoOBJ( psource, filename, frame );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_IKChain( )
+{
+ if (!GetToken(false))
+ return;
+
+ int i;
+ for ( i = 0; i < g_numikchains; i++)
+ {
+ if (stricmp( token, g_ikchain[i].name ) == 0)
+ {
+ break;
+ }
+ }
+ if (i < g_numikchains)
+ {
+ if (!g_quiet)
+ {
+ printf("duplicate ikchain \"%s\" ignored\n", token );
+ }
+ while (TokenAvailable())
+ {
+ GetToken(false);
+ }
+ return;
+ }
+
+ V_strcpy_safe( g_ikchain[g_numikchains].name, token );
+
+ GetToken(false);
+ V_strcpy_safe( g_ikchain[g_numikchains].bonename, token );
+
+ g_ikchain[g_numikchains].axis = STUDIO_Z;
+ g_ikchain[g_numikchains].value = 0.0;
+ g_ikchain[g_numikchains].height = 18.0;
+ 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 (stricmp( "height", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].height = verify_atof( token );
+ }
+ else if (stricmp( "pad", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].radius = verify_atof( token ) / 2.0;
+ }
+ else if (stricmp( "floor", token ) == 0)
+ {
+ GetToken(false);
+ g_ikchain[g_numikchains].floor = verify_atof( token );
+ }
+ else if (stricmp( "knee", token ) == 0)
+ {
+ 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 (stricmp( "center", token ) == 0)
+ {
+ 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( )
+{
+ GetToken(false);
+ V_strcpy_safe( g_ikautoplaylock[g_numikautoplaylocks].name, token );
+
+ GetToken(false);
+ g_ikautoplaylock[g_numikautoplaylocks].flPosWeight = verify_atof( token );
+
+ GetToken(false);
+ g_ikautoplaylock[g_numikautoplaylocks].flLocalQWeight = verify_atof( token );
+
+ g_numikautoplaylocks++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Root ()
+{
+ if (GetToken (false))
+ {
+ V_strcpy_safe( rootname, token );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Controller (void)
+{
+ if (GetToken (false))
+ {
+ if (!stricmp("mouth",token))
+ {
+ g_bonecontroller[g_numbonecontrollers].inputfield = 4;
+ }
+ else
+ {
+ g_bonecontroller[g_numbonecontrollers].inputfield = verify_atoi(token);
+ }
+ if (GetToken(false))
+ {
+ V_strcpy_safe( g_bonecontroller[g_numbonecontrollers].name, token );
+ GetToken(false);
+ if ((g_bonecontroller[g_numbonecontrollers].type = lookupControl(token)) == -1)
+ {
+ MdlWarning("unknown g_bonecontroller type '%s'\n", token );
+ return;
+ }
+ GetToken(false);
+ g_bonecontroller[g_numbonecontrollers].start = verify_atof( token );
+ GetToken(false);
+ g_bonecontroller[g_numbonecontrollers].end = verify_atof( token );
+
+ if (g_bonecontroller[g_numbonecontrollers].type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR))
+ {
+ if (((int)(g_bonecontroller[g_numbonecontrollers].start + 360) % 360) == ((int)(g_bonecontroller[g_numbonecontrollers].end + 360) % 360))
+ {
+ g_bonecontroller[g_numbonecontrollers].type |= STUDIO_RLOOP;
+ }
+ }
+ g_numbonecontrollers++;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+// Debugging function that enumerate all a models bones to stdout.
+static void SpewBones()
+{
+ MdlWarning("g_numbones %i\n",g_numbones);
+
+ for ( int i = g_numbones; --i >= 0; )
+ {
+ printf("%s\n",g_bonetable[i].name);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_ScreenAlign ( void )
+{
+ if (GetToken (false))
+ {
+
+ Assert( g_numscreenalignedbones < MAXSTUDIOSRCBONES );
+
+ V_strcpy_safe( g_screenalignedbone[g_numscreenalignedbones].name, token );
+ g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_SPHERE;
+
+ if( GetToken( false ) )
+ {
+ if( !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
+ {
+ TokenError( "$screenalign: expected bone name\n" );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+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;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+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;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Gamma (void)
+{
+ GetToken (false);
+ g_gamma = verify_atof( token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_TextureGroup( )
+{
+ if( g_bCreateMakefile )
+ {
+ return;
+ }
+ int i;
+ int depth = 0;
+ int index = 0;
+ int group = 0;
+
+
+ if (!GetToken(false))
+ return;
+
+ if (g_numskinref == 0)
+ g_numskinref = g_numtextures;
+
+ while (1)
+ {
+ if(!GetToken(true))
+ {
+ break;
+ }
+
+ if (endofscript)
+ {
+ if (depth != 0)
+ {
+ TokenError("missing }\n" );
+ }
+ return;
+ }
+ if (token[0] == '{')
+ {
+ depth++;
+ }
+ else if (token[0] == '}')
+ {
+ depth--;
+ if (depth == 0)
+ break;
+ group++;
+ index = 0;
+ }
+ else if (depth == 2)
+ {
+ i = UseTextureAsMaterial( 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++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Hitgroup( )
+{
+ GetToken (false);
+ g_hitgroup[g_numhitgroups].group = verify_atoi( token );
+ GetToken (false);
+ V_strcpy_safe( g_hitgroup[g_numhitgroups].name, token );
+ g_numhitgroups++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Hitbox( )
+{
+ bool autogenerated = false;
+ if ( g_hitboxsets.Size() == 0 )
+ {
+ g_hitboxsets.AddToTail();
+ autogenerated = true;
+ }
+
+ // Last one
+ s_hitboxset *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
+ V_strcpy_safe( set->hitboxsetname, "default" );
+ }
+
+ GetToken (false);
+ set->hitbox[set->numhitboxes].group = verify_atoi( token );
+
+ // Grab the bone name:
+ GetToken (false);
+ V_strcpy_safe( set->hitbox[set->numhitboxes].name, token );
+
+ 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
+ scale_vertex( set->hitbox[set->numhitboxes].bmin );
+ scale_vertex( set->hitbox[set->numhitboxes].bmax );
+ // clear out the hitboxname:
+ memset( set->hitbox[set->numhitboxes].hitboxname, 0, sizeof( set->hitbox[set->numhitboxes].hitboxname ) );
+
+ // Grab the hit box name if present:
+ if( TokenAvailable() )
+ {
+ GetToken (false);
+ V_strcpy_safe( set->hitbox[set->numhitboxes].hitboxname, token );
+ }
+
+
+ set->numhitboxes++;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_HitboxSet( void )
+{
+ // Add a new hitboxset
+ s_hitboxset *set = &g_hitboxsets[ g_hitboxsets.AddToTail() ];
+ GetToken( false );
+ memset( set, 0, sizeof( *set ) );
+ V_strcpy_safe( set->hitboxsetname, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a default surface property to the entire model
+//-----------------------------------------------------------------------------
+struct SurfacePropName_t
+{
+ char m_pJointName[128];
+ char m_pSurfaceProp[128];
+};
+
+static char s_pDefaultSurfaceProp[128] = {"default"};
+static CUtlVector<SurfacePropName_t> s_JointSurfaceProp;
+
+//-----------------------------------------------------------------------------
+// Assigns a default surface property to the entire model
+//-----------------------------------------------------------------------------
+void Cmd_SurfaceProp ()
+{
+ GetToken (false);
+ V_strcpy_safe( s_pDefaultSurfaceProp, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a surface property to a particular joint
+//-----------------------------------------------------------------------------
+void Cmd_JointSurfaceProp ()
+{
+ // Get joint name...
+ GetToken (false);
+
+ // Search for the name in our list
+ int i;
+ for ( i = s_JointSurfaceProp.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointSurfaceProp[i].m_pJointName, token))
+ {
+ break;
+ }
+ }
+
+ // Add new entry if we haven't seen this name before
+ if (i < 0)
+ {
+ i = s_JointSurfaceProp.AddToTail();
+ V_strcpy_safe( s_JointSurfaceProp[i].m_pJointName, token );
+ }
+
+ // surface property name
+ GetToken(false);
+ V_strcpy_safe( s_JointSurfaceProp[i].m_pSurfaceProp, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the default surface prop name
+//-----------------------------------------------------------------------------
+char* GetDefaultSurfaceProp ( )
+{
+ return s_pDefaultSurfaceProp;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns surface property for a given joint
+//-----------------------------------------------------------------------------
+static char* FindSurfaceProp ( const char* pJointName )
+{
+ for ( int i = s_JointSurfaceProp.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointSurfaceProp[i].m_pJointName, pJointName))
+ {
+ return s_JointSurfaceProp[i].m_pSurfaceProp;
+ }
+ }
+
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns surface property for a given joint
+//-----------------------------------------------------------------------------
+char* GetSurfaceProp ( const char* pJointName )
+{
+ while( pJointName )
+ {
+ // First try to find this joint
+ char* pSurfaceProp = FindSurfaceProp( pJointName );
+ if (pSurfaceProp)
+ return pSurfaceProp;
+
+ // If we can't find the joint, then find it's parent...
+ if (!g_numbones)
+ return s_pDefaultSurfaceProp;
+
+ int i = findGlobalBone( pJointName );
+
+ if ((i >= 0) && (g_bonetable[i].parent >= 0))
+ {
+ pJointName = g_bonetable[g_bonetable[i].parent].name;
+ }
+ else
+ {
+ pJointName = 0;
+ }
+ }
+
+ // No match, return the default one
+ return s_pDefaultSurfaceProp;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns surface property for a given joint
+//-----------------------------------------------------------------------------
+void ConsistencyCheckSurfaceProp ( )
+{
+ for ( int i = s_JointSurfaceProp.Count(); --i >= 0; )
+ {
+ int j = findGlobalBone( s_JointSurfaceProp[i].m_pJointName );
+
+ if (j < 0)
+ {
+ MdlWarning("You specified a joint surface property for joint\n"
+ " \"%s\" which either doesn't exist or was optimized out.\n", s_JointSurfaceProp[i].m_pJointName );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a default contents to the entire model
+//-----------------------------------------------------------------------------
+struct ContentsName_t
+{
+ char m_pJointName[128];
+ int m_nContents;
+};
+
+static int s_nDefaultContents = CONTENTS_SOLID;
+static CUtlVector<ContentsName_t> s_JointContents;
+
+
+//-----------------------------------------------------------------------------
+// Parse contents flags
+//-----------------------------------------------------------------------------
+static void ParseContents( int *pAddFlags, int *pRemoveFlags )
+{
+ *pAddFlags = 0;
+ *pRemoveFlags = 0;
+ do
+ {
+ GetToken (false);
+
+ if ( !stricmp( token, "grate" ) )
+ {
+ *pAddFlags |= CONTENTS_GRATE;
+ *pRemoveFlags |= CONTENTS_SOLID;
+ }
+ else if ( !stricmp( token, "ladder" ) )
+ {
+ *pAddFlags |= CONTENTS_LADDER;
+ }
+ else if ( !stricmp( token, "solid" ) )
+ {
+ *pAddFlags |= CONTENTS_SOLID;
+ }
+ else if ( !stricmp( token, "monster" ) )
+ {
+ *pAddFlags |= CONTENTS_MONSTER;
+ }
+ else if ( !stricmp( token, "notsolid" ) )
+ {
+ *pRemoveFlags |= CONTENTS_SOLID;
+ }
+ } while (TokenAvailable());
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns a default contents to the entire model
+//-----------------------------------------------------------------------------
+void Cmd_Contents()
+{
+ int nAddFlags, nRemoveFlags;
+ ParseContents( &nAddFlags, &nRemoveFlags );
+ s_nDefaultContents |= nAddFlags;
+ s_nDefaultContents &= ~nRemoveFlags;
+}
+
+
+//-----------------------------------------------------------------------------
+// Assigns contents to a particular joint
+//-----------------------------------------------------------------------------
+void Cmd_JointContents ()
+{
+ // Get joint name...
+ GetToken (false);
+
+ // Search for the name in our list
+ int i;
+ for ( i = s_JointContents.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointContents[i].m_pJointName, token))
+ {
+ break;
+ }
+ }
+
+ // Add new entry if we haven't seen this name before
+ if (i < 0)
+ {
+ i = s_JointContents.AddToTail();
+ V_strcpy_safe( s_JointContents[i].m_pJointName, token );
+ }
+
+ int nAddFlags, nRemoveFlags;
+ ParseContents( &nAddFlags, &nRemoveFlags );
+ s_JointContents[i].m_nContents = CONTENTS_SOLID;
+ s_JointContents[i].m_nContents |= nAddFlags;
+ s_JointContents[i].m_nContents &= ~nRemoveFlags;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the default contents
+//-----------------------------------------------------------------------------
+int GetDefaultContents( )
+{
+ return s_nDefaultContents;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns contents for a given joint
+//-----------------------------------------------------------------------------
+static int FindContents( const char* pJointName )
+{
+ for ( int i = s_JointContents.Count(); --i >= 0; )
+ {
+ if (!stricmp(s_JointContents[i].m_pJointName, pJointName))
+ {
+ return s_JointContents[i].m_nContents;
+ }
+ }
+
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns contents for a given joint
+//-----------------------------------------------------------------------------
+int GetContents( const char* pJointName )
+{
+ while( pJointName )
+ {
+ // First try to find this joint
+ int nContents = FindContents( pJointName );
+ if (nContents != -1)
+ return nContents;
+
+ // If we can't find the joint, then find it's parent...
+ if (!g_numbones)
+ return s_nDefaultContents;
+
+ int i = findGlobalBone( pJointName );
+
+ if ((i >= 0) && (g_bonetable[i].parent >= 0))
+ {
+ pJointName = g_bonetable[g_bonetable[i].parent].name;
+ }
+ else
+ {
+ pJointName = 0;
+ }
+ }
+
+ // No match, return the default one
+ return s_nDefaultContents;
+}
+
+
+//-----------------------------------------------------------------------------
+// Checks specified contents
+//-----------------------------------------------------------------------------
+void ConsistencyCheckContents( )
+{
+ for ( int i = s_JointContents.Count(); --i >= 0; )
+ {
+ int j = findGlobalBone( s_JointContents[i].m_pJointName );
+
+ if (j < 0)
+ {
+ MdlWarning("You specified a joint contents for joint\n"
+ " \"%s\" which either doesn't exist or was optimized out.\n", s_JointSurfaceProp[i].m_pJointName );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_BoneMerge( )
+{
+ if( g_bCreateMakefile )
+ return;
+
+ int nIndex = g_BoneMerge.AddToTail();
+
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_BoneMerge[nIndex].bonename, token );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Attachment( )
+{
+ if( g_bCreateMakefile )
+ return;
+
+ // name
+ GetToken (false);
+ V_strcpy_safe( g_attachment[g_numattachments].name, token );
+
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_attachment[g_numattachments].bonename, token );
+
+ Vector tmp;
+
+ // position
+ GetToken (false);
+ tmp.x = verify_atof( token );
+ GetToken (false);
+ tmp.y = verify_atof( token );
+ GetToken (false);
+ tmp.z = verify_atof( token );
+
+ scale_vertex( tmp );
+ // identity matrix
+ AngleMatrix( QAngle( 0, 0, 0 ), g_attachment[g_numattachments].local );
+
+ while (TokenAvailable())
+ {
+ GetToken (false);
+
+ if (stricmp(token,"absolute") == 0)
+ {
+ g_attachment[g_numattachments].type |= IS_ABSOLUTE;
+ AngleIMatrix( g_defaultrotation, g_attachment[g_numattachments].local );
+ // AngleIMatrix( Vector( 0, 0, 0 ), g_attachment[g_numattachments].local );
+ }
+ else if (stricmp(token,"rigid") == 0)
+ {
+ g_attachment[g_numattachments].type |= IS_RIGID;
+ }
+ else if (stricmp(token,"world_align") == 0)
+ {
+ g_attachment[g_numattachments].flags |= ATTACHMENT_FLAG_WORLD_ALIGN;
+ }
+ else if (stricmp(token,"rotate") == 0)
+ {
+ QAngle angles;
+ for (int i = 0; i < 3; ++i)
+ {
+ if (!TokenAvailable())
+ break;
+
+ GetToken(false);
+ angles[i] = verify_atof( token );
+ }
+ AngleMatrix( angles, g_attachment[g_numattachments].local );
+ }
+ else if (stricmp(token,"x_and_z_axes") == 0)
+ {
+ int i;
+ 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 );
+ }
+ VectorNormalize( xaxis );
+ VectorMA( zaxis, -DotProduct( zaxis, xaxis ), xaxis, zaxis );
+ VectorNormalize( zaxis );
+ CrossProduct( zaxis, xaxis, yaxis );
+ MatrixSetColumn( xaxis, 0, g_attachment[g_numattachments].local );
+ MatrixSetColumn( yaxis, 1, g_attachment[g_numattachments].local );
+ MatrixSetColumn( zaxis, 2, g_attachment[g_numattachments].local );
+ MatrixSetColumn( vec3_origin, 3, g_attachment[g_numattachments].local );
+ }
+ else
+ {
+ TokenError("unknown attachment (%s) option: ", g_attachment[g_numattachments].name, token );
+ }
+ }
+
+ g_attachment[g_numattachments].local[0][3] = tmp.x;
+ g_attachment[g_numattachments].local[1][3] = tmp.y;
+ g_attachment[g_numattachments].local[2][3] = tmp.z;
+
+ g_numattachments++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int LookupAttachment( char *name )
+{
+ int i;
+ for (i = 0; i < g_numattachments; i++)
+ {
+ if (stricmp( g_attachment[i].name, name ) == 0)
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void Cmd_Renamebone( )
+{
+ // from
+ GetToken (false);
+ V_strcpy_safe( g_renamedbone[g_numrenamedbones].from, token );
+
+ // to
+ GetToken (false);
+ V_strcpy_safe( g_renamedbone[g_numrenamedbones].to, token );
+
+ g_numrenamedbones++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+
+void Cmd_Skiptransition( )
+{
+ 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++;
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// The following code is all related to LODs
+//
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Parse replacemodel command, causes an LOD to use a new model
+//-----------------------------------------------------------------------------
+
+static void Cmd_ReplaceModel( LodScriptData_t& lodData )
+{
+ int i = lodData.modelReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.modelReplacements[i];
+
+ // from
+ GetToken( false );
+
+ // Strip off extensions for the source...
+ char* pDot = strrchr( token, '.' );
+ if (pDot)
+ {
+ *pDot = 0;
+ }
+
+ if (!FindCachedSource( token, "" ))
+ {
+ // must have prior knowledge of the from
+ TokenError( "Unknown replace model '%s'\n", token );
+ }
+
+ newReplacement.SetSrcName( token );
+
+ // to
+ GetToken( false );
+ newReplacement.SetDstName( token );
+
+ // check for "reverse"
+ bool reverse = false;
+ if( TokenAvailable() && GetToken( false ) )
+ {
+ if( stricmp( "reverse", token ) == 0 )
+ {
+ reverse = true;
+ }
+ else
+ {
+ TokenError( "\"%s\" unexpected\n", token );
+ }
+ }
+
+ // If the LOD system tells us to replace "blank", let's forget
+ // we ever read this. Have to do it here so parsing works
+ if( !stricmp( newReplacement.GetSrcName(), "blank" ) )
+ {
+ lodData.modelReplacements.FastRemove( i );
+ return;
+ }
+
+ // Load the source right here baby! That way its bones will get converted
+ if ( !lodData.IsStrippedFromModel() )
+ {
+ newReplacement.m_pSource = Load_Source( newReplacement.GetDstName(), "smd", reverse, false );
+ }
+ else if ( !g_quiet )
+ {
+ printf( "Stripped lod \"%s\" @ %.1f\n", newReplacement.GetDstName(), lodData.switchValue );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Parse removemodel command, causes an LOD to stop using a model
+//-----------------------------------------------------------------------------
+
+static void Cmd_RemoveModel( LodScriptData_t& lodData )
+{
+ int i = lodData.modelReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.modelReplacements[i];
+
+ // from
+ GetToken( false );
+
+ // Strip off extensions...
+ char* pDot = strrchr( token, '.' );
+ if (pDot)
+ *pDot = 0;
+
+ newReplacement.SetSrcName( token );
+
+ // to
+ newReplacement.SetDstName( "" );
+
+ // If the LOD system tells us to replace "blank", let's forget
+ // we ever read this. Have to do it here so parsing works
+ if( !stricmp( newReplacement.GetSrcName(), "blank" ) )
+ {
+ lodData.modelReplacements.FastRemove( i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Parse replacebone command, causes a part of an LOD model to use a different bone
+//-----------------------------------------------------------------------------
+
+static void Cmd_ReplaceBone( LodScriptData_t& lodData )
+{
+ int i = lodData.boneReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.boneReplacements[i];
+
+ // from
+ GetToken( false );
+ newReplacement.SetSrcName( token );
+
+ // to
+ GetToken( false );
+ newReplacement.SetDstName( token );
+}
+
+//-----------------------------------------------------------------------------
+// Parse bonetreecollapse command, causes the entire subtree to use the same bone as the node
+//-----------------------------------------------------------------------------
+
+static void Cmd_BoneTreeCollapse( LodScriptData_t& lodData )
+{
+ int i = lodData.boneTreeCollapses.AddToTail();
+ CLodScriptReplacement_t& newCollapse = lodData.boneTreeCollapses[i];
+
+ // from
+ GetToken( false );
+ newCollapse.SetSrcName( token );
+}
+
+//-----------------------------------------------------------------------------
+// Parse replacematerial command, causes a material to be used in place of another
+//-----------------------------------------------------------------------------
+
+static void Cmd_ReplaceMaterial( LodScriptData_t& lodData )
+{
+ int i = lodData.materialReplacements.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.materialReplacements[i];
+
+ // from
+ GetToken( false );
+ newReplacement.SetSrcName( token );
+
+ // to
+ GetToken( false );
+ newReplacement.SetDstName( token );
+
+ if ( !lodData.IsStrippedFromModel() )
+ {
+ // make sure it goes into the master list
+ UseTextureAsMaterial( LookupTexture( token ) );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Parse removemesh command, causes a mesh to not be used anymore
+//-----------------------------------------------------------------------------
+
+static void Cmd_RemoveMesh( LodScriptData_t& lodData )
+{
+ int i = lodData.meshRemovals.AddToTail();
+ CLodScriptReplacement_t& newReplacement = lodData.meshRemovals[i];
+
+ // from
+ GetToken( false );
+ Q_FixSlashes( token );
+ newReplacement.SetSrcName( token );
+}
+
+void Cmd_LOD( const char *cmdname )
+{
+ if ( gflags & STUDIOHDR_FLAGS_HASSHADOWLOD )
+ {
+ MdlError( "Model can only have one $shadowlod and it must be the last lod in the .qc (%d) : %s\n", g_iLinecount, g_szLine );
+ }
+
+ int i = g_ScriptLODs.AddToTail();
+ LodScriptData_t& newLOD = g_ScriptLODs[i];
+
+ if( g_ScriptLODs.Count() > MAX_NUM_LODS )
+ {
+ MdlError( "Too many LODs (MAX_NUM_LODS==%d)\n", ( int )MAX_NUM_LODS );
+ }
+
+ // Shadow lod reserves -1 as switch value
+ // which uniquely identifies a shadow lod
+ newLOD.switchValue = -1.0f;
+
+ bool isShadowCall = ( !stricmp( cmdname, "$shadowlod" ) ) ? true : false;
+
+ if ( isShadowCall )
+ {
+ if ( TokenAvailable() )
+ {
+ GetToken( false );
+ MdlWarning( "(%d) : %s: Ignoring switch value on %s command line\n", g_iLinecount, cmdname, g_szLine );
+ }
+
+ // Disable facial animation by default
+ newLOD.EnableFacialAnimation( false );
+ }
+ else
+ {
+ if ( TokenAvailable() )
+ {
+ GetToken( false );
+ newLOD.switchValue = verify_atof( token );
+ if ( newLOD.switchValue < 0.0f )
+ {
+ MdlError( "Negative switch value reserved for $shadowlod (%d) : %s\n", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ MdlError( "Expected LOD switch value (%d) : %s\n", g_iLinecount, g_szLine );
+ }
+ }
+
+ GetToken( true );
+ if( stricmp( "{", token ) != 0 )
+ {
+ MdlError( "\"{\" expected while processing %s (%d) : %s", cmdname, g_iLinecount, g_szLine );
+ }
+
+ // In case we are stripping all lods and it's not Lod0, strip it
+ if ( i && g_bStripLods )
+ newLOD.StripFromModel( true );
+
+ while( 1 )
+ {
+ GetToken( true );
+ if( stricmp( "replacemodel", token ) == 0 )
+ {
+ Cmd_ReplaceModel(newLOD);
+ }
+ else if( stricmp( "removemodel", token ) == 0 )
+ {
+ Cmd_RemoveModel(newLOD);
+ }
+ else if( stricmp( "replacebone", token ) == 0 )
+ {
+ Cmd_ReplaceBone( newLOD );
+ }
+ else if( stricmp( "bonetreecollapse", token ) == 0 )
+ {
+ Cmd_BoneTreeCollapse( newLOD );
+ }
+ else if( stricmp( "replacematerial", token ) == 0 )
+ {
+ Cmd_ReplaceMaterial( newLOD );
+ }
+ else if( stricmp( "removemesh", token ) == 0 )
+ {
+ Cmd_RemoveMesh( newLOD );
+ }
+ else if( stricmp( "nofacial", token ) == 0 )
+ {
+ newLOD.EnableFacialAnimation( false );
+ }
+ else if( stricmp( "facial", token ) == 0 )
+ {
+ if (isShadowCall)
+ {
+ // facial animation has no reasonable purpose on a shadow lod
+ TokenError( "Facial animation is not allowed for $shadowlod\n" );
+ }
+
+ newLOD.EnableFacialAnimation( true );
+ }
+ else if ( stricmp( "use_shadowlod_materials", token ) == 0 )
+ {
+ if (isShadowCall)
+ {
+ gflags |= STUDIOHDR_FLAGS_USE_SHADOWLOD_MATERIALS;
+ }
+ }
+ else if( stricmp( "}", token ) == 0 )
+ {
+ break;
+ }
+ else
+ {
+ MdlError( "invalid input while processing %s (%d) : %s", cmdname, g_iLinecount, g_szLine );
+ }
+ }
+
+ // If the LOD is stripped, then forget we saw it
+ if ( newLOD.IsStrippedFromModel() )
+ {
+ g_ScriptLODs.FastRemove( i );
+ }
+}
+
+void Cmd_ShadowLOD( void )
+{
+ if (!g_quiet)
+ {
+ printf( "Processing $shadowlod\n" );
+ }
+
+ // Act like it's a regular lod entry
+ Cmd_LOD( "$shadowlod" );
+
+ // Mark .mdl as having shadow lod (we also check above that we have only one of these
+ // and that it's the last entered lod )
+ gflags |= STUDIOHDR_FLAGS_HASSHADOWLOD;
+}
+
+
+//-----------------------------------------------------------------------------
+// A couple commands related to translucency sorting
+//-----------------------------------------------------------------------------
+void Cmd_Opaque( )
+{
+ // Force Opaque has precedence
+ gflags |= STUDIOHDR_FLAGS_FORCE_OPAQUE;
+ gflags &= ~STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS;
+}
+
+void Cmd_TranslucentTwoPass( )
+{
+ // Force Opaque has precedence
+ if ((gflags & STUDIOHDR_FLAGS_FORCE_OPAQUE) == 0)
+ {
+ gflags |= STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model be rendered with ambient boost heuristic (first used on Alyx in Episode 1)
+//-----------------------------------------------------------------------------
+void Cmd_AmbientBoost()
+{
+ gflags |= STUDIOHDR_FLAGS_AMBIENT_BOOST;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should not cast shadows (useful for first-person models as used in L4D)
+//-----------------------------------------------------------------------------
+void Cmd_DoNotCastShadows()
+{
+ gflags |= STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should cast texutre-based shadows in vrad (NOTE: only applicable to prop_static)
+//-----------------------------------------------------------------------------
+void Cmd_CastTextureShadows()
+{
+ gflags |= STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS;
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates the model should not fade out even if the level or fallback settings say to
+//-----------------------------------------------------------------------------
+void Cmd_NoForcedFade()
+{
+ gflags |= STUDIOHDR_FLAGS_NO_FORCED_FADE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates the model should not use the bone origin when calculating bboxes, sequence boxes, etc.
+//-----------------------------------------------------------------------------
+void Cmd_SkipBoneInBBox()
+{
+ g_bUseBoneInBBox = false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates the model will lengthen the viseme check to always include two phonemes
+//-----------------------------------------------------------------------------
+void Cmd_ForcePhonemeCrossfade()
+{
+ gflags |= STUDIOHDR_FLAGS_FORCE_PHONEME_CROSSFADE;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should keep pre-defined bone lengths regardless of animation changes
+//-----------------------------------------------------------------------------
+void Cmd_LockBoneLengths()
+{
+ g_bLockBoneLengths = true;
+}
+
+//-----------------------------------------------------------------------------
+// Indicates the model should replace pre-defined bone lengths and default orientations
+//-----------------------------------------------------------------------------
+void Cmd_UnlockDefineBones()
+{
+ g_bOverridePreDefinedBones = true;
+}
+
+//-----------------------------------------------------------------------------
+// Mark this model as obsolete so that it'll show the obsolete material in game.
+//-----------------------------------------------------------------------------
+void Cmd_Obsolete( )
+{
+ // Force Opaque has precedence
+ gflags |= STUDIOHDR_FLAGS_OBSOLETE;
+}
+
+//-----------------------------------------------------------------------------
+// The bones should be moved so that they center themselves on the verts they own.
+//-----------------------------------------------------------------------------
+void Cmd_CenterBonesOnVerts( )
+{
+ // force centering on bones
+ g_bCenterBonesOnVerts = true;
+}
+
+//-----------------------------------------------------------------------------
+// How far back should simple motion extract pull back from the last frame
+//-----------------------------------------------------------------------------
+void Cmd_MotionExtractionRollBack( )
+{
+ GetToken( false );
+ g_flDefaultMotionRollback = atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// rules for breaking up long animations into multiple sub anims
+//-----------------------------------------------------------------------------
+void Cmd_SectionFrames( )
+{
+ GetToken( false );
+ g_sectionFrames = atof( token );
+ GetToken( false );
+ g_minSectionFrameLimit = atoi( token );
+}
+
+
+//-----------------------------------------------------------------------------
+// world space clamping boundaries for animations
+//-----------------------------------------------------------------------------
+void Cmd_ClampWorldspace( )
+{
+ GetToken (false);
+ g_vecMinWorldspace[0] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMinWorldspace[1] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMinWorldspace[2] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMaxWorldspace[0] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMaxWorldspace[1] = verify_atof( token );
+
+ GetToken (false);
+ g_vecMaxWorldspace[2] = verify_atof( token );
+}
+
+//-----------------------------------------------------------------------------
+// Key value block!
+//-----------------------------------------------------------------------------
+void Option_KeyValues( CUtlVector< 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 ( !stricmp( token, "}" ) )
+ {
+ nLevel--;
+ if ( nLevel <= 0 )
+ break;
+ AppendKeyValueText( pKeyValue, " }\n" );
+ }
+ else if ( !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" );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: force a specific parent child relationship
+//-----------------------------------------------------------------------------
+
+void Cmd_ForcedHierarchy( )
+{
+ // child name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].childname, token );
+
+ // parent name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].parentname, token );
+
+ g_numforcedhierarchy++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: insert a virtual bone between a child and parent (currently unsupported)
+//-----------------------------------------------------------------------------
+
+void Cmd_InsertHierarchy( )
+{
+ // child name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].childname, token );
+
+ // subparent name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].subparentname, token );
+
+ // parent name
+ GetToken (false);
+ V_strcpy_safe( g_forcedhierarchy[g_numforcedhierarchy].parentname, token );
+
+ g_numforcedhierarchy++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: rotate a specific bone
+//-----------------------------------------------------------------------------
+
+void Cmd_ForceRealign( )
+{
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_forcedrealign[g_numforcedrealign].name, token );
+
+ // 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);
+ V_strcpy_safe( g_limitrotation[g_numlimitrotation].name, token );
+
+ while (TokenAvailable())
+ {
+ // sequence name
+ GetToken (false);
+ // This was a call to strcpyn but since sequencename is an array of char*
+ // it was passing sizeof(char*) as the number of characters to copy, which
+ // makes no sense. Commenting out until a better idea comes along.
+ Assert( 0 );
+ //V_strcpy_safe( g_limitrotation[g_numlimitrotation].sequencename[g_limitrotation[g_numlimitrotation].numseq++], token );
+ }
+
+ g_numlimitrotation++;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: specify bones to store, even if nothing references them
+//-----------------------------------------------------------------------------
+
+void Cmd_DefineBone( )
+{
+ // bone name
+ GetToken (false);
+ V_strcpy_safe( g_importbone[g_numimportbones].name, token );
+
+ // parent name
+ GetToken (false);
+ V_strcpy_safe( g_importbone[g_numimportbones].parent, token );
+
+ Vector pos;
+ QAngle angles;
+
+ // 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);
+ angles.x = verify_atof( token );
+ GetToken (false);
+ angles.y = verify_atof( token );
+ GetToken (false);
+ angles.z = verify_atof( token );
+ AngleMatrix( angles, pos, g_importbone[g_numimportbones].rawLocal );
+
+ 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);
+ angles.x = verify_atof( token );
+ GetToken (false);
+ angles.y = verify_atof( token );
+ GetToken (false);
+ angles.z = verify_atof( token );
+
+ AngleMatrix( angles, pos, g_importbone[g_numimportbones].srcRealign );
+ }
+ else
+ {
+ SetIdentityMatrix( g_importbone[g_numimportbones].srcRealign );
+ }
+
+ g_numimportbones++;
+}
+
+
+//----------------------------------------------------------------------------------------------
+float ParseJiggleStiffness( void )
+{
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting stiffness value\n" );
+ return 0.0f;
+ }
+
+ float stiffness = verify_atof( token );
+
+ const float minStiffness = 0.0f;
+ const float maxStiffness = 1000.0f;
+
+ return clamp( stiffness, minStiffness, maxStiffness );
+}
+
+
+//----------------------------------------------------------------------------------------------
+float ParseJiggleDamping( void )
+{
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting damping value\n" );
+ return 0.0f;
+ }
+
+ float damping = verify_atof( token );
+
+ const float minDamping = 0.0f;
+ const float maxDamping = 10.0f;
+
+ return clamp( damping, minDamping, maxDamping );
+}
+
+
+//----------------------------------------------------------------------------------------------
+bool ParseJiggleAngleConstraint( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_ANGLE_CONSTRAINT;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting angle value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.angleLimit = verify_atof( token ) * M_PI / 180.0f;
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+bool ParseJiggleYawConstraint( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_YAW_CONSTRAINT;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting minimum yaw value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.minYaw = verify_atof( token ) * M_PI / 180.0f;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting maximum yaw value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.maxYaw = verify_atof( token ) * M_PI / 180.0f;
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+bool ParseJigglePitchConstraint( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_PITCH_CONSTRAINT;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting minimum pitch value\n" );
+ return false;
+ }
+
+ jiggleInfo->data.minPitch = verify_atof( token ) * M_PI / 180.0f;
+
+ if ( !GetToken( false ) )
+ {
+ MdlError( "$jigglebone: expecting maximum pitch value\n" );
+ 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.
+ */
+bool 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
+ MdlError( "$jigglebone: invalid syntax '%s'\n", token );
+ return false;
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse parameters for is_flexible subsection
+ */
+bool ParseFlexibleJiggle( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= (JIGGLE_IS_FLEXIBLE | JIGGLE_HAS_LENGTH_CONSTRAINT);
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone:is_flexible: parse error\n" );
+ return false;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone:is_flexible: missing '{'\n" );
+ 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)
+ {
+ MdlError( "$jigglebone:is_flexible: invalid syntax '%s'\n", token );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse parameters for is_rigid subsection
+ */
+bool ParseRigidJiggle( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= (JIGGLE_IS_RIGID | JIGGLE_HAS_LENGTH_CONSTRAINT);
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone:is_rigid: parse error\n" );
+ return false;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone:is_rigid: missing '{'\n" );
+ return false;
+ }
+ else if (!stricmp( token, "}" ))
+ {
+ // definition complete
+ break;
+ }
+ else if (ParseCommonJiggle( jiggleInfo ) == false)
+ {
+ MdlError( "$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 )
+{
+ jiggleInfo->data.flags |= JIGGLE_HAS_BASE_SPRING;
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone:has_base_spring: parse error\n" );
+ return false;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone:has_base_spring: missing '{'\n" );
+ 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)
+ {
+ MdlError( "$jigglebone:has_base_spring: invalid syntax '%s'\n", token );
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse parameters for is_boing subsection
+ */
+bool ParseBoing( s_jigglebone_t *jiggleInfo )
+{
+ jiggleInfo->data.flags |= JIGGLE_IS_BOING;
+
+ // default values
+ jiggleInfo->data.boingImpactSpeed = 100.0f;
+ jiggleInfo->data.boingImpactAngle = 0.7071f;
+ jiggleInfo->data.boingDampingRate = 0.25f;
+ jiggleInfo->data.boingFrequency = 30.0f;
+ jiggleInfo->data.boingAmplitude = 0.35f;
+
+ bool gotOpenBracket = false;
+ while ( true )
+ {
+ if ( GetToken( true ) == false )
+ {
+ MdlError( "$jigglebone:is_boing: parse error\n" );
+ return false;
+ }
+
+ if ( !stricmp( token, "{" ) )
+ {
+ gotOpenBracket = true;
+ }
+ else if ( !gotOpenBracket )
+ {
+ MdlError( "$jigglebone:is_boing: missing '{'\n" );
+ return false;
+ }
+ else if ( !stricmp( token, "}" ) )
+ {
+ // definition complete
+ break;
+ }
+ else if ( !stricmp( token, "impact_speed" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingImpactSpeed = verify_atof( token );
+ }
+ else if ( !stricmp( token, "impact_angle" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingImpactAngle = cos( DEG2RAD( verify_atof( token ) ) );
+ }
+ else if ( !stricmp( token, "damping_rate" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingDampingRate = verify_atof( token );
+ }
+ else if ( !stricmp( token, "frequency" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingFrequency = verify_atof( token );
+ }
+ else if ( !stricmp( token, "amplitude" ) )
+ {
+ if ( !GetToken( false ) )
+ {
+ return false;
+ }
+
+ jiggleInfo->data.boingAmplitude = verify_atof( token );
+ }
+ }
+
+ return true;
+}
+
+
+//----------------------------------------------------------------------------------------------
+/**
+ * Parse $jigglebone parameters
+ */
+void Cmd_JiggleBone( void )
+{
+ struct s_jigglebone_t *jiggleInfo = &g_jigglebones[ g_numjigglebones ];
+
+ // bone name
+ GetToken( false );
+ V_strcpy_safe( jiggleInfo->bonename, token );
+
+ // 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;
+
+ bool gotOpenBracket = false;
+ while (true)
+ {
+ if (GetToken( true ) == false)
+ {
+ MdlError( "$jigglebone: parse error\n" );
+ return;
+ }
+
+ if (!stricmp( token, "{" ))
+ {
+ gotOpenBracket = true;
+ }
+ else if (!gotOpenBracket)
+ {
+ MdlError( "$jigglebone: missing '{'\n" );
+ 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 if ( !stricmp( token, "is_boing" ) )
+ {
+ if ( ParseBoing( jiggleInfo ) == false )
+ {
+ return;
+ }
+ }
+ else
+ {
+ MdlError( "$jigglebone: invalid syntax '%s'\n", token );
+ return;
+ }
+ }
+
+ if (!g_quiet)
+ Msg( "Marking bone %s as a jiggle bone\n", jiggleInfo->bonename );
+
+ g_numjigglebones++;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: specify bones to store, even if nothing references them
+//-----------------------------------------------------------------------------
+
+void Cmd_IncludeModel( )
+{
+ GetToken( false );
+ V_strcpy_safe( g_includemodel[g_numincludemodels].name, "models/" );
+ V_strcat_safe( g_includemodel[g_numincludemodels].name, token );
+ g_numincludemodels++;
+}
+
+
+/*
+=================
+=================
+*/
+
+void Grab_Vertexanimation( s_source_t *psource, const char *pAnimName )
+{
+ char cmd[1024];
+ int index;
+ Vector pos;
+ Vector normal;
+ int t = -1;
+ int count = 0;
+ static s_vertanim_t tmpvanim[MAXSTUDIOVERTS*4];
+
+ s_sourceanim_t *pAnim = FindSourceAnim( psource, pAnimName );
+ if ( !pAnim )
+ {
+ MdlError( "Unknown animation %s(%d) : %s\n", pAnimName, g_iLinecount, g_szLine );
+ }
+
+ while (GetLineInput())
+ {
+ if (sscanf( g_szLine, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &normal[0], &normal[1], &normal[2] ) == 7)
+ {
+ if ( pAnim->startframe < 0 )
+ {
+ MdlError( "Missing frame start(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ if (t < 0)
+ {
+ MdlError( "VTA Frame Sync (%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ tmpvanim[count].vertex = index;
+ VectorCopy( pos, tmpvanim[count].pos );
+ VectorCopy( normal, tmpvanim[count].normal );
+ count++;
+
+ if ( index >= psource->numvertices )
+ {
+ psource->numvertices = index + 1;
+ }
+ }
+ else
+ {
+ // flush data
+
+ if (count)
+ {
+ pAnim->numvanims[t] = count;
+
+ pAnim->vanim[t] = (s_vertanim_t *)kalloc( count, sizeof( s_vertanim_t ) );
+
+ memcpy( pAnim->vanim[t], tmpvanim, count * sizeof( s_vertanim_t ) );
+ }
+ else if (t > 0)
+ {
+ pAnim->numvanims[t] = 0;
+ }
+
+ // next command
+ if (sscanf( g_szLine, "%1023s %d", cmd, &index ))
+ {
+ if (stricmp( cmd, "time" ) == 0)
+ {
+ t = index;
+ count = 0;
+
+ if ( t < pAnim->startframe )
+ {
+ MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ if ( t > pAnim->endframe )
+ {
+ MdlError( "Frame MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+
+ t -= pAnim->startframe;
+ }
+ else if ( !Q_stricmp( cmd, "end" ) )
+ {
+ pAnim->numframes = pAnim->endframe - pAnim->startframe + 1;
+ return;
+ }
+ else
+ {
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ MdlError( "MdlError(%d) : %s", g_iLinecount, g_szLine );
+ }
+ }
+ }
+ MdlError( "unexpected EOF: %s\n", psource->filename );
+}
+
+bool GetGlobalFilePath( const char *pSrc, char *pFullPath, int nMaxLen )
+{
+ char pFileName[1024];
+ Q_strncpy( pFileName, ExpandPath( (char*)pSrc ), sizeof(pFileName) );
+
+ // This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead.
+ int nPathLength;
+ if( CmdLib_HasBasePath( pFileName, nPathLength ) )
+ {
+ char tmp[1024];
+ int i;
+
+ int nNumBasePaths = CmdLib_GetNumBasePaths();
+ for( i = 0; i < nNumBasePaths; i++ )
+ {
+ V_strcpy_safe( tmp, CmdLib_GetBasePath( i ) );
+ V_strcat_safe( tmp, pFileName + nPathLength );
+
+ struct _stat buf;
+ int rt = _stat( tmp, &buf );
+ if ( rt != -1 && ( buf.st_size > 0 ) && ( ( buf.st_mode & _S_IFDIR ) == 0 ) )
+ {
+ Q_strncpy( pFullPath, tmp, nMaxLen );
+ return true;
+ }
+ }
+ return false;
+ }
+
+ struct _stat buf;
+ int rt = _stat( pFileName, &buf );
+ if ( rt != -1 && ( buf.st_size > 0 ) && ( ( buf.st_mode & _S_IFDIR ) == 0 ) )
+ {
+ Q_strncpy( pFullPath, pFileName, nMaxLen );
+ return true;
+ }
+ return false;
+}
+
+
+int OpenGlobalFile( char *src )
+{
+ int time1;
+ char filename[1024];
+
+ V_strcpy_safe( filename, ExpandPath( src ) );
+
+ int pathLength;
+ int numBasePaths = CmdLib_GetNumBasePaths();
+ // This is kinda gross. . . doing the same work in cmdlib on SafeOpenRead.
+ if( CmdLib_HasBasePath( filename, pathLength ) )
+ {
+ char tmp[1024];
+ int i;
+ for( i = 0; i < numBasePaths; i++ )
+ {
+ V_strcpy_safe( tmp, CmdLib_GetBasePath( i ) );
+ V_strcat_safe( tmp, filename + pathLength );
+ if( g_bCreateMakefile )
+ {
+ CreateMakefile_AddDependency( tmp );
+ return 0;
+ }
+
+ time1 = FileTime( tmp );
+ if( time1 != -1 )
+ {
+ if ((g_fpInput = fopen(tmp, "r" ) ) == 0)
+ {
+ MdlWarning( "reader: could not open file '%s'\n", src );
+ return 0;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+ return 0;
+ }
+ else
+ {
+ time1 = FileTime (filename);
+ if (time1 == -1)
+ return 0;
+
+ if( g_bCreateMakefile )
+ {
+ CreateMakefile_AddDependency( filename );
+ return 0;
+ }
+ if ((g_fpInput = fopen(filename, "r" ) ) == 0)
+ {
+ MdlWarning( "reader: could not open file '%s'\n", src );
+ return 0;
+ }
+
+ return 1;
+ }
+}
+
+
+
+int Load_VTA( s_source_t *psource )
+{
+ char cmd[1024];
+ int option;
+
+ if (!OpenGlobalFile( psource->filename ))
+ return 0;
+
+ if (!g_quiet)
+ printf ("VTA MODEL %s\n", psource->filename);
+
+ g_iLinecount = 0;
+ while (GetLineInput())
+ {
+ g_iLinecount++;
+ sscanf( g_szLine, "%s %d", cmd, &option );
+ if (stricmp( cmd, "version" ) == 0)
+ {
+ if (option != 1)
+ {
+ MdlError("bad version\n");
+ }
+ }
+ else if (stricmp( cmd, "nodes" ) == 0)
+ {
+ psource->numbones = Grab_Nodes( psource->localBone );
+ }
+ else if (stricmp( cmd, "skeleton" ) == 0)
+ {
+ Grab_Animation( psource, "VertexAnimation" );
+ }
+ else if (stricmp( cmd, "vertexanimation" ) == 0)
+ {
+ Grab_Vertexanimation( psource, "VertexAnimation" );
+ }
+ else
+ {
+ MdlWarning("unknown studio command \"%s\"\n", cmd );
+ }
+ }
+ fclose( g_fpInput );
+
+ return 1;
+}
+
+
+void Grab_AxisInterpBones( )
+{
+ char cmd[1024], tmp[1025];
+ Vector basepos;
+ s_axisinterpbone_t *pAxis = NULL;
+ s_axisinterpbone_t *pBone = &g_axisinterpbones[g_numaxisinterpbones];
+
+ while (GetLineInput())
+ {
+ if (IsEnd( g_szLine ))
+ {
+ return;
+ }
+ int i = sscanf( g_szLine, "%1023s \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" %d", cmd, pBone->bonename, tmp, pBone->controlname, tmp, &pBone->axis );
+ if (i == 6 && stricmp( cmd, "bone") == 0)
+ {
+ // printf( "\"%s\" \"%s\" \"%s\" \"%s\"\n", cmd, pBone->bonename, tmp, pBone->controlname );
+ pAxis = pBone;
+ pBone->axis = pBone->axis - 1; // MAX uses 1..3, engine 0..2
+ g_numaxisinterpbones++;
+ pBone = &g_axisinterpbones[g_numaxisinterpbones];
+ }
+ else if (stricmp( cmd, "display" ) == 0)
+ {
+ // skip all display info
+ }
+ else if (stricmp( cmd, "type" ) == 0)
+ {
+ // skip all type info
+ }
+ else if (stricmp( cmd, "basepos" ) == 0)
+ {
+ i = sscanf( g_szLine, "basepos %f %f %f", &basepos.x, &basepos.y, &basepos.z );
+ // skip all type info
+ }
+ else if (stricmp( cmd, "axis" ) == 0)
+ {
+ Vector pos;
+ QAngle rot;
+ int j;
+ i = sscanf( g_szLine, "axis %d %f %f %f %f %f %f", &j, &pos[0], &pos[1], &pos[2], &rot[2], &rot[0], &rot[1] );
+ if (i == 7)
+ {
+ VectorAdd( basepos, pos, pAxis->pos[j] );
+ AngleQuaternion( rot, pAxis->quat[j] );
+ }
+ }
+ }
+}
+
+
+bool Grab_AimAtBones( )
+{
+ s_aimatbone_t *pAimAtBone( &g_aimatbones[g_numaimatbones] );
+
+ // Already know it's <aimconstraint> in the first string, otherwise wouldn't be here
+ if ( sscanf( g_szLine, "%*s %127s %127s %127s", pAimAtBone->bonename, pAimAtBone->parentname, pAimAtBone->aimname ) == 3 )
+ {
+ g_numaimatbones++;
+
+ char cmd[1024];
+ Vector vector;
+
+ while ( GetLineInput() )
+ {
+ g_iLinecount++;
+
+ if (IsEnd( g_szLine ))
+ {
+ return false;
+ }
+
+ if ( sscanf( g_szLine, "%1024s %f %f %f", cmd, &vector[0], &vector[1], &vector[2] ) != 4 )
+ {
+ // Allow blank lines to be skipped without error
+ bool allSpace( true );
+ for ( const char *pC( g_szLine ); *pC != '\0' && pC < ( g_szLine + 4096 ); ++pC )
+ {
+ if ( !V_isspace( *pC ) )
+ {
+ allSpace = false;
+ break;
+ }
+ }
+
+ if ( allSpace )
+ {
+ continue;
+ }
+
+ return true;
+ }
+
+ if ( stricmp( cmd, "<aimvector>" ) == 0)
+ {
+ // Make sure these are unit length on read
+ VectorNormalize( vector );
+ pAimAtBone->aimvector = vector;
+ }
+ else if ( stricmp( cmd, "<upvector>" ) == 0)
+ {
+ // Make sure these are unit length on read
+ VectorNormalize( vector );
+ pAimAtBone->upvector = vector;
+ }
+ else if ( stricmp( cmd, "<basepos>" ) == 0)
+ {
+ pAimAtBone->basepos = vector;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+
+ // If we get here, we're at EOF
+ return false;
+}
+
+
+
+void Grab_QuatInterpBones( )
+{
+ char cmd[1024];
+ Vector basepos;
+ RadianEuler rotateaxis( 0.0f, 0.0f, 0.0f );
+ RadianEuler jointorient( 0.0f, 0.0f, 0.0f );
+ s_quatinterpbone_t *pAxis = NULL;
+ s_quatinterpbone_t *pBone = &g_quatinterpbones[g_numquatinterpbones];
+
+ while (GetLineInput())
+ {
+ g_iLinecount++;
+ if (IsEnd( g_szLine ))
+ {
+ return;
+ }
+
+ int i = sscanf( g_szLine, "%s %s %s %s %s", cmd, pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname );
+
+ while ( i == 4 && stricmp( cmd, "<aimconstraint>" ) == 0 )
+ {
+ // If Grab_AimAtBones() returns false, there file is at EOF
+ if ( !Grab_AimAtBones() )
+ {
+ return;
+ }
+
+ // Grab_AimAtBones will read input into g_szLine same as here until it gets a line it doesn't understand, at which point
+ // it will exit leaving that line in g_szLine, so check for the end and scan the current buffer again and continue on with
+ // the normal QuatInterpBones process
+
+ i = sscanf( g_szLine, "%s %s %s %s %s", cmd, pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname );
+ }
+
+ if (i == 5 && stricmp( cmd, "<helper>") == 0)
+ {
+ // printf( "\"%s\" \"%s\" \"%s\" \"%s\"\n", cmd, pBone->bonename, tmp, pBone->controlname );
+ 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( g_szLine, "%*s %f ... ) etc...
+
+ if ( stricmp( cmd, "<display>" ) == 0)
+ {
+ // skip all display info
+ Vector size;
+ float distance;
+
+ i = sscanf( g_szLine, "<display> %f %f %f %f",
+ &size[0], &size[1], &size[2],
+ &distance );
+
+ if (i == 4)
+ {
+ pAxis->percentage = distance / 100.0;
+ pAxis->size = size;
+ }
+ else
+ {
+ MdlError( "Line %d: Unable to parse procedual <display> bone: %s", g_iLinecount, g_szLine );
+ }
+ }
+ else if ( stricmp( cmd, "<basepos>" ) == 0)
+ {
+ i = sscanf( g_szLine, "<basepos> %f %f %f", &basepos.x, &basepos.y, &basepos.z );
+ // skip all type info
+ }
+ else if ( stricmp( cmd, "<rotateaxis>" ) == 0)
+ {
+ i = sscanf( g_szLine, "%*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 ( stricmp( cmd, "<jointorient>" ) == 0)
+ {
+ i = sscanf( g_szLine, "%*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 ( stricmp( cmd, "<trigger>" ) == 0)
+ {
+ float tolerance;
+ RadianEuler trigger;
+ Vector pos;
+ RadianEuler ang;
+
+ QAngle rot;
+ int j;
+ i = sscanf( g_szLine, "<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 );
+
+ Quaternion q;
+ AngleQuaternion( ang, q );
+
+ if ( rotateaxis.x != 0.0 || rotateaxis.y != 0.0 || rotateaxis.z != 0.0 )
+ {
+ Quaternion q1;
+ Quaternion q2;
+ AngleQuaternion( rotateaxis, q1 );
+ QuaternionMult( q1, q, q2 );
+ q = q2;
+ }
+
+ if ( jointorient.x != 0.0 || jointorient.y != 0.0 || jointorient.z != 0.0 )
+ {
+ Quaternion q1;
+ Quaternion q2;
+ AngleQuaternion( jointorient, q1 );
+ QuaternionMult( q, q1, q2 );
+ q = q2;
+ }
+
+ j = pAxis->numtriggers++;
+ pAxis->tolerance[j] = DEG2RAD( tolerance );
+ AngleQuaternion( trigger, pAxis->trigger[j] );
+ VectorAdd( basepos, pos, pAxis->pos[j] );
+ pAxis->quat[j] = q;
+ }
+ else
+ {
+ MdlError( "Line %d: Unable to parse procedual <trigger> bone: %s", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ MdlError( "Line %d: Unable to parse procedual bone data: %s", g_iLinecount, g_szLine );
+ }
+ }
+ else
+ {
+ // Allow blank lines to be skipped without error
+ bool allSpace( true );
+ for ( const char *pC( g_szLine ); *pC != '\0' && pC < ( g_szLine + 4096 ); ++pC )
+ {
+ if ( !V_isspace( *pC ) )
+ {
+ allSpace = false;
+ break;
+ }
+ }
+
+ if ( !allSpace )
+ {
+ MdlError( "Line %d: Unable to parse procedual bone data: %s", g_iLinecount, g_szLine );
+ }
+ }
+ }
+}
+
+
+void Load_ProceduralBones( )
+{
+ char filename[256];
+ char cmd[1024];
+ int option;
+
+ GetToken( false );
+ V_strcpy_safe( filename, token );
+
+ if (!OpenGlobalFile( filename ))
+ return;
+
+ g_iLinecount = 0;
+
+ char ext[32];
+ Q_ExtractFileExtension( filename, ext, sizeof( ext ) );
+
+ if (stricmp( ext, "vrd") == 0)
+ {
+ Grab_QuatInterpBones( );
+ }
+ else
+ {
+ while (GetLineInput())
+ {
+ g_iLinecount++;
+ sscanf( g_szLine, "%s %d", cmd, &option );
+ if (stricmp( cmd, "version" ) == 0)
+ {
+ if (option != 1)
+ {
+ MdlError("bad version\n");
+ }
+ }
+ else if (stricmp( cmd, "proceduralbones" ) == 0)
+ {
+ Grab_AxisInterpBones( );
+ }
+ }
+ }
+ fclose( g_fpInput );
+}
+
+
+void Cmd_CD()
+{
+ if (cdset)
+ MdlError ("Two $cd in one model");
+ cdset = true;
+ GetToken (false);
+ V_strcpy_safe (cddir[0], token);
+ V_strcat_safe (cddir[0], "/" );
+ numdirs = 0;
+}
+
+
+void Cmd_CDMaterials()
+{
+ while (TokenAvailable())
+ {
+ GetToken (false);
+
+ char szPath[512];
+ Q_strncpy( szPath, token, sizeof( szPath ) );
+
+ int len = strlen( szPath );
+ if ( len > 0 && szPath[len-1] != '/' && szPath[len-1] != '\\' )
+ {
+ Q_strncat( szPath, "/", sizeof( szPath ), COPY_ALL_CHARACTERS );
+ }
+
+ Q_FixSlashes( szPath );
+ cdtextures[numcdtextures] = strdup( szPath );
+ numcdtextures++;
+ }
+}
+
+
+void Cmd_Pushd()
+{
+ GetToken(false);
+
+ V_strcpy_safe( cddir[numdirs+1], cddir[numdirs] );
+ V_strcat_safe( cddir[numdirs+1], token );
+ V_strcat_safe( cddir[numdirs+1], "/" );
+ numdirs++;
+}
+
+void Cmd_Popd()
+{
+ if (numdirs > 0)
+ numdirs--;
+}
+
+void Cmd_CollisionModel()
+{
+ DoCollisionModel( false );
+}
+
+void Cmd_CollisionJoints()
+{
+ DoCollisionModel( true );
+}
+
+void Cmd_ExternalTextures()
+{
+ MdlWarning( "ignoring $externaltextures, obsolete..." );
+}
+
+void Cmd_ClipToTextures()
+{
+ clip_texcoords = 1;
+}
+
+void Cmd_CollapseBones()
+{
+ g_collapse_bones = true;
+}
+
+void Cmd_CollapseBonesAggressive()
+{
+ g_collapse_bones = true;
+ g_collapse_bones_aggressive = true;
+}
+
+void Cmd_AlwaysCollapse()
+{
+ g_collapse_bones = true;
+ GetToken(false);
+ g_collapse.AddToTail( strdup( token ) );
+}
+
+void Cmd_CalcTransitions()
+{
+ g_bMultistageGraph = true;
+}
+
+void Cmd_StaticProp()
+{
+ g_staticprop = true;
+ gflags |= STUDIOHDR_FLAGS_STATIC_PROP;
+}
+
+void Cmd_ZBrush()
+{
+ g_bZBrush = true;
+}
+
+void Cmd_RealignBones()
+{
+ g_realignbones = true;
+}
+
+void Cmd_BaseLOD()
+{
+ Cmd_LOD( "$lod" );
+}
+
+void Cmd_KeyValues()
+{
+ Option_KeyValues( &g_KeyValueText );
+}
+
+void Cmd_ConstDirectionalLight()
+{
+ gflags |= STUDIOHDR_FLAGS_CONSTANT_DIRECTIONAL_LIGHT_DOT;
+
+ GetToken (false);
+ g_constdirectionalightdot = (byte)( verify_atof(token) * 255.0f );
+}
+
+void Cmd_MinLOD()
+{
+ GetToken( false );
+ g_minLod = atoi( token );
+
+ // "minlod" rules over "allowrootlods"
+ if ( g_numAllowedRootLODs > 0 && g_numAllowedRootLODs < g_minLod )
+ {
+ MdlWarning( "$minlod %d overrides $allowrootlods %d, proceeding with $allowrootlods %d.\n", g_minLod, g_numAllowedRootLODs, g_minLod );
+ g_numAllowedRootLODs = g_minLod;
+ }
+}
+
+void Cmd_AllowRootLODs()
+{
+ GetToken( false );
+ g_numAllowedRootLODs = atoi( token );
+
+ // Root LOD restriction has to obey "minlod" request
+ if ( g_numAllowedRootLODs > 0 && g_numAllowedRootLODs < g_minLod )
+ {
+ MdlWarning( "$allowrootlods %d is conflicting with $minlod %d, proceeding with $allowrootlods %d.\n", g_numAllowedRootLODs, g_minLod, g_minLod );
+ g_numAllowedRootLODs = g_minLod;
+ }
+}
+
+
+void Cmd_BoneSaveFrame( )
+{
+ s_bonesaveframe_t tmp;
+
+ // bone name
+ GetToken( false );
+ V_strcpy_safe( tmp.name, token );
+
+ tmp.bSavePos = false;
+ tmp.bSaveRot = false;
+ while (TokenAvailable( ))
+ {
+ GetToken( false );
+ if (stricmp( "position", token ) == 0)
+ {
+ tmp.bSavePos = true;
+ }
+ else if (stricmp( "rotation", token ) == 0)
+ {
+ tmp.bSaveRot = true;
+ }
+ else
+ {
+ MdlError( "unknown option \"%s\" on $bonesaveframe : %s\n", token, tmp.name );
+ }
+ }
+
+ g_bonesaveframe.AddToTail( tmp );
+}
+
+
+//
+// This is the master list of the commands a QC file supports.
+// To add a new command to the QC files, add it here.
+//
+struct
+{
+ char *m_pName;
+ void (*m_pCmd)();
+} g_Commands[] =
+{
+ { "$cd", Cmd_CD },
+ { "$modelname", Cmd_Modelname },
+ { "$cdmaterials", Cmd_CDMaterials },
+ { "$pushd", Cmd_Pushd },
+ { "$popd", Cmd_Popd },
+ { "$scale", Cmd_ScaleUp },
+ { "$root", Cmd_Root },
+ { "$controller", Cmd_Controller },
+ { "$screenalign", Cmd_ScreenAlign },
+ { "$model", Cmd_Model },
+ { "$collisionmodel", Cmd_CollisionModel },
+ { "$collisionjoints", Cmd_CollisionJoints },
+ { "$collisiontext", Cmd_CollisionText },
+ { "$body", Cmd_Body },
+ { "$bodygroup", Cmd_Bodygroup },
+ { "$animation", Cmd_Animation },
+ { "$autocenter", Cmd_Autocenter },
+ { "$sequence", Cmd_Sequence },
+ { "$append", Cmd_Append },
+ { "$prepend", Cmd_Prepend },
+ { "$continue", Cmd_Continue },
+ { "$declaresequence", Cmd_DeclareSequence },
+ { "$declareanimation", Cmd_DeclareAnimation },
+ { "$cmdlist", Cmd_Cmdlist },
+ { "$animblocksize", Cmd_AnimBlockSize },
+ { "$weightlist", Cmd_Weightlist },
+ { "$defaultweightlist", Cmd_DefaultWeightlist },
+ { "$ikchain", Cmd_IKChain },
+ { "$ikautoplaylock", Cmd_IKAutoplayLock },
+ { "$eyeposition", Cmd_Eyeposition },
+ { "$illumposition", Cmd_Illumposition },
+ { "$origin", Cmd_Origin },
+ { "$upaxis", Cmd_UpAxis },
+ { "$bbox", Cmd_BBox },
+ { "$cbox", Cmd_CBox },
+ { "$gamma", Cmd_Gamma },
+ { "$texturegroup", Cmd_TextureGroup },
+ { "$hgroup", Cmd_Hitgroup },
+ { "$hbox", Cmd_Hitbox },
+ { "$hboxset", Cmd_HitboxSet },
+ { "$surfaceprop", Cmd_SurfaceProp },
+ { "$jointsurfaceprop", Cmd_JointSurfaceProp },
+ { "$contents", Cmd_Contents },
+ { "$jointcontents", Cmd_JointContents },
+ { "$attachment", Cmd_Attachment },
+ { "$bonemerge", Cmd_BoneMerge },
+ { "$externaltextures", Cmd_ExternalTextures },
+ { "$cliptotextures", Cmd_ClipToTextures },
+ { "$renamebone", Cmd_Renamebone },
+ { "$collapsebones", Cmd_CollapseBones },
+ { "$collapsebonesaggressive", Cmd_CollapseBonesAggressive },
+ { "$alwayscollapse", Cmd_AlwaysCollapse },
+ { "$proceduralbones", Load_ProceduralBones },
+ { "$skiptransition", Cmd_Skiptransition },
+ { "$calctransitions", Cmd_CalcTransitions },
+ { "$staticprop", Cmd_StaticProp },
+ { "$zbrush", Cmd_ZBrush },
+ { "$realignbones", Cmd_RealignBones },
+ { "$forcerealign", Cmd_ForceRealign },
+ { "$lod", Cmd_BaseLOD },
+ { "$shadowlod", Cmd_ShadowLOD },
+ { "$poseparameter", Cmd_PoseParameter },
+ { "$heirarchy", Cmd_ForcedHierarchy },
+ { "$hierarchy", Cmd_ForcedHierarchy },
+ { "$insertbone", Cmd_InsertHierarchy },
+ { "$limitrotation", Cmd_LimitRotation },
+ { "$definebone", Cmd_DefineBone },
+ { "$jigglebone", Cmd_JiggleBone },
+ { "$includemodel", Cmd_IncludeModel },
+ { "$opaque", Cmd_Opaque },
+ { "$mostlyopaque", Cmd_TranslucentTwoPass },
+// { "$platform", Cmd_Platform },
+ { "$keyvalues", Cmd_KeyValues },
+ { "$obsolete", Cmd_Obsolete },
+ { "$renamematerial", Cmd_RenameMaterial },
+ { "$fakevta", Cmd_FakeVTA },
+ { "$noforcedfade", Cmd_NoForcedFade },
+ { "$skipboneinbbox", Cmd_SkipBoneInBBox },
+ { "$forcephonemecrossfade", Cmd_ForcePhonemeCrossfade },
+ { "$lockbonelengths", Cmd_LockBoneLengths },
+ { "$unlockdefinebones", Cmd_UnlockDefineBones },
+ { "$constantdirectionallight", Cmd_ConstDirectionalLight },
+ { "$minlod", Cmd_MinLOD },
+ { "$allowrootlods", Cmd_AllowRootLODs },
+ { "$bonesaveframe", Cmd_BoneSaveFrame },
+ { "$ambientboost", Cmd_AmbientBoost },
+ { "$centerbonesonverts", Cmd_CenterBonesOnVerts },
+ { "$donotcastshadows", Cmd_DoNotCastShadows },
+ { "$casttextureshadows", Cmd_CastTextureShadows },
+ { "$motionrollback", Cmd_MotionExtractionRollBack },
+ { "$sectionframes", Cmd_SectionFrames },
+ { "$clampworldspace", Cmd_ClampWorldspace },
+ { "$maxeyedeflection", Cmd_MaxEyeDeflection },
+ { "$boneflexdriver", Cmd_BoneFlexDriver },
+ { "$checkuv", Cmd_CheckUV }
+};
+
+
+/*
+===============
+ParseScript
+===============
+*/
+void ParseScript (void)
+{
+ while (1)
+ {
+ GetToken (true);
+ if (endofscript)
+ return;
+
+ // Check all the commands we know about.
+ int i;
+ for ( i=0; i < ARRAYSIZE( g_Commands ); i++ )
+ {
+ if ( !stricmp( g_Commands[i].m_pName, token ) )
+ {
+ g_Commands[i].m_pCmd();
+ break;
+ }
+ }
+ if ( i == ARRAYSIZE( g_Commands ) )
+ {
+ if( !g_bCreateMakefile )
+ {
+ TokenError("bad command %s\n", token);
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Generate the model name
+//-----------------------------------------------------------------------------
+bool GenerateModelName( CDmeMDLMakefile *pMDLMakeFile )
+{
+ // The model name is implicit in the makefile name
+ // NOTE: Model name is relative to the 'models' directory
+ char pOutputFullPath[MAX_PATH];
+ pMDLMakeFile->GetOutputName( pOutputFullPath, sizeof(pOutputFullPath) );
+ Q_SetExtension( pOutputFullPath, ".mdl", sizeof( pOutputFullPath) );
+
+ char pModelSubDir[MAX_PATH];
+ GetModSubdirectory( "models", pModelSubDir, sizeof(pModelSubDir) );
+
+ char pRelativePath[MAX_PATH];
+ if ( !Q_MakeRelativePath( pOutputFullPath, pModelSubDir, pRelativePath, sizeof(pRelativePath) ) )
+ {
+ MdlError( "Makefile \"%s\" doesn't lie under the correct vproject \"%s\"!\n",
+ pOutputFullPath, pModelSubDir );
+ return false;
+ }
+
+ ProcessModelName( pRelativePath );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process skins
+//-----------------------------------------------------------------------------
+bool GenerateSkin( CDmeMDLMakefile *pMDLMakeFile )
+{
+ CUtlVector< CDmeHandle< CDmeSourceSkin > > bodies;
+ pMDLMakeFile->GetSources< CDmeSourceSkin >( bodies );
+ int nCount = bodies.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !bodies[i] )
+ continue;
+
+ char pFullPath[MAX_PATH];
+ pMDLMakeFile->GetSourceFullPath( bodies[i], pFullPath, sizeof(pFullPath) );
+
+ // Empty strings are ignored
+ if ( !pFullPath[0] )
+ continue;
+
+ ProcessCmdBody( pFullPath, bodies[i] );
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process animations
+//-----------------------------------------------------------------------------
+bool GenerateAnimations( CDmeMDLMakefile *pMDLMakeFile )
+{
+ CUtlVector< CDmeHandle< CDmeSourceAnimation > > animationFiles;
+ pMDLMakeFile->GetSources< CDmeSourceAnimation >( animationFiles );
+
+ int nCount = animationFiles.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( !animationFiles[i] )
+ continue;
+
+ char pFullPath[MAX_PATH];
+ pMDLMakeFile->GetSourceFullPath( animationFiles[i], pFullPath, sizeof(pFullPath) );
+
+ // Empty strings are ignored
+ if ( !pFullPath[0] )
+ continue;
+
+ // Totally spoof the animation info.. not sure where to get it from yet
+ // assume it's an animation reference
+ // first look up an existing animation
+ s_sequence_t *pseq = ProcessCmdSequence( animationFiles[i]->m_AnimationName );
+ if ( !pseq )
+ continue;
+
+ int n;
+ s_animation_t *animations[64];
+ int numblends = 0;
+ for ( n = 0; n < g_numani; n++ )
+ {
+ if ( !Q_stricmp( pFullPath, 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, pFullPath );
+ }
+ // hack to allow animation commands to refer to same sequence
+ if ( numblends == 1 )
+ {
+ pseq->panim[0][0] = animations[0];
+ }
+
+ // Look up the source animation from the animation name
+ for ( int j = 0; j < numblends; ++j )
+ {
+ s_sourceanim_t *pSourceAnim = FindSourceAnim( animations[j]->source, animationFiles[i]->m_SourceAnimationName );
+
+ // NOTE: This always affects the first source anim read in
+ if ( pSourceAnim )
+ {
+ animations[j]->startframe = pSourceAnim->startframe;
+ animations[j]->endframe = pSourceAnim->endframe;
+
+ if ( !g_bCreateMakefile && animations[j]->endframe < animations[j]->startframe )
+ {
+ TokenError( "end frame before start frame in %s", animations[j]->name );
+ }
+
+ animations[j]->numframes = animations[j]->endframe - animations[j]->startframe + 1;
+ Q_strncpy( animations[j]->animationname, animationFiles[i]->m_SourceAnimationName, sizeof(animations[j]->animationname) );
+ }
+ else
+ {
+ MdlError( "Requested unknown animation block name %s\n", animationFiles[i]->m_SourceAnimationName.Get() );
+ }
+ }
+
+ ProcessSequence( pseq, numblends, animations, false );
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Parse the MDL makefile
+//-----------------------------------------------------------------------------
+void ParseMDLMakeFile( CDmeMDLMakefile *pMDLMakeFile )
+{
+ if ( !GenerateModelName( pMDLMakeFile ) )
+ return;
+
+ // All DMX files have Y as the up axis
+ RadianEuler angles( M_PI / 2.0f, 0.0f, M_PI / 2.0f );
+ ProcessUpAxis( angles );
+
+ // Process bodies
+ if ( !GenerateSkin( pMDLMakeFile ) )
+ return;
+
+ // Process animations
+ if ( !GenerateAnimations( pMDLMakeFile ) )
+ return;
+}
+
+
+// Used by the CheckSurfaceProps.py script.
+// They specify the .mdl file and it prints out all the surface props that the model uses.
+bool HandlePrintSurfaceProps( int &returnValue )
+{
+ const char *pFilename = CommandLine()->ParmValue( "-PrintSurfaceProps", (const char*)NULL );
+ if ( pFilename )
+ {
+ CUtlVector<char> buf;
+
+ FILE *fp = fopen( pFilename, "rb" );
+ if ( fp )
+ {
+ fseek( fp, 0, SEEK_END );
+ buf.SetSize( ftell( fp ) );
+ fseek( fp, 0, SEEK_SET );
+ fread( buf.Base(), 1, buf.Count(), fp );
+
+ fclose( fp );
+
+ studiohdr_t *pHdr = (studiohdr_t*)buf.Base();
+
+ Studio_ConvertStudioHdrToNewVersion( pHdr );
+
+ if ( pHdr->version == STUDIO_VERSION )
+ {
+ for ( int i=0; i < pHdr->numbones; i++ )
+ {
+ mstudiobone_t *pBone = pHdr->pBone( i );
+ printf( "%s\n", pBone->pszSurfaceProp() );
+ }
+
+ returnValue = 0;
+ }
+ else
+ {
+ printf( "-PrintSurfaceProps: '%s' is wrong version (%d should be %d).\n",
+ pFilename, pHdr->version, STUDIO_VERSION );
+ returnValue = 1;
+ }
+ }
+ else
+ {
+ printf( "-PrintSurfaceProps: can't open '%s'\n", pFilename );
+ returnValue = 1;
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+// Used by the modelstats.pl script.
+// They specify the .mdl file and it prints out perf info.
+bool HandleMdlReport( int &returnValue )
+{
+ const char *pFilename = CommandLine()->ParmValue( "-mdlreport", (const char*)NULL );
+ if ( pFilename )
+ {
+ CUtlVector<char> buf;
+
+ FILE *fp = fopen( pFilename, "rb" );
+ if ( fp )
+ {
+ fseek( fp, 0, SEEK_END );
+ buf.SetSize( ftell( fp ) );
+ fseek( fp, 0, SEEK_SET );
+ fread( buf.Base(), 1, buf.Count(), fp );
+
+ fclose( fp );
+
+ studiohdr_t *pHdr = (studiohdr_t*)buf.Base();
+
+ Studio_ConvertStudioHdrToNewVersion( pHdr );
+
+ if ( pHdr->version == STUDIO_VERSION )
+ {
+ int flags = SPEWPERFSTATS_SHOWPERF;
+ if( CommandLine()->CheckParm( "-mdlreportspreadsheet", NULL ) )
+ {
+ flags |= SPEWPERFSTATS_SPREADSHEET;
+ }
+ SpewPerfStats( pHdr, pFilename, flags );
+
+ returnValue = 0;
+ }
+ else
+ {
+ printf( "-mdlreport: '%s' is wrong version (%d should be %d).\n",
+ pFilename, pHdr->version, STUDIO_VERSION );
+ returnValue = 1;
+ }
+ }
+ else
+ {
+ printf( "-mdlreport: can't open '%s'\n", pFilename );
+ returnValue = 1;
+ }
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+void UsageAndExit()
+{
+ MdlError( "Bad or missing options\n"
+ "usage: studiomdl [options] <file.qc>\n"
+ "options:\n"
+ "[-a <normal_blend_angle>]\n"
+ "[-checklengths]\n"
+ "[-d] - dump glview files\n"
+ "[-definebones]\n"
+ "[-f] - flip all triangles\n"
+ "[-fullcollide] - don't truncate really big collisionmodels\n"
+ "[-game <gamedir>]\n"
+ "[-h] - dump hboxes\n"
+ "[-i] - ignore warnings\n"
+ "[-minlod <lod>] - truncate to highest detail <lod>\n"
+ "[-n] - tag bad normals\n"
+ "[-perf] report perf info upon compiling model\n"
+ "[-printbones]\n"
+ "[-printgraph]\n"
+ "[-quiet] - operate silently\n"
+ "[-r] - tag reversed\n"
+ "[-t <texture>]\n"
+ "[-x360] - generate xbox360 output\n"
+ "[-nox360] - disable xbox360 output(default)\n"
+ "[-nowarnings] - disable warnings\n"
+ "[-dumpmaterials] - dump out material names\n"
+ "[-mdlreport] model.mdl - report perf info\n"
+ "[-mdlreportspreadsheet] - report perf info as a comma-delimited spreadsheet\n"
+ "[-striplods] - use only lod0\n"
+ "[-overridedefinebones] - equivalent to specifying $unlockdefinebones in .qc file\n"
+ "[-stripmodel] - process binary model files and strip extra lod data\n"
+ "[-stripvhv] - strip hardware verts to match the stripped model\n"
+ "[-vsi] - generate stripping information .vsi file - can be used on .mdl files too\n"
+ );
+}
+
+#ifndef _DEBUG
+
+LONG __stdcall VExceptionFilter( struct _EXCEPTION_POINTERS *ExceptionInfo )
+{
+ MdlExceptionFilter( ExceptionInfo->ExceptionRecord->ExceptionCode );
+ return EXCEPTION_EXECUTE_HANDLER; // (never gets here anyway)
+}
+
+#endif
+/*
+==============
+main
+==============
+*/
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+class CStudioMDLApp : public CDefaultAppSystemGroup< CSteamAppSystemGroup >
+{
+ typedef CDefaultAppSystemGroup< CSteamAppSystemGroup > BaseClass;
+
+public:
+ // Methods of IApplication
+ virtual bool Create();
+ virtual bool PreInit( );
+ virtual int Main();
+ virtual void PostShutdown();
+
+private:
+ int Main_StripModel();
+ int Main_StripVhv();
+ int Main_MakeVsi();
+
+private:
+ bool ParseArguments();
+};
+
+static bool CStudioMDLApp_SuggestGameInfoDirFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories )
+{
+ const char *pProcessFileName = NULL;
+ int nParmCount = CommandLine()->ParmCount();
+ if ( nParmCount > 1 )
+ {
+ pProcessFileName = CommandLine()->GetParm( nParmCount - 1 );
+ }
+
+ if ( pProcessFileName )
+ {
+ Q_MakeAbsolutePath( pchPathBuffer, nBufferLength, pProcessFileName );
+
+ if ( pbBubbleDirectories )
+ *pbBubbleDirectories = true;
+
+ return true;
+ }
+
+ return false;
+}
+
+int main( int argc, char **argv )
+{
+ SetSuggestGameInfoDirFn( CStudioMDLApp_SuggestGameInfoDirFn );
+
+ CStudioMDLApp s_ApplicationObject;
+ CSteamApplication s_SteamApplicationObject( &s_ApplicationObject );
+ return AppMain( argc, argv, &s_SteamApplicationObject );
+}
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+bool CStudioMDLApp::Create()
+{
+ InstallSpewFunction();
+ // override the default spew function
+ SpewOutputFunc( MdlSpewOutputFunc );
+
+ MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false );
+
+#ifndef _DEBUG
+ SetUnhandledExceptionFilter( VExceptionFilter );
+#endif
+
+ if ( CommandLine()->ParmCount() == 1 )
+ {
+ UsageAndExit();
+ return false;
+ }
+
+ int nReturnValue;
+ if ( HandlePrintSurfaceProps( nReturnValue ) )
+ return false;
+
+ if ( !ParseArguments() )
+ return false;
+
+ AppSystemInfo_t appSystems[] =
+ {
+ { "vstdlib.dll", PROCESS_UTILS_INTERFACE_VERSION },
+ { "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION },
+ { "studiorender.dll", STUDIO_RENDER_INTERFACE_VERSION },
+ { "mdllib.dll", MDLLIB_INTERFACE_VERSION },
+ { "", "" } // Required to terminate the list
+ };
+
+ AddSystem( g_pDataModel, VDATAMODEL_INTERFACE_VERSION );
+ AddSystem( g_pDmElementFramework, VDMELEMENTFRAMEWORK_VERSION );
+ AddSystem( g_pDmSerializers, DMSERIALIZERS_INTERFACE_VERSION );
+
+ // Add in the locally-defined studio data cache
+ AppModule_t studioDataCacheModule = LoadModule( Sys_GetFactoryThis() );
+ AddSystem( studioDataCacheModule, STUDIO_DATA_CACHE_INTERFACE_VERSION );
+
+ // Add the P4 module separately so that if it is absent (say in the SDK) then the other system will initialize properly
+ if ( !CommandLine()->FindParm( "-nop4" ) )
+ {
+ AppModule_t p4Module = LoadModule( "p4lib.dll" );
+ AddSystem( p4Module, P4_INTERFACE_VERSION );
+ }
+
+ bool bOk = AddSystems( appSystems );
+ if ( !bOk )
+ return false;
+
+ IMaterialSystem *pMaterialSystem = (IMaterialSystem*)FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION );
+ if ( !pMaterialSystem )
+ return false;
+
+ pMaterialSystem->SetShaderAPI( "shaderapiempty.dll" );
+
+ return true;
+}
+
+bool CStudioMDLApp::PreInit( )
+{
+ CreateInterfaceFn factory = GetFactory();
+ ConnectTier1Libraries( &factory, 1 );
+ ConnectTier2Libraries( &factory, 1 );
+ ConnectTier3Libraries( &factory, 1 );
+
+ if ( !g_pFullFileSystem || !g_pDataModel || !g_pMaterialSystem || !g_pStudioRender )
+ {
+ Warning( "StudioMDL is missing a required interface!\n" );
+ return false;
+ }
+
+ if ( !SetupSearchPaths( g_path, false, true ) )
+ return false;
+
+ // NOTE: This is necessary to get the cmdlib filesystem stuff to work.
+ g_pFileSystem = g_pFullFileSystem;
+
+ // NOTE: This is stuff copied out of cmdlib necessary to get
+ // the tools in cmdlib working
+ FileSystem_SetupStandardDirectories( g_path, GetGameInfoPath() );
+ return true;
+}
+
+
+void CStudioMDLApp::PostShutdown()
+{
+ DisconnectTier3Libraries();
+ DisconnectTier2Libraries();
+ DisconnectTier1Libraries();
+}
+
+
+//-----------------------------------------------------------------------------
+// Method which parses arguments
+//-----------------------------------------------------------------------------
+bool CStudioMDLApp::ParseArguments()
+{
+ g_currentscale = g_defaultscale = 1.0;
+ g_defaultrotation = RadianEuler( 0, 0, M_PI / 2 );
+
+ // skip weightlist 0
+ g_numweightlist = 1;
+
+ eyeposition = Vector( 0, 0, 0 );
+ gflags = 0;
+ numrep = 0;
+ tag_reversed = 0;
+ tag_normals = 0;
+
+ normal_blend = cos( DEG2RAD( 2.0 ));
+
+ g_gamma = 2.2;
+
+ g_staticprop = false;
+ g_centerstaticprop = false;
+
+ g_realignbones = false;
+ g_constdirectionalightdot = 0;
+
+ g_bDumpGLViewFiles = false;
+ g_quiet = false;
+
+ g_illumpositionattachment = 0;
+ g_flMaxEyeDeflection = 0.0f;
+
+ int argc = CommandLine()->ParmCount();
+ int i;
+ for ( i = 1; i < argc - 1; i++ )
+ {
+ const char *pArgv = CommandLine()->GetParm( i );
+ if ( pArgv[0] != '-' )
+ continue;
+
+ if ( !Q_stricmp( pArgv, "-allowdebug" ) )
+ {
+ // Ignore, used by interface system to catch debug builds checked into release tree
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-mdlreport" ) )
+ {
+ // Will reparse later, ignore rest of arguments.
+ return true;
+ }
+
+ if ( !Q_stricmp( pArgv, "-mdlreportspreadsheet" ) )
+ {
+ // Will reparse later, ignore for now.
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-ihvtest" ) )
+ {
+ ++i;
+ g_IHVTest = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-overridedefinebones" ) )
+ {
+ g_bOverridePreDefinedBones = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-striplods" ) )
+ {
+ g_bStripLods = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-stripmodel" ) )
+ {
+ g_eRunMode = RUN_MODE_STRIP_MODEL;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-stripvhv" ) )
+ {
+ g_eRunMode = RUN_MODE_STRIP_VHV;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-vsi" ) )
+ {
+ g_bMakeVsi = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-quiet" ) )
+ {
+ g_quiet = true;
+ g_verbose = false;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-verbose" ) )
+ {
+ g_quiet = false;
+ g_verbose = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-fullcollide" ) )
+ {
+ g_badCollide = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-checklengths" ) )
+ {
+ g_bCheckLengths = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-printbones" ) )
+ {
+ g_bPrintBones = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-perf" ) )
+ {
+ g_bPerf = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-printgraph" ) )
+ {
+ g_bDumpGraph = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-definebones" ) )
+ {
+ g_definebones = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-makefile" ) )
+ {
+ g_bCreateMakefile = true;
+ g_quiet = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-verify" ) )
+ {
+ g_bVerifyOnly = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-minlod" ) )
+ {
+ g_minLod = atoi( CommandLine()->GetParm( ++i ) );
+ continue;
+ }
+
+ if (!Q_stricmp( pArgv, "-x360"))
+ {
+ StudioByteSwap::ActivateByteSwapping( true ); // Set target to big endian
+ g_bX360 = true;
+ continue;
+ }
+
+ if (!Q_stricmp( pArgv, "-nox360"))
+ {
+ g_bX360 = false;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-nowarnings" ) )
+ {
+ g_bNoWarnings = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-maxwarnings" ) )
+ {
+ g_maxWarnings = atoi( CommandLine()->GetParm( ++i ) );
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-preview" ) )
+ {
+ g_bBuildPreview = true;
+ continue;
+ }
+
+ if ( !Q_stricmp( pArgv, "-dumpmaterials" ) )
+ {
+ g_bDumpMaterials = true;
+ continue;
+ }
+
+ if ( pArgv[1] && pArgv[2] == '\0' )
+ {
+ switch( pArgv[1] )
+ {
+ case 't':
+ i++;
+ V_strcpy_safe( defaulttexture[numrep], pArgv );
+ if (i < argc - 2 && CommandLine()->GetParm(i + 1)[0] != '-')
+ {
+ i++;
+ V_strcpy_safe( sourcetexture[numrep], pArgv );
+ printf("Replacing %s with %s\n", sourcetexture[numrep], defaulttexture[numrep] );
+ }
+ printf( "Using default texture: %s\n", defaulttexture[numrep] );
+ numrep++;
+ break;
+ case 'r':
+ tag_reversed = 1;
+ break;
+ case 'n':
+ tag_normals = 1;
+ break;
+ case 'a':
+ i++;
+ normal_blend = cos( DEG2RAD( verify_atof( pArgv ) ) );
+ break;
+ case 'h':
+ dump_hboxes = 1;
+ break;
+ case 'i':
+ ignore_warnings = 1;
+ break;
+ case 'd':
+ g_bDumpGLViewFiles = true;
+ break;
+// case 'p':
+// i++;
+// V_strcpy_safe( qproject, pArgv );
+// break;
+ }
+ }
+ }
+
+ if ( i >= argc )
+ {
+ // misformed arguments
+ // otherwise generating unintended results
+ UsageAndExit();
+ return false;
+ }
+
+ const char *pArgv = CommandLine()->GetParm( i );
+ Q_strncpy( g_path, pArgv, sizeof(g_path) );
+ if ( Q_IsAbsolutePath( g_path ) )
+ {
+ // Set the working directory to be the path of the qc file
+ // so the relative-file fopen code works
+ char pQCDir[MAX_PATH];
+ Q_ExtractFilePath( g_path, pQCDir, sizeof(pQCDir) );
+ _chdir( pQCDir );
+ }
+ Q_StripExtension( pArgv, outname, sizeof( outname ) );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: search through the "GamePath" key and create a mirrored version in the content path searches
+//-----------------------------------------------------------------------------
+
+void AddContentPaths( )
+{
+ // look for the "content" in the path to the initial QC file
+ char *match = "content\\";
+ char *sp = strstr( qdir, match );
+ if (!sp)
+ return;
+
+ // copy off everything before and including "content"
+ char pre[1024];
+ strncpy( pre, qdir, sp - qdir + strlen( match ) );
+ pre[sp - qdir + strlen( match )] = '\0';
+ sp = sp + strlen( match );
+
+ // copy off everything folling the word after "content"
+ char post[1024];
+ sp = strstr( sp+1, "\\" );
+ V_strcpy_safe( post, sp );
+
+ // get a copy of the game search paths
+ char paths[1024];
+ g_pFullFileSystem->GetSearchPath( "GAME", false, paths, sizeof( paths ) );
+ if (!g_quiet)
+ printf("all paths:%s\n", paths );
+
+ // pull out the game names and insert them into a content path string
+ sp = strstr( paths, "game\\" );
+ while (sp)
+ {
+ char temp[1024];
+ sp = sp + 5;
+ char *sz = strstr( sp, "\\" );
+ if (!sz)
+ return;
+
+ V_strcpy_safe( temp, pre );
+ strncat( temp, sp, sz - sp );
+ V_strcat_safe( temp, post );
+ sp = sz;
+ sp = strstr( sp, "game\\" );
+ CmdLib_AddBasePath( temp );
+ if (!g_quiet)
+ printf("content:%s\n", temp );
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// The application object
+//-----------------------------------------------------------------------------
+int CStudioMDLApp::Main()
+{
+ const bool bP4DLLExists = g_pFullFileSystem->FileExists( "p4lib.dll", "EXECUTABLE_PATH" );
+
+ // No p4 mode if specified on the command line or no p4lib.dll found
+ if ( ( CommandLine()->FindParm( "-nop4" ) ) || ( !bP4DLLExists ) )
+ {
+ g_bNoP4 = true;
+ g_p4factory->SetDummyMode( true );
+ }
+
+ // Set the named changelist
+ g_p4factory->SetOpenFileChangeList( "StudioMDL Auto Checkout" );
+
+ // This bit of hackery allows us to access files on the harddrive
+ g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD );
+
+ MaterialSystem_Config_t config;
+ g_pMaterialSystem->OverrideConfig( config, false );
+
+ int nReturnValue;
+ if ( HandleMdlReport( nReturnValue ) )
+ return false;
+
+ // Don't bother with undo here
+ g_pDataModel->SetUndoEnabled( false );
+
+ // look for the "content\hl2x" string in the qdir and add what should be the correct path as an alternate
+ // FIXME: add these to an envvar if folks are using complicated directory mappings instead of defaults
+ char *match = "content\\hl2x\\";
+ char *sp = strstr( qdir, match );
+ if (sp)
+ {
+ char temp[1024];
+ strncpy( temp, qdir, sp - qdir + strlen( match ) );
+ temp[sp - qdir + strlen( match )] = '\0';
+ CmdLib_AddBasePath( temp );
+ V_strcat_safe( temp, "..\\..\\..\\..\\main\\content\\hl2\\" );
+ CmdLib_AddBasePath( temp );
+ }
+
+ AddContentPaths();
+
+ if (!g_quiet)
+ {
+ printf("qdir: \"%s\"\n", qdir );
+ printf("gamedir: \"%s\"\n", gamedir );
+ printf("g_path: \"%s\"\n", g_path );
+ }
+
+ switch ( g_eRunMode )
+ {
+ case RUN_MODE_STRIP_MODEL:
+ return Main_StripModel();
+
+ case RUN_MODE_STRIP_VHV:
+ return Main_StripVhv();
+
+ case RUN_MODE_BUILD:
+ default:
+ break;
+ }
+
+ const char *pExt = Q_GetFileExtension( g_path );
+
+ // Look for the presence of a .mdl file (only -vsi is currently supported for .mdl files)
+ if ( pExt && !Q_stricmp( pExt, "mdl" ) )
+ {
+ if ( g_bMakeVsi )
+ return Main_MakeVsi();
+
+ printf( "ERROR: .qc or .dmx file should be specified to build.\n" );
+ return 1;
+ }
+
+
+ if ( !g_quiet )
+ printf( "Building binary model files...\n" );
+
+ // Look for the presence of a .dmx file of the same name
+ // If so, load it first
+ CDmeMDLMakefile *pMDLMakeFile = NULL;
+
+ if ( pExt && !Q_stricmp( pExt, "dmx" ) )
+ {
+ CDmElement *pRoot;
+ if ( g_pDataModel->RestoreFromFile( g_path, NULL, NULL, &pRoot ) != DMFILEID_INVALID )
+ {
+ pMDLMakeFile = CastElement<CDmeMDLMakefile>( pRoot );
+ }
+ };
+
+ Q_FileBase( g_path, g_path, sizeof( g_path ) );
+ Q_DefaultExtension( g_path, pMDLMakeFile ? ".dmx" : ".qc", sizeof( g_path ) );
+ if (!g_quiet)
+ {
+ printf( "Working on \"%s\"\n", g_path );
+ }
+
+ // Turn on checking for special single character tokens while parsing
+ SetCheckSingleCharTokens( true );
+ SetSingleCharTokenList( "{}()," );
+
+ // Set up script loading callback, discarding default callback
+ ( void ) SetScriptLoadedCallback( StudioMdl_ScriptLoadedCallback );
+
+ // load the script
+ if ( !pMDLMakeFile )
+ {
+ LoadScriptFile(g_path);
+ }
+
+ V_strcpy_safe( fullpath, g_path );
+ V_strcpy_safe( fullpath, ExpandPath( fullpath ) );
+ V_strcpy_safe( fullpath, ExpandArg( fullpath ) );
+
+ // default to having one entry in the LOD list that doesn't do anything so
+ // that we don't have to do any special cases for the first LOD.
+ g_ScriptLODs.Purge();
+ g_ScriptLODs.AddToTail(); // add an empty one
+ g_ScriptLODs[0].switchValue = 0.0f;
+
+ //
+ // parse it
+ //
+ ClearModel();
+
+// V_strcpy_safe( g_pPlatformName, "" );
+ if ( pMDLMakeFile )
+ {
+ ParseMDLMakeFile( pMDLMakeFile );
+ }
+ else
+ {
+ ParseScript();
+ }
+
+ if ( !g_bCreateMakefile )
+ {
+ SetSkinValues();
+
+ SimplifyModel();
+
+ ConsistencyCheckSurfaceProp();
+ ConsistencyCheckContents();
+
+ CollisionModel_Build();
+
+ // ValidateSharedAnimationGroups();
+
+ WriteModelFiles();
+ }
+
+ if ( pMDLMakeFile )
+ {
+ DestroyElement( pMDLMakeFile );
+ pMDLMakeFile = NULL;
+ }
+
+ if ( g_bCreateMakefile )
+ {
+ CreateMakefile_OutputMakefile();
+ }
+ else if ( g_bMakeVsi )
+ {
+ Q_snprintf( g_path, ARRAYSIZE( g_path ), "%smodels/%s", gamedir, outname );
+ Main_MakeVsi();
+ }
+
+ if (!g_quiet)
+ {
+ printf("\nCompleted \"%s\"\n", g_path);
+ }
+
+ g_pDataModel->UnloadFile( DMFILEID_INVALID );
+
+ return 0;
+}
+
+
+
+
+
+//
+// WriteFileToDisk
+// Equivalent to g_pFullFileSystem->WriteFile( pFileName, pPath, buf ), but works
+// for relative paths.
+//
+bool WriteFileToDisk( const char *pFileName, const char *pPath, CUtlBuffer &buf )
+{
+ // For some reason calling full filesystem will write into hl2 root dir
+ // return g_pFullFileSystem->WriteFile( pFileName, pPath, buf );
+
+ FILE *f = fopen( pFileName, "wb" );
+ if ( !f )
+ return false;
+
+ fwrite( buf.Base(), 1, buf.TellPut(), f );
+ fclose( f );
+ return true;
+}
+
+//
+// WriteBufferToFile
+// Helper to concatenate file base and extension.
+//
+bool WriteBufferToFile( CUtlBuffer &buf, const char *szFilebase, const char *szExt )
+{
+ char szFilename[ 1024 ];
+ Q_snprintf( szFilename, ARRAYSIZE( szFilename ), "%s%s", szFilebase, szExt );
+ return WriteFileToDisk( szFilename, NULL, buf );
+}
+
+
+//
+// LoadBufferFromFile
+// Loads the buffer from file, return true on success, false otherwise.
+// If bError is true prints an error upon failure.
+//
+bool LoadBufferFromFile( CUtlBuffer &buffer, char const *szFilebase, char const *szExt, bool bError = true )
+{
+ char szFilename[1024];
+ Q_snprintf( szFilename, ARRAYSIZE( szFilename ), "%s%s", szFilebase, szExt );
+
+ if ( g_pFullFileSystem->ReadFile( szFilename, NULL, buffer ) )
+ return true;
+
+ if ( bError )
+ MdlError( "Failed to open '%s'!\n", szFilename );
+
+ return false;
+}
+
+
+bool Load3ModelBuffers( CUtlBuffer &bufMDL, CUtlBuffer &bufVVD, CUtlBuffer &bufVTX, char const *szFilebase )
+{
+ // Load up the mdl file
+ if ( !LoadBufferFromFile( bufMDL, szFilebase, ".mdl" ) )
+ return false;
+
+ // Load up the vvd file
+ if ( !LoadBufferFromFile( bufVVD, szFilebase, ".vvd" ) )
+ return false;
+
+ // Load up the dx90.vtx file
+ if ( !LoadBufferFromFile( bufVTX, szFilebase, ".dx90.vtx" ) )
+ return false;
+
+ return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////
+//
+// Studiomdl hooks to call the stripping routines:
+// Main_StripVhv
+// Main_StripModel
+//
+//////////////////////////////////////////////////////////////////////////
+
+int CStudioMDLApp::Main_StripVhv()
+{
+ if ( !g_quiet )
+ {
+ printf( "Stripping vhv data...\n" );
+ }
+
+ if ( !mdllib )
+ {
+ printf( "ERROR: mdllib is not available!\n" );
+ return 1;
+ }
+
+ Q_StripExtension( g_path, g_path, sizeof( g_path ) );
+ char *pExt = g_path + strlen( g_path );
+ *pExt = 0;
+
+ //
+ // ====== Load files
+ //
+
+ // Load up the vhv file
+ CUtlBuffer bufVHV;
+ if ( !LoadBufferFromFile( bufVHV, g_path, ".vhv" ) )
+ return 1;
+
+ // Load up the info.strip file
+ CUtlBuffer bufRemapping;
+ if ( !LoadBufferFromFile( bufRemapping, g_path, ".info.strip", false ) &&
+ !LoadBufferFromFile( bufRemapping, g_path, ".vsi" ) )
+ return 1;
+
+ //
+ // ====== Process file contents
+ //
+
+ bool bResult = false;
+ {
+ SpewActivate( "mdllib", 3 );
+
+ IMdlStripInfo *pMdlStripInfo = NULL;
+
+ if ( mdllib->CreateNewStripInfo( &pMdlStripInfo ) )
+ {
+ pMdlStripInfo->UnSerialize( bufRemapping );
+ bResult = pMdlStripInfo->StripHardwareVertsBuffer( bufVHV );
+ }
+
+ if ( pMdlStripInfo )
+ pMdlStripInfo->DeleteThis();
+ }
+
+ if ( !bResult )
+ {
+ printf( "ERROR: stripping failed!\n" );
+ return 1;
+ }
+
+ //
+ // ====== Save out processed data
+ //
+
+ // Save vhv
+ if ( !WriteBufferToFile( bufVHV, g_path, ".vhv.strip" ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ return 0;
+}
+
+int CStudioMDLApp::Main_MakeVsi()
+{
+ if ( !mdllib )
+ {
+ printf( "ERROR: mdllib is not available!\n" );
+ return 1;
+ }
+
+ Q_StripExtension( g_path, g_path, sizeof( g_path ) );
+ char *pExt = g_path + strlen( g_path );
+ *pExt = 0;
+
+ // Load up the files
+ CUtlBuffer bufMDL;
+ CUtlBuffer bufVVD;
+ CUtlBuffer bufVTX;
+ if ( !Load3ModelBuffers( bufMDL, bufVVD, bufVTX, g_path ) )
+ return 1;
+
+ //
+ // ====== Process file contents
+ //
+
+ CUtlBuffer bufMappingTable;
+ bool bResult = false;
+ {
+ if ( !g_quiet )
+ {
+ printf( "---------------------\n" );
+ printf( "Generating .vsi stripping information...\n" );
+
+ SpewActivate( "mdllib", 3 );
+ }
+
+ IMdlStripInfo *pMdlStripInfo = NULL;
+
+ bResult =
+ mdllib->StripModelBuffers( bufMDL, bufVVD, bufVTX, &pMdlStripInfo ) &&
+ pMdlStripInfo->Serialize( bufMappingTable );
+
+ if ( pMdlStripInfo )
+ pMdlStripInfo->DeleteThis();
+ }
+
+ if ( !bResult )
+ {
+ printf( "ERROR: stripping failed!\n" );
+ return 1;
+ }
+
+ //
+ // ====== Save out processed data
+ //
+
+ // Save remapping data using "P4 edit -> save -> P4 add" approach
+ sprintf( pExt, ".vsi" );
+ CP4AutoEditAddFile _auto_edit_vsi( g_path );
+
+ if ( !WriteFileToDisk( g_path, NULL, bufMappingTable ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+ else if ( !g_quiet )
+ {
+ printf( "Generated .vsi stripping information.\n" );
+ }
+
+ return 0;
+}
+
+int CStudioMDLApp::Main_StripModel()
+{
+ if ( !g_quiet )
+ {
+ printf( "Stripping binary model files...\n" );
+ }
+
+ if ( !mdllib )
+ {
+ printf( "ERROR: mdllib is not available!\n" );
+ return 1;
+ }
+
+ Q_FileBase( g_path, g_path, sizeof( g_path ) );
+ char *pExt = g_path + strlen( g_path );
+ *pExt = 0;
+
+ // Load up the files
+ CUtlBuffer bufMDL;
+ CUtlBuffer bufVVD;
+ CUtlBuffer bufVTX;
+ if ( !Load3ModelBuffers( bufMDL, bufVVD, bufVTX, g_path ) )
+ return 1;
+
+ //
+ // ====== Process file contents
+ //
+
+ CUtlBuffer bufMappingTable;
+ bool bResult = false;
+ {
+ SpewActivate( "mdllib", 3 );
+
+ IMdlStripInfo *pMdlStripInfo = NULL;
+
+ bResult =
+ mdllib->StripModelBuffers( bufMDL, bufVVD, bufVTX, &pMdlStripInfo ) &&
+ pMdlStripInfo->Serialize( bufMappingTable );
+
+ if ( pMdlStripInfo )
+ pMdlStripInfo->DeleteThis();
+ }
+
+ if ( !bResult )
+ {
+ printf( "ERROR: stripping failed!\n" );
+ return 1;
+ }
+
+ //
+ // ====== Save out processed data
+ //
+
+ // Save mdl
+ sprintf( pExt, ".mdl.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufMDL ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ // Save vvd
+ sprintf( pExt, ".vvd.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufVVD ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ // Save vtx
+ sprintf( pExt, ".vtx.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufVTX ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ // Save remapping data
+ sprintf( pExt, ".info.strip" );
+ if ( !WriteFileToDisk( g_path, NULL, bufMappingTable ) )
+ {
+ printf( "ERROR: Failed to save '%s'!\n", g_path );
+ return 1;
+ }
+
+ return 0;
+}