Fixing the Vehicle class in UE4 starting with NWheel implementation.

So I created this thread to see if anyone wants to work on creating an N Wheeled class vehicle with me, as opposed to a bunch of people banging their heads against the wall separately. I know there are plenty of issues with the PhysX integration of the vehicle, and it’s incredibly complicated. But I’d be willing to put in the time coding it if I could find a couple of other people willing to code also, or at least help me with some of the issues with PhysX, I know we have enough people on here that are smart and dedicated enough to fix the vehicles in the engine, and get it done in a timely manner. Also, this wasn’t added to the Horrible Physics thread because I want to focus on solutions, not endless complaining because it doesn’t work, that thread is way too negative to make for a solid work environment. I wanted to gauge interest before I started throwing my code in here, I want people working together to solve this, not just taking my code and complaining because they can’t make it work. So if I get 2 or 3 solid responses from anyone I’ll post my code and progress so far.

I’ll post this code here for now, I still need to change some things but it works.

If you haven’t added this line, you probably should’ve already if your here but anyways, add this to your PhysXVehicleManager.cpp inside the switch statement in the RemoveVehicle function or it’ll crash every time the game exits.


	case PxVehicleTypes::eDRIVENW:
		((PxVehicleDriveNW*)PVehicle)->free();
		break;

Forgot about this one, you need to add this to your VehicleProject.h, or whatever your project header file is.



// PhysX. <-- You don't have to add this obviously.
#include "PhysicsPublic.h" 
#include "PhysXPublic.h"


Your build.cs file should have the PhysX, and APEX added to it, though I don’t understand why you need APEX (APEX is compiled on top of the PhysX library, so PhysX being dependent on APEX is a somewhat circular dependency).



// Copyright Unreal Engine Community.

using UnrealBuildTool;

public class VehicleProject : ModuleRules
{
	public VehicleProject(TargetInfo Target)
	{
		PublicDependencyModuleNames.AddRange(
            new string] 
            {
                "Core",
                "CoreUObject",
                "Engine",
                "InputCore",
                "HeadMountedDisplay",
                "PhysX",
                "APEX",
            });
	}
}

VehicleMovementComponentNW.h


// Copyright Unreal Engine Community.

#pragma once

#include "Vehicles/WheeledVehicleMovementComponent.h"
#include "Curves/CurveFloat.h"
#include "VehicleMovementComponentNW.generated.h"

#if WITH_VEHICLE
namespace physx
{
	class PxVehicleDriveNW;
}
#endif // WITH_VEHICLE

USTRUCT()
struct FDrivenWheelData
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category = Setup)
	int32 DrivenWheelIndex;

	UPROPERTY(EditAnywhere, Category = Setup)
	bool IsDrivenWheel;
};

USTRUCT()
struct FVehicleDifferentialNWData
{
	GENERATED_USTRUCT_BODY()

	UPROPERTY(EditAnywhere, Category = Setup, AdvancedDisplay)
	TArray<FDrivenWheelData> DWheelData;
};

USTRUCT()
struct FVehicleEngineNWData
{
	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 FVehicleGearNWData
{
	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 FVehicleTransmissionNWData
{
	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<FVehicleGearNWData> 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()
class VEHICLEPROJECT_API UVehicleMovementComponentNW : public UWheeledVehicleMovementComponent
{
	GENERATED_UCLASS_BODY()

	UPROPERTY(EditAnywhere, Category = Vehicle)
	int32 NumOfWheels;

	// Engine.
	UPROPERTY(EditAnywhere, Category = MechanicalSetup)
	FVehicleEngineNWData EngineSetup;

	// Differential.
	UPROPERTY(EditAnywhere, Category = MechanicalSetup)
	FVehicleDifferentialNWData DifferentialSetup;

	//UPROPERTY(EditAnywhere, Category = MechanicalSetup).
	FDrivenWheelData DrivenWheelSetup;

	// Transmission Data.
	UPROPERTY(EditAnywhere, Category = MechanicalSetup)
	FVehicleTransmissionNWData 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:

#if WITH_VEHICLE

	// Allocate and setup the PhysX vehicle.
	virtual void SetupVehicle() override;
	virtual void UpdateSimulation(float DeltaTime) override;


#endif // WITH_VEHICLE

	// Update simulation data: engine.
	void UpdateEngineSetup(const FVehicleEngineNWData& NewEngineSetup);

	// Update Simulation data: differential.
	void UpdateDifferentialSetup(const FVehicleDifferentialNWData& NewDifferentialSetup);

	// Update simulation data: transmission.
	void UpdateTransmissionSetup(const FVehicleTransmissionNWData& NewGearSetup);
};

VehicleMovementComponentNW.cpp


// Copyright Unreal Engine Community.

#include "VehicleProject.h"
#include "PxVehicleDriveNW.h"
#include "VehicleMovementComponentNW.h"

UVehicleMovementComponentNW::UVehicleMovementComponentNW(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
#if WITH_VEHICLE

	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++)
	{
		FVehicleGearNWData GearData;
		GearData.DownRatio = DefAutoBoxSetup.mDownRatios*;
		GearData.UpRatio = DefAutoBoxSetup.mUpRatios*;
		GearData.Ratio = DefGearSetup.mRatios*;
		TransmissionSetup.ForwardGears.Add(GearData);
	}

	// Init steering speed curve.
	FRichCurve* SteeringCurveData = SteeringCurve.GetRichCurve();
	SteeringCurveData->AddKey(0.f, 1.f);
	SteeringCurveData->AddKey(20.f, 0.9f);
	SteeringCurveData->AddKey(60.f, 0.8f);
	SteeringCurveData->AddKey(120.f, 0.7f);

	NumOfWheels = 4;

	// Initialize WheelSetups array with 6 wheels.
	WheelSetups.SetNum(NumOfWheels);

	// Grab default values from physx.
	PxVehicleDifferentialNWData DefDifferentialSetup;
	for (int32 WheelIdx = 0; WheelIdx < NumOfWheels; ++WheelIdx)
	{
		FDrivenWheelData DiffData;
		DiffData.IsDrivenWheel = DefDifferentialSetup.getIsDrivenWheel(WheelIdx);
		DiffData.DrivenWheelIndex = WheelIdx;
		DifferentialSetup.DWheelData.Add(DiffData);
	}
#endif // WITH_VEHICLE
}


//////////////////////////////////////////////////////////////////////////
///////////Modified Code from WheeledVehicleMovementComponent4W///////////

#if WITH_EDITOR
void UVehicleMovementComponentNW::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)
		{
			FVehicleGearNWData & 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)
		{
			FVehicleGearNWData & 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.f, 1.f);
			SteeringCurve.GetRichCurve()->UpdateOrAddKey(SteerKeys[KeyIdx].Time, NewValue);
		}
	}
}
#endif // WITH_EDITOR

