Ajout de Jolt Physics + 1ere version des factory entitecomposants - camera, transform, rigidbody, collider, renderer
This commit is contained in:
276
lib/All/JoltPhysics/Samples/Tests/Vehicle/MotorcycleTest.cpp
Normal file
276
lib/All/JoltPhysics/Samples/Tests/Vehicle/MotorcycleTest.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <TestFramework.h>
|
||||
|
||||
#include <Tests/Vehicle/MotorcycleTest.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
|
||||
#include <Jolt/Physics/Collision/ShapeCast.h>
|
||||
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
|
||||
#include <Jolt/Physics/Vehicle/MotorcycleController.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Application/DebugUI.h>
|
||||
#include <Layers.h>
|
||||
#include <Renderer/DebugRendererImp.h>
|
||||
|
||||
JPH_IMPLEMENT_RTTI_VIRTUAL(MotorcycleTest)
|
||||
{
|
||||
JPH_ADD_BASE_CLASS(MotorcycleTest, VehicleTest)
|
||||
}
|
||||
|
||||
MotorcycleTest::~MotorcycleTest()
|
||||
{
|
||||
mPhysicsSystem->RemoveStepListener(mVehicleConstraint);
|
||||
}
|
||||
|
||||
void MotorcycleTest::Initialize()
|
||||
{
|
||||
VehicleTest::Initialize();
|
||||
|
||||
// Loosely based on: https://www.whitedogbikes.com/whitedogblog/yamaha-xj-900-specs/
|
||||
const float back_wheel_radius = 0.31f;
|
||||
const float back_wheel_width = 0.05f;
|
||||
const float back_wheel_pos_z = -0.75f;
|
||||
const float back_suspension_min_length = 0.3f;
|
||||
const float back_suspension_max_length = 0.5f;
|
||||
const float back_suspension_freq = 2.0f;
|
||||
const float back_brake_torque = 250.0f;
|
||||
|
||||
const float front_wheel_radius = 0.31f;
|
||||
const float front_wheel_width = 0.05f;
|
||||
const float front_wheel_pos_z = 0.75f;
|
||||
const float front_suspension_min_length = 0.3f;
|
||||
const float front_suspension_max_length = 0.5f;
|
||||
const float front_suspension_freq = 1.5f;
|
||||
const float front_brake_torque = 500.0f;
|
||||
|
||||
const float half_vehicle_length = 0.4f;
|
||||
const float half_vehicle_width = 0.2f;
|
||||
const float half_vehicle_height = 0.3f;
|
||||
|
||||
const float max_steering_angle = DegreesToRadians(30);
|
||||
|
||||
// Angle of the front suspension
|
||||
const float caster_angle = DegreesToRadians(30);
|
||||
|
||||
// Create vehicle body
|
||||
RVec3 position(0, 2, 0);
|
||||
RefConst<Shape> motorcycle_shape = OffsetCenterOfMassShapeSettings(Vec3(0, -half_vehicle_height, 0), new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length))).Create().Get();
|
||||
BodyCreationSettings motorcycle_body_settings(motorcycle_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
|
||||
motorcycle_body_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
|
||||
motorcycle_body_settings.mMassPropertiesOverride.mMass = 240.0f;
|
||||
mMotorcycleBody = mBodyInterface->CreateBody(motorcycle_body_settings);
|
||||
mBodyInterface->AddBody(mMotorcycleBody->GetID(), EActivation::Activate);
|
||||
|
||||
// Create vehicle constraint
|
||||
VehicleConstraintSettings vehicle;
|
||||
vehicle.mDrawConstraintSize = 0.1f;
|
||||
vehicle.mMaxPitchRollAngle = DegreesToRadians(60.0f);
|
||||
|
||||
// Wheels
|
||||
WheelSettingsWV *front = new WheelSettingsWV;
|
||||
front->mPosition = Vec3(0.0f, -0.9f * half_vehicle_height, front_wheel_pos_z);
|
||||
front->mMaxSteerAngle = max_steering_angle;
|
||||
front->mSuspensionDirection = Vec3(0, -1, Tan(caster_angle)).Normalized();
|
||||
front->mSteeringAxis = -front->mSuspensionDirection;
|
||||
front->mRadius = front_wheel_radius;
|
||||
front->mWidth = front_wheel_width;
|
||||
front->mSuspensionMinLength = front_suspension_min_length;
|
||||
front->mSuspensionMaxLength = front_suspension_max_length;
|
||||
front->mSuspensionSpring.mFrequency = front_suspension_freq;
|
||||
front->mMaxBrakeTorque = front_brake_torque;
|
||||
|
||||
WheelSettingsWV *back = new WheelSettingsWV;
|
||||
back->mPosition = Vec3(0.0f, -0.9f * half_vehicle_height, back_wheel_pos_z);
|
||||
back->mMaxSteerAngle = 0.0f;
|
||||
back->mRadius = back_wheel_radius;
|
||||
back->mWidth = back_wheel_width;
|
||||
back->mSuspensionMinLength = back_suspension_min_length;
|
||||
back->mSuspensionMaxLength = back_suspension_max_length;
|
||||
back->mSuspensionSpring.mFrequency = back_suspension_freq;
|
||||
back->mMaxBrakeTorque = back_brake_torque;
|
||||
|
||||
if (sOverrideFrontSuspensionForcePoint)
|
||||
{
|
||||
front->mEnableSuspensionForcePoint = true;
|
||||
front->mSuspensionForcePoint = front->mPosition + front->mSuspensionDirection * front->mSuspensionMinLength;
|
||||
}
|
||||
|
||||
if (sOverrideRearSuspensionForcePoint)
|
||||
{
|
||||
back->mEnableSuspensionForcePoint = true;
|
||||
back->mSuspensionForcePoint = back->mPosition + back->mSuspensionDirection * back->mSuspensionMinLength;
|
||||
}
|
||||
|
||||
vehicle.mWheels = { front, back };
|
||||
|
||||
MotorcycleControllerSettings *controller = new MotorcycleControllerSettings;
|
||||
controller->mEngine.mMaxTorque = 150.0f;
|
||||
controller->mEngine.mMinRPM = 1000.0f;
|
||||
controller->mEngine.mMaxRPM = 10000.0f;
|
||||
controller->mTransmission.mShiftDownRPM = 2000.0f;
|
||||
controller->mTransmission.mShiftUpRPM = 8000.0f;
|
||||
controller->mTransmission.mGearRatios = { 2.27f, 1.63f, 1.3f, 1.09f, 0.96f, 0.88f }; // From: https://www.blocklayer.com/rpm-gear-bikes
|
||||
controller->mTransmission.mReverseGearRatios = { -4.0f };
|
||||
controller->mTransmission.mClutchStrength = 2.0f;
|
||||
vehicle.mController = controller;
|
||||
|
||||
// Differential (not really applicable to a motorcycle but we need one anyway to drive it)
|
||||
controller->mDifferentials.resize(1);
|
||||
controller->mDifferentials[0].mLeftWheel = -1;
|
||||
controller->mDifferentials[0].mRightWheel = 1;
|
||||
controller->mDifferentials[0].mDifferentialRatio = 1.93f * 40.0f / 16.0f; // Combining primary and final drive (back divided by front sprockets) from: https://www.blocklayer.com/rpm-gear-bikes
|
||||
|
||||
mVehicleConstraint = new VehicleConstraint(*mMotorcycleBody, vehicle);
|
||||
mVehicleConstraint->SetVehicleCollisionTester(new VehicleCollisionTesterCastCylinder(Layers::MOVING, 1.0f)); // Use half wheel width as convex radius so we get a rounded cylinder
|
||||
mPhysicsSystem->AddConstraint(mVehicleConstraint);
|
||||
mPhysicsSystem->AddStepListener(mVehicleConstraint);
|
||||
|
||||
UpdateCameraPivot();
|
||||
}
|
||||
|
||||
void MotorcycleTest::ProcessInput(const ProcessInputParams &inParams)
|
||||
{
|
||||
// Determine acceleration and brake
|
||||
mForward = 0.0f;
|
||||
mBrake = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Z))
|
||||
mBrake = 1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Up))
|
||||
mForward = 1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Down))
|
||||
mForward = -1.0f;
|
||||
|
||||
// Check if we're reversing direction
|
||||
if (mPreviousForward * mForward < 0.0f)
|
||||
{
|
||||
// Get vehicle velocity in local space to the body of the vehicle
|
||||
float velocity = (mMotorcycleBody->GetRotation().Conjugated() * mMotorcycleBody->GetLinearVelocity()).GetZ();
|
||||
if ((mForward > 0.0f && velocity < -0.1f) || (mForward < 0.0f && velocity > 0.1f))
|
||||
{
|
||||
// Brake while we've not stopped yet
|
||||
mForward = 0.0f;
|
||||
mBrake = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// When we've come to a stop, accept the new direction
|
||||
mPreviousForward = mForward;
|
||||
}
|
||||
}
|
||||
|
||||
// Steering
|
||||
float right = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Left))
|
||||
right = -1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Right))
|
||||
right = 1.0f;
|
||||
const float steer_speed = 4.0f;
|
||||
if (right > mRight)
|
||||
mRight = min(mRight + steer_speed * inParams.mDeltaTime, right);
|
||||
else if (right < mRight)
|
||||
mRight = max(mRight - steer_speed * inParams.mDeltaTime, right);
|
||||
|
||||
// When leaned, we don't want to use the brakes fully as we'll spin out
|
||||
if (mBrake > 0.0f)
|
||||
{
|
||||
Vec3 world_up = -mPhysicsSystem->GetGravity().Normalized();
|
||||
Vec3 up = mMotorcycleBody->GetRotation() * mVehicleConstraint->GetLocalUp();
|
||||
Vec3 fwd = mMotorcycleBody->GetRotation() * mVehicleConstraint->GetLocalForward();
|
||||
float sin_lean_angle = abs(world_up.Cross(up).Dot(fwd));
|
||||
float brake_multiplier = Square(1.0f - sin_lean_angle);
|
||||
mBrake *= brake_multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
void MotorcycleTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
|
||||
{
|
||||
VehicleTest::PrePhysicsUpdate(inParams);
|
||||
|
||||
UpdateCameraPivot();
|
||||
|
||||
// On user input, assure that the motorcycle is active
|
||||
if (mRight != 0.0f || mForward != 0.0f || mBrake != 0.0f)
|
||||
mBodyInterface->ActivateBody(mMotorcycleBody->GetID());
|
||||
|
||||
// Pass the input on to the constraint
|
||||
MotorcycleController *controller = static_cast<MotorcycleController *>(mVehicleConstraint->GetController());
|
||||
controller->SetDriverInput(mForward, mRight, mBrake, false);
|
||||
controller->EnableLeanController(sEnableLeanController);
|
||||
|
||||
if (sOverrideGravity)
|
||||
{
|
||||
// When overriding gravity is requested, we cast a sphere downwards (opposite to the previous up position) and use the contact normal as the new gravity direction
|
||||
SphereShape sphere(0.5f);
|
||||
sphere.SetEmbedded();
|
||||
RShapeCast shape_cast(&sphere, Vec3::sOne(), RMat44::sTranslation(mMotorcycleBody->GetPosition()), -3.0f * mVehicleConstraint->GetWorldUp());
|
||||
ShapeCastSettings settings;
|
||||
ClosestHitCollisionCollector<CastShapeCollector> collector;
|
||||
mPhysicsSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, mMotorcycleBody->GetPosition(), collector, SpecifiedBroadPhaseLayerFilter(BroadPhaseLayers::NON_MOVING), SpecifiedObjectLayerFilter(Layers::NON_MOVING));
|
||||
if (collector.HadHit())
|
||||
mVehicleConstraint->OverrideGravity(9.81f * collector.mHit.mPenetrationAxis.Normalized());
|
||||
else
|
||||
mVehicleConstraint->ResetGravityOverride();
|
||||
}
|
||||
|
||||
// Draw our wheels (this needs to be done in the pre update since we draw the bodies too in the state before the step)
|
||||
for (uint w = 0; w < 2; ++w)
|
||||
{
|
||||
const WheelSettings *settings = mVehicleConstraint->GetWheels()[w]->GetSettings();
|
||||
RMat44 wheel_transform = mVehicleConstraint->GetWheelWorldTransform(w, Vec3::sAxisY(), Vec3::sAxisX()); // The cylinder we draw is aligned with Y so we specify that as rotational axis
|
||||
mDebugRenderer->DrawCylinder(wheel_transform, 0.5f * settings->mWidth, settings->mRadius, Color::sGreen);
|
||||
}
|
||||
}
|
||||
|
||||
void MotorcycleTest::SaveInputState(StateRecorder &inStream) const
|
||||
{
|
||||
inStream.Write(mForward);
|
||||
inStream.Write(mPreviousForward);
|
||||
inStream.Write(mRight);
|
||||
inStream.Write(mBrake);
|
||||
}
|
||||
|
||||
void MotorcycleTest::RestoreInputState(StateRecorder &inStream)
|
||||
{
|
||||
inStream.Read(mForward);
|
||||
inStream.Read(mPreviousForward);
|
||||
inStream.Read(mRight);
|
||||
inStream.Read(mBrake);
|
||||
}
|
||||
|
||||
void MotorcycleTest::GetInitialCamera(CameraState &ioState) const
|
||||
{
|
||||
// Position camera behind motorcycle
|
||||
RVec3 cam_tgt = RVec3(0, 0, 5);
|
||||
ioState.mPos = RVec3(0, 2.5f, -5);
|
||||
ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
|
||||
}
|
||||
|
||||
void MotorcycleTest::UpdateCameraPivot()
|
||||
{
|
||||
// Pivot is center of motorcycle and rotates with motorcycle around Y axis only
|
||||
Vec3 fwd = mMotorcycleBody->GetRotation().RotateAxisZ();
|
||||
fwd.SetY(0.0f);
|
||||
float len = fwd.Length();
|
||||
if (len != 0.0f)
|
||||
fwd /= len;
|
||||
else
|
||||
fwd = Vec3::sAxisZ();
|
||||
Vec3 up = Vec3::sAxisY();
|
||||
Vec3 right = up.Cross(fwd);
|
||||
mCameraPivot = RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mMotorcycleBody->GetPosition());
|
||||
}
|
||||
|
||||
void MotorcycleTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
|
||||
{
|
||||
VehicleTest::CreateSettingsMenu(inUI, inSubMenu);
|
||||
|
||||
inUI->CreateCheckBox(inSubMenu, "Override Front Suspension Force Point", sOverrideFrontSuspensionForcePoint, [](UICheckBox::EState inState) { sOverrideFrontSuspensionForcePoint = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateCheckBox(inSubMenu, "Override Rear Suspension Force Point", sOverrideRearSuspensionForcePoint, [](UICheckBox::EState inState) { sOverrideRearSuspensionForcePoint = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateCheckBox(inSubMenu, "Enable Lean Controller", sEnableLeanController, [](UICheckBox::EState inState) { sEnableLeanController = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateCheckBox(inSubMenu, "Override Gravity", sOverrideGravity, [](UICheckBox::EState inState) { sOverrideGravity = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateTextButton(inSubMenu, "Accept", [this]() { RestartTest(); });
|
||||
}
|
||||
54
lib/All/JoltPhysics/Samples/Tests/Vehicle/MotorcycleTest.h
Normal file
54
lib/All/JoltPhysics/Samples/Tests/Vehicle/MotorcycleTest.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Tests/Vehicle/VehicleTest.h>
|
||||
#include <Jolt/Physics/Vehicle/VehicleConstraint.h>
|
||||
|
||||
class MotorcycleTest : public VehicleTest
|
||||
{
|
||||
public:
|
||||
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, MotorcycleTest)
|
||||
|
||||
// Description of the test
|
||||
virtual const char * GetDescription() const override
|
||||
{
|
||||
return "This test shows how a motorcycle could be made with the vehicle constraint.\n"
|
||||
"Use the arrow keys to drive. Z for hand brake.";
|
||||
}
|
||||
|
||||
// Destructor
|
||||
virtual ~MotorcycleTest() override;
|
||||
|
||||
// See: Test
|
||||
virtual void Initialize() override;
|
||||
virtual void ProcessInput(const ProcessInputParams &inParams) override;
|
||||
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
|
||||
virtual void SaveInputState(StateRecorder &inStream) const override;
|
||||
virtual void RestoreInputState(StateRecorder &inStream) override;
|
||||
|
||||
virtual void GetInitialCamera(CameraState &ioState) const override;
|
||||
virtual RMat44 GetCameraPivot(float inCameraHeading, float inCameraPitch) const override { return mCameraPivot; }
|
||||
|
||||
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
|
||||
|
||||
private:
|
||||
void UpdateCameraPivot();
|
||||
|
||||
static inline bool sOverrideFrontSuspensionForcePoint = false; ///< If true, the front suspension force point is overridden
|
||||
static inline bool sOverrideRearSuspensionForcePoint = false; ///< If true, the rear suspension force point is overridden
|
||||
static inline bool sEnableLeanController = true; ///< If true, the lean controller is enabled
|
||||
static inline bool sOverrideGravity = false; ///< If true, gravity is overridden to always oppose the ground normal
|
||||
|
||||
Body * mMotorcycleBody; ///< The vehicle
|
||||
Ref<VehicleConstraint> mVehicleConstraint; ///< The vehicle constraint
|
||||
RMat44 mCameraPivot = RMat44::sIdentity(); ///< The camera pivot, recorded before the physics update to align with the drawn world
|
||||
|
||||
// Player input
|
||||
float mForward = 0.0f;
|
||||
float mPreviousForward = 1.0f; ///< Keeps track of last motorcycle direction so we know when to brake and when to accelerate
|
||||
float mRight = 0.0f; ///< Keeps track of the current steering angle
|
||||
float mBrake = 0.0f;
|
||||
};
|
||||
357
lib/All/JoltPhysics/Samples/Tests/Vehicle/TankTest.cpp
Normal file
357
lib/All/JoltPhysics/Samples/Tests/Vehicle/TankTest.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <TestFramework.h>
|
||||
|
||||
#include <Tests/Vehicle/TankTest.h>
|
||||
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
|
||||
#include <Jolt/Physics/Collision/RayCast.h>
|
||||
#include <Jolt/Physics/Collision/CastResult.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
|
||||
#include <Jolt/Physics/Vehicle/TrackedVehicleController.h>
|
||||
#include <Jolt/Physics/Collision/GroupFilterTable.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Application/DebugUI.h>
|
||||
#include <Layers.h>
|
||||
#include <Renderer/DebugRendererImp.h>
|
||||
|
||||
JPH_IMPLEMENT_RTTI_VIRTUAL(TankTest)
|
||||
{
|
||||
JPH_ADD_BASE_CLASS(TankTest, VehicleTest)
|
||||
}
|
||||
|
||||
TankTest::~TankTest()
|
||||
{
|
||||
mPhysicsSystem->RemoveStepListener(mVehicleConstraint);
|
||||
}
|
||||
|
||||
void TankTest::Initialize()
|
||||
{
|
||||
VehicleTest::Initialize();
|
||||
|
||||
const float wheel_radius = 0.3f;
|
||||
const float wheel_width = 0.1f;
|
||||
const float half_vehicle_length = 3.2f;
|
||||
const float half_vehicle_width = 1.7f;
|
||||
const float half_vehicle_height = 0.5f;
|
||||
const float suspension_min_length = 0.3f;
|
||||
const float suspension_max_length = 0.5f;
|
||||
const float suspension_frequency = 1.0f;
|
||||
|
||||
const float half_turret_width = 1.4f;
|
||||
const float half_turret_length = 2.0f;
|
||||
const float half_turret_height = 0.4f;
|
||||
|
||||
const float half_barrel_length = 1.5f;
|
||||
const float barrel_radius = 0.1f;
|
||||
const float barrel_rotation_offset = 0.2f;
|
||||
|
||||
static Vec3 wheel_pos[] = {
|
||||
Vec3(0.0f, -0.0f, 2.95f),
|
||||
Vec3(0.0f, -0.3f, 2.1f),
|
||||
Vec3(0.0f, -0.3f, 1.4f),
|
||||
Vec3(0.0f, -0.3f, 0.7f),
|
||||
Vec3(0.0f, -0.3f, 0.0f),
|
||||
Vec3(0.0f, -0.3f, -0.7f),
|
||||
Vec3(0.0f, -0.3f, -1.4f),
|
||||
Vec3(0.0f, -0.3f, -2.1f),
|
||||
Vec3(0.0f, -0.0f, -2.75f),
|
||||
};
|
||||
|
||||
// Create filter to prevent body, turret and barrel from colliding
|
||||
GroupFilter *filter = new GroupFilterTable;
|
||||
|
||||
// Create tank body
|
||||
RVec3 body_position(0, 2, 0);
|
||||
RefConst<Shape> tank_body_shape = OffsetCenterOfMassShapeSettings(Vec3(0, -half_vehicle_height, 0), new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length))).Create().Get();
|
||||
BodyCreationSettings tank_body_settings(tank_body_shape, body_position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
|
||||
tank_body_settings.mCollisionGroup.SetGroupFilter(filter);
|
||||
tank_body_settings.mCollisionGroup.SetGroupID(0);
|
||||
tank_body_settings.mCollisionGroup.SetSubGroupID(0);
|
||||
tank_body_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
|
||||
tank_body_settings.mMassPropertiesOverride.mMass = 4000.0f;
|
||||
mTankBody = mBodyInterface->CreateBody(tank_body_settings);
|
||||
mBodyInterface->AddBody(mTankBody->GetID(), EActivation::Activate);
|
||||
|
||||
// Create vehicle constraint
|
||||
VehicleConstraintSettings vehicle;
|
||||
vehicle.mDrawConstraintSize = 0.1f;
|
||||
vehicle.mMaxPitchRollAngle = DegreesToRadians(60.0f);
|
||||
|
||||
TrackedVehicleControllerSettings *controller = new TrackedVehicleControllerSettings;
|
||||
vehicle.mController = controller;
|
||||
|
||||
for (int t = 0; t < 2; ++t)
|
||||
{
|
||||
VehicleTrackSettings &track = controller->mTracks[t];
|
||||
|
||||
// Last wheel is driven wheel
|
||||
track.mDrivenWheel = (uint)(vehicle.mWheels.size() + size(wheel_pos) - 1);
|
||||
|
||||
for (uint wheel = 0; wheel < size(wheel_pos); ++wheel)
|
||||
{
|
||||
WheelSettingsTV *w = new WheelSettingsTV;
|
||||
w->mPosition = wheel_pos[wheel];
|
||||
w->mPosition.SetX(t == 0? half_vehicle_width : -half_vehicle_width);
|
||||
w->mRadius = wheel_radius;
|
||||
w->mWidth = wheel_width;
|
||||
w->mSuspensionMinLength = suspension_min_length;
|
||||
w->mSuspensionMaxLength = wheel == 0 || wheel == size(wheel_pos) - 1? suspension_min_length : suspension_max_length;
|
||||
w->mSuspensionSpring.mFrequency = suspension_frequency;
|
||||
|
||||
// Add the wheel to the vehicle
|
||||
track.mWheels.push_back((uint)vehicle.mWheels.size());
|
||||
vehicle.mWheels.push_back(w);
|
||||
}
|
||||
}
|
||||
|
||||
mVehicleConstraint = new VehicleConstraint(*mTankBody, vehicle);
|
||||
mVehicleConstraint->SetVehicleCollisionTester(new VehicleCollisionTesterRay(Layers::MOVING));
|
||||
#ifdef JPH_DEBUG_RENDERER
|
||||
static_cast<TrackedVehicleController *>(mVehicleConstraint->GetController())->SetRPMMeter(Vec3(0, 2, 0), 0.5f);
|
||||
#endif // JPH_DEBUG_RENDERER
|
||||
mPhysicsSystem->AddConstraint(mVehicleConstraint);
|
||||
mPhysicsSystem->AddStepListener(mVehicleConstraint);
|
||||
|
||||
// Create turret
|
||||
RVec3 turret_position = body_position + Vec3(0, half_vehicle_height + half_turret_height, 0);
|
||||
BodyCreationSettings turret_body_setings(new BoxShape(Vec3(half_turret_width, half_turret_height, half_turret_length)), turret_position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
|
||||
turret_body_setings.mCollisionGroup.SetGroupFilter(filter);
|
||||
turret_body_setings.mCollisionGroup.SetGroupID(0);
|
||||
turret_body_setings.mCollisionGroup.SetSubGroupID(0);
|
||||
turret_body_setings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
|
||||
turret_body_setings.mMassPropertiesOverride.mMass = 2000.0f;
|
||||
mTurretBody = mBodyInterface->CreateBody(turret_body_setings);
|
||||
mBodyInterface->AddBody(mTurretBody->GetID(), EActivation::Activate);
|
||||
|
||||
// Attach turret to body
|
||||
HingeConstraintSettings turret_hinge;
|
||||
turret_hinge.mPoint1 = turret_hinge.mPoint2 = body_position + Vec3(0, half_vehicle_height, 0);
|
||||
turret_hinge.mHingeAxis1 = turret_hinge.mHingeAxis2 = Vec3::sAxisY();
|
||||
turret_hinge.mNormalAxis1 = turret_hinge.mNormalAxis2 = Vec3::sAxisZ();
|
||||
turret_hinge.mMotorSettings = MotorSettings(0.5f, 1.0f);
|
||||
mTurretHinge = static_cast<HingeConstraint *>(turret_hinge.Create(*mTankBody, *mTurretBody));
|
||||
mTurretHinge->SetMotorState(EMotorState::Position);
|
||||
mPhysicsSystem->AddConstraint(mTurretHinge);
|
||||
|
||||
// Create barrel
|
||||
RVec3 barrel_position = turret_position + Vec3(0, 0, half_turret_length + half_barrel_length - barrel_rotation_offset);
|
||||
BodyCreationSettings barrel_body_setings(new CylinderShape(half_barrel_length, barrel_radius), barrel_position, Quat::sRotation(Vec3::sAxisX(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING);
|
||||
barrel_body_setings.mCollisionGroup.SetGroupFilter(filter);
|
||||
barrel_body_setings.mCollisionGroup.SetGroupID(0);
|
||||
barrel_body_setings.mCollisionGroup.SetSubGroupID(0);
|
||||
barrel_body_setings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
|
||||
barrel_body_setings.mMassPropertiesOverride.mMass = 200.0f;
|
||||
mBarrelBody = mBodyInterface->CreateBody(barrel_body_setings);
|
||||
mBodyInterface->AddBody(mBarrelBody->GetID(), EActivation::Activate);
|
||||
|
||||
// Attach barrel to turret
|
||||
HingeConstraintSettings barrel_hinge;
|
||||
barrel_hinge.mPoint1 = barrel_hinge.mPoint2 = barrel_position - Vec3(0, 0, half_barrel_length);
|
||||
barrel_hinge.mHingeAxis1 = barrel_hinge.mHingeAxis2 = -Vec3::sAxisX();
|
||||
barrel_hinge.mNormalAxis1 = barrel_hinge.mNormalAxis2 = Vec3::sAxisZ();
|
||||
barrel_hinge.mLimitsMin = DegreesToRadians(-10.0f);
|
||||
barrel_hinge.mLimitsMax = DegreesToRadians(40.0f);
|
||||
barrel_hinge.mMotorSettings = MotorSettings(10.0f, 1.0f);
|
||||
mBarrelHinge = static_cast<HingeConstraint *>(barrel_hinge.Create(*mTurretBody, *mBarrelBody));
|
||||
mBarrelHinge->SetMotorState(EMotorState::Position);
|
||||
mPhysicsSystem->AddConstraint(mBarrelHinge);
|
||||
|
||||
// Update camera pivot
|
||||
mCameraPivot = mTankBody->GetPosition();
|
||||
}
|
||||
|
||||
void TankTest::ProcessInput(const ProcessInputParams &inParams)
|
||||
{
|
||||
const float min_velocity_pivot_turn = 1.0f;
|
||||
|
||||
// Determine acceleration and brake
|
||||
mForward = 0.0f;
|
||||
mBrake = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::RShift))
|
||||
mBrake = 1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Up))
|
||||
mForward = 1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Down))
|
||||
mForward = -1.0f;
|
||||
|
||||
// Steering
|
||||
mLeftRatio = 1.0f;
|
||||
mRightRatio = 1.0f;
|
||||
float velocity = (mTankBody->GetRotation().Conjugated() * mTankBody->GetLinearVelocity()).GetZ();
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Left))
|
||||
{
|
||||
if (mBrake == 0.0f && mForward == 0.0f && abs(velocity) < min_velocity_pivot_turn)
|
||||
{
|
||||
// Pivot turn
|
||||
mLeftRatio = -1.0f;
|
||||
mForward = 1.0f;
|
||||
}
|
||||
else
|
||||
mLeftRatio = 0.6f;
|
||||
}
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Right))
|
||||
{
|
||||
if (mBrake == 0.0f && mForward == 0.0f && abs(velocity) < min_velocity_pivot_turn)
|
||||
{
|
||||
// Pivot turn
|
||||
mRightRatio = -1.0f;
|
||||
mForward = 1.0f;
|
||||
}
|
||||
else
|
||||
mRightRatio = 0.6f;
|
||||
}
|
||||
|
||||
// Check if we're reversing direction
|
||||
if (mPreviousForward * mForward < 0.0f)
|
||||
{
|
||||
// Get vehicle velocity in local space to the body of the vehicle
|
||||
if ((mForward > 0.0f && velocity < -0.1f) || (mForward < 0.0f && velocity > 0.1f))
|
||||
{
|
||||
// Brake while we've not stopped yet
|
||||
mForward = 0.0f;
|
||||
mBrake = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// When we've come to a stop, accept the new direction
|
||||
mPreviousForward = mForward;
|
||||
}
|
||||
}
|
||||
|
||||
// Cast ray to find target
|
||||
RRayCast ray { inParams.mCameraState.mPos, 1000.0f * inParams.mCameraState.mForward };
|
||||
RayCastSettings ray_settings;
|
||||
ClosestHitCollisionCollector<CastRayCollector> collector;
|
||||
IgnoreMultipleBodiesFilter body_filter;
|
||||
body_filter.Reserve(3);
|
||||
body_filter.IgnoreBody(mTankBody->GetID());
|
||||
body_filter.IgnoreBody(mTurretBody->GetID());
|
||||
body_filter.IgnoreBody(mBarrelBody->GetID());
|
||||
mPhysicsSystem->GetNarrowPhaseQuery().CastRay(ray, ray_settings, collector, {}, {}, body_filter);
|
||||
RVec3 hit_pos = collector.HadHit()? ray.GetPointOnRay(collector.mHit.mFraction) : ray.mOrigin + ray.mDirection;
|
||||
mDebugRenderer->DrawMarker(hit_pos, Color::sGreen, 1.0f);
|
||||
|
||||
// Orient the turret towards the hit position
|
||||
RMat44 turret_to_world = mTankBody->GetCenterOfMassTransform() * mTurretHinge->GetConstraintToBody1Matrix();
|
||||
Vec3 hit_pos_in_turret = Vec3(turret_to_world.InversedRotationTranslation() * hit_pos);
|
||||
mTurretHeading = ATan2(hit_pos_in_turret.GetZ(), hit_pos_in_turret.GetY());
|
||||
|
||||
// Orient barrel towards the hit position
|
||||
RMat44 barrel_to_world = mTurretBody->GetCenterOfMassTransform() * mBarrelHinge->GetConstraintToBody1Matrix();
|
||||
Vec3 hit_pos_in_barrel = Vec3(barrel_to_world.InversedRotationTranslation() * hit_pos);
|
||||
mBarrelPitch = ATan2(hit_pos_in_barrel.GetZ(), hit_pos_in_barrel.GetY());
|
||||
|
||||
// If user wants to fire
|
||||
mFire = inParams.mKeyboard->IsKeyPressed(EKey::Return);
|
||||
}
|
||||
|
||||
void TankTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
|
||||
{
|
||||
VehicleTest::PrePhysicsUpdate(inParams);
|
||||
|
||||
const float bullet_radius = 0.061f; // 120 mm
|
||||
const Vec3 bullet_pos = Vec3(0, 1.6f, 0);
|
||||
const Vec3 bullet_velocity = Vec3(0, 400.0f, 0); // Normal exit velocities are around 1100-1700 m/s, use a lower variable as we have a limit to max velocity (See: https://tanks-encyclopedia.com/coldwar-usa-120mm-gun-tank-m1e1-abrams/)
|
||||
const float bullet_mass = 40.0f; // Normal projectile weight is around 7 kg, use an increased value so the momentum is more realistic (with the lower exit velocity)
|
||||
const float bullet_reload_time = 2.0f;
|
||||
|
||||
// Update camera pivot
|
||||
mCameraPivot = mTankBody->GetPosition();
|
||||
|
||||
// Assure the tank stays active as we're controlling the turret with the mouse
|
||||
mBodyInterface->ActivateBody(mTankBody->GetID());
|
||||
|
||||
// Pass the input on to the constraint
|
||||
static_cast<TrackedVehicleController *>(mVehicleConstraint->GetController())->SetDriverInput(mForward, mLeftRatio, mRightRatio, mBrake);
|
||||
mTurretHinge->SetTargetAngle(mTurretHeading);
|
||||
mBarrelHinge->SetTargetAngle(mBarrelPitch);
|
||||
|
||||
// Update reload time
|
||||
mReloadTime = max(0.0f, mReloadTime - inParams.mDeltaTime);
|
||||
|
||||
// Shoot bullet
|
||||
if (mReloadTime == 0.0f && mFire)
|
||||
{
|
||||
// Create bullet
|
||||
BodyCreationSettings bullet_creation_settings(new SphereShape(bullet_radius), mBarrelBody->GetCenterOfMassTransform() * bullet_pos, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
|
||||
bullet_creation_settings.mMotionQuality = EMotionQuality::LinearCast;
|
||||
bullet_creation_settings.mFriction = 1.0f;
|
||||
bullet_creation_settings.mRestitution = 0.0f;
|
||||
bullet_creation_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
|
||||
bullet_creation_settings.mMassPropertiesOverride.mMass = bullet_mass;
|
||||
Body *bullet = mBodyInterface->CreateBody(bullet_creation_settings);
|
||||
bullet->SetLinearVelocity(mBarrelBody->GetRotation() * bullet_velocity);
|
||||
mBodyInterface->AddBody(bullet->GetID(), EActivation::Activate);
|
||||
|
||||
// Start reloading
|
||||
mReloadTime = bullet_reload_time;
|
||||
|
||||
// Apply opposite impulse to turret body
|
||||
mBodyInterface->AddImpulse(mTurretBody->GetID(), -bullet->GetLinearVelocity() * bullet_mass);
|
||||
}
|
||||
|
||||
// Draw our wheels (this needs to be done in the pre update since we draw the bodies too in the state before the step)
|
||||
for (uint w = 0; w < mVehicleConstraint->GetWheels().size(); ++w)
|
||||
{
|
||||
const WheelSettings *settings = mVehicleConstraint->GetWheels()[w]->GetSettings();
|
||||
RMat44 wheel_transform = mVehicleConstraint->GetWheelWorldTransform(w, Vec3::sAxisY(), Vec3::sAxisX()); // The cylinder we draw is aligned with Y so we specify that as rotational axis
|
||||
mDebugRenderer->DrawCylinder(wheel_transform, 0.5f * settings->mWidth, settings->mRadius, Color::sGreen);
|
||||
}
|
||||
}
|
||||
|
||||
void TankTest::SaveState(StateRecorder &inStream) const
|
||||
{
|
||||
VehicleTest::SaveState(inStream);
|
||||
|
||||
inStream.Write(mReloadTime);
|
||||
}
|
||||
|
||||
void TankTest::RestoreState(StateRecorder &inStream)
|
||||
{
|
||||
VehicleTest::RestoreState(inStream);
|
||||
|
||||
inStream.Read(mReloadTime);
|
||||
}
|
||||
|
||||
void TankTest::SaveInputState(StateRecorder &inStream) const
|
||||
{
|
||||
inStream.Write(mForward);
|
||||
inStream.Write(mPreviousForward);
|
||||
inStream.Write(mLeftRatio);
|
||||
inStream.Write(mRightRatio);
|
||||
inStream.Write(mBrake);
|
||||
inStream.Write(mTurretHeading);
|
||||
inStream.Write(mBarrelPitch);
|
||||
inStream.Write(mFire);
|
||||
}
|
||||
|
||||
void TankTest::RestoreInputState(StateRecorder &inStream)
|
||||
{
|
||||
inStream.Read(mForward);
|
||||
inStream.Read(mPreviousForward);
|
||||
inStream.Read(mLeftRatio);
|
||||
inStream.Read(mRightRatio);
|
||||
inStream.Read(mBrake);
|
||||
inStream.Read(mTurretHeading);
|
||||
inStream.Read(mBarrelPitch);
|
||||
inStream.Read(mFire);
|
||||
}
|
||||
|
||||
void TankTest::GetInitialCamera(CameraState &ioState) const
|
||||
{
|
||||
// Position camera behind tank
|
||||
ioState.mPos = RVec3(0, 4.0f, 0);
|
||||
ioState.mForward = Vec3(0, -2.0f, 10.0f).Normalized();
|
||||
}
|
||||
|
||||
RMat44 TankTest::GetCameraPivot(float inCameraHeading, float inCameraPitch) const
|
||||
{
|
||||
// Pivot is center of tank + a distance away from the tank based on the heading and pitch of the camera
|
||||
Vec3 fwd = Vec3(Cos(inCameraPitch) * Cos(inCameraHeading), Sin(inCameraPitch), Cos(inCameraPitch) * Sin(inCameraHeading));
|
||||
return RMat44::sTranslation(mCameraPivot - 10.0f * fwd);
|
||||
}
|
||||
57
lib/All/JoltPhysics/Samples/Tests/Vehicle/TankTest.h
Normal file
57
lib/All/JoltPhysics/Samples/Tests/Vehicle/TankTest.h
Normal file
@@ -0,0 +1,57 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Tests/Vehicle/VehicleTest.h>
|
||||
#include <Jolt/Physics/Vehicle/VehicleConstraint.h>
|
||||
#include <Jolt/Physics/Constraints/HingeConstraint.h>
|
||||
|
||||
class TankTest : public VehicleTest
|
||||
{
|
||||
public:
|
||||
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, TankTest)
|
||||
|
||||
// Description of the test
|
||||
virtual const char * GetDescription() const override
|
||||
{
|
||||
return "Shows how a tank could be made with a vehicle constraint.\n"
|
||||
"Use the arrow keys to drive. Shift to brake. Enter to fire.";
|
||||
}
|
||||
|
||||
// Destructor
|
||||
virtual ~TankTest() override;
|
||||
|
||||
// See: Test
|
||||
virtual void Initialize() override;
|
||||
virtual void ProcessInput(const ProcessInputParams &inParams) override;
|
||||
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
|
||||
virtual void SaveState(StateRecorder &inStream) const override;
|
||||
virtual void RestoreState(StateRecorder &inStream) override;
|
||||
virtual void SaveInputState(StateRecorder &inStream) const override;
|
||||
virtual void RestoreInputState(StateRecorder &inStream) override;
|
||||
|
||||
virtual void GetInitialCamera(CameraState &ioState) const override;
|
||||
virtual RMat44 GetCameraPivot(float inCameraHeading, float inCameraPitch) const override;
|
||||
|
||||
private:
|
||||
Body * mTankBody; ///< The body of the tank
|
||||
Body * mTurretBody; ///< The body of the turret of the tank
|
||||
Body * mBarrelBody; ///< The body of the barrel of the tank
|
||||
Ref<VehicleConstraint> mVehicleConstraint; ///< The vehicle constraint
|
||||
Ref<HingeConstraint> mTurretHinge; ///< Hinge connecting tank body and turret
|
||||
Ref<HingeConstraint> mBarrelHinge; ///< Hinge connecting tank turret and barrel
|
||||
float mReloadTime = 0.0f; ///< How long it still takes to reload the main gun
|
||||
RVec3 mCameraPivot = RVec3::sZero(); ///< The camera pivot, recorded before the physics update to align with the drawn world
|
||||
|
||||
// Player input
|
||||
float mForward = 0.0f;
|
||||
float mPreviousForward = 1.0f; ///< Keeps track of last car direction so we know when to brake and when to accelerate
|
||||
float mLeftRatio = 0.0f;
|
||||
float mRightRatio = 0.0f;
|
||||
float mBrake = 0.0f;
|
||||
float mTurretHeading = 0.0f;
|
||||
float mBarrelPitch = 0.0f;
|
||||
bool mFire = false;
|
||||
};
|
||||
@@ -0,0 +1,347 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <TestFramework.h>
|
||||
|
||||
#include <Tests/Vehicle/VehicleConstraintTest.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h>
|
||||
#include <Jolt/Physics/Collision/ShapeCast.h>
|
||||
#include <Jolt/Physics/Collision/CollisionCollectorImpl.h>
|
||||
#include <Jolt/Physics/Vehicle/WheeledVehicleController.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Application/DebugUI.h>
|
||||
#include <Layers.h>
|
||||
#include <Renderer/DebugRendererImp.h>
|
||||
|
||||
JPH_IMPLEMENT_RTTI_VIRTUAL(VehicleConstraintTest)
|
||||
{
|
||||
JPH_ADD_BASE_CLASS(VehicleConstraintTest, VehicleTest)
|
||||
}
|
||||
|
||||
VehicleConstraintTest::~VehicleConstraintTest()
|
||||
{
|
||||
mPhysicsSystem->RemoveStepListener(mVehicleConstraint);
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::Initialize()
|
||||
{
|
||||
VehicleTest::Initialize();
|
||||
|
||||
const float wheel_radius = 0.3f;
|
||||
const float wheel_width = 0.1f;
|
||||
const float half_vehicle_length = 2.0f;
|
||||
const float half_vehicle_width = 0.9f;
|
||||
const float half_vehicle_height = 0.2f;
|
||||
|
||||
// Create collision testers
|
||||
mTesters[0] = new VehicleCollisionTesterRay(Layers::MOVING);
|
||||
mTesters[1] = new VehicleCollisionTesterCastSphere(Layers::MOVING, 0.5f * wheel_width);
|
||||
mTesters[2] = new VehicleCollisionTesterCastCylinder(Layers::MOVING);
|
||||
|
||||
// Create vehicle body
|
||||
RVec3 position(0, 2, 0);
|
||||
RefConst<Shape> car_shape = OffsetCenterOfMassShapeSettings(Vec3(0, -half_vehicle_height, 0), new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length))).Create().Get();
|
||||
BodyCreationSettings car_body_settings(car_shape, position, Quat::sRotation(Vec3::sAxisZ(), sInitialRollAngle), EMotionType::Dynamic, Layers::MOVING);
|
||||
car_body_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
|
||||
car_body_settings.mMassPropertiesOverride.mMass = 1500.0f;
|
||||
mCarBody = mBodyInterface->CreateBody(car_body_settings);
|
||||
mBodyInterface->AddBody(mCarBody->GetID(), EActivation::Activate);
|
||||
|
||||
// Create vehicle constraint
|
||||
VehicleConstraintSettings vehicle;
|
||||
vehicle.mDrawConstraintSize = 0.1f;
|
||||
vehicle.mMaxPitchRollAngle = sMaxRollAngle;
|
||||
|
||||
// Suspension direction
|
||||
Vec3 front_suspension_dir = Vec3(Tan(sFrontSuspensionSidewaysAngle), -1, Tan(sFrontSuspensionForwardAngle)).Normalized();
|
||||
Vec3 front_steering_axis = Vec3(-Tan(sFrontKingPinAngle), 1, -Tan(sFrontCasterAngle)).Normalized();
|
||||
Vec3 front_wheel_up = Vec3(Sin(sFrontCamber), Cos(sFrontCamber), 0);
|
||||
Vec3 front_wheel_forward = Vec3(-Sin(sFrontToe), 0, Cos(sFrontToe));
|
||||
Vec3 rear_suspension_dir = Vec3(Tan(sRearSuspensionSidewaysAngle), -1, Tan(sRearSuspensionForwardAngle)).Normalized();
|
||||
Vec3 rear_steering_axis = Vec3(-Tan(sRearKingPinAngle), 1, -Tan(sRearCasterAngle)).Normalized();
|
||||
Vec3 rear_wheel_up = Vec3(Sin(sRearCamber), Cos(sRearCamber), 0);
|
||||
Vec3 rear_wheel_forward = Vec3(-Sin(sRearToe), 0, Cos(sRearToe));
|
||||
Vec3 flip_x(-1, 1, 1);
|
||||
|
||||
// Wheels, left front
|
||||
WheelSettingsWV *w1 = new WheelSettingsWV;
|
||||
w1->mPosition = Vec3(half_vehicle_width, -0.9f * half_vehicle_height, half_vehicle_length - 2.0f * wheel_radius);
|
||||
w1->mSuspensionDirection = front_suspension_dir;
|
||||
w1->mSteeringAxis = front_steering_axis;
|
||||
w1->mWheelUp = front_wheel_up;
|
||||
w1->mWheelForward = front_wheel_forward;
|
||||
w1->mSuspensionMinLength = sFrontSuspensionMinLength;
|
||||
w1->mSuspensionMaxLength = sFrontSuspensionMaxLength;
|
||||
w1->mSuspensionSpring.mFrequency = sFrontSuspensionFrequency;
|
||||
w1->mSuspensionSpring.mDamping = sFrontSuspensionDamping;
|
||||
w1->mMaxSteerAngle = sMaxSteeringAngle;
|
||||
w1->mMaxHandBrakeTorque = 0.0f; // Front wheel doesn't have hand brake
|
||||
|
||||
// Right front
|
||||
WheelSettingsWV *w2 = new WheelSettingsWV;
|
||||
w2->mPosition = Vec3(-half_vehicle_width, -0.9f * half_vehicle_height, half_vehicle_length - 2.0f * wheel_radius);
|
||||
w2->mSuspensionDirection = flip_x * front_suspension_dir;
|
||||
w2->mSteeringAxis = flip_x * front_steering_axis;
|
||||
w2->mWheelUp = flip_x * front_wheel_up;
|
||||
w2->mWheelForward = flip_x * front_wheel_forward;
|
||||
w2->mSuspensionMinLength = sFrontSuspensionMinLength;
|
||||
w2->mSuspensionMaxLength = sFrontSuspensionMaxLength;
|
||||
w2->mSuspensionSpring.mFrequency = sFrontSuspensionFrequency;
|
||||
w2->mSuspensionSpring.mDamping = sFrontSuspensionDamping;
|
||||
w2->mMaxSteerAngle = sMaxSteeringAngle;
|
||||
w2->mMaxHandBrakeTorque = 0.0f; // Front wheel doesn't have hand brake
|
||||
|
||||
// Left rear
|
||||
WheelSettingsWV *w3 = new WheelSettingsWV;
|
||||
w3->mPosition = Vec3(half_vehicle_width, -0.9f * half_vehicle_height, -half_vehicle_length + 2.0f * wheel_radius);
|
||||
w3->mSuspensionDirection = rear_suspension_dir;
|
||||
w3->mSteeringAxis = rear_steering_axis;
|
||||
w3->mWheelUp = rear_wheel_up;
|
||||
w3->mWheelForward = rear_wheel_forward;
|
||||
w3->mSuspensionMinLength = sRearSuspensionMinLength;
|
||||
w3->mSuspensionMaxLength = sRearSuspensionMaxLength;
|
||||
w3->mSuspensionSpring.mFrequency = sRearSuspensionFrequency;
|
||||
w3->mSuspensionSpring.mDamping = sRearSuspensionDamping;
|
||||
w3->mMaxSteerAngle = 0.0f;
|
||||
|
||||
// Right rear
|
||||
WheelSettingsWV *w4 = new WheelSettingsWV;
|
||||
w4->mPosition = Vec3(-half_vehicle_width, -0.9f * half_vehicle_height, -half_vehicle_length + 2.0f * wheel_radius);
|
||||
w4->mSuspensionDirection = flip_x * rear_suspension_dir;
|
||||
w4->mSteeringAxis = flip_x * rear_steering_axis;
|
||||
w4->mWheelUp = flip_x * rear_wheel_up;
|
||||
w4->mWheelForward = flip_x * rear_wheel_forward;
|
||||
w4->mSuspensionMinLength = sRearSuspensionMinLength;
|
||||
w4->mSuspensionMaxLength = sRearSuspensionMaxLength;
|
||||
w4->mSuspensionSpring.mFrequency = sRearSuspensionFrequency;
|
||||
w4->mSuspensionSpring.mDamping = sRearSuspensionDamping;
|
||||
w4->mMaxSteerAngle = 0.0f;
|
||||
|
||||
vehicle.mWheels = { w1, w2, w3, w4 };
|
||||
|
||||
for (WheelSettings *w : vehicle.mWheels)
|
||||
{
|
||||
w->mRadius = wheel_radius;
|
||||
w->mWidth = wheel_width;
|
||||
}
|
||||
|
||||
WheeledVehicleControllerSettings *controller = new WheeledVehicleControllerSettings;
|
||||
vehicle.mController = controller;
|
||||
|
||||
// Differential
|
||||
controller->mDifferentials.resize(sFourWheelDrive? 2 : 1);
|
||||
controller->mDifferentials[0].mLeftWheel = 0;
|
||||
controller->mDifferentials[0].mRightWheel = 1;
|
||||
if (sFourWheelDrive)
|
||||
{
|
||||
controller->mDifferentials[1].mLeftWheel = 2;
|
||||
controller->mDifferentials[1].mRightWheel = 3;
|
||||
|
||||
// Split engine torque
|
||||
controller->mDifferentials[0].mEngineTorqueRatio = controller->mDifferentials[1].mEngineTorqueRatio = 0.5f;
|
||||
}
|
||||
|
||||
// Anti rollbars
|
||||
if (sAntiRollbar)
|
||||
{
|
||||
vehicle.mAntiRollBars.resize(2);
|
||||
vehicle.mAntiRollBars[0].mLeftWheel = 0;
|
||||
vehicle.mAntiRollBars[0].mRightWheel = 1;
|
||||
vehicle.mAntiRollBars[1].mLeftWheel = 2;
|
||||
vehicle.mAntiRollBars[1].mRightWheel = 3;
|
||||
}
|
||||
|
||||
mVehicleConstraint = new VehicleConstraint(*mCarBody, vehicle);
|
||||
|
||||
// The vehicle settings were tweaked with a buggy implementation of the longitudinal tire impulses, this meant that PhysicsSettings::mNumVelocitySteps times more impulse
|
||||
// could be applied than intended. To keep the behavior of the vehicle the same we increase the max longitudinal impulse by the same factor. In a future version the vehicle
|
||||
// will be retweaked.
|
||||
static_cast<WheeledVehicleController *>(mVehicleConstraint->GetController())->SetTireMaxImpulseCallback(
|
||||
[](uint, float &outLongitudinalImpulse, float &outLateralImpulse, float inSuspensionImpulse, float inLongitudinalFriction, float inLateralFriction, float, float, float)
|
||||
{
|
||||
outLongitudinalImpulse = 10.0f * inLongitudinalFriction * inSuspensionImpulse;
|
||||
outLateralImpulse = inLateralFriction * inSuspensionImpulse;
|
||||
});
|
||||
|
||||
mPhysicsSystem->AddConstraint(mVehicleConstraint);
|
||||
mPhysicsSystem->AddStepListener(mVehicleConstraint);
|
||||
|
||||
UpdateCameraPivot();
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::ProcessInput(const ProcessInputParams &inParams)
|
||||
{
|
||||
// Determine acceleration and brake
|
||||
mForward = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Up))
|
||||
mForward = 1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Down))
|
||||
mForward = -1.0f;
|
||||
|
||||
// Check if we're reversing direction
|
||||
mBrake = 0.0f;
|
||||
if (mPreviousForward * mForward < 0.0f)
|
||||
{
|
||||
// Get vehicle velocity in local space to the body of the vehicle
|
||||
float velocity = (mCarBody->GetRotation().Conjugated() * mCarBody->GetLinearVelocity()).GetZ();
|
||||
if ((mForward > 0.0f && velocity < -0.1f) || (mForward < 0.0f && velocity > 0.1f))
|
||||
{
|
||||
// Brake while we've not stopped yet
|
||||
mForward = 0.0f;
|
||||
mBrake = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// When we've come to a stop, accept the new direction
|
||||
mPreviousForward = mForward;
|
||||
}
|
||||
}
|
||||
|
||||
// Hand brake will cancel gas pedal
|
||||
mHandBrake = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Z))
|
||||
{
|
||||
mForward = 0.0f;
|
||||
mHandBrake = 1.0f;
|
||||
}
|
||||
|
||||
// Steering
|
||||
mRight = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Left))
|
||||
mRight = -1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Right))
|
||||
mRight = 1.0f;
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
|
||||
{
|
||||
VehicleTest::PrePhysicsUpdate(inParams);
|
||||
|
||||
UpdateCameraPivot();
|
||||
|
||||
// On user input, assure that the car is active
|
||||
if (mRight != 0.0f || mForward != 0.0f || mBrake != 0.0f || mHandBrake != 0.0f)
|
||||
mBodyInterface->ActivateBody(mCarBody->GetID());
|
||||
|
||||
WheeledVehicleController *controller = static_cast<WheeledVehicleController *>(mVehicleConstraint->GetController());
|
||||
|
||||
// Update vehicle statistics
|
||||
controller->GetEngine().mMaxTorque = sMaxEngineTorque;
|
||||
controller->GetTransmission().mClutchStrength = sClutchStrength;
|
||||
|
||||
// Set slip ratios to the same for everything
|
||||
float limited_slip_ratio = sLimitedSlipDifferentials? 1.4f : FLT_MAX;
|
||||
controller->SetDifferentialLimitedSlipRatio(limited_slip_ratio);
|
||||
for (VehicleDifferentialSettings &d : controller->GetDifferentials())
|
||||
d.mLimitedSlipRatio = limited_slip_ratio;
|
||||
|
||||
// Pass the input on to the constraint
|
||||
controller->SetDriverInput(mForward, mRight, mBrake, mHandBrake);
|
||||
|
||||
// Set the collision tester
|
||||
mVehicleConstraint->SetVehicleCollisionTester(mTesters[sCollisionMode]);
|
||||
|
||||
if (sOverrideGravity)
|
||||
{
|
||||
// When overriding gravity is requested, we cast a sphere downwards (opposite to the previous up position) and use the contact normal as the new gravity direction
|
||||
SphereShape sphere(0.5f);
|
||||
sphere.SetEmbedded();
|
||||
RShapeCast shape_cast(&sphere, Vec3::sOne(), RMat44::sTranslation(mCarBody->GetPosition()), -3.0f * mVehicleConstraint->GetWorldUp());
|
||||
ShapeCastSettings settings;
|
||||
ClosestHitCollisionCollector<CastShapeCollector> collector;
|
||||
mPhysicsSystem->GetNarrowPhaseQuery().CastShape(shape_cast, settings, mCarBody->GetPosition(), collector, SpecifiedBroadPhaseLayerFilter(BroadPhaseLayers::NON_MOVING), SpecifiedObjectLayerFilter(Layers::NON_MOVING));
|
||||
if (collector.HadHit())
|
||||
mVehicleConstraint->OverrideGravity(9.81f * collector.mHit.mPenetrationAxis.Normalized());
|
||||
else
|
||||
mVehicleConstraint->ResetGravityOverride();
|
||||
}
|
||||
|
||||
// Draw our wheels (this needs to be done in the pre update since we draw the bodies too in the state before the step)
|
||||
for (uint w = 0; w < 4; ++w)
|
||||
{
|
||||
const WheelSettings *settings = mVehicleConstraint->GetWheels()[w]->GetSettings();
|
||||
RMat44 wheel_transform = mVehicleConstraint->GetWheelWorldTransform(w, Vec3::sAxisY(), Vec3::sAxisX()); // The cylinder we draw is aligned with Y so we specify that as rotational axis
|
||||
mDebugRenderer->DrawCylinder(wheel_transform, 0.5f * settings->mWidth, settings->mRadius, Color::sGreen);
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::SaveInputState(StateRecorder &inStream) const
|
||||
{
|
||||
inStream.Write(mForward);
|
||||
inStream.Write(mPreviousForward);
|
||||
inStream.Write(mRight);
|
||||
inStream.Write(mBrake);
|
||||
inStream.Write(mHandBrake);
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::RestoreInputState(StateRecorder &inStream)
|
||||
{
|
||||
inStream.Read(mForward);
|
||||
inStream.Read(mPreviousForward);
|
||||
inStream.Read(mRight);
|
||||
inStream.Read(mBrake);
|
||||
inStream.Read(mHandBrake);
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::GetInitialCamera(CameraState &ioState) const
|
||||
{
|
||||
// Position camera behind car
|
||||
RVec3 cam_tgt = RVec3(0, 0, 5);
|
||||
ioState.mPos = RVec3(0, 2.5f, -5);
|
||||
ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::UpdateCameraPivot()
|
||||
{
|
||||
// Pivot is center of car and rotates with car around Y axis only
|
||||
Vec3 fwd = mCarBody->GetRotation().RotateAxisZ();
|
||||
fwd.SetY(0.0f);
|
||||
float len = fwd.Length();
|
||||
if (len != 0.0f)
|
||||
fwd /= len;
|
||||
else
|
||||
fwd = Vec3::sAxisZ();
|
||||
Vec3 up = Vec3::sAxisY();
|
||||
Vec3 right = up.Cross(fwd);
|
||||
mCameraPivot = RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mCarBody->GetPosition());
|
||||
}
|
||||
|
||||
void VehicleConstraintTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
|
||||
{
|
||||
VehicleTest::CreateSettingsMenu(inUI, inSubMenu);
|
||||
|
||||
inUI->CreateSlider(inSubMenu, "Initial Roll Angle", RadiansToDegrees(sInitialRollAngle), 0.0f, 90.0f, 1.0f, [](float inValue) { sInitialRollAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Max Roll Angle", RadiansToDegrees(sMaxRollAngle), 0.0f, 90.0f, 1.0f, [](float inValue) { sMaxRollAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Max Steering Angle", RadiansToDegrees(sMaxSteeringAngle), 0.0f, 90.0f, 1.0f, [](float inValue) { sMaxSteeringAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateComboBox(inSubMenu, "Collision Mode", { "Ray", "Cast Sphere", "Cast Cylinder" }, sCollisionMode, [](int inItem) { sCollisionMode = inItem; });
|
||||
inUI->CreateCheckBox(inSubMenu, "4 Wheel Drive", sFourWheelDrive, [](UICheckBox::EState inState) { sFourWheelDrive = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateCheckBox(inSubMenu, "Anti Rollbars", sAntiRollbar, [](UICheckBox::EState inState) { sAntiRollbar = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateCheckBox(inSubMenu, "Limited Slip Differentials", sLimitedSlipDifferentials, [](UICheckBox::EState inState) { sLimitedSlipDifferentials = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateCheckBox(inSubMenu, "Override Gravity", sOverrideGravity, [](UICheckBox::EState inState) { sOverrideGravity = inState == UICheckBox::STATE_CHECKED; });
|
||||
inUI->CreateSlider(inSubMenu, "Max Engine Torque", float(sMaxEngineTorque), 100.0f, 2000.0f, 10.0f, [](float inValue) { sMaxEngineTorque = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Clutch Strength", float(sClutchStrength), 1.0f, 40.0f, 1.0f, [](float inValue) { sClutchStrength = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Front Caster Angle", RadiansToDegrees(sFrontCasterAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sFrontCasterAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Front King Pin Angle", RadiansToDegrees(sFrontKingPinAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sFrontKingPinAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Front Camber", RadiansToDegrees(sFrontCamber), -89.0f, 89.0f, 1.0f, [](float inValue) { sFrontCamber = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Front Toe", RadiansToDegrees(sFrontToe), -89.0f, 89.0f, 1.0f, [](float inValue) { sFrontToe = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Front Suspension Forward Angle", RadiansToDegrees(sFrontSuspensionForwardAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sFrontSuspensionForwardAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Front Suspension Sideways Angle", RadiansToDegrees(sFrontSuspensionSidewaysAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sFrontSuspensionSidewaysAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Front Suspension Min Length", sFrontSuspensionMinLength, 0.0f, 3.0f, 0.01f, [](float inValue) { sFrontSuspensionMinLength = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Front Suspension Max Length", sFrontSuspensionMaxLength, 0.0f, 3.0f, 0.01f, [](float inValue) { sFrontSuspensionMaxLength = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Front Suspension Frequency", sFrontSuspensionFrequency, 0.1f, 5.0f, 0.01f, [](float inValue) { sFrontSuspensionFrequency = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Front Suspension Damping", sFrontSuspensionDamping, 0.0f, 2.0f, 0.01f, [](float inValue) { sFrontSuspensionDamping = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Caster Angle", RadiansToDegrees(sRearCasterAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sRearCasterAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Rear King Pin Angle", RadiansToDegrees(sRearKingPinAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sRearKingPinAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Camber", RadiansToDegrees(sRearCamber), -89.0f, 89.0f, 1.0f, [](float inValue) { sRearCamber = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Toe", RadiansToDegrees(sRearToe), -89.0f, 89.0f, 1.0f, [](float inValue) { sRearToe = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Suspension Forward Angle", RadiansToDegrees(sRearSuspensionForwardAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sRearSuspensionForwardAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Suspension Sideways Angle", RadiansToDegrees(sRearSuspensionSidewaysAngle), -89.0f, 89.0f, 1.0f, [](float inValue) { sRearSuspensionSidewaysAngle = DegreesToRadians(inValue); });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Suspension Min Length", sRearSuspensionMinLength, 0.0f, 3.0f, 0.01f, [](float inValue) { sRearSuspensionMinLength = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Suspension Max Length", sRearSuspensionMaxLength, 0.0f, 3.0f, 0.01f, [](float inValue) { sRearSuspensionMaxLength = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Suspension Frequency", sRearSuspensionFrequency, 0.1f, 5.0f, 0.01f, [](float inValue) { sRearSuspensionFrequency = inValue; });
|
||||
inUI->CreateSlider(inSubMenu, "Rear Suspension Damping", sRearSuspensionDamping, 0.0f, 2.0f, 0.01f, [](float inValue) { sRearSuspensionDamping = inValue; });
|
||||
inUI->CreateTextButton(inSubMenu, "Accept", [this]() { RestartTest(); });
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Tests/Vehicle/VehicleTest.h>
|
||||
#include <Jolt/Physics/Vehicle/VehicleConstraint.h>
|
||||
|
||||
class VehicleConstraintTest : public VehicleTest
|
||||
{
|
||||
public:
|
||||
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, VehicleConstraintTest)
|
||||
|
||||
// Description of the test
|
||||
virtual const char * GetDescription() const override
|
||||
{
|
||||
return "Shows how a car could be made with a vehicle constraint.\n"
|
||||
"Use the arrow keys to drive. Z for hand brake.";
|
||||
}
|
||||
|
||||
// Destructor
|
||||
virtual ~VehicleConstraintTest() override;
|
||||
|
||||
// See: Test
|
||||
virtual void Initialize() override;
|
||||
virtual void ProcessInput(const ProcessInputParams &inParams) override;
|
||||
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
|
||||
virtual void SaveInputState(StateRecorder &inStream) const override;
|
||||
virtual void RestoreInputState(StateRecorder &inStream) override;
|
||||
|
||||
virtual void GetInitialCamera(CameraState &ioState) const override;
|
||||
virtual RMat44 GetCameraPivot(float inCameraHeading, float inCameraPitch) const override { return mCameraPivot; }
|
||||
|
||||
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
|
||||
|
||||
private:
|
||||
void UpdateCameraPivot();
|
||||
|
||||
static inline float sInitialRollAngle = 0;
|
||||
static inline float sMaxRollAngle = DegreesToRadians(60.0f);
|
||||
static inline float sMaxSteeringAngle = DegreesToRadians(30.0f);
|
||||
static inline int sCollisionMode = 2;
|
||||
static inline bool sFourWheelDrive = false;
|
||||
static inline bool sAntiRollbar = true;
|
||||
static inline bool sLimitedSlipDifferentials = true;
|
||||
static inline bool sOverrideGravity = false; ///< If true, gravity is overridden to always oppose the ground normal
|
||||
static inline float sMaxEngineTorque = 500.0f;
|
||||
static inline float sClutchStrength = 10.0f;
|
||||
static inline float sFrontCasterAngle = 0.0f;
|
||||
static inline float sFrontKingPinAngle = 0.0f;
|
||||
static inline float sFrontCamber = 0.0f;
|
||||
static inline float sFrontToe = 0.0f;
|
||||
static inline float sFrontSuspensionForwardAngle = 0.0f;
|
||||
static inline float sFrontSuspensionSidewaysAngle = 0.0f;
|
||||
static inline float sFrontSuspensionMinLength = 0.3f;
|
||||
static inline float sFrontSuspensionMaxLength = 0.5f;
|
||||
static inline float sFrontSuspensionFrequency = 1.5f;
|
||||
static inline float sFrontSuspensionDamping = 0.5f;
|
||||
static inline float sRearSuspensionForwardAngle = 0.0f;
|
||||
static inline float sRearSuspensionSidewaysAngle = 0.0f;
|
||||
static inline float sRearCasterAngle = 0.0f;
|
||||
static inline float sRearKingPinAngle = 0.0f;
|
||||
static inline float sRearCamber = 0.0f;
|
||||
static inline float sRearToe = 0.0f;
|
||||
static inline float sRearSuspensionMinLength = 0.3f;
|
||||
static inline float sRearSuspensionMaxLength = 0.5f;
|
||||
static inline float sRearSuspensionFrequency = 1.5f;
|
||||
static inline float sRearSuspensionDamping = 0.5f;
|
||||
|
||||
Body * mCarBody; ///< The vehicle
|
||||
Ref<VehicleConstraint> mVehicleConstraint; ///< The vehicle constraint
|
||||
Ref<VehicleCollisionTester> mTesters[3]; ///< Collision testers for the wheel
|
||||
RMat44 mCameraPivot = RMat44::sIdentity(); ///< The camera pivot, recorded before the physics update to align with the drawn world
|
||||
|
||||
// Player input
|
||||
float mForward = 0.0f;
|
||||
float mPreviousForward = 1.0f; ///< Keeps track of last car direction so we know when to brake and when to accelerate
|
||||
float mRight = 0.0f;
|
||||
float mBrake = 0.0f;
|
||||
float mHandBrake = 0.0f;
|
||||
};
|
||||
237
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleSixDOFTest.cpp
Normal file
237
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleSixDOFTest.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <TestFramework.h>
|
||||
|
||||
#include <Tests/Vehicle/VehicleSixDOFTest.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/CylinderShape.h>
|
||||
#include <Jolt/Physics/Collision/GroupFilterTable.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Application/DebugUI.h>
|
||||
#include <Layers.h>
|
||||
|
||||
JPH_IMPLEMENT_RTTI_VIRTUAL(VehicleSixDOFTest)
|
||||
{
|
||||
JPH_ADD_BASE_CLASS(VehicleSixDOFTest, VehicleTest)
|
||||
}
|
||||
|
||||
void VehicleSixDOFTest::Initialize()
|
||||
{
|
||||
VehicleTest::Initialize();
|
||||
|
||||
const float half_vehicle_length = 2.0f;
|
||||
const float half_vehicle_width = 0.9f;
|
||||
const float half_vehicle_height = 0.2f;
|
||||
|
||||
const float half_wheel_height = 0.3f;
|
||||
const float half_wheel_width = 0.05f;
|
||||
const float half_wheel_travel = 0.5f;
|
||||
|
||||
Vec3 wheel_position[] =
|
||||
{
|
||||
Vec3(-half_vehicle_width, -half_vehicle_height, half_vehicle_length - 2.0f * half_wheel_height),
|
||||
Vec3(half_vehicle_width, -half_vehicle_height, half_vehicle_length - 2.0f * half_wheel_height),
|
||||
Vec3(-half_vehicle_width, -half_vehicle_height, -half_vehicle_length + 2.0f * half_wheel_height),
|
||||
Vec3(half_vehicle_width, -half_vehicle_height, -half_vehicle_length + 2.0f * half_wheel_height),
|
||||
};
|
||||
|
||||
RVec3 position(0, 2, 0);
|
||||
|
||||
RefConst<Shape> body_shape = new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length));
|
||||
|
||||
Ref<CylinderShape> wheel_shape = new CylinderShape(half_wheel_width, half_wheel_height);
|
||||
wheel_shape->SetDensity(1.0e4f);
|
||||
|
||||
// Create group filter
|
||||
Ref<GroupFilterTable> group_filter = new GroupFilterTable;
|
||||
|
||||
// Create vehicle body
|
||||
mCarBody = mBodyInterface->CreateBody(BodyCreationSettings(body_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
|
||||
mCarBody->SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
|
||||
mBodyInterface->AddBody(mCarBody->GetID(), EActivation::Activate);
|
||||
|
||||
// Create wheels
|
||||
for (int i = 0; i < (int)EWheel::Num; ++i)
|
||||
{
|
||||
bool is_front = sIsFrontWheel((EWheel)i);
|
||||
bool is_left = sIsLeftWheel((EWheel)i);
|
||||
|
||||
RVec3 wheel_pos1 = position + wheel_position[i];
|
||||
RVec3 wheel_pos2 = wheel_pos1 - Vec3(0, half_wheel_travel, 0);
|
||||
|
||||
// Create body
|
||||
Body &wheel = *mBodyInterface->CreateBody(BodyCreationSettings(wheel_shape, wheel_pos2, Quat::sRotation(Vec3::sAxisZ(), 0.5f * JPH_PI), EMotionType::Dynamic, Layers::MOVING));
|
||||
wheel.SetFriction(1.0f);
|
||||
wheel.SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
|
||||
mBodyInterface->AddBody(wheel.GetID(), EActivation::Activate);
|
||||
|
||||
// Create constraint
|
||||
SixDOFConstraintSettings settings;
|
||||
settings.mPosition1 = wheel_pos1;
|
||||
settings.mPosition2 = wheel_pos2;
|
||||
settings.mAxisX1 = settings.mAxisX2 = is_left? -Vec3::sAxisX() : Vec3::sAxisX();
|
||||
settings.mAxisY1 = settings.mAxisY2 = Vec3::sAxisY();
|
||||
|
||||
// The suspension works in the Y translation axis only
|
||||
settings.MakeFixedAxis(EAxis::TranslationX);
|
||||
settings.SetLimitedAxis(EAxis::TranslationY, -half_wheel_travel, half_wheel_travel);
|
||||
settings.MakeFixedAxis(EAxis::TranslationZ);
|
||||
settings.mMotorSettings[EAxis::TranslationY] = MotorSettings(2.0f, 1.0f, 1.0e5f, 0.0f);
|
||||
|
||||
// Front wheel can rotate around the Y axis
|
||||
if (is_front)
|
||||
settings.SetLimitedAxis(EAxis::RotationY, -cMaxSteeringAngle, cMaxSteeringAngle);
|
||||
else
|
||||
settings.MakeFixedAxis(EAxis::RotationY);
|
||||
|
||||
// The Z axis is static
|
||||
settings.MakeFixedAxis(EAxis::RotationZ);
|
||||
|
||||
// The main engine drives the X axis
|
||||
settings.MakeFreeAxis(EAxis::RotationX);
|
||||
settings.mMotorSettings[EAxis::RotationX] = MotorSettings(2.0f, 1.0f, 0.0f, 0.5e4f);
|
||||
|
||||
// The front wheel needs to be able to steer around the Y axis
|
||||
// However the motors work in the constraint space of the wheel, and since this rotates around the
|
||||
// X axis we need to drive both the Y and Z to steer
|
||||
if (is_front)
|
||||
settings.mMotorSettings[EAxis::RotationY] = settings.mMotorSettings[EAxis::RotationZ] = MotorSettings(10.0f, 1.0f, 0.0f, 1.0e6f);
|
||||
|
||||
SixDOFConstraint *wheel_constraint = static_cast<SixDOFConstraint *>(settings.Create(*mCarBody, wheel));
|
||||
mPhysicsSystem->AddConstraint(wheel_constraint);
|
||||
mWheels[i] = wheel_constraint;
|
||||
|
||||
// Drive the suspension
|
||||
wheel_constraint->SetTargetPositionCS(Vec3(0, -half_wheel_travel, 0));
|
||||
wheel_constraint->SetMotorState(EAxis::TranslationY, EMotorState::Position);
|
||||
|
||||
// The front wheels steer around the Y axis, but in constraint space of the wheel this means we need to drive
|
||||
// both Y and Z (see comment above)
|
||||
if (is_front)
|
||||
{
|
||||
wheel_constraint->SetTargetOrientationCS(Quat::sIdentity());
|
||||
wheel_constraint->SetMotorState(EAxis::RotationY, EMotorState::Position);
|
||||
wheel_constraint->SetMotorState(EAxis::RotationZ, EMotorState::Position);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCameraPivot();
|
||||
}
|
||||
|
||||
void VehicleSixDOFTest::ProcessInput(const ProcessInputParams &inParams)
|
||||
{
|
||||
const float max_rotation_speed = 10.0f * JPH_PI;
|
||||
|
||||
// Determine steering and speed
|
||||
mSteeringAngle = 0.0f;
|
||||
mSpeed = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Left)) mSteeringAngle = cMaxSteeringAngle;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Right)) mSteeringAngle = -cMaxSteeringAngle;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Up)) mSpeed = max_rotation_speed;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Down)) mSpeed = -max_rotation_speed;
|
||||
}
|
||||
|
||||
void VehicleSixDOFTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
|
||||
{
|
||||
VehicleTest::PrePhysicsUpdate(inParams);
|
||||
|
||||
UpdateCameraPivot();
|
||||
|
||||
// On user input, assure that the car is active
|
||||
if (mSteeringAngle != 0.0f || mSpeed != 0.0f)
|
||||
mBodyInterface->ActivateBody(mCarBody->GetID());
|
||||
|
||||
// Brake if current velocity is in the opposite direction of the desired velocity
|
||||
float car_speed = mCarBody->GetLinearVelocity().Dot(mCarBody->GetRotation().RotateAxisZ());
|
||||
bool brake = mSpeed != 0.0f && car_speed != 0.0f && Sign(mSpeed) != Sign(car_speed);
|
||||
|
||||
// Front wheels
|
||||
const EWheel front_wheels[] = { EWheel::LeftFront, EWheel::RightFront };
|
||||
for (EWheel w : front_wheels)
|
||||
{
|
||||
SixDOFConstraint *wheel_constraint = mWheels[(int)w];
|
||||
if (wheel_constraint == nullptr)
|
||||
continue;
|
||||
|
||||
// Steer front wheels
|
||||
Quat steering_rotation = Quat::sRotation(Vec3::sAxisY(), mSteeringAngle);
|
||||
wheel_constraint->SetTargetOrientationCS(steering_rotation);
|
||||
|
||||
if (brake)
|
||||
{
|
||||
// Brake on front wheels
|
||||
wheel_constraint->SetTargetAngularVelocityCS(Vec3::sZero());
|
||||
wheel_constraint->SetMotorState(EAxis::RotationX, EMotorState::Velocity);
|
||||
}
|
||||
else if (mSpeed != 0.0f)
|
||||
{
|
||||
// Front wheel drive, since the motors are applied in the constraint space of the wheel
|
||||
// it is always applied on the X axis
|
||||
wheel_constraint->SetTargetAngularVelocityCS(Vec3(sIsLeftWheel(w)? -mSpeed : mSpeed, 0, 0));
|
||||
wheel_constraint->SetMotorState(EAxis::RotationX, EMotorState::Velocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Free spin
|
||||
wheel_constraint->SetMotorState(EAxis::RotationX, EMotorState::Off);
|
||||
}
|
||||
}
|
||||
|
||||
// Rear wheels
|
||||
const EWheel rear_wheels[] = { EWheel::LeftRear, EWheel::RightRear };
|
||||
for (EWheel w : rear_wheels)
|
||||
{
|
||||
SixDOFConstraint *wheel_constraint = mWheels[(int)w];
|
||||
if (wheel_constraint == nullptr)
|
||||
continue;
|
||||
|
||||
if (brake)
|
||||
{
|
||||
// Brake on rear wheels
|
||||
wheel_constraint->SetTargetAngularVelocityCS(Vec3::sZero());
|
||||
wheel_constraint->SetMotorState(EAxis::RotationX, EMotorState::Velocity);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Free spin
|
||||
wheel_constraint->SetMotorState(EAxis::RotationX, EMotorState::Off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleSixDOFTest::GetInitialCamera(CameraState &ioState) const
|
||||
{
|
||||
// Position camera behind car
|
||||
RVec3 cam_tgt = RVec3(0, 0, 5);
|
||||
ioState.mPos = RVec3(0, 2.5_r, -5);
|
||||
ioState.mForward = Vec3(cam_tgt - ioState.mPos).Normalized();
|
||||
}
|
||||
|
||||
void VehicleSixDOFTest::UpdateCameraPivot()
|
||||
{
|
||||
// Pivot is center of car and rotates with car around Y axis only
|
||||
Vec3 fwd = mCarBody->GetRotation().RotateAxisZ();
|
||||
fwd.SetY(0.0f);
|
||||
float len = fwd.Length();
|
||||
if (len != 0.0f)
|
||||
fwd /= len;
|
||||
else
|
||||
fwd = Vec3::sAxisZ();
|
||||
Vec3 up = Vec3::sAxisY();
|
||||
Vec3 right = up.Cross(fwd);
|
||||
mCameraPivot = RMat44(Vec4(right, 0), Vec4(up, 0), Vec4(fwd, 0), mCarBody->GetPosition());
|
||||
}
|
||||
|
||||
void VehicleSixDOFTest::SaveInputState(StateRecorder &inStream) const
|
||||
{
|
||||
inStream.Write(mSteeringAngle);
|
||||
inStream.Write(mSpeed);
|
||||
}
|
||||
|
||||
void VehicleSixDOFTest::RestoreInputState(StateRecorder &inStream)
|
||||
{
|
||||
inStream.Read(mSteeringAngle);
|
||||
inStream.Read(mSpeed);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Tests/Vehicle/VehicleTest.h>
|
||||
#include <Jolt/Physics/Constraints/SixDOFConstraint.h>
|
||||
|
||||
class VehicleSixDOFTest : public VehicleTest
|
||||
{
|
||||
public:
|
||||
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, VehicleSixDOFTest)
|
||||
|
||||
// Description of the test
|
||||
virtual const char * GetDescription() const override
|
||||
{
|
||||
return "Shows how a car could be made with a SixDOFConstraint.\n"
|
||||
"Use the arrow keys to drive.";
|
||||
}
|
||||
|
||||
// See: Test
|
||||
virtual void Initialize() override;
|
||||
virtual void ProcessInput(const ProcessInputParams &inParams) override;
|
||||
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
|
||||
virtual void SaveInputState(StateRecorder &inStream) const override;
|
||||
virtual void RestoreInputState(StateRecorder &inStream) override;
|
||||
|
||||
virtual void GetInitialCamera(CameraState &ioState) const override;
|
||||
virtual RMat44 GetCameraPivot(float inCameraHeading, float inCameraPitch) const override { return mCameraPivot; }
|
||||
|
||||
private:
|
||||
static constexpr float cMaxSteeringAngle = DegreesToRadians(30);
|
||||
|
||||
using EAxis = SixDOFConstraintSettings::EAxis;
|
||||
|
||||
void UpdateCameraPivot();
|
||||
|
||||
enum class EWheel : int
|
||||
{
|
||||
LeftFront,
|
||||
RightFront,
|
||||
LeftRear,
|
||||
RightRear,
|
||||
Num,
|
||||
};
|
||||
|
||||
static inline bool sIsFrontWheel(EWheel inWheel) { return inWheel == EWheel::LeftFront || inWheel == EWheel::RightFront; }
|
||||
static inline bool sIsLeftWheel(EWheel inWheel) { return inWheel == EWheel::LeftFront || inWheel == EWheel::LeftRear; }
|
||||
|
||||
Body * mCarBody;
|
||||
Ref<SixDOFConstraint> mWheels[int(EWheel::Num)];
|
||||
RMat44 mCameraPivot = RMat44::sIdentity(); ///< The camera pivot, recorded before the physics update to align with the drawn world
|
||||
|
||||
// Player input
|
||||
float mSteeringAngle = 0.0f;
|
||||
float mSpeed = 0.0f;
|
||||
};
|
||||
174
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleStressTest.cpp
Normal file
174
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleStressTest.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <TestFramework.h>
|
||||
|
||||
#include <Tests/Vehicle/VehicleStressTest.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Vehicle/WheeledVehicleController.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Layers.h>
|
||||
#include <Renderer/DebugRendererImp.h>
|
||||
|
||||
JPH_IMPLEMENT_RTTI_VIRTUAL(VehicleStressTest)
|
||||
{
|
||||
JPH_ADD_BASE_CLASS(VehicleStressTest, VehicleTest)
|
||||
}
|
||||
|
||||
VehicleStressTest::~VehicleStressTest()
|
||||
{
|
||||
for (Ref<VehicleConstraint> &c : mVehicles)
|
||||
mPhysicsSystem->RemoveStepListener(c);
|
||||
}
|
||||
|
||||
void VehicleStressTest::Initialize()
|
||||
{
|
||||
CreateMeshTerrain();
|
||||
|
||||
// Create walls so the vehicles don't fall off
|
||||
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(50.0f, 5.0f, 0.5f)), RVec3(0, 0, -50), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
|
||||
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(50.0f, 5.0f, 0.5f)), RVec3(0, 0, 50), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
|
||||
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.5f, 5.0f, 50.0f)), RVec3(-50, 0, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
|
||||
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new BoxShape(Vec3(0.5f, 5.0f, 50.0f)), RVec3(50, 0, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING), EActivation::DontActivate);
|
||||
|
||||
const float wheel_radius = 0.3f;
|
||||
const float wheel_width = 0.1f;
|
||||
const float half_vehicle_length = 2.0f;
|
||||
const float half_vehicle_width = 0.9f;
|
||||
const float half_vehicle_height = 0.2f;
|
||||
const float max_steering_angle = DegreesToRadians(30.0f);
|
||||
|
||||
// Create vehicle body
|
||||
RefConst<Shape> car_shape = new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length));
|
||||
BodyCreationSettings car_body_settings(car_shape, RVec3::sZero(), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
|
||||
car_body_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
|
||||
car_body_settings.mMassPropertiesOverride.mMass = 1500.0f;
|
||||
|
||||
// Create vehicle constraint
|
||||
VehicleConstraintSettings vehicle;
|
||||
|
||||
// Wheels, left front
|
||||
WheelSettingsWV *w1 = new WheelSettingsWV;
|
||||
w1->mPosition = Vec3(half_vehicle_width, -0.9f * half_vehicle_height, half_vehicle_length - 2.0f * wheel_radius);
|
||||
w1->mMaxSteerAngle = max_steering_angle;
|
||||
w1->mMaxHandBrakeTorque = 0.0f; // Front wheel doesn't have hand brake
|
||||
|
||||
// Right front
|
||||
WheelSettingsWV *w2 = new WheelSettingsWV;
|
||||
w2->mPosition = Vec3(-half_vehicle_width, -0.9f * half_vehicle_height, half_vehicle_length - 2.0f * wheel_radius);
|
||||
w2->mMaxSteerAngle = max_steering_angle;
|
||||
w2->mMaxHandBrakeTorque = 0.0f; // Front wheel doesn't have hand brake
|
||||
|
||||
// Left rear
|
||||
WheelSettingsWV *w3 = new WheelSettingsWV;
|
||||
w3->mPosition = Vec3(half_vehicle_width, -0.9f * half_vehicle_height, -half_vehicle_length + 2.0f * wheel_radius);
|
||||
w3->mMaxSteerAngle = 0.0f;
|
||||
|
||||
// Right rear
|
||||
WheelSettingsWV *w4 = new WheelSettingsWV;
|
||||
w4->mPosition = Vec3(-half_vehicle_width, -0.9f * half_vehicle_height, -half_vehicle_length + 2.0f * wheel_radius);
|
||||
w4->mMaxSteerAngle = 0.0f;
|
||||
|
||||
vehicle.mWheels = { w1, w2, w3, w4 };
|
||||
|
||||
for (WheelSettings *w : vehicle.mWheels)
|
||||
{
|
||||
w->mRadius = wheel_radius;
|
||||
w->mWidth = wheel_width;
|
||||
}
|
||||
|
||||
// Controller
|
||||
WheeledVehicleControllerSettings *controller = new WheeledVehicleControllerSettings;
|
||||
vehicle.mController = controller;
|
||||
vehicle.mMaxPitchRollAngle = DegreesToRadians(60.0f);
|
||||
|
||||
// Differential
|
||||
controller->mDifferentials.resize(1);
|
||||
controller->mDifferentials[0].mLeftWheel = 0;
|
||||
controller->mDifferentials[0].mRightWheel = 1;
|
||||
|
||||
for (int x = 0; x < 15; ++x)
|
||||
for (int y = 0; y < 15; ++y)
|
||||
{
|
||||
// Create body
|
||||
car_body_settings.mPosition = RVec3(-28.0f + x * 4.0f, 2.0f, -35.0f + y * 5.0f);
|
||||
Body *car_body = mBodyInterface->CreateBody(car_body_settings);
|
||||
mBodyInterface->AddBody(car_body->GetID(), EActivation::Activate);
|
||||
|
||||
// Create constraint
|
||||
VehicleConstraint *c = new VehicleConstraint(*car_body, vehicle);
|
||||
c->SetNumStepsBetweenCollisionTestActive(2); // Only test collision every other step to speed up simulation
|
||||
c->SetNumStepsBetweenCollisionTestInactive(0); // Disable collision testing when inactive
|
||||
|
||||
// Set the collision tester
|
||||
VehicleCollisionTester *tester = new VehicleCollisionTesterRay(Layers::MOVING);
|
||||
c->SetVehicleCollisionTester(tester);
|
||||
|
||||
// Add the vehicle
|
||||
mPhysicsSystem->AddConstraint(c);
|
||||
mPhysicsSystem->AddStepListener(c);
|
||||
mVehicles.push_back(c);
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleStressTest::ProcessInput(const ProcessInputParams &inParams)
|
||||
{
|
||||
// Determine acceleration and brake
|
||||
mForward = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Up))
|
||||
mForward = 1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Down))
|
||||
mForward = -1.0f;
|
||||
|
||||
// Steering
|
||||
mRight = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Left))
|
||||
mRight = -1.0f;
|
||||
else if (inParams.mKeyboard->IsKeyPressed(EKey::Right))
|
||||
mRight = 1.0f;
|
||||
|
||||
// Hand brake will cancel gas pedal
|
||||
mHandBrake = 0.0f;
|
||||
if (inParams.mKeyboard->IsKeyPressed(EKey::Z))
|
||||
{
|
||||
mForward = 0.0f;
|
||||
mHandBrake = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleStressTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
|
||||
{
|
||||
for (VehicleConstraint *c : mVehicles)
|
||||
{
|
||||
// On user input, assure that the car is active
|
||||
if (mRight != 0.0f || mForward != 0.0f)
|
||||
mBodyInterface->ActivateBody(c->GetVehicleBody()->GetID());
|
||||
|
||||
// Pass the input on to the constraint
|
||||
WheeledVehicleController *controller = static_cast<WheeledVehicleController *>(c->GetController());
|
||||
controller->SetDriverInput(mForward, mRight, 0.0f, mHandBrake);
|
||||
|
||||
// Draw our wheels (this needs to be done in the pre update since we draw the bodies too in the state before the step)
|
||||
for (uint w = 0; w < 4; ++w)
|
||||
{
|
||||
const WheelSettings *settings = c->GetWheels()[w]->GetSettings();
|
||||
RMat44 wheel_transform = c->GetWheelWorldTransform(w, Vec3::sAxisY(), Vec3::sAxisX()); // The cylinder we draw is aligned with Y so we specify that as rotational axis
|
||||
mDebugRenderer->DrawCylinder(wheel_transform, 0.5f * settings->mWidth, settings->mRadius, Color::sGreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleStressTest::SaveInputState(StateRecorder &inStream) const
|
||||
{
|
||||
inStream.Write(mForward);
|
||||
inStream.Write(mRight);
|
||||
inStream.Write(mHandBrake);
|
||||
}
|
||||
|
||||
void VehicleStressTest::RestoreInputState(StateRecorder &inStream)
|
||||
{
|
||||
inStream.Read(mForward);
|
||||
inStream.Read(mRight);
|
||||
inStream.Read(mHandBrake);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Tests/Vehicle/VehicleTest.h>
|
||||
#include <Jolt/Physics/Vehicle/VehicleConstraint.h>
|
||||
|
||||
class VehicleStressTest : public Test
|
||||
{
|
||||
public:
|
||||
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, VehicleStressTest)
|
||||
|
||||
// Description of the test
|
||||
virtual const char * GetDescription() const override
|
||||
{
|
||||
return "This test simulates a large number of vehicles to test performance.";
|
||||
}
|
||||
|
||||
// Destructor
|
||||
virtual ~VehicleStressTest() override;
|
||||
|
||||
// See: Test
|
||||
virtual void Initialize() override;
|
||||
virtual void ProcessInput(const ProcessInputParams &inParams) override;
|
||||
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
|
||||
virtual void SaveInputState(StateRecorder &inStream) const override;
|
||||
virtual void RestoreInputState(StateRecorder &inStream) override;
|
||||
|
||||
private:
|
||||
Array<Ref<VehicleConstraint>> mVehicles; ///< The vehicle constraints
|
||||
|
||||
// Player input
|
||||
float mForward = 0.0f;
|
||||
float mRight = 0.0f;
|
||||
float mHandBrake = 0.0f;
|
||||
};
|
||||
338
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleTest.cpp
Normal file
338
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleTest.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <TestFramework.h>
|
||||
|
||||
#include <Tests/Vehicle/VehicleTest.h>
|
||||
#include <Jolt/Physics/Constraints/DistanceConstraint.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/ConvexHullShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/MeshShape.h>
|
||||
#include <Jolt/Physics/Collision/GroupFilterTable.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Jolt/Physics/PhysicsScene.h>
|
||||
#include <Jolt/ObjectStream/ObjectStreamIn.h>
|
||||
#include <Layers.h>
|
||||
#include <Application/DebugUI.h>
|
||||
#include <Utils/Log.h>
|
||||
#include <Utils/AssetStream.h>
|
||||
#include <Renderer/DebugRendererImp.h>
|
||||
|
||||
JPH_IMPLEMENT_RTTI_VIRTUAL(VehicleTest)
|
||||
{
|
||||
JPH_ADD_BASE_CLASS(VehicleTest, Test)
|
||||
}
|
||||
|
||||
const char *VehicleTest::sScenes[] =
|
||||
{
|
||||
"Flat",
|
||||
"Flat With Slope",
|
||||
"Steep Slope",
|
||||
"Step",
|
||||
"Dynamic Step",
|
||||
"Playground",
|
||||
"Loop",
|
||||
#ifdef JPH_OBJECT_STREAM
|
||||
"Terrain1",
|
||||
#endif // JPH_OBJECT_STREAM
|
||||
};
|
||||
|
||||
const char *VehicleTest::sSceneName = "Playground";
|
||||
|
||||
void VehicleTest::Initialize()
|
||||
{
|
||||
if (strcmp(sSceneName, "Flat") == 0)
|
||||
{
|
||||
// Flat test floor
|
||||
Body &floor = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(1000.0f, 1.0f, 1000.0f), 0.0f), RVec3(0.0f, -1.0f, 0.0f), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
|
||||
floor.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(floor.GetID(), EActivation::DontActivate);
|
||||
|
||||
// Load a race track to have something to assess speed and steering behavior
|
||||
LoadRaceTrack("Racetracks/Zandvoort.csv");
|
||||
}
|
||||
else if (strcmp(sSceneName, "Flat With Slope") == 0)
|
||||
{
|
||||
const float cSlopeStartDistance = 100.0f;
|
||||
const float cSlopeLength = 100.0f;
|
||||
const float cSlopeAngle = DegreesToRadians(30.0f);
|
||||
|
||||
// Flat test floor
|
||||
Body &floor = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(1000.0f, 1.0f, 1000.0f), 0.0f), RVec3(0.0f, -1.0f, 0.0f), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
|
||||
floor.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(floor.GetID(), EActivation::DontActivate);
|
||||
|
||||
Body &slope_up = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(25.0f, 1.0f, cSlopeLength), 0.0f), RVec3(0.0f, cSlopeLength * Sin(cSlopeAngle) - 1.0f, cSlopeStartDistance + cSlopeLength * Cos(cSlopeAngle)), Quat::sRotation(Vec3::sAxisX(), -cSlopeAngle), EMotionType::Static, Layers::NON_MOVING));
|
||||
slope_up.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(slope_up.GetID(), EActivation::DontActivate);
|
||||
|
||||
Body &slope_down = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(25.0f, 1.0f, cSlopeLength), 0.0f), RVec3(0.0f, cSlopeLength * Sin(cSlopeAngle) - 1.0f, cSlopeStartDistance + 3.0f * cSlopeLength * Cos(cSlopeAngle)), Quat::sRotation(Vec3::sAxisX(), cSlopeAngle), EMotionType::Static, Layers::NON_MOVING));
|
||||
slope_down.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(slope_down.GetID(), EActivation::DontActivate);
|
||||
}
|
||||
else if (strcmp(sSceneName, "Steep Slope") == 0)
|
||||
{
|
||||
// Steep slope test floor (20 degrees = 36% grade)
|
||||
Body &floor = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(1000.0f, 1.0f, 1000.0f), 0.0f), RVec3(0.0f, -1.0f, 0.0f), Quat::sRotation(Vec3::sAxisX(), DegreesToRadians(-20.0f)), EMotionType::Static, Layers::NON_MOVING));
|
||||
floor.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(floor.GetID(), EActivation::DontActivate);
|
||||
}
|
||||
else if (strcmp(sSceneName, "Step") == 0)
|
||||
{
|
||||
// Flat test floor
|
||||
Body &floor = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(1000.0f, 1.0f, 1000.0f), 0.0f), RVec3(0.0f, -1.0f, 0.0f), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
|
||||
floor.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(floor.GetID(), EActivation::DontActivate);
|
||||
|
||||
// A 5cm step rotated under an angle
|
||||
constexpr float cStepHeight = 0.05f;
|
||||
Body &step = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(5.0f, 0.5f * cStepHeight, 5.0f), 0.0f), RVec3(-2.0f, 0.5f * cStepHeight, 60.0f), Quat::sRotation(Vec3::sAxisY(), -0.3f * JPH_PI), EMotionType::Static, Layers::NON_MOVING));
|
||||
step.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(step.GetID(), EActivation::DontActivate);
|
||||
}
|
||||
else if (strcmp(sSceneName, "Dynamic Step") == 0)
|
||||
{
|
||||
// Flat test floor
|
||||
Body &floor = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(1000.0f, 1.0f, 1000.0f), 0.0f), RVec3(0.0f, -1.0f, 0.0f), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
|
||||
floor.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(floor.GetID(), EActivation::DontActivate);
|
||||
|
||||
// A dynamic body that acts as a step to test sleeping behavior
|
||||
constexpr float cStepHeight = 0.05f;
|
||||
Body &step = *mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3(15.0f, 0.5f * cStepHeight, 15.0f), 0.0f), RVec3(-2.0f, 0.5f * cStepHeight, 30.0f), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
|
||||
step.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(step.GetID(), EActivation::Activate);
|
||||
}
|
||||
else if (strcmp(sSceneName, "Playground") == 0)
|
||||
{
|
||||
// Scene with hilly terrain and some objects to drive into
|
||||
Body &floor = CreateMeshTerrain();
|
||||
floor.SetFriction(1.0f);
|
||||
|
||||
CreateBridge();
|
||||
|
||||
CreateWall();
|
||||
|
||||
CreateRubble();
|
||||
}
|
||||
else if (strcmp(sSceneName, "Loop") == 0)
|
||||
{
|
||||
CreateFloor();
|
||||
|
||||
TriangleList triangles;
|
||||
const int cNumSegments = 100;
|
||||
const float cLoopWidth = 20.0f;
|
||||
const float cLoopRadius = 20.0f;
|
||||
const float cLoopThickness = 0.5f;
|
||||
Vec3 prev_center = Vec3::sZero();
|
||||
Vec3 prev_center_bottom = Vec3::sZero();
|
||||
for (int i = 0; i < cNumSegments; ++i)
|
||||
{
|
||||
float angle = i * 2.0f * JPH_PI / (cNumSegments - 1);
|
||||
Vec3 radial(0, -Cos(angle), Sin(angle));
|
||||
Vec3 center = Vec3(-i * cLoopWidth / (cNumSegments - 1), cLoopRadius, cLoopRadius) + cLoopRadius * radial;
|
||||
Vec3 half_width(0.5f * cLoopWidth, 0, 0);
|
||||
Vec3 center_bottom = center + cLoopThickness * radial;
|
||||
if (i > 0)
|
||||
{
|
||||
// Top surface
|
||||
triangles.push_back(Triangle(prev_center + half_width, prev_center - half_width, center - half_width));
|
||||
triangles.push_back(Triangle(prev_center + half_width, center - half_width, center + half_width));
|
||||
|
||||
// Bottom surface
|
||||
triangles.push_back(Triangle(prev_center_bottom + half_width, center_bottom - half_width, prev_center_bottom - half_width));
|
||||
triangles.push_back(Triangle(prev_center_bottom + half_width, center_bottom + half_width, center_bottom - half_width));
|
||||
|
||||
// Sides
|
||||
triangles.push_back(Triangle(prev_center + half_width, center + half_width, prev_center_bottom + half_width));
|
||||
triangles.push_back(Triangle(prev_center_bottom + half_width, center + half_width, center_bottom + half_width));
|
||||
triangles.push_back(Triangle(prev_center - half_width, prev_center_bottom - half_width, center - half_width));
|
||||
triangles.push_back(Triangle(prev_center_bottom - half_width, center_bottom - half_width, center - half_width));
|
||||
}
|
||||
prev_center = center;
|
||||
prev_center_bottom = center_bottom;
|
||||
}
|
||||
MeshShapeSettings mesh(triangles);
|
||||
mesh.SetEmbedded();
|
||||
|
||||
Body &loop = *mBodyInterface->CreateBody(BodyCreationSettings(&mesh, RVec3::sZero(), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
|
||||
loop.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(loop.GetID(), EActivation::Activate);
|
||||
}
|
||||
#ifdef JPH_OBJECT_STREAM
|
||||
else
|
||||
{
|
||||
// Load scene
|
||||
Ref<PhysicsScene> scene;
|
||||
AssetStream stream(String(sSceneName) + ".bof", std::ios::in | std::ios::binary);
|
||||
if (!ObjectStreamIn::sReadObject(stream.Get(), scene))
|
||||
FatalError("Failed to load scene");
|
||||
for (BodyCreationSettings &body : scene->GetBodies())
|
||||
body.mObjectLayer = Layers::NON_MOVING;
|
||||
scene->FixInvalidScales();
|
||||
scene->CreateBodies(mPhysicsSystem);
|
||||
}
|
||||
#endif // JPH_OBJECT_STREAM
|
||||
}
|
||||
|
||||
void VehicleTest::CreateBridge()
|
||||
{
|
||||
const int cChainLength = 20;
|
||||
|
||||
// Build a collision group filter that disables collision between adjacent bodies
|
||||
Ref<GroupFilterTable> group_filter = new GroupFilterTable(cChainLength);
|
||||
for (CollisionGroup::SubGroupID i = 0; i < cChainLength - 1; ++i)
|
||||
group_filter->DisableCollision(i, i + 1);
|
||||
|
||||
Vec3 part_half_size = Vec3(2.5f, 0.25f, 1.0f);
|
||||
RefConst<Shape> part_shape = new BoxShape(part_half_size);
|
||||
|
||||
Vec3 large_part_half_size = Vec3(2.5f, 0.25f, 22.5f);
|
||||
RefConst<Shape> large_part_shape = new BoxShape(large_part_half_size);
|
||||
|
||||
Quat first_part_rot = Quat::sRotation(Vec3::sAxisX(), DegreesToRadians(-10.0f));
|
||||
|
||||
RVec3 prev_pos(-25, 7, 0);
|
||||
Body *prev_part = nullptr;
|
||||
|
||||
for (int i = 0; i < cChainLength; ++i)
|
||||
{
|
||||
RVec3 pos = prev_pos + Vec3(0, 0, 2.0f * part_half_size.GetZ());
|
||||
|
||||
Body &part = i == 0? *mBodyInterface->CreateBody(BodyCreationSettings(large_part_shape, pos - first_part_rot * Vec3(0, large_part_half_size.GetY() - part_half_size.GetY(), large_part_half_size.GetZ() - part_half_size.GetZ()), first_part_rot, EMotionType::Static, Layers::NON_MOVING))
|
||||
: *mBodyInterface->CreateBody(BodyCreationSettings(part_shape, pos, Quat::sIdentity(), i == 19? EMotionType::Static : EMotionType::Dynamic, i == 19? Layers::NON_MOVING : Layers::MOVING));
|
||||
part.SetCollisionGroup(CollisionGroup(group_filter, 1, CollisionGroup::SubGroupID(i)));
|
||||
part.SetFriction(1.0f);
|
||||
mBodyInterface->AddBody(part.GetID(), EActivation::Activate);
|
||||
|
||||
if (prev_part != nullptr)
|
||||
{
|
||||
DistanceConstraintSettings dc;
|
||||
dc.mPoint1 = prev_pos + Vec3(-part_half_size.GetX(), 0, part_half_size.GetZ());
|
||||
dc.mPoint2 = pos + Vec3(-part_half_size.GetX(), 0, -part_half_size.GetZ());
|
||||
mPhysicsSystem->AddConstraint(dc.Create(*prev_part, part));
|
||||
|
||||
dc.mPoint1 = prev_pos + Vec3(part_half_size.GetX(), 0, part_half_size.GetZ());
|
||||
dc.mPoint2 = pos + Vec3(part_half_size.GetX(), 0, -part_half_size.GetZ());
|
||||
mPhysicsSystem->AddConstraint(dc.Create(*prev_part, part));
|
||||
}
|
||||
|
||||
prev_part = ∂
|
||||
prev_pos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleTest::CreateWall()
|
||||
{
|
||||
RefConst<Shape> box_shape = new BoxShape(Vec3(0.5f, 0.5f, 0.5f));
|
||||
for (int i = 0; i < 3; ++i)
|
||||
for (int j = i / 2; j < 5 - (i + 1) / 2; ++j)
|
||||
{
|
||||
RVec3 position(2.0f + j * 1.0f + (i & 1? 0.5f : 0.0f), 2.0f + i * 1.0f, 10.0f);
|
||||
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleTest::CreateRubble()
|
||||
{
|
||||
// Flat and light objects
|
||||
RefConst<Shape> box_shape = new BoxShape(Vec3(0.5f, 0.1f, 0.5f));
|
||||
for (int i = 0; i < 5; ++i)
|
||||
for (int j = 0; j < 5; ++j)
|
||||
{
|
||||
RVec3 position(-5.0f + j, 2.0f + i * 0.2f, 10.0f + 0.5f * i);
|
||||
mBodyInterface->CreateAndAddBody(BodyCreationSettings(box_shape, position, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
|
||||
}
|
||||
|
||||
|
||||
// Light convex shapes
|
||||
default_random_engine random;
|
||||
uniform_real_distribution<float> hull_size(0.2f, 0.4f);
|
||||
for (int i = 0; i < 10; ++i)
|
||||
for (int j = 0; j < 10; ++j)
|
||||
{
|
||||
// Create random points
|
||||
Array<Vec3> points;
|
||||
for (int k = 0; k < 20; ++k)
|
||||
points.push_back(hull_size(random) * Vec3::sRandom(random));
|
||||
|
||||
mBodyInterface->CreateAndAddBody(BodyCreationSettings(new ConvexHullShapeSettings(points), RVec3(-5.0f + 0.5f * j, 2.0f, 15.0f + 0.5f * i), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING), EActivation::Activate);
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleTest::LoadRaceTrack(const char *inFileName)
|
||||
{
|
||||
// Open the track file
|
||||
AssetStream asset_stream(inFileName, std::ios::in);
|
||||
std::istream &stream = asset_stream.Get();
|
||||
|
||||
// Ignore header line
|
||||
String line;
|
||||
std::getline(stream, line);
|
||||
|
||||
// Read coordinates
|
||||
struct Segment
|
||||
{
|
||||
RVec3 mCenter;
|
||||
float mWidthLeft;
|
||||
float mWidthRight;
|
||||
};
|
||||
Array<Segment> segments;
|
||||
Real x, y;
|
||||
float wl, wr;
|
||||
char c;
|
||||
RVec3 track_center = RVec3::sZero();
|
||||
while (stream >> x >> c >> y >> c >> wl >> c >> wr)
|
||||
{
|
||||
RVec3 center(x, 0, -y);
|
||||
segments.push_back({ center, wl, wr });
|
||||
track_center += center;
|
||||
}
|
||||
if (!segments.empty())
|
||||
track_center /= (float)segments.size();
|
||||
|
||||
// Convert to line segments
|
||||
RVec3 prev_tleft = RVec3::sZero(), prev_tright = RVec3::sZero();
|
||||
for (size_t i = 0; i < segments.size(); ++i)
|
||||
{
|
||||
const Segment &segment = segments[i];
|
||||
const Segment &next_segment = segments[(i + 1) % segments.size()];
|
||||
|
||||
// Calculate left and right point of the track
|
||||
Vec3 fwd = Vec3(next_segment.mCenter - segment.mCenter);
|
||||
Vec3 right = fwd.Cross(Vec3::sAxisY()).Normalized();
|
||||
RVec3 tcenter = segment.mCenter - track_center + Vec3(0, 0.1f, 0); // Put a bit above the floor to avoid z fighting
|
||||
RVec3 tleft = tcenter - right * segment.mWidthLeft;
|
||||
RVec3 tright = tcenter + right * segment.mWidthRight;
|
||||
mTrackData.push_back({ tleft, tright });
|
||||
|
||||
// Connect left and right point with the previous left and right point
|
||||
if (i > 0)
|
||||
{
|
||||
mTrackData.push_back({ prev_tleft, tleft });
|
||||
mTrackData.push_back({ prev_tright, tright });
|
||||
}
|
||||
|
||||
prev_tleft = tleft;
|
||||
prev_tright = tright;
|
||||
}
|
||||
}
|
||||
|
||||
void VehicleTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
|
||||
{
|
||||
// Render the track
|
||||
for (const Line &l : mTrackData)
|
||||
mDebugRenderer->DrawLine(l.mStart, l.mEnd, Color::sBlack);
|
||||
}
|
||||
|
||||
void VehicleTest::CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu)
|
||||
{
|
||||
inUI->CreateTextButton(inSubMenu, "Select Scene", [this, inUI]() {
|
||||
UIElement *scene_name = inUI->CreateMenu();
|
||||
for (uint i = 0; i < size(sScenes); ++i)
|
||||
inUI->CreateTextButton(scene_name, sScenes[i], [this, i]() { sSceneName = sScenes[i]; RestartTest(); });
|
||||
inUI->ShowMenu(scene_name);
|
||||
});
|
||||
}
|
||||
|
||||
42
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleTest.h
Normal file
42
lib/All/JoltPhysics/Samples/Tests/Vehicle/VehicleTest.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Tests/Test.h>
|
||||
|
||||
// This is the base class for vehicle tests, it will create some sample geometry
|
||||
class VehicleTest : public Test
|
||||
{
|
||||
public:
|
||||
JPH_DECLARE_RTTI_VIRTUAL(JPH_NO_EXPORT, VehicleTest)
|
||||
|
||||
// See: Test
|
||||
virtual void PrePhysicsUpdate(const PreUpdateParams &inParams) override;
|
||||
virtual void Initialize() override;
|
||||
|
||||
// Optional settings menu
|
||||
virtual bool HasSettingsMenu() const override { return true; }
|
||||
virtual void CreateSettingsMenu(DebugUI *inUI, UIElement *inSubMenu) override;
|
||||
|
||||
private:
|
||||
// List of possible scene names
|
||||
static const char * sScenes[];
|
||||
|
||||
// Filename of animation to load for this test
|
||||
static const char * sSceneName;
|
||||
|
||||
void CreateBridge();
|
||||
void CreateWall();
|
||||
void CreateRubble();
|
||||
void LoadRaceTrack(const char *inFileName);
|
||||
|
||||
// A set of line segments to render a race track
|
||||
struct Line
|
||||
{
|
||||
RVec3 mStart;
|
||||
RVec3 mEnd;
|
||||
};
|
||||
Array<Line> mTrackData;
|
||||
};
|
||||
Reference in New Issue
Block a user