diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/studiomdl/studiomdl.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/studiomdl/studiomdl.cpp')
| -rw-r--r-- | utils/studiomdl/studiomdl.cpp | 10448 |
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; +} |