#if WITH_VEHICLE
static void GetVehicleDifferentialNWSetup(const FVehicleDifferentialNWData& Setup, PxVehicleDifferentialNWData& PxSetup)
{
	for (int32 i = 0; i < Setup.DWheelData.Num(); i++)
	{
		PxSetup.setDrivenWheel(Setup.DWheelData*.DrivenWheelIndex, Setup.DWheelData*.IsDrivenWheel);
	}
}

float FVehicleEngineNWData::FindPeakTorque() const
{
	// Find max torque.
	float PeakTorque = 0.f;
	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 FVehicleEngineNWData& 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.f, 1.f), Key.Value / PeakTorque); // Normalize torque to 0-1 range
	}
}

static void GetVehicleGearSetup(const FVehicleTransmissionNWData& 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*.Ratio;
	}
	PxSetup.mFinalRatio = Setup.FinalRatio;
	PxSetup.mNbRatios = Setup.ForwardGears.Num() + PxVehicleGearsData::eFIRST;
}

static void GetVehicleAutoBoxSetup(const FVehicleTransmissionNWData& Setup, PxVehicleAutoBoxData& PxSetup)
{
	for (int32 i = 0; i < Setup.ForwardGears.Num(); i++)
	{
		const FVehicleGearNWData& GearData = Setup.ForwardGears*;
		PxSetup.mUpRatios* = GearData.UpRatio;
		PxSetup.mDownRatios* = GearData.DownRatio;
	}
	PxSetup.mUpRatios[PxVehicleGearsData::eNEUTRAL] = Setup.NeutralGearUpRatio;
	PxSetup.setLatency(Setup.GearAutoBoxLatency);
}

void SetupDriveHelper(const UVehicleMovementComponentNW* 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 UVehicleMovementComponentNW::SetupVehicle()
{
	if (!UpdatedPrimitive)
	{
		return;
	}

	if (WheelSetups.Num() > 20 || WheelSetups.Num() < 2)
	{
		PVehicle = NULL;
		PVehicleDrive = NULL;
		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(NumOfWheels);
	SetupWheels(PWheelsSimData);

	// Setup drive data.
	PxVehicleDriveSimDataNW DriveData;
	SetupDriveHelper(this, PWheelsSimData, DriveData);

	// Create the vehicle.
	PxVehicleDriveNW* PVehicleDriveNW = PxVehicleDriveNW::allocate(NumOfWheels);
	check(PVehicleDriveNW);

	FBodyInstance* BodyInstance = UpdatedPrimitive->GetBodyInstance();
	BodyInstance->ExecuteOnPhysicsReadWrite(&]
	{
		PxRigidDynamic* PRigidDynamic = BodyInstance->GetPxRigidDynamic_AssumesLocked();

		PVehicleDriveNW->setup(GPhysXSDK, PRigidDynamic, *PWheelsSimData, DriveData, 0);
		PVehicleDriveNW->setToRestState();

		// Cleanup.
		PWheelsSimData->free();
	});

	// This bit of code gives a link error, not sure why, so It stays here until I figure it out.
	//ExecuteOnPxRigidDynamicReadWrite(UpdatedPrimitive->GetBodyInstance(), &] (PxRigidDynamic* PRigidDynamic)
	//{
	//	PVehicleDriveNW->setup(GPhysXSDK, PRigidDynamic, *PWheelsSimData, DriveData, 0);
	//	PVehicleDriveNW->setToRestState();

	// Cleanup.
	//	PWheelsSimData->free();
	//});

	PWheelsSimData = NULL;

	// Cache values.
	PVehicle = PVehicleDriveNW;
	PVehicleDrive = PVehicleDriveNW;

	SetUseAutoGears(TransmissionSetup.bUseGearAutoBox);
}

void UVehicleMovementComponentNW::UpdateSimulation(float DeltaTime)
{
	if (PVehicleDrive == NULL)
		return;

	UpdatedPrimitive->GetBodyInstance()->ExecuteOnPhysicsReadWrite(&]
	{
		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.f, 1.f));
		}

		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);
	});
}
#endif // WITH_VEHICLE

