Add Vehicles support

This commit is contained in:
Wojtek Figat
2021-03-26 15:11:07 +01:00
parent 5ca717a231
commit 4562dde31f
11 changed files with 1055 additions and 55 deletions

View File

@@ -276,7 +276,8 @@ void RigidBody::UpdateMass()
float raiseMassToPower = 0.75f;
// TODO: link physical material or expose density parameter
PxRigidBodyExt::updateMassAndInertia(*_actor, densityKGPerCubicUU);
PxVec3 centerOfMassOffset = C2P(_centerOfMassOffset);
PxRigidBodyExt::updateMassAndInertia(*_actor, densityKGPerCubicUU, &centerOfMassOffset);
// Grab old mass so we can apply new mass while maintaining inertia tensor
const float oldMass = _actor->getMass();

View File

@@ -14,7 +14,7 @@ class PhysicsColliderActor;
API_CLASS() class FLAXENGINE_API RigidBody : public PhysicsActor
{
DECLARE_SCENE_OBJECT(RigidBody);
private:
protected:
PxRigidDynamic* _actor;
Vector3 _cachedScale;

View File

@@ -0,0 +1,470 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "WheeledVehicle.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/Utilities.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Serialization/Serialization.h"
#if USE_EDITOR
#include "Engine/Level/Scene/SceneRendering.h"
#include "Engine/Debug/DebugDraw.h"
#endif
#if WITH_VEHICLE
#include <ThirdParty/PhysX/vehicle/PxVehicleSDK.h>
#include <ThirdParty/PhysX/vehicle/PxVehicleDrive4W.h>
#include <ThirdParty/PhysX/vehicle/PxVehicleUtilSetup.h>
#include <ThirdParty/PhysX/PxFiltering.h>
#endif
#if WITH_VEHICLE
extern void InitVehicleSDK();
extern Array<WheeledVehicle*> WheelVehicles;
#endif
WheeledVehicle::WheeledVehicle(const SpawnParams& params)
: RigidBody(params)
{
_useCCD = 1;
}
const Array<WheeledVehicle::Wheel>& WheeledVehicle::GetWheels() const
{
return _wheels;
}
void WheeledVehicle::SetWheels(const Array<Wheel>& value)
{
_wheels = value;
if (IsDuringPlay())
{
Setup();
}
}
WheeledVehicle::GearboxSettings WheeledVehicle::GetGearbox() const
{
return _gearbox;
}
void WheeledVehicle::SetGearbox(const GearboxSettings& value)
{
#if WITH_VEHICLE
auto& drive = (PxVehicleDrive4W*&)_drive;
if (drive)
{
drive->mDriveDynData.setUseAutoGears(value.AutoGear);
drive->mDriveDynData.setAutoBoxSwitchTime(Math::Max(value.SwitchTime, 0.0f));
}
#endif
_gearbox = value;
}
void WheeledVehicle::SetThrottle(float value)
{
_throttle = Math::Clamp(value, -1.0f, 1.0f);
}
void WheeledVehicle::SetSteering(float value)
{
_steering = Math::Clamp(value, -1.0f, 1.0f);
}
void WheeledVehicle::SetBrake(float value)
{
_brake = Math::Saturate(value);
}
void WheeledVehicle::SetHandbrake(float value)
{
_handBrake = Math::Saturate(value);
}
void WheeledVehicle::ClearInput()
{
_throttle = 0;
_steering = 0;
_brake = 0;
_handBrake = 0;
}
float WheeledVehicle::GetForwardSpeed() const
{
#if WITH_VEHICLE
auto& drive = (const PxVehicleDrive4W*&)_drive;
return drive ? drive->computeForwardSpeed() : 0.0f;
#else
return 0.0f;
#endif
}
float WheeledVehicle::GetSidewaysSpeed() const
{
#if WITH_VEHICLE
auto& drive = (const PxVehicleDrive4W*&)_drive;
return drive ? drive->computeSidewaysSpeed() : 0.0f;
#else
return 0.0f;
#endif
}
float WheeledVehicle::GetEngineRotationSpeed() const
{
#if WITH_VEHICLE
auto& drive = (const PxVehicleDrive4W*&)_drive;
return drive ? RadPerSToRpm(drive->mDriveDynData.getEngineRotationSpeed()) : 0.0f;
#else
return 0.0f;
#endif
}
int32 WheeledVehicle::GetCurrentGear() const
{
#if WITH_VEHICLE
auto& drive = (const PxVehicleDrive4W*&)_drive;
return drive ? (int32)drive->mDriveDynData.getCurrentGear() - 1 : 0;
#else
return 0;
#endif
}
void WheeledVehicle::SetCurrentGear(int32 value)
{
#if WITH_VEHICLE
auto& drive = (PxVehicleDrive4W*&)_drive;
if (drive)
{
drive->mDriveDynData.forceGearChange((PxU32)(value + 1));
}
#endif
}
int32 WheeledVehicle::GetTargetGear() const
{
#if WITH_VEHICLE
auto& drive = (const PxVehicleDrive4W*&)_drive;
return drive ? (int32)drive->mDriveDynData.getTargetGear() - 1 : 0;
#else
return 0;
#endif
}
void WheeledVehicle::SetTargetGear(int32 value)
{
#if WITH_VEHICLE
auto& drive = (PxVehicleDrive4W*&)_drive;
if (drive)
{
drive->mDriveDynData.startGearChange((PxU32)(value + 1));
}
#endif
}
void WheeledVehicle::GetWheelState(int32 index, WheelState& result)
{
if (index >= 0 && index < _wheels.Count())
{
const auto collider = _wheels[index].Collider.Get();
for (auto& wheelData : _wheelsData)
{
if (wheelData.Collider == collider)
{
result = wheelData.State;
return;
}
}
}
}
void WheeledVehicle::Setup()
{
#if WITH_VEHICLE
if (!_actor)
return;
auto& drive = (PxVehicleDrive4W*&)_drive;
// Get wheels
Array<Wheel*, FixedAllocation<PX_MAX_NB_WHEELS>> wheels;
_wheelsData.Clear();
for (auto& wheel : _wheels)
{
if (!wheel.Collider)
{
LOG(Warning, "Missing wheel collider in vehicle {0}", ToString());
continue;
}
if (wheel.Collider->GetParent() != this)
{
LOG(Warning, "Invalid wheel collider {1} in vehicle {0} attached to {2} (wheels needs to be added as children to vehicle)", ToString(), wheel.Collider->ToString(), wheel.Collider->GetParent() ? wheel.Collider->GetParent()->ToString() : String::Empty);
continue;
}
if (wheel.Collider->GetIsTrigger())
{
LOG(Warning, "Invalid wheel collider {1} in vehicle {0} cannot be a trigger", ToString(), wheel.Collider->ToString());
continue;
}
wheels.Add(&wheel);
}
if (wheels.IsEmpty())
{
// No wheel, no car
// No woman, no cry
if (drive)
{
WheelVehicles.Remove(this);
drive->free();
drive = nullptr;
}
return;
}
_wheelsData.Resize(wheels.Count());
InitVehicleSDK();
// Get linked shapes for the wheels mapping
Array<PxShape*, InlinedAllocation<8>> shapes;
shapes.Resize(_actor->getNbShapes());
_actor->getShapes(shapes.Get(), shapes.Count(), 0);
const PxTransform centerOfMassOffset = _actor->getCMassLocalPose();
// Initialize wheels simulation data
PxVec3 offsets[PX_MAX_NB_WHEELS];
for (int32 i = 0; i < wheels.Count(); i++)
{
Wheel& wheel = *wheels[i];
offsets[i] = C2P(wheel.Collider->GetLocalPosition());
}
PxF32 sprungMasses[PX_MAX_NB_WHEELS];
PxVehicleComputeSprungMasses(wheels.Count(), offsets, centerOfMassOffset.p, _actor->getMass(), 1, sprungMasses);
PxVehicleWheelsSimData* wheelsSimData = PxVehicleWheelsSimData::allocate(wheels.Count());
for (int32 i = 0; i < wheels.Count(); i++)
{
Wheel& wheel = *wheels[i];
auto& data = _wheelsData[i];
data.Collider = wheel.Collider;
data.LocalOrientation = wheel.Collider->GetLocalOrientation();
PxVehicleSuspensionData suspensionData;
// TODO: expose as wheel settings
const float suspensionFrequency = 7.0f;
const float suspensionDampingRatio = 1.0f;
suspensionData.mMaxCompression = 10.0f;
suspensionData.mMaxDroop = 10.0f;
suspensionData.mSprungMass = sprungMasses[i];
suspensionData.mSpringStrength = Math::Square(suspensionFrequency) * suspensionData.mSprungMass;
suspensionData.mSpringDamperRate = suspensionDampingRatio * 2.0f * Math::Sqrt(suspensionData.mSpringStrength * suspensionData.mSprungMass);
PxVehicleTireData tire;
tire.mType = 0;
PxVehicleWheelData wheelData;
wheelData.mMass = wheel.Mass;
wheelData.mRadius = wheel.Radius;
wheelData.mWidth = wheel.Width;
wheelData.mMOI = 0.5f * wheelData.mMass * Math::Square(wheelData.mRadius);
wheelData.mDampingRate = M2ToCm2(0.25f);
switch (wheel.Type)
{
case WheelType::FrontLeft:
case WheelType::FrontRight:
// Enable steering for the front wheels only
// TODO: expose as settings
wheelData.mMaxSteer = PI * 0.3333f;
wheelData.mMaxSteer = PI * 0.3333f;
break;
case WheelType::RearLeft:
case WheelType::ReadRight:
// Enable the handbrake for the rear wheels only
// TODO: expose as settings
wheelData.mMaxHandBrakeTorque = M2ToCm2(4000.0f);
wheelData.mMaxHandBrakeTorque = M2ToCm2(4000.0f);
break;
}
PxVec3 centreOffset = centerOfMassOffset.transformInv(offsets[i]);
float suspensionForceOffset = 0.0f;
PxVec3 forceAppPointOffset(centreOffset.x, centreOffset.y, centreOffset.z + suspensionForceOffset);
wheelsSimData->setTireData(i, tire);
wheelsSimData->setWheelData(i, wheelData);
wheelsSimData->setSuspensionData(i, suspensionData);
wheelsSimData->setSuspTravelDirection(i, PxVec3(0, -1, 0));
wheelsSimData->setWheelCentreOffset(i, centreOffset);
wheelsSimData->setSuspForceAppPointOffset(i, forceAppPointOffset);
wheelsSimData->setTireForceAppPointOffset(i, forceAppPointOffset);
PxShape* wheelShape = wheel.Collider->GetPxShape();
wheelsSimData->setWheelShapeMapping(i, shapes.Find(wheelShape));
// Setup Vehicle ID inside word3 for suspension raycasts to ignore self
PxFilterData filter = wheelShape->getQueryFilterData();
filter.word3 = _id.D + 1;
wheelShape->setQueryFilterData(filter);
wheelShape->setSimulationFilterData(filter);
wheelsSimData->setSceneQueryFilterData(i, filter);
}
for (auto child : Children)
{
auto collider = Cast<Collider>(child);
if (collider && collider->GetAttachedRigidBody() == this)
{
bool isWheel = false;
for (auto& w : wheels)
{
if (w->Collider == collider)
{
isWheel = true;
break;
}
}
if (!isWheel)
{
// Setup Vehicle ID inside word3 for suspension raycasts to ignore self
PxShape* shape = collider->GetPxShape();
PxFilterData filter = shape->getQueryFilterData();
filter.word3 = _id.D + 1;
shape->setQueryFilterData(filter);
shape->setSimulationFilterData(filter);
}
}
}
// Initialize vehicle drive simulation data
PxVehicleDriveSimData4W driveSimData;
{
// Differential
PxVehicleDifferential4WData diff;
// TODO: expose Differential options
diff.mType = PxVehicleDifferential4WData::eDIFF_TYPE_LS_4WD;
driveSimData.setDiffData(diff);
// Engine
PxVehicleEngineData engine;
// TODO: expose Engine options
engine.mMOI = M2ToCm2(1.0f);
engine.mPeakTorque = M2ToCm2(500.0f);
engine.mMaxOmega = RpmToRadPerS(6000.0f);
engine.mDampingRateFullThrottle = M2ToCm2(0.15f);
engine.mDampingRateZeroThrottleClutchEngaged = M2ToCm2(2.0f);
engine.mDampingRateZeroThrottleClutchDisengaged = M2ToCm2(0.35f);
driveSimData.setEngineData(engine);
// Gears
PxVehicleGearsData gears;
gears.mSwitchTime = Math::Max(_gearbox.SwitchTime, 0.0f);
driveSimData.setGearsData(gears);
// Auto Box
PxVehicleAutoBoxData autoBox;
driveSimData.setAutoBoxData(autoBox);
// Clutch
PxVehicleClutchData clutch;
// TODO: expose Clutch options
clutch.mStrength = M2ToCm2(10.0f);
driveSimData.setClutchData(clutch);
// Ackermann steer accuracy
PxVehicleAckermannGeometryData ackermann;
ackermann.mAxleSeparation = Math::Abs(wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_LEFT).x - wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_LEFT).x);
ackermann.mFrontWidth = Math::Abs(wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_RIGHT).z - wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eFRONT_LEFT).z);
ackermann.mRearWidth = Math::Abs(wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_RIGHT).z - wheelsSimData->getWheelCentreOffset(PxVehicleDrive4WWheelOrder::eREAR_LEFT).z);
driveSimData.setAckermannGeometryData(ackermann);
}
// Create vehicle drive
drive = PxVehicleDrive4W::allocate(wheels.Count());
drive->setup(CPhysX, _actor, *wheelsSimData, driveSimData, wheels.Count() - 4);
WheelVehicles.Add(this);
// Initialize
drive->setToRestState();
drive->mDriveDynData.forceGearChange(PxVehicleGearsData::eFIRST);
drive->mDriveDynData.setUseAutoGears(_gearbox.AutoGear);
wheelsSimData->free();
#else
LOG(Fatal, "PhysX Vehicle SDK is not supported.");
#endif
}
#if USE_EDITOR
void WheeledVehicle::DrawPhysicsDebug(RenderView& view)
{
for (auto& wheel : _wheels)
{
if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger())
{
DEBUG_DRAW_WIRE_CYLINDER(wheel.Collider->GetPosition(), wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.8f, 0, true);
}
}
}
void WheeledVehicle::OnDebugDrawSelected()
{
for (auto& wheel : _wheels)
{
if (wheel.Collider && wheel.Collider->GetParent() == this && !wheel.Collider->GetIsTrigger())
{
DEBUG_DRAW_WIRE_CYLINDER(wheel.Collider->GetPosition(), wheel.Collider->GetOrientation(), wheel.Radius, wheel.Width, Color::Red * 0.4f, 0, false);
}
}
RigidBody::OnDebugDrawSelected();
}
#endif
void WheeledVehicle::Serialize(SerializeStream& stream, const void* otherObj)
{
RigidBody::Serialize(stream, otherObj);
SERIALIZE_GET_OTHER_OBJ(WheeledVehicle);
SERIALIZE_MEMBER(Wheels, _wheels);
SERIALIZE(UseReverseAsBrake);
SERIALIZE_MEMBER(Gearbox, _gearbox);
}
void WheeledVehicle::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
RigidBody::Deserialize(stream, modifier);
DESERIALIZE_MEMBER(Wheels, _wheels);
DESERIALIZE(UseReverseAsBrake);
DESERIALIZE_MEMBER(Gearbox, _gearbox);
}
void WheeledVehicle::BeginPlay(SceneBeginData* data)
{
RigidBody::BeginPlay(data);
#if WITH_VEHICLE
Setup();
#endif
#if USE_EDITOR
GetSceneRendering()->AddPhysicsDebug<WheeledVehicle, &WheeledVehicle::DrawPhysicsDebug>(this);
#endif
}
void WheeledVehicle::EndPlay()
{
#if USE_EDITOR
GetSceneRendering()->RemovePhysicsDebug<WheeledVehicle, &WheeledVehicle::DrawPhysicsDebug>(this);
#endif
#if WITH_VEHICLE
auto& drive = (PxVehicleDrive4W*&)_drive;
if (drive)
{
// Parkway Drive
WheelVehicles.Remove(this);
drive->free();
drive = nullptr;
}
#endif
RigidBody::EndPlay();
}

