Files
GTASource/game/Vehicles/Trailer.cpp

2057 lines
70 KiB
C++
Raw Permalink Normal View History

2025-02-23 17:40:52 +08:00
#include "vehicles/trailer.h"
// rage
#include "Fragment/Instance.h"
#include "phArticulated\ArticulatedCollider.h"
#include "phBound/boundcomposite.h"
#include "physics/constraintmgr.h"
#include "physics/constraintdistance.h"
#include "physics/constraintspherical.h"
#include "physics/constraintrotation.h"
// framework
#include "fwmaths/vectorutil.h"
// game
#include "animation/AnimBones.h"
#include "network/events/NetworkEventTypes.h"
#include "physics/gtainst.h"
#include "physics/physics.h"
#include "vehicles/Bike.h"
#include "vehicles/vehicle.h"
#include "vehicles/VehicleFactory.h"
#include "vehicles/wheel.h"
#include "vehicleAi/VehicleIntelligence.h"
#include "control/record.h"
#include "game/modelIndices.h"
VEHICLE_OPTIMISATIONS();
const Vector3 CTrailer::sm_offsets[MAX_CARGO_VEHICLES] =
{
Vector3(0.0f, 0.0f, 1.5f), // low middle (as viewed when trailer forward points left in screen space)
Vector3(0.0f, 0.0f, 3.5f), // top middle
Vector3(0.0f,-4.8f, 1.5f), // low left
Vector3(0.0f,-4.9f, 3.5f), // top left
Vector3(0.0f, 4.8f, 1.5f), // low right
Vector3(0.0f, 4.8f, 3.5f) // top right
};
//////////////////////////////////////////////////////////////////////////
// CTrailerLegs
CTrailerLegs::CTrailerLegs()
{
m_iBoneIndex = -1;
m_iFragChild = -1;
m_fCurrentRatio = m_fTargetRatio = 0.999f;
m_fDisplaceDistance = 0.0f;
}
CTrailerLegs::CTrailerLegs(int iBoneIndex, CVehicle *pParent)
{
Init(iBoneIndex, pParent);
}
void CTrailerLegs::Init(int iBoneIndex, CVehicle* pParent)
{
Assert(pParent);
Assert(pParent->GetVehicleFragInst());
Assert(iBoneIndex > -1);
m_iFragChild = pParent->GetVehicleFragInst()->GetComponentFromBoneIndex(iBoneIndex);
m_iBoneIndex = iBoneIndex;
}
static dev_float sfLegDeploySpeed = 3.0f;
void CTrailerLegs::ProcessPhysics(CVehicle* pParent, const float fTimestep, const int UNUSED_PARAM(nTimeslice))
{
// first progress current ratio to desired
float fDiff = m_fTargetRatio - m_fCurrentRatio;
if(fDiff == 0.0f)
{
return;
}
m_fCurrentRatio += Clamp(fDiff, -sfLegDeploySpeed * fTimestep, sfLegDeploySpeed * fTimestep);
m_fCurrentRatio = Clamp(m_fCurrentRatio, 0.0f, 1.0f);
UpdateToCurrentRatio(*pParent);
}
void CTrailerLegs::UpdateToCurrentRatio(CVehicle & parentTrailer)
{
if(m_iBoneIndex == -1 || m_iFragChild == -1)
{
// No collision
return;
}
// Need to be awake to progress, because we need an articulated collider to adjust link attachment matrices
if(parentTrailer.IsAsleep())
{
parentTrailer.ActivatePhysics();
}
// We move the legs up by their bounding box value
// At 1.0f they are at authored position
// Find the authored link attach position of the group
const fragInst* pFragInst = parentTrailer.GetVehicleFragInst();
Assert(pFragInst);
// Move up by z extents of this component part
Assert(pFragInst->GetCached());
const phBoundComposite* pOriginalComposite = pFragInst->GetTypePhysics()->GetCompositeBounds();
phBoundComposite* pInstanceComposite = pFragInst->GetCacheEntry()->GetBound();
phBound* pChildBound = pInstanceComposite->GetBound(m_iFragChild);
Matrix34 matLegBound = RCC_MATRIX34(pOriginalComposite->GetCurrentMatrix(m_iFragChild));
float fDisplaceDist = pChildBound->GetBoundingBoxMax().GetZf() - pChildBound->GetBoundingBoxMin().GetZf();
// Add extra bit on until art is fixed
static dev_float sfAdditionalLegExtend = 0.2f;
static dev_float sfAdditionalLegExtendForTrailerLarge = 0.4f;
float additionalLegExtend = sfAdditionalLegExtend;
if( MI_TRAILER_TRAILERLARGE.IsValid() && parentTrailer.GetModelIndex() == MI_TRAILER_TRAILERLARGE )
{
additionalLegExtend = sfAdditionalLegExtendForTrailerLarge;
}
float fOriginalMatLegBoundZ = matLegBound.d.z;
matLegBound.d.z += m_fDisplaceDistance;
pInstanceComposite->SetLastMatrix(m_iFragChild, MATRIX34_TO_MAT34V(matLegBound));
m_fDisplaceDistance = fDisplaceDist * (1.0f - m_fCurrentRatio) - additionalLegExtend;
matLegBound.d.z = fOriginalMatLegBoundZ;
matLegBound.d.z += m_fDisplaceDistance;
pInstanceComposite->SetCurrentMatrix(m_iFragChild, MATRIX34_TO_MAT34V(matLegBound));
// Now setup the link attach matrices so the current matrix doesn't get overwritten by updatecurrentandlastmatrices
if (const fragCacheEntry* pEntry = parentTrailer.GetVehicleFragInst()->GetCacheEntry())
{
if (Matrix34* linkAttachmentMatrices = pEntry->GetHierInst()->linkAttachment->GetElements())
{
phArticulatedBody* body = pEntry->GetHierInst()->body;
Assert(body);
const phArticulatedCollider* collider = pEntry->GetHierInst()->articulatedCollider;
int bodyPartIndex = collider->GetLinkFromComponent(m_iFragChild);
Assert(bodyPartIndex>=0);
Matrix34 bodyPartMatrix(MAT34V_TO_MATRIX34(body->GetLink(bodyPartIndex).GetMatrix()));
bodyPartMatrix.Transpose();
Matrix34& linkAttachment = linkAttachmentMatrices[m_iFragChild];
linkAttachment.Set(matLegBound);
linkAttachment.Dot(RCC_MATRIX34(parentTrailer.GetVehicleFragInst()->GetMatrix()));
linkAttachment.d.Subtract(RCC_VECTOR3(collider->GetPosition()));
linkAttachment.DotTranspose(bodyPartMatrix);
}
}
float oldRadius = pInstanceComposite->GetRadiusAroundCentroid();
pInstanceComposite->CalculateCompositeExtents();
CPhysics::GetLevel()->RebuildCompositeBvh(pFragInst->GetLevelIndex());
// toggle the legs to not collide when fully up
if(m_iFragChild > -1)
{
if(m_fCurrentRatio <= 0.0f)
{
pInstanceComposite->SetIncludeFlags(m_iFragChild, 0);
}
else
{
pInstanceComposite->SetIncludeFlags(m_iFragChild, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
}
}
if (oldRadius != pInstanceComposite->GetRadiusAroundCentroid())
{
CPhysics::GetLevel()->UpdateObjectLocationAndRadius(pFragInst->GetLevelIndex(), (Mat34V_Ptr)NULL);
}
}
void CTrailerLegs::PreRender(CVehicle *pParent)
{
if(pParent->GetSkeleton() )
{
if(m_iBoneIndex > -1)
{
Matrix34& legMatrix = pParent->GetLocalMtxNonConst(m_iBoneIndex);
legMatrix.Identity3x3();
legMatrix.d = RCC_VECTOR3(pParent->GetSkeleton()->GetSkeletonData().GetBoneData(m_iBoneIndex)->GetDefaultTranslation());
static dev_float sfLegDisplaceMax = 0.25f;
if(m_fDisplaceDistance < sfLegDisplaceMax)
{
legMatrix.d.z += m_fDisplaceDistance;
}
else
{
legMatrix.d.z += sfLegDisplaceMax;
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// CTrailer
CTrailer::CTrailer(const eEntityOwnedBy ownedBy, u32 popType) :
CAutomobile(ownedBy, popType, VEHICLE_TYPE_TRAILER),
m_vLastFramesCrossParentUpTrailerUp(VEC3_ZERO),
m_AttachedParent(nullptr)
{
//m_pRotConstraintX = NULL;
//m_pRotConstraintZ = NULL;
m_isAttachedToParent = false;
for (s32 i = 0; i < MAX_CARGO_VEHICLES; ++i)
{
m_cargoVehicles[i] = NULL;
}
m_cargoVehiclesAttached = false;
m_bCollidedWithAttachParent = false;
m_bHasBreakableExtras = false;
m_bIsTrailerAttachmentEnabled = true;
m_bCanLocalPlayerAttach = true;
m_bDisableCollisionsWithObjectsBasicallyAttachedToParent = true;
m_bDisablingImpacts = false;
//m_bHasExtendingSides = false;
//m_extendingSidesRatio = 0.0f;
//m_extendingSidesTargetRatio = 0.0f;
//m_scriptOverridingExtendableRatio = false;
//m_stationaryDuration = 0.0f;
}
CTrailer::~CTrailer()
{
#if __ASSERT
for(int i=0; i<MAX_CARGO_VEHICLES; i++)
{
Assertf(!m_cargoVehicles[i],"Trailer being destructed with cargo vehicles still attached. Are cargo vehicles being added outside of CVehicleFactory?");
}
#endif
}
void CTrailer::DoProcessControl(bool fullUpdate, float fFullUpdateTimeStep)
{
CAutomobile::DoProcessControl(fullUpdate, fFullUpdateTimeStep);
// Todo check type
if(GetIsAttached())
{
m_trailerLegs.SetTargetRatio(0.0f);
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
if(pAttachParent)
{
Assert(pAttachParent->GetIsTypeVehicle());
CVehicle* pParentVehicle = static_cast<CVehicle*>(pAttachParent);
//copy the vehicle controls into the trailer so it will update its reversing lights and brake lights.
m_vehControls = pParentVehicle->m_vehControls;
m_nVehicleFlags.bLightsOn = pParentVehicle->m_nVehicleFlags.bLightsOn;
}
}
else
{
float trailerLegsRatio = 1.0f;
if(GetStatus() == STATUS_WRECKED)
{
// Don't extend the legs if wrecked (This'll actually pull them in if they were extended too - that's probably ok)
trailerLegsRatio = 0.0f;
}
m_nVehicleFlags.bLightsOn = false;
m_trailerLegs.SetTargetRatio(trailerLegsRatio);
}
if (m_cargoVehiclesAttached)
{
bool doorOpen = false;
for (s32 i = 0; i < GetNumDoors(); ++i)
{
CCarDoor* pDoor = GetDoor(i);
if(pDoor && pDoor->GetIsIntact(this) && pDoor->GetHierarchyId()!=VEH_BOOT)
{
if (pDoor->GetTargetDoorRatio() > 0.f)
{
doorOpen = true;
break;
}
}
}
// Trailers are using basic attachments so they can't resolve collisions with the ground. Just detach if we
// get into a bad state.
const ScalarV minimumUpAngleCosine(V_HALF); // 60 degrees
bool isTrailerUpsideDown = IsLessThanAll(GetTransform().GetUp().GetZ(),minimumUpAngleCosine) != 0;
if (!IsSuperDummy() && (doorOpen || m_nVehicleFlags.bIsDrowning || isTrailerUpsideDown))
{
DetachCargoVehicles();
}
Vector3 currentVelocity = GetVelocity();
Vector3 currentAngVelocity = GetAngVelocity();
for (int cargoVehicleIndex = 0; cargoVehicleIndex < MAX_CARGO_VEHICLES; ++cargoVehicleIndex)
{
if (CVehicle* pVehicle = m_cargoVehicles[cargoVehicleIndex])
{
if( AssertVerify(pVehicle->m_nVehicleFlags.bIsCargoVehicle) &&
AssertVerify(pVehicle->GetParentTrailer() == this) &&
AssertVerify(!pVehicle->IsRunningCarRecording()) &&
AssertVerify(pVehicle->GetOwnedBy() != ENTITY_OWNEDBY_CUTSCENE))
{
pVehicle->SetInactivePlaybackVelocity(currentVelocity);
pVehicle->SetInactivePlaybackAngVelocity(currentAngVelocity);
pVehicle->CacheAiData();
}
}
}
}
//if( m_bHasExtendingSides &&
// GetStatus() != STATUS_WRECKED )
//{
// if( !m_scriptOverridingExtendableRatio &&
// GetNumContactWheels() )
// {
// TUNE_GROUP_FLOAT( VEHICLE_TRAILER_EXTENDABLE_SIDES, StationaryDuration, 5.0f, 0.0f, 100.0f, 0.1f );
// TUNE_GROUP_FLOAT( VEHICLE_TRAILER_EXTENDABLE_SIDES, VelocityToRetractSidesSqr, 25.0f, 0.0f, 10000.0f, 0.1f );
// float velocityMag = GetVelocity().Mag2();
// if( velocityMag < 1.0f )
// {
// m_stationaryDuration = Min( m_stationaryDuration + fwTimer::GetTimeStep(), StationaryDuration + 1.0f );
// }
// else
// {
// m_stationaryDuration = 0.0f;
// }
// if( m_stationaryDuration > StationaryDuration )
// {
// m_extendingSidesTargetRatio = 1.0f;
// }
// else if( velocityMag > VelocityToRetractSidesSqr )
// {
// m_extendingSidesTargetRatio = 0.0f;
// }
// }
// UpdateExtendingSides( fwTimer::GetTimeStep() );
//}
}
static dev_float sfTrailerZOpposeDetach = -0.35f;
static dev_float sfTrailerZOpposeLoseObjects = 0.1f;
float CTrailer::sm_fTrailerYOpposeDetach = 0.25f;
float CTrailer::sm_fTrailerYOpposeDetachSphericalConstraintInMP = 0.10f;
static dev_float sfTrailerLoseObjectsMinImpulseMagSum = 10.0f;
static dev_float sfTrailerLoseObjectsMaxImpulseMagSum = 60.0f;
static dev_float sfTrailerLoseObjectsSpeedSq = 36.0f;
static dev_float sfTrailerLoseObjectsMinAngSpeedSq = 1.8f;
static dev_float sfTrailerLoseObjectsMaxAngSpeedSq = 6.0f;
float CTrailer::sm_fAttachOffsetHauler = 0.2f;
void CTrailer::StartWheelIntegratorIfNecessary(ePhysicsResult automobilePhysicsResult, float fTimeStep)
{
if(automobilePhysicsResult == PHYSICS_NEED_SECOND_PASS)
{
if(phCollider* pCollider = GetCollider())
{
StartWheelIntegratorTask(pCollider,fTimeStep);
}
}
}
ePhysicsResult CTrailer::ProcessPhysics(float fTimeStep, bool bCanPostpone, int nTimeSlice)
{
const ePhysicsResult nResult = CAutomobile::ProcessPhysics(fTimeStep, bCanPostpone, nTimeSlice);
if( m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodDummy) ||
m_vehicleAiLod.IsLodFlagSet(CVehicleAILod::AL_LodSuperDummy))
{
StartWheelIntegratorIfNecessary(nResult,fTimeStep);
return nResult;
}
m_trailerLegs.ProcessPhysics(this, fTimeStep, nTimeSlice);
phConstraintBase* pPosConstraint = PHCONSTRAINT->GetTemporaryPointer(m_posConstraintHandle);
phConstraintRotation* pRotXConstraint = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) );
phConstraintRotation* pRotYConstraint = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) );
phConstraintRotation* pRotZConstraint = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotZConstraintHandle) );
//if either constraint has failed just disconnect
if( (pPosConstraint && pPosConstraint->IsBroken()) || (pRotXConstraint && pRotXConstraint->IsBroken()) || (pRotYConstraint && pRotYConstraint->IsBroken()) || (pRotZConstraint && pRotZConstraint->IsBroken()) )
{
DetachFromParent(0);
StartWheelIntegratorIfNecessary(nResult,fTimeStep);
return nResult;
}
else if(GetAttachParent())
{
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
ScalarV sUpDotParent = Dot(GetTransform().GetC(), pAttachParent->GetTransform().GetC());
ScalarV sForwardDotParent = Dot(GetTransform().GetB(), pAttachParent->GetTransform().GetB());
bool bIsParentHeli = pAttachParent->GetIsTypeVehicle() && static_cast<CVehicle*>(pAttachParent)->InheritsFromHeli();
float fTrailerYOpposeDetach = sm_fTrailerYOpposeDetach;
if(NetworkInterface::IsGameInProgress())// Add some leeway for reconnecting with a spherical constraint but only for MP so we don't break any missions.
{
if(pPosConstraint && pPosConstraint->GetType() == phConstraintBase::SPHERICAL)
{
fTrailerYOpposeDetach = sm_fTrailerYOpposeDetachSphericalConstraintInMP;
}
}
if(!IsNetworkClone() && !bIsParentHeli && (IsLessThanAll(sUpDotParent, ScalarVFromF32(sfTrailerZOpposeDetach)) ||
(IsLessThanAll(sForwardDotParent, ScalarVFromF32(fTrailerYOpposeDetach)) && GetCollidedWithAttachParent())))
{
DetachFromParent(0);
StartWheelIntegratorIfNecessary(nResult,fTimeStep);
return nResult;
}
else if(m_bHasBreakableExtras)
{
// Make the objects in the back of the trailer fall out
const CCollisionHistory* pCollisionHistory = GetFrameCollisionHistory();
const CCollisionHistory* pParentCollisionHistory = GetAttachParentVehicle() ? GetAttachParentVehicle()->GetFrameCollisionHistory() : NULL;
float fMaxImpulseMag = pCollisionHistory ? pCollisionHistory->GetCollisionImpulseMagSum() : 0.0f;
if(pCollisionHistory && pParentCollisionHistory)
fMaxImpulseMag = Max(pCollisionHistory->GetCollisionImpulseMagSum(), pParentCollisionHistory->GetCollisionImpulseMagSum());
// Trailer tipped over, everything should fall out
float fBreakOffChance = IsLessThanAll(sUpDotParent, ScalarVFromF32(sfTrailerZOpposeLoseObjects)) ? 1.0f : 0.0f;
// Easier to fall out at higher speeds
if(GetAngVelocity().Mag2() > sfTrailerLoseObjectsMinAngSpeedSq)
fBreakOffChance = Max(fBreakOffChance, Clamp(GetAngVelocity().Mag2()/sfTrailerLoseObjectsMaxAngSpeedSq, 0.0f, 0.5f));
float fLinearSpeedMult = Clamp(GetVelocity().Mag2()/sfTrailerLoseObjectsSpeedSq, 0.0f, 2.0f);
if(fMaxImpulseMag * fLinearSpeedMult > sfTrailerLoseObjectsMinImpulseMagSum * GetMass())
fBreakOffChance = Max(fBreakOffChance, Clamp((fMaxImpulseMag * fLinearSpeedMult)/(sfTrailerLoseObjectsMaxImpulseMagSum * GetMass()), 0.0f, 1.0f));
if(fBreakOffChance > 0.0f)
GetVehicleDamage()->BreakOffMiscComponents(0.0f,fBreakOffChance, GetLocalMtx(0).d);
}
}
// Keep the positional constraint up to date
if(pPosConstraint)
{
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
Assertf(pAttachParent,"Unexpected NULL attach parent");
if(pAttachParent)
{
Assert(pAttachParent->GetIsTypeVehicle());
CVehicle* pParentVehicle = static_cast<CVehicle*>(pAttachParent);
// Make sure the truck and trailer don't turn into a dummy whilst they are attached
pParentVehicle->m_nVehicleFlags.bPreventBeingDummyThisFrame = !CVehicleAILodManager::ms_bAllowTrailersToConvertFromReal;
m_nVehicleFlags.bPreventBeingDummyThisFrame = !CVehicleAILodManager::ms_bAllowTrailersToConvertFromReal;
CTrailerHandlingData* pTrailerHandling = pHandling->GetTrailerHandlingData();
if(CPhysics::GetLevel()->IsActive(GetVehicleFragInst()->GetLevelIndex()))
{
// Add an extra spring to try and keep the trailer upright if we have a rotational constraint
Vector3 parentUp = VEC3V_TO_VECTOR3(pParentVehicle->GetTransform().GetC());
Vector3 trailerUp = VEC3V_TO_VECTOR3(GetTransform().GetC());
Vector3 crossParentUpTrailerUp;
if(pTrailerHandling)
{
const float springConst = pTrailerHandling->m_fUprightSpringConstant;
const float dampingConst = pTrailerHandling->m_fUprightDampingConstant;
// Work out the difference in the two vehicles up vectors and use this to create a torque to keep the trailer upright in regards to the truck
crossParentUpTrailerUp.Cross(parentUp, trailerUp);
// Work out the torque to apply with some damping.
Vector3 dampingToApply(VEC3_ZERO);
if(fTimeStep > 0.0f)
{
dampingToApply = ((crossParentUpTrailerUp-m_vLastFramesCrossParentUpTrailerUp) / fTimeStep);
}
Vector3 torqueToApply = ((crossParentUpTrailerUp * springConst) - (dampingToApply * dampingConst)) * GetMass();// scale the torque by mass
// Keep track of the previous frames difference so we can do some damping
m_vLastFramesCrossParentUpTrailerUp = crossParentUpTrailerUp;
ApplyTorque(torqueToApply);
}
}
// Look up attachment point of parent
int iParentAttachIndex = pParentVehicle->GetBoneIndex(VEH_ATTACH);
int iMyAttachIndex = GetBoneIndex(TRAILER_ATTACH);
Assertf(iParentAttachIndex > -1, "Cannot attach to a vehicle with no attach point");
Assertf(iMyAttachIndex > -1, "This trailer has no attach point: %s", GetVehicleModelInfo()->GetModelName());
if(iParentAttachIndex == -1 || iMyAttachIndex == -1)
{
StartWheelIntegratorIfNecessary(nResult,fTimeStep);
return nResult;
}
// Assume A is trailer, B is truck
Matrix34 matAttachA, matAttachB;
GetGlobalMtx(iMyAttachIndex, matAttachA);
pParentVehicle->GetGlobalMtx(iParentAttachIndex, matAttachB);
if(pParentVehicle->GetModelIndex() == MI_CAR_HAULER && NetworkInterface::IsGameInProgress())
{
matAttachB.d += matAttachB.c * sm_fAttachOffsetHauler;
}
Assert(pPosConstraint->GetInstanceA() == GetVehicleFragInst());
Assert(pPosConstraint->GetInstanceB() == pParentVehicle->GetVehicleFragInst());
//Raise up the spherical constraint's Z position if we're colliding with our parent to prevent bouncing
if(pTrailerHandling && pPosConstraint->GetType() == phConstraintBase::SPHERICAL)
{
if(GetCollidedWithAttachParent() && pTrailerHandling->m_fAttachRaiseZ > 0.0f)
{
matAttachA.d.z += pTrailerHandling->m_fAttachRaiseZ;
matAttachB.d.z += pTrailerHandling->m_fAttachRaiseZ;
}
}
pPosConstraint->SetWorldPosA(VECTOR3_TO_VEC3V(matAttachA.d));
pPosConstraint->SetWorldPosB(VECTOR3_TO_VEC3V(matAttachB.d));
SetCollidedWithAttachParent(false);
//Constraint length functionality is not currently used
#if 0
static dev_float sfDistReductionSpeed = 0.2f;
static dev_float sfDistReductionVelThresholdSq = 5.0f * 5.0f;
static dev_float sfLengthZeroThreshold = 0.01f; // Joints act badly with small lengths, make sure they go to 0 eventually
// Only do this at speed
if(GetVelocity().Mag2() > sfDistReductionVelThresholdSq && m_pPosConstraint->GetLength() > sfLengthZeroThreshold)
{
// Pull constraint closer to desired position
Vector3 vSep = matAttachA.d - matAttachB.d;
static dev_float sfReleaseMagSq = 0.5f * 0.5f; // quick way to detect funk stuff happening. just detach for now. Will need to handle teleports properly soon though
if(vSep.Mag2() > sfReleaseMagSq)
{
if (Verifyf(!IsNetworkClone(), "Trying to detach a network clone trailer"))
{
DetachFromParent(0);
}
}
float fLength = vSep.Mag();
fLength = Max(fLength - (sfDistReductionSpeed * fTimeStep), 0.0f);
m_pPosConstraint->SetLength(fLength);
}
#endif
}
}
// The boot component is typically used on the trailers as a rear ramp object
// We'd like to keep it physical, but it tends to bounce up to high when scraping along the ground
// So, we're going to aggressively damp its velocity when rotating upwards
const ScalarV angularDampingTerm(140.0f);
const ScalarV angularDampingSquaredTerm(290.0f);
int rampIndex = GetBoneIndex(VEH_BOOT);
fragInstGta* pTrailerFragInst = GetVehicleFragInst();
if(rampIndex != -1 && pTrailerFragInst != NULL)
{
phArticulatedCollider* pArtCollider = pTrailerFragInst->GetArticulatedCollider();
if(pArtCollider != NULL && pArtCollider->IsArticulated())
{
ScalarV timeStep(fwTimer::GetTimeStep());
const Vec3V trailerMatRight = GetTransform().GetA();
const int fragChildIndex = pTrailerFragInst->GetComponentFromBoneIndex(rampIndex);
const int rampLinkIndex = pArtCollider->GetLinkFromComponent(fragChildIndex);
phArticulatedBody* artBody = pArtCollider->GetBody();
// Have to do this before we make any changes
artBody->EnsureVelocitiesFullyPropagated();
const Vec3V rampAngVel = artBody->GetAngularVelocity(rampLinkIndex);
const ScalarV angVelMagAlongAxis = Dot(rampAngVel, trailerMatRight);
// Apply only when rotating up
if( IsLessThanAll(angVelMagAlongAxis, ScalarV(V_ZERO)) != 0 )
{
// Linear
const ScalarV dampedAngMagAlongAxis = Scale(angVelMagAlongAxis, angularDampingTerm);
// Squared
const ScalarV sqrdAngVelMagAlongAxis = Scale(angVelMagAlongAxis, Abs(angVelMagAlongAxis));
const ScalarV sqrdDampedAngMagAlongAxis = Scale(sqrdAngVelMagAlongAxis, angularDampingSquaredTerm);
// Total velocity change
const ScalarV angMagDelta = Scale( rage::Add(dampedAngMagAlongAxis, sqrdDampedAngMagAlongAxis), timeStep );
const ScalarV angMagDeltaClamped = SelectFT( IsLessThan(Abs(angMagDelta), Abs(angVelMagAlongAxis)), angVelMagAlongAxis, angMagDelta );
const Vec3V dampedAngVelFinal = Subtract(rampAngVel, Scale(trailerMatRight, angMagDeltaClamped));
// Do it
artBody->SetAngularVelocity(rampLinkIndex, dampedAngVelFinal);
// And this has to happen after our changes
artBody->ResetPropagationVelocities();
}
}
}
StartWheelIntegratorIfNecessary(nResult,fTimeStep);
return nResult;
}
void CTrailer::InitCompositeBound()
{
CAutomobile::InitCompositeBound();
// Turn on collision with the trailer legs
int iLegsBoneIndex = GetBoneIndex(TRAILER_LEGS);
if(iLegsBoneIndex > -1)
{
Assert(GetFragInst());
Assert(GetFragInst()->GetType());
int iCompositeBoundIndex = GetFragInst()->GetComponentFromBoneIndex(iLegsBoneIndex);
if(iCompositeBoundIndex > -1)
{
Assert(GetFragInst()->GetArchetype()->GetBound()->GetType() == phBound::COMPOSITE);
phBoundComposite* pCompBound = static_cast<phBoundComposite*>(GetFragInst()->GetArchetype()->GetBound());
pCompBound->SetIncludeFlags(iCompositeBoundIndex, ArchetypeFlags::GTA_VEHICLE_INCLUDE_TYPES);
}
}
}
int CTrailer::InitPhys()
{
CAutomobile::InitPhys();
int iLegsIndex = GetBoneIndex(TRAILER_LEGS);
//vehicleAssertf(iLegsIndex > -1,"Vehilce %s is missing trailer leg collision", GetVehicleModelInfo()->GetModelName()); //some trailer will have no legs, so no need for assert
if(iLegsIndex > -1)
{
m_trailerLegs.Init(iLegsIndex, this);
}
for(int nExtra=VEH_BREAKABLE_EXTRA_1; nExtra <= VEH_LAST_BREAKABLE_EXTRA; nExtra++)
{
if(GetBoneIndex((eHierarchyId)nExtra) > -1)
{
m_bHasBreakableExtras = true;
break;
}
}
//for( int nExtra = VEH_EXTENDABLE_SIDE_L; nExtra <= VEH_EXTENDABLE_SIDE_R; nExtra++ )
//{
// if( GetBoneIndex( (eHierarchyId)nExtra ) > -1 )
// {
// m_bHasExtendingSides = true;
// break;
// }
//}
if (MI_TRAILER_TRAILERLARGE.IsValid() && GetModelIndex() == MI_TRAILER_TRAILERLARGE)
{
m_nVehicleFlags.bDisableSuperDummy = true;
}
return INIT_OK;
}
void EnforceConstraintLimits(phConstraintRotation* pConstraint, float fMin, float fMax, bool bIsPitch)
{
if(bIsPitch)
{
fMin = fwAngle::LimitRadianAngleForPitch(fMin);
fMax = fwAngle::LimitRadianAngleForPitch(fMax);
}
else
{
fMin = fwAngle::LimitRadianAngle(fMin);
fMax = fwAngle::LimitRadianAngle(fMax);
}
if(fMin > fMax)
{
SwapEm(fMin, fMax);
}
pConstraint->SetMaxLimit(fMax);
pConstraint->SetMinLimit(fMin);
}
void EnforceConstraintLimits(phConstraintRotation* pConstraint, float fMin, float fMax, float fCurrent, bool bIsPitch)
{
fMin -= fCurrent;
fMax -= fCurrent;
EnforceConstraintLimits(pConstraint, fMin, fMax, bIsPitch);
}
static dev_float sfConstraintStrengthPosition = 5500000.0f;
static dev_float sfConstraintStrengthRotation = 5000000.0f;
#if __BANK
bool CTrailer::DebugAttachToParentVehicle(const char *strCodeFile, const char* strCodeFunction, u32 nCodeLine, CVehicle* pParent, bool bWarp, float fInvMass, bool bForceReAttach)
#else
bool CTrailer::AttachToParentVehicle(CVehicle* pParent, bool bWarp, float fInvMass, bool bForceReAttach)
#endif
{
Assert(pParent);
Assert(!IsDummy());
Assert(!pParent->IsDummy());
TUNE_GROUP_BOOL(TRAILER_HACKS, DISABLE_TRAILER_ATTACHMENT, false);
// Script are blocking this trailer from being attached to vehicles
if( !m_bIsTrailerAttachmentEnabled || DISABLE_TRAILER_ATTACHMENT )
{
return false;
}
// use CPhysical attachment system
// Attach at tail position
int iThisBoneIndex = GetBoneIndex(TRAILER_ATTACH);
int iOtherBoneIndex = pParent->GetBoneIndex(VEH_ATTACH);
// Assert that it is possible to attach this vehicle to other vehicle
Assertf(iThisBoneIndex > -1, "This trailer has no attach point: %s", GetVehicleModelInfo()->GetModelName());
Assertf(iOtherBoneIndex > -1, "Cannot attach to a vehicle with no attach point, %s", pParent->GetVehicleModelInfo()->GetModelName());
if(iThisBoneIndex == -1 || iOtherBoneIndex == -1)
{
return false;
}
if(!bForceReAttach)
{
//make sure we're not already attached to this trailer, could happen if someone used the script command to attach.
CPhysical* pNextChild = (CPhysical *) pParent->GetChildAttachment();
while(pNextChild)
{
if(pNextChild == this)
{
return true;//already attached to this trailer.
}
else
pNextChild = (CPhysical *) pNextChild->GetSiblingAttachment();
}
}
if(GetIsAttached())
{
DetachFromParent(0);
}
if( pParent )
{
for(int i = 0; i < GetNumberOfVehicleGadgets(); i++)
{
CVehicleGadget *pVehicleGadget = GetVehicleGadget(i);
if( pVehicleGadget->GetType() == VGT_TRAILER_ATTACH_POINT )
{
CVehicleTrailerAttachPoint *pTrailerAttachPoint = static_cast<CVehicleTrailerAttachPoint*>(pVehicleGadget);
CTrailer *pTrailer = pTrailerAttachPoint->GetAttachedTrailer( pParent );
if(pTrailer)
{
pTrailerAttachPoint->DetachTrailer( pParent );
}
}
}
CPhysical* pNextChild = (CPhysical*) pParent->GetChildAttachment();
while( pNextChild )
{
if( pNextChild->GetType() == ENTITY_TYPE_VEHICLE )
{
CVehicle* pChildVehicle = static_cast< CVehicle* >( pNextChild );
if( pChildVehicle->InheritsFromTrailer() )
{
CTrailer* pChildTrailer = static_cast< CTrailer* >( pChildVehicle );
pChildTrailer->DetachFromParent( 0 );
}
}
pNextChild = (CPhysical *)pNextChild->GetSiblingAttachment();
}
}
Matrix34 matMyBone;
GetGlobalMtx(iThisBoneIndex, matMyBone);
Matrix34 matOtherBone;
pParent->GetGlobalMtx(iOtherBoneIndex, matOtherBone);
if(pParent->GetModelIndex() == MI_CAR_HAULER && NetworkInterface::IsGameInProgress())
{
matOtherBone.d += matOtherBone.c * sm_fAttachOffsetHauler;
}
// Check if we should warp
bool warpPositionOnly = false;
if(!bWarp)
{
Vector3 vSep = matMyBone.d - matOtherBone.d;
const float arbitraryAttachDistanceScale = 1.1f;
const float warpRadius = CVehicleTrailerAttachPoint::ms_fAttachRadius * arbitraryAttachDistanceScale;
if(vSep.Mag2() > warpRadius * warpRadius)
{
bWarp = true;
warpPositionOnly = true;
}
}
static dev_bool sbDoPairedColl = true;
u32 uAttachFlags = ATTACH_STATE_PHYSICAL | ATTACH_FLAG_POS_CONSTRAINT | ATTACH_FLAG_ROT_CONSTRAINT | ATTACH_FLAG_DELETE_WITH_PARENT;
if(sbDoPairedColl)
{
uAttachFlags |= ATTACH_FLAG_DO_PAIRED_COLL;
}
if(bWarp)
{
uAttachFlags |= ATTACH_FLAG_INITIAL_WARP;
WarpToPosition( pParent, warpPositionOnly );
}
if(IsDummy() && !CVehicleAILodManager::ms_bAllowTrailersToConvertFromReal)
{
TryToMakeFromDummy();
Assert(!IsDummy());
}
if(pParent->IsDummy() && !CVehicleAILodManager::ms_bAllowTrailersToConvertFromReal)
{
pParent->TryToMakeFromDummy();
Assert(!pParent->IsDummy());
}
//get the component that corresponds to the bone, or just use 0
int nEntComponent = pParent->GetVehicleFragInst()->GetControllingComponentFromBoneIndex(iOtherBoneIndex);
if(nEntComponent < 0)
nEntComponent = 0;
int nMyComponent = GetVehicleFragInst()->GetControllingComponentFromBoneIndex(iThisBoneIndex);
if(nMyComponent < 0)
nMyComponent = 0;
// Make sure we are allowed to activate
if(GetCurrentPhysicsInst())
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_NEVER_ACTIVATE, false);
ActivatePhysics();
// Get matrix of trailer in cab space
// This might need to be the other way round!
//Matrix34 matTrailerLocal = MAT34V_TO_MATRIX34(GetMatrix());
//float fCurrentPitch = GetMatrix().c.AngleX(pParent->GetMatrix().c);
//float fCurrentHeading = GetMatrix().b.AngleZ(pParent->GetMatrix().b);
// Set up 4x constraints
// Assert that we aren't about to leak constraints
Assert( !m_posConstraintHandle.IsValid() );
Assert( !m_rotXConstraintHandle.IsValid() );
Assert( !m_rotYConstraintHandle.IsValid() );
Assert( !m_rotZConstraintHandle.IsValid() );
phConstraintBase* pPosConstraint = NULL;
// Now set up rotation constraints
phConstraintRotation* pRotConstraintX = NULL;
phConstraintRotation* pRotConstraintY = NULL;
phConstraintRotation* pRotConstraintZ = NULL;
CTrailerHandlingData* pTrailerHandling = pHandling->GetTrailerHandlingData();
const float posConstraintMassRatio = pTrailerHandling ? pTrailerHandling->m_fPosConstraintMassRatio : 1.0f;
const float rotConstraintMassRatio = posConstraintMassRatio;
if( pTrailerHandling &&
MI_TRAILER_TRAILERLARGE.IsValid() &&
GetModelIndex() == MI_TRAILER_TRAILERLARGE )
{
if( pTrailerHandling->m_fAttachLimitPitch >= 0.0f )
{
const float sfPitchLimit = DtoR * ( pTrailerHandling->m_fAttachLimitPitch );
float fCurrentPitch = GetTransform().GetPitch() - pParent->GetTransform().GetPitch();
fCurrentPitch = fwAngle::LimitRadianAngleForPitch( fCurrentPitch );
if( Abs( fCurrentPitch ) > sfPitchLimit )
{
return false;
}
}
if( pTrailerHandling->m_fAttachLimitRoll >= 0.0f )
{
const float sfRollLimit = DtoR * ( pTrailerHandling->m_fAttachLimitRoll );
float fCurrentRoll = pParent->GetTransform().GetRoll() - GetTransform().GetRoll();
fCurrentRoll = fwAngle::LimitRadianAngle( fCurrentRoll );
if( Abs( fCurrentRoll ) > sfRollLimit )
{
return false;
}
}
if(pTrailerHandling->m_fAttachLimitYaw >= 0.0f)
{
const float sfYawLimit = DtoR * ( pTrailerHandling->m_fAttachLimitYaw );
float fCurrentYaw = GetTransform().GetHeading() - pParent->GetTransform().GetHeading();
fCurrentYaw = fwAngle::LimitRadianAngle(fCurrentYaw);
if( Abs( fCurrentYaw ) > sfYawLimit )
{
return false;
}
}
}
//Don't add distance constraints if we're doing a playback
if(!GetIntelligence() || GetIntelligence()->GetRecordingNumber() < 0 || CVehicleRecordingMgr::GetUseCarAI(GetIntelligence()->GetRecordingNumber()))
{
if(pTrailerHandling && pTrailerHandling->m_fAttachedMaxDistance > 0.0f)
{
// Distance constraint is available
// When used with a small amount of allowed penetration the connection may be more tolerant
// of bad configurations or insufficient solver iterations
const float distConstraintMin = 0.0f;
const float distConstraintMax = pTrailerHandling->m_fAttachedMaxDistance;
const float distConstraintAllowedPen = pTrailerHandling->m_fAttachedMaxPenetration;
//
phConstraintDistance::Params posConstraintParams;
posConstraintParams.worldAnchorA = VECTOR3_TO_VEC3V(matOtherBone.d);
posConstraintParams.worldAnchorB = VECTOR3_TO_VEC3V(matOtherBone.d);
posConstraintParams.instanceA = GetVehicleFragInst(); // If this order is changed than the massInvScale setting also needs to be swapped
posConstraintParams.instanceB = pParent->GetVehicleFragInst();
Assign(posConstraintParams.componentA, nMyComponent);
Assign(posConstraintParams.componentB, nEntComponent);
posConstraintParams.minDistance = distConstraintMin;
posConstraintParams.maxDistance = distConstraintMax;
posConstraintParams.allowedPenetration = distConstraintAllowedPen;
posConstraintParams.massInvScaleA = posConstraintMassRatio;
posConstraintParams.softAttach = true;
m_posConstraintHandle = PHCONSTRAINT->Insert(posConstraintParams);
Assertf(m_posConstraintHandle.IsValid(), "Failed to create trailer constraint");
phConstraintDistance* pDistConstraint = static_cast<phConstraintDistance*>( PHCONSTRAINT->GetTemporaryPointer(m_posConstraintHandle) );
Assertf(pDistConstraint, "Trailer constraint pointer NULL from phConstraintMgr");
pPosConstraint = pDistConstraint;
}
else
{
// Alternatively the old method of a spherical constraint is still possible
// This seems to work better on the large trailers such as for a traditional 18-wheeler
// Spherical constraints avoid the downside of the distance constraint which is that they can overshoot
// and possibly oscillate around the target position
phConstraintSpherical::Params posConstraintParams;
posConstraintParams.worldPosition = VECTOR3_TO_VEC3V(matOtherBone.d);
posConstraintParams.instanceA = GetVehicleFragInst(); // If this order is changed than massInvScaleB also needs to be swapped
posConstraintParams.instanceB = pParent->GetVehicleFragInst();
Assign(posConstraintParams.componentA, nMyComponent);
Assign(posConstraintParams.componentB, nEntComponent);
posConstraintParams.massInvScaleA = posConstraintMassRatio;
posConstraintParams.softAttach = true;
m_posConstraintHandle = PHCONSTRAINT->Insert(posConstraintParams);
Assertf(m_posConstraintHandle.IsValid(), "Failed to create trailer constraint");
phConstraintSpherical* pSphConstraint = static_cast<phConstraintSpherical*>( PHCONSTRAINT->GetTemporaryPointer(m_posConstraintHandle) );
Assertf(pSphConstraint, "Trailer constraint pointer NULL from phConstraintMgr");
pPosConstraint = pSphConstraint;
}
// Rotation constraints only if handling data specifies
if(pTrailerHandling)
{
if(pTrailerHandling->m_fAttachLimitPitch >= 0.0f)
{
const float sfPitchLimit = DtoR*(pTrailerHandling->m_fAttachLimitPitch);
float fCurrentPitch = GetTransform().GetPitch() - pParent->GetTransform().GetPitch();
phConstraintRotation::Params rotXConstraintParams;
rotXConstraintParams.instanceA = GetVehicleFragInst();
rotXConstraintParams.instanceB = pParent->GetVehicleFragInst();
Assign(rotXConstraintParams.componentA, nMyComponent);
Assign(rotXConstraintParams.componentB, nEntComponent);
rotXConstraintParams.worldAxis = pParent->GetTransform().GetA();
rotXConstraintParams.massInvScaleA = rotConstraintMassRatio;
m_rotXConstraintHandle = PHCONSTRAINT->Insert(rotXConstraintParams);
pRotConstraintX = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer( m_rotXConstraintHandle ) );
if(vehicleVerifyf(pRotConstraintX, "Failed to create trailer constraint"))
{
EnforceConstraintLimits(pRotConstraintX, -sfPitchLimit, sfPitchLimit, fCurrentPitch, true);
}
}
//
if(pTrailerHandling->m_fAttachLimitRoll >= 0.0f)
{
const float sfRollLimit = DtoR*(pTrailerHandling->m_fAttachLimitRoll);
float fCurrentRoll = pParent->GetTransform().GetRoll() - GetTransform().GetRoll();
phConstraintRotation::Params rotYConstraintParams;
rotYConstraintParams.instanceA = GetVehicleFragInst();
rotYConstraintParams.instanceB = pParent->GetVehicleFragInst();
Assign(rotYConstraintParams.componentA, nMyComponent);
Assign(rotYConstraintParams.componentB, nEntComponent);
rotYConstraintParams.worldAxis = pParent->GetTransform().GetB();
rotYConstraintParams.massInvScaleA = rotConstraintMassRatio;
m_rotYConstraintHandle = PHCONSTRAINT->Insert(rotYConstraintParams);
pRotConstraintY = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer( m_rotYConstraintHandle ) );
if(vehicleVerifyf(pRotConstraintY, "Failed to create trailer constraint"))
{
EnforceConstraintLimits(pRotConstraintY, -sfRollLimit, sfRollLimit, fCurrentRoll, false);
}
}
//I don't think this constraint is strictly necessary, as the trailer can collide with the cab
if(pTrailerHandling->m_fAttachLimitYaw >= 0.0f)
{
const float sfYawLimit = DtoR*(pTrailerHandling->m_fAttachLimitYaw);
float fCurrentYaw = GetTransform().GetHeading() - pParent->GetTransform().GetHeading();
phConstraintRotation::Params rotZConstraintParams;
rotZConstraintParams.instanceA = GetVehicleFragInst();
rotZConstraintParams.instanceB = pParent->GetVehicleFragInst();
Assign(rotZConstraintParams.componentA, nMyComponent);
Assign(rotZConstraintParams.componentB, nEntComponent);
rotZConstraintParams.worldAxis = pParent->GetTransform().GetC();
rotZConstraintParams.massInvScaleA = rotConstraintMassRatio;
m_rotZConstraintHandle = PHCONSTRAINT->Insert(rotZConstraintParams);
pRotConstraintZ = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer( m_rotZConstraintHandle ) );
if(vehicleVerifyf(pRotConstraintZ, "Failed to create trailer constraint"))
{
EnforceConstraintLimits(pRotConstraintZ, -sfYawLimit, sfYawLimit, fCurrentYaw, false);
}
}
}
}
// network clones cannot detach unless told to via an update. The attachment state must stay synced with the master trailer.
if (!IsNetworkClone())
{
#if __BANK
static dev_float fForceInvMass = 0.0f;
if(fForceInvMass > 0.0f)
{
fInvMass = fForceInvMass;
}
#endif
if( pPosConstraint )
{
pPosConstraint->SetBreakable(true,sfConstraintStrengthPosition);
pPosConstraint->SetMassInvScaleB(fInvMass);
}
if( pRotConstraintX )
{
pRotConstraintX->SetBreakable(true,sfConstraintStrengthRotation);
pRotConstraintX->SetMassInvScaleB(fInvMass);
}
if( pRotConstraintY )
{
pRotConstraintY->SetBreakable(true,sfConstraintStrengthRotation);
pRotConstraintY->SetMassInvScaleB(fInvMass);
}
if( pRotConstraintZ )
{
pRotConstraintZ->SetBreakable(true,sfConstraintStrengthRotation);
pRotConstraintZ->SetMassInvScaleB(fInvMass);
}
}
fwAttachmentEntityExtension *extension = CreateAttachmentExtensionIfMissing();
extension->SetParentAttachment(this, pParent);
extension->SetAttachFlags(uAttachFlags);
#if __BANK
extension->DebugSetInvocationData(strCodeFile, strCodeFunction, nCodeLine);
#endif
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_OPTIONAL_ITERATIONS, true);
pParent->GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_OPTIONAL_ITERATIONS, true);
//make sure the truck and trailer don't turn into a dummy
pParent->m_nVehicleFlags.bPreventBeingDummyThisFrame = !CVehicleAILodManager::ms_bAllowTrailersToConvertFromReal;
m_nVehicleFlags.bPreventBeingDummyThisFrame = !CVehicleAILodManager::ms_bAllowTrailersToConvertFromReal;
m_isAttachedToParent = true;
// Update the trucks vehicle gadget so it knows its attached to this trailer.
// Could make it so people have to call the vehicle gadget to connect up but that might be confusing
for(s32 i = 0; i < pParent->GetNumberOfVehicleGadgets(); i++)
{
CVehicleGadget *pVehicleGadget = pParent->GetVehicleGadget(i);
if(pVehicleGadget->GetType() == VGT_TRAILER_ATTACH_POINT)
{
CVehicleTrailerAttachPoint *pTrailerAttachPoint = static_cast<CVehicleTrailerAttachPoint*>(pVehicleGadget);
pTrailerAttachPoint->SetAttachedTrailer(this);
}
}
m_trailerLegs.SetTargetRatio(0.0f);
m_AttachedParent = pParent;
SetVelocity( pParent->GetVelocity() );
SetAngVelocity( pParent->GetAngVelocity() );
return true;
}
void CTrailer::SetTrailerConstraintIndestructible(bool indestructibleTrailerConstraint)
{
phConstraintBase* pPosConstraint = PHCONSTRAINT->GetTemporaryPointer(m_posConstraintHandle);
phConstraintRotation* pRotConstraintX = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) );
phConstraintRotation* pRotConstraintY = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) );
phConstraintRotation* pRotConstraintZ = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotZConstraintHandle) );
if(pPosConstraint && pRotConstraintY)
{
if(indestructibleTrailerConstraint)
{
pPosConstraint->SetBreakable(false);
pRotConstraintX->SetBreakable(false);
pRotConstraintY->SetBreakable(false);
pRotConstraintZ->SetBreakable(false);
}
else if (Verifyf(!IsNetworkClone(), "Trying to set destructable trailer constraints on a network clone"))
{
pPosConstraint->SetBreakable(true, sfConstraintStrengthPosition);
pRotConstraintX->SetBreakable(true, sfConstraintStrengthRotation);
pRotConstraintY->SetBreakable(true, sfConstraintStrengthRotation);
pRotConstraintZ->SetBreakable(true, sfConstraintStrengthRotation);
}
}
}
bool CTrailer::IsTrailerConstraintIndestructible() const
{
phConstraintBase* pPosConstraint = PHCONSTRAINT->GetTemporaryPointer(m_posConstraintHandle);
phConstraintRotation* pRotConstraintX = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) );
phConstraintRotation* pRotConstraintY = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) );
phConstraintRotation* pRotConstraintZ = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotZConstraintHandle) );
bool breakable = (pPosConstraint && pPosConstraint->IsBreakable()) ||
(pRotConstraintX && pRotConstraintX->IsBreakable()) ||
(pRotConstraintY && pRotConstraintY->IsBreakable()) ||
(pRotConstraintZ && pRotConstraintZ->IsBreakable());
return( !breakable );
}
void CTrailer::WarpToPosition( CVehicle* pParent, bool translateOnly )
{
const int iThisBoneIndex = GetBoneIndex(TRAILER_ATTACH);
const int iOtherBoneIndex = pParent->GetBoneIndex(VEH_ATTACH);
// Assert that it is possible to attach this vehicle to other vehicle
Assertf(iThisBoneIndex > -1, "This trailer has no attach point: %s", GetVehicleModelInfo()->GetModelName());
Assertf(iOtherBoneIndex > -1, "Cannot attach to a vehicle with no attach point");
if(iThisBoneIndex == -1 || iOtherBoneIndex == -1)
{
return;
}
const Mat34V trailerMtxCur = GetTransform().GetMatrix();
Mat34V trailerMtxNew;
#if ENABLE_FRAG_OPTIMIZATION
Mat34V trailerAttachBoneToInstMtx;
ComputeObjectMtx(iThisBoneIndex, trailerAttachBoneToInstMtx);
#else
Assert(GetSkeleton());
Mat34V trailerAttachBoneToInstMtx = GetSkeleton()->GetObjectMtx(iThisBoneIndex);
#endif
Mat34V cabAttachBoneToWorldMtx(V_IDENTITY);
// What to do if global matrices aren't up to date?
pParent->GetGlobalMtx(iOtherBoneIndex, RC_MATRIX34(cabAttachBoneToWorldMtx));
if(pParent->GetModelIndex() == MI_CAR_HAULER && NetworkInterface::IsGameInProgress())
{
cabAttachBoneToWorldMtx.SetCol3( rage::Add(cabAttachBoneToWorldMtx.GetCol3(), rage::Scale(cabAttachBoneToWorldMtx.GetCol2(), ScalarV(sm_fAttachOffsetHauler)) ));
}
if(translateOnly)
{
Mat34V trailerAttachBoneToWorldMtx;
Transform(trailerAttachBoneToWorldMtx, trailerMtxCur, trailerAttachBoneToInstMtx);
const Vec3V offsetToParentBone = Subtract(cabAttachBoneToWorldMtx.GetCol3(), trailerAttachBoneToWorldMtx.GetCol3());
trailerMtxNew = trailerMtxCur;
trailerMtxNew.SetCol3( rage::Add(trailerMtxNew.GetCol3(), offsetToParentBone) );
}
else
{
// We want to set the trailer matrix such that the world space of the attach bone is equal to the cab's attach bone world matrix
// - Could be written as matrices: [(trailer)(trailerBone)] = [(cab)(cabBone)]
// -- Then we right multiply both by (trailerBone)^-1 and notice that we already have the cabBone in world space
// -- Resulting in us setting (trailer) = (cabBoneInWorldSpace)(trailerBone)^-1
Mat34V parentAttachBoneToInstMtx = pParent->GetSkeleton()->GetObjectMtx( iOtherBoneIndex );
// some of the assets have been created with the attachment bone the wrong way around
if( parentAttachBoneToInstMtx.a().GetXf() * trailerAttachBoneToInstMtx.a().GetXf() < 0.0f )
{
trailerAttachBoneToInstMtx.GetCol0Ref() = trailerAttachBoneToInstMtx.GetCol0Ref() * ScalarV( -1.0f );
trailerAttachBoneToInstMtx.GetCol1Ref() = trailerAttachBoneToInstMtx.GetCol1Ref() * ScalarV( -1.0f );
}
Mat34V trailerAttachBoneToInstMtxInverse;
InvertTransformOrtho(trailerAttachBoneToInstMtxInverse, trailerAttachBoneToInstMtx);
Transform(trailerMtxNew, cabAttachBoneToWorldMtx, trailerAttachBoneToInstMtxInverse);
}
//
SetMatrix(MAT34V_TO_MATRIX34(trailerMtxNew),true,true,true);
const Mat34V trailerMtxPrev = trailerMtxCur;
// Matrix that we can use to calculate the full motion of an attachment's center given the change we've just made to the trailer
Mat34V positionOffsetMtx;
Subtract(positionOffsetMtx, trailerMtxNew, trailerMtxPrev);
Mat34V rotMat;
UnTransform3x3Ortho(rotMat, trailerMtxPrev, trailerMtxNew);
rotMat.SetCol3(Vec3V(V_ZERO));
UpdateInertiasAfterTeleport();
// We also need to move any physical attachments along with the trailer, we're going to
// detach and reattach them so we need to cache them in an array so we don't change the
// attachments tree while traversing it
if(GetChildAttachment())
{
atFixedArray<CPhysical *, MAX_ATTACHMENTS> attachedObjects;
GetPhysicallyAttachedObjects((CPhysical*)GetChildAttachment(), attachedObjects);
const int numAttachedObjects = attachedObjects.GetCount();
for(unsigned index = 0; index < numAttachedObjects; index++)
{
CPhysical *attachedObject = attachedObjects[index];
if(attachedObject)
{
Mat34V newMat = attachedObject->GetTransform().GetMatrix();
const Vec3V posOffset = Transform(positionOffsetMtx, Subtract(newMat.GetCol3(), trailerMtxPrev.GetCol3()));
Transform3x3(newMat, rotMat, newMat);
newMat.SetCol3( rage::Add(newMat.GetCol3(), posOffset) );
attachedObject->SetMatrix(MAT34V_TO_MATRIX34(newMat), true, true, true);
}
}
}
}
void CTrailer::WarpParentToPosition()
{
if( m_AttachedParent )
{
const int iThisBoneIndex = GetBoneIndex(TRAILER_ATTACH);
const int iOtherBoneIndex = m_AttachedParent->GetBoneIndex(VEH_ATTACH);
Mat34V parentMtxNew( V_IDENTITY );
Mat34V trailerAttachBoneToInstMtx = GetSkeleton()->GetObjectMtx( iThisBoneIndex );
Mat34V trailerAttachBoneToWorldMtx( V_IDENTITY );
// What to do if global matrices aren't up to date?
GetGlobalMtx( iThisBoneIndex, RC_MATRIX34( trailerAttachBoneToWorldMtx ) );
m_AttachedParent->GetGlobalMtx( iOtherBoneIndex, RC_MATRIX34( parentMtxNew ) );
// We want to set the trailer matrix such that the world space of the attach bone is equal to the cab's attach bone world matrix
// - Could be written as matrices: [(trailer)(trailerBone)] = [(cab)(cabBone)]
// -- Then we right multiply both by (trailerBone)^-1 and notice that we already have the cabBone in world space
// -- Resulting in us setting (trailer) = (cabBoneInWorldSpace)(trailerBone)^-1
Mat34V parentAttachBoneToInstMtx = m_AttachedParent->GetSkeleton()->GetObjectMtx( iOtherBoneIndex );
// some of the assets have been created with the attachment bone the wrong way around
if( parentAttachBoneToInstMtx.a().GetXf() * trailerAttachBoneToInstMtx.a().GetXf() < 0.0f )
{
trailerAttachBoneToInstMtx.GetCol0Ref() = trailerAttachBoneToInstMtx.GetCol0Ref() * ScalarV( -1.0f );
trailerAttachBoneToInstMtx.GetCol1Ref() = trailerAttachBoneToInstMtx.GetCol1Ref() * ScalarV( -1.0f );
}
Vector3 vSep = VEC3V_TO_VECTOR3( trailerAttachBoneToWorldMtx.d() - parentMtxNew.d() );
const float arbitraryAttachDistanceScale = 8.0f;
const float warpRadius = Max( 2.0f, CVehicleTrailerAttachPoint::ms_fAttachRadius * arbitraryAttachDistanceScale );
if(vSep.Mag2() < warpRadius * warpRadius)
{
return;
}
//Mat34V parentAttachBoneToInstMtxInverse;
//InvertTransformOrtho( parentAttachBoneToInstMtxInverse, parentAttachBoneToInstMtx );
//Transform( parentMtxNew, trailerAttachBoneToWorldMtx, parentAttachBoneToInstMtxInverse );
Transform( parentMtxNew, trailerAttachBoneToWorldMtx, parentAttachBoneToInstMtx );
//
m_AttachedParent->SetMatrix( MAT34V_TO_MATRIX34( parentMtxNew ), true, true, true );
Vector3 vecSetCoords = VEC3V_TO_VECTOR3( parentMtxNew.d() );
m_AttachedParent->TeleportWithoutUpdateGadgets( vecSetCoords, camFrame::ComputeHeadingFromMatrix( MAT34V_TO_MATRIX34( parentMtxNew ) ), true, true );
m_AttachedParent->UpdateGadgetsAfterTeleport( MAT34V_TO_MATRIX34( parentMtxNew ), false, true, false );
m_AttachedParent->UpdateInertiasAfterTeleport();
}
}
void CTrailer::GetPhysicallyAttachedObjects(CPhysical *attachedObj, atFixedArray<CPhysical *, MAX_ATTACHMENTS> &attachmentList)
{
if(attachedObj)
{
fwAttachmentEntityExtension *attachExt = attachedObj->GetAttachmentExtension();
Assertf(attachExt, "Attached child doesn't have an attachment extension?");
if(attachExt)
{
if(attachExt->GetAttachState() == ATTACH_STATE_PHYSICAL)
{
attachmentList.Push(attachedObj);
}
if(attachedObj->GetSiblingAttachment())
{
GetPhysicallyAttachedObjects((CPhysical*)attachedObj->GetSiblingAttachment(), attachmentList);
}
if(attachedObj->GetChildAttachment())
{
GetPhysicallyAttachedObjects((CPhysical*)attachedObj->GetChildAttachment(), attachmentList);
}
}
}
}
void CTrailer::AddCargoVehicle(u32 index, CVehicle* veh)
{
if (Verifyf(index < MAX_CARGO_VEHICLES, "Invalid cargo vehicle index"))
{
Assertf(m_cargoVehicles[index] == NULL, "Vehicle alredy attached as cargo at index %d", index);
m_cargoVehicles[index] = veh;
m_cargoVehiclesAttached = true;
if (veh)
{
veh->SetIsCargoVehicle(true);
veh->m_nVehicleFlags.bCanMakeIntoDummyVehicle = true;
}
}
}
int CTrailer::FindCargoVehicle(CVehicle* veh) const
{
if (Verifyf(veh, "Invalid cargo vehicle pointer"))
{
for (s32 i = 0; i < MAX_CARGO_VEHICLES; ++i)
{
if (m_cargoVehicles[i] == veh)
{
return i;
}
}
}
return -1;
}
u32 CTrailer::RemoveCargoVehicle(CVehicle* veh)
{
if (Verifyf(veh, "Invalid cargo vehicle pointer"))
{
for (s32 i = 0; i < MAX_CARGO_VEHICLES; ++i)
{
if (m_cargoVehicles[i] == veh)
{
m_cargoVehicles[i] = NULL;
if (veh)
{
veh->SetIsCargoVehicle(false);
if(veh->InheritsFromBoat())
{
veh->m_nVehicleFlags.bCanMakeIntoDummyVehicle = false;
}
}
return i;
}
}
}
return (u32)-1;
}
void CTrailer::DetachCargoVehicles()
{
for (s32 i = 0; i < MAX_CARGO_VEHICLES; ++i)
{
if (m_cargoVehicles[i])
{
Assertf(IsNetworkClone() || m_cargoVehicles[i]->GetIsPhysicalAParentAttachment(this), "Cargo vehicle at index %d isn't attached to current trailer", i);
m_cargoVehicles[i]->DetachFromParent(/*DETACH_FLAG_NO_COLLISION_UNTIL_CLEAR | */DETACH_FLAG_APPLY_VELOCITY | DETACH_FLAG_ACTIVATE_PHYSICS);
m_cargoVehicles[i]->SetParentTrailer(NULL);
m_cargoVehicles[i]->SetIsCargoVehicle(false);
if(m_cargoVehicles[i]->InheritsFromBoat())
{
m_cargoVehicles[i]->m_nVehicleFlags.bCanMakeIntoDummyVehicle = false;
CVehicleFactory::GetFactory()->DetachedBoatOnTrailer();
}
m_cargoVehicles[i] = NULL;
}
}
m_cargoVehiclesAttached = false;
}
void CTrailer::DetachFromParent(u16 nDetachFlags)
{
#if __ASSERT
// Make sure we aren't going to leak constraints
// If correct attach flags are set then CPhysical will clean us up
Assert( !m_posConstraintHandle.IsValid() || GetAttachFlags() & ATTACH_FLAG_POS_CONSTRAINT);
Assert( !m_rotXConstraintHandle.IsValid() || GetAttachFlags() & ATTACH_FLAG_ROT_CONSTRAINT);
Assert( !m_rotYConstraintHandle.IsValid() || GetAttachFlags() & ATTACH_FLAG_ROT_CONSTRAINT);
Assert( !m_rotZConstraintHandle.IsValid() || GetAttachFlags() & ATTACH_FLAG_ROT_CONSTRAINT);
#endif
if(GetCurrentPhysicsInst())
{
GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_OPTIONAL_ITERATIONS, false);
}
if(GetAttachParent() && GetAttachParent()->GetCurrentPhysicsInst())
{
GetAttachParent()->GetCurrentPhysicsInst()->SetInstFlag(phInst::FLAG_OPTIONAL_ITERATIONS, false);
}
if(m_posConstraintHandle.IsValid())
{
PHCONSTRAINT->Remove(m_posConstraintHandle);
m_posConstraintHandle.Reset();
}
if(m_rotXConstraintHandle.IsValid())
{
PHCONSTRAINT->Remove(m_rotXConstraintHandle);
m_rotXConstraintHandle.Reset();
}
if(m_rotYConstraintHandle.IsValid())
{
PHCONSTRAINT->Remove(m_rotYConstraintHandle);
m_rotYConstraintHandle.Reset();
}
if(m_rotZConstraintHandle.IsValid())
{
PHCONSTRAINT->Remove(m_rotZConstraintHandle);
m_rotZConstraintHandle.Reset();
}
// Update the trucks vehicle gadget so it knows its no longer attached to this trailer.
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
if(pAttachParent)
{
Assert(pAttachParent->GetIsTypeVehicle());
CVehicle* pParentVehicle = static_cast<CVehicle*>(pAttachParent);
// Could make it so people have to call the vehicle gadget to disconnect but that might be confusing
for(s32 i = 0; i < pParentVehicle->GetNumberOfVehicleGadgets(); i++)
{
CVehicleGadget *pVehicleGadget = pParentVehicle->GetVehicleGadget(i);
if(pVehicleGadget->GetType() == VGT_TRAILER_ATTACH_POINT)
{
CVehicleTrailerAttachPoint *pTrailerAttachPoint = static_cast<CVehicleTrailerAttachPoint*>(pVehicleGadget);
pTrailerAttachPoint->SetAttachedTrailer(NULL);
}
else if(pVehicleGadget->GetType() == VGT_PICK_UP_ROPE || pVehicleGadget->GetType() == VGT_PICK_UP_ROPE_MAGNET)
{
// if the trail is attached to the grappling hook, notify the detachment
CVehicleGadgetPickUpRope *pPickUpRope = static_cast<CVehicleGadgetPickUpRope*>(pVehicleGadget);
if(!pPickUpRope->IsDetaching())
{
pPickUpRope->DetachEntity();
}
}
}
}
//DetachCargoVehicles();
CPhysical::DetachFromParent(nDetachFlags);
m_AttachedParent = nullptr;
m_isAttachedToParent = false;
SetCollidedWithAttachParent(false);
}
float CTrailer::GetTotalMass()
{
float fTotalMass = GetMass();
if(GetChildAttachment())
{
CPhysical* pNextChild = (CPhysical *) GetChildAttachment();
while(pNextChild)
{
fwAttachmentEntityExtension *nextChildAttachExt = pNextChild->GetAttachmentExtension();
Assertf(nextChildAttachExt, "Attached child doesn't have an attachment extension?");
if(nextChildAttachExt->GetAttachState() == ATTACH_STATE_PHYSICAL)
{
if(nextChildAttachExt->GetNumConstraintHandles() > 0)
{
float avgInvMassRatio = 0.0f;
for(int i = 0; i < nextChildAttachExt->GetNumConstraintHandles(); i++)
{
if( phConstraintBase* pConstraint = PHCONSTRAINT->GetTemporaryPointer(nextChildAttachExt->GetConstraintHandle(i)) )
{
avgInvMassRatio += pConstraint->GetMassInvScaleB();
}
}
avgInvMassRatio = avgInvMassRatio/nextChildAttachExt->GetNumConstraintHandles();
fTotalMass += pNextChild->GetMass()*avgInvMassRatio;
}
else
{
fTotalMass += pNextChild->GetMass();
}
pNextChild = (CPhysical *) nextChildAttachExt->GetSiblingAttachment();
}
else
{
break;
}
}
}
return fTotalMass;
}
void CTrailer::SetInverseMassScale(float fInvMass)
{
phConstraintBase* pPosConstraint = PHCONSTRAINT->GetTemporaryPointer(m_posConstraintHandle);
phConstraintRotation* pRotConstraintX = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) );
phConstraintRotation* pRotConstraintY = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) );
phConstraintRotation* pRotConstraintZ = static_cast<phConstraintRotation*>( PHCONSTRAINT->GetTemporaryPointer(m_rotZConstraintHandle) );
if( pPosConstraint )
{
pPosConstraint->SetMassInvScaleB(fInvMass);
}
if( pRotConstraintX )
{
pRotConstraintX->SetMassInvScaleB(fInvMass);
}
if( pRotConstraintY )
{
pRotConstraintY->SetMassInvScaleB(fInvMass);
}
if( pRotConstraintZ )
{
pRotConstraintZ->SetMassInvScaleB(fInvMass);
}
}
ePrerenderStatus CTrailer::PreRender(const bool bIsVisibleInMainViewport)
{
m_trailerLegs.PreRender(this);
return CAutomobile::PreRender(bIsVisibleInMainViewport);
}
void CTrailer::PreRender2(const bool bIsVisibleInMainViewport)
{
CVehicle::PreRender2(bIsVisibleInMainViewport);
s32 lightFlags = 0;
CPhysical* pAttachParent = (CPhysical *) GetAttachParent();
if(pAttachParent)
{
Assert(pAttachParent->GetIsTypeVehicle());
CVehicle* pParentVehicle = static_cast<CVehicle*>(pAttachParent);
if(pParentVehicle->IsEngineOn())//if the engines on make sure the trailers lights are on
lightFlags |= LIGHTS_ALWAYS_HAVE_POWER;
if(pParentVehicle->GetDriver())
lightFlags |=LIGHTS_IGNORE_DRIVER_CHECK;
}
DoVehicleLights(lightFlags);
}
bool CTrailer::UsesDummyPhysics(const eVehicleDummyMode vdm) const
{
switch(vdm)
{
case VDM_REAL:
return false;
case VDM_DUMMY:
{
if(GetAttachParent())
return false;
return true;
}
case VDM_SUPERDUMMY:
return true;
default:
Assert(false);
return false;
}
}
bool CTrailer::GetCanMakeIntoDummy(eVehicleDummyMode dummyMode)
{
// JB: Trailers cannot switch to VDM_DUMMY currently.
// This is because the handling physics goes crazy when running with the m_pDummyInst active.
// I tried enabling dummy mode whilst keeping the fragInst active, but the bikes kept
// getting stuck on the road collision (because they're running along the road nodes)
if(dummyMode == VDM_DUMMY)
{
BANK_ONLY(SetNonConversionReason("Trailer cannot use VDM_DUMMY currently due to issues with attachments.");)
return false;
}
return CVehicle::GetCanMakeIntoDummy(dummyMode);
}
void CTrailer::SwitchToFullFragmentPhysics(const eVehicleDummyMode prevMode)
{
CVehicle::SwitchToFullFragmentPhysics(prevMode);
// init constraints/attachments/etc
}
void CTrailer::SwitchToDummyPhysics(const eVehicleDummyMode prevMode)
{
Assertf(false, "Trailers cannot convert into dummy currently.");
// remove constraints/attachments/etc
CVehicle::SwitchToDummyPhysics(prevMode);
}
void CTrailer::SwitchToSuperDummyPhysics(const eVehicleDummyMode prevMode)
{
// remove constraints/attachments/etc
CVehicle::SwitchToSuperDummyPhysics(prevMode);
}
bool CTrailer::TryToMakeFromDummy(const bool bSkipClearanceTest)
{
if(GetDummyAttachmentParent())
{
Assertf(false, "You must not call TryToMakeFromDummy() on an attached CTrailer; instead call it on the parent vehicle, which will ensure that the attached trailer converts as well.");
return false;
}
else
{
return CAutomobile::TryToMakeFromDummy(bSkipClearanceTest);
}
}
dev_float dfTrailerContactDepthToDetach = 0.5f;
void CTrailer::ProcessPreComputeImpacts(phContactIterator impacts)
{
CAutomobile::ProcessPreComputeImpacts(impacts);
bool impactsDisabled = false;
const int numTurretBones = 6;
eHierarchyId trailerLargeTurretBones[ numTurretBones ] = { VEH_TURRET_2_MOD, VEH_TURRET_3_MOD, VEH_TURRET_2_BASE, VEH_TURRET_3_BASE, VEH_TURRET_2_BARREL, VEH_TURRET_3_BARREL };
bool isTrailerLarge = MI_TRAILER_TRAILERLARGE.IsValid() && GetModelIndex() == MI_TRAILER_TRAILERLARGE;
if(GetAttachParent() && GetAttachParent()->GetCurrentPhysicsInst())
{
impacts.Reset();
while(!impacts.AtEnd())
{
if(!impacts.IsDisabled() && !impacts.IsConstraint())
{
phInst* pOtherInstance = impacts.GetOtherInstance();
if(pOtherInstance == GetAttachParent()->GetCurrentPhysicsInst())
{
if(impacts.GetDepth() > dfTrailerContactDepthToDetach)
{
CPhysical *pAttachParent = (CPhysical *)GetAttachParent();
if(!NetworkUtils::IsNetworkCloneOrMigrating(pAttachParent))
{
DetachFromParent(0);
}
else
{
// send an event to the owner of the attached entity requesting they detach
CRequestDetachmentEvent::Trigger(*this);
}
break;
}
}
}
impacts++;
}
}
// Disable the push collision when player vaulting through the trailer, B*1747991
impacts.Reset();
while(!impacts.AtEnd())
{
if(!impacts.IsDisabled() && !impacts.IsConstraint())
{
phInst* pOtherInstance = impacts.GetOtherInstance();
CEntity* pOtherEntity = CPhysics::GetEntityFromInst(pOtherInstance);
if(pOtherEntity && pOtherEntity->GetIsTypePed())
{
CPed *pOtherPed = static_cast<CPed*>(pOtherEntity);
if(pOtherPed->GetPedResetFlag(CPED_RESET_FLAG_IsVaulting))
{
impacts.SetDepth(0.0f);
}
}
if( GetAttachParent() && GetAttachParent()->GetCurrentPhysicsInst() &&
( !pOtherEntity || pOtherEntity->GetIsAnyFixedFlagSet() ) &&
isTrailerLarge )
{
impacts.SetFriction( 0.01f );
}
if( pOtherEntity &&
pOtherEntity->GetIsTypeVehicle() )
{
if( isTrailerLarge &&
MI_BIKE_OPPRESSOR.IsValid() &&
pOtherEntity->GetModelIndex() == MI_BIKE_OPPRESSOR )
{
Vec3V myNormal;
impacts.GetMyNormal( myNormal );
if( myNormal.GetZf() > 0.8f )
{
if( GetVelocity().Mag2() > 10.0f )
{
CBike* bike = static_cast< CBike* >( pOtherEntity );
CPed* driver = bike->GetDriver();
if( driver )
{
driver->KnockPedOffVehicle( true );
}
}
}
}
}
if( m_bDisableCollisionsWithObjectsBasicallyAttachedToParent &&
pOtherEntity &&
pOtherEntity->GetIsTypeObject() )
{
if( GetAttachParent() &&
pOtherEntity->GetAttachParent() == GetAttachParent() )
{
impacts.DisableImpact();
continue;
}
}
if( isTrailerLarge )
{
static dev_float forwardNormalLimit = -0.5f;
Vec3V myNormal;
impacts.GetMyNormal( myNormal );
float forwardNormal = Dot( GetTransform().GetForward(), myNormal ).Getf();
if( forwardNormal < forwardNormalLimit )
{
for( int i = 0; i < numTurretBones; i++ )
{
fragInst* pFragInst = GetVehicleFragInst();
if( pFragInst )
{
int turretComponent = pFragInst->GetComponentFromBoneIndex( GetBoneIndex( trailerLargeTurretBones[ i ] ) );
if( turretComponent == impacts.GetMyComponent() )
{
impacts.DisableImpact();
continue;
}
}
}
}
if( m_isAttachedToParent &&
( !pOtherEntity ||
pOtherEntity->GetIsAnyFixedFlagSet() ) )
{
Vec3V vMyPosition = impacts.GetMyPosition();
vMyPosition = GetTransform().UnTransform( vMyPosition );
static dev_float minZForNormalFlattening = 1.5f;
static dev_float minZForUpright = 0.7f;
static dev_float maxZToDisableNormal = -0.8f;
if( vMyPosition.GetZf() > minZForNormalFlattening &&
GetTransform().GetUp().GetZf() > minZForUpright )
{
Vec3V myNormal;
impacts.GetMyNormal( myNormal );
if( myNormal.GetZf() < maxZToDisableNormal ||
myNormal.GetZf() > 0.0f ) // we should never get any upwards facing normals on the top of the trailer.
{
impacts.DisableImpact();
impactsDisabled = true;
}
else if( m_bDisablingImpacts || impactsDisabled )
{
impacts.DisableImpact();
impactsDisabled = true;
}
}
}
}
}
impacts++;
}
m_bDisablingImpacts = impactsDisabled;
}
///////////////////////////////////////////////////////////////////////////////////
// CalcVirtualFrontWheelAtAttachmentPoint
//
// Calculates the point in world space where the trailer attachment
// point projected down to the rear wheel contact height. Used to
// calculate the forward vector for super-dummy trailers.
//
// vParentAttachPoint = attachment point on parent, global
// vRearWheelContactPoint = average contact point of rear wheels
// vRearWheelDesired = desired position of a rear wheel (arbitrarily chosen), to define local height of trailer
//
Vec3V_Out CTrailer::CalcTrailerVirtualWheelAtAttachmentPoint(Vec3V_In vParentAttachPoint, Vec3V_In vRearWheelContactPoint, Vec3V_In vRearWheelDesired, float fWheelRadius) const
{
Matrix34 trailerAttachMtx;
GetGlobalMtx(GetBoneIndex(TRAILER_ATTACH),trailerAttachMtx);
Vec3V vTrailerAttach = RCC_VEC3V(trailerAttachMtx.d);
Vec3V vToRearWheelDesired = Subtract(vRearWheelDesired,vTrailerAttach);
ScalarV fVirtualWheelContactOffset = Abs(Dot(vToRearWheelDesired,GetTransform().GetC()));
fVirtualWheelContactOffset += ScalarV(fWheelRadius);
// The calculation ...
// r = height of vehicle attachment above rear wheel contact in trailer space (should only vary with load and a little with orientation)
ScalarV r(fVirtualWheelContactOffset);
// v = vRearWheelContactPoint - vParentAttachPoint
Vec3V v = Subtract(vRearWheelContactPoint,vParentAttachPoint);
ScalarV vMag = Mag(v);
if((vMag < r).Getb())
{
// Degenerate case... just point at vA for now
return vParentAttachPoint;
}
Vec3V vNorm = v / vMag;
// We are trying to calculate vP, which is on the circle of radius r from vA and vW->vP is tangent to the circle
// I.e. we are trying to calculate the direction the trailer should be oriented toward.
// x = distance along v from vA to vP (this is the core unknown calculation)
ScalarV x = square(r) / vMag;
// w = v X world up (i.e. horizontal perp to v)
Vec3V w = CrossZAxis(v);
// u = v X w (i.e. vertical plane down perp to v)
Vec3V u = Cross(v,w);
if(u.GetZf()>0.0f)
{
u = Negate(u);
}
Vec3V uNorm = Normalize(u);
// y = distance along u from vA to vP
ScalarV y (sqrtf((square(r)-square(x)).Getf()));
Vec3V vP = vNorm * x + uNorm * y;
vP += vParentAttachPoint;
#if DEBUG_DRAW && __BANK
static bool bDebugDraw = false;
if(bDebugDraw)
{
grcDebugDraw::Cross(vParentAttachPoint,0.3f,Color_red,-1);
grcDebugDraw::Cross(vTrailerAttach,0.3f,Color_purple,-1);
grcDebugDraw::Line(vParentAttachPoint,vTrailerAttach,Color_white,Color_white,-1);
grcDebugDraw::Line(vRearWheelDesired,vRearWheelContactPoint,Color_yellow,Color_blue,-1);
grcDebugDraw::Line(vRearWheelContactPoint,vP,Color_pink,Color_pink,-1);
}
#endif
return vP;
}
///////////////////////////////////////////////////////////////////////////////////
// FUNCTION : BlowUpCar
// PURPOSE : Does everything needed to destroy a car
///////////////////////////////////////////////////////////////////////////////////
void CTrailer::BlowUpCar( CEntity *pCulprit, bool bInACutscene, bool bAddExplosion, bool bNetCall, u32 weaponHash, bool bDelayedExplosion)
{
if (GetStatus() == STATUS_WRECKED)
{
return; // Don't blow up a 2nd time
}
// Need to try to swap the whole vehicle setup out of dummy mode for blowing up an attached trailer
if(CVehicle* parentVeh = GetDummyAttachmentParent())
{
parentVeh->MakeRealOrFixForExplosion();
}
DetachFromParent(0);
if (m_nPhysicalFlags.bNotDamagedByAnything)
{
return; // If the flag is set for don't damage this trailer (usually set by script)
}
//if the trailer has any petrol blow it up
CAutomobile::BlowUpCar( pCulprit, bInACutscene, bAddExplosion && (GetVehicleDamage()->GetPetrolTankLevel() > 0.0f), bNetCall, weaponHash, bDelayedExplosion);
// Modifying the CG of articulated stuff will cause lots of side effects.
if(!IsColliderArticulated() && AssertVerify(pHandling))
{
Vec3V vCentreOfMassOffset = pHandling->m_vecCentreOfMassOffset;
phArchetype* pArch = GetVehicleFragInst()->GetArchetype();
phBound* pBound = pArch->GetBound();
Vec3V cgOff = pBound->GetCGOffset();
cgOff = rage::Add(cgOff, vCentreOfMassOffset);
pBound->SetCGOffset(cgOff);
if(phCollider* pCollider = GetCollider())
{
// Moving the CG means the collider and instance are no longer aligned.
pCollider->SetColliderMatrixFromInstance();
}
}
// This is never actually going to work - Either an InitArticulated or BreakOffAboveGroup
// will reset COM to the fragTypeLOD or recalculate COM based on remaining child masses =/
// - I think the only way around would be to break everything off right before setting the COM here and somehow prevent it from ever becoming articulated as well...
// No longer attaches automatically - we're broken
m_nVehicleFlags.bAutomaticallyAttaches = false;
m_nVehicleFlags.bScansWithNonPlayerDriver = false;
//m_extendingSidesTargetRatio = m_extendingSidesRatio;
}
//void CTrailer::UpdateExtendingSides( float fTimeStep )
//{
// TUNE_GROUP_FLOAT( VEHICLE_TRAILER_EXTENDABLE_SIDES, extendingSpeed, 5.0f, 0.0f, 20.0f, 0.1f );
// float speed = extendingSpeed * fTimeStep;
//
// int leftBoneIndex = GetBoneIndex( VEH_EXTENDABLE_SIDE_L );
// int rightBoneIndex = GetBoneIndex( VEH_EXTENDABLE_SIDE_R );
// float previousRatio = m_extendingSidesRatio;
//
// if( leftBoneIndex != -1 )
// {
// SlideMechanicalPart( leftBoneIndex, m_extendingSidesTargetRatio, m_extendingSidesRatio, Vector3( -1.0f, 0.0f, 0.0f ), speed );
// }
// if( rightBoneIndex != -1 )
// {
// SlideMechanicalPart( rightBoneIndex, m_extendingSidesTargetRatio, previousRatio, Vector3( 1.0f, 0.0f, 0.0f ), speed );
// m_extendingSidesRatio = previousRatio;
// }
//
// if( m_extendingSidesTargetRatio != m_extendingSidesRatio )
// {
// ActivatePhysics();
//
// if(fragInst* pInst = GetFragInst())
// {
// pInst->PoseBoundsFromSkeleton( true, true, false );
// }
// }
//}
//void CTrailer::SetExendableRatio( float targetRatio )
//{
// m_extendingSidesTargetRatio = targetRatio;
// UpdateExtendingSides( 100.0f );
//}
#if __BANK
bank_bool CTrailer::ms_bDebugDrawTrailers = false;
void CTrailer::InitWidgets(bkBank* pBank)
{
if(pBank)
{
pBank->PushGroup("Trailers");
pBank->AddToggle("Debug draw trailers",&ms_bDebugDrawTrailers);
pBank->PopGroup();
}
}
#endif // __BANK