void UVehicleMovementComponentNW::UpdateEngineSetup(const FVehicleEngineNWData& NewEngineSetup)
{
#if WITH_VEHICLE
	if (PVehicleDrive)
	{
		PxVehicleEngineData EngineData;
		GetVehicleEngineSetup(NewEngineSetup, EngineData);

		PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
		PVehicleDriveNW->mDriveSimData.setEngineData(EngineData);
	}
#endif // WITH_VEHICLE
}

void UVehicleMovementComponentNW::UpdateDifferentialSetup(const FVehicleDifferentialNWData& NewDifferentialSetup)
{
#if WITH_VEHICLE
	if (PVehicleDrive)
	{
		PxVehicleDifferentialNWData DifferentialData;
		GetVehicleDifferentialNWSetup(NewDifferentialSetup, DifferentialData);

		PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
		PVehicleDriveNW->mDriveSimData.setDiffData(DifferentialData);
	}
#endif
}

void UVehicleMovementComponentNW::UpdateTransmissionSetup(const FVehicleTransmissionNWData& NewTransmissionSetup)
{
#if WITH_VEHICLE
	if (PVehicleDrive)
	{
		PxVehicleGearsData GearData;
		GetVehicleGearSetup(NewTransmissionSetup, GearData);

		PxVehicleAutoBoxData AutoBoxData;
		GetVehicleAutoBoxSetup(NewTransmissionSetup, AutoBoxData);

		PxVehicleDriveNW* PVehicleDriveNW = (PxVehicleDriveNW*)PVehicleDrive;
		PVehicleDriveNW->mDriveSimData.setGearsData(GearData);
		PVehicleDriveNW->mDriveSimData.setAutoBoxData(AutoBoxData);
	}
#endif
}

void BackwardsConvertCm2ToM2(float& val, float defaultValue)
{
	if (val != defaultValue)
	{
		val = Cm2ToM2(val);
	}
}

void UVehicleMovementComponentNW::Serialize(FArchive & Ar)
{
	Super::Serialize(Ar);
#if WITH_VEHICLE
	if (Ar.IsLoading() && Ar.UE4Ver() < VER_UE4_VEHICLES_UNIT_CHANGE)
	{
		PxVehicleEngineData DefEngineData;
		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 compatible code fails in the rare case that they were using very strange values that are the new defaults in the correct units.
		BackwardsConvertCm2ToM2(EngineSetup.DampingRateFullThrottle, DefEngineData.mDampingRateFullThrottle);
		BackwardsConvertCm2ToM2(EngineSetup.DampingRateZeroThrottleClutchDisengaged, DefEngineData.mDampingRateZeroThrottleClutchDisengaged);
		BackwardsConvertCm2ToM2(EngineSetup.DampingRateZeroThrottleClutchEngaged, DefEngineData.mDampingRateZeroThrottleClutchEngaged);
		BackwardsConvertCm2ToM2(EngineSetup.MOI, DefEngineData.mMOI);
		BackwardsConvertCm2ToM2(TransmissionSetup.ClutchStrength, DefClutchData.mStrength);
	}
#endif
}

void UVehicleMovementComponentNW::ComputeConstants()
{
	Super::ComputeConstants();
	MaxEngineRPM = EngineSetup.MaxRPM;
}

WheeledVehicleNW.h


// Copyright Unreal Engine Community.

#pragma once

#include "GameFramework/WheeledVehicle.h"
#include "WheeledVehicleNW.generated.h"

class UPhysicalMaterial;
class UCameraComponent;
class USpringArmComponent;
class UTextRenderComponent;
class UInputComponent;

UCLASS(config = Game)
class VEHICLEPROJECT_API AWheeledVehicleNW : public AWheeledVehicle
{
	GENERATED_UCLASS_BODY()

	// Spring arm that will offset the camera.
	UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	USpringArmComponent* SpringArm;

	// Camera component that will be our viewpoint.
	UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UCameraComponent* Camera;

	// Camera component for the In-Car view.
	UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UCameraComponent* InternalCamera;

	/** Text component for the In-Car speed */
	UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UTextRenderComponent* InCarSpeed;

	/** Text component for the In-Car gear */
	UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UTextRenderComponent* InCarGear;

	/** Audio component for the engine sound */
	UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly, meta = (AllowPrivateAccess = "true"))
	UAudioComponent* EngineSoundComponent;

