//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "cbase.h" #include "physics_vehicle.h" #include "ivp_material.hxx" #include #include "utlsymbol.h" #include "tier1/strtools.h" #include "vcollide_parse_private.h" #include "ctype.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Purpose: This is the data stored for each material/surface propery list //----------------------------------------------------------------------------- class CSurface : public IVP_Material { public: // IVP_Material virtual IVP_DOUBLE get_friction_factor() { return data.physics.friction; } virtual IVP_DOUBLE get_elasticity() { return data.physics.elasticity; } virtual const char *get_name(); // UNDONE: not implemented here. virtual IVP_DOUBLE get_second_friction_factor() { return 0; } virtual IVP_DOUBLE get_adhesion() { return 0; } virtual IVP_DOUBLE get_damping() { return data.physics.dampening; } // strings CUtlSymbol m_name; unsigned short m_pad; // physics properties surfacedata_t data; }; class CPhysicsSurfaceProps; class CIVPMaterialManager : public IVP_Material_Manager { typedef IVP_Material_Manager BaseClass; public: CIVPMaterialManager( void ); void Init( CPhysicsSurfaceProps *pProps ) { m_props = pProps; } void SetPropMap( int *map, int mapSize ); int RemapIVPMaterialIndex( int ivpMaterialIndex ) const; // IVP_Material_Manager virtual IVP_Material *get_material_by_index(IVP_Real_Object *pObject, const IVP_U_Point *world_position, int index); virtual IVP_DOUBLE get_friction_factor(IVP_Contact_Situation *situation) // returns values >0, value of 1.0f means object stands on a 45 degres hill { // vehicle wheels get no friction with stuff that isn't ground // helps keep control of the car // traction on less than 60 degree slopes. float wheelFriction = 1.0f; if ( ShouldOverrideWheelContactFriction( &wheelFriction, situation->objects[0], situation->objects[1], &situation->surf_normal ) ) { return wheelFriction; } IVP_DOUBLE factor = BaseClass::get_friction_factor( situation ); factor = clamp(factor,0.0,1.0); return factor; } virtual IVP_DOUBLE get_elasticity(IVP_Contact_Situation *situation) // range [0, 1.0f[, the relative speed after a collision compared to the speed before { IVP_DOUBLE flElasticity = BaseClass::get_elasticity( situation ); if ( flElasticity > 1.0f ) { flElasticity = 1.0f; } else if ( flElasticity < 0 ) { flElasticity = 0; } return flElasticity; } private: CPhysicsSurfaceProps *m_props; unsigned short m_propMap[128]; }; //----------------------------------------------------------------------------- // Purpose: This is the main database of materials //----------------------------------------------------------------------------- class CPhysicsSurfaceProps : public IPhysicsSurfacePropsInternal { public: CPhysicsSurfaceProps( void ); ~CPhysicsSurfaceProps( void ); virtual int ParseSurfaceData( const char *pFilename, const char *pTextfile ); virtual int SurfacePropCount( void ) const; virtual int GetSurfaceIndex( const char *pPropertyName ) const; virtual void GetPhysicsProperties( int surfaceDataIndex, float *density, float *thickness, float *friction, float *elasticity ) const; virtual void GetPhysicsParameters( int surfaceDataIndex, surfacephysicsparams_t *pParamsOut ) const; virtual surfacedata_t *GetSurfaceData( int surfaceDataIndex ); virtual const char *GetString( unsigned short stringTableIndex ) const; virtual const char *GetPropName( int surfaceDataIndex ) const; virtual void SetWorldMaterialIndexTable( int *pMapArray, int mapSize ); virtual int RemapIVPMaterialIndex( int ivpMaterialIndex ) const { return m_ivpManager.RemapIVPMaterialIndex( ivpMaterialIndex ); } bool IsReservedMaterialIndex( int materialIndex ) const; virtual const char *GetReservedMaterialName( int materialIndex ) const; int GetReservedFallBack( int materialIndex ) const; int GetReservedSurfaceIndex( const char *pPropertyName ) const; // The database is derived from the IVP material class const IVP_Material *GetIVPMaterial( int materialIndex ) const; IVP_Material *GetIVPMaterial( int materialIndex ); virtual int GetIVPMaterialIndex( const IVP_Material *pIVP ) const; IVP_Material_Manager *GetIVPManager( void ) { return &m_ivpManager; } const char *GetNameString( CUtlSymbol name ) const { return m_strings.String(name); } private: const CSurface *GetInternalSurface( int materialIndex ) const; CSurface *GetInternalSurface( int materialIndex ); void CopyPhysicsProperties( CSurface *pOut, int baseIndex ); bool AddFileToDatabase( const char *pFilename ); private: CUtlSymbolTableMT m_strings; CUtlVector m_props; CUtlVector m_fileList; CIVPMaterialManager m_ivpManager; bool m_init; int m_shadowFallback; }; // Singleton database object CPhysicsSurfaceProps g_SurfaceDatabase; EXPOSE_SINGLE_INTERFACE_GLOBALVAR(CPhysicsSurfaceProps, IPhysicsSurfaceProps, VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, g_SurfaceDatabase); // Global pointer to singleton for VPHYSICS.DLL internal access IPhysicsSurfacePropsInternal *physprops = &g_SurfaceDatabase; const char *CSurface::get_name() { return g_SurfaceDatabase.GetNameString( m_name ); } CPhysicsSurfaceProps::CPhysicsSurfaceProps( void ) : m_fileList(8,8), m_strings( 0, 32, true ) { m_ivpManager.Init( this ); // Force index 0 to be the empty string. Allows game code to check for zero, but // still resolve to a string m_strings.AddString(""); m_init = false; m_shadowFallback = 0; } CPhysicsSurfaceProps::~CPhysicsSurfaceProps( void ) { } int CPhysicsSurfaceProps::SurfacePropCount( void ) const { return m_props.Size(); } // Add the filename to a list to make sure each file is only processed once bool CPhysicsSurfaceProps::AddFileToDatabase( const char *pFilename ) { CUtlSymbol id = m_strings.AddString( pFilename ); for ( int i = 0; i < m_fileList.Size(); i++ ) { if ( m_fileList[i] == id ) return false; } m_fileList.AddToTail( id ); return true; } int CPhysicsSurfaceProps::GetSurfaceIndex( const char *pPropertyName ) const { if ( pPropertyName[0] == '$' ) { int index = GetReservedSurfaceIndex( pPropertyName ); if ( index >= 0 ) return index; } CUtlSymbol id = m_strings.Find( pPropertyName ); if ( id.IsValid() ) { // BUGBUG: Linear search is slow!!! for ( int i = 0; i < m_props.Size(); i++ ) { // NOTE: Just comparing strings by index is pretty fast though if ( m_props[i].m_name == id ) return i; } } return -1; } const char *CPhysicsSurfaceProps::GetPropName( int surfaceDataIndex ) const { const CSurface *pSurface = GetInternalSurface( surfaceDataIndex ); if ( pSurface ) { return GetNameString( pSurface->m_name ); } return NULL; } // UNDONE: move reserved materials into this table, or into a parallel table // that gets hooked out here. CSurface *CPhysicsSurfaceProps::GetInternalSurface( int materialIndex ) { if ( IsReservedMaterialIndex( materialIndex ) ) { materialIndex = GetReservedFallBack( materialIndex ); } if ( materialIndex < 0 || materialIndex > m_props.Size()-1 ) { return NULL; } return &m_props[materialIndex]; } // this function is actually const except for the return type, so this is safe const CSurface *CPhysicsSurfaceProps::GetInternalSurface( int materialIndex ) const { return const_cast(this)->GetInternalSurface(materialIndex); } void CPhysicsSurfaceProps::GetPhysicsProperties( int materialIndex, float *density, float *thickness, float *friction, float *elasticity ) const { const CSurface *pSurface = GetInternalSurface( materialIndex ); if ( !pSurface ) { pSurface = GetInternalSurface( GetSurfaceIndex( "default" ) ); Assert ( pSurface ); } if ( pSurface ) { if ( friction ) { *friction = (float)pSurface->data.physics.friction; } if ( elasticity ) { *elasticity = (float)pSurface->data.physics.elasticity; } if ( density ) { *density = pSurface->data.physics.density; } if ( thickness ) { *thickness = pSurface->data.physics.thickness; } } } void CPhysicsSurfaceProps::GetPhysicsParameters( int surfaceDataIndex, surfacephysicsparams_t *pParamsOut ) const { if ( !pParamsOut ) return; const CSurface *pSurface = GetInternalSurface( surfaceDataIndex ); if ( pSurface ) { *pParamsOut = pSurface->data.physics; } } surfacedata_t *CPhysicsSurfaceProps::GetSurfaceData( int materialIndex ) { CSurface *pSurface = GetInternalSurface( materialIndex ); if (!pSurface) pSurface = GetInternalSurface( 0 ); // Zero is always the "default" property Assert ( pSurface ); return &pSurface->data; } const char *CPhysicsSurfaceProps::GetString( unsigned short stringTableIndex ) const { return m_strings.String( stringTableIndex ); } bool CPhysicsSurfaceProps::IsReservedMaterialIndex( int materialIndex ) const { return (materialIndex > 127) ? true : false; } const char *CPhysicsSurfaceProps::GetReservedMaterialName( int materialIndex ) const { // NOTE: All of these must start with '$' switch( materialIndex ) { case MATERIAL_INDEX_SHADOW: return "$MATERIAL_INDEX_SHADOW"; } return NULL; } int CPhysicsSurfaceProps::GetReservedSurfaceIndex( const char *pPropertyName ) const { if ( !Q_stricmp( pPropertyName, "$MATERIAL_INDEX_SHADOW" ) ) { return MATERIAL_INDEX_SHADOW; } return -1; } const IVP_Material *CPhysicsSurfaceProps::GetIVPMaterial( int materialIndex ) const { return GetInternalSurface(materialIndex); } IVP_Material *CPhysicsSurfaceProps::GetIVPMaterial( int materialIndex ) { return GetInternalSurface(materialIndex); } int CPhysicsSurfaceProps::GetReservedFallBack( int materialIndex ) const { switch( materialIndex ) { case MATERIAL_INDEX_SHADOW: return m_shadowFallback; } return 0; } int CPhysicsSurfaceProps::GetIVPMaterialIndex( const IVP_Material *pIVP ) const { int index = (const CSurface *)pIVP - m_props.Base(); if ( index >= 0 && index < m_props.Size() ) return index; return -1; } void CPhysicsSurfaceProps::CopyPhysicsProperties( CSurface *pOut, int baseIndex ) { const CSurface *pSurface = GetInternalSurface( baseIndex ); if ( pSurface ) { pOut->data = pSurface->data; } } int CPhysicsSurfaceProps::ParseSurfaceData( const char *pFileName, const char *pTextfile ) { if ( !AddFileToDatabase( pFileName ) ) { return 0; } const char *pText = pTextfile; do { char key[MAX_KEYVALUE], value[MAX_KEYVALUE]; pText = ParseKeyvalue( pText, key, value ); if ( !strcmp(value, "{") ) { CSurface prop; memset( &prop.data, 0, sizeof(prop.data) ); prop.m_name = m_strings.AddString( key ); int baseMaterial = GetSurfaceIndex( key ); if ( baseMaterial < 0 ) { baseMaterial = GetSurfaceIndex( "default" ); } CopyPhysicsProperties( &prop, baseMaterial ); do { pText = ParseKeyvalue( pText, key, value ); if ( !strcmpi( key, "}" ) ) { // already in the database, don't add again, override values instead const char *pOverride = m_strings.String(prop.m_name); int propIndex = GetSurfaceIndex( pOverride ); if ( propIndex >= 0 ) { CSurface *pSurface = GetInternalSurface( propIndex ); pSurface->data = prop.data; break; } m_props.AddToTail( prop ); break; } else if ( !strcmpi( key, "base" ) ) { baseMaterial = GetSurfaceIndex( value ); CopyPhysicsProperties( &prop, baseMaterial ); } else if ( !strcmpi( key, "thickness" ) ) { prop.data.physics.thickness = atof(value); } else if ( !strcmpi( key, "density" ) ) { prop.data.physics.density = atof(value); } else if ( !strcmpi( key, "elasticity" ) ) { prop.data.physics.elasticity = atof(value); } else if ( !strcmpi( key, "friction" ) ) { prop.data.physics.friction = atof(value); } else if ( !strcmpi( key, "maxspeedfactor" ) ) { prop.data.game.maxSpeedFactor = atof(value); } else if ( !strcmpi( key, "jumpfactor" ) ) { prop.data.game.jumpFactor = atof(value); } else if ( !strcmpi( key, "climbable" ) ) { prop.data.game.climbable = atoi(value); } // audio parameters else if ( !strcmpi( key, "audioReflectivity" ) ) { prop.data.audio.reflectivity = atof(value); } else if ( !strcmpi( key, "audioHardnessFactor" ) ) { prop.data.audio.hardnessFactor = atof(value); } else if ( !strcmpi( key, "audioHardMinVelocity" ) ) { prop.data.audio.hardVelocityThreshold = atof(value); } else if ( !strcmpi( key, "audioRoughnessFactor" ) ) { prop.data.audio.roughnessFactor = atof(value); } else if ( !strcmpi( key, "scrapeRoughThreshold" ) ) { prop.data.audio.roughThreshold = atof(value); } else if ( !strcmpi( key, "impactHardThreshold" ) ) { prop.data.audio.hardThreshold = atof(value); } // sound names else if ( !strcmpi( key, "stepleft" ) ) { prop.data.sounds.stepleft = m_strings.AddString( value ); } else if ( !strcmpi( key, "stepright" ) ) { prop.data.sounds.stepright = m_strings.AddString( value ); } else if ( !strcmpi( key, "impactsoft" ) ) { prop.data.sounds.impactSoft = m_strings.AddString( value ); } else if ( !strcmpi( key, "impacthard" ) ) { prop.data.sounds.impactHard = m_strings.AddString( value ); } else if ( !strcmpi( key, "scrapesmooth" ) ) { prop.data.sounds.scrapeSmooth = m_strings.AddString( value ); } else if ( !strcmpi( key, "scraperough" ) ) { prop.data.sounds.scrapeRough = m_strings.AddString( value ); } else if ( !strcmpi( key, "bulletimpact" ) ) { prop.data.sounds.bulletImpact = m_strings.AddString( value ); } else if ( !strcmpi( key, "break" ) ) { prop.data.sounds.breakSound = m_strings.AddString( value ); } else if ( !strcmpi( key, "strain" ) ) { prop.data.sounds.strainSound = m_strings.AddString( value ); } else if ( !strcmpi( key, "rolling" ) ) { prop.data.sounds.rolling = m_strings.AddString( value ); } else if ( !strcmpi( key, "gamematerial" ) ) { if ( strlen(value) == 1 && !V_isdigit( value[0]) ) { prop.data.game.material = toupper(value[0]); } else { prop.data.game.material = atoi(value); } } else if ( !strcmpi( key, "dampening" ) ) { prop.data.physics.dampening = atof(value); } else { // force a breakpoint AssertMsg2( 0, "Bad surfaceprop key %s (%s)\n", key, value ); } } while (pText); } } while (pText); if ( !m_init ) { m_init = true; //AddReservedMaterials CSurface prop; int baseMaterial = GetSurfaceIndex( "default" ); memset( &prop.data, 0, sizeof(prop.data) ); prop.m_name = m_strings.AddString( GetReservedMaterialName(MATERIAL_INDEX_SHADOW) ); CopyPhysicsProperties( &prop, baseMaterial ); prop.data.physics.elasticity = 1e-3f; prop.data.physics.friction = 0.8f; m_shadowFallback = m_props.AddToTail( prop ); } return m_props.Size(); } void CPhysicsSurfaceProps::SetWorldMaterialIndexTable( int *pMapArray, int mapSize ) { m_ivpManager.SetPropMap( pMapArray, mapSize ); } CIVPMaterialManager::CIVPMaterialManager( void ) : IVP_Material_Manager( IVP_FALSE ) { // by default every index maps to itself (NULL translation) for ( int i = 0; i < ARRAYSIZE(m_propMap); i++ ) { m_propMap[i] = i; } } int CIVPMaterialManager::RemapIVPMaterialIndex( int ivpMaterialIndex ) const { if ( ivpMaterialIndex > 127 ) return ivpMaterialIndex; return m_propMap[ivpMaterialIndex]; } // remap the incoming (from IVP) index and get the appropriate material // note that ivp will only supply indices between 1 and 127 IVP_Material *CIVPMaterialManager::get_material_by_index(IVP_Real_Object *pObject, const IVP_U_Point *world_position, int index) { IVP_Material *tmp = m_props->GetIVPMaterial( RemapIVPMaterialIndex(index) ); Assert(tmp); if ( tmp ) { return tmp; } else { return m_props->GetIVPMaterial( m_props->GetSurfaceIndex( "default" ) ); } } // Installs a LUT for remapping IVP material indices to physprop indices // A table of the names of the materials in index order is stored with the // compiled bsp file. This is then remapped dynamically without touching the // per-triangle indices on load. If we wanted to support multiple LUTs, it would // be better to preprocess/remap the triangles in the collision models at load time void CIVPMaterialManager::SetPropMap( int *map, int mapSize ) { // ??? just ignore any extra bits if ( mapSize > 128 ) { mapSize = 128; } for ( int i = 0; i < mapSize; i++ ) { m_propMap[i] = (unsigned short)map[i]; } }