Support for N Wheeled vehicles

This commit is contained in:
Jacopo Bartiromo 2022-01-28 14:52:45 +01:00 committed by Jacopo Bartiromo
parent 207ac8c78c
commit 0a9f67c6c3
8 changed files with 1137 additions and 236 deletions

View File

@ -93,7 +93,8 @@ public class Carla : ModuleRules
"PhysXVehicles",
"PhysXVehicleLib",
"Slate",
"SlateCore"
"SlateCore",
"PhysicsCore"
// ... add private dependencies that you statically link with here ...
}
);

View File

@ -0,0 +1,76 @@
// Copyright (c) 2021 Computer Vision Center (CVC) at the Universitat Autonoma
// de Barcelona (UAB).
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.
#pragma once
PRAGMA_DISABLE_DEPRECATION_WARNINGS
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "MovementComponents/BaseCarlaMovementComponent.h"
#include "DisplayDebugHelpers.h"
#include "CarlaSimpleVehicle.generated.h"
UCLASS()
class CARLA_API ACarlaSimpleVehicle : public APawn
{
GENERATED_BODY()
public:
ACarlaSimpleVehicle(const FObjectInitializer& ObjectInitializer);
~ACarlaSimpleVehicle();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
/** The main skeletal mesh associated with this Vehicle */
UPROPERTY(Category = Vehicle, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
class USkeletalMeshComponent* Mesh;
/** vehicle simulation component */
UPROPERTY(Category = Vehicle, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
class UWheeledVehicleMovementComponent* VehicleMovement;
public:
/** Name of the MeshComponent. Use this name if you want to prevent creation of the component (with ObjectInitializer.DoNotCreateDefaultSubobject). */
static FName VehicleMeshComponentName;
/** Name of the VehicleMovement. Use this name if you want to use a different class (with ObjectInitializer.SetDefaultSubobjectClass). */
static FName VehicleMovementComponentName;
/** Util to get the wheeled vehicle movement component */
class UWheeledVehicleMovementComponent* GetVehicleMovementComponent() const;
//~ Begin AActor Interface
virtual void DisplayDebug(class UCanvas* Canvas, const FDebugDisplayInfo& DebugDisplay, float& YL, float& YPos) override;
//~ End Actor Interface
/** Returns Mesh subobject **/
class USkeletalMeshComponent* GetMesh() const { return Mesh; }
/** Returns VehicleMovement subobject **/
class UWheeledVehicleMovementComponent* GetVehicleMovement() const { return VehicleMovement; }
private:
UPROPERTY(Category = "CARLA Simple Wheeled Vehicle", VisibleAnywhere)
bool bPhysicsEnabled = true;
// Small workarround to allow optional CarSim plugin usage
UPROPERTY(Category = "CARLA Simple Wheeled Vehicle", VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
UBaseCarlaMovementComponent* BaseMovementComponent = nullptr;
};
PRAGMA_ENABLE_DEPRECATION_WARNINGS

View File

@ -74,6 +74,26 @@ void ACarlaWheeledVehicle::SetWheelCollision(UWheeledVehicleMovementComponent4W
}
void ACarlaWheeledVehicle::SetWheelCollisionNW(UWheeledVehicleMovementComponentNW *VehicleNW,
const FVehiclePhysicsControl &PhysicsControl ) {
#ifdef WHEEL_SWEEP_ENABLED
const bool IsEqual = VehicleNW->UseSweepWheelCollision == PhysicsControl.UseSweepWheelCollision;
if (IsEqual)
return;
VehicleNW->UseSweepWheelCollision = PhysicsControl.UseSweepWheelCollision;
#else
if (PhysicsControl.UseSweepWheelCollision)
UE_LOG(LogCarla, Warning, TEXT("Error: Sweep for wheel collision is not available. \
Make sure you have installed the required patch.") );
#endif
}
void ACarlaWheeledVehicle::BeginPlay()
{
Super::BeginPlay();
@ -136,15 +156,17 @@ void ACarlaWheeledVehicle::BeginPlay()
float FrictionScale = 3.5f;
UWheeledVehicleMovementComponent4W *Vehicle4W = Cast<UWheeledVehicleMovementComponent4W>(
GetVehicleMovementComponent());
check(Vehicle4W != nullptr);
UWheeledVehicleMovementComponent* MovementComponent = GetVehicleMovementComponent();
if (MovementComponent)
{
check(MovementComponent != nullptr);
// Setup Tire Configs with default value. This is needed to avoid getting
// friction values of previously created TireConfigs for the same vehicle
// blueprint.
TArray<float> OriginalFrictions;
OriginalFrictions.Init(FrictionScale, Vehicle4W->Wheels.Num());
OriginalFrictions.Init(FrictionScale, MovementComponent->Wheels.Num());
SetWheelsFrictionScale(OriginalFrictions);
// Check if it overlaps with a Friction trigger, if so, update the friction
@ -161,18 +183,18 @@ void ACarlaWheeledVehicle::BeginPlay()
}
// Set the friction scale to Wheel CDO and update wheel setups
TArray<FWheelSetup> NewWheelSetups = Vehicle4W->WheelSetups;
TArray<FWheelSetup> NewWheelSetups = MovementComponent->WheelSetups;
for (const auto &WheelSetup : NewWheelSetups)
{
UVehicleWheel *Wheel = WheelSetup.WheelClass.GetDefaultObject();
check(Wheel != nullptr);
}
Vehicle4W->WheelSetups = NewWheelSetups;
MovementComponent->WheelSetups = NewWheelSetups;
LastPhysicsControl = GetVehiclePhysicsControl();
}
}
void ACarlaWheeledVehicle::AdjustVehicleBounds()
{
@ -282,39 +304,48 @@ void ACarlaWheeledVehicle::SetHandbrakeInput(const bool Value)
TArray<float> ACarlaWheeledVehicle::GetWheelsFrictionScale()
{
UWheeledVehicleMovementComponent4W *Vehicle4W = Cast<UWheeledVehicleMovementComponent4W>(
GetVehicleMovement());
check(Vehicle4W != nullptr);
UWheeledVehicleMovementComponent* Movement = GetVehicleMovement();
TArray<float> WheelsFrictionScale;
for (auto &Wheel : Vehicle4W->Wheels)
if (Movement)
{
check(Movement != nullptr);
for (auto &Wheel : Movement->Wheels)
{
WheelsFrictionScale.Add(Wheel->TireConfig->GetFrictionScale());
}
}
return WheelsFrictionScale;
}
void ACarlaWheeledVehicle::SetWheelsFrictionScale(TArray<float> &WheelsFrictionScale)
{
UWheeledVehicleMovementComponent4W *Vehicle4W = Cast<UWheeledVehicleMovementComponent4W>(
GetVehicleMovement());
check(Vehicle4W != nullptr);
check(Vehicle4W->Wheels.Num() == WheelsFrictionScale.Num());
for (int32 i = 0; i < Vehicle4W->Wheels.Num(); ++i)
UWheeledVehicleMovementComponent* Movement = GetVehicleMovement();
if (Movement)
{
Vehicle4W->Wheels[i]->TireConfig->SetFrictionScale(WheelsFrictionScale[i]);
check(Movement != nullptr);
check(Movement->Wheels.Num() == WheelsFrictionScale.Num());
for (int32 i = 0; i < Movement->Wheels.Num(); ++i)
{
Movement->Wheels[i]->TireConfig->SetFrictionScale(WheelsFrictionScale[i]);
}
}
}
FVehiclePhysicsControl ACarlaWheeledVehicle::GetVehiclePhysicsControl() const
{
FVehiclePhysicsControl PhysicsControl;
if (!bIsSixWVehicle) {
UWheeledVehicleMovementComponent4W *Vehicle4W = Cast<UWheeledVehicleMovementComponent4W>(
GetVehicleMovement());
check(Vehicle4W != nullptr);
FVehiclePhysicsControl PhysicsControl;
// Engine Setup
PhysicsControl.TorqueCurve = Vehicle4W->EngineSetup.TorqueCurve.EditorCurveData;
PhysicsControl.MaxRPM = Vehicle4W->EngineSetup.MaxRPM;
@ -387,6 +418,85 @@ FVehiclePhysicsControl ACarlaWheeledVehicle::GetVehiclePhysicsControl() const
PhysicsControl.Wheels = Wheels;
} else {
UWheeledVehicleMovementComponentNW *VehicleNW = Cast<UWheeledVehicleMovementComponentNW>(
GetVehicleMovement());
check(VehicleNW != nullptr);
// Engine Setup
PhysicsControl.TorqueCurve = VehicleNW->EngineSetup.TorqueCurve.EditorCurveData;
PhysicsControl.MaxRPM = VehicleNW->EngineSetup.MaxRPM;
PhysicsControl.MOI = VehicleNW->EngineSetup.MOI;
PhysicsControl.DampingRateFullThrottle = VehicleNW->EngineSetup.DampingRateFullThrottle;
PhysicsControl.DampingRateZeroThrottleClutchEngaged =
VehicleNW->EngineSetup.DampingRateZeroThrottleClutchEngaged;
PhysicsControl.DampingRateZeroThrottleClutchDisengaged =
VehicleNW->EngineSetup.DampingRateZeroThrottleClutchDisengaged;
// Transmission Setup
PhysicsControl.bUseGearAutoBox = VehicleNW->TransmissionSetup.bUseGearAutoBox;
PhysicsControl.GearSwitchTime = VehicleNW->TransmissionSetup.GearSwitchTime;
PhysicsControl.ClutchStrength = VehicleNW->TransmissionSetup.ClutchStrength;
PhysicsControl.FinalRatio = VehicleNW->TransmissionSetup.FinalRatio;
TArray<FGearPhysicsControl> ForwardGears;
for (const auto &Gear : VehicleNW->TransmissionSetup.ForwardGears)
{
FGearPhysicsControl GearPhysicsControl;
GearPhysicsControl.Ratio = Gear.Ratio;
GearPhysicsControl.UpRatio = Gear.UpRatio;
GearPhysicsControl.DownRatio = Gear.DownRatio;
ForwardGears.Add(GearPhysicsControl);
}
PhysicsControl.ForwardGears = ForwardGears;
// VehicleNW Setup
PhysicsControl.Mass = VehicleNW->Mass;
PhysicsControl.DragCoefficient = VehicleNW->DragCoefficient;
// Center of mass offset (Center of mass is always zero vector in local
// position)
UPrimitiveComponent *UpdatedPrimitive = Cast<UPrimitiveComponent>(VehicleNW->UpdatedComponent);
check(UpdatedPrimitive != nullptr);
PhysicsControl.CenterOfMass = UpdatedPrimitive->BodyInstance.COMNudge;
// Transmission Setup
PhysicsControl.SteeringCurve = VehicleNW->SteeringCurve.EditorCurveData;
// Wheels Setup
TArray<FWheelPhysicsControl> Wheels;
for (int32 i = 0; i < VehicleNW->WheelSetups.Num(); ++i)
{
FWheelPhysicsControl PhysicsWheel;
PxVehicleWheelData PWheelData = VehicleNW->PVehicle->mWheelsSimData.getWheelData(i);
PhysicsWheel.DampingRate = Cm2ToM2(PWheelData.mDampingRate);
PhysicsWheel.MaxSteerAngle = FMath::RadiansToDegrees(PWheelData.mMaxSteer);
PhysicsWheel.Radius = PWheelData.mRadius;
PhysicsWheel.MaxBrakeTorque = Cm2ToM2(PWheelData.mMaxBrakeTorque);
PhysicsWheel.MaxHandBrakeTorque = Cm2ToM2(PWheelData.mMaxHandBrakeTorque);
PxVehicleTireData PTireData = VehicleNW->PVehicle->mWheelsSimData.getTireData(i);
PhysicsWheel.LatStiffMaxLoad = PTireData.mLatStiffX;
PhysicsWheel.LatStiffValue = PTireData.mLatStiffY;
PhysicsWheel.LongStiffValue = PTireData.mLongitudinalStiffnessPerUnitGravity;
PhysicsWheel.TireFriction = VehicleNW->Wheels[i]->TireConfig->GetFrictionScale();
PhysicsWheel.Position = VehicleNW->Wheels[i]->Location;
Wheels.Add(PhysicsWheel);
}
PhysicsControl.Wheels = Wheels;
}
return PhysicsControl;
}
@ -403,7 +513,7 @@ void ACarlaWheeledVehicle::RestoreVehiclePhysicsControl()
void ACarlaWheeledVehicle::ApplyVehiclePhysicsControl(const FVehiclePhysicsControl &PhysicsControl)
{
LastPhysicsControl = PhysicsControl;
if (!bIsSixWVehicle) {
UWheeledVehicleMovementComponent4W *Vehicle4W = Cast<UWheeledVehicleMovementComponent4W>(
GetVehicleMovement());
check(Vehicle4W != nullptr);
@ -505,6 +615,106 @@ void ACarlaWheeledVehicle::ApplyVehiclePhysicsControl(const FVehiclePhysicsContr
}
ResetConstraints();
} else {
UWheeledVehicleMovementComponentNW *VehicleNW = Cast<UWheeledVehicleMovementComponentNW>(
GetVehicleMovement());
check(VehicleNW != nullptr);
// Engine Setup
VehicleNW->EngineSetup.TorqueCurve.EditorCurveData = PhysicsControl.TorqueCurve;
VehicleNW->EngineSetup.MaxRPM = PhysicsControl.MaxRPM;
VehicleNW->EngineSetup.MOI = PhysicsControl.MOI;
VehicleNW->EngineSetup.DampingRateFullThrottle = PhysicsControl.DampingRateFullThrottle;
VehicleNW->EngineSetup.DampingRateZeroThrottleClutchEngaged =
PhysicsControl.DampingRateZeroThrottleClutchEngaged;
VehicleNW->EngineSetup.DampingRateZeroThrottleClutchDisengaged =
PhysicsControl.DampingRateZeroThrottleClutchDisengaged;
// Transmission Setup
VehicleNW->TransmissionSetup.bUseGearAutoBox = PhysicsControl.bUseGearAutoBox;
VehicleNW->TransmissionSetup.GearSwitchTime = PhysicsControl.GearSwitchTime;
VehicleNW->TransmissionSetup.ClutchStrength = PhysicsControl.ClutchStrength;
VehicleNW->TransmissionSetup.FinalRatio = PhysicsControl.FinalRatio;
TArray<FVehicleNWGearData> ForwardGears;
for (const auto &Gear : PhysicsControl.ForwardGears)
{
FVehicleNWGearData GearData;
GearData.Ratio = Gear.Ratio;
GearData.UpRatio = Gear.UpRatio;
GearData.DownRatio = Gear.DownRatio;
ForwardGears.Add(GearData);
}
VehicleNW->TransmissionSetup.ForwardGears = ForwardGears;
// VehicleNW Setup
VehicleNW->Mass = PhysicsControl.Mass;
VehicleNW->DragCoefficient = PhysicsControl.DragCoefficient;
// Center of mass
UPrimitiveComponent *UpdatedPrimitive = Cast<UPrimitiveComponent>(VehicleNW->UpdatedComponent);
check(UpdatedPrimitive != nullptr);
UpdatedPrimitive->BodyInstance.COMNudge = PhysicsControl.CenterOfMass;
// Transmission Setup
VehicleNW->SteeringCurve.EditorCurveData = PhysicsControl.SteeringCurve;
// Wheels Setup
const int PhysicsWheelsNum = PhysicsControl.Wheels.Num();
// Change, if required, the collision mode for wheels
SetWheelCollisionNW(VehicleNW, PhysicsControl);
TArray<FWheelSetup> NewWheelSetups = VehicleNW->WheelSetups;
for (int32 i = 0; i < PhysicsWheelsNum; ++i)
{
UVehicleWheel *Wheel = NewWheelSetups[i].WheelClass.GetDefaultObject();
check(Wheel != nullptr);
// Assigning new tire config
Wheel->TireConfig = DuplicateObject<UTireConfig>(Wheel->TireConfig, nullptr);
// Setting a new value to friction
Wheel->TireConfig->SetFrictionScale(PhysicsControl.Wheels[i].TireFriction);
}
VehicleNW->WheelSetups = NewWheelSetups;
// Recreate Physics State for vehicle setup
GetWorld()->GetPhysicsScene()->GetPxScene()->lockWrite();
VehicleNW->RecreatePhysicsState();
GetWorld()->GetPhysicsScene()->GetPxScene()->unlockWrite();
for (int32 i = 0; i < PhysicsWheelsNum; ++i)
{
PxVehicleWheelData PWheelData = VehicleNW->PVehicle->mWheelsSimData.getWheelData(i);
PWheelData.mRadius = PhysicsControl.Wheels[i].Radius;
PWheelData.mMaxSteer = FMath::DegreesToRadians(PhysicsControl.Wheels[i].MaxSteerAngle);
PWheelData.mDampingRate = M2ToCm2(PhysicsControl.Wheels[i].DampingRate);
PWheelData.mMaxBrakeTorque = M2ToCm2(PhysicsControl.Wheels[i].MaxBrakeTorque);
PWheelData.mMaxHandBrakeTorque = M2ToCm2(PhysicsControl.Wheels[i].MaxHandBrakeTorque);
VehicleNW->PVehicle->mWheelsSimData.setWheelData(i, PWheelData);
PxVehicleTireData PTireData = VehicleNW->PVehicle->mWheelsSimData.getTireData(i);
PTireData.mLatStiffX = PhysicsControl.Wheels[i].LatStiffMaxLoad;
PTireData.mLatStiffY = PhysicsControl.Wheels[i].LatStiffValue;
PTireData.mLongitudinalStiffnessPerUnitGravity = PhysicsControl.Wheels[i].LongStiffValue;
VehicleNW->PVehicle->mWheelsSimData.setTireData(i, PTireData);
}
ResetConstraints();
}
auto * Recorder = UCarlaStatics::GetRecorder(GetWorld());
if (Recorder && Recorder->IsEnabled())
@ -568,7 +778,6 @@ void ACarlaWheeledVehicle::SetWheelSteerDirection(EVehicleWheelLocation WheelLoc
if (bPhysicsEnabled == false)
{
check((uint8)WheelLocation >= 0)
check((uint8)WheelLocation < 4)
UVehicleAnimInstance *VehicleAnim = Cast<UVehicleAnimInstance>(GetMesh()->GetAnimInstance());
check(VehicleAnim != nullptr)
VehicleAnim->SetWheelRotYaw((uint8)WheelLocation, AngleInDeg);
@ -582,7 +791,6 @@ void ACarlaWheeledVehicle::SetWheelSteerDirection(EVehicleWheelLocation WheelLoc
float ACarlaWheeledVehicle::GetWheelSteerAngle(EVehicleWheelLocation WheelLocation) {
check((uint8)WheelLocation >= 0)
check((uint8)WheelLocation < 4)
UVehicleAnimInstance *VehicleAnim = Cast<UVehicleAnimInstance>(GetMesh()->GetAnimInstance());
check(VehicleAnim != nullptr)
check(VehicleAnim->GetWheeledVehicleMovementComponent() != nullptr)
@ -603,9 +811,10 @@ void ACarlaWheeledVehicle::SetSimulatePhysics(bool enabled) {
return;
}
UWheeledVehicleMovementComponent4W *Vehicle4W = Cast<UWheeledVehicleMovementComponent4W>(
GetVehicleMovement());
check(Vehicle4W != nullptr);
UWheeledVehicleMovementComponent* Movement = GetVehicleMovement();
if (Movement)
{
check(Movement != nullptr);
if(bPhysicsEnabled == enabled)
return;
@ -621,12 +830,12 @@ void ACarlaWheeledVehicle::SetSimulatePhysics(bool enabled) {
GetWorld()->GetPhysicsScene()->GetPxScene()->lockWrite();
if (enabled)
{
Vehicle4W->RecreatePhysicsState();
Movement->RecreatePhysicsState();
VehicleAnim->ResetWheelCustomRotations();
}
else
{
Vehicle4W->DestroyPhysicsState();
Movement->DestroyPhysicsState();
}
GetWorld()->GetPhysicsScene()->GetPxScene()->unlockWrite();
@ -634,6 +843,7 @@ void ACarlaWheeledVehicle::SetSimulatePhysics(bool enabled) {
bPhysicsEnabled = enabled;
ResetConstraints();
}
}

View File

@ -15,6 +15,7 @@
#include "Vehicle/VehiclePhysicsControl.h"
#include "VehicleVelocityControl.h"
#include "WheeledVehicleMovementComponent4W.h"
#include "WheeledVehicleMovementComponentNW.h"
#include "VehicleAnimInstance.h"
#include "PhysicsEngine/PhysicsConstraintComponent.h"
#include "MovementComponents/BaseCarlaMovementComponent.h"
@ -160,6 +161,8 @@ public:
void SetWheelCollision(UWheeledVehicleMovementComponent4W *Vehicle4W, const FVehiclePhysicsControl &PhysicsControl);
void SetWheelCollisionNW(UWheeledVehicleMovementComponentNW *VehicleNW, const FVehiclePhysicsControl &PhysicsControl);
void SetVehicleLightState(const FVehicleLightState &LightState);
UFUNCTION(BlueprintNativeEvent)
@ -328,6 +331,9 @@ public:
float CarSimOriginOffset = 150.f;
//-------------------------------------------
UPROPERTY(Category="CARLA Wheeled Vehicle", EditAnywhere)
bool bIsSixWVehicle = false;
private:
UPROPERTY(Category="CARLA Wheeled Vehicle", VisibleAnywhere)

View File

@ -0,0 +1,15 @@
// Copyright (c) 2022 Computer Vision Center (CVC) at the Universitat Autonoma
// de Barcelona (UAB).
// Copyright (c) 2019 Intel Corporation
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.
#include "CarlaWheeledVehicleNW.h"
#include "WheeledVehicleMovementComponentNW.h"
ACarlaWheeledVehicleNW::ACarlaWheeledVehicleNW(const FObjectInitializer& ObjectInitializer) :
Super(ObjectInitializer.SetDefaultSubobjectClass<UWheeledVehicleMovementComponentNW>(AWheeledVehicle::VehicleMovementComponentName))
{}
ACarlaWheeledVehicleNW::~ACarlaWheeledVehicleNW() {}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2022 Computer Vision Center (CVC) at the Universitat Autonoma
// de Barcelona (UAB).
//
// This work is licensed under the terms of the MIT license.
// For a copy, see <https://opensource.org/licenses/MIT>.
#pragma once
#include "CarlaWheeledVehicle.h"
#include "CarlaWheeledVehicleNW.generated.h"
/// Base class for CARLA wheeled vehicles.
UCLASS()
class CARLA_API ACarlaWheeledVehicleNW : public ACarlaWheeledVehicle
{
GENERATED_BODY()
// ===========================================================================
/// @name Constructor and destructor
// ===========================================================================
/// @{
public:
ACarlaWheeledVehicleNW(const FObjectInitializer &ObjectInitializer);
~ACarlaWheeledVehicleNW();
};

View File

@ -0,0 +1,404 @@
#include "WheeledVehicleMovementComponentNW.h"
#include "PhysicsPublic.h"
#include "PhysXPublic.h"
#include "PhysXVehicleManager.h"
#include "Components/PrimitiveComponent.h"
#include "Logging/MessageLog.h"
UWheeledVehicleMovementComponentNW::UWheeledVehicleMovementComponentNW(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
// grab default values from physx
PxVehicleEngineData DefEngineData;
EngineSetup.MOI = DefEngineData.mMOI;
EngineSetup.MaxRPM = OmegaToRPM(DefEngineData.mMaxOmega);
EngineSetup.DampingRateFullThrottle = DefEngineData.mDampingRateFullThrottle;
EngineSetup.DampingRateZeroThrottleClutchEngaged = DefEngineData.mDampingRateZeroThrottleClutchEngaged;
EngineSetup.DampingRateZeroThrottleClutchDisengaged = DefEngineData.mDampingRateZeroThrottleClutchDisengaged;
// Convert from PhysX curve to ours
FRichCurve* TorqueCurveData = EngineSetup.TorqueCurve.GetRichCurve();
for (PxU32 KeyIdx = 0; KeyIdx < DefEngineData.mTorqueCurve.getNbDataPairs(); ++KeyIdx)
{
float Input = DefEngineData.mTorqueCurve.getX(KeyIdx) * EngineSetup.MaxRPM;
float Output = DefEngineData.mTorqueCurve.getY(KeyIdx) * DefEngineData.mPeakTorque;
TorqueCurveData->AddKey(Input, Output);
}
PxVehicleClutchData DefClutchData;
TransmissionSetup.ClutchStrength = DefClutchData.mStrength;
PxVehicleGearsData DefGearSetup;
TransmissionSetup.GearSwitchTime = DefGearSetup.mSwitchTime;
TransmissionSetup.ReverseGearRatio = DefGearSetup.mRatios[PxVehicleGearsData::eREVERSE];
TransmissionSetup.FinalRatio = DefGearSetup.mFinalRatio;
PxVehicleAutoBoxData DefAutoBoxSetup;
TransmissionSetup.NeutralGearUpRatio = DefAutoBoxSetup.mUpRatios[PxVehicleGearsData::eNEUTRAL];
TransmissionSetup.GearAutoBoxLatency = DefAutoBoxSetup.getLatency();
TransmissionSetup.bUseGearAutoBox = true;
for (uint32 i = PxVehicleGearsData::eFIRST; i < DefGearSetup.mNbRatios; ++i)
{
FVehicleNWGearData GearData;
GearData.DownRatio = DefAutoBoxSetup.mDownRatios[i];
GearData.UpRatio = DefAutoBoxSetup.mUpRatios[i];
GearData.Ratio = DefGearSetup.mRatios[i];
TransmissionSetup.ForwardGears.Add(GearData);
}
// Init steering speed curve
FRichCurve* SteeringCurveData = SteeringCurve.GetRichCurve();
SteeringCurveData->AddKey(0.0f, 1.0f);
SteeringCurveData->AddKey(20.0f, 0.9f);
SteeringCurveData->AddKey(60.0f, 0.8f);
SteeringCurveData->AddKey(120.0f, 0.7f);
// Initialize WheelSetups array with 4 wheels, this can be modified via editor later
const int32 NbrWheels = 4;
WheelSetups.SetNum(NbrWheels);
DifferentialSetup.SetNum(NbrWheels);
IdleBrakeInput = 10;
}
#if WITH_EDITOR
void UWheeledVehicleMovementComponentNW::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
const FName PropertyName = PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if (PropertyName == TEXT("DownRatio"))
{
for (int32 GearIdx = 0; GearIdx < TransmissionSetup.ForwardGears.Num(); ++GearIdx)
{
FVehicleNWGearData& GearData = TransmissionSetup.ForwardGears[GearIdx];
GearData.DownRatio = FMath::Min(GearData.DownRatio, GearData.UpRatio);
}
}
else if (PropertyName == TEXT("UpRatio"))
{
for (int32 GearIdx = 0; GearIdx < TransmissionSetup.ForwardGears.Num(); ++GearIdx)
{
FVehicleNWGearData& GearData = TransmissionSetup.ForwardGears[GearIdx];
GearData.UpRatio = FMath::Max(GearData.DownRatio, GearData.UpRatio);
}
}
else if (PropertyName == TEXT("SteeringCurve"))
{
//make sure values are capped between 0 and 1
TArray<FRichCurveKey> SteerKeys = SteeringCurve.GetRichCurve()->GetCopyOfKeys();
for (int32 KeyIdx = 0; KeyIdx < SteerKeys.Num(); ++KeyIdx)
{
float NewValue = FMath::Clamp(SteerKeys[KeyIdx].Value, 0.0f, 1.0f);
SteeringCurve.GetRichCurve()->UpdateOrAddKey(SteerKeys[KeyIdx].Time, NewValue);
}
}
}
#endif
static void GetVehicleDifferentialNWSetup(const TArray<FVehicleNWWheelDifferentialData>& Setup, PxVehicleDifferentialNWData& PxSetup)
{
for (int32 i = 0; i < Setup.Num(); ++i)
{
PxSetup.setDrivenWheel(i, Setup[i].bDriven);
}
}
float FVehicleNWEngineData::FindPeakTorque() const
{
// Find max torque
float PeakTorque = 0.0f;
TArray<FRichCurveKey> TorqueKeys = TorqueCurve.GetRichCurveConst()->GetCopyOfKeys();
for (int32 KeyIdx = 0; KeyIdx < TorqueKeys.Num(); ++KeyIdx)
{
FRichCurveKey& Key = TorqueKeys[KeyIdx];
PeakTorque = FMath::Max(PeakTorque, Key.Value);
}
return PeakTorque;
}
static void GetVehicleEngineSetup(const FVehicleNWEngineData& Setup, PxVehicleEngineData& PxSetup)
{
PxSetup.mMOI = M2ToCm2(Setup.MOI);
PxSetup.mMaxOmega = RPMToOmega(Setup.MaxRPM);
PxSetup.mDampingRateFullThrottle = M2ToCm2(Setup.DampingRateFullThrottle);
PxSetup.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(Setup.DampingRateZeroThrottleClutchEngaged);
PxSetup.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(Setup.DampingRateZeroThrottleClutchDisengaged);
float PeakTorque = Setup.FindPeakTorque(); // In Nm
PxSetup.mPeakTorque = M2ToCm2(PeakTorque); // convert Nm to (kg cm^2/s^2)
// Convert from our curve to PhysX
PxSetup.mTorqueCurve.clear();
TArray<FRichCurveKey> TorqueKeys = Setup.TorqueCurve.GetRichCurveConst()->GetCopyOfKeys();
int32 NumTorqueCurveKeys = FMath::Min<int32>(TorqueKeys.Num(), PxVehicleEngineData::eMAX_NB_ENGINE_TORQUE_CURVE_ENTRIES);
for (int32 KeyIdx = 0; KeyIdx < NumTorqueCurveKeys; ++KeyIdx)
{
FRichCurveKey& Key = TorqueKeys[KeyIdx];
PxSetup.mTorqueCurve.addPair(FMath::Clamp(Key.Time / Setup.MaxRPM, 0.0f, 1.0f), Key.Value / PeakTorque); // Normalize torque to 0-1 range
}
}
static void GetVehicleGearSetup(const FVehicleNWTransmissionData& Setup, PxVehicleGearsData& PxSetup)
{
PxSetup.mSwitchTime = Setup.GearSwitchTime;
PxSetup.mRatios[PxVehicleGearsData::eREVERSE] = Setup.ReverseGearRatio;
for (int32 i = 0; i < Setup.ForwardGears.Num(); i++)
{
PxSetup.mRatios[i + PxVehicleGearsData::eFIRST] = Setup.ForwardGears[i].Ratio;
}
PxSetup.mFinalRatio = Setup.FinalRatio;
PxSetup.mNbRatios = Setup.ForwardGears.Num() + PxVehicleGearsData::eFIRST;
}
static void GetVehicleAutoBoxSetup(const FVehicleNWTransmissionData& Setup, PxVehicleAutoBoxData& PxSetup)
{
for (int32 i = 0; i < Setup.ForwardGears.Num(); ++i)
{
const FVehicleNWGearData& GearData = Setup.ForwardGears[i];
PxSetup.mUpRatios[i + PxVehicleGearsData::eFIRST] = GearData.UpRatio;
PxSetup.mDownRatios[i + PxVehicleGearsData::eFIRST] = GearData.DownRatio;
}
PxSetup.mUpRatios[PxVehicleGearsData::eNEUTRAL] = Setup.NeutralGearUpRatio;
PxSetup.setLatency(Setup.GearAutoBoxLatency);
}
int32 UWheeledVehicleMovementComponentNW::GetCustomGearBoxNumForwardGears() const
{
return TransmissionSetup.ForwardGears.Num();
}
void SetupDriveHelper(const UWheeledVehicleMovementComponentNW* VehicleData, const PxVehicleWheelsSimData* PWheelsSimData, PxVehicleDriveSimDataNW& DriveData)
{
PxVehicleDifferentialNWData DifferentialSetup;
GetVehicleDifferentialNWSetup(VehicleData->DifferentialSetup, DifferentialSetup);
DriveData.setDiffData(DifferentialSetup);
PxVehicleEngineData EngineSetup;
GetVehicleEngineSetup(VehicleData->EngineSetup, EngineSetup);
DriveData.setEngineData(EngineSetup);
PxVehicleClutchData ClutchSetup;
ClutchSetup.mStrength = M2ToCm2(VehicleData->TransmissionSetup.ClutchStrength);
DriveData.setClutchData(ClutchSetup);
PxVehicleGearsData GearSetup;
GetVehicleGearSetup(VehicleData->TransmissionSetup, GearSetup);
DriveData.setGearsData(GearSetup);
PxVehicleAutoBoxData AutoBoxSetup;
GetVehicleAutoBoxSetup(VehicleData->TransmissionSetup, AutoBoxSetup);
DriveData.setAutoBoxData(AutoBoxSetup);
}
void UWheeledVehicleMovementComponentNW::SetupVehicle()
{
if (!UpdatedPrimitive)
{
return;
}
if (WheelSetups.Num() < 2)
{
PVehicle = nullptr;
PVehicleDrive = nullptr;
return;
}
for (int32 WheelIdx = 0; WheelIdx < WheelSetups.Num(); ++WheelIdx)
{
const FWheelSetup& WheelSetup = WheelSetups[WheelIdx];
if (WheelSetup.BoneName == NAME_None)
{
return;
}
}
// Setup the chassis and wheel shapes
SetupVehicleShapes();
// Setup mass properties
SetupVehicleMass();
// Setup the wheels
PxVehicleWheelsSimData* PWheelsSimData = PxVehicleWheelsSimData::allocate(WheelSetups.Num());
SetupWheels(PWheelsSimData);
// Setup drive data
PxVehicleDriveSimDataNW DriveData;
SetupDriveHelper(this, PWheelsSimData, DriveData);
// Create the vehicle
PxVehicleDriveNW* PVehicleDriveNW = PxVehicleDriveNW::allocate(WheelSetups.Num());
check(PVehicleDriveNW);
FBodyInstance* TargetInstance = UpdatedPrimitive->GetBodyInstance();
FPhysicsCommand::ExecuteWrite(TargetInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor)
{
PxRigidActor* PActor = FPhysicsInterface::GetPxRigidActor_AssumesLocked(Actor);
if (!PActor)
{
return;
}
if (PxRigidDynamic* PVehicleActor = PActor->is<PxRigidDynamic>())
{
PVehicleDriveNW->setup(GPhysXSDK, PVehicleActor, *PWheelsSimData, DriveData, 0);
PVehicleDriveNW->setToRestState();
// cleanup
PWheelsSimData->free();
}
});
PWheelsSimData = nullptr;
// cache values
PVehicle = PVehicleDriveNW;
PVehicleDrive = PVehicleDriveNW;
SetUseAutoGears(TransmissionSetup.bUseGearAutoBox);
}
void UWheeledVehicleMovementComponentNW::UpdateSimulation(float DeltaTime)
{
if (PVehicleDrive == nullptr)
return;
FBodyInstance* TargetInstance = UpdatedPrimitive->GetBodyInstance();
FPhysicsCommand::ExecuteWrite(TargetInstance->ActorHandle, [&](const FPhysicsActorHandle& Actor)
{
PxVehicleDriveNWRawInputData RawInputData;
RawInputData.setAnalogAccel(ThrottleInput);
RawInputData.setAnalogSteer(SteeringInput);
RawInputData.setAnalogBrake(BrakeInput);
RawInputData.setAnalogHandbrake(HandbrakeInput);
if (!PVehicleDrive->mDriveDynData.getUseAutoGears())
{
RawInputData.setGearUp(bRawGearUpInput);
RawInputData.setGearDown(bRawGearDownInput);
}
// Convert from our curve to PxFixedSizeLookupTable
PxFixedSizeLookupTable<8> SpeedSteerLookup;
TArray<FRichCurveKey> SteerKeys = SteeringCurve.GetRichCurve()->GetCopyOfKeys();
const int32 MaxSteeringSamples = FMath::Min(8, SteerKeys.Num());
for (int32 KeyIdx = 0; KeyIdx < MaxSteeringSamples; KeyIdx++)
{
FRichCurveKey& Key = SteerKeys[KeyIdx];
SpeedSteerLookup.addPair(KmHToCmS(Key.Time), FMath::Clamp(Key.Value, 0.0f, 1.0f));
}
PxVehiclePadSmoothingData SmoothData = {
{ ThrottleInputRate.RiseRate, BrakeInputRate.RiseRate, HandbrakeInputRate.RiseRate, SteeringInputRate.RiseRate, SteeringInputRate.RiseRate },
{ ThrottleInputRate.FallRate, BrakeInputRate.FallRate, HandbrakeInputRate.FallRate, SteeringInputRate.FallRate, SteeringInputRate.FallRate }
};
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(SmoothData, SpeedSteerLookup, RawInputData, DeltaTime, false, *PVehicleDriveNW);
});
}
void UWheeledVehicleMovementComponentNW::UpdateEngineSetup(const FVehicleNWEngineData& NewEngineSetup)
{
if (PVehicleDrive)
{
PxVehicleEngineData EngineData;
GetVehicleEngineSetup(NewEngineSetup, EngineData);
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PVehicleDriveNW->mDriveSimData.setEngineData(EngineData);
}
}
void UWheeledVehicleMovementComponentNW::UpdateDifferentialSetup(const TArray<FVehicleNWWheelDifferentialData>& NewDifferentialSetup)
{
if (PVehicleDrive)
{
PxVehicleDifferentialNWData DifferentialData;
GetVehicleDifferentialNWSetup(NewDifferentialSetup, DifferentialData);
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PVehicleDriveNW->mDriveSimData.setDiffData(DifferentialData);
}
}
void UWheeledVehicleMovementComponentNW::UpdateTransmissionSetup(const FVehicleNWTransmissionData& NewTransmissionSetup)
{
if (PVehicleDrive)
{
PxVehicleGearsData GearData;
GetVehicleGearSetup(NewTransmissionSetup, GearData);
PxVehicleAutoBoxData AutoBoxData;
GetVehicleAutoBoxSetup(NewTransmissionSetup, AutoBoxData);
PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
PVehicleDriveNW->mDriveSimData.setGearsData(GearData);
PVehicleDriveNW->mDriveSimData.setAutoBoxData(AutoBoxData);
}
}
void BackwardsConvertCm2ToM2NW(float& val, float defaultValue)
{
if (val != defaultValue)
{
val = Cm2ToM2(val);
}
}
void UWheeledVehicleMovementComponentNW::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_VEHICLES_UNIT_CHANGE)
{
PxVehicleEngineData DefEngineData;
const float DefaultRPM = OmegaToRPM(DefEngineData.mMaxOmega);
// We need to convert from old units to new. This backwards compatible code fails in the rare case that they were using very strange values that are the new defaults in the correct units.
EngineSetup.MaxRPM = EngineSetup.MaxRPM != DefaultRPM ? OmegaToRPM(EngineSetup.MaxRPM) : DefaultRPM; //need to convert from rad/s to RPM
}
if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_VEHICLES_UNIT_CHANGE2)
{
PxVehicleEngineData DefEngineData;
PxVehicleClutchData DefClutchData;
// We need to convert from old units to new. This backwards compatable code fails in the rare case that they were using very strange values that are the new defaults in the correct units.
BackwardsConvertCm2ToM2NW(EngineSetup.DampingRateFullThrottle, DefEngineData.mDampingRateFullThrottle);
BackwardsConvertCm2ToM2NW(EngineSetup.DampingRateZeroThrottleClutchDisengaged, DefEngineData.mDampingRateZeroThrottleClutchDisengaged);
BackwardsConvertCm2ToM2NW(EngineSetup.DampingRateZeroThrottleClutchEngaged, DefEngineData.mDampingRateZeroThrottleClutchEngaged);
BackwardsConvertCm2ToM2NW(EngineSetup.MOI, DefEngineData.mMOI);
BackwardsConvertCm2ToM2NW(TransmissionSetup.ClutchStrength, DefClutchData.mStrength);
}
}
void UWheeledVehicleMovementComponentNW::ComputeConstants()
{
Super::ComputeConstants();
MaxEngineRPM = EngineSetup.MaxRPM;
}
const void* UWheeledVehicleMovementComponentNW::GetTireData(physx::PxVehicleWheels* InWheels, UVehicleWheel* InWheel)
{
const void* realShaderData = &InWheels->mWheelsSimData.getTireData((PxU32)InWheel->WheelIndex);
return realShaderData;
}
const int32 UWheeledVehicleMovementComponentNW::GetWheelShapeMapping(physx::PxVehicleWheels* InWheels, uint32 InWheel)
{
const physx::PxI32 ShapeIndex = InWheels->mWheelsSimData.getWheelShapeMapping((PxU32)InWheel);
return ShapeIndex;
}
const physx::PxVehicleWheelData UWheeledVehicleMovementComponentNW::GetWheelData(physx::PxVehicleWheels* InWheels, uint32 InWheel)
{
const physx::PxVehicleWheelData WheelData = InWheels->mWheelsSimData.getWheelData((physx::PxU32)InWheel);
return WheelData;
}

View File

@ -0,0 +1,161 @@
#pragma once
#include "WheeledVehicleMovementComponent.h"
#include "Curves/CurveFloat.h"
#include "WheeledVehicleMovementComponentNW.generated.h"
namespace physx
{
class PxVehicleWheelData;
}
USTRUCT()
struct FVehicleNWWheelDifferentialData
{
GENERATED_USTRUCT_BODY()
/** If True, torque is applied to this wheel */
UPROPERTY(EditAnywhere, Category = Setup)
bool bDriven;
FVehicleNWWheelDifferentialData()
: bDriven(true)
{ }
};
USTRUCT()
struct FVehicleNWEngineData
{
GENERATED_USTRUCT_BODY()
/** Torque (Nm) at a given RPM*/
UPROPERTY(EditAnywhere, Category = Setup)
FRuntimeFloatCurve TorqueCurve;
/** Maximum revolutions per minute of the engine */
UPROPERTY(EditAnywhere, Category = Setup, meta = (ClampMin = "0.01", UIMin = "0.01"))
float MaxRPM;
/** Moment of inertia of the engine around the axis of rotation (Kgm^2). */
UPROPERTY(EditAnywhere, Category = Setup, meta = (ClampMin = "0.01", UIMin = "0.01"))
float MOI;
/** Damping rate of engine when full throttle is applied (Kgm^2/s) */
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float DampingRateFullThrottle;
/** Damping rate of engine in at zero throttle when the clutch is engaged (Kgm^2/s)*/
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float DampingRateZeroThrottleClutchEngaged;
/** Damping rate of engine in at zero throttle when the clutch is disengaged (in neutral gear) (Kgm^2/s)*/
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float DampingRateZeroThrottleClutchDisengaged;
/** Find the peak torque produced by the TorqueCurve */
float FindPeakTorque() const;
};
USTRUCT()
struct FVehicleNWGearData
{
GENERATED_USTRUCT_BODY()
/** Determines the amount of torque multiplication*/
UPROPERTY(EditAnywhere, Category = Setup)
float Ratio;
/** Value of engineRevs/maxEngineRevs that is low enough to gear down*/
UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0", ClampMax = "1.0", UIMax = "1.0"), Category = Setup)
float DownRatio;
/** Value of engineRevs/maxEngineRevs that is high enough to gear up*/
UPROPERTY(EditAnywhere, meta = (ClampMin = "0.0", UIMin = "0.0", ClampMax = "1.0", UIMax = "1.0"), Category = Setup)
float UpRatio;
};
USTRUCT()
struct FVehicleNWTransmissionData
{
GENERATED_USTRUCT_BODY()
/** Whether to use automatic transmission */
UPROPERTY(EditAnywhere, Category = VehicleSetup, meta = (DisplayName = "Automatic Transmission"))
bool bUseGearAutoBox;
/** Time it takes to switch gears (seconds) */
UPROPERTY(EditAnywhere, Category = Setup, meta = (ClampMin = "0.0", UIMin = "0.0"))
float GearSwitchTime;
/** Minimum time it takes the automatic transmission to initiate a gear change (seconds)*/
UPROPERTY(EditAnywhere, Category = Setup, meta = (editcondition = "bUseGearAutoBox", ClampMin = "0.0", UIMin = "0.0"))
float GearAutoBoxLatency;
/** The final gear ratio multiplies the transmission gear ratios.*/
UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup)
float FinalRatio;
/** Forward gear ratios (up to 30) */
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay)
TArray<FVehicleNWGearData> ForwardGears;
/** Reverse gear ratio */
UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup)
float ReverseGearRatio;
/** Value of engineRevs/maxEngineRevs that is high enough to increment gear*/
UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup, meta = (ClampMin = "0.0", UIMin = "0.0", ClampMax = "1.0", UIMax = "1.0"))
float NeutralGearUpRatio;
/** Strength of clutch (Kgm^2/s)*/
UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay, meta = (ClampMin = "0.0", UIMin = "0.0"))
float ClutchStrength;
};
UCLASS(ClassGroup = (Physics), meta = (BlueprintSpawnableComponent), hidecategories = (PlanarMovement, "Components|Movement|Planar", Activation, "Components|Activation"))
class PHYSXVEHICLES_API UWheeledVehicleMovementComponentNW : public UWheeledVehicleMovementComponent
{
GENERATED_UCLASS_BODY()
/** Engine */
UPROPERTY(EditAnywhere, Category = MechanicalSetup)
FVehicleNWEngineData EngineSetup;
/** Differential */
UPROPERTY(EditAnywhere, Category = MechanicalSetup)
TArray<FVehicleNWWheelDifferentialData> DifferentialSetup;
/** Transmission data */
UPROPERTY(EditAnywhere, Category = MechanicalSetup)
FVehicleNWTransmissionData TransmissionSetup;
/** Maximum steering versus forward speed (km/h) */
UPROPERTY(EditAnywhere, Category = SteeringSetup)
FRuntimeFloatCurve SteeringCurve;
virtual void Serialize(FArchive& Ar) override;
virtual void ComputeConstants() override;
#if WITH_EDITOR
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
protected:
virtual const void* GetTireData(physx::PxVehicleWheels* Wheels, UVehicleWheel* Wheel);
virtual const int32 GetWheelShapeMapping(physx::PxVehicleWheels* Wheels, uint32 Wheel);
virtual const physx::PxVehicleWheelData GetWheelData(physx::PxVehicleWheels* Wheels, uint32 Wheel);
/** Allocate and setup the PhysX vehicle */
virtual void SetupVehicle() override;
virtual int32 GetCustomGearBoxNumForwardGears() const;
virtual void UpdateSimulation(float DeltaTime) override;
/** update simulation data: engine */
virtual void UpdateEngineSetup(const FVehicleNWEngineData& NewEngineSetup);
/** update simulation data: differential */
virtual void UpdateDifferentialSetup(const TArray<FVehicleNWWheelDifferentialData>& NewDifferentialSetup);
/** update simulation data: transmission */
virtual void UpdateTransmissionSetup(const FVehicleNWTransmissionData& NewGearSetup);
};