public:

	/** The current speed as a string e.g. 10 km/h */
	UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
	FText SpeedDisplayString;

	/** The current gear as a string (R,N, 1,2 etc) */
	UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
	FText GearDisplayString;

	/** The color of the In-Car gear text in forward gears */
	UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
	FColor	GearDisplayColor;

	/** The color of the In-Car gear text when in reverse */
	UPROPERTY(Category = Display, VisibleDefaultsOnly, BlueprintReadOnly)
	FColor	GearDisplayReverseColor;

	/** Are we using In-Car camera */
	UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly)
	bool bInCarCameraActive;

	/** Are we in reverse gear */
	UPROPERTY(Category = Camera, VisibleDefaultsOnly, BlueprintReadOnly)
	bool bInReverseGear;

	/** Initial offset of In-Car camera */
	FVector InternalCameraOrigin;

	// Begin Pawn interface
	virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;
	// End Pawn interface

	// Begin Actor interface
	virtual void Tick(float Delta) override;
	virtual void BeginPlay() override;
	// End Actor interface

	/** Handle pressing forwards */
	void MoveForward(float Val);

	/** Setup the strings used on the HUD. */
	void SetupInCarHUD();

	/** Update the physics material used by the vehicle mesh */
	void UpdatePhysicsMaterial();

	/** Handle pressing right */
	void MoveRight(float Val);
	/** Handle handbrake pressed */
	void OnHandbrakePressed();
	/** Handle handbrake released */
	void OnHandbrakeReleased();
	/** Switch between cameras */
	void OnToggleCamera();
	/** Handle reset VR device */
	void OnResetVR();

	static const FName LookUpBinding;
	static const FName LookRightBinding;
	static const FName EngineAudioRPM;

private:
	/**
	* Activate In-Car camera. Enable camera and sets visibility of in-car HUD display.
	*
	* @param	bState true will enable in car view and set visibility of various
	*/
	void EnableIncarView(const bool bState);

	/** Update the gear and speed strings */
	void UpdateHUDStrings();

	/* Are we on a 'slippery' surface */
	bool bIsLowFriction;
	/** Slippery Material instance */
	UPhysicalMaterial* SlipperyMaterial;
	/** Non Slippery Material instance */
	UPhysicalMaterial* NonSlipperyMaterial;

public:
	/** Returns SpringArm subobject **/
	FORCEINLINE USpringArmComponent* GetSpringArm() const { return SpringArm; }
	/** Returns Camera subobject **/
	FORCEINLINE UCameraComponent* GetCamera() const { return Camera; }
	/** Returns InternalCamera subobject **/
	FORCEINLINE UCameraComponent* GetInternalCamera() const { return InternalCamera; }
	/** Returns InCarSpeed subobject **/
	FORCEINLINE UTextRenderComponent* GetInCarSpeed() const { return InCarSpeed; }
	/** Returns InCarGear subobject **/
	FORCEINLINE UTextRenderComponent* GetInCarGear() const { return InCarGear; }
	/** Returns EngineSoundComponent subobject **/
	FORCEINLINE UAudioComponent* GetEngineSoundComponent() const { return EngineSoundComponent; }
};

VehicleMovementComponentNW.cpp


// Copyright Unreal Engine Community.

#include "VehicleProject.h"
#include "VehicleProjectWheelFront.h"
#include "VehicleProjectWheelRear.h"
#include "VehicleProjectHud.h"
#include "Components/SkeletalMeshComponent.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
#include "Components/InputComponent.h"
#include "Components/TextRenderComponent.h"
#include "Sound/SoundCue.h"
#include "VehicleMovementComponentNW.h"
#include "PhysicalMaterials/PhysicalMaterial.h"
#include "Engine/SkeletalMesh.h"
#include "WheeledVehicleNW.h"

#ifdef HMD_INTGERATION
// Needed for VR Headset.
#include "Engine.h"
#include "IHeadMountedDisplay.h"
#endif // HMD_INTGERATION.

const FName AWheeledVehicleNW::LookUpBinding("LookUp");
const FName AWheeledVehicleNW::LookRightBinding("LookRight");
const FName AWheeledVehicleNW::EngineAudioRPM("RPM");

#define LOCTEXT_NAMESPACE "VehicleNW"