View File

@@ -0,0 +1,263 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#pragma once
#include "Engine/Physics/Actors/RigidBody.h"
#include "Engine/Physics/Colliders/Collider.h"
#include "Engine/Scripting/ScriptingObjectReference.h"
class Physics;
/// <summary>
/// Representation of the car vehicle that uses wheels. Built on top of the RigidBody with collider representing its chassis shape and wheels.
/// </summary>
/// <seealso cref="RigidBody" />
API_CLASS() class FLAXENGINE_API WheeledVehicle : public RigidBody
{
friend Physics;
DECLARE_SCENE_OBJECT(WheeledVehicle);
public:
/// <summary>
/// Vehicle gearbox settings.
/// </summary>
API_STRUCT() struct GearboxSettings : ISerializable
{
DECLARE_SCRIPTING_TYPE_MINIMAL(GearboxSettings);
API_AUTO_SERIALIZATION();
/// <summary>
/// If enabled the vehicle gears will be changes automatically, otherwise it's fully manual.
/// </summary>
API_FIELD() bool AutoGear = true;
/// <summary>
/// Time it takes to switch gear. Specified in seconds (s).
/// </summary>
API_FIELD() float SwitchTime = 0.5f;
};
/// <summary>
/// Vehicle wheel types.
/// </summary>
API_ENUM() enum class WheelType
{
// Left wheel of the front axle.
FrontLeft,
// Right wheel of the front axle.
FrontRight,
// Left wheel of the rear axle.
RearLeft,
// Right wheel of the rear axle.
ReadRight,
// Non-drivable wheel.
NoDrive,
};
/// <summary>
/// Vehicle wheel settings.
/// </summary>
API_STRUCT() struct Wheel : ISerializable
{
DECLARE_SCRIPTING_TYPE_MINIMAL(Wheel);
API_AUTO_SERIALIZATION();
/// <summary>
/// Wheel placement type.
/// </summary>
API_FIELD() WheelType Type = WheelType::FrontLeft;
/// <summary>
/// Combined mass of the wheel and the tire in kg. Typically, a wheel has mass between 20Kg and 80Kg but can be lower and higher depending on the vehicle.
/// </summary>
API_FIELD() float Mass = 20.0f;
/// <summary>
/// Distance in metres between the center of the wheel and the outside rim of the tire. It is important that the value of the radius closely matches the radius of the render mesh of the wheel. Any mismatch will result in the wheels either hovering above the ground or intersecting the ground.
/// </summary>
API_FIELD() float Radius = 50.0f;
/// <summary>
/// Full width of the wheel in metres. This parameter has no bearing on the handling but is a very useful parameter to have when trying to render debug data relating to the wheel/tire/suspension.
/// </summary>
API_FIELD() float Width = 20.0f;
/// <summary>
/// Collider that represents the wheel shape and it's placement. Has to be attached as a child to the vehicle. Triangle mesh collider is not supported (use convex mesh or basic shapes).
/// </summary>
API_FIELD() ScriptingObjectReference<Collider> Collider;
};
/// <summary>
/// Vehicle wheel dynamic simulation state container.
/// </summary>
API_STRUCT() struct WheelState
{
DECLARE_SCRIPTING_TYPE_MINIMAL(WheelState);
/// <summary>
/// True if suspension travel limits forbid the wheel from touching the drivable surface.
/// </summary>
API_FIELD() bool IsInAir = false;
/// <summary>
/// The wheel is not in the air then it's set to the collider of the driving surface under the corresponding vehicle wheel.
/// </summary>
API_FIELD() PhysicsColliderActor* TireContactCollider = nullptr;
/// <summary>
/// The wheel is not in the air then it's set to the point on the drivable surface hit by the tire.
/// </summary>
API_FIELD() Vector3 TireContactPoint = Vector3::Zero;
/// <summary>
/// The wheel is not in the air then it's set to the normal on the drivable surface hit by the tire.
/// </summary>
API_FIELD() Vector3 TireContactNormal = Vector3::Zero;
/// <summary>
/// The friction experienced by the tire for the combination of tire type and surface type after accounting.
/// </summary>
API_FIELD() float TireFriction = 0.0f;
};
private:
struct WheelData
{
Collider* Collider;
Quaternion LocalOrientation;
WheelState State;
};
void* _drive = nullptr;
Array<WheelData, FixedAllocation<20>> _wheelsData;
float _throttle = 0.0f, _steering = 0.0f, _brake = 0.0f, _handBrake = 0.0f;
GearboxSettings _gearbox;
Array<Wheel> _wheels;
public:
/// <summary>
/// Gets the vehicle wheels settings.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(0), EditorDisplay(\"Vehicle\")") const Array<Wheel>& GetWheels() const;
/// <summary>
/// Sets the vehicle wheels settings.
/// </summary>
API_PROPERTY() void SetWheels(const Array<Wheel>& value);
/// <summary>
/// If checked, the negative throttle value will be used as brake and reverse to behave in a more arcade style where holding reverse also functions as brake. Disable it for more realistic driving controls.
/// </summary>
API_FIELD(Attributes="EditorOrder(1), EditorDisplay(\"Vehicle\")")
bool UseReverseAsBrake = true;
/// <summary>
/// Gets the vehicle gearbox settings.
/// </summary>
API_PROPERTY(Attributes="EditorOrder(5), EditorDisplay(\"Vehicle\")") GearboxSettings GetGearbox() const;
/// <summary>
/// Sets the vehicle gearbox settings.
/// </summary>
API_PROPERTY() void SetGearbox(const GearboxSettings& value);
public:
/// <summary>
/// Sets the input for vehicle throttle. It is the analog accelerator pedal value in range (0,1) where 1 represents the pedal fully pressed and 0 represents the pedal in its rest state.
/// </summary>
/// <param name="value">The value (-1,1 range). When using UseReverseAsBrake it can be negative and will be used as brake and backward driving.</param>
API_FUNCTION() void SetThrottle(float value);
/// <summary>
/// Sets the input for vehicle steering. Steer is the analog steer value in range (-1,1) where -1 represents the steering wheel at left lock and +1 represents the steering wheel at right lock.
/// </summary>
/// <param name="value">The value (-1,1 range).</param>
API_FUNCTION() void SetSteering(float value);
/// <summary>
/// Sets the input for vehicle brakes. Brake is the analog brake pedal value in range (0,1) where 1 represents the pedal fully pressed and 0 represents the pedal in its rest state.
/// </summary>
/// <param name="value">The value (0,1 range).</param>
API_FUNCTION() void SetBrake(float value);
/// <summary>
/// Sets the input for vehicle handbrake. Handbrake is the analog handbrake value in range (0,1) where 1 represents the handbrake fully engaged and 0 represents the handbrake in its rest state.
/// </summary>
/// <param name="value">The value (0,1 range).</param>
API_FUNCTION() void SetHandbrake(float value);
/// <summary>
/// Clears all the vehicle control inputs to the default values (throttle, steering, breaks).
/// </summary>
API_FUNCTION() void ClearInput();
public:
/// <summary>
/// Gets the current forward vehicle movement speed (along forward vector of the actor transform).
/// </summary>
API_PROPERTY() float GetForwardSpeed() const;
/// <summary>
/// Gets the current sideways vehicle movement speed (along right vector of the actor transform).
/// </summary>
API_PROPERTY() float GetSidewaysSpeed() const;
/// <summary>
/// Gets the current engine rotation speed (Revolutions Per Minute is the number of turns in one minute).
/// </summary>
API_PROPERTY() float GetEngineRotationSpeed() const;
/// <summary>
/// Gets the current gear number. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher.
/// </summary>
API_PROPERTY(Attributes="HideInEditor") int32 GetCurrentGear() const;
/// <summary>
/// Sets the current gear number. The gear change is instant. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher.
/// </summary>
API_PROPERTY() void SetCurrentGear(int32 value);
/// <summary>
/// Gets the target gear number. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher.
/// </summary>
API_PROPERTY(Attributes="HideInEditor") int32 GetTargetGear() const;
/// <summary>
/// Sets the target gear number. Gearbox will change the current gear to the target. Neutral gears is 0, reverse gears is -1, forward gears are 1 and higher.
/// </summary>
API_PROPERTY() void SetTargetGear(int32 value);
/// <summary>
/// Gets the current state of the wheel.
/// </summary>
/// <param name="index">The index of the wheel.</param>
/// <param name="result">The current state.</param>
API_FUNCTION() void GetWheelState(int32 index, API_PARAM(Out) WheelState& result);
private:
void Setup();
#if USE_EDITOR
void DrawPhysicsDebug(RenderView& view);
#endif
public:
// [Vehicle]
#if USE_EDITOR
void OnDebugDrawSelected() override;
#endif
void Serialize(SerializeStream& stream, const void* otherObj) override;
void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override;
protected:
// [Vehicle]
void BeginPlay(SceneBeginData* data) override;
void EndPlay() override;
};

