Files
GTASource/game/modelinfo/BaseModelInfo.cpp

2723 lines
85 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
//
//
// Filename: BaseModelInfo.cpp
// Creator: Adam Fowler
// $Author: $
// $Date: $
// $Revision: $
// Description: Base class for all model info classes
//
//
#include "BaseModelInfo.h"
// Rage headers
#include "diag/art_channel.h"
#include "fragment/tune.h"
#include "fragment/typecloth.h"
#include "fragment/typechild.h"
#include "fragment/typegroup.h"
#include "fwanimation/animmanager.h"
#include "fwscene/stores/blendshapestore.h"
#include "fwscene/stores/drawablestore.h"
#include "fwscene/stores/dwdstore.h"
#include "fwscene/stores/expressionsdictionarystore.h"
#include "fwscene/stores/fragmentstore.h"
#include "fwscene/stores/txdstore.h"
#include "math/vecmath.h"
#include "phbound/boundbvh.h"
#include "phbound/boundcomposite.h"
#include "phglass/glassinstance.h"
#include "profile/profiler.h"
#include "rmcore/drawable.h"
#include "streaming/packfilemanager.h"
#include "system/bootmgr.h"
#include "system/spinlock.h"
//fw headers
#include "fwscene/stores/maptypesstore.h"
#if __XENON
#include "grcore/indexbuffer.h"
#include "grcore/vertexbuffer.h"
#include "grmodel/geometry.h"
#endif
// Game headers
#include "audio/emitteraudioentity.h"
#include "audio/northaudioengine.h"
#include "cloth/clotharchetype.h"
#include "control/trafficlights.h"
#include "control/replay/ReplayMovieControllerNew.h"
#include "debug/DebugArchetypeProxy.h"
#include "game/ModelIndices.h"
#include "ModelInfo/ModelInfo.h"
#include "ModelInfo/ModelInfo_Factories.h"
#include "modelinfo/timemodelinfo.h"
#include "modelinfo/vehiclemodelinfo.h"
#include "physics/gtainst.h"
#include "physics/gtamaterial.h"
#include "physics/gtamaterialmanager.h"
#include "physics/Tunable.h"
#include "renderer/gtadrawable.h"
#include "renderer/hierarchyids.h"
#include "renderer/RenderTargetMgr.h"
#include "scene/2deffect.h"
#include "scene/loader/MapData_Buildings.h"
#include "scene/EntityBatch.h"
#include "scene/texLod.h"
#include "shaders/customshadereffectbase.h"
#include "streaming/streaming.h"
#include "tools/sectortools.h"
#include "vehicles/VehicleGadgets.h"
#include "Objects/Door.h"
#include "Objects/DoorTuning.h"
#include "objects/dummyobject.h"
#include "physics/physics.h"
#include "scene/building.h"
#include "scene/animatedbuilding.h"
#include "scene/entities/compentity.h"
//float CBaseModelInfo::ms_lodDistScale = 1.0f;
PARAM(fragtuning, "Load fragment tuning");
#if __ASSERT
PARAM(propasserts, "Assert on prop checks");
#endif
#if __BANK
PARAM(audiotag, "Enable audio model tagging");
#endif
#if __DEV
#endif //__DEV
SCENE_OPTIMISATIONS()
#define MIN_SIZE_FOR_NETWORK_SYNC (0.8f)
#define MIN_MASS_FOR_NETWORK_SYNC (200.0f)
#define MAX_LOD_TXD_SIZE (10*1024*1024)
#if __DEV
bool CBaseModelInfo::ms_bPrintWaterSampleEvents = false;
u32 CBaseModelInfo::ms_nNumWaterSamplesInMemory = 0;
#endif
EXT_PF_PAGE(GTA_Buoyancy);
PF_GROUP(WaterSamples);
PF_LINK(GTA_Buoyancy, WaterSamples);
PF_VALUE_INT(NumWaterSamplesInMemory, WaterSamples);
int CBaseModelInfo::g_wdcascade_TechniqueGroupId = 0;
int CBaseModelInfo::g_wdcascade_shadowtexture_TechniqueGroupId = 0;
const u32 g_defaultAudioModelCollisions = ATSTRINGHASH("MOD_DEFAULT", 0x05f03e8c2);
extern const u32 g_boatCollisionHashName1 = ATSTRINGHASH("apa_mp_apa_yacht_radar_01a", 0x49566DB0);
extern const u32 g_boatCollisionHashName2 = ATSTRINGHASH("apa_mp_apa_yacht_option3_cold", 0x3CBB90AF);
extern const u32 g_boatCollisionHashName3 = ATSTRINGHASH("apa_mp_apa_yacht_option3_colb", 0x44E3A0FB);
extern const u32 g_boatCollisionHashName4 = ATSTRINGHASH("apa_mp_apa_yacht_option3_colc", 0xAEC8F4C8);
extern const u32 g_boatCollisionHashName5 = ATSTRINGHASH("apa_mp_apa_yacht_option3_cola", 0x3A628BF9);
CBaseModelInfo::~CBaseModelInfo(void)
{
if (IsStreamedArchetype())
{
// cleanup the model info streaming module entry - ready for re-use
strStreamingModule* pModelInfoStreamingModule = CModelInfo::GetStreamingModule();
Assert(pModelInfoStreamingModule);
strIndex modelInfoStrIndex = pModelInfoStreamingModule->GetStreamingIndex(GetStreamSlot());
strStreamingInfoManager &infoMgr = strStreamingEngine::GetInfo();
infoMgr.RemoveObject(modelInfoStrIndex);
ASSERT_ONLY(bool bRet = )infoMgr.UnregisterObject(modelInfoStrIndex);
Assert(bRet);
}
Assertf(m_pBuoyancyInfo == NULL && m_p2dEffectPtrs == NULL, "Shutdown() on an archeype wasn't called - we're leaking memory! (b=%p, 2d=%p)",
m_pBuoyancyInfo, m_p2dEffectPtrs);
}
//
// name: Init
// description: Initialise base modelinfo
void CBaseModelInfo::Init()
{
fwArchetype::Init();
m_ptfxAssetSlot = -1;
m_fxEntityFlags = 0;
m_pShaderEffectType = NULL;
m_lodSkelBoneMap = NULL;
m_lodSkelBoneNum = 0;
m_bHasPreRenderEffects = 0;
m_bNeverDummy = false;
m_bLeakObjectsIntentionally = false;
m_bHasDrawableProxyForWaterReflections = false;
}
void CBaseModelInfo::ConvertLoadFlagsAndAttributes(u32 loadFlags, u32 attributes)
{
SetDrawLast(loadFlags & FLAG_DRAW_LAST);
SetIsTypeObject(loadFlags & FLAG_IS_TYPE_OBJECT);
SetDontCastShadows(loadFlags & FLAG_DONT_CAST_SHADOWS);
SetIsShadowProxy(loadFlags & FLAG_SHADOW_ONLY);
SetIsFixed(loadFlags & FLAG_IS_FIXED);
SetIsTree(loadFlags & FLAG_IS_TREE);
SetUsesDoorPhysics(loadFlags & FLAG_DOOR_PHYSICS);
SetIsFixedForNavigation(loadFlags & FLAG_IS_FIXED_FOR_NAVIGATION);
SetNotAvoidedByPeds(loadFlags & FLAG_DONT_AVOID_BY_PEDS);
SetDoesNotProvideAICover(loadFlags & FLAG_DOES_NOT_PROVIDE_AI_COVER);
SetDoesNotProvidePlayerCover(loadFlags & FLAG_DOES_NOT_PROVIDE_PLAYER_COVER);
SetDontWriteZBuffer(loadFlags & FLAG_DONT_WRITE_ZBUFFER);
SetIsClimbableByAI(loadFlags & FLAG_PROP_CLIMBABLE_BY_AI);
SetOverridePhysicsBounds(loadFlags & FLAG_OVERRIDE_PHYSICS_BOUNDS);
SetHasPreReflectedWaterProxy(loadFlags & FLAG_HAS_PRE_REFLECTED_WATER_PROXY);
SetAutoStartAnim( (loadFlags & FLAG_AUTOSTART_ANIM)!=0 );
SetHasAlphaShadow( (loadFlags & FLAG_HAS_ALPHA_SHADOW));
SetNeverDummyFlag((loadFlags & FLAG_HAS_CLOTH) != 0); //environmental cloth only works for real objects rather than dummy objects
//I don't know how it got removed!!!!
SetUseAmbientScale(loadFlags & FLAG_USE_AMBIENT_SCALE);
// GW - Animated objects seem to be causing a crash so I've turned them off until Adam can have a look at it
SetHasAnimation(loadFlags & FLAG_HAS_ANIM);
SetHasUvAnimation(loadFlags & FLAG_HAS_UVANIM);
SetIsHDTxdCapable(!(loadFlags & FLAG_SUPPRESS_HD_TXDS));
SetHasDrawableProxyForWaterReflections( (loadFlags & FLAG_HAS_DRAWABLE_PROXY_FOR_WATER_REFLECTIONS)!=0 );
if(attributes == MODEL_ATTRIBUTE_IS_TREE_DEPRECATED)
SetIsTree(true);
if(loadFlags & FLAG_IS_LADDER_DEPRECATED)
attributes = MODEL_ATTRIBUTE_IS_LADDER;
SetAttributes(attributes);
if(TestAttribute(MODEL_ATTRIBUTE_IS_TRAFFIC_LIGHT))
{
SetHasPreRenderEffects();
}
}
void CBaseModelInfo::InitArchetypeFromDefinition(strLocalIndex mapTypeDefIndex, fwArchetypeDef* definition, bool bHasAssetReferences)
{
fwArchetype::InitArchetypeFromDefinition(mapTypeDefIndex, definition, bHasAssetReferences);
CBaseArchetypeDef& def = *smart_cast<CBaseArchetypeDef*>(definition);
u32 specialAttribute = def.m_specialAttribute;
#if !__FINAL
if(def.m_flags & FLAG_IS_DEBUG)
{
specialAttribute = MODEL_ATTRIBUTE_IS_DEBUG;
}
#endif
if (!((specialAttribute << ATOMIC_L_BITSHIFT) < (u32) BIT(31)))
{
Warningf("special attribute bits 5+ are set (%d) in %s (from %s.ityp)", specialAttribute, GetModelName(), g_MapTypesStore.GetName(mapTypeDefIndex));
specialAttribute &= 31;
}
ConvertLoadFlagsAndAttributes(def.m_flags, specialAttribute);
#if NORTH_CLOTHS && __ASSERT
// Bendables: flag FLAG_IS_TYPE_OBJECT (MODEL_IS_TYPE_OBJECT) MUST be set whenever MODEL_ATTRIBUTE_IS_BENDABLE_PLANT is set:
if(GetAttributes()==MODEL_ATTRIBUTE_IS_BENDABLE_PLANT)
{
Assertf(GetIsTypeObject(),"IDE: Object %s is attributed as bendable plant, but its MODEL_IS_TYPE_OBJECT flag is not set.", def.m_name.c_str());
SetIsTypeObject(TRUE); // quick fix
}
#endif
#if __BANK
if(PARAM_audiotag.Get())
{
// CModelInfo::SetAudioCollisionSettings
const u32 maxAudioSettingsNameLength = 64;
const char *audioSettingsNamePrefix = "MOD_";
char audioSettingsName[maxAudioSettingsNameLength];
strncpy(audioSettingsName, audioSettingsNamePrefix, maxAudioSettingsNameLength);
strncat(audioSettingsName, definition->m_name.GetCStr(), maxAudioSettingsNameLength);
ModelAudioCollisionSettings * moda = audNorthAudioEngine::GetObject<ModelAudioCollisionSettings>(audStringHash(audioSettingsName));
SetAudioCollisionSettings(moda, 0);
}
#endif
}
fwEntity* CBaseModelInfo::CreateEntity()
{
CEntity* entity;
if (GetIsTypeObject())
{
entity = rage_new CDummyObject( ENTITY_OWNEDBY_IPL );
if(!entity)
modelinfoErrorf("CDummyObject pool is full");
}
else
{
if (GetModelType() == MI_TYPE_COMPOSITE){
entity = rage_new CCompEntity( ENTITY_OWNEDBY_IPL );
modelinfoAssertf(entity,"CCompEntity pool is full");
}
else if(GetClipDictionaryIndex() != -1 && GetHasAnimation())
{
entity = rage_new CAnimatedBuilding( ENTITY_OWNEDBY_IPL );
modelinfoAssertf(entity,"CAnimatedBuilding pool is full");
}
else
{
entity = rage_new CBuilding( ENTITY_OWNEDBY_IPL );
modelinfoAssertf(entity,"CBuilding pool is full");
}
}
return entity;
}
fwEntity* CBaseModelInfo::CreateEntityFromDefinition(const fwPropInstanceListDef* UNUSED_PARAM(definition))
{
return rage_new CEntityBatch( ENTITY_OWNEDBY_IPL );
}
fwEntity* CBaseModelInfo::CreateEntityFromDefinition(const fwGrassInstanceListDef* UNUSED_PARAM(definition))
{
return rage_new CGrassBatch( ENTITY_OWNEDBY_IPL );
}
#if USE_DEBUG_ARCHETYPE_PROXIES
PARAM(CheckDynArch,"");
#endif // USE_DEBUG_ARCHETYPE_PROXIES
#if __BANK
void CBaseModelInfo::DebugPostInit() const
{
#if USE_DEBUG_ARCHETYPE_PROXIES
if (PARAM_CheckDynArch.Get())
{
CDebugArchetype::CheckDebugArchetypeProxy(this);
}
#endif // USE_DEBUG_ARCHETYPE_PROXIES
}
#endif // __BANK
//
// name: Init
// description: shutdown base modelinfo
void CBaseModelInfo::Shutdown()
{
fwArchetype::Shutdown();
modelinfoAssertf(GetNumRefs() == 0, "Can't delete %s as it is still referenced", GetModelName());
DeleteMasterFragData();
DeleteMasterDrawableData();
if (m_p2dEffectPtrs){
delete m_p2dEffectPtrs;
m_p2dEffectPtrs = NULL;
}
if(m_pBuoyancyInfo)
{
DeleteWaterSamples();
}
}
void CBaseModelInfo::InitMasterDrawableData(u32 modelidx)
{
SetHasLoaded(true);
rmcDrawable* pDrawable = GetDrawable(); // lookup from internal indices
if (!pDrawable)
SetHasLoaded(false);
modelinfoAssert(pDrawable);
#if __ASSERT
if (GetNumRefs() != 0)
{
// Something is wrong - we're supposed to have a reference count of 0. Let's find out what's going on here.
strStreamingModule* streamingModule = fwArchetypeManager::GetStreamingModule();
fwModelId modelId((strLocalIndex(modelidx)));
strLocalIndex objIndex = modelId.ConvertToStreamingIndex();
strIndex archIndex = streamingModule->GetStreamingIndex(objIndex);
int strNumRefs = streamingModule->GetNumRefs(objIndex);
modelinfoErrorf("Reference count mismatch for %s (strIndex %d) - streaming ref count is %d. Dependencies below:", GetModelName(), archIndex.Get(), strNumRefs);
strStreamingEngine::GetInfo().PrintDependenciesRecurse(archIndex);
modelinfoErrorf("Dependents:");
strStreamingEngine::GetInfo().PrintAllDependents(archIndex);
}
#endif // __ASSERT
modelinfoAssertf(GetNumRefs() == 0, "Trying to initialize drawable data for archetype %s, but the reference count is %d instead of zero. See TTY for details.",
GetModelName(), GetNumRefs());
// get bounds if we don't already have them
rmcLodGroup& pLodGroup = GetDrawable()->GetLodGroup();
if(GetBoundingSphereRadius() == 0.0f)
{
SetBoundingSphere(pLodGroup.GetCullSphere(),pLodGroup.GetCullRadius());
Vector3 bbMin, bbMax;
pLodGroup.GetBoundingBox(bbMin, bbMax);
SetBoundingBox(spdAABB(RCC_VEC3V(bbMin), RCC_VEC3V(bbMax)));
}
SetupCustomShaderEffects();
//compute draw bucket mask
rmcLodGroup &lodGroup = pDrawable->GetLodGroup();
lodGroup.ComputeBucketMask(pDrawable->GetShaderGroup());
artAssertf(GetAssetParentTxdIndex() < 0 || !GetIsLod() || CStreaming::GetObjectPhysicalSize(strLocalIndex(GetAssetParentTxdIndex()), g_TxdStore.GetStreamingModuleId()) < MAX_LOD_TXD_SIZE,
"Lod texture dictionary %s used by %s is too large %dMB",
g_TxdStore.GetName(strLocalIndex(GetAssetParentTxdIndex())),
GetModelName(),
CStreaming::GetObjectPhysicalSize(strLocalIndex(GetAssetParentTxdIndex()), g_TxdStore.GetStreamingModuleId())>>10);
#if NORTH_CLOTHS
if(GetAttributes() == MODEL_ATTRIBUTE_IS_BENDABLE_PLANT)
{
InitClothArchetype();
}
#endif // NORTH_CLOTHS
if( GetCarryScriptedRT() && !gRenderTargetMgr.IsNamedRenderTargetLinked(modelidx, m_scriptId))
{
CRenderTargetMgr::namedRendertarget const* pRT = gRenderTargetMgr.LinkNamedRendertargets(modelidx,m_scriptId);
if(pRT)
{
#if GTA_REPLAY
// Fix for url:bugstar:5835869 - make sure the RT linkup is recorded
CBaseModelInfo *modelinfo = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(modelidx)));
ReplayMovieControllerNew::OnLinkNamedRenderTarget(modelinfo->GetModelNameHash(), m_scriptId, pRT);
#endif // GTA_REPLAY
}
}
const atArray<CLightAttr>* pLightArray = GetLightArray();
if(pLightArray && pLightArray->GetCount() > 0)
{
SetHasPreRenderEffects();
}
#if __BANK || (__WIN32PC && !__FINAL)
SetIsProp(false);
u16 index = 0xffff;
strStreamingModule* module = NULL;
switch (GetDrawableType())
{
case DT_FRAGMENT:
module = &g_FragmentStore;
index = static_cast<u16>(GetFragmentIndex());
break;
case DT_DRAWABLE:
module = &g_DrawableStore;
index = static_cast<u16>(GetDrawableIndex());
break;
case DT_DRAWABLEDICTIONARY:
module = &g_DwdStore;
index = static_cast<u16>(GetDrawDictIndex());
break;
default:
break;
}
if (module && index != 0xffff)
{
strStreamingInfo* info = module->GetStreamingInfo(strLocalIndex(index));
if (info)
{
s32 imageIndex = strPackfileManager::GetImageFileIndexFromHandle(info->GetHandle()).Get();
if (imageIndex != -1)
{
strStreamingFile* file = strPackfileManager::GetImageFile(imageIndex);
if (file && file->m_contentsType == CDataFileMgr::CONTENTS_PROPS)
SetIsProp(true);
}
}
}
#endif // __BANK
}
//
// name: DeleteDrawable
// description: Delete the drawable for this modelinfo
void CBaseModelInfo::DeleteMasterDrawableData()
{
modelinfoAssertf(GetNumRefs() == 0, "Trying to delete drawable data for archetype %s, but the reference count is still %d instead of zero.",
GetModelName(), GetNumRefs());
if (m_pShaderEffectType)
{
m_pShaderEffectType->RestoreModelInfoDrawable(GetDrawable());
m_pShaderEffectType->RemoveRef();
m_pShaderEffectType = NULL;
SetHasLoaded(false);
}
#if NORTH_CLOTHS
if(GetAttributes() == MODEL_ATTRIBUTE_IS_BENDABLE_PLANT)
{
DeleteClothArchetype();
}
#endif //NORTH_CLOTHS
}
//
//
//
//
bool CBaseModelInfo::SetupCustomShaderEffects()
{
rmcDrawable *pDrawable = GetDrawable();
if (pDrawable == NULL){
return(false);
}
#if __ASSERT
extern char *CCustomShaderEffectPed_EntityName; // debug only: defined in CCustomShaderEffectPed.cpp
CCustomShaderEffectPed_EntityName = (char*)this->GetModelName();
extern char* CCustomShaderEffectBase_EntityName;
CCustomShaderEffectBase_EntityName = (char*)this->GetModelName(); // debug only: defined in CCustomShaderEffectBase.cpp
#endif //__ASSERT...
m_pShaderEffectType = CCustomShaderEffectBaseType::SetupMasterForModelInfo(this);
if(m_pShaderEffectType)
{
artAssertf(m_pShaderEffectType->AreShadersValid(pDrawable), "%s: Model has wrong shaders applied", GetModelName());
}
return(TRUE);
}
//
//
//
//
void CBaseModelInfo::DestroyCustomShaderEffects()
{
if (m_pShaderEffectType)
{
m_pShaderEffectType->RestoreModelInfoDrawable(GetDrawable());
m_pShaderEffectType->RemoveRef();
m_pShaderEffectType = NULL;
}
}
void CBaseModelInfo::SetupDynamicCoverCollisionBounds(phArchetypeDamp* pPhysicsArchetype)
{
// This function is meant to be called on frags which have extra bounds intended as cover bounds which must
// then be disabled entirely once the frag has broken due to having a size greater than the remaining pieces.
physicsAssert(pPhysicsArchetype);
physicsAssert(pPhysicsArchetype->GetBound());
if(artVerifyf(pPhysicsArchetype->GetBound()->GetType()==phBound::COMPOSITE,
"An object(%s) without a composite bound shouldn't be tagged with model attribute: 'MODEL_ATTRIBUTE_HAS_DYNAMIC_COVER_BOUND'.",
GetModelName()))
{
// Take a copy of the archetype type / include flags to use in the case where the per-component type / include
// flags have not been set up.
u32 nArchTypeFlags = pPhysicsArchetype->GetTypeFlags();
u32 nArchIncludeFlags = pPhysicsArchetype->GetIncludeFlags();
bool bNeedToInitFlags = false;
phBoundComposite* pCompBound = static_cast<phBoundComposite*>(pPhysicsArchetype->GetBound());
if(!pCompBound->GetTypeAndIncludeFlags())
{
pCompBound->AllocateTypeAndIncludeFlags();
bNeedToInitFlags = true;
}
for(int nComponent = 0; nComponent < pCompBound->GetNumBounds(); ++nComponent)
{
if(phBound* pBound = pCompBound->GetBound(nComponent))
{
for(int nMatIdx = 0; nMatIdx < pBound->GetNumMaterials(); ++nMatIdx)
{
phMaterialMgr::Id nMaterialId = pBound->GetMaterialId(nMatIdx);
phMaterialMgr::Id nUnpackedMaterialId = phMaterialMgrGta::UnpackMtlId(nMaterialId);
if(nUnpackedMaterialId == PGTAMATERIALMGR->g_idPhysDynamicCoverBound)
{
// We have found a dynamic cover bound, set up the type / include flags accordingly.
if(physicsVerifyf(pCompBound->GetTypeAndIncludeFlags(), "No composite bound type/include flags array allocated for %s.", GetModelName()))
{
pCompBound->SetTypeFlags(nComponent, ArchetypeFlags::GTA_MAP_TYPE_COVER);
pCompBound->SetIncludeFlags(nComponent, ArchetypeFlags::GTA_AI_TEST);
u32 nTypeFlags = pPhysicsArchetype->GetTypeFlags();
u32 nIncludeFlags = pPhysicsArchetype->GetIncludeFlags();
pPhysicsArchetype->SetTypeFlags(nTypeFlags|ArchetypeFlags::GTA_MAP_TYPE_COVER);
pPhysicsArchetype->SetIncludeFlags(nIncludeFlags|ArchetypeFlags::GTA_AI_TEST);
#if __ASSERT
const fragPhysicsLOD* physicsLOD = GetFragType()->GetPhysics(0);
const fragTypeChild& child = *physicsLOD->GetChild(nComponent);
Assertf(child.GetDamagedEntity() == NULL, "Trying to set cover bounds on a damageable group '%s' of '%s'. This isn't supported.", physicsLOD->GetGroup(child.GetOwnerGroupPointerIndex())->GetDebugName(),pPhysicsArchetype->GetFilename());
#endif // __ASSERT
}
}
else if(bNeedToInitFlags)
{
pCompBound->SetTypeFlags(nComponent, nArchTypeFlags);
pCompBound->SetIncludeFlags(nComponent, nArchIncludeFlags);
}
}
}
}
}
}
void CBaseModelInfo::ClearDynamicCoverCollisionBounds(phArchetypeDamp* pPhysicsArchetype)
{
// This function is meant to be called on frags which have extra bounds intended as cover bounds which must
// then be disabled entirely once the frag has broken due to having a size greater than the remaining pieces.
physicsAssert(pPhysicsArchetype);
physicsAssert(pPhysicsArchetype->GetBound());
if(artVerifyf(pPhysicsArchetype->GetBound()->GetType()==phBound::COMPOSITE,
"An object(%s) without a composite bound shouldn't be tagged with model attribute: 'MODEL_ATTRIBUTE_HAS_DYNAMIC_COVER_BOUND'.",
GetModelName()))
{
// If we don't find any bounds with GTA_MAP_TYPE_COVER used, we can get rid of this from the archetype flags.
bool bCoverTypeUsed = false;
phBoundComposite* pCompBound = static_cast<phBoundComposite*>(pPhysicsArchetype->GetBound());
physicsAssertf(pCompBound->GetTypeAndIncludeFlags(), "Model %s has no type and include flag array in its phBoundComposite.", GetModelName());
for(int nComponent = 0; nComponent < pCompBound->GetNumBounds(); ++nComponent)
{
phBound* pBound = pCompBound->GetBound(nComponent);
if(pBound)
{
for(int nMatIdx = 0; nMatIdx < pBound->GetNumMaterials(); ++nMatIdx)
{
phMaterialMgr::Id nMaterialId = pBound->GetMaterialId(nMatIdx);
phMaterialMgr::Id nUnpackedMaterialId = phMaterialMgrGta::UnpackMtlId(nMaterialId);
if(nUnpackedMaterialId == PGTAMATERIALMGR->g_idPhysDynamicCoverBound)
{
// We have found a dynamic cover bound, clear the type / include flags accordingly.
pCompBound->SetTypeFlags(nComponent, 0);
pCompBound->SetIncludeFlags(nComponent, 0);
}
}
if(pCompBound->GetTypeFlags(nComponent)&ArchetypeFlags::GTA_MAP_TYPE_COVER)
bCoverTypeUsed = true;
}
}
if(!bCoverTypeUsed)
{
u32 nTypeFlags = pPhysicsArchetype->GetTypeFlags();
pPhysicsArchetype->SetTypeFlags(nTypeFlags & ~ArchetypeFlags::GTA_MAP_TYPE_COVER);
}
}
}
//
// name: SetPhysics
// description: Set models physics bounds
//
#define DEFAULT_OBJECT_DRAG_COEFF (0.05f)
#define DEFAULT_OBJECT_DOOR_ROT_DAMP (0.01f)
//
#define MAX_NUM_POLYS_BEFORE_GETTING_NERFED 512
void CBaseModelInfo::SetPhysics(phArchetypeDamp* pPhysics)
{
fwArchetype::SetPhysics(pPhysics);
if(pPhysics != NULL)
{
if(pPhysics->GetBound())
{
#if __DEV
if(GetBoundingSphereRadius() > 50.0f && !GetIsFixed())
modelinfoWarningf("m_bsRadius %f > 50.0f : %s : Bounding radius too big for dynamic object", GetBoundingSphereRadius(), GetModelName());
#endif
if(pPhysics->GetBound()->GetType()==phBound::GEOMETRY)
{
// if (!Verifyf(nPolys < MAX_NUM_POLYS_BEFORE_GETTING_NERFED, "%s has WAY TOO MANY POLYS %d in its bounds, bound is being nerfed to prevent crashes", GetModelName(), nPolys))
// {
// ((phBoundGeometry*)pPhysics->GetBound())->ClearPolysAndVerts();
// }
#if __ASSERT
int nPolys = ((phBoundGeometry*)pPhysics->GetBound())->GetNumPolygons();
if(nPolys > 128)
{
if(PARAM_propasserts.Get())
physicsAssertf(false, "%s has too many polys %d in its bounds", GetModelName(), nPolys);
else
physicsDisplayf("%s has too many polys %d in its bounds", GetModelName(), nPolys);
}
#endif // __ASSERT
}
else if(pPhysics->GetBound()->GetType()==phBound::COMPOSITE)
{
ASSERT_ONLY(int nTotalPolys = 0;)
phBoundComposite* pBoundComposite = (phBoundComposite*)pPhysics->GetBound();
for(int i=0; i<pBoundComposite->GetNumBounds(); i++)
{
if(pBoundComposite->GetBound(i) && pBoundComposite->GetBound(i)->GetType()==phBound::GEOMETRY)
{
// int nPolys = ((phBoundGeometry*)pBoundComposite->GetBound(i))->GetNumPolygons();
// if (!Verifyf(nPolys < MAX_NUM_POLYS_BEFORE_GETTING_NERFED, "%s has WAY TOO MANY POLYS %d in its bounds, bound is being nerfed to prevent crashes", GetModelName(), nPolys))
// {
// ((phBoundGeometry*)pBoundComposite->GetBound(i))->ClearPolysAndVerts();
// }
#if __ASSERT
// else if(nPolys > 32)
// {
// if(PARAM_propasserts.Get())
// physicsAssertf(false, "%s has too many polys %d in one of its bounds", GetModelName(), nPolys);
// else
// physicsDisplayf("%s has too many polys %d in one of its bounds", GetModelName(), nPolys);
// }
nTotalPolys += ((phBoundGeometry*)pBoundComposite->GetBound(i))->GetNumPolygons();
#endif // __ASSERT
}
#if __ASSERT
// count other types (box, capsule, sphere) as cost of 2 polys (wild approximation)
else
nTotalPolys += 2;
#endif // __ASSERT
}
#if __ASSERT
if(nTotalPolys > 256)
{
if(PARAM_propasserts.Get())
physicsAssertf(false, "%s has too many polys (or equivalent) %d in total in its composite bound", GetModelName(), nTotalPolys);
else
physicsDisplayf("%s has too many polys (or equivalent) %d in total in its composite bound", GetModelName(), nTotalPolys);
}
#endif // __ASSERT
}
}
if (GetIsTypeObject())
{
if(!GetIsFixed())
{
#if __ASSERT
if(pPhysics->GetBound()->GetType()==phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = (phBoundComposite*)pPhysics->GetBound();
for(int i=0; i<pBoundComposite->GetNumBounds(); i++)
modelinfoAssertf(pBoundComposite->GetBound(i)->IsConvex(), "%s:Bound is convex", GetModelName());
}
else
modelinfoAssertf(pPhysics->GetBound()->IsConvex(), "%s:Bound is convex", GetModelName());
#endif
if(GetUsesDoorPhysics())
{
pPhysics->GetBound()->SetCGOffset(Vec3V(V_ZERO));
}
const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry(GetModelNameHash());
ComputeMass(pPhysics, pTuning);
ComputeDamping(pPhysics, pTuning);
}
pPhysics->SetTypeFlags(ArchetypeFlags::GTA_OBJECT_TYPE);
if(GetUsesDoorPhysics())
pPhysics->SetIncludeFlags(ArchetypeFlags::GTA_DOOR_OBJECT_INCLUDE_TYPES);
else
pPhysics->SetIncludeFlags(ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES);
}
else if(GetModelType()==MI_TYPE_BASE || GetModelType()==MI_TYPE_MLO)
{
#if __DEV
// if(GetBoundingSphereRadius() > 100.0f && !GetIsFixed())
// modelinfoWarningf("m_bsRadius %f > 100.0f : %s : Bounding radius too big for dynamic object", GetBoundingSphereRadius(), GetModelName());
#endif
// Set the top-level type / include flags for non-composite bounds here.
if(pPhysics->GetBound()->GetType()!=phBound::COMPOSITE)
{
pPhysics->SetTypeFlags(ArchetypeFlags::GTA_MAP_TYPE_MOVER|ArchetypeFlags::GTA_MAP_TYPE_WEAPON|ArchetypeFlags::GTA_MAP_TYPE_HORSE
|ArchetypeFlags::GTA_MAP_TYPE_COVER|ArchetypeFlags::GTA_MAP_TYPE_VEHICLE);
pPhysics->SetIncludeFlags(ArchetypeFlags::GTA_BUILDING_INCLUDE_TYPES);
}
}
// Any objects coming through with this model attribute set should have its collision bound type set to GTA_FOLIAGE_TYPE.
if(GetAttributes()==MODEL_ATTRIBUTE_NOISY_BUSH || GetAttributes()==MODEL_ATTRIBUTE_NOISY_AND_DEFORMABLE_BUSH)
{
pPhysics->SetTypeFlags(ArchetypeFlags::GTA_FOLIAGE_TYPE);
pPhysics->SetIncludeFlags(ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES & ~ArchetypeFlags::GTA_WHEEL_TEST);
}
// setup material flags on this bound
if(pPhysics->GetBound())
CopyMaterialFlagsToBound(pPhysics->GetBound());
if(GetUsesDoorPhysics())
{
static strStreamingObjectName modelNameNeedsSyncInNetworkGame("v_ilev_j2_door",0x4F97336B);
if(modelNameNeedsSyncInNetworkGame.GetHash() == GetModelNameHash())
{
SetSyncObjInNetworkGame(true);
}
}
if (GetIsTypeObject() && !GetSyncObjInNetworkGame() && !GetUsesDoorPhysics()
&& !TestAttribute(MODEL_ATTRIBUTE_IS_TRAFFIC_LIGHT) && !TestAttribute(MODEL_ATTRIBUTE_IS_STREET_LIGHT))
{
if(GetNum2dEffectType(ET_EXPLOSION) > 0)
SetSyncObjInNetworkGame(true);
if(!GetSyncObjInNetworkGame())
{
if(pPhysics->GetMass() > MIN_MASS_FOR_NETWORK_SYNC)
{
if(pPhysics->GetBound())
{
Vector3 vecBBoxExtents = VEC3V_TO_VECTOR3(pPhysics->GetBound()->GetBoundingBoxMax() - pPhysics->GetBound()->GetBoundingBoxMin());
if(vecBBoxExtents.x > MIN_SIZE_FOR_NETWORK_SYNC || vecBBoxExtents.y > MIN_SIZE_FOR_NETWORK_SYNC || vecBBoxExtents.z > MIN_SIZE_FOR_NETWORK_SYNC)
{
SetSyncObjInNetworkGame(true);
}
}
}
}
}
// Setup extra collision bounds for any props meant for use with the forklift. We give them an extra bound here which
// covers all the small collision bounds which can cause the prop to be unstable when colliding with other objects. This
// extra box will have its collision flags set such that it will not collide with the forklift forks (which have a
// special archetype flag), but will collide with all other objects.
/*if(const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry(GetModelNameHash()))
{
if(pTuning->CanBePickedUpByForklift())
{
CVehicleGadgetForks::CreateNewBoundsForPallet(pPhysics, pTuning->GetForkliftAttachBoxMinZ(), pTuning->GetForkliftAttachBoxMaxZ(),
pTuning->GetForkliftAddTopAndBottomExtraBoxes());
}
}*/
if(GetIsTypeObject() && pPhysics->GetBound()->GetType()==phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = (phBoundComposite*)pPhysics->GetBound();
if(pBoundComposite->GetTypeAndIncludeFlags())
{
if(!GetUsesDoorPhysics())
{
u32 nTypeFlags = pPhysics->GetTypeFlags();
u32 nNewArchetypeTypeFlags = nTypeFlags;
u32 nIncludeFlags = pPhysics->GetIncludeFlags();
for(int i=0; i<pBoundComposite->GetNumBounds(); ++i)
{
u32 nThisComponentTypeFlags = pBoundComposite->GetTypeFlags(i);
nNewArchetypeTypeFlags |= nThisComponentTypeFlags;
// Stair slope is incompatible with object type.
if(nThisComponentTypeFlags==ArchetypeFlags::GTA_STAIR_SLOPE_TYPE)
{
pBoundComposite->SetTypeFlags(i, nThisComponentTypeFlags);
pBoundComposite->SetIncludeFlags(i, nIncludeFlags);
}
else if(nThisComponentTypeFlags == (ArchetypeFlags::GTA_MAP_TYPE_COVER|ArchetypeFlags::GTA_OBJECT_TYPE)
|| nThisComponentTypeFlags == ArchetypeFlags::GTA_MAP_TYPE_COVER)
{
pBoundComposite->SetTypeFlags(i, nTypeFlags|nThisComponentTypeFlags);
pBoundComposite->SetIncludeFlags(i, nIncludeFlags & ~(ArchetypeFlags::GTA_PROJECTILE_TYPE|ArchetypeFlags::GTA_WEAPON_TEST));
}
else
{
pBoundComposite->SetTypeFlags(i, nTypeFlags|nThisComponentTypeFlags);
pBoundComposite->SetIncludeFlags(i, nIncludeFlags);
}
}
// Set the top-level type / include flags for non-composite objects here.
pPhysics->SetTypeFlags(nNewArchetypeTypeFlags);
}
else
{
// Clear out any per-component type/include flag information for Doors.
u32 nTypeFlags = pPhysics->GetTypeFlags();
u32 nIncludeFlags = pPhysics->GetIncludeFlags();
for(int i=0; i<pBoundComposite->GetNumBounds(); ++i)
{
pBoundComposite->SetTypeFlags(i, nTypeFlags);
pBoundComposite->SetIncludeFlags(i, nIncludeFlags);
}
}
}
}
}
else // If physArch set to null
{
//Displayf("CBaseModelInfo::SetPhysics Null physics for %s", GetModelName());
DeleteWaterSamples();
}
}
void CBaseModelInfo::CreateBuoyancyIfNeeded()
{
if(GetPhysics() != NULL)
{
if((GetIsTypeObject() && !GetIsFixed())
|| GetModelType() == MI_TYPE_PED
|| GetModelType() == MI_TYPE_VEHICLE)
{
Assert(m_pBuoyancyInfo == NULL);
InitWaterSamples();
}
}
}
void CBaseModelInfo::CopyMaterialFlagsToSingleBound(phBound* pBound)
{
Assert(pBound->GetType() != phBound::COMPOSITE);
for(int nMat = 0; nMat < pBound->GetNumMaterials(); nMat++)
{
phMaterialMgr::Id nMaterialId = pBound->GetMaterialId(nMat);
Assertf(PGTAMATERIALMGR->GetPolyFlagVehicleWheel(nMaterialId) == false, "POLYFLAG_VEHICLE_WHEEL shouldn't be set on resources. Material Id(0x%" I64FMT "X) Model Name(%s)", nMaterialId, GetModelName());
if(PGTAMATERIALMGR->GetMtlFlagSeeThrough(nMaterialId))
PGTAMATERIALMGR->PackPolyFlag(nMaterialId, POLYFLAG_SEE_THROUGH);
if(PGTAMATERIALMGR->GetMtlFlagShootThroughFx(nMaterialId))
{
// If this material has shoot through FX, remove POLYFLAG_SHOOT_THROUGH and add POLYFLAG_SHOOT_THROUGH_FX
nMaterialId &= ~PGTAMATERIALMGR->GetPackedPolyFlagValue(POLYFLAG_SHOOT_THROUGH);
PGTAMATERIALMGR->PackPolyFlag(nMaterialId,POLYFLAG_SHOOT_THROUGH_FX);
}
else if(PGTAMATERIALMGR->GetMtlFlagShootThrough(nMaterialId))
{
PGTAMATERIALMGR->PackPolyFlag(nMaterialId, POLYFLAG_SHOOT_THROUGH);
}
Assertf(!(PGTAMATERIALMGR->GetPolyFlagShootThrough(nMaterialId) && PGTAMATERIALMGR->GetPolyFlagShootThroughFx(nMaterialId)), "POLYFLAG_SHOOT_THROUGH and POLYFLAG_SHOOT_THROUGH_FX should be exclusive. Material Id(0x%" I64FMT "X) Model Name(%s)", nMaterialId, GetModelName());
pBound->SetMaterial(nMaterialId, nMat);
}
}
void CBaseModelInfo::CopyMaterialFlagsToBound(phBound* pBound)
{
if(pBound->GetType()==phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = (phBoundComposite*)pBound;
for(int nComponent = 0; nComponent < pBoundComposite->GetNumBounds(); nComponent++)
{
phBound* pBoundPart = pBoundComposite->GetBound(nComponent);
if(pBoundPart)
{
CopyMaterialFlagsToSingleBound(pBoundPart);
}
}
}
else
{
CopyMaterialFlagsToSingleBound(pBound);
}
}
void CBaseModelInfo::ComputeMass(phArchetypeDamp* pPhysicsArchetype, const CTunableObjectEntry* pTuning) const
{
float fDensity = 1.0f;
int iNumMats = pPhysicsArchetype->GetBound()->GetNumMaterials();
modelinfoAssertf(iNumMats, "Bound has no materials : \"%s\"", GetModelName());
if(iNumMats > 0)
{
// Don't want the material of the first component bound dominating the overall mass calculation. Instead we
// average the density of all materials in the composite, weighting each one by the volume of its bound.
if(pPhysicsArchetype->GetBound()->GetType()==phBound::COMPOSITE)
{
float fTotalVolume = 0.0f;
phBoundComposite* pCompBound = static_cast<phBoundComposite*>(pPhysicsArchetype->GetBound());
fDensity = 0.0f;
for(int nComponent = 0; nComponent < pCompBound->GetNumBounds(); ++nComponent)
{
phBound* pBound = pCompBound->GetBound(nComponent);
float fVolume = pBound->GetVolume();
// Take the density from the first material on the bound.
phMaterialGta* pMat = (phMaterialGta*)&(pBound->GetMaterial(0));
fDensity += pMat->GetDensity()*fVolume;
fTotalVolume += fVolume;
}
// Compute the weighted average density.
fDensity /= fTotalVolume;
}
else
{
phMaterialGta *pMat = (phMaterialGta *)&(pPhysicsArchetype->GetBound()->GetMaterial(0));
fDensity = pMat->GetDensity();
}
}
float fVolume = pPhysicsArchetype->GetBound()->GetComputeVolume();
modelinfoAssertf(fVolume > 0.0f || GetIsFixed(), "%s:Negative volume calculated for dynamic object", GetModelName());
fVolume = fabs(fVolume);
float fMass = fVolume * fDensity;
if(GetUsesDoorPhysics())
{
bool bBigDoor = pPhysicsArchetype->GetBound()->GetBoundingBoxMax().GetXf() - pPhysicsArchetype->GetBound()->GetBoundingBoxMin().GetXf() > 2.0f;
if(bBigDoor)
fMass *= 0.3f;
else
fMass *= 0.1f;
//See if there is a door mass multiplier
u8 type = CDoor::DetermineDoorType(this, GetBoundingBoxMin(), GetBoundingBoxMax());
const CDoorTuning& tuneData = CDoorTuningManager::GetInstance().GetTuningForDoorModel(GetModelNameHash(), type);
fMass *= tuneData.m_MassMultiplier;
pPhysicsArchetype->GetBound()->SetCGOffset(Vec3V(V_ZERO));
if(bBigDoor)
fMass = rage::Clamp(fMass, 50.0f, 100.0f);
else
fMass = rage::Clamp(fMass, 35.0f, 70.0f);
}
// Check for a tuning override
if(pTuning)
{
if(pTuning->GetMass() >= 0)
{
fMass = pTuning->GetMass();
}
}
// Set the mass and angular inertia of the archetype
pPhysicsArchetype->SetMassOnly(fMass);
pPhysicsArchetype->SetAngInertia(pPhysicsArchetype->GetBound()->GetComputeAngularInertia(fMass));
// Check if we should offset the centre of mass for this object.
if(pTuning)
{
if(pTuning->GetCentreOfMassOffset().IsNonZero())
{
pPhysicsArchetype->GetBound()->SetCGOffset(RCC_VEC3V(pTuning->GetCentreOfMassOffset()));
}
}
}
bool CBaseModelInfo::ComputeDamping(phArchetypeDamp* pPhysicsArchetype, const CTunableObjectEntry* pTuning) const
{
Vector3 vecDragCoef;
vecDragCoef.Set(DEFAULT_OBJECT_DRAG_COEFF);
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V2, vecDragCoef);
if(GetUsesDoorPhysics())
{
vecDragCoef.Set(DEFAULT_OBJECT_DOOR_ROT_DAMP);
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V, vecDragCoef);
}
// Check for a tuning override
bool bAppliedTuning = false;
if(pTuning)
{
Vector3 vZero(0,0,0);
if(pTuning->GetLinearSpeedDamping().IsGreaterOrEqualThan(vZero))
{
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::LINEAR_C,pTuning->GetLinearSpeedDamping());
bAppliedTuning = true;
}
if(pTuning->GetLinearVelocityDamping().IsGreaterOrEqualThan(vZero))
{
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V,pTuning->GetLinearVelocityDamping());
bAppliedTuning = true;
}
if(pTuning->GetLinearVelocitySquaredDamping().IsGreaterOrEqualThan(vZero))
{
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V2,pTuning->GetLinearVelocitySquaredDamping());
bAppliedTuning = true;
}
if(pTuning->GetAngularSpeedDamping().IsGreaterOrEqualThan(vZero))
{
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_C,pTuning->GetAngularSpeedDamping());
bAppliedTuning = true;
}
if(pTuning->GetAngularVelocityDamping().IsGreaterOrEqualThan(vZero))
{
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V,pTuning->GetAngularVelocityDamping());
bAppliedTuning = true;
}
if(pTuning->GetAngularVelocitySquaredDamping().IsGreaterOrEqualThan(vZero))
{
pPhysicsArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V2,pTuning->GetAngularVelocitySquaredDamping());
bAppliedTuning = true;
}
}
return bAppliedTuning;
}
//
// name: CBaseModelInfo::SetDrawableDependciesAsDirty
// description: Tag the entry in the drawable or fragment store as dirty (which ever one is used by this model info)
void CBaseModelInfo::SetDrawableDependenciesAsDirty(bool markTexDict)
{
switch (GetDrawableType()) {
case DT_FRAGMENT:
modelinfoAssert(GetFragmentIndex() != INVALID_FRAG_IDX);
CStreaming::SetDoNotDefrag(strLocalIndex(GetFragmentIndex()), g_FragmentStore.GetStreamingModuleId());
break;
case DT_DRAWABLE:
modelinfoAssert(GetDrawableIndex() != INVALID_DRAWABLE_IDX);
CStreaming::SetDoNotDefrag(strLocalIndex(GetDrawableIndex()), g_DrawableStore.GetStreamingModuleId());
break;
default:
modelinfoAssertf(false,"Can't set dependency as dirty for this model info : %s", GetModelName());
}
u32 txdIndex = GetAssetParentTxdIndex();
if ( markTexDict && txdIndex != -1)
{
CStreaming::SetDoNotDefrag(strLocalIndex(txdIndex), g_TxdStore.GetStreamingModuleId());
}
}
void CBaseModelInfo::BumpDrawableRefCount()
{
switch (GetDrawableType()) {
case DT_FRAGMENT:
modelinfoAssert(GetFragmentIndex() != INVALID_FRAG_IDX);
g_FragmentStore.AddRef(strLocalIndex(GetFragmentIndex()), REF_OTHER);
break;
case DT_DRAWABLE:
modelinfoAssert(GetDrawableIndex() != INVALID_DRAWABLE_IDX);
g_DrawableStore.AddRef(strLocalIndex(GetDrawableIndex()), REF_OTHER);
break;
default:
modelinfoAssertf(false,"Can't Get dependencies for this model info : %s", GetModelName());
}
}
int CBaseModelInfo::GetDrawableRefCount()
{
switch (GetDrawableType()) {
case DT_FRAGMENT:
modelinfoAssert(GetFragmentIndex() != INVALID_FRAG_IDX);
return g_FragmentStore.GetNumRefs(strLocalIndex(GetFragmentIndex()));
case DT_DRAWABLE:
modelinfoAssert(GetDrawableIndex() != INVALID_DRAWABLE_IDX);
return g_DrawableStore.GetNumRefs(strLocalIndex(GetDrawableIndex()));
default:
modelinfoAssertf(false,"Can't Get ref count for this model info : %s", GetModelName());
}
return -1;
}
void CBaseModelInfo::ReleaseDrawable()
{
switch (GetDrawableType()) {
case DT_FRAGMENT:
modelinfoAssert(GetFragmentIndex() != INVALID_FRAG_IDX);
g_FragmentStore.RemoveRef(strLocalIndex(GetFragmentIndex()), REF_OTHER);
break;
case DT_DRAWABLE:
modelinfoAssert(GetDrawableIndex() != INVALID_DRAWABLE_IDX);
g_DrawableStore.RemoveRef(strLocalIndex(GetDrawableIndex()), REF_OTHER);
break;
default:
modelinfoAssertf(false,"Can't RemoveRef for this model info : %s", GetModelName());
}
}
//
// name: CBaseModelInfo::SetFragType
// description:
//void CBaseModelInfo::SetFragType(gtaFragType *UNUSED_PARAM(pType))
void CBaseModelInfo::InitMasterFragData(u32 modelIdx)
{
SetHasLoaded(true);
gtaFragType* pFragType = static_cast<gtaFragType *>(GetFragType()); // lookup from store
if (!pFragType)
SetHasLoaded(false);
modelinfoAssert(pFragType);
SetupCustomShaderEffects();
// fragments shouldn't be fixed, otherwise they can't fragment, so what's the point of them being fragments
if(GetIsFixed())
SetIsFixed(false);
Assertf(IsFiniteAll(VECTOR3_TO_VEC3V(pFragType->GetPhysics(0)->GetUnbrokenCGOffset())) &&
IsFiniteAll(VECTOR3_TO_VEC3V(pFragType->GetPhysics(0)->GetOriginalRootCGOffset())) &&
IsFiniteAll(VECTOR3_TO_VEC3V(pFragType->GetPhysics(0)->GetRootCGOffset())), "Fragment '%s' loaded in with non-finite CG.", pFragType->GetTuneName());
phArchetypeDamp *pArchetype = pFragType->GetPhysics(0)->GetArchetype();
if(pArchetype)
{
if(pArchetype->GetBound())
{
UpdateBoundingVolumes(*pArchetype->GetBound());
}
if (GetIsTypeObject() || GetModelType() == MI_TYPE_VEHICLE)
{
if(pFragType->GetNumEnvCloths()>0)
{
Assert( pFragType->GetTypeEnvCloth(0) );
const clothInstanceTuning* pClothTuning = pFragType->GetTypeEnvCloth(0)->m_Cloth.GetTuning();
const bool activateOnHit = pClothTuning ? pClothTuning->GetFlag( clothInstanceTuning::CLOTH_TUNE_ACTIVATE_ON_HIT ): false;
if( activateOnHit )
{
pArchetype->SetTypeFlags(ArchetypeFlags::GTA_OBJECT_TYPE);
pArchetype->SetIncludeFlags(ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES);
}
else
{
pArchetype->SetTypeFlags(ArchetypeFlags::GTA_ENVCLOTH_OBJECT_TYPE);
pArchetype->SetIncludeFlags(ArchetypeFlags::GTA_ENVCLOTH_OBJECT_INCLUDE_TYPES);
}
}
else if(pFragType->GetNumGlassPaneModelInfos()>0)
{
pArchetype->SetTypeFlags(ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_GLASS_TYPE | ArchetypeFlags::GTA_UNSMASHED_TYPE);
pArchetype->SetIncludeFlags(ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES);
phBound* pBound = pArchetype->GetBound();
Assert(pBound);
if(phBound::IsTypeComposite(pBound->GetType()))
{
// Only glass parts of composites should be marked as such
phBoundComposite* pBoundComposite = static_cast<phBoundComposite*>(pBound);
bool createdTypeIncludeFlags = false;
if(pBoundComposite->GetTypeAndIncludeFlags() == NULL)
{
createdTypeIncludeFlags = true;
pBoundComposite->AllocateTypeAndIncludeFlags();
}
const fragPhysicsLOD* physicsLOD = pFragType->GetPhysics(0);
for(int componentIndex = 0; componentIndex < pBoundComposite->GetNumBounds(); ++componentIndex)
{
// If the composite already had type include flags just add these type flags on, otherwise start from 0. We don't want to
// stomp mover/weapon flags.
u32 oldTypeFlags = createdTypeIncludeFlags ? 0 : pBoundComposite->GetTypeFlags(componentIndex);
if(physicsLOD->GetGroup(physicsLOD->GetChild(componentIndex)->GetOwnerGroupPointerIndex())->GetMadeOfGlass())
{
pBoundComposite->SetTypeFlags(componentIndex, oldTypeFlags | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_GLASS_TYPE | ArchetypeFlags::GTA_UNSMASHED_TYPE);
}
else
{
pBoundComposite->SetTypeFlags(componentIndex, oldTypeFlags | ArchetypeFlags::GTA_OBJECT_TYPE);
}
}
}
}
else
{
u32 nTypeFlags = ArchetypeFlags::GTA_OBJECT_TYPE;
//Horrible hack but they want these to hit map type mover
if(( GetModelNameHash() == MI_PROP_STUNT_TUBE_01.GetName().GetHash() ||
GetModelNameHash() == MI_PROP_STUNT_TUBE_02.GetName().GetHash() ||
GetModelNameHash() == MI_PROP_STUNT_TUBE_03.GetName().GetHash() ||
GetModelNameHash() == MI_PROP_STUNT_TUBE_04.GetName().GetHash() ||
GetModelNameHash() == MI_PROP_STUNT_TUBE_05.GetName().GetHash() ) )
{
nTypeFlags = ArchetypeFlags::GTA_MAP_TYPE_MOVER|ArchetypeFlags::GTA_OBJECT_TYPE;
}
pArchetype->SetTypeFlags(nTypeFlags);
pArchetype->SetIncludeFlags(ArchetypeFlags::GTA_OBJECT_INCLUDE_TYPES);
}
// Do this last so that doors with glass panels behave correctly in the world.
if(GetUsesDoorPhysics())
{
u32 nDoorTypeFlags = ArchetypeFlags::GTA_OBJECT_TYPE;
u32 nDoorIncludeFlags = ArchetypeFlags::GTA_DOOR_OBJECT_INCLUDE_TYPES;
pArchetype->AddTypeFlags(nDoorTypeFlags);
pArchetype->SetIncludeFlags(nDoorIncludeFlags);
// We don't want doors getting map type flags applied to child parts.
phBound* pBound = pArchetype->GetBound();
Assert(pBound);
if(pBound->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(pBound);
if(pBoundComp->GetTypeAndIncludeFlags())
{
for(int i = 0; i < pBoundComp->GetNumBounds(); ++i)
{
// Set the type and include flags for this component.
pBoundComp->SetTypeFlags(i, nDoorTypeFlags);
pBoundComp->SetIncludeFlags(i, nDoorIncludeFlags);
}
}
}
}
Vector3 vecDragCoef;
vecDragCoef.Set(DEFAULT_OBJECT_DRAG_COEFF);
pArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V2, vecDragCoef);
}
else if(GetModelType()==MI_TYPE_BASE)
{
pArchetype->SetTypeFlags(ArchetypeFlags::GTA_MAP_TYPE_MOVER|ArchetypeFlags::GTA_MAP_TYPE_WEAPON);
pArchetype->SetIncludeFlags(ArchetypeFlags::GTA_BUILDING_INCLUDE_TYPES);
}
// setup material flags on this bound
if(pArchetype->GetBound())
CopyMaterialFlagsToBound(pArchetype->GetBound());
if( NetworkInterface::IsGameInProgress() && ( GetModelNameHash() == g_boatCollisionHashName5
|| GetModelNameHash() == g_boatCollisionHashName1
|| GetModelNameHash() == g_boatCollisionHashName2
|| GetModelNameHash() == g_boatCollisionHashName3
|| GetModelNameHash() == g_boatCollisionHashName4
)
)
{
u32 typeFlags = ArchetypeFlags::GTA_MAP_TYPE_COVER | ArchetypeFlags::GTA_OBJECT_TYPE;
pArchetype->SetTypeFlags( typeFlags );
u32 includeFlags = ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE | ArchetypeFlags::GTA_VEHICLE_BVH_TYPE | ArchetypeFlags::GTA_PED_TYPE
| ArchetypeFlags::GTA_RAGDOLL_TYPE| ArchetypeFlags::GTA_HORSE_TYPE| ArchetypeFlags::GTA_HORSE_RAGDOLL_TYPE;
pArchetype->SetIncludeFlags( includeFlags );
}
}
#if __BANK
if(PARAM_fragtuning.Get())
{
// Store a pointer to the name, which is owned by a ConstString that is the key to the type map
gtaFragType* tuningType = NULL;
//m_pFragType->SetTuneName(GetModelName());
const char* tuneName = pFragType->GetTuneName();
if (const char* baseName = strrchr(tuneName, '/'))
{
tuneName = baseName + 1;
}
if (fragTune::IsInstantiated() && FRAGTUNE->GetForcedTuningPath())
{
ASSET.PushFolder(FRAGTUNE->GetForcedTuningPath());
}
else
{
ASSET.PushFolder("$/tune/types/fragments");
}
fiStream* tuningStream = ASSET.Open(tuneName, "tune", true, true);
ASSET.PopFolder();
if(tuningStream)
{
fiAsciiTokenizer tuningTok;
tuningTok.Init(tuneName, tuningStream);
// sysMemStartTemp();
tuningType = rage_new gtaFragType;
tuningType->LoadASCII(tuningTok, tuneName, false);
// sysMemEndTemp();
pFragType->ApplyTuningData(tuningType, NULL);
tuningStream->Close();
// sysMemStartTemp();
delete tuningType;
// sysMemEndTemp();
}
}
#endif
const atArray<CLightAttr>* pLightArray = GetLightArray();
if(pLightArray && pLightArray->GetCount() > 0)
{
SetHasPreRenderEffects();
}
// for the moment, fragments with a single child that are supposed to dissapear when dead aren't working
// they only work if they get put into the fragment cache (which sometimes happens because of the projected texture stuff)
// lets just force it to happen for now, so its consistant at least!
if(pFragType->GetPhysics(0)->GetNumChildGroups()==1 && pFragType->GetPhysics(0)->GetAllGroups()[0]->GetDisappearsWhenDead())
{
pFragType->SetNeedsCacheEntryToActivate();
}
fragDrawable* pFragDrawable=pFragType->GetCommonDrawable();
rmcLodGroup &lodGroup = pFragDrawable->GetLodGroup();
lodGroup.ComputeBucketMask(pFragDrawable->GetShaderGroup());
#if __ASSERT
const float MASS_LOWER_BOUND = 0.01f;
modelinfoAssertf(pFragType->GetPhysics(0)->GetArchetype()->GetMass() > MASS_LOWER_BOUND, "%s: Fragment whole has very small mass (%g)", GetModelName(), pFragType->GetPhysics(0)->GetArchetype()->GetMass());
for(int i=0; i<pFragType->GetPhysics(0)->GetNumChildren(); i++)
{
modelinfoAssertf(pFragType->GetPhysics(0)->GetAllChildren()[i]->GetUndamagedMass() > MASS_LOWER_BOUND, "%s: Fragment child %d has very small mass (%g)", GetModelName(), i, pFragType->GetPhysics(0)->GetAllChildren()[i]->GetUndamagedMass());
modelinfoAssertf(pFragType->GetPhysics(0)->GetAllChildren()[i]->GetDamagedEntity() == NULL || pFragType->GetPhysics(0)->GetAllChildren()[i]->GetDamagedMass() > MASS_LOWER_BOUND, "%s: Fragment damaged child %d has very small mass (%g)", GetModelName(), i, pFragType->GetPhysics(0)->GetAllChildren()[i]->GetDamagedMass());
}
#endif // __ASSERT
// don't do the same checks for vehicles and peds
if(GetModelType()==MI_TYPE_BASE && pArchetype && pArchetype->GetBound()->GetType()==phBound::COMPOSITE && !GetIsFixed())
{
ASSERT_ONLY(int nTotalPolys = 0;)
phBoundComposite* pBoundComposite = (phBoundComposite*)pArchetype->GetBound();
for(int i=0; i<pBoundComposite->GetNumBounds(); i++)
{
if(pBoundComposite->GetBound(i) && pBoundComposite->GetBound(i)->GetType()==phBound::GEOMETRY)
{
int nPolys = ((phBoundGeometry*)pBoundComposite->GetBound(i))->GetNumPolygons();
if (!Verifyf(nPolys < MAX_NUM_POLYS_BEFORE_GETTING_NERFED, "%s has WAY TOO MANY POLYS %d in its bounds, bound is being nerfed to prevent crashes", GetModelName(), nPolys))
{
((phBoundGeometry*)pBoundComposite->GetBound(i))->ClearPolysAndVerts();
}
#if __ASSERT
else if(((phBoundGeometry*)pBoundComposite->GetBound(i))->GetNumPolygons() > 32)
{
if(PARAM_propasserts.Get())
physicsAssertf(false, "%s has too many polys %d in one of its bounds", GetModelName(), nPolys);
else
physicsDisplayf("%s has too many polys %d in one of its bounds", GetModelName(), nPolys);
}
nTotalPolys += ((phBoundGeometry*)pBoundComposite->GetBound(i))->GetNumPolygons();
#endif // __ASSERT
}
// count other types (box, capsule, sphere) as cost of 2 polys (wild approximation)
#if __ASSERT
else
nTotalPolys += 2;
#endif // __ASSERT
}
#if __ASSERT
if(nTotalPolys > 128)
{
if(PARAM_propasserts.Get())
physicsAssertf(false, "%s has too many polys (or equivalent) %d in total in its composite bound", GetModelName(), nTotalPolys);
else
physicsDisplayf("%s has too many polys (or equivalent) %d in total in its composite bound", GetModelName(), nTotalPolys);
}
#endif // __ASSERT
}
#if SECTOR_TOOLS_EXPORT
// If we have enabled our Sector Tools on the command line then we
// prevent fragment objects breaking apart. We also lock any hinges
// to ensure props etc. don't flap around.
if ( CSectorTools::GetEnabled() )
{
for ( int i=0; i<GetFragType()->GetPhysics(0)->GetNumChildGroups(); ++i )
{
GetFragType()->GetPhysics(0)->GetAllGroups()[i]->SetStrength( -1.0f );
GetFragType()->GetPhysics(0)->GetAllGroups()[i]->SetLatchStrength( -1.0f );
}
}
#endif // SECTOR_TOOLS_EXPORT
if (GetIsTypeObject() && !GetSyncObjInNetworkGame() && !GetUsesDoorPhysics()
&& !TestAttribute(MODEL_ATTRIBUTE_IS_TRAFFIC_LIGHT) && !TestAttribute(MODEL_ATTRIBUTE_IS_STREET_LIGHT))
{
if(GetNum2dEffectType(ET_EXPLOSION) > 0)
SetSyncObjInNetworkGame(true);
if(!GetSyncObjInNetworkGame())
{
float fGroupMassMax = 0.0f;
for(int i=0; i<GetFragType()->GetPhysics(0)->GetNumChildGroups(); i++)
{
if(GetFragType()->GetPhysics(0)->GetAllGroups()[i]->GetTotalUndamagedMass() > fGroupMassMax)
fGroupMassMax = GetFragType()->GetPhysics(0)->GetAllGroups()[i]->GetTotalUndamagedMass();
}
if(fGroupMassMax > MIN_MASS_FOR_NETWORK_SYNC)
{
phBoundComposite* pBoundComp = static_cast<phBoundComposite*>(GetFragType()->GetPhysics(0)->GetArchetype()->GetBound());
for(int i=0; i<pBoundComp->GetNumBounds(); i++)
{
if(pBoundComp->GetBound(i))
{
Vector3 vecBBoxExtents = VEC3V_TO_VECTOR3(pBoundComp->GetBound(i)->GetBoundingBoxMax() - pBoundComp->GetBound(i)->GetBoundingBoxMin());
if(vecBBoxExtents.x > MIN_SIZE_FOR_NETWORK_SYNC || vecBBoxExtents.x > MIN_SIZE_FOR_NETWORK_SYNC || vecBBoxExtents.x > MIN_SIZE_FOR_NETWORK_SYNC)
{
SetSyncObjInNetworkGame(true);
break;
}
}
}
}
}
}
if(TestAttribute(MODEL_ATTRIBUTE_IS_TRAFFIC_LIGHT))
{
CTrafficLights::SetupModelInfo(this);
}
else if(TestAttribute(MODEL_ATTRIBUTE_IS_RAIL_CROSSING_LIGHT))
{
CRailwayCrossingLights::SetupModelInfo(this);
}
else if(TestAttribute(MODEL_ATTRIBUTE_IS_RAIL_CROSSING_DOOR))
{
CRailwayBarrierLights::SetupModelInfo(this);
}
// If this object has dynamic cover bounds, let's search for them and set their collision appropriately.
if(TestAttribute(MODEL_ATTRIBUTE_HAS_DYNAMIC_COVER_BOUND) && pArchetype && pArchetype->GetBound())
{
SetupDynamicCoverCollisionBounds(pArchetype);
}
const bool bModelNeedsBuoyancy = (GetIsTypeObject() && !GetIsFixed())
|| GetModelType() == MI_TYPE_VEHICLE
|| GetModelType() == MI_TYPE_PED
|| GetModelType() == MI_TYPE_WEAPON;
Assert(GetModelType()!=MI_TYPE_NONE);
Assertf(!m_pBuoyancyInfo || m_pBuoyancyInfo->m_WaterSamples, "m_pBuoyancyInfo=0x%p, m_WaterSamples=0x%p", m_pBuoyancyInfo, m_pBuoyancyInfo->m_WaterSamples);
if(m_pBuoyancyInfo == NULL && bModelNeedsBuoyancy)
{
InitWaterSamples();
}
if( GetCarryScriptedRT() && !gRenderTargetMgr.IsNamedRenderTargetLinked(modelIdx, m_scriptId))
{
gRenderTargetMgr.LinkNamedRendertargets(modelIdx,m_scriptId);
}
// If this model has glass update the frame information
if(pFragType && pFragType->GetNumGlassPaneModelInfos() > 0)
{
u16 frameFlags = 15 << 2;
const CTunableObjectEntry* pTunableObj = CTunableObjectManager::GetInstance().GetTuningEntry(GetModelNameHash());
if(pTunableObj)
{
int iGlassFlags = pTunableObj->GetGlassFrameFlags();
frameFlags = (u16)(iGlassFlags << 2);
}
for(int i = 0; i < pFragType->GetNumGlassPaneModelInfos(); i++)
{
bgPaneModelInfoBase* pModelInfo = pFragType->GetAllGlassPaneModelInfos()[i];
pModelInfo->m_flags |= frameFlags;
}
}
//---GTA deathmatch/race creator TitleUpdate hack---
// Need to prevent this prop from activating unless bumped or it'll break apart. Can't update resources
// in titleupdate so we need to hack this in.
if(pFragType && GetModelNameHash() == atHashValue("prop_offroad_barrel02",0xc4932af2))
{
// Probably unnecessary check
if(pFragType->GetPhysics(0))
{
pFragType->GetPhysics(0)->SetMinMoveForce(0.01f);
}
}
//--------------------------------------------------
#if __BANK || (__WIN32PC && !__FINAL)
SetIsProp(false);
u16 index = 0xffff;
strStreamingModule* module = NULL;
switch (GetDrawableType())
{
case DT_FRAGMENT:
module = &g_FragmentStore;
index = static_cast<u16>(GetFragmentIndex());
break;
case DT_DRAWABLE:
module = &g_DrawableStore;
index = static_cast<u16>(GetDrawableIndex());
break;
case DT_DRAWABLEDICTIONARY:
module = &g_DwdStore;
index = static_cast<u16>(GetDrawDictIndex());
break;
default:
break;
}
if (module && index != 0xffff)
{
strStreamingInfo* info = module->GetStreamingInfo(strLocalIndex(index));
if (info)
{
s32 imageIndex = strPackfileManager::GetImageFileIndexFromHandle(info->GetHandle()).Get();
if (imageIndex != -1)
{
strStreamingFile* file = strPackfileManager::GetImageFile(imageIndex);
if (file && file->m_contentsType == CDataFileMgr::CONTENTS_PROPS)
SetIsProp(true);
}
}
}
#endif // __BANK
}
//
// name: CBaseModelInfo::DeleteFragType
// description:
void CBaseModelInfo::DeleteMasterFragData()
{
if (m_pShaderEffectType)
{
#if __ASSERT
if (GetNumRefs() != 0)
{
modelinfoErrorf("Non-zero refcount (%d) on %s upon deletion. List of dependencies:", GetNumRefs(), GetModelName());
strStreamingModule* streamingModule = fwArchetypeManager::GetStreamingModule();
u32 objIndex = fwArchetypeManager::GetArchetypeIndex(GetModelNameHash());
strIndex archIndex = streamingModule->GetStreamingIndex(strLocalIndex(objIndex));
strStreamingEngine::GetInfo().PrintAllDependents(archIndex);
modelinfoAssertf(false, "Model %s still has a reference count of %d as we're trying to delete it. See TTY for dependencies.", GetModelName(), GetNumRefs());
}
#endif // __ASSERT
m_pShaderEffectType->RestoreModelInfoDrawable(GetDrawable());
m_pShaderEffectType->RemoveRef();
m_pShaderEffectType = NULL;
}
if(GetDrawableType() == DT_FRAGMENT)
{
modelinfoAssert(GetFragmentIndex() != INVALID_FRAG_IDX);
//delete m_pFragType;
//m_pFragType = NULL;
SetHasLoaded(false);
DeleteWaterSamples();
}
}
void CBaseModelInfo::Init2dEffects()
{
if (m_p2dEffectPtrs){
m_p2dEffectPtrs->Reset(); // should I do this?
}
}
s32 CBaseModelInfo::GetNum2dEffectType(e2dEffectType type) const
{
int count = 0;
if(type != ET_LIGHT)
{
int numPtrs = m_p2dEffectPtrs ? m_p2dEffectPtrs->GetCount() : 0;
for(int i = 0; i < numPtrs; i++)
{
if ((*m_p2dEffectPtrs)[i]->GetType() == type)
{
count++;
}
}
}
else
{
GET_2D_EFFECTS(this);
for(int i = 0; i < numEffects; i++)
{
if(a2dEffects[i]->GetType() == type)
{
count++;
}
}
}
return count;
}
const ModelAudioCollisionSettings*CBaseModelInfo::GetAudioCollisionSettings()
{
if(m_AudioCollisionsOverride)
{
return m_AudioCollisionsOverride;
}
int numPtrs = m_p2dEffectPtrs ? m_p2dEffectPtrs->GetCount() : 0;
for(int i = 0; i < numPtrs; i++)
{
if ((*m_p2dEffectPtrs)[i]->GetType() == ET_AUDIOCOLLISION)
{
return (*m_p2dEffectPtrs)[i]->GetAudioCollisionInfo()->settings;
}
}
return g_MacsDefault;
}
void CBaseModelInfo::SetAudioCollisionSettings(const ModelAudioCollisionSettings * settings, u32 UNUSED_PARAM(hash))
{
m_AudioCollisionsOverride = settings;
}
s32 CBaseModelInfo::GetNum2dEffectLights() const
{
gtaFragType* pFragType = static_cast<gtaFragType *>(GetFragType());
if(pFragType)
return (0 + pFragType->m_lights.GetCount());
gtaDrawable* pDrawable = (gtaDrawable*)GetDrawable();
if(pDrawable)
return 0 + pDrawable->m_lights.GetCount();
return 0;
}
s32 CBaseModelInfo::GetNum2dEffects() const
{
u32 num2dEffects = 0;
if (m_p2dEffectPtrs){
num2dEffects = m_p2dEffectPtrs->GetCount();
}
gtaFragType* pFragType = static_cast<gtaFragType *>(GetFragType());
if(pFragType)
return (num2dEffects + pFragType->m_lights.GetCount());
gtaDrawable* pDrawable = (gtaDrawable*)GetDrawable();
if(pDrawable)
return num2dEffects + pDrawable->m_lights.GetCount();
return num2dEffects;
}
bool CBaseModelInfo::Has2dEffects() const
{
if (m_p2dEffectPtrs && m_p2dEffectPtrs->GetCount())
{
return true;
}
gtaFragType* pFragType = static_cast<gtaFragType *>(GetFragType());
if(pFragType)
return (pFragType->m_lights.GetCount() > 0);
gtaDrawable* pDrawable = (gtaDrawable*)GetDrawable();
if(pDrawable)
return (pDrawable->m_lights.GetCount() > 0);
return false;
}
const atArray<C2dEffect*>* CBaseModelInfo::Get2dEffectsNoLights()
{
return m_p2dEffectPtrs;
}
const atArray<CLightAttr>* CBaseModelInfo::GetLightArray()
{
gtaFragType* pFragType = static_cast<gtaFragType *>(GetFragType());
if(pFragType)
{
return &pFragType->m_lights;
}
gtaDrawable* pDrawable = (gtaDrawable*)GetDrawable();
if(pDrawable)
{
return &pDrawable->m_lights;
}
return NULL;
}
void CBaseModelInfo::Get2dEffects(atArray<C2dEffect*>& effects) const
{
modelinfoFatalAssertf(GetNum2dEffects() < MAX_NUM_2DEFFECTS, "Model found with %d effects - Need to increase MAX_NUM_2DEFFECTS (currently %d)", GetNum2dEffects(), MAX_NUM_2DEFFECTS);
u32 numEffects = 0;
if (m_p2dEffectPtrs){
numEffects = m_p2dEffectPtrs->GetCount();
for(int i = 0; i < numEffects; i++)
{
effects.Push((*m_p2dEffectPtrs)[i]);
}
}
gtaFragType* pFragType = static_cast<gtaFragType *>(GetFragType());
if(pFragType)
{
int numLights = pFragType->m_lights.GetCount();
for(int i = 0; i < numLights; i++)
{
effects.Push(&(pFragType->m_lights[i]));
}
return;
}
gtaDrawable* pDrawable = (gtaDrawable*)GetDrawable();
if(pDrawable)
{
int numLights = pDrawable->m_lights.GetCount();
for(int i = 0; i < numLights; i++)
{
effects.Push(&(pDrawable->m_lights[i]));
}
}
}
C2dEffect* CBaseModelInfo::Get2dEffect(s32 i)
{
u32 num2dEffects = 0;
if (m_p2dEffectPtrs){
num2dEffects = m_p2dEffectPtrs->GetCount();
if(i < num2dEffects)
{
return((*m_p2dEffectPtrs)[i]);
}
}
gtaFragType* pFragType = static_cast<gtaFragType *>(GetFragType());
if((pFragType != NULL) && (i >= num2dEffects))
{
if(i - num2dEffects < pFragType->m_lights.GetCount())
return &(pFragType->m_lights[i - num2dEffects]);
else
return NULL;
}
gtaDrawable* pDrawable = (gtaDrawable*)GetDrawable();
if((pDrawable != NULL) && (i >= num2dEffects))
{
s32 index = i - num2dEffects;
if((index >= 0) && (index < pDrawable->m_lights.GetCount()))
{
return &(pDrawable->m_lights[i - num2dEffects]);
}
else
{
return NULL;
}
}
return NULL;
}
C2dEffect** CBaseModelInfo::GetNewEffect()
{
modelinfoAssertf(m_p2dEffectPtrs, "No effect array is initialised for this archetype : %s", GetModelName());
modelinfoAssertf(m_p2dEffectPtrs->GetCount() < m_p2dEffectPtrs->GetCapacity(), "%s:Too many 2d effects on this modelinfo", GetModelName());
return(&(m_p2dEffectPtrs->Append()));
}
void CBaseModelInfo::Add2dEffect(C2dEffect** ppEffect)
{
u8 effectType = (*ppEffect)->GetType();
// set has spawn point flag
if(effectType == ET_SPAWN_POINT)
SetHasSpawnPoints(true);
if (effectType == ET_LIGHT || effectType == ET_LIGHT_SHAFT || effectType == ET_SCROLLBAR || effectType == ET_WINDDISTURBANCE ||
(effectType==ET_PARTICLE && ((*ppEffect)->GetParticle()->m_fxType==PT_AMBIENT_FX || (*ppEffect)->GetParticle()->m_fxType==PT_INWATER_FX)))
{
SetHasPreRenderEffects();
}
}
s32 CBaseModelInfo::GetNum2dEffectsNoLights(void) const
{
if (m_p2dEffectPtrs){
return(m_p2dEffectPtrs->GetCount());
} else {
return(0);
}
}
void CBaseModelInfo::SetNum2dEffects(u32 numEffects)
{
modelinfoAssertf(!m_p2dEffectPtrs, "2D effect array is already initialised for this archetype : %s", GetModelName());
m_p2dEffectPtrs = rage_new atArray<C2dEffect*>;
Assert(m_p2dEffectPtrs);
m_p2dEffectPtrs->Reset();
m_p2dEffectPtrs->Reserve(numEffects);
}
// check the txd chain - check each slot for a valid HDTxd index & if any found then set HDTexCapable flag for this type
void CBaseModelInfo::CheckForHDTxdSlots(void){
// ConvertLoadFlags() will set this to true if FLAG_SUPPRESS_HD_TXDS is not set
if (!GetIsHDTxdCapable())
{
return;
}
// Reset it to false before we search for HD Texture dictionaries.
SetIsHDTxdCapable(false);
// check the base asset for HD mapping
eStoreType type = STORE_ASSET_INVALID;
s32 slot = -1;
switch(GetDrawableType()){
case(DT_DRAWABLE):
type = STORE_ASSET_DRB;
slot = GetDrawableIndex();
break;
case(DT_DRAWABLEDICTIONARY):
type = STORE_ASSET_DWD;
slot = GetDrawDictIndex();
break;
case(DT_FRAGMENT):
type = STORE_ASSET_FRG;
slot = GetFragmentIndex();
break;
default:
return;
}
if (CTexLod::HasAssetHDMapping(type, slot)){
SetIsHDTxdCapable(true);
return;
}
// check txd chain for any txd with HD mapping
strLocalIndex txdIndex = strLocalIndex(GetAssetParentTxdIndex());
while(txdIndex != -1){
Assertf(CTexLod::HasAssetHDMapping(STORE_ASSET_TXD, txdIndex.Get()) == g_TxdStore.GetIsHDCapable(txdIndex), "found cache mismatch for %s", (g_TxdStore.GetSlot(txdIndex))->m_name.GetCStr());
if(g_TxdStore.GetIsHDCapable(txdIndex)) {
SetIsHDTxdCapable(true);
break;
}
txdIndex = g_TxdStore.GetParentTxdSlot(txdIndex);
}
}
fwAssetLocation CBaseModelInfo::GetAssetLocation(void) const{
// check the base asset for HD mapping
eStoreType type = STORE_ASSET_INVALID;
s32 slot = -1;
switch(GetDrawableType()){
case(DT_DRAWABLE):
type = STORE_ASSET_DRB;
slot = GetDrawableIndex();
break;
case(DT_DRAWABLEDICTIONARY):
type = STORE_ASSET_DWD;
slot = GetDrawDictIndex();
break;
case(DT_FRAGMENT):
type = STORE_ASSET_FRG;
slot = GetFragmentIndex();
break;
default:
break;
}
return(fwAssetLocation(type, slot));
}
// --- CTimeModelInfo ----------------------------------------------------------
CTimeInfo* CTimeInfo::FindOtherTimeModel(const char* pName)
{
char otherName[256];
char* pIdentifier;
u32 i;
CBaseModelInfo* pModelInfo;
CTimeInfo* pTimeInfo=NULL;
safecpy(otherName, pName);
pIdentifier = strstr(otherName, "_nt");
if(pIdentifier)
{
strncpy(pIdentifier, "_dy", 4);
}
else
{
pIdentifier = strstr(otherName, "_dy");
if(pIdentifier)
{
strncpy(pIdentifier, "_nt", 4);
}
else
return NULL;
}
u32 key = atStringHash(otherName);
// Go through list of models and compare name with model names
const u32 maxModelInfos = CModelInfo::GetMaxModelInfos();
for(i=0; i<maxModelInfos; i++)
{
pModelInfo = CModelInfo::GetBaseModelInfo(fwModelId(strLocalIndex(i)));
if(pModelInfo)
{
pTimeInfo = pModelInfo->GetTimeInfo();
if(pTimeInfo)
{
if(pModelInfo->GetHashKey() == key)
{
break;
}
}
}
}
if(i != maxModelInfos)
{
modelinfoAssertf((pTimeInfo->m_hoursOnOff ^ m_hoursOnOff) == 0x00ffffff, "%s:Times do not correspond with the other time model", pName);
m_otherModel = i;
return pTimeInfo;
}
return NULL;
}
static dev_float BASE_BUOYANCY_CONSTANT = 1.1f;
static dev_float BASE_DRAG_MULT = 50.0f;
static dev_float MIN_FLOATER_SUBMERGE_DEPTH = 0.3f;
static dev_float BASE_SINK_MULTIPLIER = 0.6f;
static dev_float BASE_SINK_DENSITY_THRESHOLD = 1000.0f;
void CBaseModelInfo::InitWaterSamples()
{
#if __DEV
if(ms_bPrintWaterSampleEvents)
{
modelinfoDisplayf("[Buoyancy] Creating water samples for model %s", GetModelName());
}
#endif
modelinfoAssertf(m_pBuoyancyInfo == NULL,"InitWaterSamples() called when water samples already exist");
if(m_pBuoyancyInfo != NULL)
{
return;
}
// going to allocate our own water samples
CWaterSample tempWaterSamples[MAX_WATER_SAMPLES];
phBound* pBound = NULL;
phBound* pBoundForSampleGeneration = NULL;
phArchetypeDamp *pPhysics = GetPhysics();
int nComponent = 0;
gtaFragType* pFragType = static_cast<gtaFragType*>(GetFragType());
const CTunableObjectEntry* pTuning = CTunableObjectManager::GetInstance().GetTuningEntry(GetModelNameHash());
if(pTuning && pTuning->GetBoundIndexForWaterSamples() >= 0)
{
int nComponentIndexForWaterSamples = pTuning->GetBoundIndexForWaterSamples();
phBoundComposite* pCompBound = NULL;
if(pFragType)
{
pCompBound = pFragType->GetPhysics(0)->GetCompositeBounds();
}
else if(pPhysics && pPhysics->GetBound() && pPhysics->GetBound()->GetType() == phBound::COMPOSITE)
{
pCompBound = static_cast<phBoundComposite*>(pPhysics->GetBound());
}
else
{
modelinfoAssert(false);
}
if(pCompBound)
{
if(modelinfoVerifyf(nComponentIndexForWaterSamples < pCompBound->GetNumBounds(),
"<boundIndexForWaterSamples> is set to %d for %s which is greater than total number of bounds %d",
nComponentIndexForWaterSamples, GetModelName(), pCompBound->GetNumBounds()))
{
nComponent = nComponentIndexForWaterSamples;
pBound = pBoundForSampleGeneration = pCompBound->GetBound(nComponent);
}
else
{
pBound = pBoundForSampleGeneration = NULL;
}
}
}
if(!pBound || !pBoundForSampleGeneration)
{
if(pFragType)
{
pBound = pBoundForSampleGeneration = pFragType->GetPhysics(0)->GetCompositeBounds();
}
else if(pPhysics)
{
pBound = pBoundForSampleGeneration = pPhysics->GetBound();
if(pBound && pBound->GetType() == phBound::COMPOSITE)
{
// For non-frag objects just use the biggest component to generate samples
pBoundForSampleGeneration = GetLargestBoundFromComposite(static_cast<phBoundComposite*>(pBound));
nComponent = pBoundForSampleGeneration->GetIndexFromBound();
}
}
else
{
modelinfoAssert(false);
}
}
int nNumSamplesNeeded = GenerateWaterSamplesFromBoundR(tempWaterSamples,MAX_WATER_SAMPLES,pBoundForSampleGeneration,nComponent);
if(nNumSamplesNeeded == 0)
{
//modelinfoAssertf(false,"Water sample generation failed for object %s",GetModelName()); // cloth ends up with no buoyancy info
return;
}
m_pBuoyancyInfo = rage_new CBuoyancyInfo;
if(m_pBuoyancyInfo==NULL)
{
modelinfoAssertf(false,"Could not allocate buoyancy info");
return;
}
modelinfoAssert(m_pBuoyancyInfo->m_WaterSamples == NULL);
modelinfoAssert(m_pBuoyancyInfo->m_nNumWaterSamples == 0);
m_pBuoyancyInfo->m_WaterSamples = rage_new CWaterSample[nNumSamplesNeeded];
#if __DEV
ms_nNumWaterSamplesInMemory += m_pBuoyancyInfo->m_nNumWaterSamples;
PF_SET(NumWaterSamplesInMemory, ms_nNumWaterSamplesInMemory);
#endif // __DEV
if(m_pBuoyancyInfo->m_WaterSamples==NULL)
{
modelinfoAssertf(false,"Could not allocate buoyancy samples");
return;
}
m_pBuoyancyInfo->m_nNumWaterSamples = (s16)nNumSamplesNeeded;
float fSizeTotal = 0.0f;
if(pFragType)
{
// Setup frag type
// Need to balance buoyancy of each sample so that when object breaks up it still floats
// Sample buoyancy = mass fraction / size fraction
// Where mass fraction = mass of componenet / total mass
// sample fraction = no of samples / total samples
const float fTotalMass = pFragType->GetPhysics(0)->GetTotalUndamagedMass();
const int nNumChildren = pFragType->GetPhysics(0)->GetNumChildren();
float* fMassFraction = Alloca(float, nNumChildren);
float* fSizeFraction = Alloca(float, nNumChildren); // Number of samples per component
modelinfoAssert(fMassFraction && fSizeFraction);
for(int iChildIndex = 0; iChildIndex < nNumChildren; iChildIndex++)
{
fMassFraction[iChildIndex] = pFragType->GetPhysics(0)->GetAllChildren()[iChildIndex]->GetUndamagedMass() / fTotalMass;
fSizeFraction[iChildIndex] = 0.0f;
}
for(int iSampleIndex = 0; iSampleIndex < nNumSamplesNeeded; iSampleIndex++)
{
modelinfoAssert(tempWaterSamples[iSampleIndex].m_nComponent < nNumChildren);
fSizeFraction[tempWaterSamples[iSampleIndex].m_nComponent] += tempWaterSamples[iSampleIndex].m_fSize;
fSizeTotal += tempWaterSamples->m_fSize;
}
for(int iSampleIndex = 0; iSampleIndex < nNumSamplesNeeded; iSampleIndex++)
{
const int nChildIndex = tempWaterSamples[iSampleIndex].m_nComponent;
tempWaterSamples[iSampleIndex].m_fBuoyancyMult *= fSizeTotal * fMassFraction[nChildIndex]/
fSizeFraction[nChildIndex];
m_pBuoyancyInfo->m_WaterSamples[iSampleIndex].Set(tempWaterSamples[iSampleIndex]);
}
}
else
{
// Setup Non-frag type
for(int iSampleIndex = 0; iSampleIndex < nNumSamplesNeeded; iSampleIndex++)
{
m_pBuoyancyInfo->m_WaterSamples[iSampleIndex].Set(tempWaterSamples[iSampleIndex]);
fSizeTotal += tempWaterSamples->m_fSize;
}
}
float fBuoyancyConstant = BASE_BUOYANCY_CONSTANT;
bool bFloater = false;
float fTargetFloatPos = 0.0f;
GET_2D_EFFECTS_NOLIGHTS(this);
for(int i=0; i < iNumEffects && bFloater == false; i++)
{
C2dEffect *pEffect = (*pa2dEffects)[i];
if(pEffect->GetType()==ET_BUOYANCY)
{
// get the height the object is supposed to float at
Vector3 vFloatPos;
pEffect->GetPos(vFloatPos);
fTargetFloatPos = vFloatPos.z;
bFloater = true;
}
}
if(bFloater)
{
fBuoyancyConstant = (GetBoundingBoxMax().z - GetBoundingBoxMin().z) / rage::Max(MIN_FLOATER_SUBMERGE_DEPTH, fTargetFloatPos - GetBoundingBoxMin().z);
static dev_bool bChangeCG = true;
if(bChangeCG)
{
// Check if we should offset the centre of mass for this object.
if(pTuning)
{
if(pTuning->GetCentreOfMassOffset().IsNonZero())
{
pBound->SetCGOffset(RCC_VEC3V(pTuning->GetCentreOfMassOffset()));
}
}
else
{
Vector3 vecCgOffset = VEC3V_TO_VECTOR3(pBound->GetCGOffset());
static dev_float fMagicNumber = 0.6f;
vecCgOffset.z = fMagicNumber*(fTargetFloatPos + GetBoundingBoxMin().z);
pBound->SetCGOffset(RCC_VEC3V(vecCgOffset));
}
}
}
// Check if we should scale the buoyancy constant for this object.
if(pTuning)
{
if(pTuning->GetBuoyancyFactor() != 1.0f)
{
fBuoyancyConstant *= pTuning->GetBuoyancyFactor();
}
}
m_pBuoyancyInfo->m_fBuoyancyConstant = fBuoyancyConstant * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultXY = m_pBuoyancyInfo->m_fDragMultZUp = m_pBuoyancyInfo->m_fDragMultZDown = BASE_DRAG_MULT * -GRAVITY / fSizeTotal;
if(pTuning && pTuning->GetBuoyancyDragFactor() != 1.0f)
{
float fMultiplier = pTuning->GetBuoyancyDragFactor();
m_pBuoyancyInfo->m_fDragMultXY *= fMultiplier;
m_pBuoyancyInfo->m_fDragMultZUp *= fMultiplier;
m_pBuoyancyInfo->m_fDragMultZDown *= fMultiplier;
}
}
void CBaseModelInfo::DeleteWaterSamples()
{
if(m_pBuoyancyInfo)
{
#if __DEV
if(ms_bPrintWaterSampleEvents)
{
modelinfoDisplayf("[Buoyancy] Deleting water samples for model %s", GetModelName());
}
ms_nNumWaterSamplesInMemory -= m_pBuoyancyInfo->m_nNumWaterSamples;
PF_SET(NumWaterSamplesInMemory, ms_nNumWaterSamplesInMemory);
#endif
delete m_pBuoyancyInfo;
m_pBuoyancyInfo = NULL;
}
}
bool CBaseModelInfo::GetGeneratesWindAudio() const
{
if(!m_p2dEffectPtrs)
{
return false;
}
atArray<C2dEffect*>& effectPtrs = *m_p2dEffectPtrs;
int numPtrs = effectPtrs.GetCount();
for(int i = 0; i < numPtrs; i++)
{
if (effectPtrs[i]->GetType() == ET_AUDIOEFFECT)
{
if(g_EmitterAudioEntity.GetGeneratesWindAudio(effectPtrs[i]->GetAudio()))
{
return true;
}
}
}
return false;
}
void CBaseModelInfo::InitLodSkeletonMap(const crSkeletonData* skelData, const crSkeletonData* lodSkelData, const CBaseModelInfo* ASSERT_ONLY(slodMI))
{
if (!skelData || !lodSkelData)
return;
if (!skelData->HasBoneIds() || !lodSkelData->HasBoneIds())
return;
const u32 numLodBones = lodSkelData->GetNumBones();
if (numLodBones <= 0)
return;
Assertf(skelData->GetNumBones() >= numLodBones, "Lod skeleton (%s) has more bones than original skeleton (%s)! (%d >= %d)", slodMI->GetModelName(), GetModelName(), skelData->GetNumBones(), numLodBones);
m_lodSkelBoneNum = (u16)numLodBones;
if (m_lodSkelBoneMap)
delete[] m_lodSkelBoneMap;
m_lodSkelBoneMap = rage_aligned_new(16) u16[m_lodSkelBoneNum];
Assert(m_lodSkelBoneNum < 256);
for (u8 i = 0; i < m_lodSkelBoneNum; ++i)
{
u16 lodBoneId = lodSkelData->GetBoneData(i)->GetBoneId();
// if a bone in the lod skeleton isn't found in the original skeleton it will be skinned to previous bone (or 0 if this is the first one)
s32 boneIndex = 0;
if (!Verifyf(skelData->ConvertBoneIdToIndex(lodBoneId, boneIndex), "Couldn't find bone index for id %d (%s) from lod skeleton (%s) in original skeleton! (%s)", lodBoneId, lodSkelData->GetBoneData(i)->GetName(), slodMI->GetModelName(), GetModelName()))
{
boneIndex = (i > 0) ? m_lodSkelBoneMap[i - 1] : 0;
}
Assert(boneIndex < 65536);
m_lodSkelBoneMap[i] = boneIndex & 0xffff;
}
}
void CBaseModelInfo::InitLodSkeletonMap(const crSkeletonData* skelData, const atArray<int>& skinnedBones)
{
if (!skelData)
return;
const int numLodBones = skinnedBones.GetCount();
if (numLodBones <= 0)
return;
Assertf(skelData->GetNumBones() >= numLodBones, "Lod skeleton has more bones than original skeleton for '%s'! (%d >= %d)", GetModelName(), skelData->GetNumBones(), numLodBones);
m_lodSkelBoneNum = (u16)numLodBones;
if (m_lodSkelBoneMap)
delete[] m_lodSkelBoneMap;
m_lodSkelBoneMap = rage_aligned_new(16) u16[m_lodSkelBoneNum];
Assert(m_lodSkelBoneNum < 256);
for (u8 i = 0; i < m_lodSkelBoneNum; ++i)
{
s32 boneIndex = skinnedBones[i];
Assert(boneIndex < 65536);
m_lodSkelBoneMap[i] = boneIndex & 0xffff;
}
}
void CBaseModelInfo::ShutdownLodSkeletonMap()
{
if (m_lodSkelBoneMap)
{
delete[] m_lodSkelBoneMap;
m_lodSkelBoneMap = NULL;
}
m_lodSkelBoneNum = 0;
}
s32 CBaseModelInfo::GetLodSkeletonBoneIndex(s32 boneIndex)
{
s32 lodBoneIndex = boneIndex;
if (m_lodSkelBoneMap)
{
for (s32 i = 0; i < m_lodSkelBoneNum; ++i)
{
if ((s32)m_lodSkelBoneMap[i] == boneIndex)
{
lodBoneIndex = i;
break;
}
}
}
return lodBoneIndex;
}
#if NORTH_CLOTHS
void CBaseModelInfo::InitClothArchetype()
{
// Need a skeleton to create a cloth
Assertf(GetExtension<CClothArchetype>() == NULL, "CBaseModelInfo already has cloth data");
rmcDrawable* pDrawable = GetDrawable();
Assertf(pDrawable, "Can't call InitClothArchetype without a loaded drawable");
if(pDrawable && pDrawable->GetSkeletonData())
{
// Lookup plant tuning data
const CPlantTuning& plantTuningData=CPlantTuning::GetPlantTuneData(GetHashKey());
CClothArchetype* pClothArchetype = CClothArchetypeCreator::CreatePlant(*pDrawable->GetSkeletonData(), plantTuningData);
Assertf(pClothArchetype, "Failed to create cloth archetype for model %s", GetModelName());
GetExtensionList().Add(*pClothArchetype);
}
}
void CBaseModelInfo::DeleteClothArchetype()
{
GetExtensionList().Destroy(CClothArchetype::GetAutoId());
}
#endif // NORTH_CLOTHS
static dev_float MIN_WATER_SAMPLE_SIZE = 0.1f;
int CBaseModelInfo::GenerateWaterSamplesFromBoundR(CWaterSample* pWaterSamples,int nNumSamplesInArray,phBound* pBound, int iCurrentComponent)
{
modelinfoAssert(pWaterSamples);
modelinfoAssert(pBound);
if(!pBound)
{
return 0;
}
else if(pBound->GetType() == phBound::COMPOSITE)
{
int nNumSamplesUsed = 0;
phBoundComposite* pComposite = static_cast<phBoundComposite*>(pBound);
for(int iCompositeIndex = 0; iCompositeIndex < pComposite->GetNumBounds(); iCompositeIndex++)
{
if(pComposite->GetBound(iCompositeIndex))
nNumSamplesUsed += GenerateWaterSamplesFromBoundR(&(pWaterSamples[nNumSamplesUsed]),nNumSamplesInArray - nNumSamplesUsed, pComposite->GetBound(iCompositeIndex), iCompositeIndex);
}
return nNumSamplesUsed;
}
else
{
if(nNumSamplesInArray == 0)
{
return 0;
}
// min 2 samples in each direction so it doesn't just roll about in water
int aNumSamples[3] = {1, 1, 2};
// need to look at size of object and decide how many samples to use in each direction
const Vector3 vBBoxMin = VEC3V_TO_VECTOR3(pBound->GetBoundingBoxMin());
const Vector3 vBBoxMax = VEC3V_TO_VECTOR3(pBound->GetBoundingBoxMax());
Vector3 vecSize = vBBoxMax - vBBoxMin;
float fWaterSampleLength = 0.5f*vecSize.x;
fWaterSampleLength = rage::Max(fWaterSampleLength,MIN_WATER_SAMPLE_SIZE);
// Check material of object. If high density (e.g. stone, concrete) then lower buoyancy
float fBuoyancyMult = 1.0f;
phMaterialGta* pMat = (phMaterialGta*)&(pBound->GetMaterial(0));
if(pMat && pMat->GetDensity() > BASE_SINK_DENSITY_THRESHOLD)
{
fBuoyancyMult *= BASE_SINK_MULTIPLIER;
}
for(int i=0; i<3; i++)
{
float fSize = *(&vecSize.x + i); // Choose x,y,z of vecSize depending on i
if(fSize > 6.0f)
{
aNumSamples[i] = (int)(fSize / 2.0f);
if(aNumSamples[i] > 5)
aNumSamples[i] = 5;
}
else if(fSize > 1.0f)
aNumSamples[i] = 3;
else if(fSize > 0.3f)
aNumSamples[i] = 2;
if(fWaterSampleLength * (float)aNumSamples[i] > 0.5f*fSize)
fWaterSampleLength = 0.5f*fSize /(float)aNumSamples[i];
}
int nNumSamplesToUse = (s16)(aNumSamples[0] * aNumSamples[1] * aNumSamples[2]);
// check we didn't create too many sample points
if(nNumSamplesToUse > nNumSamplesInArray)
{
int nSamplesPerSide = (int)rage::Powf((float)nNumSamplesInArray, 1.0f / 3.0f);
modelinfoAssert(nSamplesPerSide > 0);
// clamp all but the biggest side to 3 points (5 is max per side normally)
int nBiggestSide = (vecSize.x > vecSize.y) ? 0 : 1;
if(vecSize.z > vecSize.x && vecSize.z > vecSize.y)
nBiggestSide = 2;
for(int i=0; i<3; i++)
{
if(i!=nBiggestSide && aNumSamples[i] > nSamplesPerSide)
aNumSamples[i] = nSamplesPerSide;
}
nNumSamplesToUse = (s16)(aNumSamples[0] * aNumSamples[1] * aNumSamples[2]);
if(nNumSamplesToUse > nNumSamplesInArray)
{
// Forced to clamp biggest side as well
aNumSamples[nBiggestSide] = nSamplesPerSide;
}
}
modelinfoAssertf(nNumSamplesInArray <= MAX_WATER_SAMPLES, "Object automatic water sample generation made too many points");
// set them up
Vector3 vecStart = vBBoxMin;
Vector3 vecStep(vecSize.x / (float)aNumSamples[0], vecSize.y / (float)aNumSamples[1], vecSize.z / (float)aNumSamples[2]);
int n = 0;
Vector3 vecPos;
int nNumSamplesUsed = 0;
vecPos[0] = vecStart[0] + 0.5f*vecStep[0];
for(int i=0; i<aNumSamples[0]; i++, vecPos[0] += vecStep[0])
{
vecPos[1] = vecStart[1] + 0.5f*vecStep[1];
for(int j=0; j<aNumSamples[1]; j++, vecPos[1] += vecStep[1])
{
vecPos[2] = vecStart[2] + 0.5f*vecStep[2];
for(int k=0; k<aNumSamples[2]; k++, vecPos[2] += vecStep[2], n++)
{
modelinfoAssert(n < nNumSamplesInArray);
// want to be very careful we don't overflow the array
if(n < nNumSamplesInArray)
{
pWaterSamples[n].m_vSampleOffset.Set(vecPos);
pWaterSamples[n].m_fSize = fWaterSampleLength;
pWaterSamples[n].m_nComponent = static_cast<u16>(iCurrentComponent);
pWaterSamples[n].m_fBuoyancyMult = fBuoyancyMult;
nNumSamplesUsed ++;
}
}
}
}
return nNumSamplesUsed;
}
}
phBound* CBaseModelInfo::GetLargestBoundFromComposite(phBoundComposite* pCompositeBound)
{
modelinfoAssert(pCompositeBound);
int iLargestBound = 0;
float fLargestVolume = 0.0f;
for(int iCompositeIndex = 0; iCompositeIndex < pCompositeBound->GetNumBounds(); iCompositeIndex++ )
{
phBound* pBoundPart = pCompositeBound->GetBound(iCompositeIndex);
if(!pBoundPart)
{
continue;
}
if(pBoundPart->GetType() == phBound::COMPOSITE)
{
pBoundPart = GetLargestBoundFromComposite(static_cast<phBoundComposite*>(pBoundPart));
}
const float fVolume = pBoundPart->GetComputeVolume();
if(fVolume > fLargestVolume)
{
fLargestVolume = fVolume;
iLargestBound = iCompositeIndex;
}
}
phBound* pLargestBound = pCompositeBound->GetBound(iLargestBound);
if(pLargestBound == NULL)
{
modelinfoAssertf(false, "This composite bound has no parts");
pLargestBound = pCompositeBound;
}
else
{
pLargestBound->SetIndexInBound(iLargestBound);
}
return pLargestBound;
}
// create phArchetype if required, return true if it exists or if was created successfully
bool CBaseModelInfo::CreatePhysicsArchetype()
{
if (HasPhysics())
{
return true;
}
if (GetHasLoaded() && GetHasBoundInDrawable())
{
gtaDrawable* pDrawable = (gtaDrawable*) GetDrawable();
if (pDrawable && pDrawable->m_pPhBound)
{
// create phArchetype and set it up
phArchetypeDamp* pPhArch = rage_new phArchetypeDamp();
if (pPhArch)
{
if (GetDrawableType()==DT_DRAWABLE)
{
g_DrawableStore.AddRef(strLocalIndex(GetDrawableIndex()), REF_OTHER);
// char refString[16];
// g_DrawableStore.GetRefCountString(GetDrawableIndex(), refString, sizeof(refString));
// Displayf("Physics reference to %s raised to %s", GetModelName(), refString);
}
GetDynamicComponentPtr()->ResetPhysicsArchRefs();
pPhArch->SetBound(pDrawable->m_pPhBound);
DEV_ONLY( pPhArch->SetFilename(GetModelName()); )
pPhArch->AddRef();
CPhysics::AdditionalSetupModelInfoPhysics(*pPhArch);
SetPhysics(pPhArch); // also calls UpdateBoundingVolumes()
AddRef();
phBound* pBound = pPhArch->GetBound();
if( pBound->GetType() == phBound::COMPOSITE )
{
phBoundComposite* pCompBound = static_cast<phBoundComposite*>( pBound );
for(int nComponent = 0; nComponent < pCompBound->GetNumBounds(); ++nComponent)
{
if(phBound* pBound = pCompBound->GetBound(nComponent))
{
for(int nMatIdx = 0; nMatIdx < pBound->GetNumMaterials(); ++nMatIdx)
{
phMaterialMgr::Id nMaterialId = PGTAMATERIALMGR->UnpackMtlId( pBound->GetMaterialId(nMatIdx) );
if( nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleSpeedUp ||
nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleSlowDown ||
nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleRefill ||
nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleBoostCancel ||
nMaterialId == PGTAMATERIALMGR->g_idPhysPropPlacement
)
{
pCompBound->SetIncludeFlags( nComponent, ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_SCRIPT_TEST );
}
if( nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleTyreBurst )
{
pCompBound->SetIncludeFlags( nComponent, ArchetypeFlags::GTA_WHEEL_TEST );
}
}
}
}
}
else
{
for(int nMatIdx = 0; nMatIdx < pBound->GetNumMaterials(); ++nMatIdx)
{
phMaterialMgr::Id nMaterialId = PGTAMATERIALMGR->UnpackMtlId( pBound->GetMaterialId(nMatIdx) );
if( nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleSpeedUp ||
nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleSlowDown ||
nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleRefill ||
nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleBoostCancel ||
nMaterialId == PGTAMATERIALMGR->g_idPhysPropPlacement
)
{
pPhArch->SetIncludeFlags( ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_SCRIPT_TEST );
}
if( nMaterialId == PGTAMATERIALMGR->g_idPhysVehicleTyreBurst )
{
pPhArch->SetIncludeFlags( ArchetypeFlags::GTA_WHEEL_TEST );
}
}
}
return true;
}
}
}
return false;
}
// destroy phArchetype
void CBaseModelInfo::DeletePhysicsArchetype()
{
Assertf(HasPhysics(), "No phArchetype to delete for %s", GetModelName());
if ( GetDynamicComponentPtr() && Verifyf(GetHasBoundInDrawable(), "Deleting phArchetype for %s but it doesn't have any bound data in its drawable", GetModelName()) )
{
phArchetypeDamp* pPhArchetype = GetPhysics();
if (pPhArchetype)
{
pPhArchetype->Release(false);
Assertf( GetDynamicComponentPtr()->GetNumPhysicsArchRefs()==0, "Deleting phArchetype for %s but it has %d refs", GetModelName(), GetDynamicComponentPtr()->GetNumPhysicsArchRefs() );
delete pPhArchetype;
SetPhysics(NULL);
if (GetDrawableType()==DT_DRAWABLE)
{
g_DrawableStore.RemoveRef(strLocalIndex(GetDrawableIndex()), REF_OTHER);
// char refString[16];
// g_DrawableStore.GetRefCountString(GetDrawableIndex(), refString, sizeof(refString));
// Displayf("Physics reference to %s dropped to %s", GetModelName(), refString);
}
RemoveRef();
}
}
}
// increment references to phArchetype to prevent it being deleted
void CBaseModelInfo::AddRefToDrawablePhysics()
{
if ( GetDynamicComponentPtr() )
{
Assertf(HasPhysics(), "Adding a ref to drawable bounds for %s but no phArchetype exists", GetModelName());
Assertf(GetHasBoundInDrawable(), "Adding a ref to drawable bounds for %s but it doesn't have any", GetModelName());
GetDynamicComponentPtr()->AddPhysicsArchRef();
}
}
// decrement references to phArchetype and delete if num refs hit 0 as a result
void CBaseModelInfo::RemoveRefFromDrawablePhysics()
{
if ( GetDynamicComponentPtr() != NULL )
{
GetDynamicComponentPtr()->RemovePhysicsArchRef();
if (GetDynamicComponentPtr()->GetNumPhysicsArchRefs() == 0)
{
DeletePhysicsArchetype();
}
}
}
#if __BANK
void CBaseModelInfo::ReloadWaterSamples()
{
DeleteWaterSamples();
InitWaterSamples();
}
#endif // __BANK