AWheeledVehicleNW::AWheeledVehicleNW(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer.SetDefaultSubobjectClass<UVehicleMovementComponentNW>(VehicleMovementComponentName))
{
	// Car mesh.
	static ConstructorHelpers::FObjectFinder<USkeletalMesh> CarMesh(TEXT("/Game/VehicleAdv/Vehicle/Vehicle_SkelMesh.Vehicle_SkelMesh"));
	GetMesh()->SetSkeletalMesh(CarMesh.Object);

	static ConstructorHelpers::FClassFinder<UObject> AnimBPClass(TEXT("/Game/VehicleAdv/Vehicle/VehicleAnimationBlueprint"));
	GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
	GetMesh()->SetAnimInstanceClass(AnimBPClass.Class);

	// Setup friction materials.
	static ConstructorHelpers::FObjectFinder<UPhysicalMaterial> SlipperyMat(TEXT("/Game/VehicleAdv/PhysicsMaterials/Slippery.Slippery"));
	SlipperyMaterial = SlipperyMat.Object;

	static ConstructorHelpers::FObjectFinder<UPhysicalMaterial> NonSlipperyMat(TEXT("/Game/VehicleAdv/PhysicsMaterials/NonSlippery.NonSlippery"));
	NonSlipperyMaterial = NonSlipperyMat.Object;

	UVehicleMovementComponentNW* VehicleNW = CastChecked<UVehicleMovementComponentNW>(GetVehicleMovementComponent());

	check(VehicleNW->WheelSetups.Num() == 4);

	// Wheels/Tires.
	// Setup the wheels.
	VehicleNW->WheelSetups[0].WheelClass = UVehicleProjectWheelFront::StaticClass();
	VehicleNW->WheelSetups[0].BoneName = FName("PhysWheel_FL");
	VehicleNW->WheelSetups[0].AdditionalOffset = FVector(0.f, 0.f, 0.f);

	VehicleNW->WheelSetups[1].WheelClass = UVehicleProjectWheelFront::StaticClass();
	VehicleNW->WheelSetups[1].BoneName = FName("PhysWheel_FR");
	VehicleNW->WheelSetups[1].AdditionalOffset = FVector(0.f, 0.f, 0.f);

	VehicleNW->WheelSetups[2].WheelClass = UVehicleProjectWheelRear::StaticClass();
	VehicleNW->WheelSetups[2].BoneName = FName("PhysWheel_BL");
	VehicleNW->WheelSetups[2].AdditionalOffset = FVector(0.f, 0.f, 0.f);

	VehicleNW->WheelSetups[3].WheelClass = UVehicleProjectWheelRear::StaticClass();
	VehicleNW->WheelSetups[3].BoneName = FName("PhysWheel_BR");
	VehicleNW->WheelSetups[3].AdditionalOffset = FVector(0.f, 0.f, 0.f);

	// Adjust the tire loading.
	VehicleNW->MinNormalizedTireLoad = 0.0f;
	VehicleNW->MinNormalizedTireLoadFiltered = 0.2f;
	VehicleNW->MaxNormalizedTireLoad = 2.0f;
	VehicleNW->MaxNormalizedTireLoadFiltered = 2.0f;

	// Engine.
	// Torque setup.
	VehicleNW->MaxEngineRPM = 5700.0f;
	VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->Reset();
	VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(0.0f, 400.0f);
	VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(1890.0f, 500.0f);
	VehicleNW->EngineSetup.TorqueCurve.GetRichCurve()->AddKey(5730.0f, 400.0f);

	// Adjust the steering .
	VehicleNW->SteeringCurve.GetRichCurve()->Reset();
	VehicleNW->SteeringCurve.GetRichCurve()->AddKey(0.0f, 1.0f);
	VehicleNW->SteeringCurve.GetRichCurve()->AddKey(40.0f, 0.7f);
	VehicleNW->SteeringCurve.GetRichCurve()->AddKey(120.0f, 0.6f);

	// Automatic gearbox.
	VehicleNW->TransmissionSetup.bUseGearAutoBox = true;
	VehicleNW->TransmissionSetup.GearSwitchTime = 0.15f;
	VehicleNW->TransmissionSetup.GearAutoBoxLatency = 1.0f;

	// Physics settings.
	// Adjust the center of mass - the buggy is quite low.
	UPrimitiveComponent* UpdatedPrimitive = Cast<UPrimitiveComponent>(VehicleNW->UpdatedComponent);
	if (UpdatedPrimitive)
	{
		UpdatedPrimitive->BodyInstance.COMNudge = FVector(8.0f, 0.0f, 0.0f);
	}

	// Set the inertia scale. This controls how the mass of the vehicle is distributed.
	VehicleNW->InertiaTensorScale = FVector(1.0f, 1.333f, 1.2f);

	// Create a spring arm component for our chase camera.
	SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
	SpringArm->SetRelativeLocation(FVector(0.0f, 0.0f, 34.0f));
	SpringArm->SetWorldRotation(FRotator(-20.0f, 0.0f, 0.0f));
	SpringArm->AttachTo(RootComponent);
	SpringArm->TargetArmLength = 125.0f;
	SpringArm->bEnableCameraLag = false;
	SpringArm->bEnableCameraRotationLag = false;
	SpringArm->bInheritPitch = true;
	SpringArm->bInheritYaw = true;
	SpringArm->bInheritRoll = true;

	// Create the chase camera component .
	Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("ChaseCamera"));
	Camera->AttachTo(SpringArm, USpringArmComponent::SocketName);
	Camera->SetRelativeRotation(FRotator(10.0f, 0.0f, 0.0f));
	Camera->bUsePawnControlRotation = false;
	Camera->FieldOfView = 90.f;

	// Create In-Car camera component .
	InternalCameraOrigin = FVector(-34.0f, 0.0f, 50.0f);
	InternalCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("InternalCamera"));
	//InternalCamera->AttachTo(SpringArm, USpringArmComponent::SocketName);
	InternalCamera->bUsePawnControlRotation = false;
	InternalCamera->FieldOfView = 90.f;
	InternalCamera->SetRelativeLocation(InternalCameraOrigin);
	InternalCamera->AttachTo(GetMesh());

	// In car HUD.
	// Create text render component for in car speed display.
	InCarSpeed = CreateDefaultSubobject<UTextRenderComponent>(TEXT("IncarSpeed"));
	InCarSpeed->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f));
	InCarSpeed->SetRelativeLocation(FVector(35.0f, -6.0f, 20.0f));
	InCarSpeed->SetRelativeRotation(FRotator(0.0f, 180.0f, 0.0f));
	InCarSpeed->AttachTo(GetMesh());

	// Create text render component for in car gear display.
	InCarGear = CreateDefaultSubobject<UTextRenderComponent>(TEXT("IncarGear"));
	InCarGear->SetRelativeScale3D(FVector(0.1f, 0.1f, 0.1f));
	InCarGear->SetRelativeLocation(FVector(35.0f, 5.0f, 20.0f));
	InCarGear->SetRelativeRotation(FRotator(0.0f, 180.0f, 0.0f));
	InCarGear->AttachTo(GetMesh());

	// Setup the audio component and allocate it a sound cue.
	static ConstructorHelpers::FObjectFinder<USoundCue> SoundCue(TEXT("/Game/VehicleAdv/Sound/Engine_Loop_Cue.Engine_Loop_Cue"));
	EngineSoundComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("EngineSound"));
	EngineSoundComponent->SetSound(SoundCue.Object);
	EngineSoundComponent->AttachTo(GetMesh());

	// Colors for the in-car gear display. One for normal one for reverse.
	GearDisplayReverseColor = FColor(255, 0, 0, 255);
	GearDisplayColor = FColor(255, 255, 255, 255);

	bIsLowFriction = false;
	bInReverseGear = false;
}

