Files
GTASource/game/modelinfo/VehicleModelInfo.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

8496 lines
296 KiB
C++

//
// Filename: VehicleModelInfo.cpp
// Creator: Adam Fowler
// $Author: $
// $Date: $
// $Revision: $
// Description: Class describing a model with a hierarchy
//
//
// C headers
#include "ctype.h"
// Rage headers
#include "grcore/effect_config.h" // edge
#include "crskeleton/jointdata.h"
#include "crskeleton/skeleton.h"
#include "crskeleton/skeletondata.h"
#include "diag/art_channel.h"
#include "file/default_paths.h"
#include "fragment/typeGroup.h"
#include "parser/manager.h"
#include "parser/psofile.h"
#include "parser/restparserservices.h"
#include "phbound/BoundBox.h"
#include "phbound/BoundCapsule.h"
#include "phbound/BoundComposite.h"
#include "phbound/boundbvh.h"
#include "phBound/support.h"
#include "system/memory.h"
#include "system/nelem.h"
// Framework headers
#include "fwanimation/animmanager.h"
#include "grcore/debugdraw.h"
#include "fwdebug/picker.h"
#include "fwmaths/angle.h"
#include "fwpheffects/ropedatamanager.h"
#include "fwscene/texLod.h"
#include "fwscene/stores/txdstore.h"
#include "fwscene/stores/fragmentstore.h"
#include "fwscene/stores/clipdictionarystore.h"
#include "fwscene/stores/expressionsdictionarystore.h"
#include "fwsys/fileExts.h"
#include "fwsys/metadatastore.h"
#include "vfx/ptfx/ptfxmanager.h"
#include "vfx/particles/PtFxManager.h"
// Game headers
#include "audio/radioaudioentity.h"
#include "core/game.h"
#include "debug/DebugScene.h"
#include "frontend/Scaleform/ScaleFormStore.h"
#include "frontend/NewHud.h"
#include "frontend/UIReticule.h"
#include "game/config.h"
#include "game/modelindices.h"
#include "ModelInfo/ModelInfo.h"
#include "ModelInfo/ModelInfo_Factories.h"
#include "modelinfo/VehicleModelInfoColors.h"
#include "modelinfo/VehicleModelInfoExtensions.h"
#include "modelinfo/VehicleModelInfoPlates.h"
#include "modelinfo/VehicleModelInfoLights.h"
#include "modelinfo/VehicleModelInfoVariation.h"
#include "modelinfo/VehicleModelInfo.h"
#include "modelinfo/VehicleModelInfoData_parser.h"
#include "objects/CoverTuning.h"
#include "pathserver/ExportCollision.h"
#include "pathserver/NavGenParam.h"
#include "physics/gtainst.h"
#include "physics/floater.h"
#include "renderer/HierarchyIds.h"
#include "renderer/RenderPhases/RenderPhaseReflection.h"
#include "renderer/DrawLists/drawList.h"
#include "renderer/RenderTargetMgr.h"
#include "scene/DataFileMgr.h"
#include "scene/texLod.h"
#include "scene/world/GameWorld.h"
#include "shaders/CustomShaderEffectVehicle.h"
#include "streaming/populationstreaming.h"
#include "streaming/streaming.h"
#include "streaming/streamingvisualize.h"
#include "system/FileMgr.h"
#include "Task/Vehicle/TaskCarUtils.h"
#include "Task/Vehicle/TaskInVehicle.h"
#include "Task/Vehicle/TaskEnterVehicle.h"
#include "Task/Combat/Cover/Cover.h"
#include "vehicles/cargen.h"
#include "vehicles/Metadata/VehicleMetadataManager.h"
#include "vehicles/Metadata/VehicleSeatInfo.h"
#include "vehicles/Metadata/VehicleEntryPointInfo.h"
#include "vehicles/Metadata/VehicleLayoutInfo.h"
#include "vehicles/Metadata/VehicleScenarioLayoutInfo.h"
#include "vehicles/HandlingMgr.h"
#include "vehicles/Planes.h"
#include "vehicles/Train.h"
#include "vehicles/Vehicle.h"
#include "vehicles/VehicleFactory.h"
#include "vehicles/vehicleLightSwitch.h"
#include "vehicles/wheel.h"
#include "vfx/vehicleglass/VehicleGlassManager.h"
#include "scene/EntityIterator.h"
#include "stats/StatsMgr.h"
#include "peds/ped.h"
#include "peds/PedHelmetComponent.h"
#if __BANK
#include "grcore/debugdraw.h"
#include "grcore/setup.h"
#include "debug/UiGadget/UiColorScheme.h"
#include "debug/UiGadget/UiGadgetWindow.h"
#include "debug/UiGadget/UiGadgetList.h"
#include "debug/TextureViewer/TextureViewerSearch.h"
#include "debug/TextureViewer/TextureViewer.h"
#endif
VEHICLE_OPTIMISATIONS()
AI_OPTIMISATIONS()
namespace rage {
XPARAM(noptfx);
}
PARAM(vehdepdebug, "Temp param to hunt down why MP specific vehicle dependencies aren't always loaded in MP games");
EXT_PF_VALUE_INT(NumWaterSamplesInMemory);
fwPool<CVehicleStructure>* CVehicleStructure::m_pInfoPool = NULL;
fwPtrListSingleLink CVehicleModelInfo::ms_HDStreamRequests;
s8 CVehicleModelInfo::ms_numHdVehicles = 0;
float CVehicleModelInfo::ms_maxNumberMultiplier = 1.0f;
CVehicleModelInfoVarGlobal * CVehicleModelInfo::ms_VehicleColours = NULL;
fwPsoStoreLoadInstance CVehicleModelInfo::ms_VehicleColoursLoadInst;
CVehicleModelInfoVariation* CVehicleModelInfo::ms_VehicleVariations = NULL;
fwPsoStoreLoadInstance CVehicleModelInfo::ms_VehicleVariationsLoadInst;
CVehicleModColors* CVehicleModelInfo::ms_ModColors = NULL;
fwPsoStoreLoadInstance CVehicleModelInfo::ms_ModColorsLoadInst;
static CVehicleModColorsGen9 s_ModColorsGen9;
atArray<strIndex> CVehicleModelInfo::ms_residentObjects;
int CVehicleModelInfo::ms_commonTxd = -1;
sVehicleDashboardData CVehicleModelInfo::ms_cachedDashboardData;
u32 CVehicleModelInfo::ms_dialsRenderTargetOwner = 0xd1a15c0d;
u32 CVehicleModelInfo::ms_dialsRenderTargetSizeX = 0;
u32 CVehicleModelInfo::ms_dialsRenderTargetSizeY = 0;
u32 CVehicleModelInfo::ms_dialsRenderTargetId = 0;
u32 CVehicleModelInfo::ms_dialsFrameCountReq = 0;
s32 CVehicleModelInfo::ms_dialsMovieId = -1;
s32 CVehicleModelInfo::ms_dialsType = -1;
s32 CVehicleModelInfo::ms_activeDialsMovie = -1;
atFinalHashString CVehicleModelInfo::ms_rtNameHash;
u32 CVehicleModelInfo::ms_lastTrackId = 0xffffffff;
const char* CVehicleModelInfo::ms_lastStationStr = NULL;
u8 CVehicleModelInfo::ms_requestPerFrame = 0;
const char* s_dialsMovieNames[MAX_DIALS_MOVIES] = {"DASHBOARD", "AIRCRAFT_DIALS", "DIALS_LAZER", "DIALS_LAZER_VINTAGE", "PARTY_DASHBOARD"};
atFixedArray<VehicleClassInfo, VC_MAX> CVehicleModelInfo::ms_VehicleClassInfo(VC_MAX);
#if __BANK
bool CVehicleModelInfo::ms_showHDMemUsage = false;
bool CVehicleModelInfo::ms_ListHDAssets = false;
u32 CVehicleModelInfo::ms_HDTotalPhysicalMemory = 0;
u32 CVehicleModelInfo::ms_HDTotalVirtualMemory = 0;
atArray<HDVehilceRefCount> CVehicleModelInfo::ms_HDVehicleInfos;
CUiGadgetWindow* CVehicleModelInfo::ms_pDebugWindow;
CUiGadgetList* CVehicleModelInfo::ms_pHDAssetList;
CUiGadgetList* CVehicleModelInfo::ms_pSelectItemList;
CVehicleModelInfo* CVehicleModelInfo::ms_pSelectedVehicle = NULL;
u32 CVehicleModelInfo::ms_numLoadedInfos = 0;
#endif
class CVehicleMetaDataFileMounter : public CDataFileMountInterface
{
virtual bool LoadDataFile(const CDataFileMgr::DataFile & file)
{
CVehicleModelInfo::LoadVehicleMetaFile(file.m_filename, false, fwFactory::EXTRA);
#if __BANK
CVehicleFactory::UpdateVehicleList();
#endif // __BANK
CTrain::TrainConfigsPostLoad(); // bind carriage names to model IDs
return true;
}
virtual void UnloadDataFile(const CDataFileMgr::DataFile &file)
{
CVehicleModelInfo::UnloadVehicleMetaFile(file.m_filename);
#if __BANK
CVehicleFactory::UpdateVehicleList();
#endif // __BANK
CTrain::TrainConfigsPostLoad(); // bind carriage names to model IDs
}
} g_VehicleMetaDataFileMounter;
class CVehicleVariationDataFileMounter : public CDataFileMountInterface
{
virtual bool LoadDataFile(const CDataFileMgr::DataFile & file)
{
CVehicleModelInfo::AppendVehicleVariations(file.m_filename);
return true;
}
virtual void UnloadDataFile(const CDataFileMgr::DataFile & /*file*/)
{
}
} g_VehicleVariationDataFileMounter;
class CVehicleColorsDataFileMounter : public CDataFileMountInterface
{
virtual bool LoadDataFile(const CDataFileMgr::DataFile & file)
{
CVehicleModelInfo::AppendVehicleColors(file.m_filename);
return true;
}
virtual void UnloadDataFile(const CDataFileMgr::DataFile & /*file*/)
{
}
} g_VehicleColorsDataFileMounter;
//
// Constructor
//
CVehicleStructure::CVehicleStructure()
{
for(int i=0; i<VEH_NUM_NODES; i++)
m_nBoneIndices[i] = -1;
for(int i=0; i<NUM_VEH_CWHEELS_MAX; i++)
{
m_nWheelInstances[i][0] = -1;
m_nWheelInstances[i][1] = -1;
m_fWheelTyreRadius[i] = 1.0f;
m_fWheelTyreWidth[i] = 1.0f;
m_fWheelRimRadius[i] = 1.0f;
m_fWheelScaleInv[i] = 1.0f;
}
m_decalBoneFlags.SetAll();
m_bWheelInstanceSeparateRear = false;
m_numSkinnedWheels = 0xf;
}
CVehicleModelInfo::InitData::InitData() {}
void CVehicleModelInfo::InitClass(unsigned initMode)
{
if(initMode == INIT_AFTER_MAP_LOADED)
{
CDataFileMount::RegisterMountInterface(CDataFileMgr::VEHICLE_METADATA_FILE, &g_VehicleMetaDataFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::VEHICLE_VARIATION_FILE, &g_VehicleVariationDataFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::CARCOLS_FILE, &g_VehicleColorsDataFileMounter);
SetupCommonData();
}
}
void CVehicleModelInfo::ShutdownClass(unsigned shutdownMode)
{
if(shutdownMode == SHUTDOWN_WITH_MAP_LOADED)
{
DeleteCommonData();
}
}
void CVehicleModelInfo::UnloadResidentObjects()
{
bool bUseBurnoutTexture = CGameConfig::Get().UseVehicleBurnoutTexture();
if (bUseBurnoutTexture)
CCustomShaderEffectVehicle::SetupBurnoutTexture(-1);
g_vehicleGlassMan.PrepareVehicleRpfSwitch();
for (int i = 0; i < ms_residentObjects.GetCount(); ++i)
{
strIndex objIndex = ms_residentObjects[i];
strStreamingEngine::GetInfo().ClearRequiredFlag(objIndex, STRFLAG_DONTDELETE);
#if __BANK
// Temporary test code
if (strStreamingEngine::GetInfo().GetStreamingInfoRef(objIndex).GetDependentCount() != 0)
{
Errorf("Shared resource %s still has dependencies", strStreamingEngine::GetObjectName(objIndex));
strStreamingEngine::GetInfo().PrintAllDependents(objIndex);
Assertf(false, "Shared vehicle resource still has dependencies - something didn't get flushed. Check TTY for dependencies.");
}
#endif // __BANK
strStreamingEngine::GetInfo().RemoveObject(objIndex);
}
}
void CVehicleModelInfo::LoadResidentObjects(bool bBlockUntilDone/*=false*/)
{
for (int i = 0; i < ms_residentObjects.GetCount(); ++i)
{
strStreamingEngine::GetInfo().RequestObject(ms_residentObjects[i], STRFLAG_DONTDELETE);
}
if (bBlockUntilDone)
{
CStreaming::LoadAllRequestedObjects();
}
bool bUseBurnoutTexture = CGameConfig::Get().UseVehicleBurnoutTexture();
#if NAVMESH_EXPORT
if(CNavMeshDataExporter::WillExportCollision()) bUseBurnoutTexture = false;
#endif
g_vehicleGlassMan.CompleteVehicleRpfSwitch();
// assume first resident object is the common texture dictionary
if (bUseBurnoutTexture)
{
CCustomShaderEffectVehicle::SetupBurnoutTexture(ms_commonTxd);
}
}
//
// name: SetupCommonData
// description: Setup data common to all vehicle modelinfo structures
// (eg Common textures, Vehicle colours)
void CVehicleModelInfo::SetupCommonData()
{
CVehicleModelInfoVarGlobal::Init();
#if VEHICLE_SUPPORT_PAINT_RAMP
CCustomShaderEffectVehicle::LoadRampTxd();
#endif
// load colours for vehicles
LoadVehicleColours();
// load permanently resident texture dictionaries
if (CFileMgr::IsGameInstalled())
{
LoadResidentObjects(true);
}
// initialise instanced wheel render
CWheelInstanceRenderer::Initialise();
// allocate space for info pool
const char *poolName = "VehicleStruct";
int iMaxVehicleStructPoolSize = fwConfigManager::GetInstance().GetSizeOfPool(atStringHash(poolName), CONFIGURED_FROM_FILE );
#if NAVMESH_EXPORT
// For exporting navmeshes we need to load in all the vehicles - gameconfig.xml defined pool size *8 should be enough
iMaxVehicleStructPoolSize = CNavMeshDataExporter::WillExportCollision() ? iMaxVehicleStructPoolSize*8 : iMaxVehicleStructPoolSize;
#endif
#if COMMERCE_CONTAINER
CVehicleStructure::m_pInfoPool = rage_new fwPool<CVehicleStructure>(
iMaxVehicleStructPoolSize,
0.35f,
poolName,
0,
sizeof(CVehicleStructure),
false);
#else
CVehicleStructure::m_pInfoPool = rage_new fwPool<CVehicleStructure>(
iMaxVehicleStructPoolSize,
0.35f,
poolName);
#endif
}
//
// name: DeleteCommonData
// description: Delete common data
void CVehicleModelInfo::DeleteCommonData()
{
// close instanced wheel render
CWheelInstanceRenderer::Shutdown();
delete CVehicleStructure::m_pInfoPool;
UnloadResidentObjects();
ms_residentObjects.ResetCount();
#if VEHICLE_SUPPORT_PAINT_RAMP
CCustomShaderEffectVehicle::UnloadRampTxd();
#endif
}
/*
//
// name: LoadVehicleUpgrades
// description: Loads the vehicle upgrade file
void CVehicleModelInfo::LoadVehicleUpgrades()
{
enum ModLoadingState {
LOADING_NOTHING,
LOADING_LINKS,
LOADING_MODS,
LOADING_WHEELS
};
ModLoadingState loadingState = LOADING_NOTHING;
char* pLine;
const char whitespace[] = " \t,";
FileHandle fd;
char* pToken;
s32 i;
fd = CFileMgr::OpenFile("DATA\\CARMODS.DAT");
modelinfoAssertf(CFileMgr::IsValidFileHandle(fd), "problem loading carmods.dat");
// initialise wheel upgrades
for(i=0; i<NUM_WHEEL_UPGRADE_CLASSES; i++)
ms_numWheelUpgrades[i] = 0;
// get each line. Each line is a model file
while((pLine = CFileMgr::ReadLine(fd)) != NULL)
{
if(pLine[0] == '#' || pLine[0] == '\0')
continue;
if(loadingState == LOADING_NOTHING)
{
if(!strncmp("link", pLine, 4))
loadingState = LOADING_LINKS;
else if(!strncmp("mods", pLine, 4))
loadingState = LOADING_MODS;
else if(!strncmp("wheel", pLine, 5))
loadingState = LOADING_WHEELS;
}
else
{
if(!strncmp("end", pLine, 3))
loadingState = LOADING_NOTHING;
switch(loadingState)
{
case LOADING_MODS:
pToken = strtok(pLine, whitespace); // get first token
if(pToken)
{
s32 index=-1;
i=0;
CBaseModelInfo* pModelInfo = CModelInfo::GetBaseModelInfo(pToken, &index);
modelinfoAssertf(pModelInfo, "%s:Unrecognised model in carmods.dat", pToken);
modelinfoAssertf(pModelInfo->GetModelType() == MI_TYPE_VEHICLE, "%s:Unrecognised car model in carmods.dat", pToken);
while((pToken = strtok(NULL, whitespace)) != NULL)
{
modelinfoAssertf(i<MAX_POSSIBLE_UPGRADES, "%s:Vehicle has too many upgrades", pModelInfo->GetModelName());
CBaseModelInfo* pUpgradeInfo = CModelInfo::GetBaseModelInfo(pToken, &index);
modelinfoAssertf(pUpgradeInfo, "%s:Unrecognised model in carmods.dat", pToken);
// setup upgrade and store in vehicle modelinfo
pUpgradeInfo->SetupVehicleUpgradeFlags(pToken);
((CVehicleModelInfo*)pModelInfo)->SetVehicleUpgrade(i++, index);
}
// setup hydraulics, stereo and nitro upgrades
CBaseModelInfo* pUpgradeInfo = CModelInfo::GetBaseModelInfo("hydralics", &index);
pUpgradeInfo->SetupVehicleUpgradeFlags("hydralics");
((CVehicleModelInfo*)pModelInfo)->SetVehicleUpgrade(i++, index);
pUpgradeInfo = CModelInfo::GetBaseModelInfo("stereo", &index);
pUpgradeInfo->SetupVehicleUpgradeFlags("stereo");
((CVehicleModelInfo*)pModelInfo)->SetVehicleUpgrade(i++, index);
}
break;
case LOADING_LINKS:
{
s32 id1=-1, id2=-1;
pToken = strtok(pLine, whitespace); // get first token
if(pToken)
{
// Get first model
CBaseModelInfo* pUpgradeInfo = CModelInfo::GetBaseModelInfo(pToken, &id1);
modelinfoAssertf(id1 != -1, "%s:Unrecognised model in carmods.dat", pToken);
pUpgradeInfo->SetupVehicleUpgradeFlags(pToken);
pToken = strtok(NULL, whitespace); // get second token
modelinfoAssertf(pToken, "Corrupt carmods.dat file");
// Get second model
pUpgradeInfo = CModelInfo::GetBaseModelInfo(pToken, &id2);
modelinfoAssertf(id2 != -1, "%s:Unrecognised model in carmods.dat", pToken);
pUpgradeInfo->SetupVehicleUpgradeFlags(pToken);
// store link
ms_linkedUpgrades.AddUpgradeLink(static_cast<s16>(id1), static_cast<s16>(id2));
}
}
break;
case LOADING_WHEELS:
{
s32 wheelClass;
s32 index=-1;
sscanf(pLine, "%d", &wheelClass);
pToken = strtok(pLine, whitespace); // get first token
while((pToken = strtok(NULL, whitespace)) != NULL)
{
CBaseModelInfo* pWheelInfo = CModelInfo::GetBaseModelInfo(pToken, &index);
modelinfoAssertf(pWheelInfo, "%s:Unrecognised wheel object", pToken);
pWheelInfo->SetupVehicleUpgradeFlags(pToken);
AddWheelUpgrade(wheelClass, index);
}
}
break;
default:
break;
}
}
}
modelinfoAssertf(loadingState == LOADING_NOTHING, "Corrupt carmods.dat file");
CFileMgr::CloseFile(fd);
}
*/
#if !__PS3
float CVehicleModelInfo::ms_LodMultiplierBias = 0.0f;
void CVehicleModelInfo::SetLodMultiplierBias( float multiplierBias )
{
ms_LodMultiplierBias = multiplierBias;
}
#endif
//
// Load vehicle colour data from xml
//
void CVehicleModelInfo::LoadVehicleColours()
{
fwPsoStoreLoader colLoader = fwPsoStoreLoader::MakeSimpleFileLoader<CVehicleModelInfoVarGlobal>();
colLoader.Unload(ms_VehicleColoursLoadInst);
colLoader.Load("platform:/data/carcols." META_FILE_EXT, ms_VehicleColoursLoadInst);
ms_VehicleColours = reinterpret_cast<CVehicleModelInfoVarGlobal*>(ms_VehicleColoursLoadInst.GetInstance());
ms_VehicleColours->OnPostLoad(BANK_ONLY(RS_ASSETS "/titleupdate/dev_ng/data/carcols.pso.meta"));
modelinfoFatalAssertf(ms_VehicleColours, "Error loading carcols." META_FILE_EXT);
if (ms_VehicleColours)
parRestRegisterSingleton("Vehicles/carcols", *ms_VehicleColours, "platform:/data/carcols." META_FILE_EXT); // Make this object browsable / editable via rest services (i.e. the web)
#if __ASSERT
if(1)
{
const int kitCount = ms_VehicleColours->m_Kits.GetCount();
for(int k=0; k<kitCount; k++)
{
CVehicleKit& kit = ms_VehicleColours->m_Kits[k];
// check vehicle modkits for duplicate drawables:
const atArray<CVehicleModVisible>& kitVisibleMods = kit.GetVisibleMods();
const int modsCount = kitVisibleMods.GetCount();
for(int m=0; m<modsCount; m++)
{
const u32 modModelHash = kitVisibleMods[m].GetNameHash();
if(modModelHash != MID_DUMMY_MOD_FRAG_MODEL) // skip this check for "dummy_mod_frag_model"
{
if (kitVisibleMods[m].GetType() == VMT_INVALID)
{
Assertf(false, "Vehicle modkit %s has drawable %s with type VMT_INVALID. Maybe there is a spelling mistake in the <type> field for this drawable in carcols.pso.meta. This is a vehicle art error.",
kit.GetNameHashString().GetCStr(), kitVisibleMods[m].GetNameHashString().GetCStr() );
Warningf("Vehicle modkit %s has drawable %s with type VMT_INVALID. Maybe there is a spelling mistake in the <type> field for this drawable in carcols.pso.meta. This is a vehicle art error.",
kit.GetNameHashString().GetCStr(), kitVisibleMods[m].GetNameHashString().GetCStr() );
}
// check if this drawable is not shared with any other mod in this kit:
for(int i=0; i<modsCount; i++)
{
if(i != m)
{
if(kitVisibleMods[i].GetNameHash() == modModelHash)
{ // found shared drawable (not supported):
Assertf(false, "Vehicle modkit %s uses the same drawable %s for 2 different mods! The indices are %d and %d. This is a vehicle art error.", kit.GetNameHashString().GetCStr(), kitVisibleMods[m].GetNameHashString().GetCStr(), m, i);
Warningf("Vehicle modkit %s uses the same drawable %s for 2 different mods! The indices are %d and %d. This is a vehicle art error.", kit.GetNameHashString().GetCStr(), kitVisibleMods[m].GetNameHashString().GetCStr(), m, i);
}
}
}
}
}
// check to make sure the number of horns is correct
ValidateHorns(kit);
}
}
#endif //__ASSERT...
#if __BANK
static bool bEnableSave=false;
if(bEnableSave)
{
PARSER.SaveObject(RS_ASSETS "/export/data/carcols.pso.meta", "", ms_VehicleColours);
}
ms_VehicleColours->AddCarColFile(RS_ASSETS "/titleupdate/dev_ng/data/carcols.pso.meta");
#endif // __BANK
fwPsoStoreLoader varLoader = fwPsoStoreLoader::MakeSimpleFileLoader<CVehicleModelInfoVariation>();
varLoader.Unload(ms_VehicleVariationsLoadInst);
varLoader.Load("platform:/data/carvariations." META_FILE_EXT, ms_VehicleVariationsLoadInst);
ms_VehicleVariations = reinterpret_cast<CVehicleModelInfoVariation*>(ms_VehicleVariationsLoadInst.GetInstance());
modelinfoFatalAssertf(ms_VehicleVariations, "Error loading platform:/data/carvariations." META_FILE_EXT);
ms_VehicleVariations->OnPostLoad();
fwPsoStoreLoader modLoader = fwPsoStoreLoader::MakeSimpleFileLoader<CVehicleModColors>();
modLoader.Unload(ms_ModColorsLoadInst);
modLoader.Load("platform:/data/carmodcols." META_FILE_EXT, ms_ModColorsLoadInst);
ms_ModColors = reinterpret_cast<CVehicleModColors*>(ms_ModColorsLoadInst.GetInstance());
modelinfoFatalAssertf(ms_ModColors, "Error loading platform:/data/carmodcols." META_FILE_EXT);
if (ms_ModColors)
parRestRegisterSingleton("Vehicles/carmodcols", *ms_ModColors, "platform:/data/carmodcols." META_FILE_EXT);
#if !__FINAL
// Create default no siren if it doesn't exist.
if( ms_VehicleColours->m_Sirens.GetCount() == 0 )
{
sirenSettings & Siren = ms_VehicleColours->m_Sirens.Grow();
Siren.name = StringDuplicate("0 NoSirens");
}
#endif // !__FINAL
// Copy the loaded colors into the vehicle model list
const int numCols = ms_VehicleColours->GetColorCount();
for(int i=0;i<ms_VehicleVariations->GetVariationData().GetCount();i++)
{
const CVehicleVariationData& varData = ms_VehicleVariations->GetVariationData()[i];
CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfoFromName(varData.GetModelName(), NULL);
artAssertf(pModelInfo, "carvariations.pso.meta is referencing a vehicle (%s) that doesn't exist", varData.GetModelName());
if( pModelInfo )
{
pModelInfo->UpdateModelColors(varData, numCols);
pModelInfo->UpdateModKits(varData);
pModelInfo->UpdateWindowsWithExposedEdges(varData);
pModelInfo->UpdatePlateProbabilities(varData);
}
}
#if !ENABLE_VEHICLECOLOURTEXT
// clean up the color names if not needed (Russ is looking to expand the parser so we can selectively not load data in certain configurations, when this happens change this)
for(int i=0;i<numCols;i++)
{
StringFree(ms_VehicleColours->m_Colors[i].m_colorName);
ms_VehicleColours->m_Colors[i].m_colorName = NULL;
}
#endif
#if __BANK
// Update the vehiclecolours data with any vehicles which have been added since we last saved
// this is a n^2 search - may need optimizing for speed should the vehicle list become substantial (is beta only though)
fwArchetypeDynamicFactory<CVehicleModelInfo>& vehModelInfoStore = CModelInfo::GetVehicleModelInfoStore();
atArray<CVehicleModelInfo*> vehicleTypes;
vehModelInfoStore.GatherPtrs(vehicleTypes);
const u32 numVehicles = vehicleTypes.GetCount();
const int numXmlVehiclesAtStart = ms_VehicleVariations->GetVariationData().GetCount();
atBitSet bUsedXmlEntries(numXmlVehiclesAtStart);
for(u32 i=0;i<numVehicles;i++)
{
CVehicleModelInfo& modelInfo = *vehicleTypes[i];
const char* modelName = modelInfo.GetModelName();
bool foundIt = false;
for(int j=0;j<numXmlVehiclesAtStart;j++)
if (bUsedXmlEntries.IsClear(j) && stricmp(ms_VehicleVariations->GetVariationData()[j].GetModelName(), modelName) == 0)
{
foundIt = true;
bUsedXmlEntries.Set(j);
break;
}
if (!foundIt)
{
CVehicleVariationData& varData = ms_VehicleVariations->GetVariationData().Grow();
varData.SetModelName(modelInfo.GetModelName());
const u32 numColors = modelInfo.GetNumPossibleColours();
varData.ReserveColors(numColors);
for(u32 j=0;j<numColors;j++)
{
CVehicleModelColorIndices& indices = varData.AppendColor();
indices.m_indices[0] = modelInfo.GetPossibleColours(0, j);
indices.m_indices[1] = modelInfo.GetPossibleColours(1, j);
indices.m_indices[2] = modelInfo.GetPossibleColours(2, j);
indices.m_indices[3] = modelInfo.GetPossibleColours(3, j);
indices.m_indices[4] = modelInfo.GetPossibleColours(4, j);
indices.m_indices[5] = modelInfo.GetPossibleColours(5, j);
}
varData.SetLightSettings(modelInfo.GetLightSettingsId());
varData.SetSirenSettings(modelInfo.GetSirenSettingsId());
}
}
for(int j=0, deleteIdx=0;j<numXmlVehiclesAtStart;j++)
if (bUsedXmlEntries.IsClear(j))
ms_VehicleVariations->GetVariationData().Delete(deleteIdx);
else
deleteIdx++;
#else // __BANK
// We no longer need the model color data (it has been copied into the ModelInfo classes and we are not editing)
// Free up the memory!
ms_VehicleVariations->GetVariationData().Reset();
#endif // !__BANK
}
void CVehicleModelInfo::LoadVehicleMetaFile(const char *filename, bool permanent, s32 mapTypeDefIndex)
{
struct InitDataList* pInitDataList = NULL;
parStructure* structure = pInitDataList->parser_GetStaticStructure();
parSettings settings = PARSER.Settings();
settings.SetFlag(parSettings::CULL_OTHER_PLATFORM_DATA,true);
if(PARSER.CreateAndLoadAnyType(filename, "meta", structure, reinterpret_cast<parPtrToStructure&>(pInitDataList),&settings))
{
// add slot for resident txd
strLocalIndex txtSlot = g_TxdStore.FindSlot(pInitDataList->m_residentTxd.c_str());
if (txtSlot == -1)
{
txtSlot = g_TxdStore.AddSlot(pInitDataList->m_residentTxd.c_str());
}
SetupHDSharedTxd(pInitDataList->m_residentTxd.c_str());
modelinfoAssertf((ms_commonTxd == -1) || (txtSlot == ms_commonTxd), "Don't remap the common txd - existing vehicles will break");
//@@: location CVEHICLEMODELINFO_LOADVEHICLEMETAFILE
SetCommonTxd(txtSlot.Get());
AddResidentObject(txtSlot, g_TxdStore.GetStreamingModuleId());
// add slots for resident anims
for(int i=0; i<pInitDataList->m_residentAnims.GetCount(); i++)
{
strLocalIndex slot = g_ClipDictionaryStore.FindSlot(pInitDataList->m_residentAnims[i].c_str());
if (slot == -1)
{
slot = g_ClipDictionaryStore.AddSlot(pInitDataList->m_residentAnims[i].c_str());
}
AddResidentObject(slot, g_ClipDictionaryStore.GetStreamingModuleId());
}
u32 maxCountAsFacebookDriven = 0;
// load model infos
if (CFileMgr::IsGameInstalled())
{
for (s32 i = 0; i < pInitDataList->m_InitDatas.GetCount(); ++i)
{
fwModelId id;
if(!CModelInfo::GetBaseModelInfoFromName(pInitDataList->m_InitDatas[i].m_modelName.c_str(), &id))
{
CVehicleModelInfo* pModelInfo = CModelInfo::AddVehicleModel(pInitDataList->m_InitDatas[i].m_modelName.c_str(), permanent, mapTypeDefIndex);
GAMETOOL_ONLY(if (pModelInfo))
{
pModelInfo->Init(pInitDataList->m_InitDatas[i]);
#if __BANK
pModelInfo->SetVehicleDLCPack(filename);
#endif //__BANK
if (!permanent)
{
CModelInfo::PostLoad(pModelInfo);
}
if (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_COUNT_AS_FACEBOOK_DRIVEN))
{
++maxCountAsFacebookDriven;
}
}
}
else
{
Warningf("CVehicleModelInfo::LoadVehicleMetaFile Model info for vehicle '%s' already exists!", pInitDataList->m_InitDatas[i].m_modelName.c_str());
}
}
}
CStatsMgr::SetMaxCountAsFacebookDriven(maxCountAsFacebookDriven);
strLocalIndex vehiclePatchSlot = g_TxdStore.FindSlot("vehshare_tire");
if (vehiclePatchSlot != -1)
{
g_TxdStore.SetParentTxdSlot(vehiclePatchSlot,strLocalIndex(GetCommonTxd()));
}
// load txd relationships
for (s32 i = 0; i < pInitDataList->m_txdRelationships.GetCount(); ++i)
{
{
strLocalIndex txdSlot = strLocalIndex(g_TxdStore.FindSlot(pInitDataList->m_txdRelationships[i].m_child.c_str()));
if(txdSlot == -1)
{
txdSlot = g_TxdStore.AddSlot(pInitDataList->m_txdRelationships[i].m_child.c_str());
}
strLocalIndex txdParentSlot = strLocalIndex(g_TxdStore.FindSlot(pInitDataList->m_txdRelationships[i].m_parent.c_str()));
if (vehiclePatchSlot != -1 && txdParentSlot == GetCommonTxd() )
{
txdParentSlot = vehiclePatchSlot;
}
else if(txdParentSlot == -1)
{
{
txdParentSlot = g_TxdStore.AddSlot(pInitDataList->m_txdRelationships[i].m_parent.c_str());
}
}
g_TxdStore.SetParentTxdSlot(txdSlot,txdParentSlot);
}
}
}
delete pInitDataList;
// some shared .txds for vehicles aren't being set as upgradeable through data...
static bool bSetUpVehShare_Truck = true;
if (bSetUpVehShare_Truck)
{
SetupHDSharedTxd("vehshare_truck");
bSetUpVehShare_Truck = false;
}
}
void CVehicleModelInfo::UnloadVehicleMetaFile(const char *filename)
{
struct InitDataList* pInitDataList = NULL;
if (PARSER.LoadObjectPtr(filename, "meta", pInitDataList))
{
// unload model infos
for (s32 i = 0; i < pInitDataList->m_InitDatas.GetCount(); ++i)
{
UnloadModelInfoAssets(pInitDataList->m_InitDatas[i].m_modelName.c_str());
}
}
delete pInitDataList;
}
void CVehicleModelInfo::UnloadModelInfoAssets(const char *name)
{
fwModelId modelId = CModelInfo::GetModelIdFromName(name);
Assert(modelId.IsValid());
if(CVehicleModelInfo* vmi = static_cast<CVehicleModelInfo*>(CModelInfo::GetBaseModelInfo(modelId)))
{
for(int i = vmi->GetNumVehicleInstances() - 1; i >= 0; i--)
{
CVehicle* veh = vmi->GetVehicleInstance(i);
if(veh)
{
#if !__NO_OUTPUT
Errorf("Removing vehicle model info '%s'. Found instance of still alive vehicle Veh ptr index = %d 0x%p - (%.2f, %.2f, %.2f).", vmi->GetModelName(), i,
veh, veh->GetTransform().GetPosition().GetXf(), veh->GetTransform().GetPosition().GetYf(), veh->GetTransform().GetPosition().GetZf());
#endif // !__NO_OUTPUT
g_ptFxManager.RemovePtFxFromEntity(veh);
CVehicleFactory::GetFactory()->Destroy(veh);
}
}
CModelInfo::RemoveAssets(modelId);
#if __BANK
CVehicleFactory::UpdateVehicleList();
#endif // __BANK
}
else
{
Errorf("CVehicleModelInfo::UnloadModelInfo can't find model info for '%s'", name);
}
}
void CVehicleModelInfo::AppendVehicleVariations(const char *filename)
{
const int numCols = ms_VehicleColours->GetColorCount();
CVehicleModelInfoVariation* pNewVehicleVariations = NULL;
if (PARSER.LoadObjectPtr(filename, "meta", pNewVehicleVariations))
{
pNewVehicleVariations->OnPostLoad();
for(int i = 0; i < pNewVehicleVariations->GetVariationData().GetCount(); i++)
{
const CVehicleVariationData& varData = pNewVehicleVariations->GetVariationData()[i];
CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfoFromName(varData.GetModelName(), NULL);
artAssertf(pModelInfo, "%s is referencing a vehicle (%s) that doesn't exist", filename, varData.GetModelName());
if( pModelInfo )
{
pModelInfo->UpdateModelColors(varData, numCols);
pModelInfo->UpdateModKits(varData);
pModelInfo->UpdateWindowsWithExposedEdges(varData);
pModelInfo->UpdatePlateProbabilities(varData);
#if __BANK
ms_VehicleVariations->GetVariationData().PushAndGrow(varData);
#endif // __BANK
}
}
}
delete pNewVehicleVariations;
}
void CVehicleModelInfo::AppendVehicleColors(const char *filename)
{
CVehicleModelInfoVarGlobal pNewVehicleColors ;
if (PARSER.LoadObject(filename, "meta", pNewVehicleColors))
{
Assertf(pNewVehicleColors.m_Colors.GetCount() == 0,
"%s has new colors new colors currently not supported by DLC code!", filename);
Assertf(pNewVehicleColors.m_WindowColors.GetCount() == 0,
"%s has new window colors new window colors currently not supported by DLC code!", filename);
Assertf(pNewVehicleColors.m_MetallicSettings.GetCount() == 0,
"%s has new metallic settings metallic settings can't be extended!", filename);
int kitsStart = ms_VehicleColours->m_Kits.GetCount();
int lightsStart = ms_VehicleColours->m_Lights.GetCount();
int sirensStart = ms_VehicleColours->m_Sirens.GetCount();
int nAddedKits = AppendArray(ms_VehicleColours->m_Kits, pNewVehicleColors.m_Kits, INVALID_VEHICLE_KIT_ID);
int nAddedLights = AppendArray(ms_VehicleColours->m_Lights, pNewVehicleColors.m_Lights, INVALID_VEHICLE_LIGHT_SETTINGS_ID);
int nAddedSirens = AppendArray(ms_VehicleColours->m_Sirens, pNewVehicleColors.m_Sirens, INVALID_SIREN_SETTINGS_ID);
ms_VehicleColours->UpdateKitIds(kitsStart, nAddedKits BANK_ONLY(, filename));
ms_VehicleColours->UpdateLightIds(lightsStart, nAddedLights BANK_ONLY(, filename));
ms_VehicleColours->UpdateSirenIds(sirensStart, nAddedSirens BANK_ONLY(, filename));
for(int i = 0; i < VWT_MAX; i++)
{
int wheelsStart = ms_VehicleColours->m_Wheels[i].GetCount();
int nAddedWheels = AppendArray(ms_VehicleColours->m_Wheels[i], pNewVehicleColors.m_Wheels[i], INVALID_VEHICLE_WHEEL_ID);
ms_VehicleColours->UpdateWheelIds((eVehicleWheelType)i, wheelsStart, nAddedWheels BANK_ONLY(, filename));
}
#if __BANK
const int newKitCount = pNewVehicleColors.m_Kits.GetCount();
for(int k = 0; k < newKitCount; k++)
{
ValidateHorns(pNewVehicleColors.m_Kits[k]);
}
ms_VehicleColours->AddCarColFile(filename);
#endif // __BANK
}
}
void CVehicleModelInfo::AppendVehicleColorsGen9(const char *filename)
{
CVehicleModelColorsGen9 colsGen9;
if (PARSER.LoadObject(filename, "meta", colsGen9))
{
u32 colNum = colsGen9.m_Colors.GetCount();
for (u32 i = 0; i < colNum; ++i)
{
CVehicleModelColorGen9& c = ms_VehicleColours->GetColors().Grow();
c = colsGen9.m_Colors[i];
}
}
}
CVehicleModColorsGen9* CVehicleModelInfo::GetVehicleModColorsGen9() { return &s_ModColorsGen9; }
void CVehicleModelInfo::LoadVehicleModColorsGen9(const char *filename)
{
PARSER.LoadObject(filename, "meta", s_ModColorsGen9);
}
#if __BANK
void CVehicleModelInfo::AddWidgets(bkBank & b)
{
b.PushGroup("Vehicle Colours", false);
if (ms_VehicleColours)
ms_VehicleColours->AddWidgets(b);
b.PopGroup();
b.PushGroup("Vehicle License Plates", false);
if(ms_VehicleColours)
{
b.AddTitle( "NOTE: License plate settings have been moved so they are grouped with the Car Colors. The save button has been added here"
"as well for convenience. Please keep in mind that saving here will also save any changes to the car color palettes.");
b.AddTitle("NOTE: This will NOT save the colors assigned to each individual car.");
b.AddButton("Save", CFA(CVehicleModelInfo::SaveVehicleColours));
ms_VehicleColours->GetLicensePlateData().AddWidgets(b);
}
b.PopGroup();
b.PushGroup("Vehicle Variations", false);
if (ms_VehicleVariations)
{
ms_VehicleVariations->AddWidgets(b);
b.AddButton("Save", CFA(CVehicleModelInfo::SaveVehicleVariations));
}
b.PopGroup();
b.AddButton("Reload Meta Data", CFA(CVehicleModelInfo::ReloadColourMetadata));
b.AddButton("Dump instance data", DumpVehicleInstanceDataCB);
b.AddButton("Dump vehicle model infos", CFA(CModelInfo::DumpVehicleModelInfos));
b.AddButton("Dump average vehicle size", CFA(CModelInfo::DumpAverageVehicleSize));
}
void CVehicleModelInfo::AddHDWidgets(bkBank &bank)
{
bank.PushGroup("Vehicles", false);
bank.AddToggle("Show HD Vehicle Memory Usage", &ms_showHDMemUsage);
bank.AddToggle("List HD Assets", &ms_ListHDAssets);
bank.AddButton("Show select Asset's TXD in Texture Viewer", &ShowSelectTxdInTexViewCB);
bank.PopGroup();
}
void CVehicleModelInfo::DebugUpdate()
{
if (PARAM_vehdepdebug.Get())
CModelInfo::CheckVehicleAssetDependencies();
if (ms_showHDMemUsage)
{
GetHDMemoryUsage(ms_HDTotalPhysicalMemory, ms_HDTotalVirtualMemory);
grcDebugDraw::TextFontPush(grcSetup::GetMiniFixedWidthFont());
const atVarString temp("HD Vehicle Memory Usage - Physical %.2fMB, Virtual %.2fMB",
float((double)ms_HDTotalPhysicalMemory / (1024.0 * 1024.0)),
float((double)ms_HDTotalVirtualMemory / (1024.0 * 1024.0)));
grcDebugDraw::Text(Vector2(270.0f, 600.0f), DD_ePCS_Pixels, CRGBA_White(), temp, true, 1.0f, 1.0f);
grcDebugDraw::TextFontPop();
}
if (ms_ListHDAssets)
{
static s32 currentSelectionSlot = -1;
if (ms_pDebugWindow == NULL)
{
// this creates a window
CUiColorScheme colorScheme;
ms_pDebugWindow = rage_new CUiGadgetWindow(40.0f, 40.0f, 620.0f, 180.0f, colorScheme);
ms_pDebugWindow->SetTitle("Active HD Vehicles");
// this attaches a scrolling list to the window
float afColumnOffsets[] = { 0.0f, 80.0f, 200.0f };
const char* columnTitles1[] = { "Model", "HD Physical Size", "HD Virtual Size" };
ms_pHDAssetList = rage_new CUiGadgetList(42.0f, 78.0f, 600.0f, 8, 3, afColumnOffsets, colorScheme, columnTitles1);
ms_pDebugWindow->AttachChild(ms_pHDAssetList);
ms_pHDAssetList->SetUpdateCellCB(UpdateCellForAsset);
// this attaches a scrolly list to the window
float afColumnOffsets2[] = { 0.0f, 100.0f, 200.0f, 400.0f };
const char* columnTitles2[] = { "Asset Name", "Type", "HD Physical Size", "HD Virtual Size" };
ms_pSelectItemList = rage_new CUiGadgetList(42.0f, 190.0f, 600.0f, 2, 4, afColumnOffsets2, colorScheme, columnTitles2);
ms_pDebugWindow->AttachChild(ms_pSelectItemList);
ms_pSelectItemList->SetUpdateCellCB(UpdateCellForItem);
ms_pSelectItemList->SetSelectorEnabled(false);
g_UiGadgetRoot.AttachChild(ms_pDebugWindow);
currentSelectionSlot = -1;
}
if (ms_pHDAssetList->UserProcessClick())
{
currentSelectionSlot = ms_pHDAssetList->GetCurrentIndex(); // user clicked, so pick up new slot
}
if (currentSelectionSlot >= ms_HDVehicleInfos.GetCount())
{
currentSelectionSlot = ms_HDVehicleInfos.GetCount() - 1;
}
ms_pSelectedVehicle = ms_HDVehicleInfos.GetCount() && currentSelectionSlot >= 0 ? ms_HDVehicleInfos[currentSelectionSlot].pVMI : NULL;
ms_pHDAssetList->SetNumEntries(ms_HDVehicleInfos.GetCount()); // this invalidates current index in window!
if (currentSelectionSlot != -1)
{
ms_pHDAssetList->SetCurrentIndex(currentSelectionSlot);
}
if (ms_pSelectedVehicle)
{
ms_pSelectItemList->SetNumEntries(2);
}
}
else
{
if (ms_pDebugWindow)
{
ms_pSelectItemList->DetachFromParent();
ms_pHDAssetList->DetachFromParent();
ms_pDebugWindow->DetachFromParent();
}
delete ms_pSelectItemList;
ms_pSelectItemList = NULL;
delete ms_pHDAssetList;
ms_pHDAssetList = NULL;
delete ms_pDebugWindow;
ms_pDebugWindow = NULL;
}
}
void CVehicleModelInfo::UpdateCellForAsset(CUiGadgetText* pResult, u32 row, u32 column, void * UNUSED_PARAM( extraCallbackData ) )
{
if (row >= ms_HDVehicleInfos.GetCount())
{
return;
}
CVehicleModelInfo *pVI = ms_HDVehicleInfos[row].pVMI;
if (column == 0)
{
pResult->SetString(pVI->GetModelName());
}
else if (column == 1)
{
strStreamingInfoManager &infoMgr = strStreamingEngine::GetInfo();
strIndex strFragIndex = g_FragmentStore.GetStreamingIndex(strLocalIndex(pVI->m_HDfragIdx));
strIndex strTexIndex = g_TxdStore.GetStreamingIndex(strLocalIndex(pVI->m_HDtxdIdx));
strStreamingInfo& fragInfo = infoMgr.GetStreamingInfoRef(strFragIndex);
strStreamingInfo& texInfo = infoMgr.GetStreamingInfoRef(strTexIndex);
u32 totalPhysicalMemory = fragInfo.ComputePhysicalSize(strFragIndex) + texInfo.ComputePhysicalSize(strTexIndex);
char sizeInK[16];
snprintf(sizeInK, 16, "%dK", int((double)totalPhysicalMemory / 1024.0));
sizeInK[15] = '\0';
pResult->SetString(sizeInK);
}
else //if (column == 2)
{
strIndex strFragIndex = g_FragmentStore.GetStreamingIndex(strLocalIndex(pVI->m_HDfragIdx));
strIndex strTexIndex = g_TxdStore.GetStreamingIndex(strLocalIndex(pVI->m_HDtxdIdx));
strStreamingInfo& fragInfo = strStreamingEngine::GetInfo().GetStreamingInfoRef(strFragIndex);
strStreamingInfo& texInfo = strStreamingEngine::GetInfo().GetStreamingInfoRef(strTexIndex);
u32 totalVirtualMemory = fragInfo.ComputeVirtualSize(strFragIndex) + texInfo.ComputeVirtualSize(strTexIndex);
char sizeInK[16];
snprintf(sizeInK, 16, "%dK", int((double)totalVirtualMemory / 1024.0));
sizeInK[15] = '\0';
pResult->SetString(sizeInK);
}
}
void CVehicleModelInfo::UpdateCellForItem(CUiGadgetText* pResult, u32 row, u32 column, void * UNUSED_PARAM(extraCallbackData) )
{
if (ms_pSelectedVehicle == NULL)
{
return;
}
if (column == 0)
{
if (row == 0)
{
pResult->SetString(g_FragmentStore.GetName(strLocalIndex(ms_pSelectedVehicle->m_HDfragIdx)));
}
else
{
pResult->SetString(g_TxdStore.GetName(strLocalIndex(ms_pSelectedVehicle->m_HDtxdIdx)));
}
}
else if (column == 1)
{
if (row == 0)
{
pResult->SetString("FRG");
}
else
{
pResult->SetString("TXD");
}
}
else if (column == 2 || column == 3)
{
strStreamingInfoManager &infoMgr = strStreamingEngine::GetInfo();
if (row == 0)
{
strIndex strFragIndex = g_FragmentStore.GetStreamingIndex(strLocalIndex(ms_pSelectedVehicle->m_HDfragIdx));
strStreamingInfo& fragInfo = infoMgr.GetStreamingInfoRef(strFragIndex);
u32 size = column == 2 ? fragInfo.ComputePhysicalSize(strFragIndex) : fragInfo.ComputeVirtualSize(strFragIndex);
char sizeInK[16];
snprintf(sizeInK, 16, "%dK", int((double)size / 1024.0));
sizeInK[15] = '\0';
pResult->SetString(sizeInK);
}
else
{
strIndex strTexIndex = g_TxdStore.GetStreamingIndex(strLocalIndex(ms_pSelectedVehicle->m_HDtxdIdx));
strStreamingInfo& texInfo = infoMgr.GetStreamingInfoRef(strTexIndex);
u32 size = column == 2 ? texInfo.ComputePhysicalSize(strTexIndex) : texInfo.ComputeVirtualSize(strTexIndex);
char sizeInK[16];
snprintf(sizeInK, 16, "%dK", int((double)size / 1024.0));
sizeInK[15] = '\0';
pResult->SetString(sizeInK);
}
}
}
void CVehicleModelInfo::ShowSelectTxdInTexViewCB()
{
if (ms_pSelectedVehicle)
{
CTxdRef ref(AST_TxdStore, ms_pSelectedVehicle->m_HDtxdIdx, INDEX_NONE, "");
CDebugTextureViewerInterface::SelectTxd(ref, false, true);
}
}
void CVehicleModelInfo::GetHDMemoryUsage(u32 &physicalMem, u32 &virtualMem)
{
physicalMem = 0;
virtualMem = 0;
for (int i = 0; i < ms_HDVehicleInfos.GetCount(); ++i)
{
CVehicleModelInfo *pInfo = ms_HDVehicleInfos[i].pVMI;
strStreamingInfoManager &infoMgr = strStreamingEngine::GetInfo();
strIndex strFragIndex = g_FragmentStore.GetStreamingIndex(strLocalIndex(pInfo->m_HDfragIdx));
strStreamingInfo& fragInfo = infoMgr.GetStreamingInfoRef(strFragIndex);
physicalMem += fragInfo.ComputePhysicalSize(strFragIndex);
virtualMem += fragInfo.ComputeVirtualSize(strFragIndex);
strIndex strTexIndex = g_TxdStore.GetStreamingIndex(strLocalIndex(pInfo->m_HDtxdIdx));
strStreamingInfo& texInfo = infoMgr.GetStreamingInfoRef(strTexIndex);
physicalMem += texInfo.ComputePhysicalSize(strTexIndex);
virtualMem += texInfo.ComputeVirtualSize(strTexIndex);
}
}
#endif // __BANK
const Vector3 &CVehicleModelInfo::GetPassengerMobilePhoneIKOffset(u32 iSeat)
{
for(int i = 0; i < m_aFirstPersonMobilePhoneSeatIKOffset.GetCount(); ++i)
{
if(iSeat == (u32)m_aFirstPersonMobilePhoneSeatIKOffset[i].GetSeatIndex())
{
return m_aFirstPersonMobilePhoneSeatIKOffset[i].GetOffset();
}
}
return m_FirstPersonPassengerMobilePhoneOffset;
}
// returns the color index as returned by GetPossibleColor
u8 CVehicleModelInfo::GetLiveryColor(s32 livery, s32 color, s32 index) const
{
if (livery < 0)
return 255;
if (color < 0 || color >= MAX_NUM_LIVERY_COLORS)
return 255;
if (m_liveryColors[livery][color] < 0 || m_liveryColors[livery][color] >= GetNumPossibleColours())
return 255;
Assertf(index < NUM_VEH_BASE_COLOURS, "Invalid color index passed to GetLiveryColor (%d)", index);
return GetPossibleColours(index, m_liveryColors[livery][color]);
}
// returns the index into the possible colors array
u8 CVehicleModelInfo::GetLiveryColorIndex(s32 livery, s32 color) const
{
if (livery < 0)
return 255;
if (color < 0 || color >= MAX_NUM_LIVERY_COLORS)
return 255;
if (m_liveryColors[livery][color] < 0 || m_liveryColors[livery][color] >= GetNumPossibleColours())
return 255;
return m_liveryColors[livery][color];
}
//
// Update the colors in the model info
//
void CVehicleModelInfo::UpdateModelColors(const CVehicleVariationData& varData, const int NOTFINAL_ONLY(maxNumCols))
{
modelinfoAssertf(GetModelType() == MI_TYPE_VEHICLE, "ModelInfo isn't a vehicle");
m_numPossibleColours = varData.GetNumColors();
if (m_numPossibleColours == 0) {
modelinfoWarningf("%s: No possible colors set", varData.GetModelName());
}
modelinfoAssertf(m_numPossibleColours <= MAX_VEH_POSSIBLE_COLOURS, "Too many colours for vehicle: %s", varData.GetModelName());
#if __BANK
// allow us to force all colors to be the same value (for editing)
const int widgetForcedColor = varData.GetWidgetSelectedColorIndex(); // -1 if no color selected
#endif // __BANK
u32 numLiveryColors[MAX_NUM_LIVERIES] = {0};
for(int j=0; j<m_numPossibleColours; j++)
{
#if __BANK
const CVehicleModelColorIndices& cols = varData.GetColor(widgetForcedColor>=0 ? widgetForcedColor : j);
#else
const CVehicleModelColorIndices& cols = varData.GetColor(j);
#endif // __BANK
m_possibleColours[0][j] = cols.m_indices[0];
m_possibleColours[1][j] = cols.m_indices[1];
m_possibleColours[2][j] = cols.m_indices[2];
m_possibleColours[3][j] = cols.m_indices[3];
m_possibleColours[4][j] = cols.m_indices[4];
m_possibleColours[5][j] = cols.m_indices[5];
for (s32 i = 0; i < MAX_NUM_LIVERIES; ++i)
if (cols.m_liveries[i] && Verifyf(numLiveryColors[i] < MAX_NUM_LIVERY_COLORS, "Trying to add more than %d colors to livery %d on '%s'", MAX_NUM_LIVERY_COLORS, i, GetModelName()))
m_liveryColors[i][numLiveryColors[i]++] = (s8)j;
}
m_lightSettings = varData.GetLightSettings();
m_sirenSettings = varData.GetSirenSettings();
#if !__FINAL
ASSERT_ONLY(const char *vehName = varData.GetModelName());
// Repair any fucked up indices
for(int j=0; j<m_numPossibleColours; j++)
{
modelinfoAssertf(m_possibleColours[0][j] < maxNumCols, "carvariations.pso.meta: Bad color index found in '%s', defaulting to index 0", vehName);
modelinfoAssertf(m_possibleColours[1][j] < maxNumCols, "carvariations.pso.meta: Bad color index found in '%s', defaulting to index 0", vehName);
modelinfoAssertf(m_possibleColours[2][j] < maxNumCols, "carvariations.pso.meta: Bad color index found in '%s', defaulting to index 0", vehName);
modelinfoAssertf(m_possibleColours[3][j] < maxNumCols, "carvariations.pso.meta: Bad color index found in '%s', defaulting to index 0", vehName);
modelinfoAssertf(m_possibleColours[4][j] < maxNumCols, "carvariations.pso.meta: Bad color index found in '%s', defaulting to index 0", vehName);
modelinfoAssertf(m_possibleColours[5][j] < maxNumCols, "carvariations.pso.meta: Bad color index found in '%s', defaulting to index 0", vehName);
if( m_possibleColours[0][j] > maxNumCols )
m_possibleColours[0][j] = 0;
if( m_possibleColours[1][j] > maxNumCols )
m_possibleColours[1][j] = 0;
if( m_possibleColours[2][j] > maxNumCols )
m_possibleColours[2][j] = 0;
if( m_possibleColours[3][j] > maxNumCols )
m_possibleColours[3][j] = 0;
if( m_possibleColours[4][j] > maxNumCols )
m_possibleColours[4][j] = 0;
if( m_possibleColours[5][j] > maxNumCols )
m_possibleColours[5][j] = 0;
}
modelinfoAssertf(m_lightSettings < ms_VehicleColours->m_Lights.GetCount(), "carvariations.pso.meta: Bad light index %d found in '%s', defaulting to index 0", m_lightSettings, vehName);
if( m_lightSettings >= ms_VehicleColours->m_Lights.GetCount() )
{
m_lightSettings = 0;
}
modelinfoAssertf(m_sirenSettings < ms_VehicleColours->m_Sirens.GetCount(), "carvariations.pso.meta: Bad siren index %d found in '%s', defaulting to index 0", m_sirenSettings, vehName);
if( m_sirenSettings >= ms_VehicleColours->m_Sirens.GetCount() )
{
m_sirenSettings = 0;
}
#endif // __BANK
}
void CVehicleModelInfo::UpdateVehicleClassInfo(CVehicleModelInfo* pVehicleModelInfo)
{
#if !__GAMETOOL
CHandlingData *pHandling = CHandlingDataMgr::GetHandlingData(pVehicleModelInfo->GetHandlingId());
VehicleClassInfo *pVehicleClassInfo = CVehicleModelInfo::GetVehicleClassInfo(pVehicleModelInfo->GetVehicleClass());
float maxTraction = pHandling->m_fTractionCurveMax;
if( pHandling->GetCarHandlingData() &&
pHandling->GetCarHandlingData()->aFlags & CF_USE_DOWNFORCE_BIAS )
{
maxTraction += CWheel::ms_fDownforceMult * pHandling->m_fDownforceModifier * 2.0f;
}
pVehicleClassInfo->m_fMaxTraction = rage::Max( pVehicleClassInfo->m_fMaxTraction, maxTraction );
pVehicleModelInfo->CalculateMaxFlatVelocityEstimate(pHandling);
pVehicleClassInfo->m_fMaxSpeedEstimate = rage::Max(pVehicleClassInfo->m_fMaxSpeedEstimate, pHandling->m_fEstimatedMaxFlatVel);
if(CFlyingHandlingData *pFlyHandling = pHandling->GetFlyingHandlingData())
{
pVehicleClassInfo->m_fMaxBraking = rage::Max(pVehicleClassInfo->m_fMaxBraking, pHandling->GetFlyingHandlingData()->m_fThrust * (-GRAVITY) * pHandling->m_fEstimatedMaxFlatVel * 0.01f);
pVehicleClassInfo->m_fMaxAcceleration = rage::Max(pVehicleClassInfo->m_fMaxAcceleration, pFlyHandling->m_fThrust * (-GRAVITY));
}
else
{
pVehicleClassInfo->m_fMaxBraking = rage::Max(pVehicleClassInfo->m_fMaxBraking, pHandling->m_fBrakeForce);
pVehicleClassInfo->m_fMaxAcceleration = rage::Max(pVehicleClassInfo->m_fMaxAcceleration, pHandling->m_fInitialDriveForce);
}
if(pHandling->GetFlyingHandlingData())
{
//add yaw pitch and roll together and average them
pVehicleClassInfo->m_fMaxAgility = rage::Max(pVehicleClassInfo->m_fMaxAgility,
((-pHandling->GetFlyingHandlingData()->m_fYawMult) +
pHandling->GetFlyingHandlingData()->m_fRollMult +
pHandling->GetFlyingHandlingData()->m_fPitchMult) *
pHandling->m_fEstimatedMaxFlatVel * (-GRAVITY) / 3.0f);
}
else if(CBoatHandlingData *pBoatHandling = pHandling->GetBoatHandlingData())
{
pVehicleClassInfo->m_fMaxAgility = rage::Max(pVehicleClassInfo->m_fMaxAgility, pBoatHandling->m_vecMoveResistance.GetXf());
}
// HACK to fix issue with trailers weebling when falling over.
// Modify the COM of the trailer so it doesn't naturally want to self right.
if(pVehicleModelInfo->GetModelNameHash() == MI_TRAILER_TRAILER.GetName().GetHash())
{
pHandling->m_vecCentreOfMassOffset.SetZf(-1.0f);
if(pHandling->GetTrailerHandlingData())
{
pHandling->GetTrailerHandlingData()->m_fUprightSpringConstant = -36.0f;
pHandling->GetTrailerHandlingData()->m_fUprightDampingConstant = 30.0f;
}
}
#else
(void)pVehicleModelInfo;
#endif // !__GAMETOOL
}
void CVehicleModelInfo::UpdateModKits(const CVehicleVariationData& varData)
{
m_modKits.Reset();
m_modKits.Reserve(varData.GetNumKits());
for (s32 i = 0; i < varData.GetNumKits(); ++i)
{
atHashString kit = varData.GetKit(i);
for (s32 f = 0; f < ms_VehicleColours->m_Kits.GetCount(); ++f)
{
if (ms_VehicleColours->m_Kits[f].GetNameHash() == kit.GetHash())
{
m_modKits.Push((u16)f);
break;
}
}
}
}
void CVehicleModelInfo::UpdateWindowsWithExposedEdges(const CVehicleVariationData& varData)
{
m_windowsWithExposedEdges = 0;
if (varData.GetNumWindowsWithExposedEdges() > 0)
{
const ObjectNameIdAssociation windows[] =
{
{"windscreen", VEH_WINDSCREEN},
{"windscreen_r", VEH_WINDSCREEN_R},
{"window_lf", VEH_WINDOW_LF},
{"window_rf", VEH_WINDOW_RF},
{"window_lr", VEH_WINDOW_LR},
{"window_rr", VEH_WINDOW_RR},
{"window_lm", VEH_WINDOW_LM},
{"window_rm", VEH_WINDOW_RM},
};
ASSERT_ONLY(int actualCount = 0;)
for (int i = 0; i < NELEM(windows); i++)
{
const int b = windows[i].hierId - VEH_FIRST_WINDOW;
Assert(b >= 0 && b < 8);
const char* hierarchyIdName = windows[i].pName;
const u32 hierarchyIdNameHash = atStringHash(hierarchyIdName);
if (varData.GetDoesWindowHaveExposedEdges(hierarchyIdNameHash))
{
m_windowsWithExposedEdges |= BIT(b);
ASSERT_ONLY(actualCount++;)
}
}
#if __ASSERT
const int expectedCount = varData.GetNumWindowsWithExposedEdges();
if (actualCount < expectedCount)
{
Assertf(0, "%s: carvariations.pso.meta indicates %d windows with exposed edges, only %d applied", GetModelName(), expectedCount, actualCount);
}
#endif // __ASSERT
}
}
void CVehicleModelInfo::UpdatePlateProbabilities(const CVehicleVariationData& varData)
{
m_plateProbabilities = varData.GetPlateProbabilities().GetProbabilityArray();
}
void CVehicleModelInfo::GenerateVariation(CVehicle* pVehicle, CVehicleVariationInstance& variation, bool bFullyRandom /*= false*/)
{
if (m_modKits.GetCount() == 0)
return;
Assertf(!GetVehicleFlag(CVehicleModelInfoFlags::FLAG_CANNOT_BE_MODDED), "Setting variation instance on non-modable vehicle '%s'", GetModelName());
// note, we pick a random kit, and a random mod index if bFullyRandom is set
u64 modsSelected = 0;
u32 kitIndex = fwRandom::GetRandomNumberInRange(0, m_modKits.GetCount());
variation.SetKitIndex(m_modKits[(u8)kitIndex]);
const CVehicleKit& kit = ms_VehicleColours->m_Kits[m_modKits[(u8)kitIndex]];
// visible mods
if (bFullyRandom)
{
atFixedArray<atArray<s32>, eVehicleModType::VMT_RENDERABLE> modsMatchingType;
// Initialize mod array
for (int i = 0; i < eVehicleModType::VMT_RENDERABLE; ++i)
{
atArray<s32> newArray;
modsMatchingType.Push(newArray);
}
// Sort visible mods into array
for (int i = 0; i < kit.GetVisibleMods().GetCount(); ++i)
{
const CVehicleModVisible& mod = kit.GetVisibleMods()[i];
if (Verifyf(mod.GetType() != VMT_INVALID, "CVehicleModelInfo::GenerateVariation - Visible Mod %d (name='%s') has type VMT_INVALID", i, mod.GetNameHashString().TryGetCStr()))
{
modsMatchingType[mod.GetType()].PushAndGrow(i);
}
}
// Pick random mods to assign
for (int i = 0; i < eVehicleModType::VMT_RENDERABLE; ++i)
{
int iNumModsOfType = modsMatchingType[i].GetCount();
if (iNumModsOfType > 0)
{
int iRandomModIndex = fwRandom::GetRandomNumberInRange(0, iNumModsOfType);
variation.SetModIndex((eVehicleModType)i, (u8)modsMatchingType[i][iRandomModIndex], pVehicle, false);
}
}
}
else
{
for (s32 i = 0; i < kit.GetVisibleMods().GetCount(); ++i)
{
const CVehicleModVisible& mod = kit.GetVisibleMods()[i];
// skip mod slots that have already been filled
if ((modsSelected & (u64(1) << mod.GetType())) != 0)
continue;
//if (fwRandom::GetRandomNumberInRange(0.f, 1.f) > 0.5f)
// continue;
// assign mod to instance and flag slot as filled
variation.SetModIndex(mod.GetType(), (u8)i, pVehicle, false);
modsSelected |= u64(1) << mod.GetType();
Assert(variation.m_numMods <= VMT_MAX); // can't have more than one per slot
}
}
// stat mods
for (s32 i = 0; i < kit.GetStatMods().GetCount(); ++i)
{
const CVehicleModStat& mod = kit.GetStatMods()[i];
// skip mod slots that have already been filled
if ((modsSelected & (u64(1) << mod.GetType())) != 0)
continue;
if (fwRandom::GetRandomNumberInRange(0.f, 1.f) > 0.5f)
continue;
// assign mod to instance and flag slot as filled
variation.SetModIndex(mod.GetType(), (u8)i, pVehicle, false);
modsSelected |= u64(1) << mod.GetType();
Assert(variation.m_numMods <= VMT_MAX); // can't have more than one per slot
}
// wheels
u32 wheelIdx = fwRandom::GetRandomNumberInRange(0, GetVehicleColours()->m_Wheels[GetWheelType()].GetCount());
variation.SetModIndex(VMT_WHEELS, (u8)wheelIdx, pVehicle, false);
const u32 modelNameHash = GetModelNameHash();
if (GetIsBike() || (modelNameHash==MID_TORNADO6) || (modelNameHash==MID_IMPALER2) || (modelNameHash==MID_IMPALER4) || (modelNameHash==MID_PEYOTE2))
{
if(GetIsBike())
{
Assertf(GetWheelType()==VWT_BIKE, "Bike '%s' has wrong wheel type!", GetModelName());
}
u32 wheelRearIdx = fwRandom::GetRandomNumberInRange(0, GetVehicleColours()->m_Wheels[GetWheelType()].GetCount());
variation.SetModIndex(VMT_WHEELS_REAR_OR_HYDRAULICS, (u8)wheelRearIdx, pVehicle, false);
}
// toggle mods
variation.ToggleMod(VMT_NITROUS, fwRandom::GetRandomNumberInRange(0.f, 1.f) > 0.5f);
variation.ToggleMod(VMT_TURBO, fwRandom::GetRandomNumberInRange(0.f, 1.f) > 0.5f);
variation.ToggleMod(VMT_SUBWOOFER, fwRandom::GetRandomNumberInRange(0.f, 1.f) > 0.5f);
variation.ToggleMod(VMT_TYRE_SMOKE, fwRandom::GetRandomNumberInRange(0.f, 1.f) > 0.5f);
variation.ToggleMod(VMT_XENON_LIGHTS, fwRandom::GetRandomNumberInRange(0.f, 1.f) > 0.5f);
// random smoke color if mod happened to be turned on
if (variation.IsToggleModOn(VMT_TYRE_SMOKE))
{
variation.SetSmokeColorR((u8)fwRandom::GetRandomNumberInRange(0, 255));
variation.SetSmokeColorG((u8)fwRandom::GetRandomNumberInRange(0, 255));
variation.SetSmokeColorB((u8)fwRandom::GetRandomNumberInRange(0, 255));
}
// random window tint
variation.SetWindowTint((u8)fwRandom::GetRandomNumberInRange(0, ms_VehicleColours->m_WindowColors.GetCount()));
}
#if __BANK
void CVehicleModelInfo::RefreshVehicleWidgets()
{
if (ms_VehicleColours)
ms_VehicleColours->RefreshVehicleWidgets();
if (ms_VehicleVariations)
ms_VehicleVariations->RefreshVehicleWidgets();
}
// Update the colors on all active vehicles (use when tuning vehicle colors)
void CVehicleModelInfo::RefreshAllVehicleBodyColors()
{
CEntityIterator entityIterator( IterateVehicles );
CEntity* pEntity = entityIterator.GetNext();
while(pEntity)
{
// let shaders know, that body colours changed
CVehicle * pVehicle = static_cast<CVehicle*>(pEntity);
pVehicle->UpdateBodyColourRemapping();
pEntity = entityIterator.GetNext();
}
}
void CVehicleModelInfo::RefreshAllVehicleLightSettings()
{
for(int i=0;i<ms_VehicleVariations->GetVariationData().GetCount();i++)
{
const CVehicleVariationData& varData = ms_VehicleVariations->GetVariationData()[i];
CVehicleModelInfo* pModelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfoFromName(varData.GetModelName(), NULL);
modelinfoAssertf(pModelInfo, "Vehicle ModelInfo %s doesn't exist", varData.GetModelName());
if( pModelInfo )
{
pModelInfo->UpdateModelColors(varData, 256);
}
}
}
void CVehicleModelInfo::SaveVehicleColours(const char* filename)
{
#if !__NO_OUTPUT
COwnershipInfo<CVehicleKit, VehicleKitId>::DumpInfo();
COwnershipInfo<vehicleLightSettings, vehicleLightSettingsId>::DumpInfo();
COwnershipInfo<sirenSettings, sirenSettingsId>::DumpInfo();
#endif // __NO_OUTPUT
CVehicleModelInfoVarGlobal* carCols = NULL;
CVehicleModelInfoVarGlobal carColInst;
// For the original file, copy over all the carcol info.
fwPsoStoreLoadInstance tempInstance;
if(!stricmp(filename, RS_ASSETS "/titleupdate/dev_ng/data/carcols.pso.meta"))
{
fwPsoStoreLoader colLoader = fwPsoStoreLoader::MakeSimpleFileLoader<CVehicleModelInfoVarGlobal>();
colLoader.Load("platform:/data/carcols." META_FILE_EXT, tempInstance);
carCols = reinterpret_cast<CVehicleModelInfoVarGlobal*>(tempInstance.GetInstance());
}
else
{
carCols = &carColInst;
}
carCols->m_Kits.Reset();
carCols->m_Lights.Reset();
carCols->m_Sirens.Reset();
// Fill it up depending on ownership info.
for(int j=0; j<ms_VehicleColours->m_Kits.GetCount(); ++j)
{
if(COwnershipInfo<CVehicleKit, VehicleKitId>::Found(ms_VehicleColours->m_Kits[j].GetId(), filename))
carCols->m_Kits.PushAndGrow(ms_VehicleColours->m_Kits[j]);
}
for(int j=0; j<ms_VehicleColours->m_Lights.GetCount(); ++j)
{
if(COwnershipInfo<vehicleLightSettings, vehicleLightSettingsId>::Found(ms_VehicleColours->m_Lights[j].GetId(), filename))
carCols->m_Lights.PushAndGrow(ms_VehicleColours->m_Lights[j]);
}
for(int j=0; j<ms_VehicleColours->m_Sirens.GetCount(); ++j)
{
if(COwnershipInfo<sirenSettings, sirenSettingsId>::Found(ms_VehicleColours->m_Sirens[j].GetId(), filename))
carCols->m_Sirens.PushAndGrow(ms_VehicleColours->m_Sirens[j]);
}
// Save to corresponding file.
const fiDevice *device = fiDevice::GetDevice(filename);
if(Verifyf(device, "Couldn't get device for %s", filename))
{
char path[RAGE_MAX_PATH];
device->FixRelativeName(path, RAGE_MAX_PATH, filename);
vehicleVerifyf(PARSER.SaveObject(path, "meta", carCols, parManager::XML), "Failed to save carcols. Please add extracontent for %s in your command line", device->GetDebugName());
}
}
void CVehicleModelInfo::ReloadColourMetadata()
{
COwnershipInfo<vehicleLightSettings, vehicleLightSettingsId>::Reset();
COwnershipInfo<CVehicleKit, VehicleKitId>::Reset();
COwnershipInfo<sirenSettings, sirenSettingsId>::Reset();
LoadVehicleColours();
for(int i=0; i<ms_VehicleColours->GetCarColFileCount(); ++i)
{
AppendVehicleColors(ms_VehicleColours->GetCarColFile(i));
}
}
void CVehicleModelInfo::SaveVehicleVariations()
{
ms_VehicleVariations->OnPreSave();
AssertVerify(PARSER.SaveObject(RS_ASSETS "/export/data/carvariations.pso.meta", "", ms_VehicleVariations, parManager::XML));
}
#endif // __BANK
#if __DEV
void CVehicleModelInfo::CheckMissingColour()
{
if(GetNumPossibleColours() == 0)
{
modelinfoWarningf( "%s Doesn't have valid colours set up in carcols.pso.meta\n", GetModelName());
}
}
#endif // __DEV
#if __BANK
void CVehicleModelInfo::CheckDependenciesAreLoaded()
{
if (!GetHasLoaded())
return;
strIndex deps[STREAMING_MAX_DEPENDENCIES];
s32 numDeps = CModelInfo::GetStreamingModule()->GetDependencies(GetStreamSlot(), &deps[0], STREAMING_MAX_DEPENDENCIES);
for (s32 i = 0; i < numDeps; ++i)
{
strStreamingInfo& info = *strStreamingEngine::GetInfo().GetStreamingInfo(deps[i]);
if (info.GetStatus() != STRINFO_LOADED)
{
Assertf(false, "Dependency '%s' (%s) for vehicle isn't loaded!", strStreamingEngine::GetObjectName(deps[i]), strStreamingInfo::GetFriendlyStatusName(info.GetStatus()));
}
}
}
void CVehicleModelInfo::DumpVehicleModelInfos()
{
fwModelId modelId = CModelInfo::GetModelIdFromName(GetModelName());
Displayf("%s, %d refs, %s %s", GetModelName(), GetNumRefs(), ( (modelId.IsValid() && CModelInfo::GetAssetRequiredFlag(modelId))? "required" : ""), (GetHasLoaded()? "(loaded)" : ""));
if(GetHasLoaded())
{
ms_numLoadedInfos++;
}
}
atArray<sDebugSize> CVehicleModelInfo::ms_vehicleSizes;
void CVehicleModelInfo::DumpAverageVehicleSize()
{
fwModelId modelId = CModelInfo::GetModelIdFromName(GetModelName());
if (modelId.IsValid())
{
if (!CModelInfo::HaveAssetsLoaded(modelId))
{
CModelInfo::RequestAssets(modelId, STRFLAG_FORCE_LOAD);
CStreaming::LoadAllRequestedObjects();
}
}
u32 virtualSize = 0;
u32 physicalSize = 0;
CModelInfo::GetObjectAndDependenciesSizes(CModelInfo::GetModelIdFromName(GetModelName()), virtualSize, physicalSize, CVehicleModelInfo::GetResidentObjects().GetElements(), CVehicleModelInfo::GetResidentObjects().GetCount(), true);
u32 virtualSizeHd = 0;
u32 physicalSizeHd = 0;
if (GetHDTxdIndex() != -1)
{
virtualSizeHd = CStreaming::GetObjectVirtualSize(strLocalIndex(GetHDTxdIndex()), g_TxdStore.GetStreamingModuleId());
physicalSizeHd = CStreaming::GetObjectPhysicalSize(strLocalIndex(GetHDTxdIndex()), g_TxdStore.GetStreamingModuleId());
}
if (GetHDFragmentIndex() != -1)
{
virtualSizeHd += CStreaming::GetObjectVirtualSize(strLocalIndex(GetHDFragmentIndex()), g_FragmentStore.GetStreamingModuleId());
physicalSizeHd += CStreaming::GetObjectPhysicalSize(strLocalIndex(GetHDFragmentIndex()), g_FragmentStore.GetStreamingModuleId());
}
sDebugSize data;
data.main = virtualSize;
data.vram = physicalSize;
data.mainHd = virtualSizeHd;
data.vramHd = physicalSizeHd;
data.name = GetModelName();
ms_vehicleSizes.PushAndGrow(data);
CModelInfo::RemoveAssets(modelId);
}
void CVehicleModelInfo::DebugRecalculateVehicleCoverPoints()
{
if( m_data )
{
CalculateVehicleCoverPoints();
}
}
#endif // __BANK
//
//
//
//
void CVehicleModelInfo::InitMasterDrawableData(u32 UNUSED_PARAM(modelIdx))
{
Assertf(0, "%s: called InitMasterDrawableData, is this vehicle not a frag?", GetModelName());
}
//
//
//
void CVehicleModelInfo::DeleteMasterDrawableData()
{
}
//
//
//
//
void CVehicleModelInfo::DestroyCustomShaderEffects()
{
modelinfoAssert(m_data);
if(m_data->m_pHDShaderEffectType)
{
gtaFragType* pFrag = GetHDFragType();
if(pFrag)
{
// check txds are set as expected
vehicleAssertf(g_TxdStore.HasObjectLoaded(strLocalIndex(GetAssetParentTxdIndex())), "archetype %s : unexpected txd state", GetModelName());
m_data->m_pHDShaderEffectType->RestoreModelInfoDrawable(pFrag->GetCommonDrawable());
}
m_data->m_pHDShaderEffectType->RemoveRef();
m_data->m_pHDShaderEffectType = NULL;
}
CBaseModelInfo::DestroyCustomShaderEffects();
}
void CVehicleModelInfo::InitMasterFragData(u32 modelIdx)
{
modelinfoAssert(!m_data);
m_data = rage_new CVehicleModelInfoData();
CBaseModelInfo::InitMasterFragData(modelIdx);
ConfirmHDFiles();
InitVehData(modelIdx);
InitPhys();
// the rope textures are loaded already loaded since they should be a dependency for this modelinfo
// but we need to call this function so the ropedata gets patched up correctly
if (m_bNeedsRopeTexture)
{
ropeDataManager::LoadRopeTexures();
}
s8* skelToRenderBoneMap = NULL;
u8 numBones = 0;
gtaFragType* frag = GetFragType();
if (frag)
{
const crSkeletonData* lodSkelData = frag->GetCommonDrawable()->GetSkeletonData();
numBones = (u8)lodSkelData->GetNumBones();
skelToRenderBoneMap = rage_new s8[numBones];
memset(skelToRenderBoneMap, 0xff, numBones * sizeof(s8));
atArray<int> skinnedBones;
skinnedBones.Reserve(lodSkelData->GetNumBones() + NUM_VEH_CWHEELS_MAX);
for (s32 i = 0; i < lodSkelData->GetNumBones(); ++i)
{
const crBoneData* boneData = lodSkelData->GetBoneData(i);
if (boneData->HasDofs(crBoneData::IS_SKINNED))
{
s32 boneIndex = boneData->GetIndex();
Assert(boneIndex < lodSkelData->GetNumBones());
skelToRenderBoneMap[boneIndex] = (s8)skinnedBones.GetCount();
skinnedBones.Push(i);
}
}
if (skinnedBones.GetCount() > 0)
{
// we have all skinned bones we need for the vehicle, append the bones needed for the wheels
u32 numWheels = 0;
fragType* frag = GetFragType();
if (frag && frag->GetPhysics(0))
{
fragTypeChild** children = frag->GetPhysics(0)->GetAllChildren();
if (children)
{
for (s32 i = 0; i < NUM_VEH_CWHEELS_MAX; ++i)
{
int nThisChild = GetStructure()->m_nWheelInstances[i][0];
int nDrawChild = GetStructure()->m_nWheelInstances[i][1];
if (nThisChild > -1 && nDrawChild > -1)
{
const fragTypeChild* child = children[nThisChild];
if (child)
{
s32 boneIndex = frag->GetBoneIndexFromID(child->GetBoneID());
if (boneIndex != -1)
{
Assert(boneIndex < lodSkelData->GetNumBones());
skelToRenderBoneMap[boneIndex] = (s8)skinnedBones.GetCount();
skinnedBones.Push(boneIndex);
numWheels++;
}
}
}
}
}
}
Assertf(numWheels < NUM_VEH_CWHEELS_MAX, "Found %d skinned wheels in '%s', max %d!", numWheels, GetModelName(), NUM_VEH_CWHEELS_MAX);
GetStructure()->m_numSkinnedWheels = numWheels;
InitLodSkeletonMap(lodSkelData, skinnedBones);
}
else
{
delete[] skelToRenderBoneMap;
skelToRenderBoneMap = NULL;
numBones = 0;
}
}
// set up the flags for the vehicle bones that want decals applied
SetDecalBoneFlags();
delete[] skelToRenderBoneMap;
}
void CVehicleModelInfo::DeleteMasterFragData()
{
if (!m_data) // InitMasterFragData hasn't been called yet, can happen when game is shut down
return;
modelinfoAssert(GetNumHdRefs() == 0);
if(!Verifyf(!m_data->m_pHDShaderEffectType, "HD shader effect type not cleaned when removing vehicle '%s'", GetModelName()))
{
gtaFragType* pFrag = GetHDFragType();
if(pFrag)
{
// check txds are set as expected
vehicleAssertf(g_TxdStore.HasObjectLoaded(strLocalIndex(GetAssetParentTxdIndex())), "archetype %s : unexpected txd state", GetModelName());
m_data->m_pHDShaderEffectType->RestoreModelInfoDrawable(pFrag->GetCommonDrawable());
}
m_data->m_pHDShaderEffectType->RemoveRef();
m_data->m_pHDShaderEffectType = NULL;
}
if(m_data->m_pStructure)
{
delete m_data->m_pStructure;
m_data->m_pStructure = NULL;
}
if (m_bNeedsRopeTexture)
{
ropeDataManager::UnloadRopeTexures();
}
CBaseModelInfo::DeleteMasterFragData();
m_data->m_WheelOffsets.Reset();
delete m_data;
m_data = NULL;
}
void CVehicleModelInfo::SetPhysics(phArchetypeDamp* pPhysics)
{
CBaseModelInfo::SetPhysics(pPhysics);
InitPhys();
}
void CVehicleModelInfo::Init(const InitData& rInitData)
{
Init();
strLocalIndex vehiclePatchSlot = g_TxdStore.FindSlot("vehshare_tire");
if (vehiclePatchSlot == -1)
{
vehiclePatchSlot = CVehicleModelInfo::GetCommonTxd();
}
strLocalIndex parentTxdSlot = strLocalIndex(FindTxdSlotIndex(rInitData.m_txdName.c_str()));
if(parentTxdSlot == -1)
{
// This model has no individual texture dictionary
// so just set it to use the shared one
parentTxdSlot = vehiclePatchSlot;
}
// setup vehicle texture dictionaries so that they look in the vehshare.txd. If a parent is already setup don't override it
else if (g_TxdStore.GetParentTxdSlot(parentTxdSlot) == -1)
{
g_TxdStore.SetParentTxdSlot(parentTxdSlot, vehiclePatchSlot);
}
SetDrawableOrFragmentFile(rInitData.m_modelName.c_str(), parentTxdSlot.Get(), true); // known to be a fragment
// only setup the expressions if there is a dictionary name and an expression name.
if (stricmp(rInitData.m_expressionDictName.c_str(), "null") && stricmp(rInitData.m_expressionName.c_str(), "null"))
{
SetExpressionDictionaryIndex(rInitData.m_expressionDictName.c_str());
SetExpressionHash(rInitData.m_expressionName.c_str());
}
// setup the animation dictionary
if (rInitData.m_animConvRoofDictName.c_str() && stricmp(rInitData.m_animConvRoofDictName.c_str(), "null"))
{
SetClipDictionaryIndex(rInitData.m_animConvRoofDictName.c_str());
// Is the current slot valid and is the animation dictionary in the image?
vehicleAssertf(g_ClipDictionaryStore.IsValidSlot(strLocalIndex(GetClipDictionaryIndex())) &&
g_ClipDictionaryStore.IsObjectInImage(GetClipDictionaryIndex()), "Animation dictionary %s not found", rInitData.m_animConvRoofDictName.c_str() );
if(stricmp(rInitData.m_animConvRoofName.c_str(), "null"))
{
//convert the name into a hash
u32 uAnimationConvertibleRoofName = atStringHash(rInitData.m_animConvRoofName.c_str());
SetConvertibleRoofAnimNameHash(uAnimationConvertibleRoofName);
}
}
SetVehicleMetaDataFile(rInitData.m_modelName.c_str());
#if NAVMESH_EXPORT
if(!CNavMeshDataExporter::WillExportCollision())
#endif
{
// setup the particle effect asset
if (stricmp(rInitData.m_ptfxAssetName.c_str(), "null"))
{
strLocalIndex slot = ptfxManager::GetAssetStore().FindSlotFromHashKey(atStringHash(rInitData.m_ptfxAssetName.c_str()));
if (Verifyf(slot.IsValid(), "%s is trying to load a PTFX asset (%s) which doesn't exist", GetModelName(), rInitData.m_ptfxAssetName.c_str()))
{
SetPtFxAssetSlot(slot.Get());
}
}
}
u32 uHash = rInitData.m_layout.GetHash();
m_pVehicleLayoutInfo = (uHash != 0) ? CVehicleMetadataMgr::GetInstance().GetVehicleLayoutInfo(uHash) : NULL;
u32 uPOVHash = rInitData.m_POVTuningInfo.GetHash();
m_pPOVTuningInfo = (uHash != 0) ? CVehicleMetadataMgr::GetInstance().GetPOVTuningInfo(uPOVHash) : NULL;
u32 uCoverHash = rInitData.m_coverBoundOffsets.GetHash();
m_pVehicleCoverBoundOffsetInfo = (uCoverHash != 0) ? CVehicleMetadataMgr::GetInstance().GetVehicleCoverBoundOffsetInfo(uCoverHash) : NULL;
for(int i = 0; i < rInitData.m_firstPersonDrivebyData.GetCount(); ++i)
{
const CFirstPersonDriveByLookAroundData *pDrivebyData = CVehicleMetadataMgr::GetFirstPersonDriveByLookAroundData(rInitData.m_firstPersonDrivebyData[i]);
vehicleAssertf(pDrivebyData, "%s is trying to reference an invalid driveby data! %s", GetModelName(), rInitData.m_firstPersonDrivebyData[i].GetCStr());
m_apFirstPersonLookAroundData.PushAndGrow(pDrivebyData);
}
// remove underscores from game name. Before using it
u32 idx = 0;
char gameName[MAX_VEHICLE_GAME_NAME] = { 0 };
const char* pChar = rInitData.m_gameName.c_str();
while(*pChar)
{
if(*pChar == '_')
gameName[idx] = ' ';
else
gameName[idx] = *pChar;
idx++;
pChar++;
// Stop if we have filled up the buffer, to avoid an array overrun and
// possibly corrupting the stack. The name is already silently truncated to
// eight characters by SetGameName().
if(idx >= sizeof(gameName) - 1)
{
break;
}
}
SetGameName(gameName);
SetVehicleMakeName(rInitData.m_vehicleMakeName.c_str());
SetAudioNameHash(rInitData.m_audioNameHash.GetHash());
SetVehicleType(rInitData.m_type);
Assertf((m_vehicleType > VEHICLE_TYPE_NONE) && (m_vehicleType < VEHICLE_TYPE_NUM), "Bad vehicle type on '%s'", GetModelName());
// Setup the explosion info (after the type and name are setup)
u32 uExplosionInfoHash = rInitData.m_explosionInfo.GetHash();
if(uExplosionInfoHash == ATSTRINGHASH("EXPLOSION_INFO_DEFAULT",0x13416fdd))
{
// If the user left this vehicle type as the default, set it to the vehicle type's default
switch(GetVehicleType())
{
case VEHICLE_TYPE_CAR: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_CAR",0xee911a2b); break;
case VEHICLE_TYPE_PLANE: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_PLANE",0x0062ce5d); break;
case VEHICLE_TYPE_TRAILER: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_TRAILER", 0xe5253930); break;
case VEHICLE_TYPE_QUADBIKE: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_QUADBIKE", 0x8aa04604); break;
case VEHICLE_TYPE_DRAFT: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_DRAFT", 0x5b987700); break;
case VEHICLE_TYPE_SUBMARINECAR: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_CAR", 0xee911a2b); break; // JUST USE THE CAR EXPLOSION INFO FOR THE SUBMARINE CAR
case VEHICLE_TYPE_HELI: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_HELI", 0x493dff90); break;
case VEHICLE_TYPE_BLIMP: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_BLIMP", 0x030c2852); break;
case VEHICLE_TYPE_AUTOGYRO: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_AUTOGYRO", 0xd74d8ad7); break;
case VEHICLE_TYPE_BIKE: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_BIKE", 0xd1d17df8); break;
case VEHICLE_TYPE_BICYCLE: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_BICYCLE", 0x9ee8b39d); break;
case VEHICLE_TYPE_BOAT: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_BOAT", 0x4ce74c96); break;
case VEHICLE_TYPE_TRAIN: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_TRAIN", 0xeb8b34f5); break;
case VEHICLE_TYPE_SUBMARINE: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_SUBMARINE", 0xad524f8e); break;
case VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_CAR", 0xee911a2b); break;
case VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE: uExplosionInfoHash = ATSTRINGHASH("EXPLOSION_INFO_QUADBIKE", 0x8aa04604); break;
default: Assertf(false, "Unknown vehicle type %i for '%s'", GetVehicleType(), GetGameName());
}
}
m_pVehicleExplosionInfo = (uExplosionInfoHash != 0) ? CVehicleMetadataMgr::GetInstance().GetVehicleExplosionInfo(uExplosionInfoHash) : NULL;
SetSteeringWheelMult(rInitData.m_steerWheelMult);
SetFirstPersonSteeringWheelMult(rInitData.m_firstPersonSteerWheelMult);
SetWheels(rInitData.m_wheelScale, rInitData.m_wheelScaleRear);
// Set handling ID
BANK_ONLY(SetHandlingIdHash(atHashString(rInitData.m_handlingId));)
s32 handlingId = CHandlingDataMgr::RegisterHandlingData(rInitData.m_handlingId, true);
Assertf(handlingId != -1, "Invalid car handling ID");
SetHandlingId(handlingId);
SetDefaultDirtLevel(u32(rInitData.m_dirtLevelMin * 15.0f), u32(rInitData.m_dirtLevelMax * 15.0f));
Assertf(rInitData.m_envEffScaleMin <= rInitData.m_envEffScaleMax, "%s: envEffScale: Min must be smaller than max in vehicles.meta.", rInitData.m_modelName.c_str());
SetEnvEffScale(rInitData.m_envEffScaleMin, rInitData.m_envEffScaleMax);
Assertf(rInitData.m_envEffScaleMin2 <= rInitData.m_envEffScaleMax2, "%s: envEffScale2: Min must be smaller than max in vehicles.meta.", rInitData.m_modelName.c_str());
SetEnvEffScale2(rInitData.m_envEffScaleMin2, rInitData.m_envEffScaleMax2);
SetDamageMapScale(rInitData.m_damageMapScale);
SetDamageOffsetScale(rInitData.m_damageOffsetScale);
SetDiffuseTint(rInitData.m_diffuseTint);
// set flags
SetVehicleFreq(rInitData.m_frequency);
SetVehicleMaxNumber((u16) rInitData.m_maxNum);
SetVehicleSwankness((eSwankness) rInitData.m_swankness);
m_maxNumOfSameColor = rInitData.m_maxNumOfSameColor;
m_defaultBodyHealth = rInitData.m_defaultBodyHealth;
m_VehicleFlags = rInitData.m_flags;
SetCameraNameHash(rInitData.m_cameraName.GetHash());
SetAimCameraNameHash(rInitData.m_aimCameraName.GetHash());
SetBonnetCameraNameHash(rInitData.m_bonnetCameraName.GetHash());
SetPovTurretCameraNameHash(rInitData.m_povTurretCameraName.GetHash());
SetFirstPersonDriveByIKOffset(rInitData.m_FirstPersonDriveByIKOffset);
SetFirstPersonDriveByUnarmedIKOffset(rInitData.m_FirstPersonDriveByUnarmedIKOffset);
SetFirstPersonDriveByLeftPassengerIKOffset(rInitData.m_FirstPersonDriveByLeftPassengerIKOffset);
SetFirstPersonDriveByRightPassengerIKOffset(rInitData.m_FirstPersonDriveByRightPassengerIKOffset);
SetFirstPersonDriveByRightRearPassengerIKOffset(rInitData.m_FirstPersonDriveByRightRearPassengerIKOffset);
SetFirstPersonDriveByLeftPassengerUnarmedIKOffset(rInitData.m_FirstPersonDriveByLeftPassengerUnarmedIKOffset);
SetFirstPersonDriveByRightPassengerUnarmedIKOffset(rInitData.m_FirstPersonDriveByRightPassengerUnarmedIKOffset);
SetFirstPersonProjectileDriveByIKOffset(rInitData.m_FirstPersonProjectileDriveByIKOffset);
SetFirstPersonProjectileDriveByPassengerIKOffset(rInitData.m_FirstPersonProjectileDriveByPassengerIKOffset);
SetFirstPersonProjectileDriveByRearLeftIKOffset(rInitData.m_FirstPersonProjectileDriveByRearLeftIKOffset);
SetFirstPersonProjectileDriveByRearRightIKOffset(rInitData.m_FirstPersonProjectileDriveByRearRightIKOffset);
SetFirstPersonVisorSwitchIKOffset(rInitData.m_FirstPersonVisorSwitchIKOffset);
SetFirstPersonMobilePhoneOffset(rInitData.m_FirstPersonMobilePhoneOffset);
SetFirstPersonPassengerMobilePhoneOffset(rInitData.m_FirstPersonPassengerMobilePhoneOffset);
m_aFirstPersonMobilePhoneSeatIKOffset = rInitData.m_FirstPersonMobilePhoneSeatIKOffset;
SetPovCameraNameHash(rInitData.m_povCameraName.GetHash());
SetPovCameraOffset(rInitData.m_PovCameraOffset);
SetPovPassengerCameraOffset(rInitData.m_PovPassengerCameraOffset);
SetPovRearPassengerCameraOffset(rInitData.m_PovRearPassengerCameraOffset);
SetPovCameraVerticalAdjustmentForRollCage(rInitData.m_PovCameraVerticalAdjustmentForRollCage);
m_shouldUseCinematicViewMode = rInitData.m_shouldUseCinematicViewMode;
m_bShouldCameraTransitionOnClimbUpDown = rInitData.m_shouldCameraTransitionOnClimbUpDown;
m_bShouldCameraIgnoreExiting = rInitData.m_shouldCameraIgnoreExiting;
m_bAllowPretendOccupants = rInitData.m_AllowPretendOccupants;
m_bAllowJoyriding = rInitData.m_AllowJoyriding;
m_bAllowSundayDriving = rInitData.m_AllowSundayDriving;
m_bAllowBodyColorMapping = rInitData.m_AllowBodyColorMapping;
m_pretendOccupantsScale = rInitData.m_pretendOccupantsScale;
m_visibleSpawnDistScale = rInitData.m_visibleSpawnDistScale;
for (s32 i = 0; i < VLT_MAX; ++i)
m_lodDistances[i] = rInitData.m_lodDistances[i];
// compensate for missing the VLT_LOD3 distance - 05/05/2014
for (int i = 0; i < VLT_LOD_FADE; ++i)
{
Assertf(m_lodDistances[i] > 0.0f, "Unspecified lod distance for vehicle %s (index %d)", gameName, i);
}
if (m_lodDistances[VLT_LOD_FADE] == 0.0f)
{
m_lodDistances[VLT_LOD_FADE] = m_lodDistances[VLT_LOD3];
}
SetLodDistance(GetVehicleLodDistance(VLT_LOD_FADE));
if (rInitData.m_lodDistances[VLT_HD] > 0.f){
SetupHDFiles(rInitData.m_modelName);
}
m_identicalModelSpawnDistance = rInitData.m_identicalModelSpawnDistance;
m_trackerPathWidth = rInitData.m_trackerPathWidth;
m_weaponForceMult = rInitData.m_weaponForceMult;
m_trailers = rInitData.m_trailers;
m_additionalTrailers = rInitData.m_additionalTrailers;
m_drivers = rInitData.m_drivers;
m_vfxExtraInfos = rInitData.m_vfxExtraInfos;
if(rInitData.m_doorsWithCollisionWhenClosed.GetCount()>0 || rInitData.m_driveableDoors.GetCount()>0)
{
CVehicleModelInfoDoors* pDoorExtension = GetExtension<CVehicleModelInfoDoors>();
if(!pDoorExtension)
{
CVehicleModelInfoDoors *pNewDoorExtension = rage_new CVehicleModelInfoDoors();
GetExtensionList().Add(*pNewDoorExtension);
pDoorExtension = GetExtension<CVehicleModelInfoDoors>();
}
Assert(pDoorExtension);
for(u32 i=0; i < rInitData.m_doorsWithCollisionWhenClosed.GetCount(); ++i)
{
eDoorId doorId = rInitData.m_doorsWithCollisionWhenClosed[i];
pDoorExtension->AddDoorWithCollisionId(pDoorExtension->ConvertExtensionIdToHierarchyId(doorId));
}
for(u32 i=0; i < rInitData.m_driveableDoors.GetCount(); ++i)
{
eDoorId doorId = rInitData.m_driveableDoors[i];
pDoorExtension->AddDriveableDoorId(pDoorExtension->ConvertExtensionIdToHierarchyId(doorId));
}
for(u32 i=0; i < rInitData.m_doorStiffnessMultipliers.GetCount(); ++i)
{
CDoorStiffnessInfo rDoorInfo = rInitData.m_doorStiffnessMultipliers[i];
eDoorId doorId = rDoorInfo.GetDoorId();
pDoorExtension->AddStiffnessMultForThisDoorId(pDoorExtension->ConvertExtensionIdToHierarchyId(doorId), rDoorInfo.GetStiffnessMult());
}
}
if(rInitData.m_animConvRoofWindowsAffected.GetCount()>0)
{
CConvertibleRoofWindowInfo* pWindowExtension = GetExtension<CConvertibleRoofWindowInfo>();
if(!pWindowExtension)
{
CConvertibleRoofWindowInfo *pNewWindowExtension = rage_new CConvertibleRoofWindowInfo();
GetExtensionList().Add(*pNewWindowExtension);
pWindowExtension = GetExtension<CConvertibleRoofWindowInfo>();
}
Assert(pWindowExtension);
Assertf(rInitData.m_animConvRoofWindowsAffected.GetCount()<=VEH_EXT_NUM_WINDOWS,
"Too many windows (%d) listed as affected by convertible roof anim in vehicles.meta for %s. Max allowed = %d",
rInitData.m_animConvRoofWindowsAffected.GetCount(), GetModelName(), VEH_EXT_NUM_WINDOWS);
for(u32 i=0; i < rInitData.m_animConvRoofWindowsAffected.GetCount(); ++i)
{
eWindowId nWindowId = rInitData.m_animConvRoofWindowsAffected[i];
Assertf(pWindowExtension->ConvertExtensionIdToHierarchyId(nWindowId) >= VEH_FIRST_WINDOW
&& pWindowExtension->ConvertExtensionIdToHierarchyId(nWindowId) <= VEH_LAST_WINDOW,
"Invalid window ID (%d) for vehicle %s in vehicles.meta", nWindowId, GetModelName());
pWindowExtension->AddWindowId(pWindowExtension->ConvertExtensionIdToHierarchyId(nWindowId));
}
}
if (rInitData.m_pOverrideRagdollThreshold!=NULL)
{
CVehicleModelInfoRagdollActivation* pExtension = GetExtension<CVehicleModelInfoRagdollActivation>();
if(!pExtension)
{
CVehicleModelInfoRagdollActivation *pNewExtension = rage_new CVehicleModelInfoRagdollActivation(rInitData.m_pOverrideRagdollThreshold->m_MinComponent, rInitData.m_pOverrideRagdollThreshold->m_MaxComponent, rInitData.m_pOverrideRagdollThreshold->m_ThresholdMult);
GetExtensionList().Add(*pNewExtension);
pExtension = GetExtension<CVehicleModelInfoRagdollActivation>();
}
Assert(pExtension);
}
if(rInitData.m_buoyancySphereOffset.IsNonZero() || rInitData.m_buoyancySphereSizeScale != 1.0f
|| rInitData.m_additionalVfxWaterSamples.GetCount() > 0)
{
CVehicleModelInfoBuoyancy* pBuoyancyExtension = GetExtension<CVehicleModelInfoBuoyancy>();
if(!pBuoyancyExtension)
{
CVehicleModelInfoBuoyancy* pNewBuoyancyExtension = rage_new CVehicleModelInfoBuoyancy();
GetExtensionList().Add(*pNewBuoyancyExtension);
pBuoyancyExtension = GetExtension<CVehicleModelInfoBuoyancy>();
}
Assert(pBuoyancyExtension);
pBuoyancyExtension->SetBuoyancySphereOffset(rInitData.m_buoyancySphereOffset);
pBuoyancyExtension->SetBuoyancySphereSizeScale(rInitData.m_buoyancySphereSizeScale);
pBuoyancyExtension->SetAdditionalVfxWaterSamples(rInitData.m_additionalVfxWaterSamples);
}
if(rInitData.m_bumpersNeedToCollideWithMap)
{
CVehicleModelInfoBumperCollision* pBumperColExtension = GetExtension<CVehicleModelInfoBumperCollision>();
if(!pBumperColExtension)
{
CVehicleModelInfoBumperCollision* pNewBumperColExtension = rage_new CVehicleModelInfoBumperCollision();
GetExtensionList().Add(*pNewBumperColExtension);
pBumperColExtension = GetExtension<CVehicleModelInfoBumperCollision>();
}
Assert(pBumperColExtension);
pBumperColExtension->SetBumpersNeedMapCollision(rInitData.m_bumpersNeedToCollideWithMap);
}
m_bNeedsRopeTexture = rInitData.m_needsRopeTexture;
m_extraIncludes = rInitData.m_extraIncludes;
m_requiredExtras = rInitData.m_requiredExtras;
m_wheelType = rInitData.m_wheelType;
//Copy the spawn points to the base model info.
m_uVehicleScenarioLayoutInfoHash = rInitData.m_scenarioLayout;
const CVehicleScenarioLayoutInfo* pScenarioLayoutInfo = GetVehicleScenarioLayoutInfo();
if ( pScenarioLayoutInfo != NULL )
{
u32 numScenarioPoints = pScenarioLayoutInfo->GetNumScenarioPoints();
SetNum2dEffects(numScenarioPoints);
for(u32 i = 0; i < numScenarioPoints; ++i)
{
//Allocate the spawn point.
C2dEffect* pEffect = CModelInfo::GetSpawnPointStore().CreateItem(fwFactory::GLOBAL);
CSpawnPoint* pSpawnPoint = pEffect->GetSpawnPoint();
//Initialize the spawn point from the extension.
pSpawnPoint->InitArchetypeExtensionFromDefinition(pScenarioLayoutInfo->GetScenarioPointInfo(i), NULL);
//Add the spawn point.
C2dEffect** ppEffect = GetNewEffect();
*ppEffect = pEffect;
Add2dEffect(ppEffect);
}
}
SetVfxInfo(rInitData.m_vfxInfoName.GetHash());
m_HDTextureDist = rInitData.m_HDTextureDist;
m_rewards = rInitData.m_rewards;
m_cinematicPartCamera = rInitData.m_cinematicPartCamera;
m_NmBraceOverrideSet = rInitData.m_NmBraceOverrideSet;
Assertf((rInitData.m_plateType & ~0x3) == 0, "Enum value for vehicle plate type is too large");
m_plateType = (u8)(rInitData.m_plateType & 0x3);
Assertf((rInitData.m_vehicleClass & ~0x1f) == 0, "Enum value for vehicle class is too large");
m_vehicleClass = (u8)(rInitData.m_vehicleClass & 0x1f);
m_dashboardType = rInitData.m_dashboardType;
m_maxSteeringWheelAngle = rInitData.m_maxSteeringWheelAngle;
m_maxSteeringWheelAnimAngle = rInitData.m_maxSteeringWheelAnimAngle;
m_minSeatHeight = rInitData.m_minSeatHeight;
m_lockOnPositionOffset = rInitData.m_lockOnPositionOffset;
m_LowriderArmWindowHeight = rInitData.m_LowriderArmWindowHeight;
m_LowriderLeanAccelModifier = rInitData.m_LowriderLeanAccelModifier;
m_numSeatsOverride = rInitData.m_numSeatsOverride;
}
void CVehicleModelInfo::ReInit(const InitData& rInitData)
{
// FA: we could potentially do a full init here (everything we do in Init(InitData)) but I've chosen to only do the requested
// items first. It should be trivial to add most things if needed but might not be 100% safe to do them all blindly so we can deal
// with those cases when needed.
for (s32 i = 0; i < VLT_MAX; ++i)
m_lodDistances[i] = rInitData.m_lodDistances[i];
SetLodDistance(GetVehicleLodDistance(VLT_LOD_FADE));
m_identicalModelSpawnDistance = rInitData.m_identicalModelSpawnDistance;
m_trailers = rInitData.m_trailers;
m_additionalTrailers = rInitData.m_additionalTrailers;
m_drivers = rInitData.m_drivers;
m_HDTextureDist = rInitData.m_HDTextureDist;
}
#if __BANK
void CVehicleModelInfo::SetVehicleDLCPack(const char* fileName)
{
char pString[128];
strcpy(pString, fileName);
const char* pToken = strtok(pString, ":");
if (pToken)
{
m_dlcpack = atHashString(pToken);
}
}
#endif //__BANK
void CVehicleModelInfo::SetVehicleMetaDataFile(const char* UNUSED_PARAM(pName))
{
#if 0
modelinfoAssert(pName);
if (stricmp(pName, "null") == 0)
{
m_vehicleMetaDataFileIndex = -1;
return;
}
m_vehicleMetaDataFileIndex = g_fwMetaDataStore.FindSlot(pName);
#endif
}
const CVehicleModelInfoMetaData* CVehicleModelInfo::GetVehicleMetaData() const
{
strLocalIndex index = strLocalIndex(GetVehicleMetaDataFileIndex());
if (index == -1)
return NULL;
if (!g_fwMetaDataStore.Get(index))
return NULL;
return g_fwMetaDataStore.Get(index)->GetObject<CVehicleModelInfoMetaData>();
}
void CVehicleModelInfo::RequestDials(const sVehicleDashboardData& params)
{
#if !__GAMETOOL
fwModelId modelId = fwArchetypeManager::LookupModelId(this);
if(!Verifyf(ms_requestPerFrame == 0,"Ignorable - Requesting too many Dials in one frame, the limit is ONE"))
{
return;
}
ms_requestPerFrame++;
// link render target
if (ms_dialsRenderTargetId == 0)
{
ms_rtNameHash = m_data->m_dialsTextureHash;
const CRenderTargetMgr::namedRendertarget* rt = gRenderTargetMgr.GetNamedRendertarget(ms_rtNameHash, ms_dialsRenderTargetOwner);
if (rt == NULL)
{
gRenderTargetMgr.RegisterNamedRendertarget(ms_rtNameHash, ms_dialsRenderTargetOwner, m_data->m_dialsRenderTargetUniqueId);
}
#if RSG_PC && GTA_REPLAY
else
{
const bool bReplayActive = CReplayMgr::IsPlaying() || CReplayMgr::IsEditModeActive();
if((GRCDEVICE.UsingMultipleGPUs() && bReplayActive) && rt->owner == ms_dialsRenderTargetOwner && rt->isLinked && rt->release)
{
return;
}
}
#endif
if (GetDrawable() && !gRenderTargetMgr.IsNamedRenderTargetLinked(modelId.GetModelIndex(), ms_dialsRenderTargetOwner))
{
gRenderTargetMgr.LinkNamedRendertargets(m_data->m_dialsTextureHash, ms_dialsRenderTargetOwner, m_data->m_dialsTexture, modelId.GetModelIndex(), GetDrawableType(), GetFragmentIndex());
}
CRenderTargetMgr::namedRendertarget* pRenderTarget = gRenderTargetMgr.GetNamedRendertarget(ms_rtNameHash, ms_dialsRenderTargetOwner);
if (pRenderTarget && pRenderTarget->texture)
{
ms_dialsRenderTargetSizeX = (u32)pRenderTarget->texture->GetWidth();
ms_dialsRenderTargetSizeY = (u32)pRenderTarget->texture->GetHeight();
ms_dialsRenderTargetId = pRenderTarget->uniqueId;
}
Assert(ms_dialsRenderTargetId != 0);
}
else
{
// these asserts trigger when char switching from one vehicle to the other. i.e. the old vehicle doesn't have enough time to free the dials
// we can ignore this and there'll be a few frames delay
//Assertf(gRenderTargetMgr.IsNamedRenderTargetLinked(modelId.GetModelIndex(), ms_dialsRenderTargetOwner), "%s requesting dials but render target isn't linked!", GetModelName());
//Assert(ms_rtNameHash == m_data->m_dialsTextureHash);
}
if (ms_rtNameHash == m_data->m_dialsTextureHash)
{
ms_dialsFrameCountReq = fwTimer::GetFrameCount();
}
s32 requestedIndex = -1;
switch (GetVehicleDashboardType())
{
case VDT_MAVERICK: // aircraft
requestedIndex = DIALS_AIRCRAFT;
break;
case VDT_LAZER: // lazer jet
requestedIndex = DIALS_LAZER;
break;
case VDT_LAZER_VINTAGE:
requestedIndex = DIALS_LAZER_VINTAGE;
break;
case VDT_PBUS2:
requestedIndex = DIALS_PBUS_2;
break;
default: // all other vehicles
requestedIndex = DIALS_VEHICLE;
break;
}
// if we want a different movie, release old one here. the correct one will be allocated when Update gets called
if (requestedIndex != ms_activeDialsMovie)
{
if (CScaleformMgr::IsMovieActive(ms_dialsMovieId))
{
CScaleformMgr::RequestRemoveMovie(ms_dialsMovieId);
ms_dialsMovieId = -1;
ms_dialsType = -1;
}
ms_activeDialsMovie = requestedIndex;
}
if (CScaleformMgr::IsMovieActive(ms_dialsMovieId))
{
bool bDialsUpdated = false;
if (ms_dialsType == -1)
{
ms_dialsType = (s32)GetVehicleDashboardType();
if (CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_VEHICLE_TYPE"))
{
CScaleformMgr::AddParamInt(ms_dialsType);
CScaleformMgr::EndMethod();
}
}
if (ms_activeDialsMovie == DIALS_VEHICLE)
{
if (ms_cachedDashboardData.HaveVehicleDialsChanged(params) && CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_DASHBOARD_DIALS"))
{
CScaleformMgr::AddParamFloat(params.revs);
CScaleformMgr::AddParamFloat(params.speed);
CScaleformMgr::AddParamFloat(params.fuel);
CScaleformMgr::AddParamFloat(params.engineTemp);
CScaleformMgr::AddParamFloat(params.vacuum);
CScaleformMgr::AddParamFloat(params.boost);
CScaleformMgr::AddParamFloat(params.oilTemperature);
CScaleformMgr::AddParamFloat(params.oilPressure);
CScaleformMgr::AddParamFloat(params.currentGear);
CScaleformMgr::EndMethod();
bDialsUpdated = true;
}
if (ms_cachedDashboardData.HaveVehicleLightsChanged(params) && CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_DASHBOARD_LIGHTS"))
{
CScaleformMgr::AddParamBool(params.indicatorLeft);
CScaleformMgr::AddParamBool(params.indicatorRight);
CScaleformMgr::AddParamBool(params.handBrakeLight);
CScaleformMgr::AddParamBool(params.engineLight);
CScaleformMgr::AddParamBool(params.absLight);
CScaleformMgr::AddParamBool(params.fuelLight);
CScaleformMgr::AddParamBool(params.oilLight);
CScaleformMgr::AddParamBool(params.headLightsLight);
CScaleformMgr::AddParamBool(params.fullBeamLight);
CScaleformMgr::AddParamBool(params.batteryLight);
CScaleformMgr::EndMethod();
bDialsUpdated = true;
}
// radio
#if GTA_REPLAY
if(CReplayMgr::IsEditModeActive())
{
const char* stationStr = "replay";
if ( stationStr != ms_lastStationStr )
{
if (CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_RADIO"))
{
CScaleformMgr::AddParamString(""); // tuning
CScaleformMgr::AddParamString("");
CScaleformMgr::AddParamString("");
CScaleformMgr::AddParamString("");
CScaleformMgr::EndMethod();
}
ms_lastStationStr = stationStr;
}
}
else
#endif
{
u32 trackId = 1;
WIN32PC_ONLY(bool isUserTrack = false);
const audRadioStation *playerStation = g_RadioAudioEntity.GetPlayerRadioStationPendingRetune();
if(playerStation)
{
const audRadioTrack &track = playerStation->GetCurrentTrack();
if(track.GetCategory() == RADIO_TRACK_CAT_MUSIC || track.GetCategory() == RADIO_TRACK_CAT_TAKEOVER_MUSIC)
{
trackId = track.GetTextId();
WIN32PC_ONLY(isUserTrack = track.IsUserTrack());
}
}
const char* stationName = g_RadioAudioEntity.GetPlayerRadioStationNamePendingRetune();
bool radioActive = stationName && (g_RadioAudioEntity.IsPlayerRadioActive() || g_RadioAudioEntity.IsRetuningVehicleRadio());
const char* stationStr = radioActive ? stationName : (stationName ? "" : "CAR_RADOFF");
if( !TheText.HasAdditionalTextLoaded(RADIO_WHEEL_TEXT_SLOT) )
TheText.RequestAdditionalText("TRACKID", RADIO_WHEEL_TEXT_SLOT);
if ((trackId != ms_lastTrackId || stationStr != ms_lastStationStr) && TheText.HasAdditionalTextLoaded(RADIO_WHEEL_TEXT_SLOT))
{
if (CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_RADIO"))
{
#define FILTER_UNKNOWN(inVal) ( inVal&&TheText.DoesTextLabelExist(inVal)?TheText.Get(inVal):"")
char artistStr[16] = {0};
char trackStr[16] = {0};
if (radioActive)
{
formatf(artistStr, "%dA", trackId);
formatf(trackStr, "%dS", trackId);
}
CScaleformMgr::AddParamString(""); // tuning
CScaleformMgr::AddParamString(FILTER_UNKNOWN(stationStr));
#if RSG_PC
if(isUserTrack)
{
CScaleformMgr::AddParamString(audRadioStation::GetUserRadioTrackManager()->GetTrackArtist(audRadioTrack::GetUserTrackIndexFromTextId(trackId)));
CScaleformMgr::AddParamString(audRadioStation::GetUserRadioTrackManager()->GetTrackTitle(audRadioTrack::GetUserTrackIndexFromTextId(trackId)));
}
else
{
#endif
CScaleformMgr::AddParamString(FILTER_UNKNOWN(artistStr));
CScaleformMgr::AddParamString(FILTER_UNKNOWN(trackStr));
#if RSG_PC
}
#endif // RSG_PC
CScaleformMgr::EndMethod();
}
ms_lastTrackId = trackId;
ms_lastStationStr = stationStr;
}
}
}
else if (ms_activeDialsMovie == DIALS_AIRCRAFT || DIALS_LAZER || DIALS_LAZER_VINTAGE)
{
if (ms_cachedDashboardData.HaveAircraftDialsChanged(params) && CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_DASHBOARD_DIALS"))
{
CScaleformMgr::AddParamFloat(params.aircraftFuel);
CScaleformMgr::AddParamFloat(params.aircraftTemp);
CScaleformMgr::AddParamFloat(params.aircraftOilPressure);
CScaleformMgr::AddParamFloat(params.aircraftBattery);
CScaleformMgr::AddParamFloat(params.aircraftFuelPressure);
CScaleformMgr::AddParamFloat(params.aircraftAirSpeed);
CScaleformMgr::AddParamFloat(params.aircraftVerticalSpeed);
CScaleformMgr::AddParamFloat(params.aircraftCompass);
CScaleformMgr::AddParamFloat(params.aircraftRoll);
CScaleformMgr::AddParamFloat(params.aircraftPitch);
CScaleformMgr::AddParamFloat(params.aircraftAltitudeSmall);
CScaleformMgr::AddParamFloat(params.aircraftAltitudeLarge);
CScaleformMgr::EndMethod();
bDialsUpdated = true;
}
if (ms_cachedDashboardData.HaveAircraftLightsChanged(params) && CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_DASHBOARD_LIGHTS"))
{
CScaleformMgr::AddParamBool(params.aircraftGearUp);
CScaleformMgr::AddParamBool(params.aircraftGearDown);
CScaleformMgr::AddParamBool(params.pressureAlarm);
CScaleformMgr::EndMethod();
bDialsUpdated = true;
}
if (ms_activeDialsMovie == DIALS_LAZER || DIALS_LAZER_VINTAGE)
{
if (ms_cachedDashboardData.HaveAircraftHudDialsChanged(params) && CScaleformMgr::BeginMethod(ms_dialsMovieId, SF_BASE_CLASS_SCRIPT, "SET_AIRCRAFT_HUD"))
{
CScaleformMgr::AddParamFloat(params.aircraftAir);
CScaleformMgr::AddParamFloat(params.aircraftFuel);
CScaleformMgr::AddParamFloat(params.aircraftOil);
CScaleformMgr::AddParamFloat(params.aircraftVacuum);
CScaleformMgr::EndMethod();
bDialsUpdated = true;
}
CPed* pPlayer = FindPlayerPed();
if( pPlayer && pPlayer->GetHelmetComponent())
{
const u32 LAZER_COCKPIT_ROCKETS = (u32)-199376390;
const u32 LAZER_COCKPIT_MACHINE = 1931187857;
u32 uWeaponReticuleHash = CNewHud::GetReticule().GetPreviousValues().m_iWeaponHashForReticule;
if( (uWeaponReticuleHash == LAZER_COCKPIT_ROCKETS || uWeaponReticuleHash == LAZER_COCKPIT_MACHINE) &&
pPlayer->GetHelmetComponent()->HasPilotHelmetEquippedInAircraftInFPS(false))
{
if(ms_cachedDashboardData.HaveAircraftHudDialsChanged(params) && CHudTools::BeginHudScaleformMethod(NEW_HUD_RETICLE, "SET_AIRCRAFT_HUD"))
{
CScaleformMgr::AddParamFloat(params.aircraftPitch);
CScaleformMgr::AddParamFloat(params.aircraftRoll);
CScaleformMgr::AddParamFloat(params.aircraftAltitudeLarge);
CScaleformMgr::AddParamFloat(params.aircraftAirSpeed);
CScaleformMgr::EndMethod();
bDialsUpdated = true;
}
}
}
}
}
else if(ms_activeDialsMovie == DIALS_PBUS_2)
{
// TODO: Fill in the stuff
}
if(bDialsUpdated)
{
ms_cachedDashboardData = params;
}
}
#else
(void)params;
#endif
}
void CVehicleModelInfo::RenderDialsToRenderTarget(u32 targetId)
{
#if !__GAMETOOL
if ( (ms_dialsRenderTargetId == 0) || (ms_dialsRenderTargetId != targetId) || (!CScaleformMgr::IsMovieActive(ms_dialsMovieId)) || (ms_activeDialsMovie == -1) )
{
return;
}
Vector2 vSize(1.0f, 1.0f);
if (ms_dialsRenderTargetSizeX > 0 && ms_dialsRenderTargetSizeY > 0 && CScaleformMgr::GetScreenSize().x > 0 && CScaleformMgr::GetScreenSize().y > 0)
{
vSize.x = (float)ms_dialsRenderTargetSizeX / CScaleformMgr::GetScreenSize().x;
vSize.y = (float)ms_dialsRenderTargetSizeY / CScaleformMgr::GetScreenSize().y;
}
// set the pos & size of the movie:
CScaleformMgr::ChangeMovieParams(ms_dialsMovieId, Vector2(0.f, 0.f), vSize, GFxMovieView::SM_ExactFit);
CScaleformMgr::RenderMovie(ms_dialsMovieId, fwTimer::GetSystemTimeStep());
#else
(void)targetId;
#endif
}
void CVehicleModelInfo::SetBoneIndexes(ObjectNameIdAssociation *pAssocArray, u16* pNameHashes, bool bOverRide)
{
modelinfoAssert(m_data);
crSkeletonData *pSkeletonData = NULL;
if(GetFragType())
pSkeletonData = GetFragType()->GetCommonDrawable()->GetSkeletonData();
else
pSkeletonData = GetDrawable()->GetSkeletonData();
if (pSkeletonData)
{
s32 i=0;
u16 rootHash = atHash16U("chassis");
while(pAssocArray[i].pName != NULL)
{
const crBoneData *pBone = NULL;
// for some reason the exporter allows the root bone (the chassis) to have a boneId of 0 in the skeleton
s32 boneIdx = -1;
if (pNameHashes[i] == rootHash)
boneIdx = 0;
else
pSkeletonData->ConvertBoneIdToIndex(pNameHashes[i], boneIdx);
if (boneIdx != -1)
{
pBone = pSkeletonData->GetBoneData(boneIdx);
}
#if __ASSERT
const crBoneData* bone2 = pSkeletonData->FindBoneData(pAssocArray[i].pName);
if( pBone != bone2 )
{
Displayf( "Vehicle (%s) bone name clash: bones '%s' and '%s' have hashes %d and %d", GetModelName(), pAssocArray[i].pName, pBone ? pBone->GetName() : "null" , atHash16U(pAssocArray[i].pName), atHash16U(pBone ? pBone->GetName() : "null"));
}
#endif // __ASSERT
if (pBone)
{
m_data->m_pStructure->m_nBoneIndices[pAssocArray[i].hierId] = (s8)pBone->GetIndex();
#if __ASSERT
// check we don't have any light bones at the centre of the vehicle
if(pAssocArray[i].hierId >= VEH_HEADLIGHT_L && pAssocArray[i].hierId <= VEH_SIREN_GLASS_MAX && pAssocArray[i].hierId != VEH_EMISSIVES)
modelinfoAssertf(RCC_VECTOR3(pBone->GetDefaultTranslation()).IsNonZero(), "%s bone %s is at zero", GetModelName(), pBone->GetName());
#endif
}
i++;
}
// only want to set up light stuff once - using base ids
if(!bOverRide)
{
// Now that's done, we need to deal with remaping the missing light bones.
const s32 tailLightL = GetBoneIndex(VEH_TAILLIGHT_L);
const s32 BrakeLightL = GetBoneIndex(VEH_BRAKELIGHT_L);
const s32 IndicatorLR = GetBoneIndex(VEH_INDICATOR_LR);
const s32 tailLightR = GetBoneIndex(VEH_TAILLIGHT_R);
const s32 BrakeLightR = GetBoneIndex(VEH_BRAKELIGHT_R);
const s32 IndicatorRR = GetBoneIndex(VEH_INDICATOR_RR);
const bool gotTail = (-1 != tailLightL) || (-1 != tailLightR);
const bool gotBrake = (-1 != BrakeLightL) || (-1 != BrakeLightR);
const bool gotIndicator = (-1 != IndicatorLR) || (-1 != IndicatorRR);
m_bRemap = true;
m_bHasTailLight = gotTail;
m_bHasIndicatorLight = gotIndicator;
m_bHasBrakeLight = gotBrake;
if( (false == gotTail) ||
(false == gotBrake) ||
(false == gotIndicator) )
{
s32 remapToL = -1;
s32 remapToR = -1;
// something's missing, we need to remap
// Select where to remap
if( gotTail )
{
remapToL = tailLightL;
remapToR = tailLightR;
}
else if ( gotBrake )
{
remapToL = BrakeLightL;
remapToR = BrakeLightR;
}
else if ( gotIndicator )
{
remapToL = IndicatorLR;
remapToR = IndicatorRR;
}
// We can't fail remapping... or can we ?
// modelinfoAssert(remapToL != -1);
// modelinfoAssert(remapToR != -1);
if( false == gotTail )
{
m_data->m_pStructure->m_nBoneIndices[VEH_TAILLIGHT_L] = (s8)remapToL;
m_data->m_pStructure->m_nBoneIndices[VEH_TAILLIGHT_R] = (s8)remapToR;
}
if( false == gotBrake )
{
m_data->m_pStructure->m_nBoneIndices[VEH_BRAKELIGHT_L] = (s8)remapToL;
m_data->m_pStructure->m_nBoneIndices[VEH_BRAKELIGHT_R] = (s8)remapToR;
}
if( false == gotIndicator )
{
m_data->m_pStructure->m_nBoneIndices[VEH_INDICATOR_LR] = (s8)remapToL;
m_data->m_pStructure->m_nBoneIndices[VEH_INDICATOR_RR] = (s8)remapToR;
}
}
// Setup extra lights
const s32 extraLight1 = GetBoneIndex(VEH_EXTRALIGHT_1);
const s32 extraLight2 = GetBoneIndex(VEH_EXTRALIGHT_2);
const s32 extraLight3 = GetBoneIndex(VEH_EXTRALIGHT_3);
const s32 extraLight4 = GetBoneIndex(VEH_EXTRALIGHT_4);
m_bHasExtraLights = (extraLight1 != -1) ||
(extraLight2 != -1) ||
(extraLight3 != -1) ||
(extraLight4 != -1);
const s32 extraLightCount = ((extraLight1 != -1) ? 1 : 0) +
((extraLight2 != -1) ? 1 : 0) +
((extraLight3 != -1) ? 1 : 0) +
((extraLight4 != -1) ? 1 : 0);
m_bDoubleExtralights = (extraLightCount > 2);
// Setup neons
const s32 neonL = GetBoneIndex(VEH_NEON_L);
const s32 neonR = GetBoneIndex(VEH_NEON_R);
const s32 neonF = GetBoneIndex(VEH_NEON_F);
const s32 neonB = GetBoneIndex(VEH_NEON_B);
m_bHasNeons = (neonL != -1) ||
(neonR != -1) ||
(neonF != -1) ||
(neonB != -1);
}
}
m_bHasSteeringWheelBone = (GetBoneIndex(VEH_STEERING_WHEEL) != -1);
}
void CVehicleModelInfo::SetDecalBoneFlags()
{
modelinfoAssert(m_data);
// go through the vehicle hierarchy ids
for (int i=0; i<VEH_NUM_NODES; i++)
{
// get the bone index of this hierarchy id
int boneIndex = m_data->m_pStructure->m_nBoneIndices[i];
// check if there is a valid bone
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
// don't project onto chassis bones
if (i<=VEH_CHASSIS_DUMMY)
{
m_data->m_pStructure->m_decalBoneFlags.Clear(boneIndex);
}
// don't project onto door handle, wheel, suspension, transmission, wheel hub or window bones
else if (i>=VEH_HANDLE_DSIDE_F && i<=VEH_LAST_WINDOW)
{
m_data->m_pStructure->m_decalBoneFlags.Clear(boneIndex);
}
// don't project onto exhaust, engine, petrol, steering wheel, grip or breakable light bones
else if (i>=VEH_EXHAUST && i<=VEH_LASTBREAKABLELIGHT)
{
m_data->m_pStructure->m_decalBoneFlags.Clear(boneIndex);
}
// don't project onto interior lights or siren bones
else if (i>=VEH_INTERIORLIGHT && i<=VEH_SIREN_GLASS_MAX)
{
m_data->m_pStructure->m_decalBoneFlags.Clear(boneIndex);
}
// don't project onto spring bones
else if (i>=VEH_SPRING_RF && i<=VEH_SPRING_LR)
{
m_data->m_pStructure->m_decalBoneFlags.Clear(boneIndex);
}
}
}
}
// deal with exceptions to the general case
if (GetIsBike())
{
// project onto these
int boneIndex = m_data->m_pStructure->m_nBoneIndices[BIKE_SWINGARM];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[BIKE_FORKS_U];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[BIKE_FORKS_L];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
}
else if (GetIsPlane())
{
// project onto these
int boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_RL];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_RR];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_DOOR_FL];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_DOOR_FR];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_DOOR_RL1];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_DOOR_RR1];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_DOOR_RL2];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
boneIndex = m_data->m_pStructure->m_nBoneIndices[LANDING_GEAR_DOOR_RR2];
if (boneIndex>-1)
{
if (Verifyf(boneIndex<VEH_MAX_DECAL_BONE_FLAGS, "out of range decal bone index"))
{
m_data->m_pStructure->m_decalBoneFlags.Set(boneIndex);
}
}
}
}
void CVehicleModelInfo::CalculateHeightsAboveRoad(float *pFrontHeight, float *pRearHeight)
{
if(GetVehicleType()!=VEHICLE_TYPE_BOAT && GetVehicleType()!=VEHICLE_TYPE_TRAIN && GetVehicleType()!=VEHICLE_TYPE_TRAILER && GetStructure())
{
// find handling data for this vehicle
CHandlingData *pHandling = CHandlingDataMgr::GetHandlingData(GetHandlingId());
CVehicleWeaponHandlingData* pWeaponHandling = pHandling->GetWeaponHandlingData();
if( GetBoneIndex(VEH_WHEEL_LF) != -1 )
{
*pFrontHeight = -CWheel::GetWheelOffset(this, VEH_WHEEL_LF).z + GetTyreRadius(true) + pHandling->m_fSuspensionRaise;
if(pWeaponHandling)
{
// don't add the wheel impact offset to the front wheels on the half-track
if( !MI_CAR_HALFTRACK.IsValid() ||
GetModelNameHash() != MI_CAR_HALFTRACK.GetName().GetHash() )
{
*pFrontHeight += pWeaponHandling->GetWheelImpactOffset();
}
else
{
static float halfTrackHack = 0.01f;
*pFrontHeight += halfTrackHack;
}
}
}
else if( GetBoneIndex(VEH_WHEEL_RF) != -1 )
{
*pFrontHeight = -CWheel::GetWheelOffset(this, VEH_WHEEL_RF).z + GetTyreRadius(true) + pHandling->m_fSuspensionRaise;
if(pWeaponHandling)
{
*pFrontHeight += pWeaponHandling->GetWheelImpactOffset();
}
}
else
{
*pFrontHeight = -GetBoundingBoxMin().z;
}
if( GetBoneIndex(VEH_WHEEL_LR)!=-1 )
{
*pRearHeight = -CWheel::GetWheelOffset(this, VEH_WHEEL_LR).z + GetTyreRadius(false) + pHandling->m_fSuspensionRaise;
if(pWeaponHandling)
{
*pRearHeight += pWeaponHandling->GetWheelImpactOffset();
}
// don't add the wheel impact offset to the front wheels on the half-track
if( MI_CAR_HALFTRACK.IsValid() &&
GetModelNameHash() == MI_CAR_HALFTRACK.GetName().GetHash() )
{
static float halfTrackRearHack = 0.06f;
*pRearHeight += halfTrackRearHack;
}
}
else if( GetBoneIndex(VEH_WHEEL_RR)!=-1 )
{
*pRearHeight = -CWheel::GetWheelOffset(this, VEH_WHEEL_RR).z + GetTyreRadius(false) + pHandling->m_fSuspensionRaise;
if(pWeaponHandling)
{
*pRearHeight += pWeaponHandling->GetWheelImpactOffset();
}
}
else
{
*pRearHeight = -GetBoundingBoxMin().z;
}
modelinfoAssertf((square(*pFrontHeight) < 4.0f*4.0f && square(*pRearHeight) < 4.0f*4.0f ) || GetIsPlane(), "%s:CalculateHeightsAboveRoad: Vehicle seems too high above road", GetModelName());
}
else
{
*pFrontHeight = *pRearHeight = -GetBoundingBoxMin().z;
}
}
//
// name: CVehicleModelInfo::Init
// description: Initialise vehicle model info class
void CVehicleModelInfo::Init()
{
CBaseModelInfo::Init();
m_vehicleType = VEHICLE_TYPE_NONE;
for (s32 i = 0; i < VLT_MAX; ++i)
m_lodDistances[i] = -1.f;
m_type = MI_TYPE_VEHICLE;
m_numPossibleColours = 0;
m_gangPopGroup = -1;
m_bRemap = false;
m_wheelBaseMultiplier = 0.0f;
m_FirstPersonDriveByIKOffset.Zero();
m_FirstPersonDriveByUnarmedIKOffset.Zero();
m_FirstPersonDriveByLeftPassengerIKOffset.Zero();
m_FirstPersonDriveByRightPassengerIKOffset.Zero();
m_FirstPersonDriveByRightRearPassengerIKOffset.Zero();
m_FirstPersonDriveByLeftPassengerUnarmedIKOffset.Zero();
m_FirstPersonDriveByRightPassengerUnarmedIKOffset.Zero();
m_FirstPersonProjectileDriveByIKOffset.Zero();
m_FirstPersonProjectileDriveByPassengerIKOffset.Zero();
m_FirstPersonProjectileDriveByRearLeftIKOffset.Zero();
m_FirstPersonProjectileDriveByRearRightIKOffset.Zero();
m_FirstPersonVisorSwitchIKOffset.Zero();
m_FirstPersonMobilePhoneOffset.Zero();
m_FirstPersonPassengerMobilePhoneOffset.Zero();
m_aFirstPersonMobilePhoneSeatIKOffset.clear();
m_PovCameraOffset.Zero();
m_PovPassengerCameraOffset.Zero();
m_PovRearPassengerCameraOffset.Zero();
m_PovCameraVerticalAdjustmentForRollCage = 0.0f;
m_uCameraNameHash = 0;
m_uAimCameraNameHash = 0;
m_uBonnetCameraNameHash = 0;
m_uPovCameraNameHash = 0;
m_uPovTurretCameraNameHash = 0;
m_audioNameHash = 0;
m_shouldUseCinematicViewMode = true;
m_bShouldCameraTransitionOnClimbUpDown = false;
m_bShouldCameraIgnoreExiting = false;
m_bAllowPretendOccupants = true;
m_bAllowJoyriding = true;
m_bAllowSundayDriving = true;
m_uConvertibleRoofAnimHash = 0;
m_fSteeringWheelMult = 0.0f;
m_fFirstPersonSteeringWheelMult = 0.0f;
m_pVehicleLayoutInfo = NULL;
m_pPOVTuningInfo = NULL;
m_pVehicleCoverBoundOffsetInfo = NULL;
m_pVehicleExplosionInfo = NULL;
m_uVehicleScenarioLayoutInfoHash = 0;
m_pretendOccupantsScale = 1.0f;
m_visibleSpawnDistScale = 1.0f;
m_bHasSeatCollision = false;
m_bHasSteeringWheelBone = false;
m_bNeedsRopeTexture = false;
m_uNumDependencies = 0;
m_bDependenciesValid = false;
// HD files stuff
m_HDfragIdx = -1;
m_HDtxdIdx = -1;
m_bRequestLoadHDFiles = false;
m_bAreHDFilesLoaded = false;
m_bRequestHDFilesRemoval = false;
m_numHDRefs = 0;
m_numHDRenderRefs = 0;
// Light Setting
m_lightSettings = 0;
m_sirenSettings = 0;
// vfx info
m_pVfxInfo = NULL;
// invalidate livery colors
for (s32 i = 0; i < MAX_NUM_LIVERIES; ++i)
for (s32 f = 0; f < MAX_NUM_LIVERY_COLORS; ++f)
m_liveryColors[i][f] = -1;
m_windowsWithExposedEdges = 0;
m_bHasRequestedDrivers = false;
m_maxNumOfSameColor = 10;
m_defaultBodyHealth = VEH_DAMAGE_HEALTH_STD;
SetUseAmbientScale(true);
m_LastTimeUsed = 0;
m_data = NULL;
}
//
// name: CVehicleModelInfo::Shutdown
// description: Initialise vehicle model info class
void CVehicleModelInfo::Shutdown()
{
CBaseModelInfo::Shutdown();
}
#if __DEV
PARAM(carbones, "Verify all vehicle bone names at load time");
#endif
void CVehicleModelInfo::InitVehData(s32 UNUSED_PARAM(modelIdx))
{
modelinfoAssert(m_vehicleType != VEHICLE_TYPE_NONE);
modelinfoAssert(m_data);
modelinfoAssert(m_data->m_pStructure == NULL);
#if __BANK
if (CVehicleStructure::m_pInfoPool->GetNoOfFreeSpaces() <= 0)
{
s32 numAppropriate = gPopStreaming.GetAppropriateLoadedCars().CountMembers();
s32 numInappropriate = gPopStreaming.GetInAppropriateLoadedCars().CountMembers();
s32 numSpecial = gPopStreaming.GetSpecialCars().CountMembers();
s32 numBoats = gPopStreaming.GetLoadedBoats().CountMembers();
s32 numDiscarded = gPopStreaming.GetDiscardedCars().CountMembers();
#if GTA_REPLAY
//s32 numReplay = gPopStreaming.GetReplayCars().CountMembers();
Displayf("CVehicleModelInfo::InitVehData: %d appropriate, %d inappropriate, %d special, %d boats and %d discarded cars! (%d REPLAY Requested)\n",
numAppropriate, numInappropriate, numSpecial, numBoats, numDiscarded, numSpecial);
#else
Displayf("CVehicleModelInfo::InitVehData: %d appropriate, %d inappropriate, %d special, %d boats and %d discarded cars!\n",
numAppropriate, numInappropriate, numSpecial, numBoats, numDiscarded);
#endif
CModelInfo::DumpVehicleModelInfos();
}
#endif // __BANK
m_data->m_pStructure = rage_new CVehicleStructure;
Assert(m_data->m_pStructure);
// set up base set of bone ids - same for all vehicles
SetBoneIndexes(CVehicleFactory::GetFactory()->GetBaseVehicleTypeDesc(m_vehicleType), CVehicleFactory::GetFactory()->GetBaseVehicleTypeDescHashes(m_vehicleType), false);
// if extra set of ids is different from base, they override existing ids with these
if( CVehicleFactory::GetFactory()->GetExtraVehicleTypeDesc(m_vehicleType) != CVehicleFactory::GetFactory()->GetBaseVehicleTypeDesc(m_vehicleType) )
{
SetBoneIndexes(CVehicleFactory::GetFactory()->GetExtraVehicleTypeDesc(m_vehicleType), CVehicleFactory::GetFactory()->GetExtraVehicleTypeDescHashes(m_vehicleType), true);
}
if( m_vehicleType == VEHICLE_TYPE_HELI ||
m_vehicleType == VEHICLE_TYPE_PLANE )
{
SetBoneIndexes( CVehicleFactory::GetFactory()->GetExtraLandingGearTypeDesc(), CVehicleFactory::GetFactory()->GetExtraLandingGearTypeDescHashes(), true);
}
#if __DEV
if(PARAM_carbones.Get())
{
crSkeletonData *pSkeletonData = NULL;
if(GetFragType())
pSkeletonData = GetFragType()->GetCommonDrawable()->GetSkeletonData();
else
pSkeletonData = GetDrawable()->GetSkeletonData();
if(pSkeletonData)
{
for(int nBoneIndex=0; nBoneIndex<pSkeletonData->GetNumBones(); nBoneIndex++)
{
bool bFound = false;
for(int i=0; i<VEH_NUM_NODES; i++)
{
if(m_data->m_pStructure->m_nBoneIndices[i] == (s8)nBoneIndex)
{
bFound = true;
break;
}
}
if(!bFound)
{
modelinfoAssertf(false, "Vehicle %s Bone %s is not recognised", GetModelName(), pSkeletonData->GetBoneData(nBoneIndex)->GetName());
}
}
}
}
#endif
// Setup environment map texture.
rmcDrawable *drawable = GetDrawable();
modelinfoAssert(NULL != drawable);
m_data->m_numAvailableLODs = 0;
if (drawable)
{
if (drawable->GetLodGroup().ContainsLod(LOD_LOW)) {
m_data->m_numAvailableLODs = 3;
} else if (drawable->GetLodGroup().ContainsLod(LOD_MED)) {
m_data->m_numAvailableLODs = 2;
} else if (drawable->GetLodGroup().ContainsLod(LOD_HIGH)) {
m_data->m_numAvailableLODs = 1;
}
}
modelinfoAssertf(m_data->m_numAvailableLODs > 0, "%s can't determine number of available LOD levels", GetModelName());
m_data->m_liveryCount = -1;
// We got livery/sign texture, so we need to count them...
// Take the kids away, the following code is a dirtydirtydirty hack.
if( GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_LIVERY) )
{
grmShaderGroup& shaderGrp = drawable->GetShaderGroup();
const s32 shaderCount = shaderGrp.GetCount();
// Go through all shaders, and gather what we need..
for(int i=0; i<shaderCount; i++)
{
const grmShader& shader = shaderGrp[i];
const grcEffectVar diffuseTex2 = shader.LookupVar("DiffuseTex2",false);
if( diffuseTex2 != grcevNONE )
{
grcTexture *mainTexture;
shader.GetVar( diffuseTex2, mainTexture);
if (modelinfoVerifyf(mainTexture, "livery: mainTexture is NULL on model %s (shader=%s)", GetModelName(), shader.GetName()))
{
const char *mainTexName = mainTexture->GetName();
const char *postFix = strstr(mainTexName,"_sign_");
if( NULL != postFix )
{
u32 nameLen = ptrdiff_t_to_int(postFix - mainTexName);
modelinfoAssertf(nameLen>0, "Livery Texture name is too short for model %s\n", GetModelName());
modelinfoAssertf(nameLen<31, "Livery Texture name is too long for model %s\n", GetModelName());
char prefix[32];
strncpy(prefix, mainTexName, nameLen); // ho, look! a pointer hack!
prefix[nameLen] = 0;
fwTxd *txd = g_TxdStore.Get(strLocalIndex(GetAssetParentTxdIndex()));
modelinfoAssert(txd);
char texName[32];
int signCount = 0;
sprintf(texName,"%s_sign_%d",prefix,signCount+1);
grcTexture *texture = txd->Lookup(texName);
while( texture )
{
if(signCount < MAX_NUM_LIVERIES)
{
m_data->m_liveries[signCount] = txd->ComputeHash(texName);
signCount++;
sprintf(texName,"%s_sign_%d",prefix,signCount+1);
texture = txd->Lookup(texName);
}
else
{
modelinfoAssertf(false, "Too many livery textures applied to vehicle %s\n", GetModelName());
texture = NULL;
}
}
m_data->m_liveryCount = (s8)signCount;
modelinfoAssert(m_data->m_liveryCount != 0);
break;
}
}
}// if( diffuseTex2 != grcevNONE )...
}// for(int i=0; i<shaderCount; i++)...
for(int i=0; i<shaderCount; i++)
{
const grmShader& shader = shaderGrp[i];
const grcEffectVar diffuseTex3 = shader.LookupVar("DiffuseTex3",false);
if( diffuseTex3 != grcevNONE )
{
grcTexture *mainTexture;
shader.GetVar( diffuseTex3, mainTexture);
if (modelinfoVerifyf(mainTexture, "livery2: mainTexture is NULL on model %s (shader=%s)", GetModelName(), shader.GetName()))
{
const char *mainTexName = mainTexture->GetName();
const char *postFix = strstr(mainTexName,"_lvr_");
if( postFix != NULL )
{
u32 nameLen = ptrdiff_t_to_int(postFix - mainTexName);
modelinfoAssertf(nameLen>0, "Livery Texture name is too short for model %s\n", GetModelName());
modelinfoAssertf(nameLen<31, "Livery Texture name is too long for model %s\n", GetModelName());
char prefix[32];
strncpy(prefix, mainTexName, nameLen); // ho, look! a pointer hack!
prefix[nameLen] = 0;
fwTxd *txd = g_TxdStore.Get(strLocalIndex(GetAssetParentTxdIndex()));
modelinfoAssert(txd);
char texName[32];
int lvrCount = 0;
sprintf(texName,"%s_lvr_%d",prefix,lvrCount+1);
grcTexture *texture = txd->Lookup(texName);
while( texture )
{
if(lvrCount < MAX_NUM_LIVERIES)
{
m_data->m_liveries2[lvrCount] = txd->ComputeHash(texName);
lvrCount++;
sprintf(texName,"%s_lvr_%d",prefix,lvrCount+1);
texture = txd->Lookup(texName);
}
else
{
modelinfoAssertf(false, "Too many livery2 textures applied to vehicle %s\n", GetModelName());
texture = NULL;
}
}
m_data->m_livery2Count = (s8)lvrCount;
modelinfoAssert(m_data->m_livery2Count != 0);
break;
}
}
}// if( diffuseTex2 != grcevNONE )...
}//for(int i=0; i<shaderCount; i++)...
}//if( GetVehicleFlag(CVehicleModelInfoFlags::FLAG_HAS_LIVERY) )...
// check if we have a dashboard dials rendertarget
if (drawable)
{
const grmShaderGroup* shaderGroup = &drawable->GetShaderGroup();
for (u32 i = 0; i < shaderGroup->GetCount(); ++i)
{
grmShader* shader = shaderGroup->GetShaderPtr(i);
grcEffectVar varId = shader->LookupVar("diffuseTex", false);
if (varId != grcevNONE)
{
char texName[256];
grcTexture *diffuseTex = NULL;
shader->GetVar(varId, diffuseTex);
if (diffuseTex)
{
StringNormalize(texName, diffuseTex->GetName(), sizeof(texName));
fiAssetManager::RemoveExtensionFromPath(texName, sizeof(texName), texName);
char* scriptRT = strstr(texName, "script_rt_");
if (scriptRT)
{
const char* rtName = texName + strlen("script_rt_");
m_data->m_dialsTextureHash.SetFromString(rtName);
m_data->m_dialsRenderTargetUniqueId = CRenderTargetMgr::GetRenderTargetUniqueId(rtName, ms_dialsRenderTargetOwner);
m_data->m_dialsTexture = diffuseTex;
break;
}
}
}
}
}
if (!drawable->GetLodGroup().ContainsLod(1))
{
m_lodDistances[VLT_LOD0] = m_lodDistances[VLT_LOD1];
// m_lodDistances[VLT_LOD1] = m_lodDistances[VLT_LOD_FADE];
}
drawable->GetLodGroup().SetLodThresh(0, GetVehicleLodDistance(VLT_LOD0));
drawable->GetLodGroup().SetLodThresh(1, GetVehicleLodDistance(VLT_LOD1));
drawable->GetLodGroup().SetLodThresh(2, GetVehicleLodDistance(VLT_LOD2));
if (drawable->GetLodGroup().ContainsLod(3))
{
drawable->GetLodGroup().SetLodThresh(3, GetVehicleLodDistance(VLT_LOD3));
}
CacheWheelOffsets();
#if __ASSERT
// make sure we have no embedded textures in the drawables, these would be duplicated for the HD assets unnecessarily
fragType* frag = GetFragType();
if (Verifyf(frag, "Frag not loaded for vehicle '%s'", GetModelName()))
{
rmcDrawable* commonDrawable = frag->GetCommonDrawable();
if (commonDrawable)
Assertf(!commonDrawable->GetShaderGroup().GetTexDict(), "Common drawable for vehicle '%s' has a texture dictionary!", GetModelName());
rmcDrawable* clothDrawable = frag->GetClothDrawable();
if (clothDrawable)
Assertf(!clothDrawable->GetShaderGroup().GetTexDict(), "Cloth drawable for vehicle '%s' has a texture dictionary!", GetModelName());
}
#endif // __ASSERT
}
//-------------------------------------------------------------------------
// Returns the information on the coverpoint passed
//-------------------------------------------------------------------------
CCoverPointInfo* CVehicleModelInfo::GetCoverPoint( s32 iCoverPoint )
{
modelinfoAssert(m_data);
modelinfoAssert( iCoverPoint >= 0 && iCoverPoint < m_data->m_iCoverPoints );
return &m_data->m_aCoverPoints[iCoverPoint];
}
// #define DISTANCE_FROM_COVER_TO_VEHICLE_HIGH (0.1f)
// #define DISTANCE_FROM_COVER_TO_VEHICLE_LOW (0.1f)
#define DISTANCE_FROM_COVER_TO_CORNER_DEFAULT (0.7f)
#define DISTANCE_FROM_COVER_TO_CORNER_SIDE_FRONT (1.0f)
#define VEHICLE_LENGTH_FOR_MID_COVERPOINTS (2.0f)
#define NUM_VEH_COVER_INTERSECTIONS (18)
static phIntersection aCoverIntersections[NUM_VEH_COVER_INTERSECTIONS];
//
// name: CalculateVehicleCoverPoints
// description: Calculates a set of cover points for a vehicle collision model
void CVehicleModelInfo::CalculateVehicleCoverPoints( void )
{
modelinfoAssert(m_data);
m_data->m_iCoverPoints = 0;
if (GetIsPlane() || GetIsRotaryAircraft())
return;
phArchetype *pArchetype = GetPhysics();
if(GetFragType())
pArchetype = GetFragType()->GetPhysics(0)->GetArchetype();
// there's the potential for the physics not being there
if(pArchetype==NULL)
return;
// Create a test instance of the vehicles collision model at the origin
phInstGta* pTestInst = rage_new phInstGta(PH_INST_MAPCOL);
Matrix34 mat;
mat.Identity();
pTestInst->Init(*pArchetype, mat);
// Only use the chassis to test collision, ignoring any destroyable bits we
// can shoot through like the police car's lights
phBound* pCarTestBound = pTestInst->GetArchetype()->GetBound();
phBoundComposite* pCompositeBound = dynamic_cast<phBoundComposite*>(pTestInst->GetArchetype()->GetBound());
if(pCompositeBound)
pCarTestBound = pCompositeBound->GetBound(0);
float fCapsuleRadius = 1.0f;
float fCapsuleLength = 1.0f;
Vector3 avCapsulePositions[CVehicleModelInfoData::HEIGHT_MAP_SIZE];
// Size capsule tests
SizeCoverTestForVehicle(pTestInst, avCapsulePositions, fCapsuleRadius, fCapsuleLength);
phShapeTest<phShapeSweptSphere> capsuleTester;
phSegment segment;
phIntersection isectResult;
float afHeights[CVehicleModelInfoData::HEIGHT_MAP_SIZE] = {-999.0f, -999.0f, -999.0f, -999.0f, -999.0f, -999.0f};
for(int i=0; i<CVehicleModelInfoData::HEIGHT_MAP_SIZE; i++)
{
segment.Set(avCapsulePositions[i] + 0.5f*fCapsuleLength*ZAXIS, avCapsulePositions[i] - 0.5f*fCapsuleLength*ZAXIS);
capsuleTester.InitSweptSphere(segment, fCapsuleRadius, &isectResult, 1);
if(capsuleTester.TestOneObject(*pCarTestBound))
{
float fHeight = isectResult.GetPosition().GetZf() - GetBoundingBoxMin().z;
if( fHeight > afHeights[i])
{
// As the base of the car is unlikely to be at the origin, use the minimum bounding box to get the true height
afHeights[i] = fHeight;
}
}
}
// remove the vehicle collision instance;
delete pTestInst;
pTestInst = NULL;
// Store the height map in the model
for( s32 i=0; i<CVehicleModelInfoData::HEIGHT_MAP_SIZE; i++ )
{
m_data->m_afHeightMap[i] = afHeights[i];
}
// Compute the bounding box min and max
Vector3 vBoundingMin, vBoundingMax;
vBoundingMin = GetBoundingBoxMin();
vBoundingMax = GetBoundingBoxMax();
// If vehicle is flagged to use coverbound info for cover generation
if( GetVehicleFlag(CVehicleModelInfoFlags::FLAG_USE_COVERBOUND_INFO_FOR_COVERGEN) )
{
// Check if the vehicle has specified cover bounds to use instead of the model info bounding box
const CVehicleCoverBoundOffsetInfo* pCoverBoundOffsetInfo = GetVehicleCoverBoundOffsetInfo();
if( pCoverBoundOffsetInfo )
{
const atArray<CVehicleCoverBoundInfo>& coverBoundsInfoArray = pCoverBoundOffsetInfo->GetCoverBoundInfoArray();
const int iCoverBoundCount = coverBoundsInfoArray.GetCount();
// If there is a list of bounds to use
if( iCoverBoundCount > 0 )
{
// initialize the mins at zeros and push them out using the list of bounds
vBoundingMin.Zero();
vBoundingMax.Zero();
// traverse the list of cover bounds, updating the effective bounding min and max values
for(int i=0; i < iCoverBoundCount; i++)
{
const CVehicleCoverBoundInfo& rCoverBoundInfo = coverBoundsInfoArray[i];
const Vec3V vMin = VECTOR3_TO_VEC3V(rCoverBoundInfo.m_Position) + (- Vec3V(rCoverBoundInfo.m_Width * 0.5f, rCoverBoundInfo.m_Length * 0.5f, rCoverBoundInfo.m_Height * 0.5f));
const Vec3V vMax = VECTOR3_TO_VEC3V(rCoverBoundInfo.m_Position) + ( Vec3V(rCoverBoundInfo.m_Width * 0.5f, rCoverBoundInfo.m_Length * 0.5f, rCoverBoundInfo.m_Height * 0.5f));
if( vMin.GetXf() < vBoundingMin.x ) { vBoundingMin.x = vMin.GetXf(); }
if( vMin.GetYf() < vBoundingMin.y ) { vBoundingMin.y = vMin.GetYf(); }
if( vMin.GetZf() < vBoundingMin.z ) { vBoundingMin.z = vMin.GetZf(); }
if( vMax.GetXf() > vBoundingMax.x ) { vBoundingMax.x = vMax.GetXf(); }
if( vMax.GetYf() > vBoundingMax.y ) { vBoundingMax.y = vMax.GetYf(); }
if( vMax.GetZf() > vBoundingMax.z ) { vBoundingMax.z = vMax.GetZf(); }
}
}
else // apply extra offsets
{
vBoundingMin.x = vBoundingMin.x + pCoverBoundOffsetInfo->GetExtraSideOffset();
vBoundingMax.x = vBoundingMax.x - pCoverBoundOffsetInfo->GetExtraSideOffset();
vBoundingMin.y = vBoundingMin.y + pCoverBoundOffsetInfo->GetExtraBackwardOffset();
vBoundingMax.y = vBoundingMax.y - pCoverBoundOffsetInfo->GetExtraForwardOffset();
vBoundingMax.z = vBoundingMax.z - pCoverBoundOffsetInfo->GetExtraZOffset();
}
}
}
// Get the object cover tuning, which may be the default
// Set conditions according to tuning settings.
const CCoverTuning& coverTuning = CCoverTuningManager::GetInstance().GetTuningForModel(GetHashKey());
bool bSkipNorthFaceEast = (coverTuning.m_Flags & CCoverTuning::NoCoverNorthFaceEast) > 0;
bool bSkipNorthFaceWest = (coverTuning.m_Flags & CCoverTuning::NoCoverNorthFaceWest) > 0;
bool bSkipSouthFaceEast = (coverTuning.m_Flags & CCoverTuning::NoCoverSouthFaceEast) > 0;
bool bSkipSouthFaceWest = (coverTuning.m_Flags & CCoverTuning::NoCoverSouthFaceWest) > 0;
bool bSkipEastFaceNorth = (coverTuning.m_Flags & CCoverTuning::NoCoverEastFaceNorth) > 0;
bool bSkipEastFaceSouth = (coverTuning.m_Flags & CCoverTuning::NoCoverEastFaceSouth) > 0;
bool bSkipWestFaceNorth = (coverTuning.m_Flags & CCoverTuning::NoCoverWestFaceNorth) > 0;
bool bSkipWestFaceSouth = (coverTuning.m_Flags & CCoverTuning::NoCoverWestFaceSouth) > 0;
bool bForceLowCornerNorthFaceEast = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerNorthFaceEast) > 0;
bool bForceLowCornerNorthFaceWest = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerNorthFaceWest) > 0;
bool bForceLowCornerSouthFaceEast = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerSouthFaceEast) > 0;
bool bForceLowCornerSouthFaceWest = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerSouthFaceWest) > 0;
bool bForceLowCornerEastFaceNorth = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerEastFaceNorth) > 0;
bool bForceLowCornerEastFaceSouth = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerEastFaceSouth) > 0;
bool bForceLowCornerWestFaceNorth = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerWestFaceNorth) > 0;
bool bForceLowCornerWestFaceSouth = (coverTuning.m_Flags & CCoverTuning::ForceLowCornerWestFaceSouth) > 0;
// Constant height for forcing low corner cover
const float fCoverHeightLowCorner = LOW_COVER_MAX_HEIGHT - 0.01f;
// Height map // Cover points
// // 1 2
// |-------| // 0|-----|3
// | 0 3 | // | |
// | | // | |
// | 1 4 | // | |
// | | // | |
// | 2 5 | // | |
// |-------| // 7|-----|4
// // 6 5
// Local variables
s32 iNextFreePoint = 0;
Vector3 vOffset;
float fCoverHeight;
float fEffectiveHeight;
CCoverPoint::eCoverHeight eCoverHeight;
CCoverPoint::eCoverUsage eEffectiveUsage;
float fCoverToVehicle;
TUNE_GROUP_FLOAT(COVER_TUNE, DIST_COVER_TO_VEH_LOW, 0.25f, 0.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(COVER_TUNE, DIST_COVER_TO_VEH_HIGH, 0.25f, 0.0f, 1.0f, 0.01f);
// Uber hack for B*1613530, I think the extra seat bones make the bounding box bigger, so we need separate offsets
TUNE_GROUP_FLOAT(COVER_TUNE, DIST_COVER_TO_VEH_LOW_GRANGER, -0.15f, -1.0f, 1.0f, 0.01f);
TUNE_GROUP_FLOAT(COVER_TUNE, DIST_COVER_TO_VEH_HIGH_GRANGER, -0.15f, -1.0f, 1.0f, 0.01f);
const u32 GRANGER_HASH = ATSTRINGHASH("granger", 0x9628879c);
const bool bIsGranger = (GetHashKey() == GRANGER_HASH);
const float fDistCoverToVehLow = bIsGranger ? DIST_COVER_TO_VEH_LOW_GRANGER : DIST_COVER_TO_VEH_HIGH;
const float fDistCoverToVehHigh = bIsGranger ? DIST_COVER_TO_VEH_HIGH_GRANGER : DIST_COVER_TO_VEH_HIGH;
// Left Bonnet Left (0)
if( !bSkipWestFaceNorth )
{
fCoverHeight = rage::Max( afHeights[0], afHeights[3] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? fDistCoverToVehLow : fDistCoverToVehHigh;
vOffset = Vector3( vBoundingMin.x - fCoverToVehicle, vBoundingMax.y - DISTANCE_FROM_COVER_TO_CORNER_SIDE_FRONT, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerWestFaceNorth )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
eEffectiveUsage = CCoverPoint::COVUSE_WALLTOBOTH;
if( eCoverHeight >= CCoverPoint::COVHEIGHT_TOOHIGH )
{
eEffectiveUsage = CCoverPoint::COVUSE_WALLTORIGHT;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 270.0f, fEffectiveHeight, eEffectiveUsage, CCoverPoint::COVARC_90, CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
// Left Bonnet Front (1)
if( !bSkipNorthFaceWest )
{
fCoverHeight = rage::Max( afHeights[0], afHeights[3] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? DIST_COVER_TO_VEH_LOW : DIST_COVER_TO_VEH_HIGH;
vOffset = Vector3( vBoundingMin.x + DISTANCE_FROM_COVER_TO_CORNER_DEFAULT, vBoundingMax.y + fCoverToVehicle, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerNorthFaceWest )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 180.0f, fEffectiveHeight, CCoverPoint::COVUSE_WALLTOLEFT, CCoverPoint::COVARC_90,CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
// Right Bonnet Front (2)
if( !bSkipNorthFaceEast )
{
fCoverHeight = rage::Max( afHeights[0], afHeights[3] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? DIST_COVER_TO_VEH_LOW : DIST_COVER_TO_VEH_HIGH;
vOffset = Vector3( vBoundingMax.x - DISTANCE_FROM_COVER_TO_CORNER_DEFAULT, vBoundingMax.y + fCoverToVehicle, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerNorthFaceEast )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 180.0f, fEffectiveHeight, CCoverPoint::COVUSE_WALLTORIGHT, CCoverPoint::COVARC_90,CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
// Right Bonnet Right (3)
if( !bSkipEastFaceNorth )
{
fCoverHeight = rage::Max( afHeights[0], afHeights[3] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? fDistCoverToVehLow : fDistCoverToVehHigh;
vOffset = Vector3( vBoundingMax.x + fCoverToVehicle, vBoundingMax.y - DISTANCE_FROM_COVER_TO_CORNER_SIDE_FRONT, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerEastFaceNorth )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
eEffectiveUsage = CCoverPoint::COVUSE_WALLTOBOTH;
if( eCoverHeight >= CCoverPoint::COVHEIGHT_TOOHIGH )
{
eEffectiveUsage = CCoverPoint::COVUSE_WALLTOLEFT;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 90.0f, fEffectiveHeight, eEffectiveUsage, CCoverPoint::COVARC_90,CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
// Right Boot Right (4)
if( !bSkipEastFaceSouth )
{
fCoverHeight = rage::Max( afHeights[2], afHeights[5] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? fDistCoverToVehLow : fDistCoverToVehHigh;
vOffset = Vector3( vBoundingMax.x + fCoverToVehicle, vBoundingMin.y + DISTANCE_FROM_COVER_TO_CORNER_DEFAULT, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerEastFaceSouth )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
eEffectiveUsage = CCoverPoint::COVUSE_WALLTOBOTH;
if( eCoverHeight >= CCoverPoint::COVHEIGHT_TOOHIGH )
{
eEffectiveUsage = CCoverPoint::COVUSE_WALLTORIGHT;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 90.0f, fEffectiveHeight, eEffectiveUsage, CCoverPoint::COVARC_90,CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
// Right Boot Rear (5)
if( !bSkipSouthFaceEast )
{
fCoverHeight = rage::Max( afHeights[2], afHeights[5] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? DIST_COVER_TO_VEH_LOW : DIST_COVER_TO_VEH_HIGH;
vOffset = Vector3( vBoundingMax.x - DISTANCE_FROM_COVER_TO_CORNER_DEFAULT, vBoundingMin.y - fCoverToVehicle, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerSouthFaceEast )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 0.0f, fEffectiveHeight, CCoverPoint::COVUSE_WALLTOLEFT, CCoverPoint::COVARC_90,CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
// Left Boot Rear (6)
if( !bSkipSouthFaceWest )
{
fCoverHeight = rage::Max( afHeights[2], afHeights[5] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? DIST_COVER_TO_VEH_LOW : DIST_COVER_TO_VEH_HIGH;
vOffset = Vector3( vBoundingMin.x + DISTANCE_FROM_COVER_TO_CORNER_DEFAULT, vBoundingMin.y - fCoverToVehicle, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerSouthFaceWest )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 0.0f, fEffectiveHeight, CCoverPoint::COVUSE_WALLTORIGHT, CCoverPoint::COVARC_90,CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
// Left Boot Left (7)
if( !bSkipWestFaceSouth )
{
fCoverHeight = rage::Max( afHeights[2], afHeights[5] );
fCoverToVehicle = fCoverHeight < HIGH_COVER_MAX_HEIGHT_FOR_VEHICLES ? fDistCoverToVehLow : fDistCoverToVehHigh;
vOffset = Vector3( vBoundingMin.x - fCoverToVehicle, vBoundingMin.y + DISTANCE_FROM_COVER_TO_CORNER_DEFAULT, vBoundingMin.z );
eCoverHeight = CCover::FindCoverPointHeight( fCoverHeight, CCoverPoint::COVTYPE_VEHICLE );
fEffectiveHeight = fCoverHeight;
// force low corner
if( eCoverHeight < CCoverPoint::COVHEIGHT_TOOHIGH || bForceLowCornerWestFaceSouth )
{
fEffectiveHeight = fCoverHeightLowCorner;
}
eEffectiveUsage = CCoverPoint::COVUSE_WALLTOBOTH;
if( eCoverHeight >= CCoverPoint::COVHEIGHT_TOOHIGH )
{
eEffectiveUsage = CCoverPoint::COVUSE_WALLTOLEFT;
}
m_data->m_aCoverPoints[iNextFreePoint].init( vOffset, 270.0f, fEffectiveHeight, eEffectiveUsage, CCoverPoint::COVARC_90,CCoverPoint::COVTYPE_VEHICLE );
iNextFreePoint++;
m_data->m_iCoverPoints++;
}
}
//-------------------------------------------------------------------------
// Builds a list of entry/exit points for peds to use when getting in the vehicle
//-------------------------------------------------------------------------
void CVehicleModelInfo::InitLayout( void )
{
modelinfoAssert(CVehicleMetadataMgr::GetInstance().GetIsInitialised());
modelinfoAssert(m_data);
const CVehicleLayoutInfo* pLayoutInfo = GetVehicleLayoutInfo();
vehicleAssertf(pLayoutInfo,"%s: This vehicle is missing layout information",GetModelName());
if(!pLayoutInfo)
{
return;
}
const crSkeletonData* pSkelData = GetFragType()->GetCommonDrawable()->GetSkeletonData();
m_data->m_SeatInfo.InitFromLayoutInfo(pLayoutInfo, pSkelData, GetNumSeatsOverride());
}
#define USE_BVH_BOUND_FOR_BOAT 1
dev_float STD_VEHICLE_LINEAR_V_COEFF = 0.01f;
dev_float STD_VEHICLE_LINEAR_C_COEFF = 0.01f;
dev_float CVehicleModelInfo::STD_VEHICLE_ANGULAR_V_COEFF = 0.01f;
dev_float CVehicleModelInfo::STD_BOAT_LINEAR_V_COEFF = 0.02f;
dev_float CVehicleModelInfo::STD_BOAT_LINEAR_C_COEFF = 0.1f;
dev_float CVehicleModelInfo::STD_BOAT_ANGULAR_V2_COEFF_Y = 0.04f;
dev_float PLANE_DOOR_INCREASED_BREAKING_STRENGTH = 20000.0f;
dev_float PLANE_DOOR_BREAKING_STRENGTH = 2000.0f;
dev_float TANK_DOOR_BREAKING_STRENGTH = -1.0f;
dev_float TRAILER_DOOR_MASS = 400.0f;
dev_float TRAILER_DOOR_ANG_INERTIA = 400.0f;
dev_float STD_VEHICLE_DOOR_MASS = 25.0f;
dev_float STD_VEHICLE_DOOR_ANG_INERTIA = 5.0f;
dev_float sfVehicleForceMargin = 0.04f;
dev_bool sbVehicleForceSetMarginAndShrink = false;
dev_float sfCarMinGroundClearance = 0.35f;
dev_float sfGP1MinGroundClearance = 0.23f;
dev_float sfRCCarMinGroundClearance = 0.15f;
dev_float sfJetpackMinGroundClearance = -0.1f;
dev_float sfExtraMinGroundClearance = 0.6f;
void CVehicleModelInfo::InitPhys()
{
modelinfoAssert(m_data);
fragType* pFragType = GetFragType();
modelinfoAssertf(pFragType!=NULL, "All vehicles MUST be fragments");
if(pFragType==NULL)
return;
// Build up a list of Entry/Exit points
InitLayout();
phArchetypeDamp* pPhysicsArch = pFragType->GetPhysics(0)->GetArchetype();
// All vehicles are type NON_BVH, but only vehicles with BVHs or physical wheels need BVH_TYPE or WHEEL_TEST respectively
u32 vehicleTypeFlags = ArchetypeFlags::GTA_VEHICLE_NON_BVH_TYPE;
if(static_cast<phBoundComposite*>(pPhysicsArch->GetBound())->GetContainsBVH())
{
vehicleTypeFlags |= ArchetypeFlags::GTA_VEHICLE_BVH_TYPE;
}
for(int wheelIndex = 0; wheelIndex < NUM_VEH_CWHEELS_MAX; ++wheelIndex)
{
int boneIndex = GetBoneIndex(ms_aSetupWheelIds[wheelIndex]);
if(boneIndex != -1 && pFragType->GetGroupFromBoneIndex(0,boneIndex) != -1)
{
vehicleTypeFlags |= ArchetypeFlags::GTA_WHEEL_TEST;
break;
}
}
// Set flags in archetype to say what type of physics object this is.
pPhysicsArch->SetTypeFlags(vehicleTypeFlags);
// Set flags in archetype to say what type of physics object we wish to collide with.
pPhysicsArch->SetIncludeFlags(ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES|ArchetypeFlags::GTA_VEHICLE_BVH_TYPE|ArchetypeFlags::GTA_STAIR_SLOPE_TYPE);
// find handling data for this vehicle
CHandlingData *pHandling = CHandlingDataMgr::GetHandlingData(GetHandlingId());
#ifdef CHECK_VEHICLE_SETUP
#if !__NO_OUTPUT
Vector3 oldCgOffset = VEC3V_TO_VECTOR3(GetFragType()->GetPhysics(0)->GetCompositeBounds()->GetCGOffset());
modelinfoDisplayf( "%s COM: %.2f %.2f %.2f", GetModelName(), oldCgOffset.x, oldCgOffset.y, oldCgOffset.z );
//Assert( oldCgOffset.Dist(pHandling->m_vecCentreOfMassAbsolute) < 0.01f || oldCgOffset.Dist(pHandling->m_vecCentreOfMassOffset) < 0.01f);//make sure the absolute COM is in the correct place or that the COM is near the modified COM
#endif // !__NO_OUTPUT
#endif
// allocate type and include flags for all vehicles
GetFragType()->SetAllocateTypeAndIncludeFlags();
for(int i=0; i<GetFragType()->GetPhysics(0)->GetNumChildGroups(); i++)
{
GetFragType()->GetPhysics(0)->GetAllGroups()[i]->SetStrength(-1.0f);
GetFragType()->GetPhysics(0)->GetAllGroups()[i]->SetForceTransmissionScaleUp(0.1f);
GetFragType()->GetPhysics(0)->GetAllGroups()[i]->SetForceTransmissionScaleDown(0.1f);
}
crSkeletonData *pSkeletonData = GetFragType()->GetCommonDrawable()->GetSkeletonData();
if(GetBoneIndex(VEH_LIGHTCOVER)!=-1)
{
crBoneData *pBone = (crBoneData *)pSkeletonData->GetBoneData(GetBoneIndex(VEH_LIGHTCOVER));
if(pBone && pBone->GetDofs() &(crBoneData::HAS_ROTATE_LIMITS|crBoneData::HAS_TRANSLATE_LIMITS))
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(VEH_LIGHTCOVER));
if(nGroupIndex != -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
pGroup->SetForceTransmissionScaleUp(0.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
pGroup->SetLatchStrength(-1.0f); // unlatch manually in vehicle damage code
}
}
}
crBoneData *pBone = NULL;
for(int i=0; i<NUM_VEH_DOORS_MAX; i++)
{
eHierarchyId nDoorId = ms_aSetupDoorIds[i];
if(GetBoneIndex(nDoorId)!=-1)
{
pBone = (crBoneData *)pSkeletonData->GetBoneData(GetBoneIndex(nDoorId));
if(pBone->GetDofs() &(crBoneData::HAS_ROTATE_LIMITS|crBoneData::HAS_TRANSLATE_LIMITS))
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(nDoorId));
if(nGroupIndex != -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
pGroup->SetForceTransmissionScaleUp(0.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
pGroup->SetLatchStrength(-1.0f); // unlatch manually in vehicle damage code
pGroup->SetPedScale(0.5f);//Peds should find it really hard to break doors
if(nDoorId!=VEH_BONNET) // Don't let bonnets fall off unless we make them in game code.
{
if(GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_TANK))
{
pGroup->SetStrength(TANK_DOOR_BREAKING_STRENGTH);
}
else if(GetVehicleType() == VEHICLE_TYPE_PLANE)
{
if( MI_PLANE_ALKONOST.IsValid() &&
GetModelNameHash() == MI_PLANE_ALKONOST.GetName().GetHash() )
{
pGroup->SetStrength(PLANE_DOOR_INCREASED_BREAKING_STRENGTH);
}
else
{
pGroup->SetStrength(PLANE_DOOR_BREAKING_STRENGTH);
}
}
else
{
pGroup->SetStrength(500.0f); // flags in fragInstGta will stop doors breaking off until we want them too
}
if( nDoorId == VEH_FOLDING_WING_L ||
nDoorId == VEH_FOLDING_WING_R )
{
pGroup->SetStrength(-1.0f);
}
}
if(GetVehicleType() == VEHICLE_TYPE_TRAILER)
{
// We don't want to allow the ramps at the back of trailers to fall off either, so
// initialise them with infinite joint strength.
if(nDoorId==VEH_BOOT)
{
pGroup->SetStrength(-1.0f);
}
}
}
}
}
}
// Determine if the vehicle has articulated doors
bool bHasArticulatedDoors = false;
if(pSkeletonData)
{
for(int i=0; i<NUM_VEH_DOORS_MAX; i++)
{
eHierarchyId nDoorId = ms_aSetupDoorIds[i];
if(GetBoneIndex(nDoorId)!=-1)
{
if(pSkeletonData->GetBoneData(GetBoneIndex(nDoorId))->GetDofs() &(crBoneData::HAS_ROTATE_LIMITS|crBoneData::HAS_TRANSLATE_LIMITS))
{
if(GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(nDoorId)) != -1)
{
bHasArticulatedDoors = true;
break;
}
}
}
}
}
// force vehicles to copy damping values into the articulated body
if(bHasArticulatedDoors)
GetFragType()->SetForceArticulatedDamping();
// Set up the specialized masses/angular inertias for specific groups on the fragment, then scale
// the fragment's mass/angular inertia to the specified values.
if(!GetFragType()->GetIsUserModified())
{
InitFragType(pHandling);
GetFragType()->SetIsUserModified();
}
InitFromFragType();
// NOTE: After this point nobody should be messing with the child masses/angular inertias!
// ---------------------------------------------------------------------------------------
if(GetIsRotaryAircraft())
{
// don't set breaking strengths for extras on heli's because they're reused as rotors and such.
// set boot breaking strength because that's should be the rear tail plane
if(GetBoneIndex(HELI_TAIL)!=-1 && GetVehicleType() != VEHICLE_TYPE_BLIMP)
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(HELI_TAIL));
if(nGroupIndex != -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
// GTAV B*1884536 increase strength of tail on Savage as it breaks off too easily
if( MI_HELI_SAVAGE.IsValid() &&
GetModelNameHash() == MI_HELI_SAVAGE.GetName().GetHash() )
{
pGroup->SetStrength( 20000.0f );
}
else
{
pGroup->SetStrength(2000.0f);
}
pGroup->SetForceTransmissionScaleUp(0.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
const s32 nChildGroupsPointersIdx = pGroup->GetChildGroupsPointersIndex();
if((nChildGroupsPointersIdx > 0) && (nChildGroupsPointersIdx < 255)) // looks like -1 becomes 255 when encoded as u8
{
// Prevent rotor collision detaching from tail
fragTypeGroup* pChildGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nChildGroupsPointersIdx];
pChildGroup->SetStrength(-1.0f);
pChildGroup->SetForceTransmissionScaleUp(0.0f);
pChildGroup->SetForceTransmissionScaleDown(0.0f);
}
}
}
}
else if (GetIsPlane())
{
// Planes use extras for flaps so don't let them break off
// Set strengths for wings and tail
// These are flagged on the instance not to break off until we want them to
int nStartId = PLANE_FIRST_BREAKABLE_PART;
for(int i = 0; i <PLANE_NUM_BREAKABLES; i++)
{
eHierarchyId nId = (eHierarchyId)(nStartId + i);
int iBoneIndex = GetBoneIndex(nId);
if(iBoneIndex > -1)
{
int nGroup = GetFragType()->GetGroupFromBoneIndex(0,iBoneIndex);
if(nGroup > -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroup];
pGroup->SetStrength(-1.0f);
pGroup->SetForceTransmissionScaleUp(0.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
}
}
}
GetFragType()->GetPhysics(0)->GetArchetype()->SetMaxSpeed(MAX_PLANE_SPEED);
}
// set breaking strengths on extras, if vehicle is flagged as having 'strong' extras, they're infinitely strong
else if(pSkeletonData && !GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_STRONG) && !GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_ONLY_BREAK_WHEN_DESTROYED))
{
float breakStrength = 2000.0f;
if( ( MI_CAR_TROPHY_TRUCK.IsValid() &&
GetModelNameHash() == MI_CAR_TROPHY_TRUCK.GetName().GetHash() ) ||
( MI_CAR_TROPHY_TRUCK2.IsValid() &&
GetModelNameHash() == MI_CAR_TROPHY_TRUCK2.GetName().GetHash() ) ||
GetModelNameHash() == MI_CAR_PBUS2.GetName().GetHash() )
{
static float sf_trophyTruckExtraBreakStrength = 20000.0f;
breakStrength = sf_trophyTruckExtraBreakStrength;
}
for(int nExtra=VEH_EXTRA_1; nExtra<=VEH_LAST_EXTRA; nExtra++)
{
if(GetBoneIndex(nExtra)!=-1 && (!GetVehicleFlag(CVehicleModelInfoFlags::FLAG_EXTRAS_CONVERTIBLE) || nExtra > VEH_EXTRA_4))
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(nExtra));
if(nGroupIndex != -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
pGroup->SetStrength( breakStrength );
}
}
}
if( pHandling->GetCarHandlingData() &&
pHandling->GetCarHandlingData()->aFlags & CF_USE_DOWNFORCE_BIAS )
{
static const int snNumSpecialExtras = 6;
int specialExtras[ snNumSpecialExtras ] = { VEH_EXTRA_6, // front wing
VEH_EXTRA_1, // rear wing
VEH_EXTRA_4, // rollover hoop
VEH_EXTRA_5, // main bodywork
VEH_EXTRA_7, // nose bodywork
VEH_EXTRA_8, // under tray
};
float specialExtraBreakingStrengths[ snNumSpecialExtras ] = { 8800.0f,
6000.0f,
17500.0f,
2250.0f,
1550.0f,
2600.0f,
};
for( int i = 0; i < snNumSpecialExtras; i++ )
{
if( GetBoneIndex( specialExtras[ i ] ) != -1 )
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex( 0, GetBoneIndex( specialExtras[ i ] ) );
if( nGroupIndex != -1 )
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics( 0 )->GetAllGroups()[ nGroupIndex ];
pGroup->SetStrength( specialExtraBreakingStrengths[ i ] );
}
}
}
}
}
// do some stuff to the bounds - must be done after SetupFragDoorAndWheels
// because code below uses CalculateHeightsAboveRoad which uses wheel radius pulled out of wheel bounds
if(sfVehicleForceMargin > 0.0f)
{
if(GetFragType()->GetPhysics(0)->GetArchetype()->GetBound()->GetType()==phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = ((phBoundComposite*)GetFragType()->GetPhysics(0)->GetArchetype()->GetBound());
for(int nBound=0; nBound<pBoundComposite->GetNumBounds(); nBound++)
{
phBound* pBound = pBoundComposite->GetBound(nBound);
if(pBound && pBound->GetType()==phBound::GEOMETRY)
{
phBoundGeometry* pBoundGeometry = (phBoundGeometry*)pBound;
if(sbVehicleForceSetMarginAndShrink)
pBoundGeometry->SetMarginAndShrink(sfVehicleForceMargin);
pBoundGeometry->SetMargin(sfVehicleForceMargin);
}
}
}
}
fwModelId modelId = CModelInfo::GetModelIdFromName( GetModelNameHash() );
bool bReducedGroundClearanceBike = (MI_BIKE_ESSKEY.IsValid() && modelId == MI_BIKE_ESSKEY);
bool bReducedGroundClearanceTrike = (MI_TRIKE_RROCKET.IsValid() && modelId == MI_TRIKE_RROCKET);
bool bIsJetPack = ( MI_JETPACK_THRUSTER.IsValid() && modelId == MI_JETPACK_THRUSTER );
bool forceGroundClearance = ( MI_BIKE_SHOTARO.IsValid() && modelId == MI_BIKE_SHOTARO ) ||
( MI_BIKE_SANCTUS.IsValid() && modelId== MI_BIKE_SANCTUS ) ||
( MI_TRIKE_CHIMERA.IsValid() && modelId == MI_TRIKE_CHIMERA ) ||
bIsJetPack ||
bReducedGroundClearanceBike ||
bReducedGroundClearanceTrike;
bool useGP1GroundClearance = ( MI_CAR_GP1.IsValid() && modelId == MI_CAR_GP1 ) ||
( MI_CAR_RUSTON.IsValid() && modelId == MI_CAR_RUSTON ) ||
( MI_CAR_CADDY3.IsValid() && modelId == MI_CAR_CADDY3 ) ||
( MI_CAR_DELUXO.IsValid() && modelId == MI_CAR_DELUXO ) ||
( MI_CAR_TEZERACT.IsValid() && modelId == MI_CAR_TEZERACT );
bool dontUpdateGroundClearance = ( GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR ) ||
( MI_CAR_CHERNOBOG.IsValid() && modelId == MI_CAR_CHERNOBOG );
bool useExtraGroundClearance = ( MI_TANK_KHANJALI.IsValid() && modelId == MI_TANK_KHANJALI );
// want to enforce min ground clearance
if((GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType()==VEHICLE_TYPE_SUBMARINECAR || GetVehicleType()==VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE || GetVehicleType()==VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE || forceGroundClearance) && (!(pHandling->mFlags & MF_DONT_FORCE_GRND_CLEARANCE) || (pHandling->mFlags & MF_IS_RC)) && !dontUpdateGroundClearance )//some vehicles explicitly state they dont want their ground clearance enforced
{
float fHeightAboveGroundF, fHeightAboveGroundR;
CalculateHeightsAboveRoad(&fHeightAboveGroundF, &fHeightAboveGroundR);
float fGroundClearance = 0.0f;
if((pHandling->mFlags & MF_IS_RC))
{
fGroundClearance = sfRCCarMinGroundClearance;
}
else
{
if( bIsJetPack )
{
fGroundClearance = sfJetpackMinGroundClearance;
}
else if( useExtraGroundClearance )
{
fGroundClearance = sfExtraMinGroundClearance;
}
else if( !useGP1GroundClearance )
{
fGroundClearance = sfCarMinGroundClearance;
}
else
{
fGroundClearance = sfGP1MinGroundClearance;
}
}
if( GetVehicleFlag( CVehicleModelInfoFlags::FLAG_DROP_SUSPENSION_WHEN_STOPPED ) )
{
fGroundClearance -= pHandling->m_fSuspensionRaise;
}
float fMinVertexPos = -rage::Max(0.0f, fHeightAboveGroundF - fGroundClearance);
// B*1889431: Make sure the Zentorno doesn't have bounds that are too high so it doesn't sink into the ground when it gets destroyed.
if(modelId == MI_CAR_ZENTORNO)
{
// This keeps the bounds as close in line with the body of the car as possible without being so low that it hits the kerb and other small objects.
fMinVertexPos -= 0.12f;
}
if( forceGroundClearance &&
GetVehicleType() != VEHICLE_TYPE_TRAILER)
{
if(bReducedGroundClearanceBike)
{
fMinVertexPos += 0.07f;
}
else if (bReducedGroundClearanceTrike)
{
fMinVertexPos -= 0.02f;
}
else
{
fMinVertexPos += 0.15f;
}
}
// get composite bound directly from frag type
phBoundComposite* pBoundComp = GetFragType()->GetPhysics(0)->GetCompositeBounds();
Vector3 vertPos;
#if REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
const int needsUpdateListSize = pBoundComp->GetNumBounds();
phBoundGeometry ** needsUpdateList = Alloca(phBoundGeometry*,needsUpdateListSize);
int needsUpdateListCount = 0;
#endif // REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
int rampBoundIndex = -1;
if( GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMP ) )
{
int iBoneIndex = GetBoneIndex( VEH_BUMPER_F );
if(iBoneIndex > -1)
{
rampBoundIndex = GetFragType()->GetComponentFromBoneIndex( 0, iBoneIndex );
}
}
int door_DSIDE_BoundIndex = -1;
int door_PSIDE_BoundIndex = -1;
bool bHasSpecialDoors = ((modelId == MI_CAR_CYCLONE2 || modelId == MI_CAR_CORSITA) ? true : false);
if (bHasSpecialDoors)
{
int iBoneIndex_DSIDE = GetBoneIndex(VEH_DOOR_DSIDE_F);
if (iBoneIndex_DSIDE > -1)
{
door_DSIDE_BoundIndex = GetFragType()->GetComponentFromBoneIndex(0, iBoneIndex_DSIDE);
}
int iBoneIndex_PSIDE = GetBoneIndex(VEH_DOOR_PSIDE_F);
if (iBoneIndex_PSIDE > -1)
{
door_PSIDE_BoundIndex = GetFragType()->GetComponentFromBoneIndex(0, iBoneIndex_PSIDE);
}
}
for(int nBound=0; nBound < pBoundComp->GetNumBounds(); nBound++)
{
// NOTE: dont touch door bounds on cyclone2
if (door_DSIDE_BoundIndex == nBound || door_PSIDE_BoundIndex == nBound)
{
continue;
}
if( nBound != rampBoundIndex )
{
if(pBoundComp->GetBound(nBound) && pBoundComp->GetBound(nBound)->GetType()==phBound::GEOMETRY)
{
phBoundGeometry* pBoundGeom = static_cast<phBoundGeometry*>(pBoundComp->GetBound(nBound));
const Matrix34& boundMatrix = RCC_MATRIX34(pBoundComp->GetCurrentMatrix(nBound));
bool shrunkVertsChanged = false;
for(int nVert=0; nVert < pBoundGeom->GetNumVertices(); nVert++)
{
// want to deform the shrunk geometry vertices, ignore the preshrunk ones since we want shape tests to hit the original bike
if(pBoundGeom->GetShrunkVertexPointer())
{
vertPos.Set(VEC3V_TO_VECTOR3(pBoundGeom->GetShrunkVertex(nVert)));
// Projecting the vertices along a cardinal axis like this shouldn't invalidate the octant map.
// A vertex that was shadowed before will remain shadowed afterward. A vertex that wasn't
// shadowed before may become shadowed but won't hurt anything if it's left in the octant map.
// Ideally this would be done in the converter. If any additional vertex manipulation is needed
// that invalidates the octant map then it will most certainly need to be done in the converter.
// Otherwise we will have to recompute the octant map here. This would require allocating memory
// which would wasteful since we can't free the octant map memory already used in the resource.
const float fMinVertexPos_ = fMinVertexPos - boundMatrix.d.z;
if (vertPos.z < fMinVertexPos_)
vertPos.z = fMinVertexPos_;
// need to remove const from vertex pointer to be able to set it's position
shrunkVertsChanged |= pBoundGeom->SetShrunkVertex(nVert, RCC_VEC3V(vertPos));
}
// no shrunk verts is unexpected
else
modelinfoAssert(false);
}
#if REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
if(shrunkVertsChanged)
{
Assert(needsUpdateListCount < needsUpdateListSize);
needsUpdateList[needsUpdateListCount] = pBoundGeom;
needsUpdateListCount++;
}
#endif // REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
}
}
}
#if REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
if (needsUpdateListCount > 0)
{
//sysTimer timer;
// This location is actually working with the "original" bounds
// There is no inst or cache entry yet to work in
// TODO: Figure out where to allocate from and then make sure we release that memory when this fragType is removed (Krehan mentioned that the gamevirt pool might be ok since our allocations should be in the <4k range)
//fragMemStartCacheHeapFunc(pFragInst->GetCacheEntry());
// for (int i = 0 ; i < needsUpdateListCount ; i++)
// needsUpdateList[i]->RecomputeOctantMap();
//fragMemEndCacheHeap();
//float time = timer.GetUsTime();
//Displayf("### MODELINFO Took %f to regenerate octant maps for %d bounds.",time,needsUpdateListCount);
}
#endif // REGENERATE_OCTANT_MAP_AFTER_DEFORMATION
}
// setup material flags on this bound
if(GetFragType()->GetPhysics(0)->GetArchetype()->GetBound())
CopyMaterialFlagsToBound(GetFragType()->GetPhysics(0)->GetArchetype()->GetBound());
// setup damping characteristics of different types of vehicle
if(GetIsRotaryAircraft() || GetVehicleType()==VEHICLE_TYPE_PLANE)
{
CFlyingHandlingData* pFlyingHandling = pHandling->GetFlyingHandlingData();
if(pFlyingHandling)
{
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_V2, Vector3(pHandling->m_fInitialDragCoeff, pHandling->m_fInitialDragCoeff, pHandling->m_fInitialDragCoeff));
float fLV = bHasArticulatedDoors ? 0.5f*pFlyingHandling->m_fMoveRes : pFlyingHandling->m_fMoveRes;
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_V, Vector3(fLV, fLV, fLV));
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_C, Vector3(STD_VEHICLE_LINEAR_C_COEFF, STD_VEHICLE_LINEAR_C_COEFF, STD_VEHICLE_LINEAR_C_COEFF));
if(MagSquared(pFlyingHandling->m_vecTurnRes).Getf() > 0.0f)
{
Vector3 vecAV = bHasArticulatedDoors ? 0.5f*RCC_VECTOR3(pFlyingHandling->m_vecTurnRes) : RCC_VECTOR3(pFlyingHandling->m_vecTurnRes);
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::ANGULAR_V, vecAV);
}
if(MagSquared(pFlyingHandling->m_vecSpeedRes).Getf() > 0.0f)
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::ANGULAR_V2, RCC_VECTOR3(pFlyingHandling->m_vecSpeedRes));
CalculateMaxFlatVelocityEstimate(pHandling); // Need max velocity for speed damping calculation
}
else
modelinfoAssert(false);
}
else
{
// force double linear damping for non-articulated vehicles to try and match the behaviour of articulated ones
float fDV = bHasArticulatedDoors ? STD_VEHICLE_LINEAR_V_COEFF : 2.0f*STD_VEHICLE_LINEAR_V_COEFF;
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_V, Vector3(fDV, fDV, 0.0f));
// same for linear angular damping
fDV = bHasArticulatedDoors ? STD_VEHICLE_ANGULAR_V_COEFF : 2.0f*STD_VEHICLE_ANGULAR_V_COEFF;
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::ANGULAR_V, Vector3(fDV, fDV, fDV));
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_V2, Vector3(pHandling->m_fInitialDragCoeff, pHandling->m_fInitialDragCoeff, 0.0f));
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_C, Vector3(STD_VEHICLE_LINEAR_C_COEFF, STD_VEHICLE_LINEAR_C_COEFF, 0.0f));
if(GetIsBoat() || GetIsAmphibiousVehicle() )
{
CBoatHandlingData* pBoatHandling = pHandling->GetBoatHandlingData();
if(pBoatHandling)
{
fDV = bHasArticulatedDoors ? STD_BOAT_LINEAR_V_COEFF : 2.0f*STD_BOAT_LINEAR_V_COEFF;
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_V, Vector3(fDV, fDV, 0.0f));
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::LINEAR_C, Vector3(STD_BOAT_LINEAR_C_COEFF, STD_BOAT_LINEAR_C_COEFF, 0.0f));
fDV = bHasArticulatedDoors ? 1.0f : 2.0f;
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::ANGULAR_V, fDV * RCC_VECTOR3(pBoatHandling->m_vecTurnResistance));
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::ANGULAR_V2, Vector3(0.02f, STD_BOAT_ANGULAR_V2_COEFF_Y, 0.02f));
CalculateMaxFlatVelocityEstimate(pHandling);
}
if( GetIsAmphibiousVehicle() )
{
CalculateMaxFlatVelocityEstimate(pHandling);
}
}
else if(GetIsSubmarine() || GetIsSubmarineCar())
{
const CSubmarineHandlingData* pSubHandling = pHandling->GetSubmarineHandlingData();
if(physicsVerifyf(pSubHandling,"Unexpected NULL sub handling for %s",GetModelName()))
{
fDV = bHasArticulatedDoors ? 1.0f : 2.0f;
GetFragType()->GetPhysics(0)->SetDamping(phArchetypeDamp::ANGULAR_V, fDV * pSubHandling->GetTurnRes());
}
// GTAV - B*1811173 - These need setting up so BRING_VEHICLE_TO_HALT works for the submarine car.
if( GetIsSubmarineCar() )
{
CalculateMaxFlatVelocityEstimate(pHandling);
}
}
else
{
CalculateMaxFlatVelocityEstimate(pHandling);
}
}
// Make sure type archetypes are in sync with the type's damping coeff
GetFragType()->GetPhysics(0)->ApplyDamping(GetFragType()->GetPhysics(0)->GetArchetype());
// create a much simpler physics archetype for use by dummy vehicles
if(!HasPhysics())
{
if(GetVehicleType()!=VEHICLE_TYPE_BOAT)
{
Vector3 vecBBoxHalfWidth, vecBBoxCentre;
GetFragType()->GetPhysics(0)->GetArchetype()->GetBound()->GetBoundingBoxHalfWidthAndCenter(RC_VEC3V(vecBBoxHalfWidth), RC_VEC3V(vecBBoxCentre));
// create a simple box bound
phBoundBox *pBoxBound= rage_new phBoundBox(1.9f*vecBBoxHalfWidth);
pBoxBound->SetCentroidOffset(RCC_VEC3V(vecBBoxCentre));
pBoxBound->SetMaterial(MATERIALMGR.FindMaterialId("car_metal"));
phArchetypeDamp* pArchetype = rage_new phArchetypeDamp;
// set flags in archetype to say what type of physics object this is
pArchetype->SetTypeFlags(ArchetypeFlags::GTA_BOX_VEHICLE_TYPE);
// set flags in archetype to say what type of physics object we wish to collide with
pArchetype->SetIncludeFlags(ArchetypeFlags::GTA_BOX_VEHICLE_INCLUDE_TYPES);
pArchetype->SetMass(GetFragType()->GetPhysics(0)->GetArchetype()->GetMass());
pArchetype->SetAngInertia(GetFragType()->GetPhysics(0)->GetArchetype()->GetAngInertia());
float fDV = 2.0f*STD_VEHICLE_LINEAR_V_COEFF;
pArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V2, Vector3(pHandling->m_fInitialDragCoeff, pHandling->m_fInitialDragCoeff, 0.0f));
pArchetype->ActivateDamping(phArchetypeDamp::LINEAR_V, Vector3(fDV, fDV, 0.0f));
pArchetype->ActivateDamping(phArchetypeDamp::LINEAR_C, Vector3(STD_VEHICLE_LINEAR_C_COEFF, STD_VEHICLE_LINEAR_C_COEFF, 0.0f));
// set angular damping to fragType defaults
pArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_C, Vector3(0.02f, 0.02f, 0.02f));
pArchetype->ActivateDamping(phArchetypeDamp::ANGULAR_V, Vector3(STD_VEHICLE_ANGULAR_V_COEFF, STD_VEHICLE_ANGULAR_V_COEFF, STD_VEHICLE_ANGULAR_V_COEFF));
pArchetype->SetBound(pBoxBound);
// Set the max speed based on the frag type so that these two are in sync from the beginning.
pArchetype->SetMaxSpeed(GetFragType()->GetPhysics(0)->GetArchetype()->GetMaxSpeed());
// need to relinquish control of the bounds we just created now that they're added to the archetype
pBoxBound->Release();
// Add a reference to the physics archetype
// as its loading and unloading is bound to this model
pArchetype->AddRef();
// finally store in modelinfo
//SetPhysics(pArchetype, -1);
CBaseModelInfo::SetPhysics(pArchetype);
}
}
// By this point the vehicle must have a physics inst
// Examine the vehicle at each corner to determine what height of cover is available
CalculateVehicleCoverPoints();
// The wheelbase multiplier is used by the ai to make large vehicles steer in earlier. Value between 0.0 and 1.0
m_wheelBaseMultiplier = ((GetFragType()->GetPhysics(0)->GetArchetype()->GetBound()->GetBoundingBoxMax().GetYf() - GetFragType()->GetPhysics(0)->GetArchetype()->GetBound()->GetBoundingBoxMin().GetYf()) - 5.0f) / 5.0f;
m_wheelBaseMultiplier = Clamp(m_wheelBaseMultiplier, 0.0f, 1.0f);
// Set up seat bound indices
// If they have collision then the vehicle has an open top
for(int i = 0; i < m_data->m_SeatInfo.GetNumSeats(); i++)
{
int iBoneIndex = m_data->m_SeatInfo.GetBoneIndexFromSeat(i);
if(iBoneIndex > -1)
{
s16 iComponentIndex = (s16)GetFragType()->GetComponentFromBoneIndex(0, iBoneIndex);
if(iComponentIndex > -1)
{
m_data->m_iSeatFragChildren[i] = iComponentIndex;
m_bHasSeatCollision = true;
}
}
}
}
void CVehicleModelInfo::CalculateMaxFlatVelocityEstimate(CHandlingData *pHandling)
{
// want to estimate max speed of this vehicle (assuming it's in top gear)
float fDv2 = pHandling->m_fInitialDragCoeff;
float fDv = 2.0f*STD_VEHICLE_LINEAR_V_COEFF;
float fDc = STD_VEHICLE_LINEAR_C_COEFF;
float fT = pHandling->m_fInitialDriveForce * -GRAVITY;
if(pHandling->GetFlyingHandlingData())
{
fDv = pHandling->GetFlyingHandlingData()->m_fMoveRes;
fT = pHandling->GetFlyingHandlingData()->m_fThrust * -GRAVITY;
}
float fSqrTerm = fDv*fDv - 4.0f * fDv2 * (fDc - fT);
float fMaxVel1 = (-fDv + rage::Sqrtf(fSqrTerm)) / (2.0f * fDv2);
float fMaxVel2 = (-fDv - rage::Sqrtf(fSqrTerm)) / (2.0f * fDv2);
pHandling->m_fEstimatedMaxFlatVel = rage::Min(fMaxVel1, fMaxVel2);
if(pHandling->m_fEstimatedMaxFlatVel < 0.0f)
pHandling->m_fEstimatedMaxFlatVel = rage::Max(fMaxVel1, fMaxVel2);
if(GetVehicleType() == VEHICLE_TYPE_HELI)
{
pHandling->m_fEstimatedMaxFlatVel = pHandling->m_fEstimatedMaxFlatVel;
}
else if(pHandling->GetFlyingHandlingData())
{
float fThrustFallOffSpeed = Sqrtf(1.0f/pHandling->GetFlyingHandlingData()->m_fThrustFallOff);
pHandling->m_fEstimatedMaxFlatVel = Min(fThrustFallOffSpeed, pHandling->m_fEstimatedMaxFlatVel);
if( pHandling->m_fEstimatedMaxFlatVel < 10.0f )
{
pHandling->m_fEstimatedMaxFlatVel = Max( pHandling->m_fEstimatedMaxFlatVel, pHandling->m_fBoostMaxSpeed );
}
}
else
{
pHandling->m_fEstimatedMaxFlatVel = Min(pHandling->m_fInitialDriveMaxVel, pHandling->m_fEstimatedMaxFlatVel);
}
}
dev_float VEHICLE_RUDDER_MULT = 0.1f;
dev_float VEHICLE_DRAG_MULT_XY = 15.0f;
dev_float VEHICLE_DRAG_MULT_ZUP = 200.0f;
dev_float VEHICLE_DRAG_MULT_ZDOWN = 150.0f;
dev_float BIKE_DRAG_MULT_XY = 50.0f;
dev_float BIKE_DRAG_MULT_ZUP = 125.0f;
dev_float BIKE_DRAG_MULT_ZDOWN = 75.0f;
dev_float PLANE_DRAG_MULT_XY = 300.0f;
dev_float PLANE_DRAG_MULT_ZUP = 300.0f;
dev_float PLANE_DRAG_MULT_ZDOWN = 500.0f;
void CVehicleModelInfo::InitWaterSamples()
{
#if __DEV
if(CBaseModelInfo::ms_bPrintWaterSampleEvents)
{
modelinfoDisplayf("[Buoyancy] Creating water samples for vehicle %s",GetModelName());
}
#endif
// Check we haven't done this before
modelinfoAssertf(m_pBuoyancyInfo == NULL,"InitWaterSamples() called when water samples already exist");
if(m_pBuoyancyInfo != NULL)
{
return;
}
// Create buoyancy info
m_pBuoyancyInfo = rage_new CBuoyancyInfo();
if(GetIsBoat() || GetIsAmphibiousVehicle())
{
GenerateWaterSamplesForBoat();
}
else if(GetIsSubmarine() || GetIsSubmarineCar())
{
GenerateWaterSamplesForSubmarine();
}
else if(GetIsPlane())
{
GenerateWaterSamplesForPlane();
}
else if(GetIsBike())
{
GenerateWaterSamplesForBike();
}
else if( GetIsHeli() )
{
GenerateWaterSamplesForHeli();
}
else
{
GenerateWaterSamplesForVehicle();
}
#if __DEV
ms_nNumWaterSamplesInMemory += m_pBuoyancyInfo->m_nNumWaterSamples;
PF_SET(NumWaterSamplesInMemory, ms_nNumWaterSamplesInMemory);
#endif // __DEV
}
void CVehicleModelInfo::GenerateWaterSamplesForBike()
{
modelinfoAssert(m_pBuoyancyInfo);
// Calculate how many samples we need
int iSamplesNumber = 0;
int iCurrentSample = 0;
Vector3 vecBBoxMin, vecBBoxMax;
phBoundComposite* pBoundComp = GetFragType()->GetPhysics(0)->GetCompositeBounds();
vecBBoxMin.Set(LARGE_FLOAT, LARGE_FLOAT, LARGE_FLOAT);
vecBBoxMax.Set(-LARGE_FLOAT, -LARGE_FLOAT, -LARGE_FLOAT);
Vector3 vecTemp, vecTemp2;
for(int i=0; i<GetFragType()->GetPhysics(0)->GetNumChildren(); i++)
{
if(GetFragType()->GetPhysics(0)->GetAllChildren()[i] && pBoundComp->GetBound(i))
{
if(pBoundComp->GetBound(i)->GetType() != phBound::DISC
USE_GEOMETRY_CURVED_ONLY(&& pBoundComp->GetBound(i)->GetType() != phBound::GEOMETRY_CURVED))
{
RCC_MATRIX34(pBoundComp->GetCurrentMatrix(i)).Transform(VEC3V_TO_VECTOR3(pBoundComp->GetBound(i)->GetBoundingBoxMin()), vecTemp);
vecTemp2.Set(vecBBoxMin);
vecBBoxMin.Min(vecTemp, vecTemp2);
RCC_MATRIX34(pBoundComp->GetCurrentMatrix(i)).Transform(VEC3V_TO_VECTOR3(pBoundComp->GetBound(i)->GetBoundingBoxMax()), vecTemp);
vecTemp2.Set(vecBBoxMax);
vecBBoxMax.Max(vecTemp, vecTemp2);
}
else
{
iSamplesNumber++;
}
}
}
int nMaxNumSteps = 3;
int nMinNumStepsX = 1;
int nMinNumStepsY = 3;
CHandlingData* pHandling = CHandlingDataMgr::GetHandlingData(m_handlingId);
modelinfoAssert(pHandling);
// Vehicle buoyancy samples are always defined by the height of the car
const float fSize = (vecBBoxMax.z - vecBBoxMin.z) / 2.0f;
// Rows / columns of samples in XY are decided by the number of samples we can fit in each dimension
int nNumStepsX = (int) ((vecBBoxMax.x - vecBBoxMin.x) / (2.0f*fSize));
int nNumStepsY = (int) ((vecBBoxMax.y - vecBBoxMin.y) / (2.0f*fSize));
// Clamp steps to min/max values
if(nNumStepsX < nMinNumStepsX)
nNumStepsX = nMinNumStepsX;
else if(nNumStepsX > nMaxNumSteps)
nNumStepsX = nMaxNumSteps;
if(nNumStepsY < nMinNumStepsY)
nNumStepsY = nMinNumStepsY;
else if(nNumStepsY > nMaxNumSteps)
nNumStepsY = nMaxNumSteps;
iSamplesNumber += nNumStepsX * nNumStepsY;
// Create array of water samples
modelinfoAssert(m_pBuoyancyInfo->m_WaterSamples == NULL);
m_pBuoyancyInfo->m_WaterSamples = rage_new CWaterSample[iSamplesNumber];
m_pBuoyancyInfo->m_nNumWaterSamples = (s16)iSamplesNumber;
for(int i=0; i<GetFragType()->GetPhysics(0)->GetNumChildren(); i++)
{
if(GetFragType()->GetPhysics(0)->GetAllChildren()[i] && pBoundComp->GetBound(i))
{
if(pBoundComp->GetBound(i)->GetType() == phBound::DISC
USE_GEOMETRY_CURVED_ONLY(|| pBoundComp->GetBound(i)->GetType() == phBound::GEOMETRY_CURVED))
{
const Matrix34& boundMatrix = RCC_MATRIX34(pBoundComp->GetCurrentMatrix(i));
m_pBuoyancyInfo->m_WaterSamples[iCurrentSample].m_vSampleOffset.Set(boundMatrix.d);
m_pBuoyancyInfo->m_WaterSamples[iCurrentSample].m_fSize = pBoundComp->GetBound(i)->GetRadiusAroundCentroid();
m_pBuoyancyInfo->m_WaterSamples[iCurrentSample].m_nComponent = 0;
m_pBuoyancyInfo->m_WaterSamples[iCurrentSample].m_fBuoyancyMult = 0.0f;
iCurrentSample++;
}
}
}
const float fStepX = (vecBBoxMax.x - vecBBoxMin.x) / (float)(nNumStepsX);
const float fStepY = (vecBBoxMax.y - vecBBoxMin.y) / (float)(nNumStepsY);
const float fCentreZ = vecBBoxMin.z + fSize;
float fSizeTotal = 0.0f;
float fX = vecBBoxMin.x + fStepX/2.0f;
for(int i=0; i<nNumStepsX; i++, fX += fStepX)
{
float fY = vecBBoxMin.y + fStepY/2.0f;
for(int j=0; j<nNumStepsY; j++, fY += fStepY)
{
// want to be very careful we don't overflow the array
if(iCurrentSample < iSamplesNumber)
{
m_pBuoyancyInfo->m_WaterSamples[iCurrentSample].m_vSampleOffset.Set(fX, fY, fCentreZ);
m_pBuoyancyInfo->m_WaterSamples[iCurrentSample].m_fSize = fSize;
fSizeTotal += fSize;
m_pBuoyancyInfo->m_WaterSamples[iCurrentSample].m_nComponent = 0;
iCurrentSample++;
}
}
}
if(pHandling)
{
m_pBuoyancyInfo->m_fBuoyancyConstant = CHandlingDataMgr::GetHandlingData(m_handlingId)->m_fBuoyancyConstant * -GRAVITY / fSizeTotal;
}
else
{
m_pBuoyancyInfo->m_fBuoyancyConstant = 0.0f;
}
m_pBuoyancyInfo->m_fLiftMult = 0.0f;//VEHICLE_LIFT_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fKeelMult = VEHICLE_RUDDER_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultXY = BIKE_DRAG_MULT_XY * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZUp = BIKE_DRAG_MULT_ZUP * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZDown = BIKE_DRAG_MULT_ZDOWN * -GRAVITY / fSizeTotal;
}
void CVehicleModelInfo::GenerateWaterSamplesForVehicle()
{
modelinfoAssert(m_pBuoyancyInfo);
// We might be adding some additional samples defined in vehicles.meta:
int nNumAdditionalSamples = 0;
CVehicleModelInfoBuoyancy* pBuoyancyExtension = GetExtension<CVehicleModelInfoBuoyancy>();
if(pBuoyancyExtension)
{
nNumAdditionalSamples = pBuoyancyExtension->GetNumAdditionalVfxSamples();
}
Vector3 vecBBoxMin, vecBBoxMax;
GetVehicleBBoxForBuoyancy(vecBBoxMin, vecBBoxMax);
// Vehicles will likely have a composite bound. Assume the first child bound of this composite is the chassis bound
// and therefore the one which we should apply the samples to. This avoids the problem of buoyancy samples being
// created outside the bounds of vehicles like the "rhino" where the turret expands the bounding box.
if(!GetIsBike() && GetFragType() && GetFragType()->GetPhysics(0) && GetFragType()->GetPhysics(0)->GetArchetype())
{
phBound* pBound = GetFragType()->GetPhysics(0)->GetArchetype()->GetBound();
if(pBound->GetType()==phBound::COMPOSITE)
{
// This should be the chassis bound?
pBound = static_cast<phBoundComposite*>(pBound)->GetBound(0);
Vector3 vRootBoundBBoxMin = VEC3V_TO_VECTOR3(pBound->GetBoundingBoxMin());
Vector3 vRootBoundBBoxMax = VEC3V_TO_VECTOR3(pBound->GetBoundingBoxMax());
// Don't use the root bound's bounding box size if we suspect this isn't the main bound. An example
// would be a pickup truck where the root bound only covers the cab.
if(vRootBoundBBoxMax.x-vRootBoundBBoxMin.x > 0.7f*(vecBBoxMax.x-vecBBoxMin.x) &&
vRootBoundBBoxMax.y-vRootBoundBBoxMin.y > 0.7f*(vecBBoxMax.y-vecBBoxMin.y))
{
// We want to keep the full bounding box height though as things like lorry containers / bikes can have
// bounds which don't quite follow our assumptions about the chassis bound.
vecBBoxMin.x = vRootBoundBBoxMin.x;
vecBBoxMax.x = vRootBoundBBoxMax.x;
vecBBoxMin.y = vRootBoundBBoxMin.y;
vecBBoxMax.y = vRootBoundBBoxMax.y;
}
}
}
int nMaxNumSteps = 0;
int nMinNumStepsX = 2;
int nMinNumStepsY = 2;
if(GetIsBike())
{
nMaxNumSteps = 2;
nMinNumStepsX = 1;
}
else
{
nMaxNumSteps = 3;
}
CHandlingData* pHandling = CHandlingDataMgr::GetHandlingData(m_handlingId);
modelinfoAssert(pHandling);
// Vehicle buoyancy samples are always defined by the height of the car
const float fSize = (vecBBoxMax.z - vecBBoxMin.z) / 2.0f;
// Rows / columns of samples in XY are decided by the number of samples we can fit in each dimension
int nNumStepsX = (int) ((vecBBoxMax.x - vecBBoxMin.x) / (2.0f*fSize));
int nNumStepsY = (int) ((vecBBoxMax.y - vecBBoxMin.y) / (2.0f*fSize));
// Clamp steps to min/max values
if(nNumStepsX < nMinNumStepsX)
nNumStepsX = nMinNumStepsX;
else if(nNumStepsX > nMaxNumSteps)
nNumStepsX = nMaxNumSteps;
if(nNumStepsY < nMinNumStepsY)
nNumStepsY = nMinNumStepsY;
else if(nNumStepsY > nMaxNumSteps)
nNumStepsY = nMaxNumSteps;
const u32 nNumMainSamples = nNumStepsX * nNumStepsY;
// Add space for the extra samples defined in metadata.
const u32 nTotalNumSamples = nNumMainSamples + nNumAdditionalSamples;
// Create array of water samples
modelinfoAssert(m_pBuoyancyInfo->m_WaterSamples == NULL);
m_pBuoyancyInfo->m_WaterSamples = rage_new CWaterSample[nTotalNumSamples];
m_pBuoyancyInfo->m_nNumWaterSamples = (s16)nTotalNumSamples;
const float fStepX = (vecBBoxMax.x - vecBBoxMin.x) / (float)(nNumStepsX);
const float fStepY = (vecBBoxMax.y - vecBBoxMin.y) / (float)(nNumStepsY);
const float fCentreZ = vecBBoxMin.z + fSize;
float fSizeTotal = fSize * nTotalNumSamples;
int n = 0;
float fX = vecBBoxMin.x + fStepX/2.0f;
for(int i=0; i<nNumStepsX; i++, fX += fStepX)
{
float fY = vecBBoxMin.y + fStepY/2.0f;
for(int j=0; j<nNumStepsY; j++, fY += fStepY, n++)
{
modelinfoAssert(n < nTotalNumSamples);
// want to be very careful we don't overflow the array
if(n < nTotalNumSamples)
{
m_pBuoyancyInfo->m_WaterSamples[n].m_vSampleOffset.Set(fX, fY, fCentreZ);
m_pBuoyancyInfo->m_WaterSamples[n].m_fSize = fSize;
m_pBuoyancyInfo->m_WaterSamples[n].m_nComponent = 0;
CVehicleModelInfoBuoyancy* pBuoyancyExtension = GetExtension<CVehicleModelInfoBuoyancy>();
if(pBuoyancyExtension)
{
m_pBuoyancyInfo->m_WaterSamples[n].m_vSampleOffset += pBuoyancyExtension->GetBuoyancySphereOffset();
m_pBuoyancyInfo->m_WaterSamples[n].m_fSize *= pBuoyancyExtension->GetBuoyancySphereSizeScale();
}
}
}
}
if(nNumAdditionalSamples > 0)
{
for(u32 i = 0; i < nNumAdditionalSamples; ++i)
{
u32 n = nNumMainSamples + i;
modelinfoAssert(n < nTotalNumSamples);
modelinfoAssert(pBuoyancyExtension);
// Want to be very careful we don't overflow the array.
if(n < nTotalNumSamples)
{
const CAdditionalVfxWaterSample* pAdditionalVfxSamples = pBuoyancyExtension->GetAdditionalVfxSamples();
m_pBuoyancyInfo->m_WaterSamples[n].m_vSampleOffset = pAdditionalVfxSamples[i].GetPosition();
m_pBuoyancyInfo->m_WaterSamples[n].m_fSize = pAdditionalVfxSamples[i].GetSize();
m_pBuoyancyInfo->m_WaterSamples[n].m_nComponent = (u16)pAdditionalVfxSamples[i].GetComponent();
// These samples are for VFX only and we don't want them affecting physics.
m_pBuoyancyInfo->m_WaterSamples[n].m_fBuoyancyMult = 0.0f;
}
}
}
if(pHandling)
{
m_pBuoyancyInfo->m_fBuoyancyConstant =
ComputeBuoyancyConstantFromSubmergeValue(CHandlingDataMgr::GetHandlingData(m_handlingId)->m_fBuoyancyConstant, fSizeTotal);
}
else
{
m_pBuoyancyInfo->m_fBuoyancyConstant = 0.0f;
}
m_pBuoyancyInfo->m_fLiftMult = 0.0f;//VEHICLE_LIFT_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fKeelMult = VEHICLE_RUDDER_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultXY = VEHICLE_DRAG_MULT_XY * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZUp = VEHICLE_DRAG_MULT_ZUP * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZDown = VEHICLE_DRAG_MULT_ZDOWN * -GRAVITY / fSizeTotal;
}
float CVehicleModelInfo::ComputeBuoyancyConstantFromSubmergeValue(float fSubmergeValue, float fSizeTotal, bool bRecompute)
{
// If we are recomputing the buoyancy constant, we won't have access to fSizeTotal and will need to recover it first.
if(!bRecompute)
{
return fSubmergeValue * -GRAVITY / fSizeTotal;
}
else
{
fSizeTotal = -GRAVITY * CHandlingDataMgr::GetHandlingData(m_handlingId)->m_fBuoyancyConstant / m_pBuoyancyInfo->m_fBuoyancyConstant;
return fSubmergeValue * -GRAVITY / fSizeTotal;
}
}
struct sMiniSampleData
{
int nComponentIndex;
float fBoundSize;
};
s32 MiniSampleCompareCB(const sMiniSampleData* e1, const sMiniSampleData* e2)
{
return (e1->fBoundSize < e2->fBoundSize) ? 1 : -1;
}
const u32 knNumAdditionalSeaPlaneSamples = 6;
void CVehicleModelInfo::GenerateWaterSamplesForPlane()
{
modelinfoAssert(m_pBuoyancyInfo);
modelinfoAssert(m_pBuoyancyInfo->m_WaterSamples == NULL);
CHandlingData* pHandling = CHandlingDataMgr::GetHandlingData(m_handlingId);
modelinfoAssert(pHandling);
bool bSamplesInitialised = false;
// We might be adding some additional samples defined in vehicles.meta:
int nNumAdditionalVfxSamples = 0;
CVehicleModelInfoBuoyancy* pBuoyancyExtension = GetExtension<CVehicleModelInfoBuoyancy>();
if(pBuoyancyExtension)
{
nNumAdditionalVfxSamples = pBuoyancyExtension->GetNumAdditionalVfxSamples();
}
// If this is a seaplane, we will be adding some buoyancy samples for the pontoons.
CSeaPlaneHandlingData* pSeaPlaneHandling = pHandling->GetSeaPlaneHandlingData();
u32 nNumAdditionalSeaPlaneSamples = 0;
if(pSeaPlaneHandling)
{
nNumAdditionalSeaPlaneSamples = knNumAdditionalSeaPlaneSamples;
}
Vector3 vecBBoxMin, vecBBoxMax;
GetVehicleBBoxForBuoyancy(vecBBoxMin, vecBBoxMax);
// Vehicle buoyancy samples are always defined by the height of the plane.
const float fSize = (vecBBoxMax.z - vecBBoxMin.z) / 2.0f;
float fSizeTotal = 0.0f;
u32 nNumMainSamples = 0;
u32 nTotalNumSamples = 0;
if(!GetIsBike() && GetFragType() && GetFragType()->GetPhysics(0) && GetFragType()->GetPhysics(0)->GetArchetype())
{
ASSERT_ONLY(float fLateralOffsetAccum = 0.0f);
phBound* pBound = GetFragType()->GetPhysics(0)->GetArchetype()->GetBound();
Assert(pBound);
if(pBound->GetType()==phBound::COMPOSITE)
{
phBoundComposite* pCompositeBound = static_cast<phBoundComposite*>(pBound);
Assert(pCompositeBound);
// Vector3 vecBBoxSize = VEC3V_TO_VECTOR3(pCompositeBound->GetBoundingBoxSize());
atArray<sMiniSampleData> aMiniSamples;
TUNE_FLOAT(fBoundSizeThreshold, 0.75f, 0.0f, 1.0f, 0.1f);
//const float fBoundSizeThreshold = 0.75f; // As a fraction of the bounding box in each dimension.
for(int i = 0; i < pCompositeBound->GetNumBounds(); ++i)
{
// For each bound which is larger than some fraction of the total bounding box, add a
// buoyancy sample. Hopefully this should make sure we at least get samples for the
// fuselage and the wings.
phBound* pChildBound = pCompositeBound->GetBound(i);
float fChildBoundMaxSize = Max(pChildBound->GetBoundingBoxSize().GetXf(), pChildBound->GetBoundingBoxSize().GetXf(),
pChildBound->GetBoundingBoxSize().GetXf());
if(pChildBound->GetVolume()>0.0f && fChildBoundMaxSize>fBoundSizeThreshold)
{
sMiniSampleData& newSample = aMiniSamples.Grow();
// Record this component's index so that we create a sample for it below.
newSample.nComponentIndex = i;
// Also record the size of this bound so we can pick the largest ones later.
newSample.fBoundSize = fChildBoundMaxSize;
}
}
// Sort by volume.
aMiniSamples.QSort(0, -1, MiniSampleCompareCB);
// Create the water samples.
modelinfoAssert(m_pBuoyancyInfo->m_WaterSamples == NULL);
nNumMainSamples = aMiniSamples.GetCount();
// Add space for the extra samples defined in metadata.
nTotalNumSamples = nNumMainSamples + nNumAdditionalVfxSamples + nNumAdditionalSeaPlaneSamples;
m_pBuoyancyInfo->m_WaterSamples = rage_new CWaterSample[nTotalNumSamples];
m_pBuoyancyInfo->m_nNumWaterSamples = (s16)nTotalNumSamples;
fSizeTotal = fSize * nTotalNumSamples;
TUNE_FLOAT(fSampleSizeMultiplier, 7.0f, 0.0f, 20.0f, 0.01f);
for(int n = 0; n < nNumMainSamples; ++n)
{
int nBoundId = aMiniSamples[n].nComponentIndex;
// Want to be very careful we don't overflow the array.
if(modelinfoVerifyf(n < nNumMainSamples, "Not enough space in water sample array."))
{
m_pBuoyancyInfo->m_WaterSamples[n].m_vSampleOffset.Set(VEC3V_TO_VECTOR3(pCompositeBound->GetBound(nBoundId)->GetCentroidOffset()));
m_pBuoyancyInfo->m_WaterSamples[n].m_fSize = fSize*fSampleSizeMultiplier/nTotalNumSamples;
m_pBuoyancyInfo->m_WaterSamples[n].m_nComponent = static_cast<u16>(nBoundId);
m_pBuoyancyInfo->m_WaterSamples[n].m_fBuoyancyMult = 1.0f;
m_pBuoyancyInfo->m_fLiftMult = 0.0f;//VEHICLE_LIFT_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fKeelMult = VEHICLE_RUDDER_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultXY = PLANE_DRAG_MULT_XY * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZUp = PLANE_DRAG_MULT_ZUP * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZDown = PLANE_DRAG_MULT_ZDOWN * -GRAVITY / fSizeTotal;
ASSERT_ONLY(fLateralOffsetAccum += (m_pBuoyancyInfo->m_WaterSamples[n].m_vSampleOffset.x));
}
}
}
// Assert that the buoyancy spheres are symmetric about the fuselage.
modelinfoAssertf(fabs(fLateralOffsetAccum) < 0.07f*(vecBBoxMax.x-vecBBoxMin.x),
"Buoyancy spheres not distributed evenly about fuselage. Offset=%5.3f", fLateralOffsetAccum);
// We're done.
bSamplesInitialised = true;
}
// If we didn't create valid samples above, just set the plane's buoyancy samples up using the entity bounding box method:
if(!bSamplesInitialised)
{
int nMaxNumSteps = 0;
const int nMinNumSteps = 2;
nMaxNumSteps = 3;
// Rows / columns of samples in XY are decided by the number of samples we can fit in each dimension
int nNumStepsX = (int) ((vecBBoxMax.x - vecBBoxMin.x) / (2.0f*fSize));
int nNumStepsY = (int) ((vecBBoxMax.y - vecBBoxMin.y) / (2.0f*fSize));
// Clamp steps to min/max values
if(nNumStepsX < nMinNumSteps)
nNumStepsX = nMinNumSteps;
else if(nNumStepsX > nMaxNumSteps)
nNumStepsX = nMaxNumSteps;
if(nNumStepsY < nMinNumSteps)
nNumStepsY = nMinNumSteps;
else if(nNumStepsY > nMaxNumSteps)
nNumStepsY = nMaxNumSteps;
nNumMainSamples = nNumStepsX * nNumStepsY;
// Add space for the extra samples defined in metadata.
nTotalNumSamples = nNumMainSamples + nNumAdditionalVfxSamples + nNumAdditionalSeaPlaneSamples;
// Create array of water samples
if(m_pBuoyancyInfo->m_WaterSamples)
{
delete [] m_pBuoyancyInfo->m_WaterSamples;
}
m_pBuoyancyInfo->m_WaterSamples = rage_new CWaterSample[nTotalNumSamples];
m_pBuoyancyInfo->m_nNumWaterSamples = (s16)nTotalNumSamples;
const float fStepX = (vecBBoxMax.x - vecBBoxMin.x) / (float)(nNumStepsX);
const float fStepY = (vecBBoxMax.y - vecBBoxMin.y) / (float)(nNumStepsY);
const float fCentreZ = vecBBoxMin.z + fSize;
fSizeTotal = fSize * nTotalNumSamples;
int n = 0;
float fX = vecBBoxMin.x + fStepX/2.0f;
for(int i=0; i<nNumStepsX; i++, fX += fStepX)
{
float fY = vecBBoxMin.y + fStepY/2.0f;
for(int j=0; j<nNumStepsY; j++, fY += fStepY, n++)
{
modelinfoAssert(n < nNumMainSamples);
// want to be very careful we don't overflow the array
if(n < nNumMainSamples)
{
m_pBuoyancyInfo->m_WaterSamples[n].m_vSampleOffset.Set(fX, fY, fCentreZ);
m_pBuoyancyInfo->m_WaterSamples[n].m_fSize = fSize;
m_pBuoyancyInfo->m_WaterSamples[n].m_nComponent = 0;
}
}
}
m_pBuoyancyInfo->m_fLiftMult = 0.0f;//VEHICLE_LIFT_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fKeelMult = VEHICLE_RUDDER_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultXY = VEHICLE_DRAG_MULT_XY * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZUp = VEHICLE_DRAG_MULT_ZUP * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZDown = VEHICLE_DRAG_MULT_ZDOWN * -GRAVITY / fSizeTotal;
} // !bSamplesInitialised
if(nNumAdditionalVfxSamples > 0)
{
for(u32 i = 0; i < nNumAdditionalVfxSamples; ++i)
{
u32 n = nNumMainSamples + i;
modelinfoAssert(n < nTotalNumSamples);
modelinfoAssert(pBuoyancyExtension);
// Want to be very careful we don't overflow the array.
if(n < nTotalNumSamples)
{
const CAdditionalVfxWaterSample* pAdditionalVfxSamples = pBuoyancyExtension->GetAdditionalVfxSamples();
m_pBuoyancyInfo->m_WaterSamples[n].m_vSampleOffset = pAdditionalVfxSamples[i].GetPosition();
m_pBuoyancyInfo->m_WaterSamples[n].m_fSize = pAdditionalVfxSamples[i].GetSize();
m_pBuoyancyInfo->m_WaterSamples[n].m_nComponent = (u16)pAdditionalVfxSamples[i].GetComponent();
// These samples are for VFX only and we don't want them affecting physics.
m_pBuoyancyInfo->m_WaterSamples[n].m_fBuoyancyMult = 0.0f;
}
}
}
if(nNumAdditionalSeaPlaneSamples > 0)
{
GeneratePontoonSamples( pSeaPlaneHandling, knNumAdditionalSeaPlaneSamples, nNumMainSamples + nNumAdditionalVfxSamples, nTotalNumSamples );
}
if(pHandling)
{
m_pBuoyancyInfo->m_fBuoyancyConstant = 2.0f*CHandlingDataMgr::GetHandlingData(m_handlingId)->m_fBuoyancyConstant * -GRAVITY / fSizeTotal;
}
else
{
m_pBuoyancyInfo->m_fBuoyancyConstant = 0.0f;
}
}
void CVehicleModelInfo::GeneratePontoonSamples( CSeaPlaneHandlingData* pSeaPlaneHandling, u32 numPontoonSamples, u32 sampleStartIndex, u32 nTotalNumSamples )
{
const u32 nPontoonComponentIdL = pSeaPlaneHandling->m_fLeftPontoonComponentId;
const u32 nPontoonComponentIdR = pSeaPlaneHandling->m_fRightPontoonComponentId;
if( AssertVerify( GetFragType() && GetFragType()->GetPhysics( 0 ) && GetFragType()->GetPhysics( 0 )->GetArchetype() ) )
{
phBound* pBound = GetFragType()->GetPhysics( 0 )->GetArchetype()->GetBound();
if( AssertVerify( pBound && pBound->GetType() == phBound::COMPOSITE ) )
{
phBoundComposite* pCompositeBound = static_cast<phBoundComposite*>( pBound );
// Get extents of the bounding boxes for each pontoon and use these to decide where to place the samples.
phBound* pPontoonBoundL = pCompositeBound->GetBound( nPontoonComponentIdL );
phBound* pPontoonBoundR = pCompositeBound->GetBound( nPontoonComponentIdR );
float fPontoonHalfLength = 0.5f*MaxElement( pPontoonBoundL->GetBoundingBoxSize() ).Getf();
float fPontoonHalfLengthScaled = fPontoonHalfLength * pSeaPlaneHandling->m_fPontoonLengthFractionForSamples;
Vector3 vPontoonBoundCentreL = VEC3V_TO_VECTOR3( pPontoonBoundL->GetCentroidOffset() );
Vector3 vPontoonBoundCentreR = VEC3V_TO_VECTOR3( pPontoonBoundR->GetCentroidOffset() );
float fPontoonFrontL = vPontoonBoundCentreL.y + fPontoonHalfLength;
if( GetIsHeli() )
{
fPontoonFrontL = vPontoonBoundCentreL.y + fPontoonHalfLength * pSeaPlaneHandling->m_fPontoonLengthFractionForSamples;
}
float fPontoonFrontR = vPontoonBoundCentreR.y + fPontoonHalfLength;
if( GetIsHeli() )
{
fPontoonFrontR = vPontoonBoundCentreR.y + fPontoonHalfLength * pSeaPlaneHandling->m_fPontoonLengthFractionForSamples;
}
float aSampleCoordsX[ knNumAdditionalSeaPlaneSamples ] =
{
vPontoonBoundCentreL.x, vPontoonBoundCentreL.x, vPontoonBoundCentreL.x,
vPontoonBoundCentreR.x, vPontoonBoundCentreR.x, vPontoonBoundCentreR.x
};
float aSampleCoordsY[ knNumAdditionalSeaPlaneSamples ] =
{
fPontoonFrontL, fPontoonFrontL - fPontoonHalfLengthScaled, fPontoonFrontL - 2.0f*fPontoonHalfLengthScaled,
fPontoonFrontR, fPontoonFrontR - fPontoonHalfLengthScaled, fPontoonFrontR - 2.0f*fPontoonHalfLengthScaled
};
float aSampleCoordsZ[ knNumAdditionalSeaPlaneSamples ] =
{
vPontoonBoundCentreL.z, vPontoonBoundCentreL.z, vPontoonBoundCentreL.z,
vPontoonBoundCentreR.z, vPontoonBoundCentreR.z, vPontoonBoundCentreR.z
};
float aSampleSizes[ knNumAdditionalSeaPlaneSamples ] =
{
pSeaPlaneHandling->m_fPontoonSampleSizeFront, pSeaPlaneHandling->m_fPontoonSampleSizeMiddle, pSeaPlaneHandling->m_fPontoonSampleSizeRear,
pSeaPlaneHandling->m_fPontoonSampleSizeFront, pSeaPlaneHandling->m_fPontoonSampleSizeMiddle, pSeaPlaneHandling->m_fPontoonSampleSizeRear
};
u32 aSampleComponentIds[ knNumAdditionalSeaPlaneSamples ] =
{
nPontoonComponentIdL, nPontoonComponentIdL, nPontoonComponentIdL,
nPontoonComponentIdR, nPontoonComponentIdR, nPontoonComponentIdR
};
float xOffset = 0.0f;
float zOffset = 0.0f;
if( pPontoonBoundL == pPontoonBoundR )
{
static dev_float seaplanePontoonOffset = 0.5f;
xOffset = seaplanePontoonOffset;
if( pPontoonBoundL == 0 )
{
static dev_float seaplanePontoonZOffset = -0.95f;
zOffset = seaplanePontoonZOffset;
}
}
//if( GetIsHeli() )
//{
// static dev_float seaHeliBuoyancyXOffset = -0.4f;
// xOffset = seaHeliBuoyancyXOffset;
//}
for( u32 i = 0; i < numPontoonSamples; ++i )
{
u32 n = sampleStartIndex + i;
modelinfoAssert( n < nTotalNumSamples );
// Want to be very careful we don't overflow the array.
if( n < nTotalNumSamples )
{
float sampleCoordX = aSampleCoordsX[ i ];
float sampleCoordZ = aSampleCoordsZ[ i ] + zOffset;
if( i >= numPontoonSamples / 2 )
{
sampleCoordX -= xOffset;
}
else
{
sampleCoordX += xOffset;
}
m_pBuoyancyInfo->m_WaterSamples[ n ].m_vSampleOffset = Vector3( sampleCoordX, aSampleCoordsY[ i ], sampleCoordZ );
m_pBuoyancyInfo->m_WaterSamples[ n ].m_fSize = aSampleSizes[ i ];
m_pBuoyancyInfo->m_WaterSamples[ n ].m_nComponent = (u16)aSampleComponentIds[ i ];
m_pBuoyancyInfo->m_WaterSamples[ n ].m_fBuoyancyMult = pSeaPlaneHandling->m_fPontoonBuoyConst;
m_pBuoyancyInfo->m_WaterSamples[ n ].m_bPontoon = 1;
m_pBuoyancyInfo->m_WaterSamples[ n ].m_bKeel = 1;
}
}
}
}
}
void CVehicleModelInfo::GenerateWaterSamplesForBoat()
{
CHandlingData* pHandling = CHandlingDataMgr::GetHandlingData(m_handlingId);
modelinfoAssert(pHandling);
CBoatHandlingData* pBoatHandling = pHandling->GetBoatHandlingData();
modelinfoAssert(pBoatHandling);
// Decide here if we want the generic boat buoyancy model or a specialised version for dingys (with buoyancy spheres
// around the outside where the inflatable part is).
bool bDinghySetup = pBoatHandling->m_fDinghySphereBuoyConst > 0.0f;
// calculate how many samples we want to use
const int nSampleStepsX = 5;
const int nSampleStepsY = 5;
// We want the jet-ski models to be self-righting and so we will add two extra buoyancy spheres
// outside of the craft's hull.
bool bAddSelfRightingSamples = false;
int nNumAdditionalSamples = 0;
(void)sizeof(nNumAdditionalSamples);
if(pHandling->hFlags & HF_SELF_RIGHTING_IN_WATER)
{
bAddSelfRightingSamples = true;
nNumAdditionalSamples = 2;
}
Assertf(!(bDinghySetup&&bAddSelfRightingSamples), "%s is configured to be set up as a dinghy and to add self-righting samples. Is this right?", m_gameName);
const u32 knMaxNumMainSamples = 25;
const u32 knNumSelfRightingSamples = 2;
const u32 knNumDinghySamples = 7;
const u32 knNumKeelSamples = 3;
const u32 knSizeOfSampleArray = knMaxNumMainSamples+knNumSelfRightingSamples+knNumDinghySamples+knNumKeelSamples;
atFixedArray<CWaterSample, knSizeOfSampleArray> tempWaterSamples(knSizeOfSampleArray);
Vector3 vecBoxMin , vecBoxMax;
GetVehicleBBoxForBuoyancy(vecBoxMin,vecBoxMax);
vecBoxMin.x *= pBoatHandling->m_fBoxSideMult;
vecBoxMax.x *= pBoatHandling->m_fBoxSideMult;
vecBoxMin.y *= pBoatHandling->m_fBoxRearMult;
vecBoxMax.y *= pBoatHandling->m_fBoxFrontMult;
float fStartX = -0.5f * (vecBoxMax.x - vecBoxMin.x);
float fStepSizeX = (vecBoxMax.x - vecBoxMin.x) / float(nSampleStepsX - 1);
float fStartY = -0.5f * (vecBoxMax.y - vecBoxMin.y);
float fStepSizeY = (vecBoxMax.y - vecBoxMin.y) / float(nSampleStepsY - 1);
// Going to test against boat bounds so samples are definitely corresponding to boat geometry
phIntersection intersection;
phSegment seg;
//phBound* pBoatBound = m_pPhysicsArch->GetBound();
if (!GetFragType())
{
vehicleAssertf(0, "Failed to get fragment type for vehicle %s", m_gameName);
return;
}
phBound* pBoatBound = GetFragType()->GetPhysics(0)->GetArchetype()->GetBound();
int nSample = 0;
float fSizeTotal = 0.0f;
phShapeTest<phShapeProbe> shapeTest;
int numWaterSampleRows = 0;
atFixedArray<int, nSampleStepsY> waterSampleRowFirstIndex(nSampleStepsY);
for(int i=0; i<nSampleStepsY; i++)
{
waterSampleRowFirstIndex[i] = -1;
}
for(int j=0; j<nSampleStepsY; j++)
{
bool waterSampleRowFirstIndexSet = false;
for(int i=0; i<nSampleStepsX; i++)
{
Vector3 vecTop(fStartX + i*fStepSizeX, fStartY + j*fStepSizeY, pBoatHandling->m_fSampleTop);
Vector3 vecBottom(fStartX + i*fStepSizeX, fStartY + j*fStepSizeY, vecBoxMin.z+pBoatHandling->m_fSampleBottomTestCorrection*(vecBoxMax.z-vecBoxMin.z));
seg.Set(vecBottom,vecTop);
intersection.Reset();
shapeTest.InitProbe(seg, &intersection);
if(shapeTest.TestOneObject(*pBoatBound))
{
Vector3 vecSamplePos = RCC_VECTOR3(intersection.GetPosition());
vecSamplePos.z -= pBoatHandling->m_fSampleBottom;
tempWaterSamples[nSample].m_vSampleOffset.Set(0.5f*(vecTop + vecSamplePos));
tempWaterSamples[nSample].m_fSize = 0.5f*rage::Abs(vecTop.z - vecSamplePos.z);
fSizeTotal += tempWaterSamples[nSample].m_fSize;
// If we are setting this boat up as a dinghy, it will be supported by additional buoyancy spheres
// around the outside. We still need the generic boat spheres for VFX, keel forces, etc. We just don't want them to supply
// much buoyancy force.
if(bDinghySetup)
{
tempWaterSamples[nSample].m_fBuoyancyMult = DINGHY_BUOYANCY_MULT_FOR_GENERIC_SAMPLES;
}
if (waterSampleRowFirstIndexSet==false)
{
waterSampleRowFirstIndex[numWaterSampleRows++] = nSample;
waterSampleRowFirstIndexSet = true;
}
nSample++;
}
if(nSample > knMaxNumMainSamples)
{
modelinfoAssert(false);
break;
}
}
if(nSample > knMaxNumMainSamples)
break;
}
// Add the extra buoyancy spheres here. These are outside the craft's hull as if coming off an
// imaginary mast.
static dev_float fSelfRightingMastHeight = 1.5f;
static dev_float fSelfRightingSampleSize = 0.1f;
static dev_float fSelfRightingMastOffset = 0.4f;
static dev_float fSelfRightingMultiplier = 3.0f;
if(bAddSelfRightingSamples)
{
tempWaterSamples[nSample].m_vSampleOffset.Set(Vector3(fSelfRightingMastOffset, 0.0f, fSelfRightingMastHeight));
tempWaterSamples[nSample].m_fSize = fSelfRightingSampleSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fSelfRightingMultiplier;
++nSample;
tempWaterSamples[nSample].m_vSampleOffset.Set(Vector3(-fSelfRightingMastOffset, 0.0f, fSelfRightingMastHeight));
tempWaterSamples[nSample].m_fSize = fSelfRightingSampleSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fSelfRightingMultiplier;
++nSample;
}
physicsAssert(nSample<=knSizeOfSampleArray);
// Add the extra dinghy inflatable side spheres here.
static dev_float fDinghySphereSize = 0.4f;
float fDinghySphereBuoyConst = pBoatHandling->m_fDinghySphereBuoyConst;
if(bDinghySetup)
{
// From left to right, back to front (like main samples).
tempWaterSamples[nSample].m_vSampleOffset.Set(-1.15f, -3.0f, 0.4f);
tempWaterSamples[nSample].m_fSize = fDinghySphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fDinghySphereBuoyConst;
++nSample;
tempWaterSamples[nSample].m_vSampleOffset.Set(+1.15f, -3.0f, 0.4f);
tempWaterSamples[nSample].m_fSize = fDinghySphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fDinghySphereBuoyConst;
++nSample;
// Second row from back.
tempWaterSamples[nSample].m_vSampleOffset.Set(-1.15f, -1.0f, 0.4f);
tempWaterSamples[nSample].m_fSize = fDinghySphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fDinghySphereBuoyConst;
++nSample;
tempWaterSamples[nSample].m_vSampleOffset.Set(+1.15f, -1.0f, 0.4f);
tempWaterSamples[nSample].m_fSize = fDinghySphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fDinghySphereBuoyConst;
++nSample;
// Front row not prow.
tempWaterSamples[nSample].m_vSampleOffset.Set(-1.15f, +1.0f, 0.4f);
tempWaterSamples[nSample].m_fSize = fDinghySphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fDinghySphereBuoyConst;
++nSample;
tempWaterSamples[nSample].m_vSampleOffset.Set(+1.15f, +1.0f, 0.4f);
tempWaterSamples[nSample].m_fSize = fDinghySphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fDinghySphereBuoyConst;
++nSample;
// Prow sample.
tempWaterSamples[nSample].m_vSampleOffset.Set(0.00f, +3.5f, 0.75f);
tempWaterSamples[nSample].m_fSize = fDinghySphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = fDinghySphereBuoyConst;
++nSample;
}
physicsAssert(nSample<=knSizeOfSampleArray);
// Add spheres for use by the keel, only add if the size is greater then 0
if(pBoatHandling->m_fKeelSphereSize > 0.0f)
{
tempWaterSamples[nSample].m_vSampleOffset.Set(Vector3( 0.0f, vecBoxMax.y, 0.0f));
tempWaterSamples[nSample].m_fSize = pBoatHandling->m_fKeelSphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = 0.0f;
tempWaterSamples[nSample].m_bKeel = true;
tempWaterSamples[nSample].m_nComponent = 0;
++nSample;
tempWaterSamples[nSample].m_vSampleOffset.Set(Vector3( 0.0f, 0.0f, 0.0f));
tempWaterSamples[nSample].m_fSize = pBoatHandling->m_fKeelSphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = 0.0f;
tempWaterSamples[nSample].m_bKeel = true;
tempWaterSamples[nSample].m_nComponent = 0;
++nSample;
tempWaterSamples[nSample].m_vSampleOffset.Set(Vector3( 0.0f, vecBoxMin.y, 0.0f));
tempWaterSamples[nSample].m_fSize = pBoatHandling->m_fKeelSphereSize;
tempWaterSamples[nSample].m_fBuoyancyMult = 0.0f;
tempWaterSamples[nSample].m_bKeel = true;
tempWaterSamples[nSample].m_nComponent = 0;
++nSample;
}
physicsAssert(nSample<=knSizeOfSampleArray);
const int iSamplesUsed = nSample;
m_pBuoyancyInfo->m_WaterSamples = rage_new CWaterSample[iSamplesUsed];
m_pBuoyancyInfo->m_nNumWaterSamples = (s16)iSamplesUsed;
m_pBuoyancyInfo->m_nNumBoatWaterSampleRows = (s8)numWaterSampleRows;
m_pBuoyancyInfo->m_nBoatWaterSampleRowIndices = rage_new s8[numWaterSampleRows];
for (int i=0; i<numWaterSampleRows; i++)
{
m_pBuoyancyInfo->m_nBoatWaterSampleRowIndices[i] = (s8)waterSampleRowFirstIndex[i];
}
// Copy temp water samples to new array
for(int iTempSampleIndex = 0 ; iTempSampleIndex < iSamplesUsed; iTempSampleIndex++)
{
/* *** Don't need to do this with the new CBuoyancy::Process() code ***
if(pBoatHandling->m_fKeelSphereSize <= 0.0f)
{
tempWaterSamples[iTempSampleIndex].m_bKeel = true;
}*/
m_pBuoyancyInfo->m_WaterSamples[iTempSampleIndex].Set(tempWaterSamples[iTempSampleIndex]);
}
// m_nNumSamplesToUse = (s16)rage::Min(nSample, nNumSamples);
// m_nNumSamplesInArray = m_nNumSamplesToUse;
if(pHandling)
{
m_pBuoyancyInfo->m_fBuoyancyConstant = pHandling->m_fBuoyancyConstant * -GRAVITY / fSizeTotal;
}
else
{
m_pBuoyancyInfo->m_fBuoyancyConstant = 0.0f;
}
m_pBuoyancyInfo->m_fLiftMult = pBoatHandling->m_fAquaplaneForce * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fKeelMult = pHandling->m_fTractionCurveLateral * -GRAVITY / fSizeTotal * pBoatHandling->m_fTractionMultiplier;
m_pBuoyancyInfo->m_fDragMultXY = pBoatHandling->m_vecMoveResistance.GetXf() * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZUp = pBoatHandling->m_vecMoveResistance.GetYf() * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZDown = pBoatHandling->m_vecMoveResistance.GetZf() * -GRAVITY / fSizeTotal;
}
void CVehicleModelInfo::GenerateWaterSamplesForSubmarine()
{
// Use vehicle water sample code but override the drag values
GenerateWaterSamplesForVehicle();
CHandlingData* pHandling = CHandlingDataMgr::GetHandlingData(m_handlingId);
modelinfoAssert(pHandling);
const CSubmarineHandlingData* pSubHandling = pHandling->GetSubmarineHandlingData();
if(physicsVerifyf(pSubHandling,"Unexpected NULL sub handling for %s",GetModelName()))
{
m_pBuoyancyInfo->m_fDragMultXY = pSubHandling->GetMoveResXY();
m_pBuoyancyInfo->m_fDragMultZUp = m_pBuoyancyInfo->m_fDragMultZDown = pSubHandling->GetMoveResZ();
}
// for the submarine car make sure the samples are evenly spaced so we don't get an extra torque
if( GetIsSubmarineCar() )
{
int numSamples = m_pBuoyancyInfo->m_nNumWaterSamples;
int halfNumSamples = numSamples / 2;
for( int i = 0; i < halfNumSamples; i++ )
{
int index = halfNumSamples - ( 1 + i );
if( index < i )
{
break;
}
if( index == i )
{
m_pBuoyancyInfo->m_WaterSamples[ i ].m_vSampleOffset.y = 0.0f;
m_pBuoyancyInfo->m_WaterSamples[ i + halfNumSamples ].m_vSampleOffset.y = 0.0f;
}
else
{
m_pBuoyancyInfo->m_WaterSamples[ index ].m_vSampleOffset.y = -m_pBuoyancyInfo->m_WaterSamples[ i ].m_vSampleOffset.y;
m_pBuoyancyInfo->m_WaterSamples[ index + halfNumSamples ].m_vSampleOffset.y = -m_pBuoyancyInfo->m_WaterSamples[ i + halfNumSamples ].m_vSampleOffset.y;
}
}
}
}
void CVehicleModelInfo::GenerateWaterSamplesForHeli()
{
modelinfoAssert( m_pBuoyancyInfo );
// We might be adding some additional samples defined in vehicles.meta:
int nNumAdditionalSamples = 0;
CVehicleModelInfoBuoyancy* pBuoyancyExtension = GetExtension<CVehicleModelInfoBuoyancy>();
if( pBuoyancyExtension )
{
nNumAdditionalSamples = pBuoyancyExtension->GetNumAdditionalVfxSamples();
}
Vector3 vecBBoxMin, vecBBoxMax;
GetVehicleBBoxForBuoyancy( vecBBoxMin, vecBBoxMax );
// Vehicles will likely have a composite bound. Assume the first child bound of this composite is the chassis bound
// and therefore the one which we should apply the samples to. This avoids the problem of buoyancy samples being
// created outside the bounds of vehicles like the "rhino" where the turret expands the bounding box.
if( !GetIsBike() && GetFragType() && GetFragType()->GetPhysics( 0 ) && GetFragType()->GetPhysics( 0 )->GetArchetype() )
{
phBound* pBound = GetFragType()->GetPhysics( 0 )->GetArchetype()->GetBound();
if( pBound->GetType() == phBound::COMPOSITE )
{
// This should be the chassis bound?
pBound = static_cast<phBoundComposite*>( pBound )->GetBound( 0 );
Vector3 vRootBoundBBoxMin = VEC3V_TO_VECTOR3( pBound->GetBoundingBoxMin() );
Vector3 vRootBoundBBoxMax = VEC3V_TO_VECTOR3( pBound->GetBoundingBoxMax() );
// Don't use the root bound's bounding box size if we suspect this isn't the main bound. An example
// would be a pickup truck where the root bound only covers the cab.
if( vRootBoundBBoxMax.x - vRootBoundBBoxMin.x > 0.7f*( vecBBoxMax.x - vecBBoxMin.x ) &&
vRootBoundBBoxMax.y - vRootBoundBBoxMin.y > 0.7f*( vecBBoxMax.y - vecBBoxMin.y ) )
{
// We want to keep the full bounding box height though as things like lorry containers / bikes can have
// bounds which don't quite follow our assumptions about the chassis bound.
vecBBoxMin.x = vRootBoundBBoxMin.x;
vecBBoxMax.x = vRootBoundBBoxMax.x;
vecBBoxMin.y = vRootBoundBBoxMin.y;
vecBBoxMax.y = vRootBoundBBoxMax.y;
}
}
}
int nMaxNumSteps = 3;
int nMinNumStepsX = 2;
int nMinNumStepsY = 2;
CHandlingData* pHandling = CHandlingDataMgr::GetHandlingData( m_handlingId );
modelinfoAssert( pHandling );
// If this is a seaplane, we will be adding some buoyancy samples for the pontoons.
CSeaPlaneHandlingData* pSeaPlaneHandling = pHandling->GetSeaPlaneHandlingData();
u32 nNumAdditionalSeaPlaneSamples = 0;
// Vehicle buoyancy samples are always defined by the height of the car
const float fSize = ( vecBBoxMax.z - vecBBoxMin.z ) / 2.0f;
static dev_float sfSeaHeliBuoyancySampleSizeScale = 0.5f;
float fBuoyancySizeScale = 1.0f;
if( pSeaPlaneHandling )
{
nNumAdditionalSeaPlaneSamples = knNumAdditionalSeaPlaneSamples;
fBuoyancySizeScale = sfSeaHeliBuoyancySampleSizeScale;
}
// Rows / columns of samples in XY are decided by the number of samples we can fit in each dimension
int nNumStepsX = (int)( ( vecBBoxMax.x - vecBBoxMin.x ) / ( 2.0f*fSize ) );
int nNumStepsY = (int)( ( vecBBoxMax.y - vecBBoxMin.y ) / ( 2.0f*fSize ) );
// Clamp steps to min/max values
if( nNumStepsX < nMinNumStepsX )
nNumStepsX = nMinNumStepsX;
else if( nNumStepsX > nMaxNumSteps )
nNumStepsX = nMaxNumSteps;
if( nNumStepsY < nMinNumStepsY )
nNumStepsY = nMinNumStepsY;
else if( nNumStepsY > nMaxNumSteps )
nNumStepsY = nMaxNumSteps;
const u32 nNumMainSamples = nNumStepsX * nNumStepsY;
// Add space for the extra samples defined in metadata.
const u32 nTotalNumSamples = nNumMainSamples + nNumAdditionalSamples + nNumAdditionalSeaPlaneSamples;
// Create array of water samples
modelinfoAssert( m_pBuoyancyInfo->m_WaterSamples == NULL );
m_pBuoyancyInfo->m_WaterSamples = rage_new CWaterSample[ nTotalNumSamples ];
m_pBuoyancyInfo->m_nNumWaterSamples = (s16)nTotalNumSamples;
const float fStepX = ( vecBBoxMax.x - vecBBoxMin.x ) / (float)( nNumStepsX );
const float fStepY = ( vecBBoxMax.y - vecBBoxMin.y ) / (float)( nNumStepsY );
const float fCentreZ = vecBBoxMin.z + fSize;
float fSizeTotal = fSize * nTotalNumSamples;
int n = 0;
float fX = vecBBoxMin.x + fStepX / 2.0f;
for( int i = 0; i < nNumStepsX; i++, fX += fStepX )
{
float fY = vecBBoxMin.y + fStepY / 2.0f;
for( int j = 0; j < nNumStepsY; j++, fY += fStepY, n++ )
{
modelinfoAssert( n < nTotalNumSamples );
// want to be very careful we don't overflow the array
if( n < nTotalNumSamples )
{
m_pBuoyancyInfo->m_WaterSamples[ n ].m_vSampleOffset.Set( fX, fY, fCentreZ );
m_pBuoyancyInfo->m_WaterSamples[ n ].m_fSize = fSize * fBuoyancySizeScale;
m_pBuoyancyInfo->m_WaterSamples[ n ].m_nComponent = 0;
CVehicleModelInfoBuoyancy* pBuoyancyExtension = GetExtension<CVehicleModelInfoBuoyancy>();
if( pBuoyancyExtension )
{
m_pBuoyancyInfo->m_WaterSamples[ n ].m_vSampleOffset += pBuoyancyExtension->GetBuoyancySphereOffset();
m_pBuoyancyInfo->m_WaterSamples[ n ].m_fSize *= pBuoyancyExtension->GetBuoyancySphereSizeScale();
}
}
}
}
if( nNumAdditionalSamples > 0 )
{
for( u32 i = 0; i < nNumAdditionalSamples; ++i )
{
u32 n = nNumMainSamples + i;
modelinfoAssert( n < nTotalNumSamples );
modelinfoAssert( pBuoyancyExtension );
// Want to be very careful we don't overflow the array.
if( n < nTotalNumSamples )
{
const CAdditionalVfxWaterSample* pAdditionalVfxSamples = pBuoyancyExtension->GetAdditionalVfxSamples();
m_pBuoyancyInfo->m_WaterSamples[ n ].m_vSampleOffset = pAdditionalVfxSamples[ i ].GetPosition();
m_pBuoyancyInfo->m_WaterSamples[ n ].m_fSize = pAdditionalVfxSamples[ i ].GetSize();
m_pBuoyancyInfo->m_WaterSamples[ n ].m_nComponent = (u16)pAdditionalVfxSamples[ i ].GetComponent();
// These samples are for VFX only and we don't want them affecting physics.
m_pBuoyancyInfo->m_WaterSamples[ n ].m_fBuoyancyMult = 0.0f;
}
}
}
if( nNumAdditionalSeaPlaneSamples > 0 )
{
GeneratePontoonSamples( pSeaPlaneHandling, knNumAdditionalSeaPlaneSamples, nNumMainSamples + nNumAdditionalSamples, nTotalNumSamples );
}
if( pHandling )
{
m_pBuoyancyInfo->m_fBuoyancyConstant =
ComputeBuoyancyConstantFromSubmergeValue( CHandlingDataMgr::GetHandlingData( m_handlingId )->m_fBuoyancyConstant, fSizeTotal );
}
else
{
m_pBuoyancyInfo->m_fBuoyancyConstant = 0.0f;
}
m_pBuoyancyInfo->m_fLiftMult = 0.0f;//VEHICLE_LIFT_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fKeelMult = VEHICLE_RUDDER_MULT * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultXY = VEHICLE_DRAG_MULT_XY * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZUp = VEHICLE_DRAG_MULT_ZUP * -GRAVITY / fSizeTotal;
m_pBuoyancyInfo->m_fDragMultZDown = VEHICLE_DRAG_MULT_ZDOWN * -GRAVITY / fSizeTotal;
}
void CVehicleModelInfo::GetVehicleBBoxForBuoyancy(Vector3& vecBBoxMin, Vector3& vecBBoxMax)
{
// for heli, want to get a bounding box that doesn't include the main rotor or the tail
if(GetIsRotaryAircraft())
{
phBoundComposite* pBoundComp = GetFragType()->GetPhysics(0)->GetCompositeBounds();
vecBBoxMin.Set(LARGE_FLOAT, LARGE_FLOAT, LARGE_FLOAT);
vecBBoxMax.Set(-LARGE_FLOAT, -LARGE_FLOAT, -LARGE_FLOAT);
Vector3 vecTemp, vecTemp2;
for(int i=0; i<GetFragType()->GetPhysics(0)->GetNumChildren(); i++)
{
if(GetFragType()->GetPhysics(0)->GetAllChildren()[i]->GetOwnerGroupPointerIndex()==0 && pBoundComp->GetBound(i))
{
RCC_MATRIX34(pBoundComp->GetCurrentMatrix(i)).Transform(VEC3V_TO_VECTOR3(pBoundComp->GetBound(i)->GetBoundingBoxMin()), vecTemp);
vecTemp2.Set(vecBBoxMin);
vecBBoxMin.Min(vecTemp, vecTemp2);
RCC_MATRIX34(pBoundComp->GetCurrentMatrix(i)).Transform(VEC3V_TO_VECTOR3(pBoundComp->GetBound(i)->GetBoundingBoxMax()), vecTemp);
vecTemp2.Set(vecBBoxMax);
vecBBoxMax.Max(vecTemp, vecTemp2);
}
}
}
else
{
vecBBoxMin = GetBoundingBoxMin();
vecBBoxMax = GetBoundingBoxMax();
}
}
const CVehicleScenarioLayoutInfo* CVehicleModelInfo::GetVehicleScenarioLayoutInfo() const
{
if(m_uVehicleScenarioLayoutInfoHash > 0)
{
return CVehicleMetadataMgr::GetInstance().GetVehicleScenarioLayoutInfo(m_uVehicleScenarioLayoutInfoHash);
}
return NULL;
}
const CVehicleLayoutInfo* CVehicleModelInfo::GetVehicleLayoutInfo() const
{
return m_pVehicleLayoutInfo;
}
const CPOVTuningInfo* CVehicleModelInfo::GetPOVTuningInfo() const
{
return m_pPOVTuningInfo;
}
const CVehicleCoverBoundOffsetInfo* CVehicleModelInfo::GetVehicleCoverBoundOffsetInfo() const
{
return m_pVehicleCoverBoundOffsetInfo;
}
const CFirstPersonDriveByLookAroundData* CVehicleModelInfo::GetFirstPersonLookAroundData(s32 iSeat) const
{
if(iSeat < m_apFirstPersonLookAroundData.GetCount())
{
return m_apFirstPersonLookAroundData[iSeat];
}
return NULL;
}
const CVehicleExplosionInfo* CVehicleModelInfo::GetVehicleExplosionInfo() const
{
return m_pVehicleExplosionInfo;
}
const CVehicleSeatInfo* CVehicleModelInfo::GetSeatInfo(int iSeatIndex) const
{
modelinfoAssert(m_data);
int iSeatIndexOnLayout = m_data->m_SeatInfo.GetLayoutSeatIndex(iSeatIndex);
if (iSeatIndexOnLayout > -1)
{
const CVehicleLayoutInfo* pLayoutInfo = GetVehicleLayoutInfo();
vehicleAssertf(pLayoutInfo,"%s This vehicle is missing layout information", GetModelName());
if(pLayoutInfo)
{
return pLayoutInfo->GetSeatInfo(iSeatIndexOnLayout);
}
}
return NULL;
}
const CVehicleEntryPointAnimInfo* CVehicleModelInfo::GetEntryPointAnimInfo(int iEntryIndex) const
{
modelinfoAssert(m_data);
int iEntryIndexOnLayout = m_data->m_SeatInfo.GetLayoutEntrypointIndex(iEntryIndex);
if(iEntryIndexOnLayout > -1)
{
const CVehicleLayoutInfo* pLayoutInfo = GetVehicleLayoutInfo();
vehicleAssertf(pLayoutInfo,"%s This vehicle is missing layout information", GetModelName());
if(pLayoutInfo)
{
return pLayoutInfo->GetEntryPointAnimInfo(iEntryIndexOnLayout);
}
}
return NULL;
}
const CVehicleSeatAnimInfo* CVehicleModelInfo::GetSeatAnimationInfo(int iSeatIndex) const
{
modelinfoAssert(m_data);
int iSeatIndexOnLayout = m_data->m_SeatInfo.GetLayoutSeatIndex(iSeatIndex);
if (iSeatIndexOnLayout > -1)
{
const CVehicleLayoutInfo* pLayoutInfo = GetVehicleLayoutInfo();
vehicleAssertf(pLayoutInfo,"%s This vehicle is missing layout information", GetModelName() ? GetModelName() : "NULL");
if(pLayoutInfo)
{
return pLayoutInfo->GetSeatAnimationInfo(iSeatIndexOnLayout);
}
}
return NULL;
}
const CVehicleEntryPointInfo* CVehicleModelInfo::GetEntryPointInfo(int iEntryPointIndex) const
{
int iEntryPointIndexOnLayout = m_data->m_SeatInfo.GetLayoutEntrypointIndex(iEntryPointIndex);
if(iEntryPointIndexOnLayout > -1)
{
const CVehicleLayoutInfo* pLayoutInfo = GetVehicleLayoutInfo();
vehicleAssertf(pLayoutInfo,"%s This vehicle is missing layout information", GetModelName());
if(pLayoutInfo)
{
return pLayoutInfo->GetEntryPointInfo(iEntryPointIndexOnLayout);
}
}
return NULL;
}
s32 CVehicleModelInfo::GetSeatIndex(const CVehicleSeatInfo* pSeatInfo) const
{
modelinfoAssert(m_data);
modelinfoAssert(pSeatInfo);
for(int i = 0; i < m_data->m_SeatInfo.GetNumSeats(); i++)
{
if(GetSeatInfo(i) == pSeatInfo)
{
return i;
}
}
return -1;
}
s32 CVehicleModelInfo::GetEntryPointIndex(const CVehicleEntryPointInfo* pEntryPointInfo) const
{
modelinfoAssert(m_data);
modelinfoAssert(pEntryPointInfo);
for(int i = 0; i < m_data->m_SeatInfo.GetNumberEntryExitPoints(); i++)
{
if(GetEntryPointInfo(i) == pEntryPointInfo)
{
return i;
}
}
return -1;
}
void CVehicleModelInfo::SetBonesGroupMassAndAngularInertia(s32 boneIndex, float mass, const Vector3& angularInertia, fragGroupBits& tunedGroups, bool includeSubTree)
{
if(boneIndex!=-1)
{
fragType* type = GetFragType();
int groupIndex = type->GetGroupFromBoneIndex(0, boneIndex);
//int componentIndex = type->GetComponentFromBoneIndex( 0, boneIndex );
if(groupIndex != -1)
{
fragPhysicsLOD* physicsLOD = type->GetPhysics(0);
//int parentBoneIndex = boneIndex;
//
//while( componentIndex > -1 )
//{
// if( !physicsLOD->IsChildAngularInertiaScalable( (u8)componentIndex ).Getb() )
// {
// Warningf( "Wasn't able to set desired angular inertia of <%f, %f, %f> on group (%s,%i) component %i of %s",VEC3V_ARGS(RCC_VEC3V(angularInertia)),type->GetPhysics(0)->GetGroup(groupIndex)->GetDebugName(),groupIndex,componentIndex,GetModelName());
// return;
// }
// componentIndex = -1;
// parentBoneIndex = GetDrawable()->GetSkeletonData()->GetParentIndex( parentBoneIndex );
// if( parentBoneIndex > -1 )
// {
// componentIndex = type->GetComponentFromBoneIndex( 0, parentBoneIndex );
// }
//}
ASSERT_ONLY(BoolV wasAngularInertiaSet;)
if(includeSubTree)
{
fragGroupBits groupsInSubTree;
physicsLOD->GetGroupsAbove((u8)groupIndex,groupsInSubTree);
tunedGroups.Union(groupsInSubTree);
ASSERT_ONLY(wasAngularInertiaSet =) physicsLOD->SetUndamagedGroupTreeMassAndAngularInertia((u8)groupIndex,ScalarVFromF32(mass),RCC_VEC3V(angularInertia));
}
else
{
tunedGroups.Set(groupIndex);
ASSERT_ONLY(wasAngularInertiaSet =) physicsLOD->SetUndamagedGroupMassAndAngularInertia((u8)groupIndex,ScalarVFromF32(mass),RCC_VEC3V(angularInertia));
}
ASSERT_ONLY(if(!wasAngularInertiaSet.Getb()) Warningf("Wasn't able to set desired angular inertia of <%f, %f, %f> on group (%s,%i) of %s",VEC3V_ARGS(RCC_VEC3V(angularInertia)),type->GetPhysics(0)->GetGroup(groupIndex)->GetDebugName(),groupIndex,GetModelName()));
}
}
}
void CVehicleModelInfo::SetJointStiffness(s32 boneIndex, float fStiffness, fragGroupBits& tunedGroups)
{
if(boneIndex!=-1)
{
fragType* type = GetFragType();
int groupIndex = type->GetGroupFromBoneIndex(0, boneIndex);
if(groupIndex != -1)
{
fragPhysicsLOD* physicsLOD = type->GetPhysics(0);
fragTypeGroup& group = *physicsLOD->GetGroup(groupIndex);
group.SetJointStiffness(fStiffness);
tunedGroups.Set(groupIndex);
}
}
}
void CVehicleModelInfo::SetBonesGroupMass(s32 boneIndex, float mass, fragGroupBits& tunedGroups)
{
const int currentLOD = 0;
if(boneIndex!=-1)
{
fragType* type = GetFragType();
//int componentIndex = type->GetComponentFromBoneIndex( 0, boneIndex );
//fragPhysicsLOD* physicsLOD = type->GetPhysics(0);
//int parentBoneIndex = boneIndex;
//while( componentIndex > -1 )
//{
// if( !physicsLOD->IsChildAngularInertiaScalable( (u8)componentIndex ).Getb() )
// {
// Warningf( "Wasn't able to set mass of %f component %i of %s",mass,componentIndex,GetModelName());
// return;
// }
// componentIndex = -1;
// parentBoneIndex = GetDrawable()->GetSkeletonData()->GetParentIndex( parentBoneIndex );
// if( parentBoneIndex > -1 )
// {
// componentIndex = type->GetComponentFromBoneIndex( 0, parentBoneIndex );
// }
//}
SetGroupMass(type->GetGroupFromBoneIndex(currentLOD, boneIndex), mass, tunedGroups);
}
}
void CVehicleModelInfo::SetGroupMass(s32 groupIndex, float mass, fragGroupBits& tunedGroups)
{
const int currentLOD = 0;
if(groupIndex != -1)
{
fragType* type = GetFragType();
fragPhysicsLOD* physicsLOD = type->GetPhysics(currentLOD);
fragTypeGroup* pGroup = physicsLOD->GetGroup(groupIndex);
if(pGroup != NULL)
{
// Set the group mass
pGroup->SetTotalUndamagedMass(currentLOD, mass, type);
// Update the angular inertias for just the children of this changed group
u8 numChildren = pGroup->GetNumChildren();
u8 childEndIndex = numChildren + pGroup->GetChildFragmentIndex();
for(u8 childIndex = pGroup->GetChildFragmentIndex(); childIndex < childEndIndex; childIndex++)
{
fragTypeChild& child = *(physicsLOD->GetChild(childIndex));
// if( physicsLOD->IsChildAngularInertiaScalable( (u8)childIndex ).Getb() )
{
if(child.GetUndamagedEntity() && child.GetUndamagedEntity()->GetBound())
{
physicsLOD->SetUndamagedAngInertia(childIndex,VEC3V_TO_VECTOR3(child.GetUndamagedEntity()->GetBound()->GetComputeAngularInertia(child.GetUndamagedMass())));
}
else
{
physicsLOD->SetUndamagedAngInertia(childIndex,VEC3V_TO_VECTOR3(Vec3V(V_ZERO)));
}
if(child.GetDamagedEntity() && child.GetDamagedEntity()->GetBound())
{
physicsLOD->SetDamagedAngInertia(childIndex,VEC3V_TO_VECTOR3(child.GetDamagedEntity()->GetBound()->GetComputeAngularInertia(child.GetDamagedMass())));
}
else
{
physicsLOD->SetDamagedAngInertia(childIndex,VEC3V_TO_VECTOR3(Vec3V(V_ZERO)));
}
}
}
tunedGroups.Set(groupIndex);
}
}
}
Vector3 BIKE_ANG_INERTIAL_MULT(1.0f, 1.0f, 1.0f);
//
eHierarchyId CVehicleModelInfo::ms_aSetupDoorIds[NUM_VEH_DOORS_MAX] = { VEH_DOOR_DSIDE_F, VEH_DOOR_DSIDE_R, VEH_DOOR_PSIDE_F, VEH_DOOR_PSIDE_R, VEH_BONNET, VEH_BOOT, VEH_BOOT_2, VEH_FOLDING_WING_L, VEH_FOLDING_WING_R };
eHierarchyId CVehicleModelInfo::ms_aSetupWheelIds[NUM_VEH_CWHEELS_MAX] = {
VEH_WHEEL_LF,
VEH_WHEEL_RF,
VEH_WHEEL_LR,
VEH_WHEEL_RR,
VEH_WHEEL_LM1,
VEH_WHEEL_RM1,
VEH_WHEEL_LM2,
VEH_WHEEL_RM2,
VEH_WHEEL_LM3,
VEH_WHEEL_RM3
};
#define WHEEL_LF_INDEX (0)
dev_float STD_VEHICLE_BUMPER_MASS = 25.0f;
dev_float YACHT_MAST_STIFFNESS = 0.9f;
dev_float STD_HELI_ROTOR_MASS = 100.0f;
dev_float STD_HELI_ROTOR_ANG_INERTIA = 500.0f;
dev_float STD_HELI_TAIL_ROTOR_MASS = 50.0f;
dev_float STD_HELI_TAIL_ROTOR_ANG_INERTIA = 50.0f;
dev_float STD_WHEEL_MASS = 5.0f;
const Vector3 STD_WHEEL_INERTIA (0.65f,0.35f,0.35f);
dev_float STD_LANDING_GEAR_MASS = 5.0f;
dev_float STD_SEAT_MASS = 1.0f;
dev_float STD_SEAT_ANG_INERTIA = 1.0f;
dev_float STD_DIGGER_ARM_MASS = 200.0f;
dev_float STD_DIGGER_ARM_ANG_INERTIA = 200.0f;
dev_float STD_VEHICLE_BLOCKER_MASS = 1.0f;
dev_float STD_VEHICLE_BLOCKER_ANG_INERTIA = 1.0f;
dev_float STD_TANK_TURRET_MASS = 1100.0f;
dev_float LIGHT_TANK_TURRET_MASS = 550.0f;
dev_float BOAT_TURRET_MASS = 10.0f;
dev_float RC_CAR_TURRET_MASS = 2.5f;
dev_float STD_FIRE_TRUCK_TURRET_MASS = 50.0f;
dev_float STD_EOD_ARM_MASS = 1.0f;
dev_float STD_EOD_ARM_ANG_INERTIA = 1.0f;
dev_float STD_EXTRA_MASS = 10.0f;
dev_float STD_BREAKABLE_EXTRA_MASS = 10.0f;
dev_float STD_MOD_COLLISION_MASS = 1.0f;
dev_float STD_MOD_COLLISION_ANG_INERTIA = 1.0f;
dev_float STD_TOW_ARM_MASS = 350.0f;
dev_float STD_TOW_ARM_ANG_INERTIA = 350.0f;
dev_float STD_FORKLIFT_MASS = 40.0f;
dev_float STD_FORKLIFT_ANG_INERTIA = 10.0f;
dev_float STD_HANDLER_FRAME_MASS = 100.0f;
dev_float STD_HANDLER_FRAME_ANG_INERTIA = 10.0f;
dev_float STD_FREIGHTCONT2_MASS = 20042.0;
dev_float STD_FREIGHTCONT2_ANG_INERTIA = 20042.0;
#define STD_LOW_LOD_CHASSIS_MASS 1.0f
#define STD_LOW_LOD_CHASSIS_ANG_INERTIA 1.0f
#define STD_DUMMY_CHASSIS_MASS 1.0f
#define STD_DUMMY_CHASSIS_ANG_INERTIA 1.0f
dev_float PLANE_CHASSIS_MASS_MIN_RATIO = 0.6f;
dev_float BLIMP_SHELL_FRAME_MASS = 500.0f;
void CVehicleModelInfo::InitFragType(CHandlingData* pHandling)
{
modelinfoAssert(m_data);
// set up joint limits on doors so they swing about
crSkeletonData *pSkeletonData = GetFragType()->GetCommonDrawable()->GetSkeletonData();
if(pSkeletonData==NULL)
{
modelinfoAssertf(false, "A vehicle without a skeleton?");
}
fragGroupBits tunedGroups;
fragType* type = GetFragType();
fragPhysicsLOD* physicsLOD = type->GetPhysics(0);
phArchetypeDamp* undamagedArchetype = physicsLOD->GetArchetype();
// Go through seats.. some may have collision for open top vehicles
// Need to lower the mass on them
float seatMass = pHandling->m_fMass > STD_SEAT_MASS ? STD_SEAT_MASS : pHandling->m_fMass/4.0f;//if the mass of the total vehicle is less than a reasonable number them just set the mass of the seat to a tenth of the mass of the total vehicle
for(int iSeatIndex = 0; iSeatIndex < m_data->m_SeatInfo.GetNumSeats(); iSeatIndex++)
{
SetBonesGroupMassAndAngularInertia(m_data->m_SeatInfo.GetBoneIndexFromSeat(iSeatIndex),seatMass, Vector3(STD_SEAT_ANG_INERTIA,STD_SEAT_ANG_INERTIA,STD_SEAT_ANG_INERTIA),tunedGroups);
}
// Set the bumper mass and angular inertia
SetBonesGroupMass(GetBoneIndex(VEH_BUMPER_F),STD_VEHICLE_BUMPER_MASS,tunedGroups);
SetBonesGroupMass(GetBoneIndex(VEH_BUMPER_R),STD_VEHICLE_BUMPER_MASS,tunedGroups);
// set mass and inertia of rotors for helicopters
if(GetIsRotaryAircraft())
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(HELI_ROTOR_MAIN),STD_HELI_ROTOR_MASS, Vector3(STD_HELI_ROTOR_ANG_INERTIA,STD_HELI_ROTOR_ANG_INERTIA,STD_HELI_ROTOR_ANG_INERTIA),tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(HELI_ROTOR_REAR),STD_HELI_TAIL_ROTOR_MASS, Vector3(STD_HELI_TAIL_ROTOR_ANG_INERTIA,STD_HELI_TAIL_ROTOR_ANG_INERTIA,STD_HELI_TAIL_ROTOR_ANG_INERTIA),tunedGroups);
if( ( MI_HELI_DRONE.IsValid() && ( GetModelNameHash() == MI_HELI_DRONE.GetName().GetHash() ) ) ||
( MI_HELI_DRONE_2.IsValid() && ( GetModelNameHash() == MI_HELI_DRONE_2.GetName().GetHash() ) ) )
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(HELI_ROTOR_MAIN_2),STD_HELI_ROTOR_MASS, Vector3(STD_HELI_ROTOR_ANG_INERTIA,STD_HELI_ROTOR_ANG_INERTIA,STD_HELI_ROTOR_ANG_INERTIA),tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(HELI_ROTOR_REAR_2),STD_HELI_TAIL_ROTOR_MASS, Vector3(STD_HELI_TAIL_ROTOR_ANG_INERTIA,STD_HELI_TAIL_ROTOR_ANG_INERTIA,STD_HELI_TAIL_ROTOR_ANG_INERTIA),tunedGroups);
}
}
if( GetIsPlane() ||
GetIsHeli() )
{
eHierarchyId aLandingGearIds[CLandingGear::MAX_NUM_PARTS] =
{
LANDING_GEAR_F,
LANDING_GEAR_RM1,
LANDING_GEAR_LM1,
LANDING_GEAR_RL,
LANDING_GEAR_RR,
LANDING_GEAR_RM
};
for(int i = 0; i < CLandingGear::MAX_NUM_PARTS; i++)
{
int iBoneIndex = GetBoneIndex(aLandingGearIds[i]);
if(iBoneIndex > -1)
{
int iGroupIndex = GetFragType()->GetGroupFromBoneIndex(0,iBoneIndex);
if(iGroupIndex > -1)
{
// Need to set latch strengths on groups to be non zero because code decides to not work if strength is 0
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[iGroupIndex];
SetBonesGroupMassAndAngularInertia(iBoneIndex,STD_LANDING_GEAR_MASS, Vector3(STD_LANDING_GEAR_MASS,STD_LANDING_GEAR_MASS,STD_LANDING_GEAR_MASS),tunedGroups);
pGroup->SetLatchStrength(-1.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
pGroup->SetForceTransmissionScaleUp(0.0f);
}
}
}
if( !GetIsHeli() &&
( !MI_PLANE_MICROLIGHT.IsValid() ||
GetModelNameHash() != MI_PLANE_MICROLIGHT.GetName().GetHash() ) )
{
fragTypeGroup *pRootGroup = physicsLOD->GetGroup(0);
if(pRootGroup->GetTotalUndamagedMass() < undamagedArchetype->GetMass() * PLANE_CHASSIS_MASS_MIN_RATIO)
{
SetGroupMass(0, pHandling->m_fMass * PLANE_CHASSIS_MASS_MIN_RATIO, tunedGroups);
}
}
}
if(GetIsDraftVeh())
{
const float yokeMassScale = 0.3f;
const float yokeExMassScale = 0.09f;
SetBonesGroupMass(GetBoneIndex(VEH_MISC_G), pHandling->m_fMass * yokeMassScale, tunedGroups);
SetBonesGroupMass(GetBoneIndex(VEH_MISC_F), pHandling->m_fMass * yokeExMassScale, tunedGroups);
}
const s32 MAX_ROOF_PARTS = 3;
eHierarchyId aRoofIds[MAX_ROOF_PARTS] =
{
VEH_ROOF2,
VEH_ROOF,
VEH_MISC_A,
};
float aRoofMass[MAX_ROOF_PARTS] =
{
1.0f,
2.0f,
10.0f,
};
for(int i = MAX_ROOF_PARTS-1; i >= 0; i--)
{
int iBoneIndex = GetBoneIndex(aRoofIds[i]);
if(iBoneIndex > -1)
{
int iGroupIndex = GetFragType()->GetGroupFromBoneIndex(0,iBoneIndex);
if(iGroupIndex > -1)
{
// set mass of the roof parts
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[iGroupIndex];
SetBonesGroupMassAndAngularInertia(iBoneIndex,aRoofMass[i],Vector3(aRoofMass[i],aRoofMass[i],aRoofMass[i]),tunedGroups);
// Need to set latch strengths on groups to be non zero because code decides to not work if strength is 0
pGroup->SetLatchStrength(-1.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
pGroup->SetForceTransmissionScaleUp(0.0f);
}
}
else
{
break;// if we haven't found a roof element just give up
}
}
//if we have a digger arm set the mass on it
for(s16 i = VEH_DIGGER_ARM; i < VEH_DIGGER_ARM_MAX; i++)
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(i),STD_DIGGER_ARM_MASS,Vector3(STD_DIGGER_ARM_ANG_INERTIA,STD_DIGGER_ARM_ANG_INERTIA,STD_DIGGER_ARM_ANG_INERTIA),tunedGroups);
}
//set the mass on the vehicle blocker bound if we have one
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_VEHICLE_BLOCKER),STD_VEHICLE_BLOCKER_MASS,Vector3(STD_VEHICLE_BLOCKER_ANG_INERTIA,STD_VEHICLE_BLOCKER_ANG_INERTIA,STD_VEHICLE_BLOCKER_ANG_INERTIA),tunedGroups);
//if we have turrets set the mass on it
if(GetVehicleFlag(CVehicleModelInfoFlags::FLAG_IS_TANK))
{
if( GetModelNameHash() == MI_TANK_KHANJALI.GetName().GetHash() )
{
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_1_BASE ), LIGHT_TANK_TURRET_MASS, Vector3( LIGHT_TANK_TURRET_MASS, LIGHT_TANK_TURRET_MASS, LIGHT_TANK_TURRET_MASS ), tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_1_BARREL ), 100.0f, Vector3( 100.0f, 100.0f, 100.0f ), tunedGroups );
for(s16 i = VEH_TURRET_2_BASE; i <= VEH_TURRET_4_BARREL; i++)
{
SetBonesGroupMassAndAngularInertia( GetBoneIndex(i), STD_FIRE_TRUCK_TURRET_MASS, Vector3( STD_FIRE_TRUCK_TURRET_MASS, STD_FIRE_TRUCK_TURRET_MASS, STD_FIRE_TRUCK_TURRET_MASS ), tunedGroups );
}
}
else if( pHandling->mFlags & MF_IS_RC )
{
for( s16 i = VEH_TURRET_1_BASE; i < VEH_TURRET_2_BARREL; i++ )
{
SetBonesGroupMass( GetBoneIndex( i ), RC_CAR_TURRET_MASS, tunedGroups );
}
for( s16 i = VEH_TURRET_FIRST_MOD; i < VEH_TURRET_LAST_MOD; i++ )
{
SetBonesGroupMass( GetBoneIndex( i ), RC_CAR_TURRET_MASS, tunedGroups );
}
}
else
{
for(s16 i = VEH_TURRET_1_BASE; i < VEH_TURRET_2_BARREL; i++)
{
SetBonesGroupMass(GetBoneIndex(i),STD_TANK_TURRET_MASS,tunedGroups);
}
}
}
else
{
for(s16 i = VEH_TURRET_1_BASE; i < VEH_TURRET_2_BARREL; i++)
{
bool heavyTurret = MI_CAR_TERBYTE.IsValid() && GetModelNameHash() == MI_CAR_TERBYTE.GetName().GetHash();
float mass = !heavyTurret ? STD_FIRE_TRUCK_TURRET_MASS : LIGHT_TANK_TURRET_MASS;
Vector3 inertia = !heavyTurret ? Vector3( STD_FIRE_TRUCK_TURRET_MASS, STD_FIRE_TRUCK_TURRET_MASS, STD_FIRE_TRUCK_TURRET_MASS ) : Vector3( LIGHT_TANK_TURRET_MASS, LIGHT_TANK_TURRET_MASS, LIGHT_TANK_TURRET_MASS );
SetBonesGroupMassAndAngularInertia(GetBoneIndex(i), mass, inertia,tunedGroups);
}
}
if( GetIsBoat() )
{
for(s16 i = VEH_TURRET_PARTS_FIRST; i < VEH_TURRET_PARTS_LAST; i++)
{
Vector3 inertia = Vector3( BOAT_TURRET_MASS, BOAT_TURRET_MASS, BOAT_TURRET_MASS );
SetBonesGroupMassAndAngularInertia(GetBoneIndex(i), BOAT_TURRET_MASS, inertia,tunedGroups);
}
for(s16 i = VEH_TURRET_FIRST_MOD; i < VEH_TURRET_LAST_MOD; i++)
{
Vector3 inertia = Vector3( BOAT_TURRET_MASS, BOAT_TURRET_MASS, BOAT_TURRET_MASS );
SetBonesGroupMassAndAngularInertia(GetBoneIndex(i), BOAT_TURRET_MASS, inertia,tunedGroups);
}
}
//Reduce the weight of the guns on the side of the heli as they cause it to pull to one side.
if(( MI_HELI_VALKYRIE.IsValid() && ( GetModelNameHash() == MI_HELI_VALKYRIE.GetName().GetHash() )) ||
(MI_HELI_VALKYRIE2.IsValid() && ( GetModelNameHash() == MI_HELI_VALKYRIE2.GetName().GetHash() ) ) )
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_MISC_H),STD_EXTRA_MASS, Vector3(STD_EXTRA_MASS,STD_EXTRA_MASS,STD_EXTRA_MASS),tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_MISC_G),STD_EXTRA_MASS, Vector3(STD_EXTRA_MASS,STD_EXTRA_MASS,STD_EXTRA_MASS),tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_TURRET_1_BASE),STD_EXTRA_MASS, Vector3(STD_EXTRA_MASS,STD_EXTRA_MASS,STD_EXTRA_MASS),tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_TURRET_1_BARREL),STD_EXTRA_MASS, Vector3(STD_EXTRA_MASS,STD_EXTRA_MASS,STD_EXTRA_MASS),tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_TURRET_2_BASE),STD_EXTRA_MASS, Vector3(STD_EXTRA_MASS,STD_EXTRA_MASS,STD_EXTRA_MASS),tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_TURRET_2_BARREL),STD_EXTRA_MASS, Vector3(STD_EXTRA_MASS,STD_EXTRA_MASS,STD_EXTRA_MASS),tunedGroups);
}
if( ( MI_CAR_DUNE3.IsValid() && GetModelNameHash() == MI_CAR_DUNE3.GetName().GetHash() ) ||
( MI_TRAILER_TRAILERLARGE.IsValid() && GetModelNameHash() == MI_TRAILER_TRAILERLARGE.GetName().GetHash() ) )
{
Vector3 dune3TurretInertia = Vector3( 1.0f, 1.0f, 1.0f );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_1_BASE ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_1_BARREL ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_1_MOD ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_2_BASE ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_2_BARREL ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_2_MOD ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_3_BASE ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_3_BARREL ), 1.0f, dune3TurretInertia, tunedGroups );
SetBonesGroupMassAndAngularInertia( GetBoneIndex( VEH_TURRET_3_MOD ), 1.0f, dune3TurretInertia, tunedGroups );
}
else if( GetVehicleFlag( CVehicleModelInfoFlags::FLAG_TURRET_MODS_ON_ROOF ) ||
GetIsPlane() )
{
const s32 MAX_TURRET_MOD_PARTS = 14;
eHierarchyId turretModBones[ MAX_TURRET_MOD_PARTS ] = { VEH_TURRET_1_BASE, VEH_TURRET_4_BARREL, VEH_TURRET_A1_BASE, VEH_TURRET_A4_BARREL, VEH_TURRET_B1_BASE, VEH_TURRET_B4_BARREL, VEH_WEAPON_1A, VEH_WEAPON_4D_ROT, VEH_TURRET_FIRST_MOD, VEH_TURRET_LAST_MOD, VEH_TURRET_A_FIRST_MOD, VEH_TURRET_A_LAST_MOD, VEH_TURRET_B_FIRST_MOD, VEH_TURRET_B_LAST_MOD };
Vector3 stdFireTruckTurretInertia = Vector3( STD_FIRE_TRUCK_TURRET_MASS, STD_FIRE_TRUCK_TURRET_MASS, STD_FIRE_TRUCK_TURRET_MASS );
float turretMass = STD_FIRE_TRUCK_TURRET_MASS;
if( GetIsPlane() ||
GetModelNameHash() == MI_CAR_SPEEDO4.GetName().GetHash() )
{
turretMass = 1.0f;
stdFireTruckTurretInertia = Vector3( turretMass, turretMass, turretMass );
}
for( int i = 0; i < MAX_TURRET_MOD_PARTS; i+=2 )
{
for( int j = (int)turretModBones[ i ]; j <= (int)turretModBones[ i + 1 ]; j++ )
{
SetBonesGroupMassAndAngularInertia( GetBoneIndex( j ), turretMass, stdFireTruckTurretInertia, tunedGroups );
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex( i ));
if( nGroupIndex != -1 )
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[ nGroupIndex ];
pGroup->SetStrength( -1.0f );
pGroup->SetForceTransmissionScaleUp(0.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
}
}
}
}
for( int nPanel = VEH_FIRST_BREAKABLE_PANEL; nPanel <= VEH_LAST_BREAKABLE_PANEL; nPanel++ )
{
if( GetBoneIndex( nPanel )!=-1 )
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex( 0, GetBoneIndex( nPanel ) );
if( nGroupIndex != -1 )
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[ nGroupIndex ];
pGroup->SetStrength( -1.0f );
pGroup->SetForceTransmissionScaleUp(0.0f);
pGroup->SetForceTransmissionScaleDown(0.0f);
SetBonesGroupMassAndAngularInertia( GetBoneIndex( nPanel ), 10.0f, Vector3( 10.0f, 10.0f, 10.0f ), tunedGroups );
}
}
}
//if we have a tow arm set the mass on it
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_TOW_ARM),STD_TOW_ARM_MASS,Vector3(STD_TOW_ARM_ANG_INERTIA,STD_TOW_ARM_ANG_INERTIA,STD_TOW_ARM_ANG_INERTIA),tunedGroups);
//if we have a tipper set the mass on it
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_TIPPER),STD_WHEEL_MASS,Vector3(STD_WHEEL_MASS,STD_WHEEL_MASS,STD_WHEEL_MASS),tunedGroups);
if(GetVehicleType() == VEHICLE_TYPE_TRAILER) // This is a gadget for tr3 trailer (yacht).
{
//if we have a misc_e bone set the mass on it
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_MISC_E),20.0f,Vector3(20.0f,20.0f,20.0f),tunedGroups);
// ... and the damping.
SetJointStiffness(GetBoneIndex(VEH_MISC_E), YACHT_MAST_STIFFNESS, tunedGroups);
}
//if we have an EOD robot arm
for(s16 i = VEH_ARM1; i <= VEH_ARM4; i++)
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(i),STD_EOD_ARM_MASS,Vector3(STD_EOD_ARM_ANG_INERTIA,STD_EOD_ARM_ANG_INERTIA,STD_EOD_ARM_ANG_INERTIA),tunedGroups);
}
for(s16 i = VEH_EXTRA_1; i <= VEH_LAST_EXTRA; i++)
{
SetBonesGroupMass(GetBoneIndex(i),STD_EXTRA_MASS,tunedGroups);
}
for(s16 i = VEH_BREAKABLE_EXTRA_1; i <= VEH_LAST_BREAKABLE_EXTRA; i++)
{
SetBonesGroupMass(GetBoneIndex(i),STD_BREAKABLE_EXTRA_MASS,tunedGroups);
}
for(s16 i = VEH_MOD_COLLISION_1; i <= VEH_LAST_MOD_COLLISION; i++)
{
#if __ASSERT
if( GetBoneIndex( i ) != -1 )
{
atHashString dlcPackName = GetVehicleDLCPack();
atHashString s_currentDLCPackName( "dlc_mpHeist3CRC", 0x7F363F2B );
if( dlcPackName == s_currentDLCPackName )
{
fragType* type = GetFragType();
int groupIndex = type->GetGroupFromBoneIndex( 0, GetBoneIndex( i ) );
Assertf( groupIndex != -1, "Mod_col_%d has no collision bound. Mod col bones should only be used for enabling and disabling collision", i - (int)VEH_MOD_COLLISION_1 );
}
}
#endif // #if __BANK
if(pHandling && pHandling->hFlags & HF_REDUCED_MOD_MASS)
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(i),STD_MOD_COLLISION_MASS,Vector3(STD_MOD_COLLISION_ANG_INERTIA,STD_MOD_COLLISION_ANG_INERTIA,STD_MOD_COLLISION_ANG_INERTIA),tunedGroups);
}
else
{
SetBonesGroupMass(GetBoneIndex(i),STD_BREAKABLE_EXTRA_MASS,tunedGroups);
}
}
if(GetVehicleType() == VEHICLE_TYPE_BLIMP)
{
// Ensure that the broken pieces of the blimp shell get a reasonable mass. The unbroken shell BLIMP_SHELL gets destroyed during
// breaking so it doesn't need any mass.
// NOTE: This must be after the VEH_EXTRA_1 loop since BLIMP_SHELL_X are extras
for(s32 blimpShellFrameId = BLIMP_SHELL_FRAME_1; blimpShellFrameId <= BLIMP_SHELL_FRAME_LAST; ++blimpShellFrameId)
{
SetBonesGroupMass(GetBoneIndex(blimpShellFrameId),BLIMP_SHELL_FRAME_MASS,tunedGroups);
}
}
for( s16 i = VEH_SPEAKER_FIRST; i <= VEH_SPEAKER_LAST; i++ )
{
SetBonesGroupMass( GetBoneIndex( i ), STD_BREAKABLE_EXTRA_MASS, tunedGroups );
}
//if we have forks, latch them
const s32 MAX_FORKLIFT_PARTS = 3;
eHierarchyId aForkliftIds[MAX_FORKLIFT_PARTS] =
{
VEH_FORKS,
VEH_MAST,
VEH_CARRIAGE,
};
for(s16 i = 0; i < MAX_FORKLIFT_PARTS; i++)
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(aForkliftIds[i]),STD_FORKLIFT_MASS,Vector3(STD_FORKLIFT_ANG_INERTIA,STD_FORKLIFT_ANG_INERTIA,STD_FORKLIFT_ANG_INERTIA),tunedGroups);
}
// Latch joints for the Handler vehicle.
const s32 MAX_HANDLER_PARTS = 3;
eHierarchyId aHandlerFrameIds[MAX_HANDLER_PARTS] =
{
VEH_HANDLER_FRAME_1,
VEH_HANDLER_FRAME_2,
VEH_HANDLER_FRAME_3
};
for(s16 i = 0; i < MAX_HANDLER_PARTS; ++i)
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(aHandlerFrameIds[i]),STD_HANDLER_FRAME_MASS,Vector3(STD_HANDLER_FRAME_ANG_INERTIA,STD_HANDLER_FRAME_ANG_INERTIA,STD_HANDLER_FRAME_ANG_INERTIA),tunedGroups);
}
// Increase mass on freight container so the container doesn't have a mass of 1kg when it breaks off.
const s32 MAX_FREIGHTCONT2_PARTS = 2;
eHierarchyId aFreightCont2Ids[MAX_FREIGHTCONT2_PARTS] =
{
VEH_FREIGHTCONT2_CONTAINER,
VEH_FREIGHTCONT2_BOGEY,
};
for(s16 i = 0; i < MAX_FREIGHTCONT2_PARTS; ++i)
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(aFreightCont2Ids[i]),STD_FREIGHTCONT2_MASS,Vector3(STD_FREIGHTCONT2_ANG_INERTIA,STD_FREIGHTCONT2_ANG_INERTIA,STD_FREIGHTCONT2_ANG_INERTIA),tunedGroups);
}
// We've made a big change to the freight container's weight distribution here, update the centre of mass.
{
fragType* pFragType = GetFragType();
Assert(pFragType);
pFragType->ComputeMassProperties(0);
}
// Now update the doors/wheels after anything that could be their child in the heirarchy is updated
{
crBoneData *pBone = NULL;
for(int i=0; i<NUM_VEH_DOORS_MAX; i++)
{
eHierarchyId nDoorId = ms_aSetupDoorIds[i];
if(GetBoneIndex(nDoorId)!=-1)
{
pBone = (crBoneData *)pSkeletonData->GetBoneData(GetBoneIndex(nDoorId));
if(pBone->GetDofs() &(crBoneData::HAS_ROTATE_LIMITS|crBoneData::HAS_TRANSLATE_LIMITS))
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(nDoorId));
if(nGroupIndex != -1)
{
if(GetVehicleType() == VEHICLE_TYPE_TRAILER)
{
SetBonesGroupMassAndAngularInertia(GetBoneIndex(nDoorId),TRAILER_DOOR_MASS, Vector3(TRAILER_DOOR_ANG_INERTIA, TRAILER_DOOR_ANG_INERTIA, TRAILER_DOOR_ANG_INERTIA),tunedGroups,true);
}
else
{
if( GetModelNameHash() != MI_CAR_STAFFORD.GetName().GetHash() )
{
float doorMass = pHandling->m_fMass > 2 * STD_VEHICLE_DOOR_MASS ? STD_VEHICLE_DOOR_MASS : pHandling->m_fMass / 4.0f;//if the mass of the total vehicle is less than a reasonable number them just set the mass of the doors to a tenth of the mass of the total vehicle
SetBonesGroupMassAndAngularInertia( GetBoneIndex( nDoorId ), doorMass, Vector3( STD_VEHICLE_DOOR_ANG_INERTIA, STD_VEHICLE_DOOR_ANG_INERTIA, STD_VEHICLE_DOOR_ANG_INERTIA ), tunedGroups, true );
}
}
}
}
}
}
}
// Windows transmit 100% of their force to the door. Without this it becomes much more difficult to break
// the door off if it gets contacts on the window.
for (int i=VEH_FIRST_WINDOW; i<=VEH_LAST_WINDOW; i++)
{
eHierarchyId hierarchyId = (eHierarchyId)i;
int windowBoneIndex = GetBoneIndex(hierarchyId);
if (windowBoneIndex>-1)
{
int groupIndex = type->GetGroupFromBoneIndex(0, windowBoneIndex);
if(groupIndex != -1)
{
physicsLOD->GetGroup(groupIndex)->SetForceTransmissionScaleUp(1.0f);
}
}
}
for(int i=0; i<NUM_VEH_CWHEELS_MAX; i++)
{
float wheelMass = STD_WHEEL_MASS;
Vector3 wheelInertia = STD_WHEEL_INERTIA;
// Wheels above a certain size get a bigger mass.
dev_float sfBigWheelRadius = 0.7f;
dev_float sfBigWheelMass = 500.0f;
dev_float sfBigWheelHeavyVehicleThreshold = 4000.0f;
const Vector3 svBigWheelInertia (65.0f,35.0f,35.0f);
if(pHandling->m_fMass >= sfBigWheelHeavyVehicleThreshold)
{
if(GetBoneIndex(VEH_WHEEL_FIRST_WHEEL + i)!=-1)
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(VEH_WHEEL_FIRST_WHEEL + i));
if(nGroupIndex != -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
// get front wheel radius from bound
fragTypeChild* pFrontLeftChild = GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()];
if(pFrontLeftChild->GetUndamagedEntity()->GetBound()->GetRadiusAroundCentroid() > sfBigWheelRadius)
{
wheelMass = sfBigWheelMass;
wheelInertia = svBigWheelInertia;
}
}
}
}
else if (pHandling->m_fMass <= STD_WHEEL_MASS)//if the mass of the total vehicle is less than a reasonable number them just set the mass of the wheels to a tenth of the mass of the total vehicle
{
wheelMass = pHandling->m_fMass/4.0f;
wheelInertia = STD_WHEEL_INERTIA * (wheelMass/STD_WHEEL_MASS);
}
SetBonesGroupMassAndAngularInertia(GetBoneIndex(ms_aSetupWheelIds[i]),wheelMass,wheelInertia,tunedGroups);
}
// Need to make sure individual link attachment matrices get allocated for all vehicles
// This is so we can move the wheel matrices without them getting trashed by the articulated collider
GetFragType()->SetForceAllocateLinkAttachments();
//Set the low lod chassis to be very light so it doesn't interfere with the mass distribution.
modelinfoAssertf(GetBoneIndex(VEH_CHASSIS)!=-1, "Vehicle %s has no chassis bone", GetModelName());
#if __DEV
if (GetBoneIndex(VEH_CHASSIS_LOWLOD) == -1)
{
modelinfoDisplayf("Vehicle %s is missing a low lod chassis", GetModelName());
}
if( GetBoneIndex(VEH_CHASSIS_DUMMY) == -1)
{
modelinfoDisplayf("Vehicle %s is missing a dummy bound", GetModelName());
}
//Verify that the vehicle's wheels have disc bounds
phBound* pBound = undamagedArchetype->GetBound();
if(pBound->GetType() == phBound::COMPOSITE)
{
phBoundComposite* pBoundComposite = (phBoundComposite*)pBound;
for(int i = VEH_WHEEL_FIRST_WHEEL; i <= VEH_WHEEL_LAST_WHEEL; i++)
{
int nComponent = type->GetComponentFromBoneIndex(0, GetBoneIndex(i));
if(nComponent > -1)
{
if(phBound* pWheelBound = pBoundComposite->GetBound(nComponent))
{
if(pWheelBound->GetType() != phBound::DISC)
{
modelinfoDisplayf("Vehicle %s has wheels without disc bounds", GetModelName());
break;
}
}
}
}
}
#endif
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_CHASSIS_LOWLOD),STD_LOW_LOD_CHASSIS_MASS, Vector3(STD_LOW_LOD_CHASSIS_ANG_INERTIA, STD_LOW_LOD_CHASSIS_ANG_INERTIA, STD_LOW_LOD_CHASSIS_ANG_INERTIA), tunedGroups);
SetBonesGroupMassAndAngularInertia(GetBoneIndex(VEH_CHASSIS_DUMMY),STD_DUMMY_CHASSIS_MASS, Vector3(STD_HANDLER_FRAME_ANG_INERTIA,STD_HANDLER_FRAME_ANG_INERTIA,STD_HANDLER_FRAME_ANG_INERTIA), tunedGroups);
const ScalarV newMass = ScalarVFromF32(pHandling->m_fMass);
const Vec3V newCenterOfGravity = pHandling->m_vecCentreOfMassOffset;
// Figure out which children we're allowed to modify the mass/angular inertia of to reach our target total mass/angular inertia
fragGroupBits scalableGroups(true);
scalableGroups.IntersectNegate(tunedGroups);
fragChildBits scalableChildren;
physicsLOD->ConvertGroupBitsetToChildBitset(scalableGroups,scalableChildren);
ASSERT_ONLY(const ScalarV tolerance = Max(ScalarV(V_FLT_SMALL_3),Scale(ScalarV(V_FLT_SMALL_4),newMass)));
// Scale the mass to the target amount
type->ScaleUndamagedChildren(0,&newMass,NULL,scalableChildren,&newCenterOfGravity);
Assertf(IsCloseAll(ScalarVFromF32(undamagedArchetype->GetMass()),newMass,tolerance), "Wasn't able to set desired mass of %f on %s. Using %f instead.",newMass.Getf(),GetModelName(),undamagedArchetype->GetMass());
// Now that the mass has been scaled and the angular inertia alongside it, scale the angular inertia further if the tuning specifies it.
Vec3V angularInertiaScale(V_ONE);
if(GetVehicleType()==VEHICLE_TYPE_BIKE)
{
angularInertiaScale = pHandling->m_vecInertiaMultiplier;
}
else if(GetVehicleType()==VEHICLE_TYPE_CAR || GetVehicleType() == VEHICLE_TYPE_TRAILER || GetVehicleType() == VEHICLE_TYPE_QUADBIKE || GetVehicleType() == VEHICLE_TYPE_DRAFT || GetVehicleType() == VEHICLE_TYPE_SUBMARINECAR || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_AUTOMOBILE || GetVehicleType() == VEHICLE_TYPE_AMPHIBIOUS_QUADBIKE)
{
angularInertiaScale = pHandling->m_vecInertiaMultiplier;
}
if(IsCloseAll(angularInertiaScale,Vec3V(V_ONE),Vec3V(V_FLT_SMALL_3)) == 0)
{
// Scale the fragment to the new mass/angular inertia
const Vec3V newAngularInertia = Scale(VECTOR3_TO_VEC3V(undamagedArchetype->GetAngInertia()),angularInertiaScale);
type->ScaleUndamagedChildren(0,NULL,&newAngularInertia,scalableChildren,&newCenterOfGravity);
Assertf(IsCloseAll(VECTOR3_TO_VEC3V(undamagedArchetype->GetAngInertia()),newAngularInertia,Vec3V(tolerance)), "Wasn't able to scale angular inertia of <%f, %f, %f> by <%f, %f, %f> on %s. Final angular inertia: <%f, %f, %f>.",VEC3V_ARGS(newAngularInertia),VEC3V_ARGS(angularInertiaScale),GetModelName(),VEC3V_ARGS(VECTOR3_TO_VEC3V(undamagedArchetype->GetAngInertia())));
}
else
{
Assertf(undamagedArchetype->GetAngInertia().IsClose(undamagedArchetype->GetAngInertia(), SMALL_FLOAT), "Invalid inertia <%3.2f, %3.2f, %3.2f> of model %s", VEC3V_ARGS(VECTOR3_TO_VEC3V(undamagedArchetype->GetAngInertia())), GetModelName());
}
// Make sure the car void material is not in the zeroth slot, because otherwise we will pick that at random if FindElementIndex fails
phBoundComposite* pBoundComp = ((phBoundComposite*)GetFragType()->GetPhysics(0)->GetArchetype()->GetBound());
for(int nBound=0; nBound < pBoundComp->GetNumBounds(); nBound++)
{
if(pBoundComp->GetBound(nBound) && pBoundComp->GetBound(nBound)->GetType() == phBound::GEOMETRY)
{
phBoundGeometry* pBoundGeom = static_cast<phBoundGeometry*>(pBoundComp->GetBound(nBound));
if (PGTAMATERIALMGR->UnpackMtlId(pBoundGeom->GetMaterialId(0)) == PGTAMATERIALMGR->g_idCarVoid)
{
pBoundGeom->SwapZeroAndFirstMaterials();
}
}
}
if( GetModelNameHash() == MI_TANK_KHANJALI.GetName().GetHash() ||
GetModelNameHash() == MI_VAN_RIOT_2.GetName().GetHash() )
{
fragPhysicsLOD* physicsLOD = type->GetPhysics( 0 );
if( physicsLOD )
{
physicsLOD->SetSmallestAngInertia( LIGHT_TANK_TURRET_MASS * rage::ANG_INERTIA_MAX_RATIO );
}
}
if( GetVehicleFlag( CVehicleModelInfoFlags::FLAG_RAMP ) )
{
int iBoneIndex = GetBoneIndex( VEH_BUMPER_F );
if(iBoneIndex > -1)
{
int iGroupIndex = GetFragType()->GetGroupFromBoneIndex(0,iBoneIndex);
if(iGroupIndex > -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[iGroupIndex];
pGroup->SetStrength( -1.0f );
pGroup->SetForceTransmissionScaleUp(0.1f);
pGroup->SetForceTransmissionScaleDown(0.1f);
}
}
}
}
void CVehicleModelInfo::InitFromFragType()
{
modelinfoAssert(m_data);
// go through wheels
{
// search for instance meshes in left front and left rear wheel children
int nFrontLeftChild = -1;
if(GetBoneIndex(VEH_WHEEL_LF)!=-1)
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(VEH_WHEEL_LF));
if(nGroupIndex != -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
if(GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()]->GetUndamagedEntity()
&& GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()]->GetUndamagedEntity()->GetLodGroup().GetCullRadius() > 0.0f)
{
nFrontLeftChild = pGroup->GetChildFragmentIndex();
}
// get front wheel radius from bound
fragTypeChild* pFrontLeftChild = GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()];
m_tyreRadiusF = pFrontLeftChild->GetUndamagedEntity()->GetBound()->GetBoundingBoxMax().GetZf();
}
}
int nRearLeftChild = -1;
if(GetBoneIndex(VEH_WHEEL_LR)!=-1)
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(VEH_WHEEL_LR));
if(nGroupIndex != -1)
{
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
if(GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()]->GetUndamagedEntity()
&& GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()]->GetUndamagedEntity()->GetLodGroup().GetCullRadius() > 0.0f)
{
nRearLeftChild = pGroup->GetChildFragmentIndex();
m_data->m_pStructure->m_bWheelInstanceSeparateRear = true;
}
// get rear wheel radius from bound
fragTypeChild* pRearLeftChild = GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()];
m_tyreRadiusR = pRearLeftChild->GetUndamagedEntity()->GetBound()->GetBoundingBoxMax().GetZf();
// need to include wheel scale into the rim calculation because the vehicle guys are setting up rear rim radius == front for scaled wheels
if(!m_data->m_pStructure->m_bWheelInstanceSeparateRear)
m_rimRadiusR = m_rimRadiusF * m_tyreRadiusR / m_tyreRadiusF;
}
}
m_data->m_pStructure->m_isRearWheel = 0;
for(int i=0; i<NUM_VEH_CWHEELS_MAX; i++)
{
eHierarchyId nWheelId = ms_aSetupWheelIds[i];
if(GetBoneIndex(nWheelId)!=-1)
{
int nGroupIndex = GetFragType()->GetGroupFromBoneIndex(0, GetBoneIndex(nWheelId));
if(nGroupIndex != -1)
{
// set mass and inertia of wheel
fragTypeGroup* pGroup = GetFragType()->GetPhysics(0)->GetAllGroups()[nGroupIndex];
if (nWheelId != VEH_WHEEL_LF && nWheelId != VEH_WHEEL_RF)
m_data->m_pStructure->m_isRearWheel |= (1 << i);
// if this is a rear wheel, and we have a specific rear wheel child, then hook them up
if(nWheelId!=VEH_WHEEL_LF && nWheelId!=VEH_WHEEL_RF && nRearLeftChild > -1)
{
m_data->m_pStructure->m_nWheelInstances[i][0] = (s8)pGroup->GetChildFragmentIndex();
m_data->m_pStructure->m_nWheelInstances[i][1] = (s8)nRearLeftChild;
}
// else hook up to front left wheel child
else if(nFrontLeftChild > -1)
{
m_data->m_pStructure->m_nWheelInstances[i][0] = (s8)pGroup->GetChildFragmentIndex();
m_data->m_pStructure->m_nWheelInstances[i][1] = (s8)nFrontLeftChild;
}
// if we've got an instanced wheel, extract tyre and rim radius for wheel
if(m_data->m_pStructure->m_nWheelInstances[i][0] > -1)
{
fragTypeChild* pChild = GetFragType()->GetPhysics(0)->GetAllChildren()[pGroup->GetChildFragmentIndex()];
if(pChild->GetUndamagedEntity() && pChild->GetUndamagedEntity()->GetBound())
{
m_data->m_pStructure->m_fWheelTyreRadius[i] = pChild->GetUndamagedEntity()->GetBound()->GetBoundingBoxMax().GetZf();
m_data->m_pStructure->m_fWheelTyreWidth[i] = 2.0f*pChild->GetUndamagedEntity()->GetBound()->GetBoundingBoxMax().GetXf();
m_data->m_pStructure->m_fWheelRimRadius[i] = GetRimRadius(nWheelId == VEH_WHEEL_LF || nWheelId == VEH_WHEEL_RF);
if(m_data->m_pStructure->m_nWheelInstances[i][0] != m_data->m_pStructure->m_nWheelInstances[i][1] && m_data->m_pStructure->m_nWheelInstances[i][1] == (s8)nFrontLeftChild)
{
m_data->m_pStructure->m_fWheelScaleInv[i] = m_data->m_pStructure->m_fWheelTyreRadius[WHEEL_LF_INDEX] / m_data->m_pStructure->m_fWheelTyreRadius[i];
}
}
}
}
}
}
#if __PS3 && __ASSERT
// ps3: early check for instanced wheel's geometry type:
const int nWheelChild = (nRearLeftChild>-1)? nRearLeftChild : nFrontLeftChild;
if(nWheelChild > -1)
{
fragDrawable* toDraw = GetFragType()->GetPhysics(0)->GetAllChildren()[nWheelChild]->GetUndamagedEntity();
if(toDraw)
{
const rmcLodGroup& lodGroup = toDraw->GetLodGroup();
for(int nLodIndex=0; nLodIndex<2; nLodIndex++) // 0=hi LOD, 1=middle LOD
{
if(lodGroup.ContainsLod(nLodIndex))
{
const rmcLod &lod = lodGroup.GetLod(nLodIndex);
const int numLods = lod.GetCount();
for(int nLod=0; nLod<numLods; nLod++)
{
grmModel *pModel = lod.GetModel(nLod);
if(pModel)
{
const int numGeoms = pModel->GetGeometryCount();
for(int g=0; g<numGeoms; g++ )
{
if(pModel->HasGeometry(g))
{
grmGeometry& geom = pModel->GetGeometry(g);
Assertf(geom.GetType()==grmGeometry::GEOMETRYQB, "%s: Instanced wheel uses non-QB geometry (lod=%d).", GetModelName(), nLodIndex);
}
}
}
}//for(int nLod=0; nLod<numLods; nLod++)...
}
}
}
} //if(nWheelChild > -1)...
#endif //__PS3 && __ASSERT...
}// go through wheels...
}
//
// CVehicleModelInfo::ChooseVehicleColour: Choose two vehicle colours from the list of possible colours
//
void CVehicleModelInfo::ChooseVehicleColour(u8& col1, u8& col2, u8& col3, u8& col4, u8& col5, u8& col6, s32 inc)
{
modelinfoAssert(m_data);
// just cycle colours instead of using random numbers
if(m_numPossibleColours == 0)
return;
m_data->m_lastColUsed = (m_data->m_lastColUsed + inc) % m_numPossibleColours;
col1 = m_possibleColours[0][m_data->m_lastColUsed];
col2 = m_possibleColours[1][m_data->m_lastColUsed];
col3 = m_possibleColours[2][m_data->m_lastColUsed];
col4 = m_possibleColours[3][m_data->m_lastColUsed];
col5 = m_possibleColours[4][m_data->m_lastColUsed];
col6 = m_possibleColours[5][m_data->m_lastColUsed];
}
//
// CVehicleModelInfo::ChooseVehicleColour: Choose two vehicle colours from the list of possible colours
//
void CVehicleModelInfo::GetNextVehicleColour(CVehicle *pVehicle, u8& col1, u8& col2, u8& col3, u8& col4, u8& col5, u8& col6)
{
s32 newColour = 0;
if (m_numPossibleColours > 1)
{
newColour = fwRandom::GetRandomNumberInRange(0, m_numPossibleColours);
// First find the colour currently used.
for (s32 n = 0; n < m_numPossibleColours; n++)
{
if (pVehicle->GetBodyColour1() == m_possibleColours[0][n] &&
pVehicle->GetBodyColour2() == m_possibleColours[1][n] &&
pVehicle->GetBodyColour3() == m_possibleColours[2][n])
{
newColour = (n+1) % m_numPossibleColours;
break;
}
}
}
col1 = m_possibleColours[0][newColour];
col2 = m_possibleColours[1][newColour];
col3 = m_possibleColours[2][newColour];
col4 = m_possibleColours[3][newColour];
col5 = m_possibleColours[4][newColour];
col6 = m_possibleColours[5][newColour];
}
struct sVehColDist
{
s32 col;
u32 numVehs;
float distSqr;
};
//
// CVehicleModelInfo::ChooseVehicleColourFancy: Choose two vehicle colours from the list of possible colours
// This function goes through the vehicle pool and
//
void CVehicleModelInfo::ChooseVehicleColourFancy(CVehicle *pNewVehicle, u8& col1, u8& col2, u8& col3, u8& col4, u8& col5, u8& col6)
{
modelinfoAssert(m_data);
modelinfoAssertf(m_numPossibleColours > 0, "%s doesn't have any colours setup", GetModelName());
if (m_numPossibleColours<=0)
{
// should never hit here, but have been seeing issues with new vehicles when carcols.pso.meta has not been updated, best to fallback to something safe
col1 = col2 = col3 = col4 = col5 = col6 = 0;
return;
}
s32 n;
struct sVehColDist vehCols[MAX_VEH_POSSIBLE_COLOURS];
// for high end cars, keep track of the two lightest and darkest colors, we'd like to bias towards spawning them
// we only do this if they're above 160 and below 50 for light/dark respectively,
// some vehicles might not have any near white/black colors
const u8 lightThreshold = 160;
const u8 darkThreshold = 50;
float distThresholdSqr = 50.f * 50.f;
CRGBA light(0, 0, 0);
s32 lightCol = -1;;
CRGBA dark(255, 255, 255);
s32 darkCol = -1;
bool biasColorPick = GetVehicleFlag(CVehicleModelInfoFlags::FLAG_SPORTS);
// check if we have a valid livery and if it has any colors we need to restrict the choice to
bool forceLiveryColor = false;
s32 liveryId = pNewVehicle->GetLiveryId();
if (liveryId > -1)
{
for (s32 i = 0; i < MAX_NUM_LIVERY_COLORS; ++i)
{
if (m_liveryColors[liveryId][i] > -1 && m_liveryColors[liveryId][i] < m_numPossibleColours)
{
forceLiveryColor = true;
break;
}
}
}
CVehicle* playerVehicle = CGameWorld::FindLocalPlayerVehicle();
for (n = 0; n < m_numPossibleColours; n++)
{
vehCols[n].col = n;
vehCols[n].numVehs = 0;
if (biasColorPick)
{
CRGBA col = GetVehicleColours()->GetVehicleColour(m_possibleColours[0][n]);
if (col.GetRed() < dark.GetRed() && col.GetGreen() < dark.GetGreen() && col.GetBlue() < dark.GetBlue())
{
dark = col;
darkCol = n;
}
if (col.GetRed() > light.GetRed() && col.GetGreen() > light.GetGreen() && col.GetBlue() > light.GetBlue())
{
light = col;
lightCol = n;
}
}
// if there's a player vehicle of the same type make sure that color will be the last one chosen
// ignore this rule if we need to force a livery color though
if (!forceLiveryColor && playerVehicle && pNewVehicle->GetModelIndex() == playerVehicle->GetModelIndex())
{
if (playerVehicle->GetBodyColour1() == m_possibleColours[0][n] &&
playerVehicle->GetBodyColour2() == m_possibleColours[1][n] &&
playerVehicle->GetBodyColour3() == m_possibleColours[2][n])
{
vehCols[n].distSqr = -1.f;
vehCols[n].numVehs = 1000;
continue;
}
}
vehCols[n].distSqr = 999999.9f;
}
// clear unused entries
for (u32 i = m_numPossibleColours; i < MAX_VEH_POSSIBLE_COLOURS; ++i)
{
vehCols[i].col = 0;
vehCols[i].distSqr = -1;
vehCols[i].numVehs = 1000;
}
// calculate number of color instances and closest distance to each
const u32 numInsts = GetNumVehicleInstances();
for (s32 i = 0; i < numInsts; ++i)
{
CVehicle* veh = GetVehicleInstance(i);
if (veh && (veh != pNewVehicle))
{
// Find the colours for this occurence of this modelindex.
for (n = 0; n < m_numPossibleColours; n++)
{
if (veh->GetBodyColour1() == m_possibleColours[0][n] &&
veh->GetBodyColour2() == m_possibleColours[1][n] &&
veh->GetBodyColour3() == m_possibleColours[2][n])
{
float fDist = DistSquared(veh->GetTransform().GetPosition(), pNewVehicle->GetTransform().GetPosition()).Getf();
vehCols[n].distSqr = rage::Min(vehCols[n].distSqr, fDist);
vehCols[n].numVehs++;
}
}
}
}
s32 PreferedColour = -1;
if (forceLiveryColor)
{
float FurthestDist = 0.f;
for (n = 0; n < MAX_NUM_LIVERY_COLORS; ++n)
{
s8 col = m_liveryColors[liveryId][n];
if (col < 0)
continue;
if (vehCols[n].distSqr > FurthestDist)
{
FurthestDist = vehCols[col].distSqr;
PreferedColour = col;
}
}
Assertf(PreferedColour != -1, "No livery colors for vehicle '%s' (livery: %d, furthest dist: %.2f)", GetModelName(), liveryId, FurthestDist);
}
else
{
u32 maxNumVehsWithSameCol = GetMaxNumberOfSameColor();
bool randomColor = true;
if (biasColorPick)
{
// in order to bias towards white/black we need to make sure we don't break any rules
bool canSpawnLight = light.GetRed() > lightThreshold && light.GetGreen() > lightThreshold && light.GetBlue() > lightThreshold;
canSpawnLight &= vehCols[lightCol].numVehs < maxNumVehsWithSameCol;
canSpawnLight &= vehCols[lightCol].distSqr > distThresholdSqr;
bool canSpawnDark = dark.GetRed() < darkThreshold && dark.GetGreen() < darkThreshold && dark.GetBlue() < darkThreshold;
canSpawnDark &= vehCols[darkCol].numVehs < maxNumVehsWithSameCol;
canSpawnDark &= vehCols[darkCol].distSqr > distThresholdSqr;
if (canSpawnLight || canSpawnDark)
{
randomColor = false;
// roll dice, 25% to spawn black, 25% to spawn white and 50% to fallback to color based on highest distance
float dice = fwRandom::GetRandomNumberInRange(0.f, 1.f);
if (canSpawnDark && dice < 0.25f)
PreferedColour = darkCol;
else if (canSpawnLight && dice > 0.25f && dice < 0.5f)
PreferedColour = lightCol;
else
randomColor = true;
}
}
if (randomColor)
{
PreferedColour = vehCols[0].col;
float distSqr = 0.0f;
for (s32 i = 0; i < MAX_VEH_POSSIBLE_COLOURS; ++i)
{
if (vehCols[i].distSqr >= distSqr && vehCols[i].numVehs < maxNumVehsWithSameCol)
{
if (vehCols[i].distSqr != distSqr || fwRandom::GetRandomNumberInRange(0, 1) == 1)
{
distSqr = vehCols[i].distSqr;
PreferedColour = vehCols[i].col;
}
}
}
}
}
if(Verifyf(PreferedColour != -1, "No colors for vehicle '%s'", GetModelName()))
{
m_data->m_lastColUsed = PreferedColour;
col1 = m_possibleColours[0][m_data->m_lastColUsed];
col2 = m_possibleColours[1][m_data->m_lastColUsed];
col3 = m_possibleColours[2][m_data->m_lastColUsed];
col4 = m_possibleColours[3][m_data->m_lastColUsed];
col5 = m_possibleColours[4][m_data->m_lastColUsed];
col6 = m_possibleColours[5][m_data->m_lastColUsed];
}
}
bool CVehicleModelInfo::HasAnyColorsToSpawn() const
{
if (!m_data)
return true;
const u32 numVehs = GetNumVehicleInstances();
const u32 maxOfSameColor = GetMaxNumberOfSameColor();
if (numVehs < maxOfSameColor * m_numPossibleColours)
return true;
return false;
}
int CVehicleModelInfo::SelectLicensePlateTextureIndex() const
{
modelinfoAssertf(GetModelType() == MI_TYPE_VEHICLE, "ModelInfo isn't a vehicle");
int selected = ms_VehicleColours ? ms_VehicleColours->GetLicensePlateData().GetDefaultTextureIndex() : -1;
const atHashString& selectedName = CVehicleModelPlateProbabilities::SelectPlateTextureSet(m_plateProbabilities);
if (selectedName.GetHash() != 0)
{
const CVehicleModelInfoPlates::TextureArray &texArray = ms_VehicleColours->GetLicensePlateData().GetTextureArray();
for (int i=0; i<texArray.GetCount(); i++)
if (selectedName == texArray[i].GetTextureSetName())
return i;
}
return selected;
}
const vehicleLightSettings *CVehicleModelInfo::GetLightSettings() const
{
modelinfoAssert(ms_VehicleColours);
modelinfoAssert(ms_VehicleColours->m_Lights.GetCount() > 0);
modelinfoAssert(m_lightSettings < ms_VehicleColours->m_Lights.GetCount());
return &ms_VehicleColours->m_Lights[m_lightSettings];
}
const sirenSettings *CVehicleModelInfo::GetSirenSettings() const
{
modelinfoAssert(ms_VehicleColours);
if( m_sirenSettings > 0 )
{
modelinfoAssert(ms_VehicleColours->m_Sirens.GetCount() > 0);
modelinfoAssert(m_sirenSettings < ms_VehicleColours->m_Sirens.GetCount());
return &ms_VehicleColours->m_Sirens[m_sirenSettings];
}
return NULL;
}
int CVehicleModelInfo::GetStreamingDependencies(strIndex* iIndicies, int iMaxNumIndicies)
{
if(!m_bDependenciesValid)
{
CalculateStreamingDependencies();
}
for(u8 i =0; i < m_uNumDependencies && (s32)i < iMaxNumIndicies; i++)
{
iIndicies[i] = m_iDependencyStreamingIndicies[i];
}
return (int)m_uNumDependencies;
}
void CVehicleModelInfo::CalculateStreamingDependencies()
{
// Find all anim groups referenced in our model info
const CVehicleLayoutInfo* pInfo = GetVehicleLayoutInfo();
modelinfoAssertf(pInfo, "Vehicle %s is missing a vehicle layout info",GetModelName());
int iActualCount = 0;
if(pInfo)
{
s32 iAnimDictIndicesList[MAX_VEHICLE_STR_DEPENDENCIES];
int iNewCount = pInfo->GetAllStreamedAnimDictionaries(iAnimDictIndicesList,MAX_VEHICLE_STR_DEPENDENCIES);
iNewCount = Min(iNewCount, MAX_VEHICLE_STR_DEPENDENCIES);
for(int i =0; i < iNewCount; i++)
{
// Find the streaming index for the group
if(iAnimDictIndicesList[i] > -1)
{
m_iDependencyStreamingIndicies[iActualCount++] = fwAnimManager::GetStreamingModule()->GetStreamingIndex(strLocalIndex(iAnimDictIndicesList[i]));
}
}
}
//Temporary hack to stream in the tow hook prop for the towtruck and cargobob
if( GetModelNameHash() == MI_CAR_TOWTRUCK.GetName().GetHash() ||
GetModelNameHash() == MI_HELI_CARGOBOB.GetName().GetHash() ||
GetModelNameHash() == MI_HELI_CARGOBOB2.GetName().GetHash() ||
GetModelNameHash() == MI_HELI_CARGOBOB3.GetName().GetHash() ||
GetModelNameHash() == MI_HELI_CARGOBOB4.GetName().GetHash() ||
GetModelNameHash() == MI_HELI_CARGOBOB5.GetName().GetHash())
{
m_iDependencyStreamingIndicies[iActualCount++] = CModelInfo::GetStreamingModule()->GetStreamingIndex(strLocalIndex(MI_PROP_HOOK));
}
// expression loading code
strLocalIndex expressionDataFileIndex = GetExpressionDictionaryIndex();
// Stream in the expression dictionary
if (expressionDataFileIndex != -1 && g_ExpressionDictionaryStore.IsObjectInImage(expressionDataFileIndex))
{
m_iDependencyStreamingIndicies[iActualCount++] = g_ExpressionDictionaryStore.GetStreamingIndex(expressionDataFileIndex);
}
// vehicle meta data
#if LIVE_STREAMING
if (GetVehicleMetaDataFileIndex() == -1)
SetVehicleMetaDataFile(GetModelName());
#endif
if (GetVehicleMetaDataFileIndex() != -1)
m_iDependencyStreamingIndicies[iActualCount++] = g_fwMetaDataStore.GetStreamingIndex(GetVehicleMetaDataFileIndex());
taskAssertf(iActualCount <= MAX_VEHICLE_STR_DEPENDENCIES, "CVehicleModelInfo::CalculateStreamingDependencies - %s has too many dependencies", GetModelName());
m_uNumDependencies = (u8)iActualCount;
m_bDependenciesValid = true;
}
//-------------------------------------------------------------------------
// Constructs a collision model to test against vehicles in order to determine
// available cover points
//-------------------------------------------------------------------------
void CVehicleModelInfo::ConstructStaticData( void )
{
}
#if !__FINAL
//-------------------------------------------------------------------------
// Draws a debug outline representing the collision model used to
// judge the height of the vehicle at various points
//-------------------------------------------------------------------------
void CVehicleModelInfo::DebugDrawCollisionModel( Matrix34& DEBUG_DRAW_ONLY(mMat) )
{
#if DEBUG_DRAW
modelinfoAssert(m_data);
s32 i=0;
s32 numCapsules = 6;
Matrix34 capsuleMatrix[6];
// Calculate the max bounding box extents
Vector3 vMaxBoundingBox( Max(fabs(GetBoundingBoxMax().x), fabs(GetBoundingBoxMin().x)),
Max(fabs(GetBoundingBoxMax().y), fabs(GetBoundingBoxMin().y)),
Max(fabs(GetBoundingBoxMax().z), fabs(GetBoundingBoxMin().z)) );
for(i=0; i<numCapsules; i++)
{
capsuleMatrix[i].Identity();
capsuleMatrix[i].RotateX(-HALF_PI);
}
// Front left
capsuleMatrix[0].d = Vector3(0.5f*GetBoundingBoxMax().x, 0.66f*GetBoundingBoxMax().y, 0.0f);
// Mid left
capsuleMatrix[1].d = Vector3(0.5f*GetBoundingBoxMax().x, 0.0f, 0.0f);
// Back left
capsuleMatrix[2].d = Vector3(0.5f*GetBoundingBoxMax().x, 0.66f*GetBoundingBoxMin().y, 0.0f);
// Front Right
capsuleMatrix[3].d = Vector3(0.5f*GetBoundingBoxMin().x, 0.66f*GetBoundingBoxMax().y, 0.0f);
// Mid right
capsuleMatrix[4].d = Vector3(0.5f*GetBoundingBoxMin().x, 0.0f, 0.0f);
// Back right
capsuleMatrix[5].d = Vector3(0.5f*GetBoundingBoxMin().x, 0.66f*GetBoundingBoxMin().y, 0.0f);
for(i=0; i<numCapsules; i++)
{
mMat.Transform(capsuleMatrix[i].d);
grcDebugDraw::Line( capsuleMatrix[i].d + Vector3( 0.0f, 0.0f, -5.0f ), capsuleMatrix[i].d + Vector3( 0.0f, 0.0f, m_data->m_afHeightMap[i] + GetBoundingBoxMin().z), Color32(0xff, 0x00, 0x00, 0xff) );
grcDebugDraw::Sphere( capsuleMatrix[i].d + Vector3( 0.0f, 0.0f, m_data->m_afHeightMap[i]+ GetBoundingBoxMin().z), 0.2f, Color32(0x00, 0xff, 0x00, 0xff) );
}
#endif // DEBUG_DRAW
}
#endif
//-------------------------------------------------------------------------
// Returns the next coverpoint local offset in the direction given
//-------------------------------------------------------------------------
void CVehicleModelInfo::FindNextCoverPointOffsetInDirection( Vector3& vOut, const Vector3& vStart, const bool bClockwise ) const
{
modelinfoAssert(m_data);
float fCurrentBest = -999.0f;
vOut = Vector3(0.0f, 0.0f, 0.0f);
Vector3 vStartVec = vStart;
vStartVec.z = 0.0f;
vStartVec.Normalize();
Vector3 vRight;
vRight.Cross(vStartVec, Vector3(0.0f, 0.0f, 1.0f));
for( s32 i = 0; i < m_data->m_iCoverPoints; i++ )
{
// Don't check the starting point
if( vStart != m_data->m_aCoverPoints[i].m_vLocalOffset )
{
Vector3 vThisVec = m_data->m_aCoverPoints[i].m_vLocalOffset;
vThisVec.z = 0.0f;
vThisVec.Normalize();
float fDot = vStartVec.Dot(vThisVec);
float fRightDot = vRight.Dot(vThisVec);
if( ( fRightDot > 0.0f && bClockwise ) ||
( fRightDot < 0.0f && !bClockwise ) )
{
fDot -= 2.0f;
}
if( fDot > fCurrentBest )
{
fCurrentBest = fDot;
vOut = m_data->m_aCoverPoints[i].m_vLocalOffset;
}
}
}
}
s32 CVehicleModelInfo::GetEntryExitPointIndex(const CEntryExitPoint* pEntryExitPoint) const
{
modelinfoAssert(m_data);
return m_data->m_SeatInfo.GetEntryExitPointIndex(pEntryExitPoint);
}
//-------------------------------------------------------------------------
// Constructs a collision model to test against vehicles in order to determine
// available cover points
//-------------------------------------------------------------------------
void CVehicleModelInfo::SizeCoverTestForVehicle(phInstGta* UNUSED_PARAM(pTestInst), Vector3* aTestPositions, float& fCapsuleRadius, float& fCapsuleLength)
{
// Calculate the max bounding box extents
Vector3 vMaxBoundingBox(Max(fabs(GetBoundingBoxMax().x), fabs(GetBoundingBoxMin().x)),
Max(fabs(GetBoundingBoxMax().y), fabs(GetBoundingBoxMin().y)),
Max(fabs(GetBoundingBoxMax().z), fabs(GetBoundingBoxMin().z)) );
fCapsuleRadius = 0.1f * vMaxBoundingBox.y;
fCapsuleLength = vMaxBoundingBox.z * 10.0f;
// Front left
aTestPositions[0] = Vector3(0.5f*GetBoundingBoxMax().x, 0.66f*GetBoundingBoxMax().y, 0.0f);
// Mid left
aTestPositions[1] = Vector3(0.5f*GetBoundingBoxMax().x, 0.0f, 0.0f);
// Back left
aTestPositions[2] = Vector3(0.5f*GetBoundingBoxMax().x, 0.66f*GetBoundingBoxMin().y, 0.0f);
// Front Right
aTestPositions[3] = Vector3(0.5f*GetBoundingBoxMin().x, 0.66f*GetBoundingBoxMax().y, 0.0f);
// Mid right
aTestPositions[4] = Vector3(0.5f*GetBoundingBoxMin().x, 0.0f, 0.0f);
// Back right
aTestPositions[5] = Vector3(0.5f*GetBoundingBoxMin().x, 0.66f*GetBoundingBoxMin().y, 0.0f);
}
//-------------------------------------------------------------------------
// Destroy any static data associated with vehicle models
//-------------------------------------------------------------------------
void CVehicleModelInfo::DestroyStaticData( void )
{
}
// ---- HD streaming stuff ----
//
#ifdef RSG_PC
#define HD_REF_LIMIT 1500 //PC has more things going on, so for high end stuff we need to bump this number compared with NG consoles
#else
#define HD_REF_LIMIT 1000 //if you this this number of HD refs something has gone wrong
#endif
// PURPOSE: Add a reference to this archetype and add ref to any HD assets loaded
void CVehicleModelInfo::AddHDRef( bool bRenderRef )
{
AddRef();
if (bRenderRef){
Assert(m_numHDRenderRefs < HD_REF_LIMIT);
m_numHDRenderRefs++;
} else
{
Assert(m_numHDRefs < HD_REF_LIMIT);
m_numHDRefs++;
}
}
void CVehicleModelInfo::RemoveHDRef( bool bRenderRef )
{
Assert(GetNumHdRefs() > 0);
RemoveRef();
{
if (bRenderRef){
Assert(m_numHDRenderRefs > 0);
if (m_numHDRenderRefs > 0)
{
m_numHDRenderRefs--;
}
} else
{
Assert(m_numHDRefs > 0);
if (m_numHDRefs > 0)
{
m_numHDRefs--;
}
}
if (GetNumHdRefs() == 0)
{
modelinfoDisplayf("+++ vehicle cleanup : %s", GetModelName());
ReleaseHDFiles();
}
}
}
void CVehicleModelInfo::AddToHDInstanceList(size_t id)
{
modelinfoAssert(id != 0);
modelinfoAssert(!m_HDInstanceList.IsMemberOfList((void*)id));
if (GetHasHDFiles())
{
// set this to true to disable HD vehicle switching
static bool bDisableHD = false;
if (bDisableHD){
return;
}
// if this is the first one then issue the HD requests for this type
if (m_HDInstanceList.GetHeadPtr() == NULL){
RequestAndRefHDFiles();
}
m_HDInstanceList.Add((void*)id);
ms_numHdVehicles++;
}
}
void CVehicleModelInfo::RemoveFromHDInstanceList(size_t id)
{
modelinfoAssert(id != 0);
if (m_HDInstanceList.IsMemberOfList((void*)id))
{
m_HDInstanceList.Remove((void*)id);
ms_numHdVehicles--;
// if this was the last one then free up the HD requests for this type
if (m_HDInstanceList.GetHeadPtr() == NULL)
{
ReleaseRequestOrRemoveRefHD();
}
}
}
void CVehicleModelInfo::SetupHDSharedTxd(const char* pName)
{
if ( pName == NULL || strcmp(pName, "NULL") == 0 || strcmp(pName, "null") == 0)
{
return;
}
char HDName[255];
u32 size = ustrlen(pName);
if (size > 250){
return;
}
strncpy(HDName, pName, size);
HDName[size] = '+';
HDName[size+1] = 'h';
HDName[size+2] = 'i';
HDName[size+3] = '\0';
s32 targetTxdIdx = g_TxdStore.FindSlot(pName).Get();
s32 HDtxdIdx = g_TxdStore.FindSlot(HDName).Get();
if (HDtxdIdx != -1 && targetTxdIdx != -1)
{
CTexLod::StoreHDMapping(STORE_ASSET_TXD, targetTxdIdx, HDtxdIdx);
}
}
// name: SetFragment
// description: Set texture dictionary for object
void CVehicleModelInfo::SetupHDFiles(const char* pName)
{
if ( pName == NULL || strcmp(pName, "NULL") == 0 || strcmp(pName, "null") == 0)
{
return;
}
if (!(GetHDDist() > 0.0f)){
return;
}
char HDName[255];
u32 size = ustrlen(pName);
if (size > 250){
return;
}
strncpy(HDName, pName, size);
HDName[size] = '_';
HDName[size+1] = 'h';
HDName[size+2] = 'i';
HDName[size+3] = '\0';
m_HDfragIdx = g_FragmentStore.FindSlot(HDName).Get();
if (m_HDfragIdx == -1){
m_HDfragIdx = g_FragmentStore.AddSlot(HDName).Get();
}
HDName[size] = '+'; // HD txd name is different because it is machine generated
m_HDtxdIdx = g_TxdStore.FindSlot(HDName).Get();
if (m_HDtxdIdx != -1)
{
CTexLod::StoreHDMapping(STORE_ASSET_TXD, GetAssetParentTxdIndex(), m_HDtxdIdx);
}
g_FragmentStore.SetParentTxdForSlot(strLocalIndex(m_HDfragIdx), strLocalIndex(GetAssetParentTxdIndex()));
}
void CVehicleModelInfo::ConfirmHDFiles(void){
if (GetHasHDFiles()){
// verify existence of HD files
if (m_HDfragIdx != -1 && g_FragmentStore.IsObjectInImage(strLocalIndex(m_HDfragIdx))){
return;
}
}
// verification failed. Set HD dist to invalid
SetHDDist(-1.0f);
}
// issue the HD request for this vehicle type (if it has one set up)
void CVehicleModelInfo::RequestAndRefHDFiles(void){
if (GetAreHDFilesLoaded())
{
AddHDRef(false);
return;
}
if (!GetRequestLoadHDFiles()){
VehicleHDStreamReq* pNewReq = rage_new(VehicleHDStreamReq);
Assert(pNewReq);
if (pNewReq){
pNewReq->m_Requests.PushRequest(GetHDFragmentIndex(), g_FragmentStore.GetStreamingModuleId());
pNewReq->m_pTargetVehicleMI = this;
ms_HDStreamRequests.Add(pNewReq); // HD ref is added once loaded (in update
SetRequestLoadHDFiles(true);
return;
}
}
modelinfoAssertf(false, "Error in CVehicleModelInfo::RequestAndRefHDFiles");
}
// remove the request if it still outstanding, otherwise remove the ref
void CVehicleModelInfo::ReleaseRequestOrRemoveRefHD(void){
if (GetAreHDFilesLoaded())
{
Assert(m_numHDRefs > 0);
RemoveHDRef(false);
return;
}
if (GetRequestLoadHDFiles()){
// remove requested files
fwPtrNode* pLinkNode = ms_HDStreamRequests.GetHeadPtr();
while(pLinkNode) {
VehicleHDStreamReq* pStreamRequest = static_cast<VehicleHDStreamReq*>(pLinkNode->GetPtr());
pLinkNode = pLinkNode->GetNextPtr();
if (pStreamRequest && pStreamRequest->m_pTargetVehicleMI == this)
{
SetRequestLoadHDFiles(false);
ms_HDStreamRequests.Remove(pStreamRequest);
delete pStreamRequest;
}
}
return;
}
modelinfoAssertf(false, "Error in CVehicleModelInfo::ReleaseRequestOrRemoveRefHD");
}
// cleanup HD state back to normal state (whether HD requested or loaded)
void CVehicleModelInfo::ReleaseHDFiles(void)
{
modelinfoAssert(m_data);
modelinfoAssert(GetAreHDFilesLoaded());
modelinfoAssert(GetHasHDFiles());
modelinfoAssert(GetNumHdRefs() == 0);
if (GetHasHDFiles())
{
if(m_data->m_pHDShaderEffectType)
{
gtaFragType* pFrag = GetHDFragType();
if(pFrag)
{
// check txds are set as expected
vehicleAssertf(g_TxdStore.HasObjectLoaded(strLocalIndex(GetAssetParentTxdIndex())), "archetype %s : unexpected txd state", GetModelName());
m_data->m_pHDShaderEffectType->RestoreModelInfoDrawable(pFrag->GetCommonDrawable());
}
m_data->m_pHDShaderEffectType->RemoveRef();
m_data->m_pHDShaderEffectType = NULL;
}
if (GetAreHDFilesLoaded())
{
// remove loaded files ref
modelinfoAssert(GetHDFragmentIndex() != -1);
g_FragmentStore.RemoveRef(strLocalIndex(GetHDFragmentIndex()), REF_OTHER);
SetAreHDFilesLoaded(false);
#if __BANK
int i;
for (i = 0; i < ms_HDVehicleInfos.GetCount(); ++i)
{
if (ms_HDVehicleInfos[i].pVMI == this)
{
ms_HDVehicleInfos[i].count--;
if (ms_HDVehicleInfos[i].count == 0)
{
ms_HDVehicleInfos.Delete(i);
}
break;
}
}
#endif
}
}
}
// update the state of any requests, start triggering requests if desired
void CVehicleModelInfo::UpdateHDRequests(void){
STRVIS_AUTO_CONTEXT(strStreamingVisualize::HDVEHICLES);
fwPtrNode* pLinkNode = ms_HDStreamRequests.GetHeadPtr();
while(pLinkNode)
{
VehicleHDStreamReq* pStreamRequest = static_cast<VehicleHDStreamReq*>(pLinkNode->GetPtr());
pLinkNode = pLinkNode->GetNextPtr();
if (pStreamRequest)
{
if (pStreamRequest->m_Requests.HaveAllLoaded() && pStreamRequest->m_pTargetVehicleMI && (pStreamRequest->m_pTargetVehicleMI->m_data != NULL)){
// add refs
CVehicleModelInfo* pVMI = pStreamRequest->m_pTargetVehicleMI;
modelinfoAssert(pVMI);
g_FragmentStore.AddRef(strLocalIndex(pVMI->GetHDFragmentIndex()), REF_OTHER);
pVMI->SetAreHDFilesLoaded(true);
pVMI->SetRequestLoadHDFiles(false);
ms_HDStreamRequests.Remove(pStreamRequest);
delete pStreamRequest;
pVMI->SetupHDCustomShaderEffects();
pVMI->AddHDRef(false);
#if __BANK
int i;
bool found = false;
for (i = 0; i < ms_HDVehicleInfos.GetCount(); ++i)
{
if (ms_HDVehicleInfos[i].pVMI == pVMI)
{
found = true;
break;
}
}
if (found)
{
ms_HDVehicleInfos[i].count++;
}
else
{
HDVehilceRefCount vmi = { pVMI, 1 };
ms_HDVehicleInfos.PushAndGrow(vmi);
}
#endif
}
}
}
}
gtaFragType* CVehicleModelInfo::GetHDFragType() const
{
gtaFragType* ret = NULL;
strLocalIndex HDfragIdx = strLocalIndex(GetHDFragmentIndex());
if (HDfragIdx != -1)
{
if (g_FragmentStore.HasObjectLoaded(HDfragIdx))
{
ret = static_cast<gtaFragType*>(g_FragmentStore.Get(HDfragIdx));
}
}
return(ret);
}
bool CVehicleModelInfo::SetupHDCustomShaderEffects()
{
modelinfoAssert(m_data);
gtaFragType* pFrag = GetHDFragType();
if(pFrag)
{
rmcDrawable *pDrawable = pFrag->GetCommonDrawable();
if (pDrawable)
{
#if __ASSERT
extern char* CCustomShaderEffectBase_EntityName;
CCustomShaderEffectBase_EntityName = (char*)this->GetModelName(); // debug only: defined in CCustomShaderEffectBase.cpp
#endif //__ASSERT...
if(m_data->m_pHDShaderEffectType)
{ // re-initialise CSE HD (if created):
m_data->m_pHDShaderEffectType->Recreate(pDrawable, this);
m_data->m_pHDShaderEffectType->Initialise(pDrawable);
}
else
{ // create CSE HD (if not created already):
m_data->m_pHDShaderEffectType = static_cast<CCustomShaderEffectVehicleType*>(CCustomShaderEffectBaseType::SetupMasterVehicleHDInfo(this));
}
if(m_data->m_pHDShaderEffectType)
{
modelinfoAssertf(m_data->m_pHDShaderEffectType->AreShadersValid(pDrawable), "%s: Model has wrong shaders applied", GetModelName());
m_data->m_pHDShaderEffectType->SetIsHighDetail(true);
}
return(TRUE);
}
}
return(FALSE);
}
fwModelId CVehicleModelInfo::GetRandomTrailer() const
{
fwModelId ret;
if (m_trailers.GetCount() == 0)
{
return ret;
}
s32 idx = fwRandom::GetRandomNumberInRange(0, m_trailers.GetCount());
u32 spawnTrailer = fwModelId::MI_INVALID;
u32 lastTimeUsed = fwTimer::GetTimeInMilliseconds();
for (s32 i = 0; i < m_trailers.GetCount(); ++i, ++idx)
{
if (idx == m_trailers.GetCount())
idx = 0;
fwModelId trailerModelId;
CVehicleModelInfo* modelInfo = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfoFromName(m_trailers[idx], &trailerModelId);
// TR2 and TR4 not allowed in MP
if (CNetwork::IsGameInProgress() && ((u32)trailerModelId.GetModelIndex() == MI_TRAILER_TR2 || (u32)trailerModelId.GetModelIndex() == MI_TRAILER_TR4))
continue;
if(modelinfoVerifyf(modelInfo, "Invalid model info for vehicle '%s'", m_trailers[idx].GetCStr()))
{
u32 lastTimeUsedModel = modelInfo->GetLastTimeUsed();
if (lastTimeUsedModel < lastTimeUsed)
{
lastTimeUsed = lastTimeUsedModel;
spawnTrailer = trailerModelId.GetModelIndex();
}
}
}
ret.SetModelIndex(spawnTrailer);
return ret;
}
fwModelId CVehicleModelInfo::GetRandomLoadedTrailer() const
{
fwModelId ret;
if (m_trailers.GetCount() == 0)
return ret;
s32 idx = fwRandom::GetRandomNumberInRange(0, m_trailers.GetCount());
float smallestOccurrence = 999.f;
for (s32 i = 0; i < m_trailers.GetCount(); ++i, ++idx)
{
if (idx == m_trailers.GetCount())
idx = 0;
CVehicleModelInfo* vmi = NULL;
fwModelId id = GetTrailer(idx, vmi);
if(vmi && CModelInfo::HaveAssetsLoaded(id))
{
// TR2 and TR4 not allowed in MP
if (CNetwork::IsGameInProgress() && ((u32)id.GetModelIndex() == MI_TRAILER_TR2 || (u32)id.GetModelIndex() == MI_TRAILER_TR4))
continue;
float occ = (vmi->GetNumVehicleModelRefs() + vmi->GetNumVehicleModelRefsParked() + 1) / (float)vmi->GetVehicleFreq();
if (occ < smallestOccurrence)
{
smallestOccurrence = occ;
ret = id;
}
}
}
return ret;
}
fwModelId CVehicleModelInfo::GetTrailer(u32 idx, CVehicleModelInfo*& vmi) const
{
fwModelId trailerModelId;
if (idx >= m_trailers.GetCount())
return trailerModelId;
vmi = (CVehicleModelInfo*)CModelInfo::GetBaseModelInfoFromName(m_trailers[idx], &trailerModelId);
return trailerModelId;
}
bool CVehicleModelInfo::GetIsCompatibleTrailer(CVehicleModelInfo * trailerModelInfo)
{
if(trailerModelInfo)
{
u32 trailerHash = trailerModelInfo->GetModelNameHash();
// GTAV - HACK to fix B*1944449 - if this is tanker2 treat it like tanker
if( MI_TRAILER_TANKER2.IsValid() && MI_TRAILER_TANKER2.GetName().GetHash() == trailerHash )
{
trailerHash = MI_TRAILER_TANKER.GetName().GetHash();
}
for( int i = 0; i < m_trailers.GetCount(); i++ )
{
if( trailerHash == m_trailers[i])
{
return true;
}
}
if( GetModelNameHash() == MI_CAR_PHANTOM2.GetName().GetHash() )
{
if( trailerHash == MI_TRAILER_TRAILERLARGE.GetName().GetHash() )
{
return true;
}
}
for(int i = 0; i < m_additionalTrailers.GetCount(); i++)
{
if( trailerHash == m_additionalTrailers[i] )
{
return true;
}
}
}
return false;
}
u32 CVehicleModelInfo::GetRandomDriver() const
{
if (m_drivers.GetCount() == 0)
{
return fwModelId::MI_INVALID;
}
//u32 spawnDriver = fwModelId::MI_INVALID;
fwModelId spawnDriverId;
fwModelId spawnDriverLoadedId;
s32 smallestOccurrence = 999;
s32 smallestOccurrenceLoaded = 999;
s32 largestOccurrence = 0;
for (s32 i = 0; i < m_drivers.GetCount(); ++i)
{
fwModelId driverModelId;
CPedModelInfo* modelInfo = (CPedModelInfo*)CModelInfo::GetBaseModelInfoFromName(m_drivers[i].GetDriverName(), &driverModelId);
if(modelinfoVerifyf(modelInfo, "Invalid model info for ped '%s'", m_drivers[i].GetDriverName().GetCStr()))
{
s32 occ = modelInfo->GetNumPedModelRefs() + 1;
if (occ < smallestOccurrence)
{
smallestOccurrence = occ;
spawnDriverId = driverModelId;
}
if (occ > largestOccurrence)
{
largestOccurrence = occ;
}
if (CModelInfo::HaveAssetsLoaded(driverModelId) && occ < smallestOccurrenceLoaded)
{
smallestOccurrenceLoaded = occ;
spawnDriverLoadedId = driverModelId;
}
}
}
if (!spawnDriverLoadedId.IsValid())
{
if (smallestOccurrence == 999 && largestOccurrence == 999)
{
s32 idx = fwRandom::GetRandomNumberInRange(0, GetDriverCount());
CModelInfo::GetBaseModelInfoFromName(m_drivers[idx].GetDriverName(), &spawnDriverId);
}
}
else
{
spawnDriverId = spawnDriverLoadedId;
}
return spawnDriverId.GetModelIndex();
}
void CVehicleModelInfo::AddVehicleInstance(CVehicle* veh)
{
modelinfoAssert(m_data);
if (!veh)
return;
s32 poolIndex = (s32) CVehicle::GetPool()->GetJustIndex(veh);
if (Verifyf(poolIndex > -1 && poolIndex <= 65535, "Too many vehicles. See declaration of m_vehicleInstances."))
{
#if __DEV
for (s32 i = 0; i < m_data->m_vehicleInstances.GetCount(); ++i)
{
Assertf(m_data->m_vehicleInstances[i] != static_cast<u16>(poolIndex), "Stored vehicle instance already in list! index: %d - stored index: %d - truncIndex: %d - 0x%p", i, m_data->m_vehicleInstances[i], poolIndex, veh);
}
#endif
m_data->m_vehicleInstances.PushAndGrow(static_cast<u16>(poolIndex));
}
}
void CVehicleModelInfo::RemoveVehicleInstance(CVehicle* veh)
{
modelinfoAssert(m_data);
if (!veh)
return;
s32 poolIndex = (s32) CVehicle::GetPool()->GetJustIndex(veh);
if (Verifyf(poolIndex > -1 && poolIndex <= 65535, "Too many vehicles. See declaration of m_vehicleInstances."))
{
for (s32 i = 0; i < m_data->m_vehicleInstances.GetCount(); ++i)
{
if (m_data->m_vehicleInstances[i] == static_cast<u16>(poolIndex))
{
m_data->m_vehicleInstances.DeleteFast(i);
break;
}
}
}
}
CVehicle* CVehicleModelInfo::GetVehicleInstance(u32 index)
{
modelinfoAssert(m_data);
if (Verifyf(index < m_data->m_vehicleInstances.GetCount(), "CVehicleModelInfo::GetVehicleInstances - index out of range"))
{
s32 poolIndex = (s32)m_data->m_vehicleInstances[index];
return CVehicle::GetPool()->GetSlot(poolIndex);
}
return NULL;
}
bool CVehicleModelInfo::HasAnyMissionInstances()
{
if (!m_data)
return false;
const u32 numInsts = GetNumVehicleInstances();
for (s32 i = 0; i < numInsts; ++i)
{
CVehicle* veh = GetVehicleInstance(i);
if (veh)
{
if (veh->m_nDEflags.nPopType == POPTYPE_MISSION)
return true;
}
}
return false;
};
float CVehicleModelInfo::GetDistanceSqrToClosestInstance(const Vector3& pos, bool cargens)
{
if (!m_data)
return 0.f;
float minDist = 999999.f;
const u32 numInsts = GetNumVehicleInstances();
for (s32 i = 0; i < numInsts; ++i)
{
CVehicle* veh = GetVehicleInstance(i);
if (veh)
{
if (!cargens || veh->m_nDEflags.nPopType == POPTYPE_RANDOM_PARKED)
{
if(!veh->m_nVehicleFlags.bIsInVehicleReusePool)
{
float dist = DistSquared(veh->GetTransform().GetPosition(), VECTOR3_TO_VEC3V(pos)).Getf();
minDist = dist < minDist ? dist : minDist;
}
}
}
}
if (cargens)
{
fwModelId modelId = CModelInfo::GetModelIdFromName(GetModelNameHash());
for (s32 i = 0; i < CTheCarGenerators::m_scheduledVehicles.GetCount(); ++i)
{
if (!CTheCarGenerators::m_scheduledVehicles[i].valid)
continue;
if (CTheCarGenerators::m_scheduledVehicles[i].carModelId.GetModelIndex() == modelId.GetModelIndex())
{
float dist = DistSquared(VECTOR3_TO_VEC3V(CTheCarGenerators::m_scheduledVehicles[i].creationMatrix.d), VECTOR3_TO_VEC3V(pos)).Getf();
minDist = dist < minDist ? dist : minDist;
}
}
}
return minDist;
}
#if __BANK
void CVehicleModelInfo::DumpVehicleInstanceDataCB()
{
CEntity* entity = (CEntity*)g_PickerManager.GetSelectedEntity();
if (!entity || !entity->GetIsTypeVehicle())
{
Displayf("No selected vehicle\n");
return;
}
CVehicle* selectedVeh = (CVehicle*)entity;
CVehicleModelInfo* vmi = selectedVeh->GetVehicleModelInfo();
Assert(vmi);
Displayf("**** Vehicle instance data for '%s' (%u / %d) ****\n", vmi->GetModelName(), vmi->GetNumVehicleInstances(), (int) CVehicle::GetPool()->GetNoOfUsedSpaces());
for (s32 i = 0; i < vmi->GetNumVehicleInstances(); ++i)
{
CVehicle* veh = vmi->GetVehicleInstance(i);
if (veh)
Displayf("** Veh %d - 0x%p - (%.2f, %.2f, %.2f) **\n", i, veh, veh->GetTransform().GetPosition().GetXf(), veh->GetTransform().GetPosition().GetYf(), veh->GetTransform().GetPosition().GetZf());
else
Displayf("** Veh %d - null veh! **\n", i);
}
}
#endif
bool CVehicleModelInfo::GetCanPassengersBeKnockedOff()
{
return GetIsBike() || GetIsQuadBike() || GetIsAmphibiousQuad();
}
const bool CVehicleModelInfo::AllowsVehicleEntryWhenStoodOn(const CVehicle& veh)
{
const CVehicleModelInfo* pModelInfo = veh.GetVehicleModelInfo();
if (!pModelInfo)
return false;
if (pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_CONSIDERED_FOR_VEHICLE_ENTRY_WHEN_STOOD_ON))
return true;
switch (veh.GetVehicleType())
{
case VEHICLE_TYPE_BOAT:
case VEHICLE_TYPE_SUBMARINE:
case VEHICLE_TYPE_PLANE:
if (!pModelInfo->GetVehicleFlag(CVehicleModelInfoFlags::FLAG_REJECT_ENTRY_TO_VEHICLE_WHEN_STOOD_ON))
return true;
default:
return false;
}
}
#if !__SPU
Vec3V_Out CVehicleModelInfo::CalculateWheelOffset(eHierarchyId wheelId) const
{
Vec3V vecOffset(V_ZERO);
if(GetBoneIndex(wheelId) > -1)
{
const crSkeletonData* pSkeletonData = NULL;
if(GetFragType())
pSkeletonData = GetFragType()->GetCommonDrawable()->GetSkeletonData();
else if(GetDrawable())
pSkeletonData = GetDrawable()->GetSkeletonData();
if(pSkeletonData)
{
vecOffset = pSkeletonData->GetBoneData(GetBoneIndex(wheelId))->GetDefaultTranslation();
const crBoneData* pParentBoneData = pSkeletonData->GetBoneData(GetBoneIndex(wheelId))->GetParent();
while(pParentBoneData)
{
Mat34V matParent;
Mat34VFromQuatV(matParent, pParentBoneData->GetDefaultRotation());
matParent.SetCol3(pParentBoneData->GetDefaultTranslation());
vecOffset = Transform(matParent, vecOffset);
pParentBoneData = pParentBoneData->GetParent();
}
}
else
Assertf(false, "%s:CWheel::GetWheelOffset, model has no skeleton data", GetModelName());
}
#if __ASSERT
else
{
Assertf(GetBoneIndex(wheelId), "%s:CWheel::GetWheelOffset, wheel bone doesn't exist on model", GetModelName());
}
#endif // __ASSERT
return vecOffset;
}
void CVehicleModelInfo::CacheWheelOffsets()
{
modelinfoAssert(m_data);
int wheelCount = VEH_WHEEL_LAST_WHEEL - VEH_WHEEL_FIRST_WHEEL + 1;
Assert(m_data->m_pStructure);
Assertf(wheelCount > 0, "Vehicle has no wheels");
m_data->m_WheelOffsets.Resize(wheelCount);
for (int x=0; x<wheelCount; x++)
{
eHierarchyId boneId = (eHierarchyId) (x + VEH_WHEEL_FIRST_WHEEL);
if (GetBoneIndex(boneId) > -1)
{
m_data->m_WheelOffsets[x] = CalculateWheelOffset(boneId);
}
}
}
#endif // !__SPU
void CVehicleModelInfo::Update()
{
#if !__GAMETOOL
ms_requestPerFrame = 0; // reset the per frame counter
CVehicle::ResetDialsOverriddenByScript(); //sync reset point
#if __BANK
if (CVehicleFactory::ms_bForceDials)
{
CVehicle* veh = NULL;
CEntity* selected = (CEntity*)g_PickerManager.GetSelectedEntity();
if (selected && selected->GetIsTypeVehicle())
veh = (CVehicle*)selected;
if (!veh)
veh = CVehicleFactory::ms_pLastCreatedVehicle;
if(veh)
veh->RequestDials(true);
}
#endif
if (ms_activeDialsMovie != -1 && ms_dialsFrameCountReq != 0 && fwTimer::GetFrameCount() - ms_dialsFrameCountReq <= 1)
{
if (ms_dialsMovieId == -1)
{
ms_dialsMovieId = CScaleformMgr::CreateMovie(s_dialsMovieNames[ms_activeDialsMovie], Vector2(0.f, 0.f), Vector2(1.f, 1.f), false, -1, -1, true ,SF_MOVIE_TAGGED_BY_CODE, false, true);
Assert(ms_dialsMovieId != -1);
}
if (ms_dialsRenderTargetId != 0)
{
gRenderTargetMgr.UseRenderTarget((CRenderTargetMgr::RenderTargetId)ms_dialsRenderTargetId);
}
}
else
{
ReleaseDials();
}
#endif // !__GAMETOOL
}
void CVehicleModelInfo::ReleaseDials()
{
if (CScaleformMgr::IsMovieActive(ms_dialsMovieId))
{
CScaleformMgr::RequestRemoveMovie(ms_dialsMovieId);
ms_dialsMovieId = -1;
ms_dialsType = -1;
ms_activeDialsMovie = -1;
memset(&ms_cachedDashboardData, 0, sizeof(ms_cachedDashboardData));
}
if (ms_dialsRenderTargetId != 0)
{
if (gRenderTargetMgr.GetNamedRendertarget(ms_rtNameHash, ms_dialsRenderTargetOwner) != NULL)
{
gRenderTargetMgr.ReleaseNamedRendertarget(ms_rtNameHash, ms_dialsRenderTargetOwner);
}
ms_dialsRenderTargetId = 0;
}
ResetRadioInfo();
}
float CVehicleModelInfo::GetSteeringWheelMult(const CPed* pDriver)
{
return (pDriver && pDriver->IsInFirstPersonVehicleCamera() && m_fFirstPersonSteeringWheelMult >= 0.0f ) ? m_fFirstPersonSteeringWheelMult : m_fSteeringWheelMult;
}
void CVehicleModelInfo::ValidateHorns(CVehicleKit& rKit)
{
const atArray<CVehicleModStat>& kitStatMods = rKit.GetStatMods();
const int statModsCount = kitStatMods.GetCount();
int numHornMods = 0;
for(int m = 0; m < statModsCount; m++)
{
if (kitStatMods[m].GetType() == VMT_HORN)
{
numHornMods++;
}
}
if (numHornMods != 0)
{
if (numHornMods < NUM_VEHICLE_HORNS)
{
vehicleAssertf(0, "Vehicle modkit %s has %i horn mods, expecting %i. Please fix carcols.meta.", rKit.GetNameHashString().TryGetCStr(), numHornMods, NUM_VEHICLE_HORNS);
}
else if (numHornMods > NUM_VEHICLE_HORNS)
{
vehicleAssertf(0, "Vehicle modkit %s has %i horn mods, expecting %i. Update NUM_VEHICLE_HORNS in code if new horns exist, or fix carcols.meta.", rKit.GetNameHashString().TryGetCStr(), numHornMods, NUM_VEHICLE_HORNS);
}
}
}