Files
UnrealEngine/Engine/Plugins/Experimental/ChaosVehiclesPlugin/Source/ChaosVehicles/Private/ChaosVehicleMovementComponent.cpp
2025-05-18 13:04:45 +08:00

1970 lines
72 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ChaosVehicleMovementComponent.h"
#include "BodySetupCore.h"
#include "EngineGlobals.h"
#include "GameFramework/Pawn.h"
#include "GameFramework/PlayerController.h"
#include "Engine/Engine.h"
#include "Engine/SkinnedAsset.h"
#include "CanvasItem.h"
#include "Engine/Canvas.h"
#include "Components/StaticMeshComponent.h"
#include "Components/SkinnedMeshComponent.h"
#include "Components/SkeletalMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "DrawDebugHelpers.h"
#include "UObject/FrameworkObjectVersion.h"
#include "Net/UnrealNetwork.h"
#include "VehicleAnimationInstance.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "Physics/PhysicsFiltering.h"
#include "Physics/PhysicsInterfaceUtils.h"
#include "GameFramework/PawnMovementComponent.h"
#include "Logging/MessageLog.h"
#include "DisplayDebugHelpers.h"
#include "Chaos/ChaosEngineInterface.h"
#include "Chaos/PBDJointConstraintData.h"
#include "Chaos/DebugDrawQueue.h"
#include "ChaosVehicleManager.h"
#include "SimpleVehicle.h"
#include "AI/Navigation/AvoidanceManager.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "GameFramework/HUD.h"
#include "PhysicsEngine/PhysicsSettings.h"
#include "Physics/Experimental/PhysScene_Chaos.h"
#include "Chaos/Particle/ParticleUtilities.h"
#include "Chaos/ParticleHandleFwd.h"
#include "PhysicsProxy/SingleParticlePhysicsProxy.h"
#include "PBDRigidsSolver.h"
#define LOCTEXT_NAMESPACE "UVehicleMovementComponent"
#if VEHICLE_DEBUGGING_ENABLED
UE_DISABLE_OPTIMIZATION
#endif
DEFINE_LOG_CATEGORY(LogVehicle);
FVehicleDebugParams GVehicleDebugParams;
FAutoConsoleVariableRef CVarChaosVehiclesShowCOM(TEXT("p.Vehicle.ShowCOM"), GVehicleDebugParams.ShowCOM, TEXT("Enable/Disable Center Of Mass Debug Visualisation."));
FAutoConsoleVariableRef CVarChaosVehiclesShowModelAxis(TEXT("p.Vehicle.ShowModelOrigin"), GVehicleDebugParams.ShowModelOrigin, TEXT("Enable/Disable Model Origin Visualisation."));
FAutoConsoleVariableRef CVarChaosVehiclesShowAllForces(TEXT("p.Vehicle.ShowAllForces"), GVehicleDebugParams.ShowAllForces, TEXT("Enable/Disable Force Visualisation."));
FAutoConsoleVariableRef CVarChaosVehiclesAerofoilForces(TEXT("p.Vehicle.ShowAerofoilForces"), GVehicleDebugParams.ShowAerofoilForces, TEXT("Enable/Disable Aerofoil Force Visualisation."));
FAutoConsoleVariableRef CVarChaosVehiclesAerofoilSurface(TEXT("p.Vehicle.ShowAerofoilSurface"), GVehicleDebugParams.ShowAerofoilSurface, TEXT("Enable/Disable a very approximate visualisation of where the Aerofoil surface is located and its orientation."));
FAutoConsoleVariableRef CVarChaosVehiclesDisableTorqueControl(TEXT("p.Vehicle.DisableTorqueControl"), GVehicleDebugParams.DisableTorqueControl, TEXT("Enable/Disable Direct Torque Control."));
FAutoConsoleVariableRef CVarChaosVehiclesDisableStabilizeControl(TEXT("p.Vehicle.DisableStabilizeControl"), GVehicleDebugParams.DisableStabilizeControl, TEXT("Enable/Disable Position Stabilization Control."));
FAutoConsoleVariableRef CVarChaosVehiclesDisableAerodynamics(TEXT("p.Vehicle.DisableAerodynamics"), GVehicleDebugParams.DisableAerodynamics, TEXT("Enable/Disable Aerodynamic Forces Drag/Downforce."));
FAutoConsoleVariableRef CVarChaosVehiclesDisableAerofoils(TEXT("p.Vehicle.DisableAerofoils"), GVehicleDebugParams.DisableAerofoils, TEXT("Enable/Disable Aerofoil Forces."));
FAutoConsoleVariableRef CVarChaosVehiclesDisableThrusters(TEXT("p.Vehicle.DisableThrusters"), GVehicleDebugParams.DisableThrusters, TEXT("Enable/Disable Thruster Forces."));
FAutoConsoleVariableRef CVarChaosVehiclesBatchQueries(TEXT("p.Vehicle.BatchQueries"), GVehicleDebugParams.BatchQueries, TEXT("Enable/Disable Batching Of Suspension Raycasts."));
FAutoConsoleVariableRef CVarChaosVehiclesCacheTraceOverlap(TEXT("p.Vehicle.CacheTraceOverlap"), GVehicleDebugParams.CacheTraceOverlap, TEXT("Enable/Disable Caching Of Suspension Trace Overlap Test Optimization (only valid when BatchQueries enabled)."));
FAutoConsoleVariableRef CVarChaosVehiclesForceDebugScaling(TEXT("p.Vehicle.SetForceDebugScaling"), GVehicleDebugParams.ForceDebugScaling, TEXT("Set Scaling For Force Visualisation."));
FAutoConsoleVariableRef CVarChaosVehiclesSleepCounterThreshold(TEXT("p.Vehicle.SleepCounterThreshold"), GVehicleDebugParams.SleepCounterThreshold, TEXT("Set The Sleep Counter Iteration Threshold."));
FAutoConsoleVariableRef CVarChaosVehiclesDisableVehicleSleep(TEXT("p.Vehicle.DisableVehicleSleep"), GVehicleDebugParams.DisableVehicleSleep, TEXT("Disable Vehicle Agressive Sleeping."));
FAutoConsoleVariableRef CVarChaosVehiclesSetMaxMPH(TEXT("p.Vehicle.SetMaxMPH"), GVehicleDebugParams.SetMaxMPH, TEXT("Set a top speed in MPH (affects all vehicles)."));
FAutoConsoleVariableRef CVarChaosVehiclesEnableMultithreading(TEXT("p.Vehicle.EnableMultithreading"), GVehicleDebugParams.EnableMultithreading, TEXT("Enable multi-threading of vehicle updates."));
FAutoConsoleVariableRef CVarChaosVehiclesControlInputWakeTolerance(TEXT("p.Vehicle.ControlInputWakeTolerance"), GVehicleDebugParams.ControlInputWakeTolerance, TEXT("Set the control input wake tolerance."));
void FVehicleState::CaptureState(const FBodyInstance* TargetInstance, float GravityZ, float DeltaTime)
{
if (TargetInstance)
{
VehicleWorldTransform = TargetInstance->GetUnrealWorldTransform();
VehicleWorldVelocity = TargetInstance->GetUnrealWorldVelocity();
VehicleWorldAngularVelocity = TargetInstance->GetUnrealWorldAngularVelocityInRadians();
VehicleWorldCOM = TargetInstance->GetCOMPosition();
WorldVelocityNormal = VehicleWorldVelocity.GetSafeNormal();
VehicleUpAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Z);
VehicleForwardAxis = VehicleWorldTransform.GetUnitAxis(EAxis::X);
VehicleRightAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Y);
VehicleLocalVelocity = VehicleWorldTransform.InverseTransformVector(VehicleWorldVelocity);
LocalAcceleration = (VehicleLocalVelocity - LastFrameVehicleLocalVelocity) / DeltaTime;
LocalGForce = LocalAcceleration / FMath::Abs(GravityZ);
LastFrameVehicleLocalVelocity = VehicleLocalVelocity;
ForwardSpeed = FVector::DotProduct(VehicleWorldVelocity, VehicleForwardAxis);
ForwardsAcceleration = LocalAcceleration.X;
}
}
void FVehicleState::CaptureState(const Chaos::FRigidBodyHandle_Internal* Handle, float GravityZ, float DeltaTime)
{
if (Handle)
{
const FTransform WorldTM(Handle->R(), Handle->X());
VehicleWorldTransform = WorldTM;
VehicleWorldVelocity = Handle->V();
VehicleWorldAngularVelocity = Handle->W();
VehicleWorldCOM = Handle->CenterOfMass();
WorldVelocityNormal = VehicleWorldVelocity.GetSafeNormal();
VehicleUpAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Z);
VehicleForwardAxis = VehicleWorldTransform.GetUnitAxis(EAxis::X);
VehicleRightAxis = VehicleWorldTransform.GetUnitAxis(EAxis::Y);
VehicleLocalVelocity = VehicleWorldTransform.InverseTransformVector(VehicleWorldVelocity);
LocalAcceleration = (VehicleLocalVelocity - LastFrameVehicleLocalVelocity) / DeltaTime;
LocalGForce = LocalAcceleration / FMath::Abs(GravityZ);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (Chaos::FPhysicsSolverBase::IsNetworkPhysicsPredictionEnabled() && Chaos::FPhysicsSolverBase::CanDebugNetworkPhysicsPrediction())
{
UE_LOG(LogVehicle, Log, TEXT("Vehicle state last velocity = %s | World Transform = %s | World Velocity = %s"),
*LastFrameVehicleLocalVelocity.ToString(), *VehicleWorldTransform.ToString(), *VehicleWorldVelocity.ToString());
}
#endif
LastFrameVehicleLocalVelocity = VehicleLocalVelocity;
ForwardSpeed = FVector::DotProduct(VehicleWorldVelocity, VehicleForwardAxis);
ForwardsAcceleration = LocalAcceleration.X;
}
}
/**
* UChaosVehicleSimulation
*/
void UChaosVehicleSimulation::TickVehicle(UWorld* WorldIn, float DeltaTime, const FChaosVehicleAsyncInput& InputData, FChaosVehicleAsyncOutput& OutputData, Chaos::FRigidBodyHandle_Internal* Handle)
{
World = WorldIn;
RigidHandle = Handle;
// movement updates and replication
if (World && RigidHandle)
{
#if DEBUG_NETWORK_PHYSICS
if (WorldIn->IsNetMode(NM_ListenServer) || WorldIn->IsNetMode(NM_DedicatedServer))
{
UE_LOG(LogTemp, Log, TEXT("SERVER | PT | TickVehicle | Async tick vehicle with inputs at frame %d : Throttle = %f Brake = %f Roll = %f Pitch = %f Yaw = %f Steering = %f Handbrake = %f Sleeping = %d"),
InputData.PhysicsInputs.NetworkInputs.LocalFrame, VehicleInputs.ThrottleInput, VehicleInputs.BrakeInput, VehicleInputs.RollInput, VehicleInputs.PitchInput,
VehicleInputs.YawInput, VehicleInputs.SteeringInput, VehicleInputs.HandbrakeInput, VehicleState.bSleeping);
}
else if(WorldIn->IsNetMode(NM_Client))
{
UE_LOG(LogTemp, Log, TEXT("CLIENT | PT | TickVehicle | Async tick vehicle with inputs at frame %d : Throttle = %f Brake = %f Roll = %f Pitch = %f Yaw = %f Steering = %f Handbrake = %f Sleeping = %d"),
InputData.PhysicsInputs.NetworkInputs.LocalFrame, VehicleInputs.ThrottleInput, VehicleInputs.BrakeInput, VehicleInputs.RollInput, VehicleInputs.PitchInput,
VehicleInputs.YawInput, VehicleInputs.SteeringInput, VehicleInputs.HandbrakeInput, VehicleState.bSleeping);
}
#endif
if (!VehicleState.bSleeping)
{
if (CanSimulate() && Handle)
{
// Update the vehicle/wheels... states
UpdateState(DeltaTime, InputData, Handle);
// Apply the controls inputs
ApplyInput(InputData.PhysicsInputs.NetworkInputs.VehicleInputs, DeltaTime);
// Update the simulation forces/impulses...
UpdateSimulation(DeltaTime, InputData, Handle);
FillOutputState(OutputData);
}
}
}
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
DrawDebug3D();
#endif
}
void UChaosVehicleSimulation::ApplyDeferredForces(Chaos::FRigidBodyHandle_Internal* Handle)
{
DeferredForces.Apply(Handle);
}
void UChaosVehicleSimulation::UpdateState(float DeltaTime, const FChaosVehicleAsyncInput& InputData, Chaos::FRigidBodyHandle_Internal* Handle)
{
VehicleState.CaptureState(Handle, InputData.PhysicsInputs.GravityZ, DeltaTime);
}
void UChaosVehicleSimulation::UpdateSimulation(float DeltaTime, const FChaosVehicleAsyncInput& InputData, Chaos::FRigidBodyHandle_Internal* Handle)
{
ApplyAerodynamics(DeltaTime);
ApplyAerofoilForces(DeltaTime);
ApplyThrustForces(DeltaTime);
ApplyTorqueControl(DeltaTime, InputData);
}
void UChaosVehicleSimulation::FillOutputState(FChaosVehicleAsyncOutput& Output)
{}
/** Pass control Input to the vehicle systems */
void UChaosVehicleSimulation::ApplyInput(const FControlInputs& ControlInputs, float DeltaTime)
{
for (int AerofoilIdx = 0; AerofoilIdx < PVehicle->Aerofoils.Num(); AerofoilIdx++)
{
Chaos::FAerofoil& Aerofoil = PVehicle->GetAerofoil(AerofoilIdx);
switch (Aerofoil.Setup().Type)
{
case Chaos::EAerofoilType::Rudder:
Aerofoil.SetControlSurface(-ControlInputs.YawInput);
break;
case Chaos::EAerofoilType::Elevator:
Aerofoil.SetControlSurface(ControlInputs.PitchInput);
break;
case Chaos::EAerofoilType::Wing:
if (Aerofoil.Setup().Offset.Y < 0.0f)
{
Aerofoil.SetControlSurface(ControlInputs.RollInput);
}
else
{
Aerofoil.SetControlSurface(-ControlInputs.RollInput);
}
break;
}
}
for (int Thrusterdx = 0; Thrusterdx < PVehicle->Thrusters.Num(); Thrusterdx++)
{
Chaos::FSimpleThrustSim& Thruster = PVehicle->GetThruster(Thrusterdx);
Thruster.SetThrottle(ControlInputs.ThrottleInput);
switch (Thruster.Setup().Type)
{
case Chaos::EThrustType::HelicopterRotor:
{
Thruster.SetPitch(ControlInputs.PitchInput);
Thruster.SetRoll(ControlInputs.RollInput);
}
break;
case Chaos::EThrustType::Rudder:
{
Thruster.SetYaw(-ControlInputs.YawInput - ControlInputs.SteeringInput);
}
break;
case Chaos::EThrustType::Elevator:
{
Thruster.SetPitch(ControlInputs.PitchInput);
}
break;
case Chaos::EThrustType::Wing:
{
if (Thruster.Setup().Offset.Y < 0.0f)
{
Thruster.SetRoll(ControlInputs.RollInput);
}
else
{
Thruster.SetRoll(-ControlInputs.RollInput);
}
}
break;
}
}
}
void UChaosVehicleSimulation::ApplyAerodynamics(float DeltaTime)
{
if (!GVehicleDebugParams.DisableAerodynamics)
{
// This force applied all the time whether the vehicle is on the ground or not
Chaos::FSimpleAerodynamicsSim& PAerodynamics = PVehicle->GetAerodynamics();
FVector LocalDragLiftForce = (PAerodynamics.GetCombinedForces(Chaos::CmToM(VehicleState.ForwardSpeed))) * Chaos::MToCmScaling();
FVector WorldLiftDragForce = VehicleState.VehicleWorldTransform.TransformVector(LocalDragLiftForce);
AddForce(WorldLiftDragForce);
}
}
FVector GetWorldVelocityAtPoint(const Chaos::FRigidBodyHandle_Internal* RigidHandle, const FVector& WorldLocation)
{
if (RigidHandle)
{
const Chaos::FVec3 COM = RigidHandle ? Chaos::FParticleUtilitiesGT::GetCoMWorldPosition(RigidHandle) : (Chaos::FVec3)Chaos::FParticleUtilitiesGT::GetActorWorldTransform(RigidHandle).GetTranslation();
const Chaos::FVec3 Diff = WorldLocation - COM;
return RigidHandle->V() - Chaos::FVec3::CrossProduct(Diff, RigidHandle->W());
}
else
{
return FVector::ZeroVector;
}
}
void UChaosVehicleSimulation::ApplyAerofoilForces(float DeltaTime)
{
if (GVehicleDebugParams.DisableAerofoils || RigidHandle == nullptr)
return;
TArray<FVector> VelocityLocal;
TArray<FVector> VelocityWorld;
VelocityLocal.SetNum(PVehicle->Aerofoils.Num());
VelocityWorld.SetNum(PVehicle->Aerofoils.Num());
float Altitude = VehicleState.VehicleWorldTransform.GetLocation().Z;
// Work out velocity at each aerofoil before applying any forces so there's no bias on the first ones processed
for (int AerofoilIdx = 0; AerofoilIdx < PVehicle->Aerofoils.Num(); AerofoilIdx++)
{
FVector WorldLocation = VehicleState.VehicleWorldTransform.TransformPosition(PVehicle->GetAerofoil(AerofoilIdx).Setup().Offset * Chaos::MToCmScaling());
VelocityWorld[AerofoilIdx] = GetWorldVelocityAtPoint(RigidHandle, WorldLocation);
VelocityLocal[AerofoilIdx] = VehicleState.VehicleWorldTransform.InverseTransformVector(VelocityWorld[AerofoilIdx]);
}
for (int AerofoilIdx = 0; AerofoilIdx < PVehicle->Aerofoils.Num(); AerofoilIdx++)
{
Chaos::FAerofoil& Aerofoil = PVehicle->GetAerofoil(AerofoilIdx);
FVector LocalForce = Aerofoil.GetForce(VehicleState.VehicleWorldTransform, VelocityLocal[AerofoilIdx] * Chaos::CmToMScaling(), Chaos::CmToM(Altitude), DeltaTime);
FVector WorldForce = VehicleState.VehicleWorldTransform.TransformVector(LocalForce);
FVector WorldLocation = VehicleState.VehicleWorldTransform.TransformPosition(Aerofoil.GetCenterOfLiftOffset() * Chaos::MToCmScaling());
AddForceAtPosition(WorldForce * Chaos::MToCmScaling(), WorldLocation);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
FVector WorldAxis = VehicleState.VehicleWorldTransform.TransformVector(FVector::CrossProduct(FVector(1, 0, 0), Aerofoil.Setup().UpAxis));
if (GVehicleDebugParams.ShowAerofoilSurface)
{
Chaos::FDebugDrawQueue::GetInstance().DrawDebugLine(WorldLocation - WorldAxis * 150.0f, WorldLocation + WorldAxis * 150.0f, FColor::Black, false, -1.f, 0, 5.f);
}
if (GVehicleDebugParams.ShowAerofoilForces)
{
Chaos::FDebugDrawQueue::GetInstance().DrawDebugLine(WorldLocation, WorldLocation + WorldForce * GVehicleDebugParams.ForceDebugScaling, FColor::Green, false, -1.f, 0, 16.f);
}
#endif
}
}
void UChaosVehicleSimulation::ApplyThrustForces(float DeltaTime)
{
if (GVehicleDebugParams.DisableThrusters || RigidHandle == nullptr)
return;
for (int ThrusterIdx = 0; ThrusterIdx < PVehicle->Thrusters.Num(); ThrusterIdx++)
{
Chaos::FSimpleThrustSim& Thruster = PVehicle->GetThruster(ThrusterIdx);
FVector COM_Offset = RigidHandle->CenterOfMass();
COM_Offset.Z = 0.0f;
Thruster.SetWorldVelocity(VehicleState.VehicleWorldVelocity);
Thruster.Simulate(DeltaTime);
FVector ThrustWorldLocation = VehicleState.VehicleWorldTransform.TransformPosition(Thruster.GetThrustLocation() + COM_Offset);
FVector ThrustForce = VehicleState.VehicleWorldTransform.TransformVector(Thruster.GetThrustForce());
AddForceAtPosition(ThrustForce, ThrustWorldLocation);
}
}
void UChaosVehicleSimulation::ApplyTorqueControl(float DeltaTime, const FChaosVehicleAsyncInput& InputData)
{
if (!PVehicle->HasTorqueControlSetup())
{
return;
}
const FControlInputs& ControlInputsPT = InputData.PhysicsInputs.NetworkInputs.VehicleInputs;
if (!GVehicleDebugParams.DisableTorqueControl && RigidHandle)
{
FVector TotalTorque = FVector::ZeroVector;
if (PVehicle->HasTorqueControlSetup() && PVehicle->GetTorqueControl().Setup().Enabled)
{
const Chaos::FTargetRotationControlConfig& TargetRotationControl = PVehicle->GetTargetRotationControl().Setup();
auto ComputeTorque = [](const FVector& TargetUp, const FVector& CurrentUp, const FVector& AngVelocityWorld, float Stiffness, float Damping, float MaxAccel) -> FVector
{
const FQuat CurUpToTargetUp = FQuat::FindBetweenNormals(CurrentUp, TargetUp);
const FVector Axis = CurUpToTargetUp.GetRotationAxis();
const float Angle = CurUpToTargetUp.GetAngle();
float Strength = (Angle * Stiffness - FVector::DotProduct(AngVelocityWorld, Axis) * Damping);
Strength = FMath::Clamp(Strength, -MaxAccel, MaxAccel);
const FVector Torque = Axis * Strength;
return Torque;
};
FVector TargetUp = FVector(0.f, 0.f, 1.f);
float RollMaxAngleRadians = Chaos::DegToRad(TargetRotationControl.RollMaxAngle);
float PitchMaxAngleRadians = Chaos::DegToRad(TargetRotationControl.PitchMaxAngle);
float Speed = FMath::Min(Chaos::CmToM(VehicleState.ForwardSpeed), 20.0f); // cap here
float SpeeScaledRollAmount = 1.0f;
float TargetRoll = 0.f;
if (TargetRotationControl.bRollVsSpeedEnabled)
{
if (PVehicle->Wheels[0].InContact()) // HACK need IsAllowedToSteer virtual method
{
TargetRoll = ControlInputsPT.SteeringInput * TargetRotationControl.RollControlScaling * (Speed * Speed) * DeltaTime * 60.0f;
}
}
else
{
TargetRoll = ControlInputsPT.SteeringInput * TargetRotationControl.RollControlScaling;
}
FVector Rt = VehicleState.VehicleRightAxis * FMath::Max(FMath::Min(TargetRoll, RollMaxAngleRadians), -RollMaxAngleRadians);
FVector Pt = VehicleState.VehicleForwardAxis * FMath::Max(FMath::Min(ControlInputsPT.PitchInput * TargetRotationControl.PitchControlScaling, PitchMaxAngleRadians), -PitchMaxAngleRadians);
FVector UseUp = TargetUp + Rt + Pt;
UseUp.Normalize();
TargetUp = UseUp;
const FVector UpVector = VehicleState.VehicleUpAxis;
const FVector AngVelocityWorld = VehicleState.VehicleWorldAngularVelocity;
const FVector AirControlTorque = ComputeTorque(TargetUp, UpVector, AngVelocityWorld, TargetRotationControl.RotationStiffness, TargetRotationControl.RotationDamping, TargetRotationControl.MaxAccel);
const FVector ForwardVector = VehicleState.VehicleForwardAxis;
const FVector RightVector = VehicleState.VehicleRightAxis;
const float RollAirControl = FVector::DotProduct(AirControlTorque, ForwardVector);
const float PitchAirControl = FVector::DotProduct(AirControlTorque, RightVector);
const float YawAirControl = FVector::DotProduct(AirControlTorque, UpVector);
TotalTorque = RollAirControl * ForwardVector * TargetRotationControl.AutoCentreRollStrength
+ YawAirControl * UpVector * TargetRotationControl.AutoCentreYawStrength
+ PitchAirControl * RightVector * TargetRotationControl.AutoCentrePitchStrength;
}
if (PVehicle->HasTorqueControlSetup() && PVehicle->GetTorqueControl().Setup().Enabled)
{
const Chaos::FTorqueControlConfig& TorqueControl = PVehicle->GetTorqueControl().Setup();
TotalTorque -= VehicleState.VehicleForwardAxis * ControlInputsPT.RollInput * TorqueControl.RollTorqueScaling;
TotalTorque += VehicleState.VehicleRightAxis * ControlInputsPT.PitchInput * TorqueControl.PitchTorqueScaling;
TotalTorque += VehicleState.VehicleUpAxis * ControlInputsPT.YawInput * TorqueControl.YawTorqueScaling;
TotalTorque += VehicleState.VehicleUpAxis * ControlInputsPT.RollInput * TorqueControl.YawFromRollTorqueScaling;
// slowing rotation effect
FVector DampingTorque = (VehicleState.VehicleWorldAngularVelocity) * TorqueControl.RotationDamping;
// combined world torque
TotalTorque -= DampingTorque;
}
AddTorqueInRadians(TotalTorque, true, true);
}
if (!GVehicleDebugParams.DisableStabilizeControl && PVehicle->HasStabilizeControlSetup() && PVehicle->GetStabilizeControl().Setup().Enabled && RigidHandle)
{
const Chaos::FStabilizeControlConfig& StabilizeControl = PVehicle->GetStabilizeControl().Setup();
// try to cancel out velocity on Z axis
FVector CorrectionalForce = FVector::ZeroVector;
{
bool MaintainAltitude = true;
if (MaintainAltitude)
{
CorrectionalForce.Z = -StabilizeControl.AltitudeHoldZ * VehicleState.VehicleWorldVelocity.Z / DeltaTime;
}
}
// try to cancel out velocity on X/Y plane
// #todo: Will break helicopter setup??if (FMath::Abs(RollInput) < SMALL_NUMBER && FMath::Abs(PitchInput) < SMALL_NUMBER)
{
CorrectionalForce.X = -StabilizeControl.PositionHoldXY * VehicleState.VehicleWorldVelocity.X / DeltaTime;
CorrectionalForce.Y = -StabilizeControl.PositionHoldXY * VehicleState.VehicleWorldVelocity.Y / DeltaTime;
}
AddForce(CorrectionalForce);
}
}
void UChaosVehicleSimulation::DrawDebug3D()
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (RigidHandle == nullptr)
{
return;
}
const FTransform BodyTransform = VehicleState.VehicleWorldTransform;
if (GVehicleDebugParams.ShowCOM)
{
const Chaos::FVec3 COMWorld = Chaos::FParticleUtilitiesGT::GetCoMWorldPosition(RigidHandle);
Chaos::FDebugDrawQueue::GetInstance().DrawDebugCoordinateSystem(COMWorld, FRotator(BodyTransform.GetRotation()), 200.f, false, -1.f, 0, 2.f);
}
if (GVehicleDebugParams.ShowModelOrigin)
{
Chaos::FDebugDrawQueue::GetInstance().DrawDebugCoordinateSystem(BodyTransform.GetLocation(), FRotator(BodyTransform.GetRotation()), 200.f, false, -1.f, 0, 2.f);
}
#endif
}
void UChaosVehicleSimulation::AddForce(const FVector& Force, bool bAllowSubstepping, bool bAccelChange)
{
DeferredForces.Add(FDeferredForces::FApplyForceData(Force, bAllowSubstepping, bAccelChange));
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (GVehicleDebugParams.ShowAllForces)
{
FVector Position = RigidHandle->X();
Chaos::FDebugDrawQueue::GetInstance().DrawDebugDirectionalArrow(Position, Position + Force * GVehicleDebugParams.ForceDebugScaling
, 20.f, FColor::Blue, false, 0, 0, 2.f);
}
#endif
}
void UChaosVehicleSimulation::AddForceAtPosition(const FVector& Force, const FVector& Position, bool bAllowSubstepping, bool bIsLocalForce)
{
DeferredForces.Add(FDeferredForces::FApplyForceAtPositionData(Force, Position, bAllowSubstepping, bIsLocalForce));
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (GVehicleDebugParams.ShowAllForces)
{
Chaos::FDebugDrawQueue::GetInstance().DrawDebugDirectionalArrow(Position, Position + Force * GVehicleDebugParams.ForceDebugScaling
, 20.f, FColor::Blue, false, 0, 0, 2.f);
}
#endif
}
void UChaosVehicleSimulation::AddImpulse(const FVector& Impulse, bool bVelChange)
{
DeferredForces.Add(FDeferredForces::FAddImpulseData(Impulse, bVelChange));
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (GVehicleDebugParams.ShowAllForces)
{
FVector Position = VehicleState.VehicleWorldCOM;
Chaos::FDebugDrawQueue::GetInstance().DrawDebugDirectionalArrow(Position, Position + Impulse * GVehicleDebugParams.ForceDebugScaling
, 20.f, FColor::Red, false, 0, 0, 2.f);
}
#endif
}
void UChaosVehicleSimulation::AddImpulseAtPosition(const FVector& Impulse, const FVector& Position)
{
DeferredForces.Add(FDeferredForces::FAddImpulseAtPositionData(Impulse, Position));
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
if (GVehicleDebugParams.ShowAllForces)
{
Chaos::FDebugDrawQueue::GetInstance().DrawDebugDirectionalArrow(Position, Position + Impulse * GVehicleDebugParams.ForceDebugScaling
, 20.f, FColor::Red, false, 0, 0, 2.f);
}
#endif
}
void UChaosVehicleSimulation::AddTorqueInRadians(const FVector& Torque, bool bAllowSubstepping /*= true*/, bool bAccelChange /*= false*/)
{
DeferredForces.Add(FDeferredForces::FAddTorqueInRadiansData(Torque, bAllowSubstepping, bAccelChange));
}
void UChaosVehicleSimulation::InitializeWheel(int WheelIndex, const Chaos::FSimpleWheelConfig* InWheelSetup)
{
if (PVehicle->IsValid() && InWheelSetup && WheelIndex < PVehicle->Wheels.Num())
{
PVehicle->Wheels[WheelIndex].SetupPtr = InWheelSetup;
PVehicle->Wheels[WheelIndex].SetWheelRadius(InWheelSetup->WheelRadius);
}
}
void UChaosVehicleSimulation::InitializeSuspension(int WheelIndex, const Chaos::FSimpleSuspensionConfig* InSuspensionSetup)
{
if (PVehicle->IsValid() && InSuspensionSetup && WheelIndex < PVehicle->Suspension.Num())
{
PVehicle->Suspension[WheelIndex].SetupPtr = InSuspensionSetup;
}
}
/**
* UChaosVehicleMovementComponent
*/
UChaosVehicleMovementComponent::UChaosVehicleMovementComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bReverseAsBrake = true;
bParkEnabled = false;
Mass = 1500.0f;
ChassisWidth = 180.f;
ChassisHeight = 140.f;
DragCoefficient = 0.3f;
DownforceCoefficient = 0.3f;
InertiaTensorScale = FVector( 1.0f, 1.0f, 1.0f );
SleepThreshold = 10.0f;
SleepSlopeLimit = 0.866f; // 30 degrees, Cos(30)
TorqueControl.InitDefaults();
TargetRotationControl.InitDefaults();
StabilizeControl.InitDefaults();
AngErrorAccumulator = 0.0f;
TargetGear = 0;
PrevSteeringInput = 0.0f;
PrevReplicatedSteeringInput = 0.0f;
bRequiresControllerForInputs = true;
IdleBrakeInput = 0.0f;
StopThreshold = 10.0f;
WrongDirectionThreshold = 100.f;
ThrottleInputRate.RiseRate = 6.0f;
ThrottleInputRate.FallRate = 10.0f;
ThrottleInputRate.InputCurveFunction = EInputFunctionType::LinearFunction;
BrakeInputRate.RiseRate = 6.0f;
BrakeInputRate.FallRate = 10.0f;
BrakeInputRate.InputCurveFunction = EInputFunctionType::LinearFunction;
SteeringInputRate.RiseRate = 2.5f;
SteeringInputRate.FallRate = 5.0f;
SteeringInputRate.InputCurveFunction = EInputFunctionType::SquaredFunction;
HandbrakeInputRate.RiseRate = 12.0f;
HandbrakeInputRate.FallRate = 12.0f;
PitchInputRate.RiseRate = 6.0f;
PitchInputRate.FallRate = 10.0f;
PitchInputRate.InputCurveFunction = EInputFunctionType::LinearFunction;
RollInputRate.RiseRate = 6.0f;
RollInputRate.FallRate = 10.0f;
RollInputRate.InputCurveFunction = EInputFunctionType::LinearFunction;
YawInputRate.RiseRate = 6.0f;
YawInputRate.FallRate = 10.0f;
YawInputRate.InputCurveFunction = EInputFunctionType::LinearFunction;
TransmissionType = Chaos::ETransmissionType::Automatic;
SetIsReplicatedByDefault(true);
bUsingNetworkPhysicsPrediction = Chaos::FPhysicsSolverBase::IsNetworkPhysicsPredictionEnabled();
AHUD::OnShowDebugInfo.AddUObject(this, &UChaosVehicleMovementComponent::ShowDebugInfo);
if (bUsingNetworkPhysicsPrediction)
{
static const FName NetworkPhysicsComponentName(TEXT("PC_NetworkPhysicsComponent"));
NetworkPhysicsComponent = CreateDefaultSubobject<UNetworkPhysicsComponent, UNetworkPhysicsComponent>(NetworkPhysicsComponentName);
NetworkPhysicsComponent->SetNetAddressable(); // Make DSO components net addressable
NetworkPhysicsComponent->SetIsReplicated(true);
}
}
// public
void UChaosVehicleMovementComponent::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
// Custom serialization goes here...
}
#if WITH_EDITOR
void UChaosVehicleMovementComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
// Trigger a runtime rebuild of the Chaos vehicle
FChaosVehicleManager::VehicleSetupTag++;
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif // WITH_EDITOR
void UChaosVehicleMovementComponent::SetUpdatedComponent(USceneComponent* NewUpdatedComponent)
{
//Skip PawnMovementComponent and simply set PawnOwner to null if we don't have a PawnActor as owner
UNavMovementComponent::SetUpdatedComponent(NewUpdatedComponent);
PawnOwner = NewUpdatedComponent ? Cast<APawn>(NewUpdatedComponent->GetOwner()) : nullptr;
if(USkeletalMeshComponent* SKC = Cast<USkeletalMeshComponent>(NewUpdatedComponent))
{
SKC->bLocalSpaceKinematics = true;
}
}
void UChaosVehicleMovementComponent::SetOverrideController(AController* InOverrideController)
{
OverrideController = InOverrideController;
}
bool UChaosVehicleMovementComponent::ShouldCreatePhysicsState() const
{
if (!IsRegistered() || IsBeingDestroyed())
{
return false;
}
// only create 'Physics' vehicle in game
UWorld* World = GetWorld();
if (World->IsGameWorld())
{
FPhysScene* PhysScene = World->GetPhysicsScene();
if (PhysScene && FChaosVehicleManager::GetVehicleManagerFromScene(PhysScene))
{
if (CanCreateVehicle())
{
return true;
}
}
}
return false;
}
bool UChaosVehicleMovementComponent::HasValidPhysicsState() const
{
return PVehicleOutput.IsValid();
}
bool UChaosVehicleMovementComponent::CanCreateVehicle() const
{
check(GetOwner());
FString ActorName = GetOwner()->GetName();
if (UpdatedComponent == NULL)
{
UE_LOG(LogVehicle, Warning, TEXT("Can't create vehicle %s (%s). UpdatedComponent is not set."), *ActorName, *GetPathName());
return false;
}
if (UpdatedPrimitive == NULL)
{
UE_LOG(LogVehicle, Warning, TEXT("Can't create vehicle %s (%s). UpdatedComponent is not a PrimitiveComponent."), *ActorName, *GetPathName());
return false;
}
return true;
}
void UChaosVehicleMovementComponent::OnCreatePhysicsState()
{
Super::OnCreatePhysicsState();
VehicleSetupTag = FChaosVehicleManager::VehicleSetupTag;
// only create Physics vehicle in game
UWorld* World = GetWorld();
if (World->IsGameWorld())
{
if (FPhysScene* PhysScene = World->GetPhysicsScene())
{
if (FChaosVehicleManager::GetVehicleManagerFromScene(PhysScene))
{
CreateVehicle();
FixupSkeletalMesh();
if (PVehicleOutput)
{
FChaosVehicleManager* VehicleManager = FChaosVehicleManager::GetVehicleManagerFromScene(PhysScene);
VehicleManager->AddVehicle(this);
}
}
if (bUsingNetworkPhysicsPrediction)
{
if(NetworkPhysicsComponent)
{
NetworkPhysicsComponent->CreateDataHistory<FPhysicsVehicleTraits>(this);
}
}
}
}
FBodyInstance* BodyInstance = nullptr;
if (USkeletalMeshComponent* SkeletalMesh = GetSkeletalMesh())
{
// this line was causing the server wheel positions to not be updated - this is already a user property so don't override it here and leave it to user to select the right option for their scenario
//SkeletalMesh->VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
BodyInstance = &SkeletalMesh->BodyInstance;
}
}
void UChaosVehicleMovementComponent::OnDestroyPhysicsState()
{
Super::OnDestroyPhysicsState();
if (PVehicleOutput.IsValid())
{
FChaosVehicleManager* VehicleManager = FChaosVehicleManager::GetVehicleManagerFromScene(GetWorld()->GetPhysicsScene());
VehicleManager->RemoveVehicle(this);
PVehicleOutput.Reset(nullptr);
if (UpdatedComponent)
{
UpdatedComponent->RecreatePhysicsState();
}
}
if (bUsingNetworkPhysicsPrediction && NetworkPhysicsComponent)
{
NetworkPhysicsComponent->RemoveDataHistory();
}
}
void UChaosVehicleMovementComponent::PreTickGT(float DeltaTime)
{
// movement updates and replication
if (PVehicleOutput && UpdatedComponent)
{
APawn* MyOwner = Cast<APawn>(UpdatedComponent->GetOwner());
if (MyOwner)
{
UpdateState(DeltaTime);
}
}
if (!bUsingNetworkPhysicsPrediction)
{
FControlInputs ControlInputs;
ControlInputs.ThrottleInput = ThrottleInput;
ControlInputs.BrakeInput = BrakeInput;
ControlInputs.SteeringInput = SteeringInput;
ControlInputs.HandbrakeInput = HandbrakeInput;
ControlInputs.RollInput = RollInput;
ControlInputs.PitchInput = PitchInput;
ControlInputs.YawInput = YawInput;
ControlInputs.ParkingEnabled = bParkEnabled;
ProcessSleeping(ControlInputs);
}
else
{
ProcessSleeping(VehicleSimulationPT->VehicleInputs);
}
if (VehicleSetupTag != FChaosVehicleManager::VehicleSetupTag)
{
RecreatePhysicsState();
}
}
void UChaosVehicleMovementComponent::StopMovementImmediately()
{
if (bUsingNetworkPhysicsPrediction)
{
return; // Called when player onwership changes I think - client restarts - resets velocity/forces bad for our case.
}
FBodyInstance* TargetInstance = GetBodyInstance();
if (TargetInstance)
{
// if start awake is false then setting the velocity (even to zero) causes particle to wake up.
if (TargetInstance->IsInstanceAwake())
{
TargetInstance->SetLinearVelocity(FVector::ZeroVector, false);
TargetInstance->SetAngularVelocityInRadians(FVector::ZeroVector, false);
TargetInstance->ClearForces();
TargetInstance->ClearTorques();
}
}
Super::StopMovementImmediately();
ClearAllInput();
}
// Input
void UChaosVehicleMovementComponent::SetThrottleInput(float Throttle)
{
RawThrottleInput = FMath::Clamp(Throttle, -1.0f, 1.0f);
}
void UChaosVehicleMovementComponent::IncreaseThrottleInput(float ThrottleDelta)
{
RawThrottleInput = FMath::Clamp(RawThrottleInput + ThrottleDelta, 0.f, 1.0f);
}
void UChaosVehicleMovementComponent::DecreaseThrottleInput(float ThrottleDelta)
{
RawThrottleInput = FMath::Clamp(RawThrottleInput - ThrottleDelta, 0.f, 1.0f);
}
void UChaosVehicleMovementComponent::SetBrakeInput(float Brake)
{
RawBrakeInput = FMath::Clamp(Brake, -1.0f, 1.0f);
}
void UChaosVehicleMovementComponent::SetSteeringInput(float Steering)
{
RawSteeringInput = FMath::Clamp(Steering, -1.0f, 1.0f);
}
void UChaosVehicleMovementComponent::SetPitchInput(float Pitch)
{
RawPitchInput = FMath::Clamp(Pitch, -1.0f, 1.0f);
}
void UChaosVehicleMovementComponent::SetRollInput(float Roll)
{
RawRollInput = FMath::Clamp(Roll, -1.0f, 1.0f);
}
void UChaosVehicleMovementComponent::SetYawInput(float Yaw)
{
RawYawInput = FMath::Clamp(Yaw, -1.0f, 1.0f);
}
void UChaosVehicleMovementComponent::SetHandbrakeInput(bool bNewHandbrake)
{
bRawHandbrakeInput = bNewHandbrake;
}
void UChaosVehicleMovementComponent::SetParked(bool bParked)
{
bParkEnabled = bParked;
}
void UChaosVehicleMovementComponent::SetSleeping(bool bEnableSleep)
{
if (bEnableSleep)
{
PutAllEnabledRigidBodiesToSleep();
VehicleState.bSleeping = true;
}
else
{
WakeAllEnabledRigidBodies();
VehicleState.bSleeping = false;
}
}
void UChaosVehicleMovementComponent::SetChangeUpInput(bool bNewGearUp)
{
bRawGearUpInput = bNewGearUp;
}
void UChaosVehicleMovementComponent::SetChangeDownInput(bool bNewGearDown)
{
bRawGearDownInput = bNewGearDown;
}
void UChaosVehicleMovementComponent::SetTargetGear(int32 GearNum, bool bImmediate)
{
if (PVehicleOutput && GearNum != PVehicleOutput->TargetGear)
{
FBodyInstance* TargetInstance = UpdatedPrimitive->GetBodyInstance();
if (TargetInstance)
{
FPhysicsCommand::ExecuteWrite(TargetInstance->GetPhysicsActor(), [&](const FPhysicsActorHandle& Chassis)
{
if (VehicleSimulationPT && VehicleSimulationPT->PVehicle && VehicleSimulationPT->PVehicle->HasTransmission())
{
VehicleSimulationPT->PVehicle->GetTransmission().SetGear(GearNum, bImmediate);
}
});
}
TargetGear = GearNum;
}
}
void UChaosVehicleMovementComponent::SetUseAutomaticGears(bool bUseAuto)
{
TransmissionType = bUseAuto ? Chaos::ETransmissionType::Automatic : Chaos::ETransmissionType::Manual;
}
void UChaosVehicleMovementComponent::SetRequiresControllerForInputs(bool bRequiresController)
{
bRequiresControllerForInputs = bRequiresController;
}
// Data access
int32 UChaosVehicleMovementComponent::GetCurrentGear() const
{
return (PVehicleOutput) ? PVehicleOutput->CurrentGear : 0;
}
int32 UChaosVehicleMovementComponent::GetTargetGear() const
{
return TargetGear;
}
bool UChaosVehicleMovementComponent::GetUseAutoGears() const
{
return (TransmissionType == Chaos::ETransmissionType::Automatic);
}
float UChaosVehicleMovementComponent::GetForwardSpeed() const
{
return VehicleState.ForwardSpeed;
}
float UChaosVehicleMovementComponent::GetForwardSpeedMPH() const
{
return Chaos::CmSToMPH(GetForwardSpeed());
}
bool UChaosVehicleMovementComponent::IsParked() const
{
return (bParkEnabled > 0);
}
// input related
float UChaosVehicleMovementComponent::CalcSteeringInput()
{
return RawSteeringInput;
}
void UChaosVehicleMovementComponent::CalcThrottleBrakeInput(float& ThrottleOut, float& BrakeOut)
{
BrakeOut = RawBrakeInput;
ThrottleOut = RawThrottleInput;
if (bReverseAsBrake)
{
if (RawThrottleInput > 0.f)
{
// car moving backwards but player wants to move forwards...
// if vehicle is moving backwards, then press brake
if (bThrottleAsBrake && VehicleState.ForwardSpeed < -WrongDirectionThreshold)
{
BrakeOut = 1.0f;
ThrottleOut = 0.0f;
}
}
else if (RawBrakeInput > 0.f)
{
// car moving forwards but player wants to move backwards...
// if vehicle is moving forwards, then press brake
if (VehicleState.ForwardSpeed > WrongDirectionThreshold)
{
BrakeOut = 1.0f;
ThrottleOut = 0.0f;
}
else if (GetTargetGear() < 0)
{
ThrottleOut = RawBrakeInput;
BrakeOut = 0.0f;
}
}
// straight reversing
else if (RawBrakeInput > 0.f && GetTargetGear() < 0)
{
ThrottleOut = RawBrakeInput;
}
else
{
// if player isn't pressing forward or backwards...
if (VehicleState.ForwardSpeed < StopThreshold && VehicleState.ForwardSpeed > -StopThreshold) //auto brake
{
// this code is causing the sleep state to toggle back off when the vehicle is stationary. It doesn't appear to be required anymore since the vehicle
// has another mechainsim to prevent it rolling on hills, which I believe was the initial intention of the code.
//BrakeOut = 1.f;
}
else
{
BrakeOut = IdleBrakeInput;
}
}
ThrottleOut = FMath::Clamp<float>(ThrottleOut, 0.0, 1.0);
BrakeOut = FMath::Clamp<float>(BrakeOut, 0.0, 1.0);
}
else
{
BrakeOut = FMath::Abs(RawBrakeInput);
// if player isn't pressing forward or backwards...
if (RawBrakeInput < SMALL_NUMBER && RawThrottleInput < SMALL_NUMBER)
{
if (VehicleState.ForwardSpeed < StopThreshold && VehicleState.ForwardSpeed > -StopThreshold) //auto brake
{
BrakeOut = 1.f;
}
}
}
}
float UChaosVehicleMovementComponent::CalcHandbrakeInput()
{
return (bRawHandbrakeInput == true) ? 1.0f : 0.0f;
}
float UChaosVehicleMovementComponent::CalcPitchInput()
{
return RawPitchInput;
}
float UChaosVehicleMovementComponent::CalcRollInput()
{
return RawRollInput;
}
float UChaosVehicleMovementComponent::CalcYawInput()
{
return RawYawInput;
}
void UChaosVehicleMovementComponent::ClearInput()
{
SteeringInput = 0.0f;
ThrottleInput = 0.0f;
BrakeInput = 0.0f;
HandbrakeInput = 0.0f;
PitchInput = 0.0f;
RollInput = 0.0f;
YawInput = 0.0f;
// Send this immediately.
int32 CurrentGear = 0;
if (PVehicleOutput)
{
CurrentGear = PVehicleOutput->CurrentGear;
}
AController* Controller = GetController();
if (Controller && Controller->IsLocalController() && PVehicleOutput)
{
if (!bUsingNetworkPhysicsPrediction)
{
ServerUpdateState(SteeringInput, ThrottleInput, BrakeInput, HandbrakeInput, CurrentGear, RollInput, PitchInput, YawInput);
}
}
}
void UChaosVehicleMovementComponent::ClearRawInput()
{
RawBrakeInput = 0.0f;
RawSteeringInput = 0.0f;
RawThrottleInput = 0.0f;
RawPitchInput = 0.0f;
RawRollInput = 0.0f;
RawYawInput = 0.0f;
bRawGearDownInput = false;
bRawGearUpInput = false;
bRawHandbrakeInput = false;
}
// Update
void UChaosVehicleMovementComponent::UpdateState(float DeltaTime)
{
// update input values
AController* Controller = GetController();
VehicleState.CaptureState(GetBodyInstance(), GetGravityZ(), DeltaTime);
VehicleState.NumWheelsOnGround = 0;
VehicleState.bVehicleInAir = false;
int NumWheels = 0;
if (PVehicleOutput)
{
for (int WheelIdx = 0; WheelIdx < PVehicleOutput->Wheels.Num(); WheelIdx++)
{
if (PVehicleOutput->Wheels[WheelIdx].InContact)
{
VehicleState.NumWheelsOnGround++;
}
else
{
VehicleState.bVehicleInAir = true;
}
NumWheels++;
}
}
VehicleState.bAllWheelsOnGround = (VehicleState.NumWheelsOnGround == NumWheels);
bool bProcessLocally = bRequiresControllerForInputs ? (Controller && Controller->IsLocalController()) : true;
// IsLocallyControlled will fail if the owner is unpossessed (i.e. Controller == nullptr);
// Should we remove input instead of relying on replicated state in that case?
if (bProcessLocally && PVehicleOutput)
{
if (bReverseAsBrake)
{
//for reverse as state we want to automatically shift between reverse and first gear
// Note: Removed this condition to support wheel spinning when rolling backwards with accelerator pressed, rather than braking
//if (FMath::Abs(GetForwardSpeed()) < WrongDirectionThreshold) //we only shift between reverse and first if the car is slow enough.
{
if (RawBrakeInput > KINDA_SMALL_NUMBER && GetCurrentGear() >= 0 && GetTargetGear() >= 0)
{
SetTargetGear(-1, true);
}
else if (RawThrottleInput > KINDA_SMALL_NUMBER && GetCurrentGear() <= 0 && GetTargetGear() <= 0)
{
SetTargetGear(1, true);
}
}
}
else
{
if (TransmissionType == Chaos::ETransmissionType::Automatic)
{
if (RawThrottleInput > KINDA_SMALL_NUMBER
&& GetCurrentGear() == 0
&& GetTargetGear() == 0)
{
SetTargetGear(1, true);
}
}
}
float ModifiedThrottle = 0.f;
float ModifiedBrake = 0.f;
CalcThrottleBrakeInput(ModifiedThrottle, ModifiedBrake);
// Apply Inputs locally
SteeringInput = SteeringInputRate.InterpInputValue(DeltaTime, SteeringInput, CalcSteeringInput());
ThrottleInput = ThrottleInputRate.InterpInputValue(DeltaTime, ThrottleInput, ModifiedThrottle);
BrakeInput = BrakeInputRate.InterpInputValue(DeltaTime, BrakeInput, ModifiedBrake);
PitchInput = PitchInputRate.InterpInputValue(DeltaTime, PitchInput, CalcPitchInput());
RollInput = RollInputRate.InterpInputValue(DeltaTime, RollInput, CalcRollInput());
YawInput = YawInputRate.InterpInputValue(DeltaTime, YawInput, CalcYawInput());
HandbrakeInput = HandbrakeInputRate.InterpInputValue(DeltaTime, HandbrakeInput, CalcHandbrakeInput());
if (!bUsingNetworkPhysicsPrediction)
{
// and send to server - (ServerUpdateState_Implementation below)
ServerUpdateState(SteeringInput, ThrottleInput, BrakeInput, HandbrakeInput, GetCurrentGear(), RollInput, PitchInput, YawInput);
}
if (PawnOwner && PawnOwner->IsNetMode(NM_Client))
{
MarkForClientCameraUpdate();
}
}
else if (!bUsingNetworkPhysicsPrediction)
{
// use replicated values for remote pawns
SteeringInput = ReplicatedState.SteeringInput;
ThrottleInput = ReplicatedState.ThrottleInput;
BrakeInput = ReplicatedState.BrakeInput;
PitchInput = ReplicatedState.PitchInput;
RollInput = ReplicatedState.RollInput;
YawInput = ReplicatedState.YawInput;
HandbrakeInput = ReplicatedState.HandbrakeInput;
SetTargetGear(ReplicatedState.TargetGear, true);
}
}
void UChaosVehicleMovementComponent::ProcessSleeping(const FControlInputs& ControlInputs)
{
FBodyInstance* TargetInstance = GetBodyInstance();
if (TargetInstance)
{
bool PrevSleeping = VehicleState.bSleeping;
VehicleState.bSleeping = !TargetInstance->IsInstanceAwake();
// The physics system has woken vehicle up due to a collision or something
if (PrevSleeping && !VehicleState.bSleeping)
{
VehicleState.SleepCounter = 0;
}
// If the vehicle is locally controlled, we want to use the raw inputs to determine sleep.
// However, if it's on the Server or is just being replicated to other Clients then there
// won't be any Raw input. In that case, use ReplicatedState instead.
// NOTE: Even on local clients, ReplicatedState will still be populated (the call to ServerUpdateState will
// be processed locally). Maybe we should *just* use ReplicatedState?
const AController* Controller = GetController();
const bool bIsLocallyControlled = (Controller && Controller->IsLocalController()) || bUsingNetworkPhysicsPrediction;
const bool bControlInputPressed = bIsLocallyControlled ? (ControlInputs.ThrottleInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ControlInputs.BrakeInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (FMath::Abs(ControlInputs.SteeringInput - PrevSteeringInput) >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ControlInputs.RollInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ControlInputs.PitchInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ControlInputs.YawInput >= GVehicleDebugParams.ControlInputWakeTolerance)
: (ReplicatedState.ThrottleInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ReplicatedState.BrakeInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (FMath::Abs(ReplicatedState.SteeringInput - PrevReplicatedSteeringInput) >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ReplicatedState.RollInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ReplicatedState.PitchInput >= GVehicleDebugParams.ControlInputWakeTolerance)
|| (ReplicatedState.YawInput >= GVehicleDebugParams.ControlInputWakeTolerance);
PrevSteeringInput = ControlInputs.SteeringInput;
PrevReplicatedSteeringInput = ReplicatedState.SteeringInput;
// Wake if control input pressed
if (bControlInputPressed || GVehicleDebugParams.DisableVehicleSleep)
{
VehicleState.bSleeping = false;
VehicleState.SleepCounter = 0;
SetSleeping(false);
}
else if (!GVehicleDebugParams.DisableVehicleSleep && !VehicleState.bSleeping && !bControlInputPressed && VehicleState.bAllWheelsOnGround && (VehicleState.VehicleUpAxis.Z > SleepSlopeLimit))
{
float SpeedSqr = TargetInstance->GetUnrealWorldVelocity().SizeSquared();
if (SpeedSqr < (SleepThreshold * SleepThreshold))
{
if (VehicleState.SleepCounter < GVehicleDebugParams.SleepCounterThreshold)
{
VehicleState.SleepCounter++;
}
else
{
VehicleState.bSleeping = true;
SetSleeping(true);
}
}
}
}
}
/// @cond DOXYGEN_WARNINGS
bool UChaosVehicleMovementComponent::ServerUpdateState_Validate(float InSteeringInput, float InThrottleInput, float InBrakeInput, float InHandbrakeInput, int32 InCurrentGear, float InRollInput, float InPitchInput, float InYawInput)
{
return true;
}
void UChaosVehicleMovementComponent::ServerUpdateState_Implementation(float InSteeringInput, float InThrottleInput, float InBrakeInput
, float InHandbrakeInput, int32 InCurrentGear, float InRollInput, float InPitchInput, float InYawInput)
{
SteeringInput = InSteeringInput;
ThrottleInput = InThrottleInput;
BrakeInput = InBrakeInput;
HandbrakeInput = InHandbrakeInput;
RollInput = InRollInput;
PitchInput = InPitchInput;
YawInput = InYawInput;
if (!GetUseAutoGears())
{
if (UWorld* World = GetWorld())
{
if (World->GetNetMode() == NM_DedicatedServer)
{
SetTargetGear(InCurrentGear, true);
}
}
}
// update state of inputs
ReplicatedState.SteeringInput = InSteeringInput;
ReplicatedState.ThrottleInput = InThrottleInput;
ReplicatedState.BrakeInput = InBrakeInput;
ReplicatedState.HandbrakeInput = InHandbrakeInput;
ReplicatedState.TargetGear = InCurrentGear;
ReplicatedState.RollInput = InRollInput;
ReplicatedState.PitchInput = InPitchInput;
ReplicatedState.YawInput = InYawInput;
}
/// @endcond
// Setup
AController* UChaosVehicleMovementComponent::GetController() const
{
if (OverrideController)
{
return OverrideController;
}
if (UpdatedComponent)
{
if (APawn* Pawn = Cast<APawn>(UpdatedComponent->GetOwner()))
{
return Pawn->GetController();
}
}
return nullptr;
}
APlayerController* UChaosVehicleMovementComponent::GetPlayerController() const
{
if (AController* Controller = GetController())
{
return Cast<APlayerController>(Controller);
}
return nullptr;
}
FBodyInstance* UChaosVehicleMovementComponent::GetBodyInstance()
{
return UpdatedPrimitive ? UpdatedPrimitive->GetBodyInstance() : nullptr;
}
const FBodyInstance* UChaosVehicleMovementComponent::GetBodyInstance() const
{
return UpdatedPrimitive ? UpdatedPrimitive->GetBodyInstance() : nullptr;
}
UMeshComponent* UChaosVehicleMovementComponent::GetMesh() const
{
return Cast<UMeshComponent>(UpdatedComponent);
}
USkeletalMeshComponent* UChaosVehicleMovementComponent::GetSkeletalMesh()
{
return Cast<USkeletalMeshComponent>(UpdatedComponent);
}
UStaticMeshComponent* UChaosVehicleMovementComponent::GetStaticMesh()
{
return Cast<UStaticMeshComponent>(UpdatedComponent);
}
FVector UChaosVehicleMovementComponent::LocateBoneOffset(const FName InBoneName, const FVector& InExtraOffset) const
{
FVector Offset = InExtraOffset;
if (InBoneName != NAME_None)
{
if (USkinnedMeshComponent* Mesh = Cast<USkinnedMeshComponent>(GetMesh()))
{
if (ensureMsgf(Mesh->GetSkinnedAsset(), TEXT("Expected skinned asset when locating bone offset. Asset might be missing references.")))
{
const FVector BonePosition = Mesh->GetSkinnedAsset()->GetComposedRefPoseMatrix(InBoneName).GetOrigin() * Mesh->GetRelativeScale3D();
//BonePosition is local for the root BONE of the skeletal mesh - however, we are using the Root BODY which may have its own transform, so we need to return the position local to the root BODY
FMatrix RootBodyMTX = FMatrix::Identity;
if (Mesh->GetBodyInstance() && Mesh->GetBodyInstance()->BodySetup.IsValid())
{
RootBodyMTX = Mesh->GetSkinnedAsset()->GetComposedRefPoseMatrix(Mesh->GetBodyInstance()->BodySetup->BoneName);
}
const FVector LocalBonePosition = RootBodyMTX.InverseTransformPosition(BonePosition);
Offset += LocalBonePosition;
}
}
}
return Offset;
}
void UChaosVehicleMovementComponent::CreateVehicle()
{
ComputeConstants();
{
if (CanCreateVehicle())
{
check(UpdatedComponent);
if (ensure(UpdatedPrimitive != nullptr))
{
TUniquePtr<Chaos::FSimpleWheeledVehicle> PVehicle = CreatePhysicsVehicle();
// Low level physics representation
SetupVehicle(PVehicle);
if (PVehicleOutput != nullptr)
{
PostSetupVehicle();
}
// Physics thread simulation class will now take ownership of the PVehicle pointer, we cannot safely use it anymore from the game thread
VehicleSimulationPT->Init(PVehicle);
}
VehicleState.CaptureState(GetBodyInstance(), GetGravityZ(), 0.01667f);
}
}
}
void UChaosVehicleMovementComponent::SetupVehicle(TUniquePtr<Chaos::FSimpleWheeledVehicle>& PVehicle)
{
Chaos::FSimpleAerodynamicsSim AerodynamicsSim(&GetAerodynamicsConfig());
PVehicle->Aerodynamics.Add(AerodynamicsSim);
for (FVehicleAerofoilConfig& AerofoilSetup : Aerofoils)
{
Chaos::FAerofoil AerofoilSim(&AerofoilSetup.GetPhysicsAerofoilConfig(*this));
PVehicle->Aerofoils.Add(AerofoilSim);
}
for (FVehicleThrustConfig& ThrustSetup : Thrusters)
{
Chaos::FSimpleThrustSim ThrustSim(&ThrustSetup.GetPhysicsThrusterConfig(*this));
PVehicle->Thrusters.Add(ThrustSim);
}
}
void UChaosVehicleMovementComponent::PostSetupVehicle()
{
}
void UChaosVehicleMovementComponent::SetupVehicleMass()
{
if (UpdatedPrimitive && UpdatedPrimitive->GetBodyInstance())
{
//Ensure that if mass properties ever change we set them back to our override
UpdatedPrimitive->GetBodyInstance()->OnRecalculatedMassProperties().AddUObject(this, &UChaosVehicleMovementComponent::UpdateMassProperties);
UpdateMassProperties(UpdatedPrimitive->GetBodyInstance());
}
}
void UChaosVehicleMovementComponent::UpdateMassProperties(FBodyInstance* BodyInstance)
{
if (BodyInstance && FPhysicsInterface::IsValid(BodyInstance->GetPhysicsActor()) && FPhysicsInterface::IsRigidBody(BodyInstance->GetPhysicsActor()))
{
FPhysicsCommand::ExecuteWrite(BodyInstance->GetPhysicsActor(), [&](const FPhysicsActorHandle& Actor)
{
const float MassRatio = this->Mass > 0.0f ? this->Mass / BodyInstance->GetBodyMass() : 1.0f;
FVector InertiaTensor = BodyInstance->GetBodyInertiaTensor();
InertiaTensor.X *= this->InertiaTensorScale.X * MassRatio;
InertiaTensor.Y *= this->InertiaTensorScale.Y * MassRatio;
InertiaTensor.Z *= this->InertiaTensorScale.Z * MassRatio;
if (bEnableCenterOfMassOverride)
{
FTransform COMTransform = FPhysicsInterface::GetComTransformLocal_AssumesLocked(Actor);
COMTransform.SetTranslation(CenterOfMassOverride + BodyInstance->COMNudge);
FPhysicsInterface::SetComLocalPose_AssumesLocked(Actor, COMTransform);
}
FPhysicsInterface::SetMassSpaceInertiaTensor_AssumesLocked(Actor, InertiaTensor);
FPhysicsInterface::SetMass_AssumesLocked(Actor, this->Mass);
});
}
}
void UChaosVehicleMovementComponent::ComputeConstants()
{
DragArea = ChassisWidth * ChassisHeight;
}
// Debug
void UChaosVehicleMovementComponent::ShowDebugInfo(AHUD* HUD, UCanvas* Canvas, const FDebugDisplayInfo& DisplayInfo, float& YL, float& YPos)
{
static FName NAME_Vehicle = FName(TEXT("Vehicle"));
if (Canvas && HUD->ShouldDisplayDebug(NAME_Vehicle))
{
if (APlayerController* Controller = Cast<APlayerController>(GetController()))
{
if (Controller->IsLocalController())
{
DrawDebug(Canvas, YL, YPos);
}
}
}
}
void UChaosVehicleMovementComponent::DrawDebug(UCanvas* Canvas, float& YL, float& YPos)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
FBodyInstance* TargetInstance = GetBodyInstance();
if (PVehicleOutput == nullptr || TargetInstance == nullptr)
{
return;
}
UFont* RenderFont = GEngine->GetMediumFont();
// draw general vehicle data
{
Canvas->SetDrawColor(FColor::White);
YPos += 16;
float ForwardSpeedKmH = Chaos::CmSToKmH(GetForwardSpeed());
float ForwardSpeedMPH = Chaos::CmSToMPH(GetForwardSpeed());
float ForwardSpeedMSec = Chaos::CmToM(GetForwardSpeed());
if (TargetInstance)
{
FVector FinalCOM = TargetInstance->GetMassSpaceLocal().GetTranslation();
FVector OffsetCOM = TargetInstance->COMNudge;
FVector BaseCOM = FinalCOM - TargetInstance->COMNudge;
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Mass (Kg): %.1f"), TargetInstance->GetBodyMass()), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Local COM : %s"), *FinalCOM.ToString()), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("[COM Base : %s COM Offset : %s]"), *BaseCOM.ToString(), *OffsetCOM.ToString()), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Inertia : %s"), *TargetInstance->GetBodyInertiaTensor().ToString()), 4, YPos);
}
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Awake %d (Vehicle Sleep %d)"), TargetInstance->IsInstanceAwake(), VehicleState.bSleeping), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Speed (km/h): %.1f (MPH): %.1f (m/s): %.1f"), ForwardSpeedKmH, ForwardSpeedMPH, ForwardSpeedMSec), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Acceleration (m/s-2): %.1f"), Chaos::CmToM(VehicleState.LocalAcceleration.X)), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("GForce : %2.1f"), VehicleState.LocalGForce.X), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Steering: %.1f (RAW %.1f)"), SteeringInput, RawSteeringInput), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Throttle: %.1f (RAW %.1f)"), ThrottleInput, RawThrottleInput), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Brake: %.1f (RAW %.1f)"), BrakeInput, RawBrakeInput), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Roll: %.1f (RAW %.1f)"), RollInput, RawRollInput), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Pitch: %.1f (RAW %.1f)"), PitchInput, RawPitchInput), 4, YPos);
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Yaw: %.1f (RAW %.1f)"), YawInput, RawYawInput), 4, YPos);
FString GearState = GetUseAutoGears() ? "Automatic" : "Manual";
YPos += Canvas->DrawText(RenderFont, FString::Printf(TEXT("Gears: %s"), *GearState), 4, YPos);
}
#endif
}
/// @cond DOXYGEN_WARNINGS
void UChaosVehicleMovementComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UChaosVehicleMovementComponent, ReplicatedState);
DOREPLIFETIME(UChaosVehicleMovementComponent, OverrideController);
}
/// @endcond
void UChaosVehicleMovementComponent::DrawLine2D(UCanvas* Canvas, const FVector2D& StartPos, const FVector2D& EndPos, FColor Color, float Thickness)
{
if (Canvas)
{
FCanvasLineItem LineItem(StartPos, EndPos);
LineItem.SetColor(Color);
LineItem.LineThickness = Thickness;
Canvas->DrawItem(LineItem);
}
}
TUniquePtr<Chaos::FSimpleWheeledVehicle> UChaosVehicleMovementComponent::CreatePhysicsVehicle()
{
PVehicleOutput = MakeUnique<FPhysicsVehicleOutput>(); // create physics output container
return MakeUnique<Chaos::FSimpleWheeledVehicle>(); // create physics sim
}
void FVehicleAerofoilConfig::FillAerofoilSetup(const UChaosVehicleMovementComponent& MovementComponent)
{
PAerofoilConfig.Type = (Chaos::EAerofoilType)(this->AerofoilType);
PAerofoilConfig.Offset = MovementComponent.LocateBoneOffset(this->BoneName, this->Offset);
PAerofoilConfig.UpAxis = this->UpAxis;
PAerofoilConfig.Area = this->Area;
PAerofoilConfig.Camber = this->Camber;
PAerofoilConfig.MaxControlAngle = this->MaxControlAngle;
PAerofoilConfig.StallAngle = this->StallAngle;
PAerofoilConfig.LiftMultiplier = this->LiftMultiplier;
PAerofoilConfig.DragMultiplier = this->DragMultiplier;
}
void FVehicleThrustConfig::FillThrusterSetup(const UChaosVehicleMovementComponent& MovementComponent)
{
PThrusterConfig.Type = (Chaos::EThrustType)(this->ThrustType);
PThrusterConfig.Offset = MovementComponent.LocateBoneOffset(this->BoneName, this->Offset);
PThrusterConfig.Axis = this->ThrustAxis;
// PThrusterConfig.ThrustCurve = this->ThrustCurve;
PThrusterConfig.MaxThrustForce = Chaos::MToCm(this->MaxThrustForce);
PThrusterConfig.MaxControlAngle = this->MaxControlAngle;
}
TUniquePtr<FChaosVehicleAsyncInput> UChaosVehicleMovementComponent::SetCurrentAsyncInputOutput(int32 InputIdx, FChaosVehicleManagerAsyncOutput* CurOutput, FChaosVehicleManagerAsyncOutput* NextOutput, float Alpha, int32 VehicleManagerTimestamp)
{
TUniquePtr<FChaosVehicleAsyncInput> CurInput = MakeUnique<FChaosVehicleAsyncInput>();
SetCurrentAsyncInputOutputInternal(CurInput.Get(), InputIdx, CurOutput, NextOutput, Alpha, VehicleManagerTimestamp);
return CurInput;
}
/************************************************************************/
/* Setup the current async I/O data */
/************************************************************************/
void UChaosVehicleMovementComponent::SetCurrentAsyncInputOutputInternal(FChaosVehicleAsyncInput* CurInput, int32 InputIdx, FChaosVehicleManagerAsyncOutput* CurOutput, FChaosVehicleManagerAsyncOutput* NextOutput, float Alpha, int32 VehicleManagerTimestamp)
{
ensure(CurAsyncInput == nullptr); //should be reset after it was filled
ensure(CurAsyncOutput == nullptr); //should get reset after update is done
CurAsyncInput = CurInput;
CurAsyncInput->Vehicle = this;
CurAsyncType = CurInput->Type;
NextAsyncOutput = nullptr;
OutputInterpAlpha = 0.f;
// We need to find our vehicle in the output given
if (CurOutput)
{
for (int32 PendingOutputIdx = 0; PendingOutputIdx < OutputsWaitingOn.Num(); ++PendingOutputIdx)
{
// Found the correct pending output, use index to get the vehicle.
if (OutputsWaitingOn[PendingOutputIdx].Timestamp == CurOutput->Timestamp)
{
const int32 VehicleIdx = OutputsWaitingOn[PendingOutputIdx].Idx;
FChaosVehicleAsyncOutput* VehicleOutput = CurOutput->VehicleOutputs[VehicleIdx].Get();
if (VehicleOutput && VehicleOutput->bValid && VehicleOutput->Type == CurAsyncType)
{
CurAsyncOutput = VehicleOutput;
if (NextOutput && NextOutput->Timestamp == CurOutput->Timestamp)
{
// This can occur when substepping - in this case, VehicleOutputs will be in the same order in NextOutput and CurOutput.
FChaosVehicleAsyncOutput* VehicleNextOutput = NextOutput->VehicleOutputs[VehicleIdx].Get();
if (VehicleNextOutput && VehicleNextOutput->bValid && VehicleNextOutput->Type == CurAsyncType)
{
NextAsyncOutput = VehicleNextOutput;
OutputInterpAlpha = Alpha;
}
}
}
// these are sorted by timestamp, we are using latest, so remove entries that came before it.
TArray<FAsyncOutputWrapper> NewOutputsWaitingOn;
for (int32 CopyIndex = PendingOutputIdx; CopyIndex < OutputsWaitingOn.Num(); ++CopyIndex)
{
NewOutputsWaitingOn.Add(OutputsWaitingOn[CopyIndex]);
}
OutputsWaitingOn = MoveTemp(NewOutputsWaitingOn);
break;
}
}
}
if (NextOutput && CurOutput)
{
if (NextOutput->Timestamp != CurOutput->Timestamp)
{
// NextOutput and CurOutput occurred in different steps, so we need to search for our specific vehicle.
for (int32 PendingOutputIdx = 0; PendingOutputIdx < OutputsWaitingOn.Num(); ++PendingOutputIdx)
{
// Found the correct pending output, use index to get the vehicle.
if (OutputsWaitingOn[PendingOutputIdx].Timestamp == NextOutput->Timestamp)
{
FChaosVehicleAsyncOutput* VehicleOutput = NextOutput->VehicleOutputs[OutputsWaitingOn[PendingOutputIdx].Idx].Get();
if (VehicleOutput && VehicleOutput->bValid && VehicleOutput->Type == CurAsyncType)
{
NextAsyncOutput = VehicleOutput;
OutputInterpAlpha = Alpha;
}
break;
}
}
}
}
FAsyncOutputWrapper& NewOutput = OutputsWaitingOn.AddDefaulted_GetRef();
NewOutput.Timestamp = VehicleManagerTimestamp;
NewOutput.Idx = InputIdx;
}
// ---- ASYNC ----
/************************************************************************/
/* PASS ANY INUTS TO THE PHYSICS THREAD SIMULATION IN HERE */
/************************************************************************/
void UChaosVehicleMovementComponent::Update(float DeltaTime)
{
if (CurAsyncInput)
{
if (const FBodyInstance* BodyInstance = GetBodyInstance())
{
if (auto Handle = BodyInstance->GetPhysicsActor())
{
CurAsyncInput->Proxy = Handle; // vehicles are never static
FChaosVehicleAsyncInput* AsyncInput = static_cast<FChaosVehicleAsyncInput*>(CurAsyncInput);
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.ThrottleInput = ThrottleInputRate.CalcControlFunction(ThrottleInput);
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.BrakeInput = BrakeInputRate.CalcControlFunction(BrakeInput);
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.SteeringInput = SteeringInputRate.CalcControlFunction(SteeringInput);
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.HandbrakeInput = HandbrakeInput;
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.RollInput = RollInputRate.CalcControlFunction(RollInput);
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.PitchInput = PitchInputRate.CalcControlFunction(PitchInput);
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.YawInput = YawInputRate.CalcControlFunction(YawInput);
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.GearUpInput = bRawGearUpInput;
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.GearDownInput = bRawGearDownInput;
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.TransmissionType = TransmissionType;
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.ParkingEnabled = bParkEnabled;
// debug feature to limit the vehicles top speed
if ((GVehicleDebugParams.SetMaxMPH > 0.f) && (FMath::Abs(ThrottleInput) > 0.0f) && FMath::Abs(GetForwardSpeedMPH()) >= GVehicleDebugParams.SetMaxMPH)
{
AsyncInput->PhysicsInputs.NetworkInputs.VehicleInputs.ThrottleInput = 0.1f;
}
AsyncInput->PhysicsInputs.GravityZ = GetGravityZ();
}
}
}
}
void UChaosVehicleMovementComponent::ResetVehicleState()
{
ClearRawInput();
StopMovementImmediately();
OnDestroyPhysicsState();
OnCreatePhysicsState();
// Shift into neutral, force the local copy of target gear to be correct
SetTargetGear(0, true);
TargetGear = 0;
}
void UChaosVehicleMovementComponent::FinalizeSimCallbackData(FChaosVehicleManagerAsyncInput& Input)
{
CurAsyncInput = nullptr;
CurAsyncOutput = nullptr;
}
/***************************************************************************/
/* READ OUTPUT DATA - Access the async output data from the Physics Thread */
/***************************************************************************/
void UChaosVehicleMovementComponent::ParallelUpdate(float DeltaSeconds)
{
if (const FChaosVehicleAsyncOutput* CurrentOutput = static_cast<FChaosVehicleAsyncOutput*>(CurAsyncOutput))
{
if (CurrentOutput->bValid && PVehicleOutput)
{
// TODO: It would be nicer to go through CurAsyncOutput rather
// than copying into the vehicle, think about non-async path
PVehicleOutput->CurrentGear = CurAsyncOutput->VehicleSimOutput.CurrentGear;
PVehicleOutput->TargetGear = CurAsyncOutput->VehicleSimOutput.TargetGear;
// WHEN RUNNING WITH ASYNC ON & FIXED TIMESTEP THEN WE NEED TO INTERPOLATE BETWEEN THE CURRENT AND NEXT OUTPUT RESULTS
if (const FChaosVehicleAsyncOutput* NextOutput = static_cast<FChaosVehicleAsyncOutput*>(NextAsyncOutput))
{
PVehicleOutput->EngineRPM = FMath::Lerp(CurAsyncOutput->VehicleSimOutput.EngineRPM, NextAsyncOutput->VehicleSimOutput.EngineRPM, OutputInterpAlpha);
PVehicleOutput->EngineTorque = FMath::Lerp(CurAsyncOutput->VehicleSimOutput.EngineTorque, NextAsyncOutput->VehicleSimOutput.EngineTorque, OutputInterpAlpha);
PVehicleOutput->TransmissionRPM = FMath::Lerp(CurAsyncOutput->VehicleSimOutput.TransmissionRPM, NextAsyncOutput->VehicleSimOutput.TransmissionRPM, OutputInterpAlpha);
PVehicleOutput->TransmissionTorque = FMath::Lerp(CurAsyncOutput->VehicleSimOutput.TransmissionTorque, NextAsyncOutput->VehicleSimOutput.TransmissionTorque, OutputInterpAlpha);
for (int WheelIdx = 0; WheelIdx < CurrentOutput->VehicleSimOutput.Wheels.Num(); WheelIdx++)
{
const FWheelsOutput& Current = CurrentOutput->VehicleSimOutput.Wheels[WheelIdx];
const FWheelsOutput& Next = NextOutput->VehicleSimOutput.Wheels[WheelIdx];
PVehicleOutput->Wheels[WheelIdx].InContact = Current.InContact;
PVehicleOutput->Wheels[WheelIdx].SteeringAngle = FMath::Lerp(Current.SteeringAngle, Next.SteeringAngle, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].WheelRadius = FMath::Lerp(Current.WheelRadius, Next.WheelRadius, OutputInterpAlpha);
float DeltaAngle = FMath::FindDeltaAngleRadians(Current.AngularPosition, Next.AngularPosition);
PVehicleOutput->Wheels[WheelIdx].AngularPosition = Current.AngularPosition + DeltaAngle * OutputInterpAlpha;
PVehicleOutput->Wheels[WheelIdx].AngularVelocity = FMath::Lerp(Current.AngularVelocity, Next.AngularVelocity, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].LateralAdhesiveLimit = FMath::Lerp(Current.LateralAdhesiveLimit, Next.LateralAdhesiveLimit, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].LongitudinalAdhesiveLimit = FMath::Lerp(Current.LongitudinalAdhesiveLimit, Next.LongitudinalAdhesiveLimit, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].bIsSlipping = Current.bIsSlipping;
PVehicleOutput->Wheels[WheelIdx].SlipMagnitude = FMath::Lerp(Current.SlipMagnitude, Next.SlipMagnitude, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].bIsSkidding = Current.bIsSkidding;
PVehicleOutput->Wheels[WheelIdx].SkidMagnitude = FMath::Lerp(Current.SkidMagnitude, Next.SkidMagnitude, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].SkidNormal = FMath::Lerp(Current.SkidNormal, Next.SkidNormal, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].SlipAngle = FMath::Lerp(Current.SlipAngle, Next.SlipAngle, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].SuspensionOffset = FMath::Lerp(Current.SuspensionOffset, Next.SuspensionOffset, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].SpringForce = FMath::Lerp(Current.SpringForce, Next.SpringForce, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].NormalizedSuspensionLength = FMath::Lerp(Current.NormalizedSuspensionLength, Next.NormalizedSuspensionLength, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].DriveTorque = FMath::Lerp(Current.DriveTorque, Next.DriveTorque, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].BrakeTorque = FMath::Lerp(Current.BrakeTorque, Next.BrakeTorque, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].bABSActivated = Current.bABSActivated;
PVehicleOutput->Wheels[WheelIdx].bBlockingHit = Current.bBlockingHit;
PVehicleOutput->Wheels[WheelIdx].ImpactPoint = FMath::Lerp(Current.ImpactPoint, Next.ImpactPoint, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].HitLocation = FMath::Lerp(Current.HitLocation, Next.HitLocation, OutputInterpAlpha);
PVehicleOutput->Wheels[WheelIdx].PhysMaterial = Current.PhysMaterial;
}
}
else // WHEN ASYNC IS OFF IT STILL GENERATES THE ASYNC CALLBACK BUT THERE IS ONLY EVER THE CURRENT AND NO NEXT OUTPUT TO INTERPOLATE BETWEEN
{
PVehicleOutput->EngineRPM = CurAsyncOutput->VehicleSimOutput.EngineRPM;
PVehicleOutput->EngineTorque = CurAsyncOutput->VehicleSimOutput.EngineTorque;
PVehicleOutput->TransmissionRPM = CurAsyncOutput->VehicleSimOutput.TransmissionRPM;
PVehicleOutput->TransmissionTorque = CurAsyncOutput->VehicleSimOutput.TransmissionTorque;
for (int WheelIdx = 0; WheelIdx < CurrentOutput->VehicleSimOutput.Wheels.Num(); WheelIdx++)
{
const FWheelsOutput& Current = CurrentOutput->VehicleSimOutput.Wheels[WheelIdx];
PVehicleOutput->Wheels[WheelIdx].InContact = Current.InContact;
PVehicleOutput->Wheels[WheelIdx].SteeringAngle = Current.SteeringAngle;
PVehicleOutput->Wheels[WheelIdx].WheelRadius = Current.WheelRadius;
PVehicleOutput->Wheels[WheelIdx].AngularPosition = Current.AngularPosition;
PVehicleOutput->Wheels[WheelIdx].AngularVelocity = Current.AngularVelocity;
PVehicleOutput->Wheels[WheelIdx].LateralAdhesiveLimit = Current.LateralAdhesiveLimit;
PVehicleOutput->Wheels[WheelIdx].LongitudinalAdhesiveLimit = Current.LongitudinalAdhesiveLimit;
PVehicleOutput->Wheels[WheelIdx].bIsSlipping = Current.bIsSlipping;
PVehicleOutput->Wheels[WheelIdx].SlipMagnitude = Current.SlipMagnitude;
PVehicleOutput->Wheels[WheelIdx].bIsSkidding = Current.bIsSkidding;
PVehicleOutput->Wheels[WheelIdx].SkidMagnitude = Current.SkidMagnitude;
PVehicleOutput->Wheels[WheelIdx].SkidNormal = Current.SkidNormal;
PVehicleOutput->Wheels[WheelIdx].SlipAngle = Current.SlipAngle;
PVehicleOutput->Wheels[WheelIdx].SuspensionOffset = Current.SuspensionOffset;
PVehicleOutput->Wheels[WheelIdx].SpringForce = Current.SpringForce;
PVehicleOutput->Wheels[WheelIdx].NormalizedSuspensionLength = Current.NormalizedSuspensionLength;
PVehicleOutput->Wheels[WheelIdx].DriveTorque = Current.DriveTorque;
PVehicleOutput->Wheels[WheelIdx].BrakeTorque = Current.BrakeTorque;
PVehicleOutput->Wheels[WheelIdx].bABSActivated = Current.bABSActivated;
PVehicleOutput->Wheels[WheelIdx].bBlockingHit = Current.bBlockingHit;
PVehicleOutput->Wheels[WheelIdx].ImpactPoint = Current.ImpactPoint;
PVehicleOutput->Wheels[WheelIdx].HitLocation = Current.HitLocation;
PVehicleOutput->Wheels[WheelIdx].PhysMaterial = Current.PhysMaterial;
}
}
}
}
}
void UChaosVehicleMovementComponent::GetBaseSnapshot(FBaseSnapshotData& SnapshotOut) const
{
if (const FBodyInstance* TargetInstance = GetBodyInstance())
{
SnapshotOut.Transform = TargetInstance->GetUnrealWorldTransform();
SnapshotOut.LinearVelocity = TargetInstance->GetUnrealWorldVelocity();
SnapshotOut.AngularVelocity = TargetInstance->GetUnrealWorldAngularVelocityInRadians();
}
}
void UChaosVehicleMovementComponent::SetBaseSnapshot(const FBaseSnapshotData& SnapshotIn)
{
if (FBodyInstance* TargetInstance = GetBodyInstance())
{
TargetInstance->SetLinearVelocity(SnapshotIn.LinearVelocity, false);
TargetInstance->SetAngularVelocityInRadians(SnapshotIn.AngularVelocity, false);
TargetInstance->SetBodyTransform(SnapshotIn.Transform, ETeleportType::TeleportPhysics);
}
}
void UChaosVehicleMovementComponent::WakeAllEnabledRigidBodies()
{
if (USkeletalMeshComponent* Mesh = GetSkeletalMesh())
{
for (int32 i = 0; i < Mesh->Bodies.Num(); i++)
{
FBodyInstance* BI = Mesh->Bodies[i];
check(BI);
if (!BI->IsPhysicsDisabled() && BI->IsNonKinematic())
{
BI->WakeInstance();
}
}
}
}
void UChaosVehicleMovementComponent::PutAllEnabledRigidBodiesToSleep()
{
if (USkeletalMeshComponent* Mesh = GetSkeletalMesh())
{
for (int32 i = 0; i < Mesh->Bodies.Num(); i++)
{
FBodyInstance* BI = Mesh->Bodies[i];
check(BI);
if (!BI->IsPhysicsDisabled() && BI->IsNonKinematic())
{
BI->PutInstanceToSleep();
}
}
}
}
#undef LOCTEXT_NAMESPACE
#if VEHICLE_DEBUGGING_ENABLED
UE_ENABLE_OPTIMIZATION
#endif