void AWheeledVehicleNW::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
	// Set up gameplay key bindings.
	check(InputComponent);

	InputComponent->BindAxis("MoveForward", this, &AWheeledVehicleNW::MoveForward);
	InputComponent->BindAxis("MoveRight", this, &AWheeledVehicleNW::MoveRight);
	InputComponent->BindAxis(LookUpBinding);
	InputComponent->BindAxis(LookRightBinding);

	InputComponent->BindAction("Handbrake", IE_Pressed, this, &AWheeledVehicleNW::OnHandbrakePressed);
	InputComponent->BindAction("Handbrake", IE_Released, this, &AWheeledVehicleNW::OnHandbrakeReleased);
	InputComponent->BindAction("SwitchCamera", IE_Pressed, this, &AWheeledVehicleNW::OnToggleCamera);

	InputComponent->BindAction("ResetVR", IE_Pressed, this, &AWheeledVehicleNW::OnResetVR);
}

void AWheeledVehicleNW::MoveForward(float Val)
{
	GetVehicleMovementComponent()->SetThrottleInput(Val);
}

void AWheeledVehicleNW::MoveRight(float Val)
{
	GetVehicleMovementComponent()->SetSteeringInput(Val);
}

void AWheeledVehicleNW::OnHandbrakePressed()
{
	GetVehicleMovementComponent()->SetHandbrakeInput(true);
}

void AWheeledVehicleNW::OnHandbrakeReleased()
{
	GetVehicleMovementComponent()->SetHandbrakeInput(false);
}

void AWheeledVehicleNW::OnToggleCamera()
{
	EnableIncarView(!bInCarCameraActive);
}

void AWheeledVehicleNW::EnableIncarView(const bool bState)
{
	if (bState != bInCarCameraActive)
	{
		bInCarCameraActive = bState;

		if (bState == true)
		{
			OnResetVR();
			Camera->Deactivate();
			InternalCamera->Activate();

			APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
			if ((PlayerController != nullptr) && (PlayerController->PlayerCameraManager != nullptr))
			{
				PlayerController->PlayerCameraManager->bFollowHmdOrientation = true;
			}
		}
		else
		{
			InternalCamera->Deactivate();
			Camera->Activate();
		}

		InCarSpeed->SetVisibility(bInCarCameraActive);
		InCarGear->SetVisibility(bInCarCameraActive);
	}
}

void AWheeledVehicleNW::Tick(float Delta)
{
	Super::Tick(Delta);

	// Setup the flag to say we are in reverse gear.
	bInReverseGear = GetVehicleMovementComponent()->GetCurrentGear() < 0;

	// Update physics material.
	UpdatePhysicsMaterial();

	// Update the strings used in the HUD (In-car and On-screen).
	UpdateHUDStrings();

	// Set the string in the In-car HUD.
	SetupInCarHUD();

	bool bHMDActive = false;
#ifdef HMD_INTGERATION
	if ((GEngine->HMDDevice.IsValid() == true) && ((GEngine->HMDDevice->IsHeadTrackingAllowed() == true) || (GEngine->IsStereoscopic3D() == true)))
	{
		bHMDActive = true;
	}
#endif // HMD_INTGERATION.
	if (bHMDActive == false)
	{
		if ((InputComponent) && (bInCarCameraActive == true))
		{
			FRotator HeadRotation = InternalCamera->RelativeRotation;
			HeadRotation.Pitch += InputComponent->GetAxisValue(LookUpBinding);
			HeadRotation.Yaw += InputComponent->GetAxisValue(LookRightBinding);
			InternalCamera->RelativeRotation = HeadRotation;
		}
	}

	// Pass the engine RPM to the sound component.
	float RPMToAudioScale = 2500.0f / GetVehicleMovementComponent()->GetEngineMaxRotationSpeed();
	EngineSoundComponent->SetFloatParameter(EngineAudioRPM, GetVehicleMovementComponent()->GetEngineRotationSpeed()*RPMToAudioScale);
}

void AWheeledVehicleNW::BeginPlay()
{
	Super::BeginPlay();

	bool bWantInCar = false;
	// First disable both speed/gear displays.
	bInCarCameraActive = false;
	InCarSpeed->SetVisibility(bInCarCameraActive);
	InCarGear->SetVisibility(bInCarCameraActive);

#ifdef HMD_INTGERATION
	// Enable in car view if HMD is attached.
	bWantInCar = GEngine->HMDDevice.IsValid()
#endif // HMD_INTGERATION

		EnableIncarView(bWantInCar);
	// Start an engine sound playing.
	EngineSoundComponent->Play();
}