View File

@@ -350,6 +350,9 @@ void Collider::CreateStaticActor()
_staticActor = CPhysX->createRigidStatic(trans);
ASSERT(_staticActor);
_staticActor->userData = this;
#if WITH_PVD
_staticActor->setActorFlag(PxActorFlag::eVISUALIZATION, true);
#endif
// Reset local pos of the shape and link it to the actor
_shape->setLocalPose(PxTransform(C2P(_center)));

View File

@@ -9,6 +9,9 @@
#include "Utilities.h"
#include "PhysicsStepper.h"
#include "SimulationEventCallback.h"
#if WITH_VEHICLE
#include "Actors/WheeledVehicle.h"
#endif
#include "Engine/Level/Level.h"
#include "Actors/PhysicsActor.h"
#include "Engine/Profiler/ProfilerCPU.h"
@@ -18,6 +21,9 @@
#include "Engine/Engine/Time.h"
#include <ThirdParty/PhysX/PxPhysicsAPI.h>
#include <ThirdParty/PhysX/PxActor.h>
#if WITH_VEHICLE
#include <ThirdParty/PhysX/vehicle/PxVehicleUpdate.h>
#endif
#if WITH_PVD
#include <ThirdParty/PhysX/pvd/PxPvd.h>
#endif
@@ -61,7 +67,7 @@ public:
}
};
PxFilterFlags PhysiXFilterShader(
PxFilterFlags FilterShader(
PxFilterObjectAttributes attributes0, PxFilterData filterData0,
PxFilterObjectAttributes attributes1, PxFilterData filterData1,
PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
@@ -146,8 +152,22 @@ namespace
PxMaterial* DefaultMaterial = nullptr;
PxControllerManager* ControllerManager = nullptr;
PxCpuDispatcher* CpuDispatcher = nullptr;
float LastDeltaTime = 0.0f;
#if WITH_VEHICLE
bool VehicleSDKInitialized = false;
Array<PxVehicleWheels*> WheelVehiclesCache;
Array<PxRaycastQueryResult> WheelQueryResults;
Array<PxRaycastHit> WheelHitResults;
Array<PxWheelQueryResult> WheelVehiclesResultsPerWheel;
Array<PxVehicleWheelQueryResult> WheelVehiclesResultsPerVehicle;
PxBatchQuery* WheelRaycastBatchQuery = nullptr;
PxVehicleDrivableSurfaceToTireFrictionPairs* WheelTireFrictions = nullptr;
#endif
}
#if WITH_VEHICLE
Array<WheeledVehicle*> WheelVehicles;
#endif
bool Physics::AutoSimulation = true;
uint32 Physics::LayerMasks[32];
@@ -172,7 +192,7 @@ PhysicsService PhysicsServiceInstance;
PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled)
{
#if WITH_PVD
PxShapeFlags flags = PxShapeFlag::eVISUALIZATION;
PxShapeFlags flags = PxShapeFlag::eVISUALIZATION;
#else
PxShapeFlags flags = static_cast<PxShapeFlags>(0);
#endif
@@ -194,6 +214,38 @@ PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled)
return flags;
}
#if WITH_VEHICLE
void InitVehicleSDK()
{
if (!VehicleSDKInitialized)
{
VehicleSDKInitialized = true;
PxInitVehicleSDK(*CPhysX);
PxVehicleSetBasisVectors(PxVec3(0, 1, 0), PxVec3(1, 0, 0));
PxVehicleSetUpdateMode(PxVehicleUpdateMode::eVELOCITY_CHANGE);
}
}
static PxQueryHitType::Enum WheelRaycastPreFilter(PxFilterData filterData0, PxFilterData filterData1, const void* constantBlock, PxU32 constantBlockSize, PxHitFlags& queryFlags)
{
// Hardcoded id for vehicle shapes masking
if (filterData0.word3 == filterData1.word3)
{
return PxQueryHitType::eNONE;
}
// Collide for pairs (A,B) where the filtermask of A contains the ID of B and vice versa
if ((filterData0.word0 & filterData1.word1) && (filterData1.word0 & filterData0.word1))
{
return PxQueryHitType::eBLOCK;
}
return PxQueryHitType::eNONE;
}
#endif
void PhysicsSettings::Apply()
{
Time::_physicsMaxDeltaTime = MaxDeltaTime;
@@ -317,13 +369,6 @@ bool PhysicsService::Init()
_foundation = PxCreateFoundation(PX_PHYSICS_VERSION, PhysXAllocatorCallback, PhysXErrorCallback);
CHECK_INIT(_foundation, "PxCreateFoundation failed!");
// Recording memory allocations is necessary if you want to
// use the memory facilities in PVD effectively. Since PVD isn't necessarily connected
// right away, we add a mechanism that records all outstanding memory allocations and
// forwards them to PVD when it does connect.
// This certainly has a performance and memory profile effect and thus should be used
// only in non-production builds.
#if PHYSX_MEMORY_STATS
_foundation->setReportAllocationNames(true);
#endif
@@ -334,64 +379,56 @@ bool PhysicsService::Init()
PxPvd* pvd = nullptr;
#if WITH_PVD
{
// Connection parameters
const char* pvd_host_ip = "127.0.0.1"; // IP of the PC which is running PVD
int port = 5425; // TCP port to connect to, where PVD is listening
unsigned int timeout = 100; // Timeout in milliseconds to wait for PVD to respond, consoles and remote PCs need a higher timeout.
// Init PVD
pvd = PxCreatePvd(*_foundation);
PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate(pvd_host_ip, port, timeout);
const bool isConnected = pvd->connect(*transport, PxPvdInstrumentationFlag::eALL);
if (isConnected)
{
LOG(Info, "Connected to PhysX Visual Debugger (PVD)"));
}
CPVD = pvd;
}
{
// Init PVD
pvd = PxCreatePvd(*_foundation);
PxPvdTransport* transport = PxDefaultPvdSocketTransportCreate("127.0.0.1", 5425, 100);
//PxPvdTransport* transport = PxDefaultPvdFileTransportCreate("D:\\physx_sample.pxd2");
if (transport)
{
const bool isConnected = pvd->connect(*transport, PxPvdInstrumentationFlag::eALL);
if (isConnected)
{
LOG(Info, "Connected to PhysX Visual Debugger (PVD)");
}
}
CPVD = pvd;
}
#endif
// Init top-level PhysX objects
// Init PhysX
CPhysX = PxCreatePhysics(PX_PHYSICS_VERSION, *_foundation, ToleranceScale, false, pvd);
CHECK_INIT(CPhysX, "PxCreatePhysics failed!");
// Init Extensions
// Init extensions
const bool extensionsInit = PxInitExtensions(*CPhysX, pvd);
CHECK_INIT(extensionsInit, "PxInitExtensions failed!");
#if WITH_VEHICLE
PxInitVehicleSDK(*Physics);
#endif
// Init collision cooking
#if COMPILE_WITH_PHYSICS_COOKING
#if !USE_EDITOR
if (settings.SupportCookingAtRuntime)
#endif
{
// Init cooking
PxCookingParams cookingParams(ToleranceScale);
cookingParams.meshWeldTolerance = 0.1f; // Weld to 1mm precision
cookingParams.meshPreprocessParams = PxMeshPreprocessingFlags(PxMeshPreprocessingFlag::eWELD_VERTICES);
Cooking = PxCreateCooking(PX_PHYSICS_VERSION, *_foundation, cookingParams);
CHECK_INIT(Cooking, "PxCreateCooking failed!");
}
#endif
// Create scene description
PxSceneDesc sceneDesc(CPhysX->getTolerancesScale());
sceneDesc.gravity = C2P(settings.DefaultGravity);
sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS;
//sceneDesc.flags |= PxSceneFlag::eEXCLUDE_KINEMATICS_FROM_ACTIVE_ACTORS; // TODO: set it?
if (!settings.DisableCCD)
sceneDesc.flags |= PxSceneFlag::eENABLE_CCD;
if (settings.EnableAdaptiveForce)
sceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE;
sceneDesc.simulationEventCallback = &EventsCallback;
sceneDesc.filterShader = PhysiXFilterShader;
sceneDesc.filterShader = FilterShader;
sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity;
if (sceneDesc.cpuDispatcher == nullptr)
{
@@ -407,6 +444,17 @@ bool PhysicsService::Init()
// Create scene
PhysicsScene = CPhysX->createScene(sceneDesc);
CHECK_INIT(PhysicsScene, "createScene failed!");
#if WITH_PVD
auto pvdClient = PhysicsScene->getScenePvdClient();
if (pvdClient)
{
pvdClient->setScenePvdFlags(PxPvdSceneFlag::eTRANSMIT_CONSTRAINTS | PxPvdSceneFlag::eTRANSMIT_SCENEQUERIES | PxPvdSceneFlag::eTRANSMIT_CONTACTS);
}
else
{
LOG(Info, "Missing PVD client scene.");
}
#endif
// Init characters controller
ControllerManager = PxCreateControllerManager(*PhysicsScene);
@@ -428,10 +476,24 @@ void PhysicsService::Dispose()
{
// Ensure to finish (wait for simulation end)
Physics::CollectResults();
// Cleanup
if (CPhysX)
Physics::FlushRequests();
#if WITH_VEHICLE
if (VehicleSDKInitialized)
{
VehicleSDKInitialized = false;
PxCloseVehicleSDK();
}
RELEASE_PHYSX(WheelRaycastBatchQuery);
RELEASE_PHYSX(WheelTireFrictions);
WheelQueryResults.Resize(0);
WheelHitResults.Resize(0);
WheelVehiclesResultsPerWheel.Resize(0);
WheelVehiclesResultsPerVehicle.Resize(0);
#endif
// Cleanup
RELEASE_PHYSX(DefaultMaterial);
SAFE_DELETE(Stepper);
Allocator::Free(ScratchMemory);
@@ -463,15 +525,12 @@ void PhysicsService::Dispose()
if (CPhysX)
{
#if WITH_VEHICLE
PxCloseVehicleSDK();
#endif
PxCloseExtensions();
}
RELEASE_PHYSX(CPhysX);
#if WITH_PVD
RELEASE_PHYSX(CPVD);
RELEASE_PHYSX(CPVD);
#endif
RELEASE_PHYSX(_foundation);
}
@@ -571,6 +630,7 @@ void Physics::Simulate(float dt)
if (Stepper->advance(PhysicsScene, dt, ScratchMemory, SCRATCH_BLOCK_SIZE) == false)
return;
EventsCallback.Clear();
LastDeltaTime = dt;
// TODO: move this call after rendering done
Stepper->renderDone();
@@ -590,6 +650,193 @@ void Physics::CollectResults()
Stepper->wait(PhysicsScene);
}
#if WITH_VEHICLE
if (WheelVehicles.HasItems())
{
PROFILE_CPU_NAMED("Physics.Vehicles");
// Update vehicles steering
WheelVehiclesCache.Clear();
WheelVehiclesCache.EnsureCapacity(WheelVehicles.Count());
int32 wheelsCount = 0;
for (auto wheelVehicle : WheelVehicles)
{
auto drive = (PxVehicleDrive4W*)wheelVehicle->_drive;
ASSERT(drive);
WheelVehiclesCache.Add(drive);
wheelsCount += drive->mWheelsSimData.getNbWheels();
PxVehicleDrive4WRawInputData rawInputData;
float throttle = wheelVehicle->_throttle;
float brake = wheelVehicle->_brake;
if (wheelVehicle->UseReverseAsBrake)
{
const float invalidDirectionThreshold = 80.0f;
const float breakThreshold = 8.0f;
const float forwardSpeed = wheelVehicle->GetForwardSpeed();
// Automatic gear change when changing driving direction
if (Math::Abs(forwardSpeed) < invalidDirectionThreshold)
{
if (throttle < -ZeroTolerance && wheelVehicle->GetCurrentGear() >= 0 && wheelVehicle->GetTargetGear() >= 0)
{
wheelVehicle->SetCurrentGear(-1);
}
else if (throttle > ZeroTolerance && wheelVehicle->GetCurrentGear() <= 0 && wheelVehicle->GetTargetGear() <= 0)
{
wheelVehicle->SetCurrentGear(1);
}
}
// Automatic break when changing driving direction
if (throttle > 0.0f)
{
if (forwardSpeed < -invalidDirectionThreshold)
{
brake = 1.0f;
}
}
else if (throttle < 0.0f)
{
if (forwardSpeed > invalidDirectionThreshold)
{
brake = 1.0f;
}
}
else
{
if (forwardSpeed < breakThreshold && forwardSpeed > -breakThreshold)
{
brake = 1.0f;
}
}
// Block throttle if user is changing driving direction
if ((throttle > 0.0f && wheelVehicle->GetTargetGear() < 0) || (throttle < 0.0f && wheelVehicle->GetTargetGear() > 0))
{
throttle = 0.0f;
}
throttle = Math::Abs(throttle);
}
else
{
throttle = Math::Max(throttle, 0.0f);
}
rawInputData.setAnalogAccel(throttle);
rawInputData.setAnalogBrake(brake);
rawInputData.setAnalogSteer(wheelVehicle->_steering);
rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake);
// @formatter:off
// Reference: PhysX SDK docs
// TODO: expose input control smoothing data
static constexpr PxVehiclePadSmoothingData padSmoothing =
{
{
6.0f, // rise rate eANALOG_INPUT_ACCEL
6.0f, // rise rate eANALOG_INPUT_BRAKE
12.0f, // rise rate eANALOG_INPUT_HANDBRAKE
2.5f, // rise rate eANALOG_INPUT_STEER_LEFT
2.5f, // rise rate eANALOG_INPUT_STEER_RIGHT
},
{
10.0f, // fall rate eANALOG_INPUT_ACCEL
10.0f, // fall rate eANALOG_INPUT_BRAKE
12.0f, // fall rate eANALOG_INPUT_HANDBRAKE
5.0f, // fall rate eANALOG_INPUT_STEER_LEFT
5.0f, // fall rate eANALOG_INPUT_STEER_RIGHT
}
};
// Reference: PhysX SDK docs
// TODO: expose steer vs forward curve into per-vehicle (up to 8 points, values clamped into 0/1 range)
static constexpr PxF32 steerVsForwardSpeedData[] =
{
0.0f, 1.0f,
20.0f, 0.9f,
65.0f, 0.8f,
120.0f, 0.7f,
PX_MAX_F32, PX_MAX_F32,
PX_MAX_F32, PX_MAX_F32,
PX_MAX_F32, PX_MAX_F32,
PX_MAX_F32, PX_MAX_F32,
};
const PxFixedSizeLookupTable<8> steerVsForwardSpeed(steerVsForwardSpeedData, 4);
// @formatter:on
PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *drive);
}
// Update batches queries cache
if (wheelsCount > WheelQueryResults.Count())
{
if (WheelRaycastBatchQuery)
WheelRaycastBatchQuery->release();
WheelQueryResults.Resize(wheelsCount);
WheelQueryResults.Resize(WheelQueryResults.Capacity());
PxBatchQueryDesc desc(wheelsCount, 0, 0);
desc.queryMemory.userRaycastResultBuffer = WheelQueryResults.Get();
desc.queryMemory.userRaycastTouchBuffer = WheelHitResults.Get();
desc.queryMemory.raycastTouchBufferSize = wheelsCount;
desc.preFilterShader = WheelRaycastPreFilter;
WheelRaycastBatchQuery = PhysicsScene->createBatchQuery(desc);
}
// TODO: expose vehicle tires configuration
if (!WheelTireFrictions)
{
PxVehicleDrivableSurfaceType surfaceTypes[1];
surfaceTypes[0].mType = 0;
const PxMaterial* surfaceMaterials[1];
surfaceMaterials[0] = DefaultMaterial;
WheelTireFrictions = PxVehicleDrivableSurfaceToTireFrictionPairs::allocate(1, 1);
WheelTireFrictions->setup(1, 1, surfaceMaterials, surfaceTypes);
WheelTireFrictions->setTypePairFriction(0, 0, 5.0f);
}
// Setup cache for wheel states
WheelVehiclesResultsPerVehicle.Resize(WheelVehicles.Count(), false);
WheelVehiclesResultsPerWheel.Resize(wheelsCount, false);
wheelsCount = 0;
for (int32 i = 0; i < WheelVehicles.Count(); i++)
{
auto drive = (PxVehicleDrive4W*)WheelVehicles[i]->_drive;
auto& perVehicle = WheelVehiclesResultsPerVehicle[i];
perVehicle.nbWheelQueryResults = drive->mWheelsSimData.getNbWheels();
perVehicle.wheelQueryResults = WheelVehiclesResultsPerWheel.Get() + wheelsCount;
wheelsCount += perVehicle.nbWheelQueryResults;
}
// Update vehicles
PxVehicleSuspensionRaycasts(WheelRaycastBatchQuery, WheelVehiclesCache.Count(), WheelVehiclesCache.Get(), WheelQueryResults.Count(), WheelQueryResults.Get());
PxVehicleUpdates(LastDeltaTime, PhysicsScene->getGravity(), *WheelTireFrictions, WheelVehiclesCache.Count(), WheelVehiclesCache.Get(), WheelVehiclesResultsPerVehicle.Get());
// Synchronize state
for (int32 i = 0; i < WheelVehicles.Count(); i++)
{
auto wheelVehicle = WheelVehicles[i];
auto drive = WheelVehiclesCache[i];
auto& perVehicle = WheelVehiclesResultsPerVehicle[i];
// Update wheels
for (int32 j = 0; j < wheelVehicle->_wheelsData.Count(); j++)
{
auto& wheelData = wheelVehicle->_wheelsData[j];
auto& perWheel = perVehicle.wheelQueryResults[j];
auto& state = wheelData.State;
state.IsInAir = perWheel.isInAir;
state.TireContactCollider = perWheel.tireContactShape ? static_cast<PhysicsColliderActor*>(perWheel.tireContactShape->userData) : nullptr;
state.TireContactPoint = P2C(perWheel.tireContactPoint);
state.TireContactNormal = P2C(perWheel.tireContactNormal);
state.TireFriction = perWheel.tireFriction;
const float wheelRotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j);
const float wheelSteerAngle = RadiansToDegrees * perWheel.steerAngle;
wheelData.Collider->SetLocalOrientation(Quaternion::Euler(0, wheelSteerAngle, wheelRotationAngle) * wheelData.LocalOrientation);
}
}
}
#endif
{
PROFILE_CPU_NAMED("Physics.FlushActiveTransforms");

View File

@@ -67,9 +67,8 @@ API_CLASS(Static) class FLAXENGINE_API Physics
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics);
/// <summary>
/// Gets master physics object
/// Gets the master physics object.
/// </summary>
/// <returns>Physics object</returns>
static PxPhysics* GetPhysics();
#if COMPILE_WITH_PHYSICS_COOKING
@@ -77,7 +76,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics);
/// <summary>
/// Gets physics cooking object
/// </summary>
/// <returns>Physics cooking object</returns>
static PxCooking* GetCooking();
#endif
@@ -85,37 +83,31 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics);
/// <summary>
/// Gets PhysX scene object
/// </summary>
/// <returns>Scene object</returns>
static PxScene* GetScene();
/// <summary>
/// Gets PhysX characters controller manager object
/// </summary>
/// <returns>Controller manager object</returns>
static PxControllerManager* GetControllerManager();
/// <summary>
/// Gets the physics tolerances scale.
/// </summary>
/// <returns>The tolerances scale.</returns>
static PxTolerancesScale* GetTolerancesScale();
/// <summary>
/// Gets the default query filter callback used for the scene queries.
/// </summary>
/// <returns>The query filter callback.</returns>
static PxQueryFilterCallback* GetQueryFilterCallback();
/// <summary>
/// Gets the default query filter callback used for the character controller collisions detection.
/// </summary>
/// <returns>The query filter callback.</returns>
static PxQueryFilterCallback* GetCharacterQueryFilterCallback();
/// <summary>
/// Gets the default physical material.
/// </summary>
/// <returns>The native material resource.</returns>
static PxMaterial* GetDefaultMaterial();
public:

View File

@@ -74,4 +74,24 @@ inline Vector3 P2C(const PxExtendedVec3& v)
#endif
}
inline float M2ToCm2(float v)
{
return v * (100.0f * 100.0f);
}
inline float Cm2ToM2(float v)
{
return v / (100.0f * 100.0f);
}
inline float RpmToRadPerS(float v)
{
return v * (PI / 30.0f);
}
inline float RadPerSToRpm(float v)
{
return v * (30.0f / PI);
}
extern PxShapeFlags GetShapeFlags(bool isTrigger, bool isEnabled);

View File

@@ -2183,6 +2183,9 @@ void TerrainPatch::CreateCollision()
_physicsActor = CPhysX->createRigidStatic(trans);
ASSERT(_physicsActor);
_physicsActor->userData = _terrain;
#if WITH_PVD
_physicsActor->setActorFlag(PxActorFlag::eVISUALIZATION, true);
#endif
_physicsActor->attachShape(*_physicsShape);
Physics::AddActor(_physicsActor);

View File

@@ -38,7 +38,7 @@ public class PhysX : DepsModule
bool useDynamicLinking = false;
bool usePVD = false;
bool useVehicle = false;
bool useVehicle = true;
bool usePhysicsCooking = Physics.WithCooking;
var depsRoot = options.DepsFolder;
@@ -96,7 +96,7 @@ public class PhysX : DepsModule
if (useVehicle)
{
AddLib(options, depsRoot, string.Format("PhysXVehicle_static_{0}", archPostFix));
AddLib(options, depsRoot, string.Format("PhysXVehicle_static{0}", archPostFix));
}
}
}