#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; iGetIsTypeVehicle()); CVehicle* pParentVehicle = static_cast(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( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) ); phConstraintRotation* pRotYConstraint = static_cast( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) ); phConstraintRotation* pRotZConstraint = static_cast( 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(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(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(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(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( 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( 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( 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( 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( 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(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( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) ); phConstraintRotation* pRotConstraintY = static_cast( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) ); phConstraintRotation* pRotConstraintZ = static_cast( 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( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) ); phConstraintRotation* pRotConstraintY = static_cast( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) ); phConstraintRotation* pRotConstraintZ = static_cast( 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 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 &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(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(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(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( PHCONSTRAINT->GetTemporaryPointer(m_rotXConstraintHandle) ); phConstraintRotation* pRotConstraintY = static_cast( PHCONSTRAINT->GetTemporaryPointer(m_rotYConstraintHandle) ); phConstraintRotation* pRotConstraintZ = static_cast( 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(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(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