void AWheeledVehicleNW::OnResetVR()
{
#ifdef HMD_INTGERATION
	if (GEngine->HMDDevice.IsValid())
	{
		GEngine->HMDDevice->ResetOrientationAndPosition();
		InternalCamera->SetRelativeLocation(InternalCameraOrigin);
		GetController()->SetControlRotation(FRotator());
	}
#endif // HMD_INTGERATION.
}

void AWheeledVehicleNW::UpdateHUDStrings()
{
	float KPH = FMath::Abs(GetVehicleMovementComponent()->GetForwardSpeed()) * 0.036f;
	int32 KPH_int = FMath::FloorToInt(KPH);

	// Using FText because this is display text that should be localizable.
	SpeedDisplayString = FText::Format(LOCTEXT("SpeedFormat", "{0} km/h"), FText::AsNumber(KPH_int));

	if (bInReverseGear == true)
	{
		GearDisplayString = FText(LOCTEXT("ReverseGear", "R"));
	}
	else
	{
		int32 Gear = GetVehicleMovementComponent()->GetCurrentGear();
		GearDisplayString = (Gear == 0) ? LOCTEXT("N", "N") : FText::AsNumber(Gear);
	}
}

void AWheeledVehicleNW::SetupInCarHUD()
{
	APlayerController* PlayerController = Cast<APlayerController>(GetController());
	if ((PlayerController != nullptr) && (InCarSpeed != nullptr) && (InCarGear != nullptr))
	{
		// Setup the text render component strings.
		InCarSpeed->SetText(SpeedDisplayString);
		InCarGear->SetText(GearDisplayString);

		if (bInReverseGear == false)
		{
			InCarGear->SetTextRenderColor(GearDisplayColor);
		}
		else
		{
			InCarGear->SetTextRenderColor(GearDisplayReverseColor);
		}
	}
}

void AWheeledVehicleNW::UpdatePhysicsMaterial()
{
	if (GetActorUpVector().Z < 0)
	{
		if (bIsLowFriction == true)
		{
			GetMesh()->SetPhysMaterialOverride(NonSlipperyMaterial);
			bIsLowFriction = false;
		}
		else
		{
			GetMesh()->SetPhysMaterialOverride(SlipperyMaterial);
			bIsLowFriction = true;
		}
	}
}

Hi ,
creating separate thread just for N Wheeled is great idea - hope this thread will be kind of “first aid” for everyone having issues with this :slight_smile:
Couple days ago I wrote to you about my issues. Thanks for your attention, my custom WheeledVehicleMovementComponentNW class is working:
34e2c114429dfa4710911f21f32e9ab478f65429.jpeg

