// Copyright Epic Games, Inc. All Rights Reserved. #include "HeadlessChaos.h" #include "HeadlessChaosTestUtility.h" // for System Unit Tests #include "AerodynamicsSystem.h" #include "AerofoilSystem.h" #include "TransmissionSystem.h" #include "EngineSystem.h" #include "WheelSystem.h" #include "TireSystem.h" #include "SuspensionSystem.h" #include "SuspensionUtility.h" #include "SteeringUtility.h" #include "TransmissionUtility.h" // for Simulation Tests #include "Chaos/PBDRigidsEvolutionGBF.h" #include "Chaos/Box.h" #include "Chaos/Sphere.h" #include "Chaos/Utilities.h" ////////////////////////////////////////////////////////////////////////// // These tests are mostly working in real word units rather than Unreal // units as it's easier to tell if the simulations are working close to // reality. i.e. Google stopping distance @ 30MPH ==> typically 15 metres ////////////////////////////////////////////////////////////////////////// namespace ChaosTest { using namespace Chaos; GTEST_TEST(AllTraits, VehicleTest_VehicleUtilityGraph) { Chaos::FGraph Graph; Graph.Add(FVector2D(10.f, 0.f)); Graph.Add(FVector2D(20.f, 30.f)); Graph.Add(FVector2D(30.f, 40.f)); Graph.Add(FVector2D(40.f, 60.f)); Graph.Add(FVector2D(50.f, 90.f)); float Tolerance = 0.001f; EXPECT_NEAR(Graph.EvaluateY(-5.f), 0.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(5.f), 0.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(10.f), 0.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(15.f), 15.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(40.f), 60.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(25.f), 35.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(45.f), 75.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(50.f), 90.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(55.f), 90.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(60.f), 90.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(1.0E21f), 90.f, Tolerance); EXPECT_NEAR(Graph.EvaluateY(-1.0E21f), 0.f, Tolerance); } GTEST_TEST(AllTraits, VehicleTest_CalculateSlipAngle) { float SlipAngleFwds = FVehicleUtility::CalculateSlipAngle(0, 5); float SlipAngleReverse = FVehicleUtility::CalculateSlipAngle(0, -5); EXPECT_NEAR(SlipAngleFwds, 0.0f, 0.001f); EXPECT_NEAR(SlipAngleFwds, SlipAngleReverse, 0.001f); float SlipAngleFwdsSide = FVehicleUtility::CalculateSlipAngle(1, 5); float SlipAngleReverseSide = FVehicleUtility::CalculateSlipAngle(1, -5); float SlipAngleFwdsSide2 = FVehicleUtility::CalculateSlipAngle(-1, 5); float SlipAngleReverseSide2 = FVehicleUtility::CalculateSlipAngle(-1, -5); EXPECT_NEAR(SlipAngleFwdsSide, SlipAngleReverseSide, 0.001f); EXPECT_NEAR(SlipAngleFwdsSide, SlipAngleFwdsSide2, 0.001f); EXPECT_NEAR(SlipAngleFwdsSide, SlipAngleReverseSide2, 0.001f); float SlipAngleSide1 = FVehicleUtility::CalculateSlipAngle(2, 0); float SlipAngleSide2 = FVehicleUtility::CalculateSlipAngle(-2, 0); EXPECT_NEAR(SlipAngleSide1, HALF_PI, 0.001f); EXPECT_NEAR(SlipAngleSide1, SlipAngleSide2, 0.001f); } GTEST_TEST(AllTraits, VehicleTest_SteeringUtilityTurnRadius) { float RadiusTolerance = 0.01f; float Radius = 3.0f; FVector PtA(0.f, Radius, 0.f); FVector PtB(Radius, 0.f, 0.f); FVector PtC(0.f, -Radius, 0.f); FVector PtD(FMath::Sin(PI/5.f)* Radius, FMath::Cos(PI / 5.f)* Radius, 0.f); float CalculatedRadius = FVehicleUtility::TurnRadiusFromThreePoints(PtA, PtB, PtC); EXPECT_LT(CalculatedRadius - Radius, RadiusTolerance); CalculatedRadius = FVehicleUtility::TurnRadiusFromThreePoints(PtB, PtA, PtC); EXPECT_LT(CalculatedRadius - Radius, RadiusTolerance); CalculatedRadius = FVehicleUtility::TurnRadiusFromThreePoints(PtC, PtB, PtA); EXPECT_LT(CalculatedRadius - Radius, RadiusTolerance); CalculatedRadius = FVehicleUtility::TurnRadiusFromThreePoints(PtA, PtB, PtD); EXPECT_LT(CalculatedRadius - Radius, RadiusTolerance); // no answer all points lie on a line, no radius possible, returns 0 CalculatedRadius = FVehicleUtility::TurnRadiusFromThreePoints(FVector(1.f,0.f,0.f), FVector(2.f, 0.f, 0.f), FVector(3.f, 0.f, 0.f)); EXPECT_LT(CalculatedRadius, RadiusTolerance); float LargeRadius = 55.0f; FVector LargePtA(FMath::Sin(PI / 5.f) * LargeRadius, FMath::Cos(PI / 5.f) * LargeRadius, 0.f); FVector LargePtB(FMath::Sin(PI / 4.f) * LargeRadius, FMath::Cos(PI / 4.f) * LargeRadius, 0.f); FVector LargePtC(FMath::Sin(PI / 3.f) * LargeRadius, FMath::Cos(PI / 3.f) * LargeRadius, 0.f); CalculatedRadius = FVehicleUtility::TurnRadiusFromThreePoints(LargePtA, LargePtB, LargePtC); EXPECT_LT(CalculatedRadius - LargeRadius, RadiusTolerance); } GTEST_TEST(AllTraits, VehicleTest_SteeringUtilityIntersectTwoCircles) { { float R1 = 3.f; float R2 = 2.f; FVector2D IntersectionPt; bool ResultOk = FSteeringUtility::IntersectTwoCircles(R1, R2, 0.5f, IntersectionPt); EXPECT_FALSE(ResultOk); ResultOk = FSteeringUtility::IntersectTwoCircles(R1, R2, 6.0f, IntersectionPt); EXPECT_FALSE(ResultOk); ResultOk = FSteeringUtility::IntersectTwoCircles(R1, R2, 5.0f, IntersectionPt); EXPECT_TRUE(ResultOk); EXPECT_LT(IntersectionPt.X - 3.f, SMALL_NUMBER); EXPECT_LT(IntersectionPt.Y, SMALL_NUMBER); ResultOk = FSteeringUtility::IntersectTwoCircles(R1, R2, 1.0f, IntersectionPt); EXPECT_TRUE(ResultOk); EXPECT_LT(IntersectionPt.X - 3.f, SMALL_NUMBER); EXPECT_LT(IntersectionPt.Y, SMALL_NUMBER); } { float Tolerance = 0.001f; float R1 = 3.f; float R2 = 2.f; FVector2D IntersectionPt; bool ResultOk = false; for (float D = 1.f; D <= 5.0f; D += 0.2f) { FVector2D C1(0, 0); FVector2D C2(D, 0); ResultOk = FSteeringUtility::IntersectTwoCircles(R1, R2, D, IntersectionPt); EXPECT_TRUE(ResultOk); EXPECT_GT(IntersectionPt.X, 0.f); EXPECT_GE(IntersectionPt.Y, 0.f); EXPECT_LT(IntersectionPt.Y, R1); EXPECT_LT(IntersectionPt.Y, R2); EXPECT_LT((IntersectionPt - C1).Size() - R1, Tolerance); EXPECT_LT((C2 - IntersectionPt).Size() - R2, Tolerance); } } } GTEST_TEST(AllTraits, VehicleTest_SteeringUtilityCalcJointPositions) { float T = 1.0f; // Track width float Beta = 0.f; // Angle float R = 0.25f; // Radius FVector2D C1, C2; // steering rod centre, track rod centre float R1, R2; // steering rod radius, track rod radius FSteeringUtility::CalcJointPositions(T, Beta, R, C1, R1, C2, R2); EXPECT_LT(R1 - T/2.f, SMALL_NUMBER); EXPECT_LT(R2 - R, SMALL_NUMBER); EXPECT_LT(C1.X, SMALL_NUMBER); EXPECT_LT(C1.Y, SMALL_NUMBER); EXPECT_LT(C2.X - T/2.f, SMALL_NUMBER); EXPECT_LT(C2.Y-R, SMALL_NUMBER); T = 1.0f; Beta = 45.0f; R = 0.25f; FSteeringUtility::CalcJointPositions(T, Beta, R, C1, R1, C2, R2); float Dist = FMath::Sqrt(R*R/2.f); EXPECT_LT(R1 - (T / 2.f - Dist), SMALL_NUMBER); EXPECT_LT(R2 - R, SMALL_NUMBER); EXPECT_LT(C1.X, SMALL_NUMBER); EXPECT_LT(C1.Y, SMALL_NUMBER); EXPECT_LT(C2.X - T / 2.f, SMALL_NUMBER); EXPECT_LT(C2.Y - Dist, SMALL_NUMBER); T = 2.0f; Beta = 18.0f; R = 0.25f; FSteeringUtility::CalcJointPositions(T, Beta, R, C1, R1, C2, R2); float Input = 0.0f; float OutSteerAngle; FVector2D OutC1; FVector2D OutPt; FSteeringUtility::CalculateAkermannAngle(false, Input, C2, R1, R2, OutSteerAngle, OutC1, OutPt); EXPECT_LT(OutSteerAngle - Beta, KINDA_SMALL_NUMBER); EXPECT_GT(OutPt.X, 0.f); EXPECT_LT(OutPt.X, T/ 2.0f); } GTEST_TEST(AllTraits, VehicleTest_SteeringUtilityAkermannSetup) { float WheelBase = 3.8f; float TrackWidth = 1.8f; float R = 0.25f; float Beta = FSteeringUtility::CalculateBetaDegrees(TrackWidth, WheelBase); // This is a bit pointless I'm just doing the same sum - is there another way to confirm the results EXPECT_LT(Beta - RadToDeg(FMath::Atan2(0.9f, 3.8f)), KINDA_SMALL_NUMBER); // Beta is about 18 degrees +/- on a normal car EXPECT_GT(Beta, 10.f); EXPECT_LT(Beta, 25.f); float H, S; FSteeringUtility::AkermannSetup(TrackWidth, Beta, R, H, S); // This is a bit pointless I'm just doing the same sum - is there another way to confirm the results EXPECT_LT(S - (TrackWidth - 2.0f * FMath::DegreesToRadians(FMath::Sin(Beta)) * R), KINDA_SMALL_NUMBER); EXPECT_LT(H, R); EXPECT_LT(S, TrackWidth); EXPECT_GT(H, 0.f); EXPECT_GT(H, 0.f); } GTEST_TEST(AllTraits, VehicleTest_TransmissionUtilityIsWheelPowered) { TArray Wheels; FSimpleWheelConfig SetupFront; SetupFront.AxleType = FSimpleWheelConfig::EAxleType::Front; SetupFront.EngineEnabled = false; FSimpleWheelConfig SetupRear; SetupRear.AxleType = FSimpleWheelConfig::EAxleType::Rear; SetupRear.EngineEnabled = true; for (int I = 0; I < 2; I++) { FSimpleWheelSim WheelFront(&SetupFront); Wheels.Add(WheelFront); } for (int I = 0; I < 4; I++) { FSimpleWheelSim WheelRear(&SetupRear); Wheels.Add(WheelRear); } EXPECT_EQ(FTransmissionUtility::GetNumWheelsOnAxle(FSimpleWheelConfig::EAxleType::Front, Wheels), 2); EXPECT_EQ(FTransmissionUtility::GetNumWheelsOnAxle(FSimpleWheelConfig::EAxleType::Rear, Wheels), 4); EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::AllWheelDrive, Wheels[0]), true); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::AllWheelDrive, Wheels[1]), true); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::AllWheelDrive, Wheels[2]), true); // rear EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::AllWheelDrive, Wheels[5]), true); // rear EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::FrontWheelDrive, Wheels[0]), true); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::FrontWheelDrive, Wheels[1]), true); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::FrontWheelDrive, Wheels[2]), false); // rear EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::FrontWheelDrive, Wheels[5]), false); // rear EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::RearWheelDrive, Wheels[0]), false); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::RearWheelDrive, Wheels[1]), false); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::RearWheelDrive, Wheels[2]), true); // rear EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::RearWheelDrive, Wheels[5]), true); // rear EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::UndefinedDrive, Wheels[0]), false); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::UndefinedDrive, Wheels[1]), false); // front EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::UndefinedDrive, Wheels[2]), true); // rear EXPECT_EQ(FTransmissionUtility::IsWheelPowered(EDifferentialType::UndefinedDrive, Wheels[5]), true); // rear } GTEST_TEST(AllTraits, VehicleTest_TransmissionUtilityGetTorqueRatioForWheel) { TArray Wheels; FSimpleWheelConfig SetupFront; SetupFront.AxleType = FSimpleWheelConfig::EAxleType::Front; SetupFront.EngineEnabled = false; FSimpleWheelConfig SetupRear; SetupRear.AxleType = FSimpleWheelConfig::EAxleType::Rear; SetupRear.EngineEnabled = true; for (int I=0; I < 2; I++) { FSimpleWheelSim WheelFront(&SetupFront); Wheels.Add(WheelFront); } for (int I = 0; I < 4; I++) { FSimpleWheelSim WheelRear(&SetupRear); Wheels.Add(WheelRear); } EXPECT_EQ(FTransmissionUtility::GetNumWheelsOnAxle(FSimpleWheelConfig::EAxleType::Front, Wheels), 2); EXPECT_EQ(FTransmissionUtility::GetNumWheelsOnAxle(FSimpleWheelConfig::EAxleType::Rear, Wheels), 4); FSimpleDifferentialConfig DiffSetup; DiffSetup.FrontRearSplit = 0.5f; FSimpleDifferentialSim Differential(&DiffSetup); float Error = 0.01f; DiffSetup.DifferentialType = EDifferentialType::AllWheelDrive; EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 0, Wheels), 0.25f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 1, Wheels), 0.25f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 2, Wheels), 0.125f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 3, Wheels), 0.125f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 4, Wheels), 0.125f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 5, Wheels), 0.125f, Error); // rear DiffSetup.DifferentialType = EDifferentialType::FrontWheelDrive; EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 0, Wheels), 0.5f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 1, Wheels), 0.5f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 2, Wheels), 0.0f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 3, Wheels), 0.0f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 4, Wheels), 0.0f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 5, Wheels), 0.0f, Error); // rear DiffSetup.DifferentialType = EDifferentialType::RearWheelDrive; EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 0, Wheels), 0.0f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 1, Wheels), 0.0f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 2, Wheels), 0.25f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 3, Wheels), 0.25f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 4, Wheels), 0.25f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 5, Wheels), 0.25f, Error); // rear DiffSetup.DifferentialType = EDifferentialType::UndefinedDrive; EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 0, Wheels), 0.0f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 1, Wheels), 0.0f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 2, Wheels), 0.25f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 3, Wheels), 0.25f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 4, Wheels), 0.25f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 5, Wheels), 0.25f, Error); // rear Differential.FrontRearSplit = 0.8f; // more torque to the rear DiffSetup.DifferentialType = EDifferentialType::AllWheelDrive; EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 0, Wheels), 0.1f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 1, Wheels), 0.1f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 2, Wheels), 0.2f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 3, Wheels), 0.2f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 4, Wheels), 0.2f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 5, Wheels), 0.2f, Error); // rear for (int I = 0; I < 6; I++) { Wheels[I].AccessSetup().AxleType = FSimpleWheelConfig::EAxleType::UndefinedAxle; } // front 2 have SetupFront.EngineEnabled = false, the rest are true EXPECT_EQ(FTransmissionUtility::GetNumDrivenWheels(Wheels), 4); EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 0, Wheels), 0.f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 1, Wheels), 0.f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 2, Wheels), 1.f / 4.f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 3, Wheels), 1.f / 4.f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 4, Wheels), 1.f / 4.f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 5, Wheels), 1.f / 4.f, Error); // rear // now turn off torque to the rear two Wheels[4].EngineEnabled = false; Wheels[5].EngineEnabled = false; EXPECT_EQ(FTransmissionUtility::GetNumDrivenWheels(Wheels), 2); EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 0, Wheels), 0.f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 1, Wheels), 0.f, Error); // front EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 2, Wheels), 1.f / 2.f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 3, Wheels), 1.f / 2.f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 4, Wheels), 0.f, Error); // rear EXPECT_NEAR(FTransmissionUtility::GetTorqueRatioForWheel(Differential, 5, Wheels), 0.f, Error); // rear } GTEST_TEST(AllTraits, VehicleTest_SystemTemplate) { FSimpleTireConfig Setup; { Setup.Radius = 0.44f; } FSimpleTireSim Tire(&Setup); EXPECT_LT(Tire.AccessSetup().Radius - Setup.Radius, SMALL_NUMBER); EXPECT_LT(Tire.Setup().Radius - Setup.Radius, SMALL_NUMBER); } // Aerodynamics GTEST_TEST(AllTraits, VehicleTest_Aerodynamics) { FSimpleAerodynamicsConfig Setup; { Setup.AreaMetresSquared = 1.f * 2.f; // 1x2 m Setup.DragCoefficient = 0.5f; Setup.DownforceCoefficient = 0.1f; } FSimpleAerodynamicsSim Aerofoil(&Setup); Aerofoil.SetDensityOfMedium(RealWorldConsts::AirDensity()); float Drag = 0; Drag = Aerofoil.GetDragForceFromVelocity(0.f); EXPECT_LT(Drag, SMALL_NUMBER); Drag = Aerofoil.GetDragForceFromVelocity(1.f); // 1m.s-1 EXPECT_LT(Drag - (RealWorldConsts::AirDensity() * 0.5f), SMALL_NUMBER); Drag = Aerofoil.GetDragForceFromVelocity(5.f); // 5m.s-1 EXPECT_LT(Drag - (RealWorldConsts::AirDensity() * 0.5f * 25.f), SMALL_NUMBER); Drag = Aerofoil.GetDragForceFromVelocity(10.f); // 10m.s-1 EXPECT_LT(Drag - (RealWorldConsts::AirDensity() * 0.5f * 100.f), SMALL_NUMBER); float Lift = 0; Lift = Aerofoil.GetLiftForceFromVelocity(0.f); EXPECT_LT(Lift, SMALL_NUMBER); Lift = Aerofoil.GetLiftForceFromVelocity(1.f); EXPECT_LT(Lift - (RealWorldConsts::AirDensity() * 0.1f), SMALL_NUMBER); Lift = Aerofoil.GetLiftForceFromVelocity(5.f); EXPECT_LT(Lift - (RealWorldConsts::AirDensity() * 0.1f * 25.f), SMALL_NUMBER); Lift = Aerofoil.GetLiftForceFromVelocity(10.f); EXPECT_LT(Lift - (RealWorldConsts::AirDensity() * 0.1f * 100.f), SMALL_NUMBER); // investigating Unral Units vs real world units /*{ FSimpleAerodynamicsConfig Setup; { Setup.AreaMetresSquared = 1.f * 2.f; // 1x2 m Setup.DragCoefficient = 0.5f; Setup.DownforceCoefficient = 0.1f; } /////////////////////////////////////////////////////////////// FSimpleAerodynamicsSim Aerofoil(&Setup); Aerofoil.SetDensityOfMedium(RealWorldConsts::AirDensity()); float Drag5 = 0; Drag5 = Aerofoil.GetDragForceFromVelocity(5.f); ///////////////////// FSimpleAerodynamicsConfig SetupB; { Setup.AreaMetresSquared = 1.f * 2.f; // 1x2 m Setup.DragCoefficient = 0.5f; Setup.DownforceCoefficient = 0.1f; } FSimpleAerodynamicsSim AerofoilB(&Setup); Aerofoil.SetDensityOfMedium(RealWorldConsts::AirDensity()); //////////////////////////////////////////////////////////////////// // Trying to get head around standard vs unreal units. // If the mass is the same between the two simulations then the // the following is true. float PosA = 0, PosB = 0; float VelA = 100.0f; float VelB = 10000.0f; float DeltaTime = 1.f / 30.f; for (int i = 0; i < 200; i++) { float DragA = Aerofoil.GetDragForceFromVelocity(VelA); float DragB = (AerofoilB.GetDragForceFromVelocity(CmToM(VelB))); // no final MToCm conversion VelA += (DragA / 1000.0f) * DeltaTime; VelB += (DragB / 1000.0f) * DeltaTime; PosA += VelA * DeltaTime; PosB += VelB * DeltaTime; UE_LOG(LogChaos, Warning, TEXT("Drag %f %f"), DragA, DragB); UE_LOG(LogChaos, Warning, TEXT("Vel %f %f"), VelA, VelB); UE_LOG(LogChaos, Warning, TEXT("------")); } }*/ } GTEST_TEST(AllTraits, VehicleTest_Aerofoil) { FAerofoilConfig RWingSetup; RWingSetup.Offset.Set(-0.8f, 3.0f, 0.0f); RWingSetup.UpAxis.Set(0.0f, 0.f, 1.0f); RWingSetup.Area = 8.2f; RWingSetup.Camber = 3.0f; RWingSetup.MaxControlAngle = 1.0f; RWingSetup.StallAngle = 16.0f; RWingSetup.Type = EAerofoilType::Wing; FAerofoil RWing(&RWingSetup); RWing.SetControlSurface(0.0f); RWing.SetDensityOfMedium(RealWorldConsts::AirDensity()); float Altitude = 100.0f; float DeltaTime = 1.0f / 30.0f; ////////////////////////////////////////////////////////////////////////// FTransform BodyTransform = FTransform::Identity; FVector Velocity(1.0f, 0.0f, 0.0f); float AOAFlat = RWing.CalcAngleOfAttackDegrees(FVector(0, 0, 1), FVector(-1, 0, 0)); EXPECT_LT(AOAFlat, SMALL_NUMBER); float AOAFlat2 = RWing.CalcAngleOfAttackDegrees(FVector(0, 0, 1), FVector(1, 0, 0)); EXPECT_LT(AOAFlat2, SMALL_NUMBER); float AOA90 = RWing.CalcAngleOfAttackDegrees(FVector(0, 0, 1), FVector(0, 0, 1)); EXPECT_LT(AOA90 - 90.0f, SMALL_NUMBER); float AOA45 = RWing.CalcAngleOfAttackDegrees(FVector(0, 0, 1), FVector(0, 0.707, 0.707)); EXPECT_LT(AOA45 - 45.0f, SMALL_NUMBER); ////////////////////////////////////////////////////////////////////////// // Lift { float Zero = RWing.CalcLiftCoefficient(0, 0); EXPECT_LT(Zero, SMALL_NUMBER); float Two = RWing.CalcLiftCoefficient(2, 0); float NegTwo = RWing.CalcLiftCoefficient(-2, 0); EXPECT_GT(Two, SMALL_NUMBER); EXPECT_LT(NegTwo, SMALL_NUMBER); EXPECT_LT(Two - FMath::Abs(NegTwo), SMALL_NUMBER); float Three = RWing.CalcLiftCoefficient(0, 3); float NegThree = RWing.CalcLiftCoefficient(0, -3); EXPECT_GT(Three, SMALL_NUMBER); EXPECT_LT(NegThree, SMALL_NUMBER); EXPECT_LT(Three - FMath::Abs(NegThree), SMALL_NUMBER); float Nine = RWing.CalcLiftCoefficient(6, 3); float NegNine = RWing.CalcLiftCoefficient(-6, -3); EXPECT_GT(Nine, SMALL_NUMBER); EXPECT_LT(NegNine, SMALL_NUMBER); EXPECT_LT(Nine - FMath::Abs(NegNine), SMALL_NUMBER); float Stall = RWing.CalcLiftCoefficient(RWingSetup.StallAngle, 0); float StallPlus = RWing.CalcLiftCoefficient(RWingSetup.StallAngle, 5); EXPECT_GT(Stall, Nine); EXPECT_GT(Stall, Three); EXPECT_GT(Stall, Two); EXPECT_GT(Stall, StallPlus); } // Drag { float Two = RWing.CalcDragCoefficient(2, 0); float NegTwo = RWing.CalcDragCoefficient(-2, 0); EXPECT_GT(Two, SMALL_NUMBER); EXPECT_GT(NegTwo, SMALL_NUMBER); EXPECT_LT(Two - NegTwo, SMALL_NUMBER); float Six = RWing.CalcDragCoefficient(4, 2); float NegSix = RWing.CalcDragCoefficient(-4, -2); EXPECT_GT(Six, SMALL_NUMBER); EXPECT_GT(NegSix, SMALL_NUMBER); EXPECT_LT(Six - NegSix, SMALL_NUMBER); float AltNegTwo = RWing.CalcDragCoefficient(2, -4); EXPECT_GT(AltNegTwo, SMALL_NUMBER); EXPECT_LT(AltNegTwo - NegTwo, SMALL_NUMBER); } //////////////////////////////////////////////////////////////////////////// FVector Velocity1(0.0f, 0.0f, 10.0f); FVector RWForceZero = RWing.GetForce(BodyTransform, Velocity1, Altitude, DeltaTime); EXPECT_LT(FMath::Abs(RWForceZero.X), SMALL_NUMBER); EXPECT_LT(FMath::Abs(RWForceZero.Y), SMALL_NUMBER); EXPECT_LT(RWForceZero.Z, 0.f); // drag value opposes velocity direction FVector Velocity2(0.0f, 10.0f, 10.0f); FVector RWForce3 = RWing.GetForce(BodyTransform, Velocity2, Altitude, DeltaTime); EXPECT_LT(FMath::Abs(RWForce3.X), SMALL_NUMBER); EXPECT_LT(RWForce3.Y, 0.0f); EXPECT_LT(RWForce3.Z, 0.0f); FVector Velocity3(10.0f, 0.0f, 0.0f); FVector RWForce4 = RWing.GetForce(BodyTransform, Velocity3, Altitude, DeltaTime); EXPECT_LT(RWForce4.X, 0.0f); EXPECT_LT(FMath::Abs(RWForce4.Y), SMALL_NUMBER); EXPECT_GT(RWForce4.Z, 0.0f); } // Transmission GTEST_TEST(AllTraits, VehicleTest_TransmissionManualGearSelection) { FSimpleTransmissionConfig Setup; { Setup.ForwardRatios.Add(4.f); Setup.ForwardRatios.Add(3.f); Setup.ForwardRatios.Add(2.f); Setup.ForwardRatios.Add(1.f); Setup.ReverseRatios.Add(3.f); Setup.ReverseRatios.Add(6.f); Setup.FinalDriveRatio = 4.f; Setup.ChangeUpRPM = 3000; Setup.ChangeDownRPM = 1200; Setup.GearChangeTime = 0.0f; Setup.TransmissionType = ETransmissionType::Manual; Setup.AutoReverse = true; Setup.TransmissionEfficiency = 1.0f; } FSimpleTransmissionSim Transmission(&Setup); EXPECT_EQ(Transmission.GetCurrentGear(), 0); // Immediate Gear Change, since Setup.GearChangeTime = 0.0f Transmission.ChangeUp(); EXPECT_EQ(Transmission.GetCurrentGear(), 1); Transmission.ChangeUp(); Transmission.ChangeUp(); Transmission.ChangeUp(); EXPECT_EQ(Transmission.GetCurrentGear(), 4); Transmission.ChangeUp(); EXPECT_EQ(Transmission.GetCurrentGear(), 4); Transmission.SetGear(1); EXPECT_EQ(Transmission.GetCurrentGear(), 1); Transmission.ChangeDown(); EXPECT_EQ(Transmission.GetCurrentGear(), 0); Transmission.ChangeDown(); EXPECT_EQ(Transmission.GetCurrentGear(), -1); Transmission.ChangeDown(); EXPECT_EQ(Transmission.GetCurrentGear(), -2); Transmission.ChangeDown(); EXPECT_EQ(Transmission.GetCurrentGear(), -2); Transmission.ChangeUp(); EXPECT_EQ(Transmission.GetCurrentGear(), -1); Transmission.ChangeUp(); EXPECT_EQ(Transmission.GetCurrentGear(), 0); Transmission.SetGear(1); // Now change settings so we have a delay in the gear changing Transmission.AccessSetup().GearChangeTime = 0.5f; Transmission.ChangeUp(); EXPECT_EQ(Transmission.GetCurrentGear(), 0); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 0); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 2); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 2); Transmission.SetGear(4); EXPECT_EQ(Transmission.GetCurrentGear(), 0); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 0); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 4); } GTEST_TEST(AllTraits, VehicleTest_TransmissionAutoGearSelection) { FSimpleTransmissionConfig Setup; { Setup.ForwardRatios.Add(4.f); Setup.ForwardRatios.Add(3.f); Setup.ForwardRatios.Add(2.f); Setup.ForwardRatios.Add(1.f); Setup.ReverseRatios.Add(3.f); Setup.ReverseRatios.Add(6.f); Setup.FinalDriveRatio = 4.f; Setup.ChangeUpRPM = 3000; Setup.ChangeDownRPM = 1200; Setup.GearChangeTime = 0.0f; Setup.TransmissionType = ETransmissionType::Automatic; Setup.AutoReverse = true; Setup.TransmissionEfficiency = 1.0f; } FSimpleTransmissionSim Transmission(&Setup); Transmission.SetGear(1, true); Transmission.SetEngineRPM(1400); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 1); Transmission.SetEngineRPM(2000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 1); Transmission.SetEngineRPM(3000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 2); Transmission.SetEngineRPM(2000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 2); Transmission.SetEngineRPM(1000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 1); // stays in first, doesn't change to neutral Transmission.SetEngineRPM(1000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), 1); Transmission.SetGear(-2, true); Transmission.SetEngineRPM(3000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), -2); Transmission.SetEngineRPM(1000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), -1); // stays in reverse first, doesn't change to neutral Transmission.SetEngineRPM(1000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), -1); // changes to next reverse gear Transmission.SetEngineRPM(3000); Transmission.Simulate(0.25f); EXPECT_EQ(Transmission.GetCurrentGear(), -2); } GTEST_TEST(AllTraits, VehicleTest_TransmissionGearRatios) { FSimpleTransmissionConfig Setup; { Setup.ForwardRatios.Add(4.f); Setup.ForwardRatios.Add(3.f); Setup.ForwardRatios.Add(2.f); Setup.ForwardRatios.Add(1.f); Setup.ReverseRatios.Add(3.f); Setup.FinalDriveRatio = 4.f; Setup.ChangeUpRPM = 3000; Setup.ChangeDownRPM = 1200; Setup.GearChangeTime = 0.0f; Setup.TransmissionType = ETransmissionType::Automatic; Setup.AutoReverse = true; Setup.TransmissionEfficiency = 1.0f; } FSimpleTransmissionSim Transmission(&Setup); float Ratio = 0; Ratio = Transmission.GetGearRatio(-1); EXPECT_LT(-12.f - Ratio, SMALL_NUMBER); // -ve output for reverse gears Ratio = Transmission.GetGearRatio(0); EXPECT_LT(Ratio, SMALL_NUMBER); Ratio = Transmission.GetGearRatio(1); EXPECT_LT(16.f - Ratio, SMALL_NUMBER); Ratio = Transmission.GetGearRatio(2); EXPECT_LT(12.f - Ratio, SMALL_NUMBER); Ratio = Transmission.GetGearRatio(3); EXPECT_LT(8.f - Ratio, SMALL_NUMBER); Ratio = Transmission.GetGearRatio(4); EXPECT_LT(4.f - Ratio, SMALL_NUMBER); } // Engine GTEST_TEST(AllTraits, VehicleTest_EngineRPM) { // #todo: fix engine rev out of gear //FSimpleEngineConfig Setup; //{ // Setup.MaxRPM = 5000; // Setup.EngineIdleRPM = 1000; // Setup.MaxTorque = 100.f; // //Setup.TorqueCurve; //} //FSimpleEngineSim Engine(&Setup); //Engine.SetThrottle(0.f); //float DeltaTime = 1.0f / 30.0f; //float TOLERANCE = 0.01f; //// engine idle - no throttle //for (int i = 0; i < 300; i++) //{ // Engine.Simulate(DeltaTime); //} //EXPECT_LT(Engine.GetEngineRPM() - Engine.Setup().EngineIdleRPM, TOLERANCE); //// apply half throttle //Engine.SetThrottle(0.5f); //for (int i = 0; i < 300; i++) //{ // Engine.Simulate(DeltaTime); // //UE_LOG(LogChaos, Warning, TEXT("EngineSpeed %.2f rad/sec (%.1f RPM)"), Engine.GetEngineSpeed(), Engine.GetEngineRPM()); //} //EXPECT_GT(Engine.GetEngineRPM(), Engine.Setup().EngineIdleRPM); ////UE_LOG(LogChaos, Warning, TEXT("")); ////UE_LOG(LogChaos, Warning, TEXT("No Throttle")); //Engine.SetThrottle(0.0f); //// engine idle - no throttle //for (int i = 0; i < 300; i++) //{ // Engine.Simulate(DeltaTime); // //UE_LOG(LogChaos, Warning, TEXT("EngineSpeed %.2f rad/sec (%.1f RPM)"), Engine.GetEngineSpeed(), Engine.GetEngineRPM()); //} //EXPECT_LT(Engine.GetEngineRPM() - Engine.Setup().EngineIdleRPM, TOLERANCE); } // Wheel void SimulateBraking(FSimpleWheelSim& Wheel , const float Gravity , float VehicleSpeed , float DeltaTime , float& StoppingDistanceOut , float& SimulationTimeOut , bool bLoggingEnabled = false ) { StoppingDistanceOut = 0.f; SimulationTimeOut = 0.f; float MaxSimTime = 15.0f; float VehicleMass = 1300.f; float VehicleMassPerWheel = 1300.f / 4.f; Wheel.SetWheelLoadForce(VehicleMassPerWheel * Gravity); Wheel.SetMassPerWheel(VehicleMassPerWheel); // Road speed FVector Velocity = FVector(VehicleSpeed, 0.f, 0.f); // wheel rolling speed matches road speed Wheel.SetMatchingSpeed(Velocity.X); if (bLoggingEnabled) { UE_LOG(LogChaos, Warning, TEXT("--------------------START---------------------")); } while (SimulationTimeOut < MaxSimTime) { // rolling speed matches road speed Wheel.SetVehicleGroundSpeed(Velocity); Wheel.Simulate(DeltaTime); // deceleration from brake, F = m * a, a = F / m, v = dt * F / m Velocity += DeltaTime * Wheel.GetForceFromFriction() / VehicleMassPerWheel; StoppingDistanceOut += Velocity.X * DeltaTime; if (bLoggingEnabled) { UE_LOG(LogChaos, Warning, TEXT("Wheel.GetForceFromFriction() %s"), *Wheel.GetForceFromFriction().ToString()); } if (FMath::Abs(Velocity.X) < 0.05f) { Velocity.X = 0.f; break; // break out early if already stopped } SimulationTimeOut += DeltaTime; } if (bLoggingEnabled) { UE_LOG(LogChaos, Warning, TEXT("---------------------END----------------------")); } } void SimulateAccelerating(FSimpleWheelSim& Wheel , const float Gravity , float FinalVehicleSpeed , float DeltaTime , float& DistanceTravelledOut , float& SimulationTimeOut ) { DistanceTravelledOut = 0.f; SimulationTimeOut = 0.f; float MaxSimTime = 15.0f; float VehicleMass = 1300.f; float VehicleMassPerWheel = VehicleMass / 4.f; Wheel.SetWheelLoadForce(VehicleMassPerWheel * Gravity); Wheel.SetMassPerWheel(VehicleMassPerWheel); // Road speed FVector Velocity = FVector(0.f, 0.f, 0.f); // start from stationary Wheel.SetMatchingSpeed(Velocity.X); while (SimulationTimeOut < MaxSimTime) { Wheel.SetVehicleGroundSpeed(Velocity); Wheel.Simulate(DeltaTime); Velocity += DeltaTime * Wheel.GetForceFromFriction() / VehicleMassPerWheel; DistanceTravelledOut += Velocity.X * DeltaTime; SimulationTimeOut += DeltaTime; if (FMath::Abs(Velocity.X) >= FinalVehicleSpeed) { break; // time is up } } } GTEST_TEST(AllTraits, VehicleTest_WheelBrakingLongitudinalSlip) { FSimpleWheelConfig Setup; Setup.ABSEnabled = false; Setup.TractionControlEnabled = false; Setup.BrakeEnabled = true; Setup.EngineEnabled = true; Setup.WheelRadius = 0.3f; Setup.FrictionMultiplier = 1.0f; Setup.CorneringStiffness = 1000.0f; Setup.SideSlipModifier = 0.7f; FSimpleWheelSim Wheel(&Setup); // Google braking distance at 30mph says 14m (not interested in the thinking distance part) // So using a range 10-20 to ensure we are in the correct ballpark. // If specified more accurately in the test, then modifying the code would break the test all the time. // units meters float Gravity = 9.8f; float StoppingDistanceTolerance = 0.5f; float DeltaTime = 1.f / 30.f; float StoppingDistanceA = 0.f; float SimulationTime = 0.0f; Wheel.SetSurfaceFriction(RealWorldConsts::DryRoadFriction()); // reasonably ideal stopping distance - traveling forwards Wheel.SetBrakeTorque(650); SimulateBraking(Wheel, Gravity, MPHToMS(30.f), DeltaTime, StoppingDistanceA, SimulationTime); EXPECT_GT(StoppingDistanceA, 10.f); EXPECT_LT(StoppingDistanceA, 20.f); // traveling backwards stops just the same float StoppingDistanceReverseDir = 0.f; Wheel.SetBrakeTorque(650); SimulateBraking(Wheel, Gravity, MPHToMS(-30.f), DeltaTime, StoppingDistanceReverseDir, SimulationTime); EXPECT_GT(StoppingDistanceReverseDir, -20.f); EXPECT_LT(StoppingDistanceReverseDir, -10.f); EXPECT_LT(StoppingDistanceA - FMath::Abs(StoppingDistanceReverseDir), StoppingDistanceTolerance); // Changing to units of Cm should yield the same results float MToCm = 100.0f; float StoppingDistanceCm = 0.f; Wheel.SetBrakeTorque(650 * MToCm * MToCm); Wheel.SetWheelRadius(0.3f * MToCm); SimulateBraking(Wheel, Gravity * MToCm, MPHToCmS(30.f), DeltaTime, StoppingDistanceCm, SimulationTime); EXPECT_NEAR(StoppingDistanceCm, StoppingDistanceA * MToCm, 1.0f); // Similar results with different delta time float StoppingDistanceDiffDT = 0.f; Wheel.SetWheelRadius(0.3f); SimulateBraking(Wheel, Gravity, MPHToMS(30.f), DeltaTime * 0.25f, StoppingDistanceDiffDT, SimulationTime); EXPECT_LT(StoppingDistanceA - StoppingDistanceDiffDT, StoppingDistanceTolerance); // barely touching the brake - going to take longer to stop float StoppingDistanceLightBraking = 0.f; Wheel.SetBrakeTorque(150); SimulateBraking(Wheel, Gravity, MPHToMS(30.f), DeltaTime, StoppingDistanceLightBraking, SimulationTime); EXPECT_GT(StoppingDistanceLightBraking, StoppingDistanceA); // locking the wheels / too much brake torque -> dynamic friction rather than static friction -> going to take longer to stop float StoppingDistanceTooHeavyBreaking = 0.f; Wheel.SetBrakeTorque(5000); SimulateBraking(Wheel, Gravity, MPHToMS(30.f), DeltaTime, StoppingDistanceTooHeavyBreaking, SimulationTime); EXPECT_GT(StoppingDistanceTooHeavyBreaking, StoppingDistanceA); // Would have locked the wheels but ABS prevents skidding Wheel.ABSEnabled = true; float StoppingDistanceTooHeavyBreakingABS = 0.f; Wheel.SetBrakeTorque(5000); SimulateBraking(Wheel, Gravity, MPHToMS(30.f), DeltaTime, StoppingDistanceTooHeavyBreakingABS, SimulationTime); EXPECT_LT(StoppingDistanceTooHeavyBreakingABS, StoppingDistanceTooHeavyBreaking); Wheel.ABSEnabled = false; // lower initial speed - stops more quickly float StoppingDistanceLowerSpeed = 0.f; Wheel.SetBrakeTorque(650); SimulateBraking(Wheel, Gravity, MPHToMS(20.f), DeltaTime, StoppingDistanceLowerSpeed, SimulationTime); EXPECT_LT(StoppingDistanceLowerSpeed, StoppingDistanceA); // higher initial speed - stops more slowly float StoppingDistanceHigherSpeed = 0.f; Wheel.SetBrakeTorque(650); SimulateBraking(Wheel, Gravity, MPHToMS(60.f), DeltaTime, StoppingDistanceHigherSpeed, SimulationTime); EXPECT_GT(StoppingDistanceHigherSpeed, StoppingDistanceA); // slippy surface - stops more slowly float StoppingDistanceLowFriction = 0.f; Wheel.SetSurfaceFriction(0.3f); Wheel.SetBrakeTorque(650); SimulateBraking(Wheel, Gravity, MPHToMS(30.f), DeltaTime, StoppingDistanceLowFriction, SimulationTime); EXPECT_GT(StoppingDistanceLowFriction, StoppingDistanceA); } GTEST_TEST(AllTraits, VehicleTest_WheelAcceleratingLongitudinalSlip) { FSimpleWheelConfig Setup; Setup.ABSEnabled = false; Setup.TractionControlEnabled = false; Setup.BrakeEnabled = true; Setup.EngineEnabled = true; Setup.WheelRadius = 0.3f; Setup.FrictionMultiplier = 1.0f; Setup.CorneringStiffness = 1000.0f; Setup.SideSlipModifier = 0.7f; FSimpleWheelSim Wheel(&Setup); // There could be one frame extra computation on the acceleration since the last frame of brake is not using the full // amount of torque, it's clearing the last remaining velocity without pushing the vehicle back in the opposite direction // Hence a slightly larger tolerance for the result float AccelerationResultsTolerance = 1.0f; // meters // units meters float Gravity = 9.8f; float DeltaTime = 1.f / 30.f; float StoppingDistanceA = 0.f; float SimulationTimeBrake = 0.0f; Wheel.SetSurfaceFriction(RealWorldConsts::DryRoadFriction()); // How far & what time does it take to stop from 30MPH to rest Wheel.SetBrakeTorque(650); SimulateBraking(Wheel, Gravity, MPHToMS(30.0f), DeltaTime, StoppingDistanceA, SimulationTimeBrake); // How far and what time does it take to accelerate from rest to 30MPH float SimulationTimeAccel = 0.0f; float DrivingDistanceA = 0.f; Wheel.SetDriveTorque(650); SimulateAccelerating(Wheel, Gravity, MPHToMS(30.0f), DeltaTime, DrivingDistanceA, SimulationTimeAccel); // 0-30 MPH and 30-0 MPH should be the same if there's no slipping and accel torque was same as the brake torque run EXPECT_LT(DrivingDistanceA - StoppingDistanceA, AccelerationResultsTolerance); EXPECT_LT(SimulationTimeAccel - SimulationTimeBrake, AccelerationResultsTolerance); // same range as braking from 30MPH EXPECT_GT(DrivingDistanceA, 10.f); EXPECT_LT(DrivingDistanceA, 20.f); // Unreal units cm - Note for the same results the radius needs to remain at 0.3m and not also be scaled to 30(cm) float SimulationTimeAccelCM = 0.0f; float MToCm = 100.0f; float DrivingDistanceCM = 0.f; Wheel.SetDriveTorque(650.0f * MToCm * MToCm); Wheel.SetWheelRadius(0.3f * MToCm); SimulateAccelerating(Wheel, Gravity * MToCm, MPHToCmS(30.0f), DeltaTime, DrivingDistanceCM, SimulationTimeAccelCM); EXPECT_GT(DrivingDistanceCM, 10.f * MToCm); EXPECT_LT(DrivingDistanceCM, 20.f * MToCm); EXPECT_NEAR(DrivingDistanceCM, DrivingDistanceA * MToCm, AccelerationResultsTolerance); float SimulationTimeAccelSpin = 0.0f; float DrivingDistanceWheelspin = 0.f; Wheel.SetWheelRadius(0.3f); Wheel.SetDriveTorque(5000); // definitely cause wheel spin SimulateAccelerating(Wheel, Gravity, 30.0f, DeltaTime, DrivingDistanceWheelspin, SimulationTimeAccelSpin); // drives further to reach the same speed EXPECT_GT(DrivingDistanceWheelspin, DrivingDistanceA); // takes longer to reach the same speed EXPECT_GT(SimulationTimeAccelSpin, SimulationTimeAccel); // Enable traction control should be better than both of the above float SimulationTimeAccelTC = 0.0f; float DrivingDistanceTC = 0.f; Wheel.TractionControlEnabled = true; Wheel.SetDriveTorque(5000); // definitely cause wheel spin SimulateAccelerating(Wheel, Gravity, MPHToMS(30.0f), DeltaTime, DrivingDistanceTC, SimulationTimeAccelTC); // reaches target speed in a shorter distance EXPECT_LT(DrivingDistanceTC, DrivingDistanceWheelspin); // reaches speed quicker with TC on when wheel would be slipping from drive torque EXPECT_LT(SimulationTimeAccelTC, SimulationTimeAccelSpin); } GTEST_TEST(AllTraits, DISABLED_VehicleTest_WheelLateralSlip) { FSimpleWheelConfig Setup; FSimpleWheelSim Wheel(&Setup); } GTEST_TEST(AllTraits, VehicleTest_WheelRolling) { FSimpleWheelConfig Setup; FSimpleWheelSim Wheel(&Setup); float DeltaTime = 1.f / 30.f; float MaxSimTime = 10.0f; float Tolerance = 0.1f; // wheel friction losses slow wheel speed //------------------------------------------------------------------ // Car is moving FORWARDS - with AMPLE friction we would expect an initially // static rolling wheel to speed up and match the vehicle speed FVector VehicleGroundSpeed(10.0f, 0.f, 0.f); // X is forwards Wheel.SetVehicleGroundSpeed(VehicleGroundSpeed); Wheel.SetSurfaceFriction(1.0f); // Some wheel/ground friction Wheel.SetWheelLoadForce(250.f); // wheel pressed into the ground, to give it grip Wheel.Omega = 0.f; // initially wheel is static EXPECT_LT(Wheel.GetAngularVelocity(), SMALL_NUMBER); // after some time, the wheel picks up speed to match the vehicle speed float SimulatedTime = 0.f; while (SimulatedTime < MaxSimTime) { Wheel.Simulate(DeltaTime); SimulatedTime += DeltaTime; } // there's enough grip to cause the wheel to spin and match the vehivle speed float WheelGroundSpeed = Wheel.GetAngularVelocity() * Wheel.GetEffectiveRadius(); EXPECT_LT(VehicleGroundSpeed.X - WheelGroundSpeed, Tolerance); EXPECT_LT(VehicleGroundSpeed.X - WheelGroundSpeed, Tolerance); EXPECT_GT(Wheel.GetAngularVelocity(), 0.f); // +ve spin on it //------------------------------------------------------------------ // Car is moving BACKWARDS - with AMPLE friction we would expect an initially // static rolling wheel to speed up and match the vehicle speed VehicleGroundSpeed.Set(-10.0f, 0.f, 0.f); // X is -ve not travelling backwards Wheel.SetVehicleGroundSpeed(VehicleGroundSpeed); Wheel.SetSurfaceFriction(1.0f); // Some wheel/ground friction Wheel.SetWheelLoadForce(250.f); // wheel pressed into the ground, to give it grip Wheel.Omega = 0.f; // initially wheel is static EXPECT_LT(Wheel.GetAngularVelocity(), SMALL_NUMBER); // after some time, the wheel picks up speed to match the vehicle speed SimulatedTime = 0.f; while (SimulatedTime < MaxSimTime) { Wheel.Simulate(DeltaTime); SimulatedTime += DeltaTime; } // there's enough grip to cause the wheel to spin and match the vehicle speed WheelGroundSpeed = Wheel.GetAngularVelocity() * Wheel.GetEffectiveRadius(); EXPECT_LT(VehicleGroundSpeed.X - WheelGroundSpeed, Tolerance); EXPECT_LT(VehicleGroundSpeed.X - Wheel.GetWheelGroundSpeed(), Tolerance); EXPECT_LT(Wheel.GetAngularVelocity(), 0.f); // -ve spin on it //------------------------------------------------------------------ // Car is moving FROWARDS - with NO friction we would expect an initially // static wheel to NOT speed up to match the vehicle speed Wheel.SetVehicleGroundSpeed(VehicleGroundSpeed); Wheel.SetSurfaceFriction(0.0f); // No wheel/ground friction Wheel.SetWheelLoadForce(250.f); // wheel pressed into the ground, to give it grip Wheel.Omega = 0.f; // initially wheel is static EXPECT_LT(Wheel.GetAngularVelocity(), SMALL_NUMBER); // after some time, the wheel picks up speed to match the vehicle speed SimulatedTime = 0.f; while (SimulatedTime < MaxSimTime) { Wheel.Simulate(DeltaTime); SimulatedTime += DeltaTime; } WheelGroundSpeed = Wheel.GetAngularVelocity() * Wheel.GetEffectiveRadius(); // wheel is just sliding there's no friction to make it spin EXPECT_LT(WheelGroundSpeed, SMALL_NUMBER); } // Suspension float SumSprungMasses(TArray& SprungMasses) { float Sum = 0.f; for (int I = 0; I < SprungMasses.Num(); I++) { Sum += SprungMasses[I]; } return Sum; } GTEST_TEST(AllTraits, VehicleTest_SuspensionSprungMassesTwoWheels) { float TotalMass = 1000.f; float Tolerance = 0.01f; { // simple 1 Wheels unstable as offset from COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(200, 0, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - 1000.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } { // simple 2 Wheels equally spaced around COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(200, 0, 0)); MassSpringPositions.Add(FVector(-200, 0, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - 500.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - 500.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } { // simple 2 Wheels equally spaced around COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(200, 0, 50)); MassSpringPositions.Add(FVector(-200, 0, -50)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - 500.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - 500.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } { // simple 2 Wheels equally spaced around COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(200, 0, 0)); MassSpringPositions.Add(FVector(0, 0, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - 1000.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - 0.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } { // simple 2 Wheels equally spaced around COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(200, 0, 0)); MassSpringPositions.Add(FVector(-100, 0, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - TotalMass * 2.f / 3.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - TotalMass * 1.f / 3.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } } GTEST_TEST(AllTraits, VehicleTest_SuspensionSprungMassesThreeWheels) { float TotalMass = 1000.f; float Tolerance = 0.01f; { // simple 3 Wheels equally spaced around COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(200, 0, 0)); MassSpringPositions.Add(FVector(-200, -100, 0)); MassSpringPositions.Add(FVector(-200, 100, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - 500), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - 250), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[2] - 250), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } } GTEST_TEST(AllTraits, VehicleTest_SuspensionSprungMassesFourWheels) { float TotalMass = 1000.f; float Tolerance = 0.1f; { // simple 4 Wheels equally spaced around COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(200, 0, 0)); MassSpringPositions.Add(FVector(-200, 0, 0)); MassSpringPositions.Add(FVector(200, -100, 0)); MassSpringPositions.Add(FVector(-200, 100, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - 250.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - 250.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[2] - 250.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[3] - 250.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } { // simple 4 Wheels all weight on rear COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(0, 0, 0)); MassSpringPositions.Add(FVector(-200, 0, 0)); MassSpringPositions.Add(FVector(0, -100, 0)); MassSpringPositions.Add(FVector(-200, 100, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); EXPECT_LT(FMath::Abs(OutSprungMasses[0] - 500.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - 0.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[2] - 250.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[3] - 250.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } { // 4 Wheels all weight on rear COM TArray MassSpringPositions; TArray OutSprungMasses; MassSpringPositions.Add(FVector(100, 0, 0)); MassSpringPositions.Add(FVector(-200, 0, 0)); MassSpringPositions.Add(FVector(100, -100, 0)); MassSpringPositions.Add(FVector(-200, 100, 0)); FSuspensionUtility::ComputeSprungMasses(MassSpringPositions, TotalMass, OutSprungMasses); EXPECT_EQ(MassSpringPositions.Num(), OutSprungMasses.Num()); //for (int i = 0; i < 4; i++) //{ // UE_LOG(LogChaos, Warning, TEXT("SM = %f"), OutSprungMasses[i]); //} EXPECT_LT(FMath::Abs(OutSprungMasses[0] - TotalMass * 1.f / 3.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[1] - TotalMass * 1.f / 6.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[2] - TotalMass * 1.f / 4.f), Tolerance); EXPECT_LT(FMath::Abs(OutSprungMasses[3] - TotalMass * 1.f / 4.f), Tolerance); EXPECT_LT(FMath::Abs(SumSprungMasses(OutSprungMasses) - TotalMass), Tolerance); } } float PlaneZPos = 1.0f; bool RayCastPlane(FVec3& RayStart, FVec3& Direction, float Length, FReal& OutTime, FVec3& OutPosition, FVec3& OutNormal) { TPlane Plane(FVec3(1), FVec3(0, 0, PlaneZPos)); int32 FaceIndex; return Plane.Raycast(RayStart, Direction, Length, 0, OutTime, OutPosition, OutNormal, FaceIndex); } void AddForceAtPosition(FPBDRigidsEvolutionGBF& Evolution, TPBDRigidParticleHandle* Rigid, const FVector& InForce, const FVector& InPosition) { const Chaos::FVec3 WorldCOM = FParticleUtilitiesGT::GetCoMWorldPosition(Rigid); Evolution.SetParticleObjectState(Rigid, EObjectStateType::Dynamic); const Chaos::FVec3 WorldTorque = Chaos::FVec3::CrossProduct(InPosition - WorldCOM, InForce); Rigid->AddForce(InForce); Rigid->AddTorque(WorldTorque); } FVector WorldVelocityAtPoint(TPBDRigidParticleHandle* Rigid, const FVector& InPoint) { check(Rigid); const Chaos::FVec3 COM = Chaos::FParticleUtilitiesGT::GetCoMWorldPosition(Rigid); const Chaos::FVec3 Diff = InPoint - COM; const Chaos::FVec3 V = Rigid->GetV(); const Chaos::FVec3 W = Rigid->GetW(); return V - Chaos::FVec3::CrossProduct(Diff, W); } // #todo: break out vehicle simulation setup so it can be used across number of tests GTEST_TEST(AllEvolutions, VehicleTest_SuspensionSpringLoad) { FParticleUniqueIndicesMultithreaded UniqueIndices; FPBDRigidsSOAs Particles(UniqueIndices); THandleArray PhysicalMaterials; FPBDRigidsEvolutionGBF Evolution(Particles, PhysicalMaterials); float BodyMass = 1000.0f; float Gravity = FMath::Abs(Evolution.GetGravityForces().GetAcceleration(0).Z); FSimpleSuspensionConfig Setup; Setup.MaxLength = 20.0f; Setup.SpringRate = (2.0f * BodyMass * Gravity / 4.0f) / Setup.MaxLength; Setup.SpringPreload = 0.f; Setup.RaycastSafetyMargin = 0.f; Setup.SuspensionSmoothing = 0; Setup.ReboundDamping = 0.f; // calculated later Setup.CompressionDamping = 0.f; // calculated later TArray Suspensions; for (int SpringIdx = 0; SpringIdx < 4; SpringIdx++) { FSimpleSuspensionSim Suspension(&Setup); Suspensions.Add(Suspension); } float HalfLength = 100.f; float HalfWidth = 50.f; TArray LocalSpringPositions; LocalSpringPositions.Add(FVector( HalfLength, -HalfWidth, 0.f)); LocalSpringPositions.Add(FVector( HalfLength, HalfWidth, 0.f)); LocalSpringPositions.Add(FVector(-HalfLength, -HalfWidth, 0.f)); LocalSpringPositions.Add(FVector(-HalfLength, HalfWidth, 0.f)); for (int SpringIdx = 0; SpringIdx < 4; SpringIdx++) { Suspensions[SpringIdx].SetLocalRestingPosition(LocalSpringPositions[SpringIdx]); } ////////////////////////////////////////////////////////////////////////// TArray OutSprungMasses; FSuspensionUtility::ComputeSprungMasses(LocalSpringPositions, BodyMass, OutSprungMasses); TArray Traces; Traces.SetNum(4); for (int SpringIdx = 0; SpringIdx < 4; SpringIdx++) { float NaturalFrequency = FSuspensionUtility::ComputeNaturalFrequency(Setup.SpringRate, OutSprungMasses[SpringIdx]); float Damping = FSuspensionUtility::ComputeCriticalDamping(Setup.SpringRate, OutSprungMasses[SpringIdx]); Setup.ReboundDamping = Damping; Setup.CompressionDamping = Damping; } float WheelRadius = 2.0f; //const float SuspendedDisplacement = SprungMass * Gravity / Setup.SpringRate; //UE_LOG(LogChaos, Warning, TEXT("SuspendedDisplacement %.1f cm"), SuspendedDisplacement); ////////////////////////////////////////////////////////////////////////// auto Dynamic = Evolution.CreateDynamicParticles(1)[0]; TUniquePtr PhysicsMaterial = MakeUnique(); PhysicsMaterial->SleepCounterThreshold = 2; Chaos::FImplicitObjectPtr Box(new Chaos::FSphere(FVec3(0, 0, 0), 50)); Dynamic->SetGeometry(Box); Evolution.SetPhysicsMaterial(Dynamic, MakeSerializable(PhysicsMaterial)); Dynamic->SetX(FVec3(10, 10, 20)); Dynamic->M() = BodyMass; Dynamic->InvM() = 1.0f / BodyMass; Dynamic->I() = TVec3(100000.0f); Dynamic->InvI() = TVec3(1.0f / 100000.0f); Evolution.EnableParticle(Dynamic); float AccumulatedTime = 0.f; const FReal Dt = 1 / 30.f; for (int i = 0; i < 500; ++i) { // latest body transform const FTransform BodyTM(Dynamic->GetR(), Dynamic->GetX()); for (int SpringIdx = 0; SpringIdx < 4; SpringIdx++) { FSimpleSuspensionSim& Suspension = Suspensions[SpringIdx]; FSuspensionTrace& Trace = Traces[SpringIdx]; Suspension.UpdateWorldRaycastLocation(BodyTM, WheelRadius, Trace); // raycast FVec3 Start = Trace.Start; FVec3 Dir = Trace.TraceDir(); FReal CurrentLength = Suspension.Setup().MaxLength; FVec3 Position(0,0,0); FVec3 Normal(0,0,0); bool bHit = RayCastPlane(Start, Dir, Trace.Length(), CurrentLength, Position, Normal); Suspension.SetSuspensionLength(CurrentLength, WheelRadius); Suspension.SetLocalVelocityFromWorld(BodyTM, WorldVelocityAtPoint(Dynamic, Trace.Start)); Suspension.Simulate(Dt); // ComputeSuspensionForces if (bHit) { FVector SusForce = Suspension.GetSuspensionForceVector(BodyTM); AddForceAtPosition(Evolution, Dynamic, SusForce, Start); } } Evolution.AdvanceOneTimeStep(Dt); Evolution.EndFrame(Dt); AccumulatedTime += Dt; } float Tolerance = 0.5f; // half cm float ExpectedRestingPosition = (10.f + PlaneZPos + WheelRadius); EXPECT_LT(Dynamic->GetX().Z - ExpectedRestingPosition, Tolerance); } GTEST_TEST(AllTraits, VehicleTest_WheelAcceleratingLongitudinalSlip_VaryingDelta) { FSimpleWheelConfig Setup; Setup.ABSEnabled = false; Setup.TractionControlEnabled = false; Setup.BrakeEnabled = true; Setup.EngineEnabled = true; Setup.WheelRadius = 0.3f; Setup.FrictionMultiplier = 1.0f; Setup.CorneringStiffness = 1000.0f; Setup.SideSlipModifier = 0.7f; FSimpleWheelSim Wheel(&Setup); // units meters float Gravity = 9.8f; float VehicleMassPerWheel = 100.f; float ResultsTolerance = 0.01f; Wheel.SetSurfaceFriction(RealWorldConsts::DryRoadFriction()); Wheel.SetWheelLoadForce(VehicleMassPerWheel * Gravity); Wheel.SetMassPerWheel(VehicleMassPerWheel); // Road speed FVector Velocity = FVector(10.f, 0.f, 0.f); // start from stationary Wheel.SetMatchingSpeed(Velocity.X); Wheel.SetVehicleGroundSpeed(Velocity); Wheel.SetDriveTorque(100.0f); float DeltaTime = 1.f / 30.f; Wheel.Simulate(DeltaTime); FVector ForceGenerated30FPS = Wheel.GetForceFromFriction(); Wheel.Simulate(DeltaTime); FVector ForceGenerated30FPS_2 = Wheel.GetForceFromFriction(); DeltaTime = 1.f / 60.f; Wheel.Simulate(DeltaTime); FVector ForceGenerated60FPS = Wheel.GetForceFromFriction(); DeltaTime = 1.f / 50.f; Wheel.Simulate(DeltaTime); FVector ForceGenerated50FPS = Wheel.GetForceFromFriction(); EXPECT_NEAR(ForceGenerated30FPS.X, ForceGenerated30FPS_2.X, ResultsTolerance); EXPECT_NEAR(ForceGenerated30FPS.X, ForceGenerated60FPS.X, ResultsTolerance); EXPECT_NEAR(ForceGenerated30FPS.X, ForceGenerated60FPS.X, ResultsTolerance); Wheel.SetMatchingSpeed(Velocity.X); Wheel.SetVehicleGroundSpeed(Velocity); Wheel.SetDriveTorque(100.0f); } GTEST_TEST(AllTraits, VehicleTest_Suspension_VaryingDelta) { FSimpleSuspensionConfig Setup; Setup.MaxLength = 20.0f; Setup.DampingRatio = 0.5f; Setup.SpringPreload = 100.0f; Setup.SpringRate = 100.0f; Setup.SuspensionAxis = FVector(0.f, 0.f, -1.f); Setup.RestingForce = 50.0f; FSimpleSuspensionSim Suspension(&Setup); // units meters float Gravity = 9.8f; float VehicleMassPerWheel = 100.f; float WheelRadius = 32.0f; float CurrentLength = 5.0f; float DeltaTime = 1.0f / 30.0f; float ResultsTolerance = 0.01f; Suspension.SetSuspensionLength(CurrentLength, WheelRadius); Suspension.SetLocalVelocity(FVector(0.f,0.f,-8.f)); // first one has to adjust for sudden change in length Suspension.Simulate(DeltaTime); Suspension.Simulate(DeltaTime); float Force30FPS = Suspension.GetSuspensionForce(); Suspension.Simulate(DeltaTime); float Force30FPS_2 = Suspension.GetSuspensionForce(); DeltaTime = 60.0f; Suspension.Simulate(DeltaTime); float Force60FPS = Suspension.GetSuspensionForce(); DeltaTime = 50.0f; Suspension.Simulate(DeltaTime); float Force50FPS = Suspension.GetSuspensionForce(); EXPECT_NEAR(Force30FPS, Force30FPS_2, ResultsTolerance); EXPECT_NEAR(Force30FPS, Force60FPS, ResultsTolerance); EXPECT_NEAR(Force30FPS, Force50FPS, ResultsTolerance); } }