Add Vehicles support
This commit is contained in:
@@ -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, ¢erOfMassOffset);
|
||||
|
||||
// Grab old mass so we can apply new mass while maintaining inertia tensor
|
||||
const float oldMass = _actor->getMass();
|
||||
|
||||
@@ -14,7 +14,7 @@ class PhysicsColliderActor;
|
||||
API_CLASS() class FLAXENGINE_API RigidBody : public PhysicsActor
|
||||
{
|
||||
DECLARE_SCENE_OBJECT(RigidBody);
|
||||
private:
|
||||
protected:
|
||||
|
||||
PxRigidDynamic* _actor;
|
||||
Vector3 _cachedScale;
|
||||
|
||||
470
Source/Engine/Physics/Actors/WheeledVehicle.cpp
Normal file
470
Source/Engine/Physics/Actors/WheeledVehicle.cpp
Normal 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();
|
||||
}
|
||||
263
Source/Engine/Physics/Actors/WheeledVehicle.h
Normal file
263
Source/Engine/Physics/Actors/WheeledVehicle.h
Normal 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;
|
||||
};
|
||||
@@ -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)));
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
4
Source/ThirdParty/PhysX/PhysX.Build.cs
vendored
4
Source/ThirdParty/PhysX/PhysX.Build.cs
vendored
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user