Of course it is veeery early version and needs hundreds improvements, like damping etc.
In matter of fact I made it similar way to N Wheeled Vehicles - Showcase - Epic Developer Community Forums Just like Edson I removed some features, eg. Ackermann system and various differential types due to PhysX limitations.
Last time I didnt notice I lost my WheelHandler modified class due to Visual Studio crash - without that little piece of code engine didnt know theres sth to do with additional wheels and thats why my AMV had broken rear axles :slight_smile:
Time for bad news - I dont know why, but I cant move forward/backward, steering works fine as it seen in the picture above. I thought theres something wrong with my model, but all bones are fixed properly. Moving the root to (0,0,0) or modifying engine/torque/transmission parameters gives nothing. I know theres simple gimmick to fix it, but for now I don`t have any idea :confused:

Did you make a function to cycle through and tell the system how many wheels are driven? By default wheels are decoupled from the drivetrain, so you have to enable them. I changed the differential setup to only handle the wheel Id, and the IsDriven boolen, as those are the only things the PhysXNW class takes into consideration, I made a struct and an array to handle these values and set them up in the constructor for my initial tire count, though I’m sure there’s a better way, I could’ve added these to the wheel setup struct, but I like to avoid recompiling the engine as much as possible. If you have any problems, I’ll post the code in here for you, just let me know. Also, I’ve never seen that thread and I’ve googled and search for N Wheeled vehicle answers like a thousand times. That would’ve been so helpful a couple of weeks ago.

My setup ended up looking like this.

i admit i’m no help ref coding, but i’m interested
i’ve followed the main threads
Horrible Car Physics
[The Re-Inventing the Wheel Thread](https://forums.Horrible vehicle physicsunrealengine.com/showthread.php?88495-The-Re-Inventing-the-Wheel-Thread&highlight=wheel)
Tanks-tracks-and-N-wheeled-vehicles

i do have certain knowledge of having work on real vehicles including tanks and trucks, the simple fact that the second steered axle should have ca: half the turn angle (ackermann) along with castor/camber/toe-in/out , and tank neutral turns
but that for a later date.

i do wish you guys would get together a fix the vehicles for us.

Right on Geodav, your knowledge would be very helpful as my knowledge of vehicles is rather limited. As far as the ackermann, none of the PhysX ackermann is usable for the N Wheel class, so that’ll be an issue of calculating how many wheels are controlled by steering and there alignment, and then doing some form of Ackermann calculations to make sure it’s all correct. Once I get this transmission working on the N Wheel vehicle, I’ll progress to the more egregious issues such as the pop that happens from the suspension prediction, which I’ve already attempted to fix with more traces but to no avail. So it’s either a spherical trace on the wheels or some sort of magnitude of force management when the suspension reacts, or both. One wheel coming into abrupt contact with another should not cause the force of the suspension to react so violently that it flips the vehicle in the opposite direction of contact, ignoring the mass of the vehicles involved. There’s also the problem of vehicle becoming it’s own gravitational force when it leaves the ground and rotating excessively around it’s own center of mass. These are the two biggest problems that plague the vehicles in general, at least from my perspective. Though there’s also the issue of the confusing way Phat is handled, this really becomes a problem when trying to swap meshes on a vehicle, as the collision varies, but it gets processed off of the skeleton so you can either have the skeleton collision that you set or a very broken variant of the collision if you try using static meshes.

Alright, well the transmission works now, though I don’t understand why, it’s very confusing. I’ll make sure I got the rest of this figured out and then I’ll move onto the suspension pop, but that’ll probably takes some time, physics isn’t my strong suit, and even though I’ll have my CS degree in 8 days, PhysX, as well as core engine features are way more complicated than anything I studied in school. So, fair warning, it could take some time by myself.

The source of all my grief with the transmission is from this little bit of code, I removed it and all is well.

#if WITH_VEHICLE
// The instanced physX vehicle.
physx::PxVehicleDriveNW* PVehicleDrive;
#endif

So, my code was way more complicated than it needed to be, and now it’s far more simplified, I’ll tune it a little bit and put it in here, though it’s pretty basic now, and actually it’s really similar to his https://forums.unrealengine.com/showthread.php?84864-N-Wheeled-Vehicles&highlight=n+wheeled+vehicle but it’s not an engine class, after I removed all the dead weight that was counter productive to mine functioning. Honestly we’re just replicating the 4W class using NW instead with minor adjustments.

Ok, so I went ahead and added all the code to the top to make the basic N Wheel vehicle function, the only engine modification is adding the VehicleDriveNW to the switch statement in the PhysXVehicleManager class. Everything else can be done without engine compilation, though once this gets refined, I’ll make it an engine class and see if we can’t get it added to UE4. Well, I gotta do some last minute school work, but I’ll fix the wheel setup to be ordered in a bit though.

I made this today, a modified variant of the buggy, I had to remake the skeleton because I was having some crazy reimport problems, but I got the suspension functioning for all six wheels though, so that’s cool, only issue is the scale is really tiny, not much bigger than the advanced vehicle template model, but I could fix that by reimporting it if necessary. I don’t know how epic would feel about it being used, but I’m sure they’d be OK with it. This is for UE4 after all.

If you want it for a test vehicle I’ll put the .uasset on google drive or something for you guys.

for asset scale you need to be really careful due to the physics, best way to check is to add the 3rd-person template to your project then you can compare your asset to the size of the maniquin.

i’ve no idea why the advanced vehicle template has a half scale vehicle amybe that was the only way he could get it to work with the avaible code !!!

ps would it be posible to make it a code plug-in that way we (testers) don’t need the github version of the engine

super interested in this.
been trying on and off for over a year to fix that suspension pop, if you can find that gremlin ill buy you a pint. the very best of luck with that.
and if you need help turning what you have into a plugin give me a shout :slight_smile:

Suspension pop happens because lenght of Spring can change rapidly from frame to frame. You either need an actual collision mesh for the wheel (as I’ve done for aerosled) or code in the logic where spring can produce only limited amount of “spring force” per second. Suspension stiffness is already there in code, it serves as a upper bound of spring force. The velocity damping is already there as well and it removes oscillation. What is necessary is a limit of delta of suspension force. The other option is to add inertia of the spring, so that total suspension force will be dampened by inertia of the spring.
The best scenario is to have both, the solid body on the wheel and inertia on the spring.

Yeah Geodav, by scaling it, I meant I would do it back in 3ds Max, and redo the skinning. That way there wont be any issues with physics, that was the reason I had to redo it in the first place, the physics assets were showing up in game at about 10 times there actual size, that happens when the skeleton gets scaled disproportionately to the model on import, a weird quirk of the engine. As far as making it a plug-in it’s been a long time since I’ve attempted that, but I can give it a shot.

Right on Bored Engineer, so you mean something like the greater the suspension force, the greater the dampening of the inertia, barring it meets some threshold? While also keeping track of spring force, just in case?

Oh and Tegleg, my bad, I didn’t even see that you could help with making it a plugin, that would be awesome. Then I could just focus on working on the code that fixes it.

ah you don’t really want me to explain how a shock absorber works do you :wink:
the fact that is works more on the upward than the downward is known
been like 34 years since i learn that sort of stuff

According to PhysX documentation shock absorbers are already implemented and sprung mass is taken into account as well:
http://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/Vehicles.html
search for PxVehicleSuspensionData section]
Perhaps it requires more tuning to get it right. Unfortunately they don’t explain a complete algorithm there. Might need to look into code for this.

I didn’t spend much time on this yesterday other than looking into the PhysX documentation a bit. Once I finish the final draft of this paper, I’ll get back to work on it.

OK, so I exposed the camber variables to the Wheel class, as well as adding a variable that multiplies the end result of the spring strength, to see if modifying it at run time, could lessen the pop. I’m compiling the engine now, so I’ll let you guys know if it provides any sort of noticeable improvement. Though I was just thinking, I’m not sure if the wheels receive an update after their creation.