// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) // SPDX-FileCopyrightText: 2021 Jorrit Rouwe // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include 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 body_shape = new BoxShape(Vec3(half_vehicle_width, half_vehicle_height, half_vehicle_length)); Ref wheel_shape = new CylinderShape(half_wheel_width, half_wheel_height); wheel_shape->SetDensity(1.0e4f); // Create group filter Ref 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(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(DIK_LEFT)) mSteeringAngle = cMaxSteeringAngle; if (inParams.mKeyboard->IsKeyPressed(DIK_RIGHT)) mSteeringAngle = -cMaxSteeringAngle; if (inParams.mKeyboard->IsKeyPressed(DIK_UP)) mSpeed = max_rotation_speed; if (inParams.mKeyboard->IsKeyPressed(DIK_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); }