Adds feature for creating multiple physics scenes

This commit is contained in:
Iain Mckay
2021-12-01 15:30:31 +01:00
parent cc3617b5c2
commit a4e102672d
30 changed files with 2312 additions and 1337 deletions

View File

@@ -156,7 +156,7 @@ namespace FlaxEditor.States
private void SetupEditorEnvOptions()
{
Time.TimeScale = 1.0f;
Physics.AutoSimulation = true;
Physics.DefaultScene.AutoSimulation = true;
Screen.CursorVisible = true;
Screen.CursorLock = CursorLockMode.None;
}

View File

@@ -197,7 +197,7 @@ int32 Engine::Main(const Char* cmdLine)
}
// Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step)
Physics::CollectResults();
Physics::CollectResultsAll();
}
// Call on exit event
@@ -242,7 +242,7 @@ void Engine::OnFixedUpdate()
{
PROFILE_CPU_NAMED("Fixed Update");
Physics::FlushRequests();
Physics::FlushRequestsAll();
// Call event
FixedUpdate();
@@ -250,10 +250,10 @@ void Engine::OnFixedUpdate()
// Update services
EngineService::OnFixedUpdate();
if (!Time::GetGamePaused() && Physics::AutoSimulation)
if (!Time::GetGamePaused())
{
const float dt = Time::Physics.DeltaTime.GetTotalSeconds();
Physics::Simulate(dt);
Physics::SimulateAll(dt);
// After this point we should not modify physic objects state (rendering operations is mostly readonly)
// That's because auto-simulation mode is performing rendering during physics simulation
@@ -429,7 +429,7 @@ void Engine::OnExit()
EngineImpl::IsReady = false;
// Collect physics simulation results because we cannot exit with physics running
Physics::CollectResults();
Physics::CollectResultsAll();
// Before
Application::BeforeExit();

View File

@@ -17,6 +17,7 @@
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Serialization/ISerializeModifier.h"
@@ -77,6 +78,7 @@ Actor::Actor(const SpawnParams& params)
, _transform(Transform::Identity)
, _sphere(BoundingSphere::Empty)
, _box(BoundingBox::Zero)
, _physicsScene(nullptr)
, HideFlags(HideFlags::None)
{
}
@@ -1809,3 +1811,26 @@ void Actor::FromJson(const StringAnsiView& json)
Scripting::ObjectsLookupIdMapping.Set(nullptr);
OnTransformChanged();
}
void Actor::SetPhysicsScene(PhysicsScene* scene)
{
ASSERT(scene);
auto previous = GetPhysicsScene();
_physicsScene = scene;
if (previous != _physicsScene)
OnPhysicsSceneChanged(previous);
// cascade
for (auto child : Children)
child->SetPhysicsScene(scene);
}
PhysicsScene* Actor::GetPhysicsScene()
{
if (_physicsScene == nullptr)
_physicsScene = Physics::DefaultScene;
return _physicsScene;
}

View File

@@ -14,6 +14,7 @@ struct RenderView;
struct RenderContext;
class GPUContext;
class MemoryWriteStream;
class PhysicsScene;
class SceneRendering;
class SceneRenderTask;
@@ -47,6 +48,7 @@ protected:
BoundingSphere _sphere;
BoundingBox _box;
String _name;
PhysicsScene* _physicsScene;
private:
@@ -959,6 +961,20 @@ public:
/// <returns>The scene rendering interface.</returns>
SceneRendering* GetSceneRendering() const;
public:
/// <summary>
/// Set the physics world the controller is part of.
/// </summary>
API_PROPERTY(Attributes="HideInEditor") void SetPhysicsScene(PhysicsScene* scene);
/// <summary>
/// Get the physics world the controller is part of.
/// </summary>
API_PROPERTY(Attributes="HideInEditor") PhysicsScene* GetPhysicsScene();
protected:
virtual void OnPhysicsSceneChanged(PhysicsScene* previous) {};
private:
void SetSceneInHierarchy(Scene* scene);

View File

@@ -22,6 +22,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Engine/Time.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MDomain.h"
@@ -164,6 +165,8 @@ bool LevelImpl::spawnActor(Actor* actor, Actor* parent)
}
if (parent == nullptr)
parent = Level::Scenes[0];
actor->SetPhysicsScene(parent->GetPhysicsScene());
actor->SetParent(parent, true, true);
}
@@ -776,6 +779,8 @@ bool LevelImpl::unloadScene(Scene* scene)
// Force flush deleted objects so we actually delete unloaded scene objects (prevent from reloading their managed objects, etc.)
ObjectsRemovalService::Flush();
Physics::EndPlay();
return false;
}
@@ -1026,6 +1031,8 @@ bool Level::loadScene(rapidjson_flax::Value& data, int32 engineBuild, Scene** ou
{
PROFILE_CPU_NAMED("BeginPlay");
Physics::BeginPlay();
ScopeLock lock(ScenesLock);
Scenes.Add(scene);
SceneBeginData beginData;

View File

@@ -6,6 +6,8 @@
#include "Engine/Physics/Types.h"
#include "IPhysicsActor.h"
class PhysicsScene;
/// <summary>
/// A base class for all physical actors.
/// </summary>

View File

@@ -2,10 +2,14 @@
#include "RigidBody.h"
#include "PxMaterial.h"
#include <iterator>
#include "Engine/Core/Log.h"
#include "Engine/Physics/Utilities.h"
#include "Engine/Physics/Colliders/Collider.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicalMaterial.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Serialization/Serialization.h"
#include <ThirdParty/PhysX/extensions/PxRigidBodyExt.h>
#include <ThirdParty/PhysX/PxRigidActor.h>
@@ -476,7 +480,7 @@ void RigidBody::CreateActor()
// Register actor
const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy();
Physics::AddActor(_actor, putToSleep);
GetPhysicsScene()->AddActor(_actor, putToSleep);
// Update cached data
UpdateBounds();
@@ -580,7 +584,7 @@ void RigidBody::EndPlay()
if (_actor)
{
// Remove actor
Physics::RemoveActor(_actor);
GetPhysicsScene()->RemoveActor(_actor);
_actor = nullptr;
}
}
@@ -627,3 +631,15 @@ void RigidBody::OnTransformChanged()
PhysicsActor::OnTransformChanged();
}
}
void RigidBody::OnPhysicsSceneChanged(PhysicsScene* previous)
{
ASSERT(previous);
PhysicsActor::OnPhysicsSceneChanged(previous);
previous->UnlinkActor(_actor);
const bool putToSleep = !_startAwake && GetEnableSimulation() && !GetIsKinematic() && IsActiveInHierarchy();
GetPhysicsScene()->AddActor(_actor, putToSleep);
}

View File

@@ -572,4 +572,5 @@ protected:
void EndPlay() override;
void OnActiveInTreeChanged() override;
void OnTransformChanged() override;
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
};

View File

@@ -4,6 +4,7 @@
#include "Engine/Level/Actors/Spline.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Engine/Time.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Serialization/Serialization.h"
@@ -20,7 +21,7 @@ void SplineRopeBody::Tick()
PROFILE_CPU();
// Cache data
const Vector3 gravity = Physics::GetGravity() * GravityScale;
const Vector3 gravity = GetPhysicsScene()->GetGravity() * GravityScale;
auto& keyframes = _spline->Curve.GetKeyframes();
const Transform splineTransform = _spline->GetTransform();
const int32 keyframesCount = keyframes.Count();

View File

@@ -5,6 +5,7 @@
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/Utilities.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Serialization/Serialization.h"
#if USE_EDITOR
#include "Engine/Level/Scene/SceneRendering.h"
@@ -21,7 +22,6 @@
#if WITH_VEHICLE
extern void InitVehicleSDK();
extern Array<WheeledVehicle*> WheelVehicles;
#endif
namespace
@@ -237,7 +237,7 @@ void WheeledVehicle::Setup()
// Release previous
if (drive)
{
WheelVehicles.Remove(this);
GetPhysicsScene()->RemoveWheeledVehicle(this);
FreeDrive(_driveTypeCurrent, drive);
drive = nullptr;
}
@@ -501,7 +501,7 @@ void WheeledVehicle::Setup()
CRASH;
}
WheelVehicles.Add(this);
GetPhysicsScene()->AddWheeledVehicle(this);
wheelsSimData->free();
_actor->setSolverIterationCounts(12, 4);
@@ -621,6 +621,14 @@ void WheeledVehicle::OnColliderChanged(Collider* c)
Setup();
}
void WheeledVehicle::OnPhysicsSceneChanged(PhysicsScene* previous)
{
RigidBody::OnPhysicsSceneChanged(previous);
previous->RemoveWheeledVehicle(this);
GetPhysicsScene()->AddWheeledVehicle(this);
}
void WheeledVehicle::BeginPlay(SceneBeginData* data)
{
RigidBody::BeginPlay(data);
@@ -645,7 +653,7 @@ void WheeledVehicle::EndPlay()
if (drive)
{
// Parkway Drive
WheelVehicles.Remove(this);
GetPhysicsScene()->RemoveWheeledVehicle(this);
FreeDrive(_driveTypeCurrent, drive);
drive = nullptr;
}

View File

@@ -6,7 +6,7 @@
#include "Engine/Physics/Colliders/Collider.h"
#include "Engine/Scripting/ScriptingObjectReference.h"
class Physics;
class PhysicsScene;
/// <summary>
/// Representation of the car vehicle that uses wheels. Built on top of the RigidBody with collider representing its chassis shape and wheels.
@@ -14,7 +14,7 @@ class Physics;
/// <seealso cref="RigidBody" />
API_CLASS() class FLAXENGINE_API WheeledVehicle : public RigidBody
{
friend Physics;
friend PhysicsScene;
DECLARE_SCENE_OBJECT(WheeledVehicle);
public:
@@ -489,6 +489,7 @@ public:
void OnColliderChanged(Collider* c) override;
protected:
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
// [Vehicle]
void BeginPlay(SceneBeginData* data) override;

View File

@@ -7,9 +7,10 @@
#include "Engine/Serialization/Serialization.h"
#include "Engine/Engine/Time.h"
#include "Engine/Physics/PhysicalMaterial.h"
#include "Engine/Physics/PhysicsScene.h"
#include <ThirdParty/PhysX/PxRigidActor.h>
#include <ThirdParty/PhysX/PxRigidDynamic.h>
#include <ThirdParty/PhysX/PxPhysics.h>
#include <ThirdParty/PhysX/characterkinematic/PxController.h>
#include <ThirdParty/PhysX/characterkinematic/PxControllerManager.h>
#include <ThirdParty/PhysX/characterkinematic//PxCapsuleController.h>
@@ -156,7 +157,7 @@ CharacterController::CollisionFlags CharacterController::SimpleMove(const Vector
{
const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds();
Vector3 displacement = speed;
displacement += Physics::GetGravity() * deltaTime;
displacement += GetPhysicsScene()->GetGravity() * deltaTime;
displacement *= deltaTime;
return Move(displacement);
@@ -171,7 +172,7 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis
const float deltaTime = Time::GetCurrentSafe()->DeltaTime.GetTotalSeconds();
PxControllerFilters filters;
filters.mFilterData = (PxFilterData*)&_filterData;
filters.mFilterCallback = Physics::GetCharacterQueryFilterCallback();
filters.mFilterCallback = GetPhysicsScene()->GetCharacterQueryFilterCallback();
filters.mFilterFlags = PxQueryFlag::eDYNAMIC | PxQueryFlag::eSTATIC | PxQueryFlag::ePREFILTER;
filters.mCCTFilterCallback = Physics::GetCharacterControllerFilterCallback();
@@ -243,7 +244,7 @@ void CharacterController::CreateController()
desc.stepOffset = Math::Min(_stepOffset, desc.height + desc.radius * 2.0f - minSize);
// Create controller
_controller = (PxCapsuleController*)Physics::GetControllerManager()->createController(desc);
_controller = (PxCapsuleController*)GetPhysicsScene()->GetControllerManager()->createController(desc);
ASSERT(_controller);
_controller->setUpDirection(C2P(_upDirection));
const auto actor = _controller->getActor();
@@ -433,6 +434,14 @@ void CharacterController::OnTransformChanged()
}
}
void CharacterController::OnPhysicsSceneChanged(PhysicsScene* previous)
{
Collider::OnPhysicsSceneChanged(previous);
DeleteController();
CreateController();
}
void CharacterController::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base

View File

@@ -237,4 +237,5 @@ protected:
void OnDisable() override;
void OnParentChanged() override;
void OnTransformChanged() override;
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
};

View File

@@ -10,6 +10,7 @@
#include "Engine/Physics/PhysicsSettings.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicalMaterial.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Physics/Actors/RigidBody.h"
#include <ThirdParty/PhysX/geometry/PxGeometryQuery.h>
#include <ThirdParty/PhysX/PxShape.h>
@@ -17,7 +18,6 @@
#include <ThirdParty/PhysX/PxFiltering.h>
#include <ThirdParty/PhysX/PxRigidDynamic.h>
#include <ThirdParty/PhysX/PxRigidStatic.h>
#include <ThirdParty/PhysX/PxScene.h>
Collider::Collider(const SpawnParams& params)
: PhysicsColliderActor(params)
@@ -301,7 +301,7 @@ void Collider::UpdateGeometry()
actor->detachShape(*_shape);
// Release shape
Physics::RemoveCollider(this);
GetPhysicsScene()->RemoveCollider(this);
_shape->release();
_shape = nullptr;
@@ -346,14 +346,14 @@ void Collider::CreateStaticActor()
_shape->setLocalPose(PxTransform(C2P(_center)));
_staticActor->attachShape(*_shape);
Physics::AddActor(_staticActor);
GetPhysicsScene()->AddActor(_staticActor);
}
void Collider::RemoveStaticActor()
{
ASSERT(_staticActor != nullptr);
Physics::RemoveActor(_staticActor);
GetPhysicsScene()->RemoveActor(_staticActor);
_staticActor = nullptr;
}
@@ -447,7 +447,7 @@ void Collider::EndPlay()
}
// Release shape
Physics::RemoveCollider(this);
GetPhysicsScene()->RemoveCollider(this);
_shape->release();
_shape = nullptr;
}
@@ -537,3 +537,14 @@ void Collider::OnLayerChanged()
if (_shape)
UpdateLayerBits();
}
void Collider::OnPhysicsSceneChanged(PhysicsScene* previous)
{
PhysicsColliderActor::OnPhysicsSceneChanged(previous);
if (_staticActor != nullptr)
{
previous->UnlinkActor(_staticActor);
GetPhysicsScene()->AddActor(_staticActor);
}
}

View File

@@ -232,4 +232,6 @@ protected:
void OnParentChanged() override;
void OnTransformChanged() override;
void OnLayerChanged() override;
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
};

View File

@@ -6,6 +6,7 @@
#include "Engine/Serialization/Serialization.h"
#include "Engine/Physics/Utilities.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicsScene.h"
#if USE_EDITOR || !BUILD_RELEASE
#include "Engine/Debug/DebugLog.h"
#endif
@@ -20,7 +21,7 @@ MeshCollider::MeshCollider(const SpawnParams& params)
void MeshCollider::OnCollisionDataChanged()
{
// This should not be called during physics simulation, if it happened use write lock on physx scene
ASSERT(!GetScene() || !Physics::IsDuringSimulation());
ASSERT(!GetScene() || !GetPhysicsScene()->IsDuringSimulation());
if (CollisionData)
{

View File

@@ -8,12 +8,14 @@
#include "Engine/Serialization/Serialization.h"
#include "Engine/Physics/Utilities.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Profiler/ProfilerCPU.h"
#if COMPILE_WITH_PHYSICS_COOKING
#include "Engine/Physics/CollisionCooking.h"
#include <ThirdParty/PhysX/PxPhysics.h>
#include <ThirdParty/PhysX/extensions/PxDefaultStreams.h>
#endif
#include <ThirdParty/PhysX/geometry/PxTriangleMesh.h>
SplineCollider::SplineCollider(const SpawnParams& params)
@@ -45,7 +47,7 @@ void SplineCollider::ExtractGeometry(Array<Vector3>& vertexBuffer, Array<int32>&
void SplineCollider::OnCollisionDataChanged()
{
// This should not be called during physics simulation, if it happened use write lock on physx scene
ASSERT(!GetScene() || !Physics::IsDuringSimulation());
ASSERT(!GetScene() || !GetPhysicsScene()->IsDuringSimulation());
if (CollisionData)
{
@@ -172,7 +174,7 @@ void SplineCollider::EndPlay()
// Cleanup
if (_triangleMesh)
{
Physics::RemoveObject(_triangleMesh);
GetPhysicsScene()->RemoveObject(_triangleMesh);
_triangleMesh = nullptr;
}
}
@@ -306,7 +308,7 @@ void SplineCollider::GetGeometry(PxGeometryHolder& geometry)
// Create triangle mesh
if (_triangleMesh)
{
Physics::RemoveObject(_triangleMesh);
GetPhysicsScene()->RemoveObject(_triangleMesh);
_triangleMesh = nullptr;
}
PxDefaultMemoryInputData input(collisionData.Get(), collisionData.Length());

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "CollisionData.h"
#include "PhysicsScene.h"
#include "Engine/Core/Log.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/Assets/Model.h"
@@ -384,12 +386,16 @@ void CollisionData::unload(bool isReloading)
{
if (_convexMesh)
{
Physics::RemoveObject(_convexMesh);
for (auto scene : Physics::Scenes)
scene->RemoveObject(_convexMesh);
_convexMesh = nullptr;
}
if (_triangleMesh)
{
Physics::RemoveObject(_triangleMesh);
for (auto scene : Physics::Scenes)
scene->RemoveObject(_triangleMesh);
_triangleMesh = nullptr;
}
_options = CollisionDataOptions();

View File

@@ -181,7 +181,7 @@ void Joint::OnJointBreak()
void Joint::Delete()
{
// Remove the joint
Physics::RemoveJoint(this);
Physics::RemoveJointAll(this);
_joint->userData = nullptr;
_joint->release();
_joint = nullptr;

View File

@@ -3,696 +3,140 @@
#include "Physics.h"
#include "Utilities.h"
#include "CollisionData.h"
#include "PhysicsScene.h"
#include "Actors/PhysicsColliderActor.h"
#include <ThirdParty/PhysX/PxScene.h>
#include <ThirdParty/PhysX/PxQueryFiltering.h>
#include <ThirdParty/PhysX/PxRigidDynamic.h>
#include <ThirdParty/PhysX/characterkinematic/PxController.h>
// Temporary result buffer size
#define HIT_BUFFER_SIZE 128
template<typename HitType>
class DynamicHitBuffer : public PxHitCallback<HitType>
{
private:
uint32 _count;
HitType _buffer[HIT_BUFFER_SIZE];
public:
DynamicHitBuffer()
: PxHitCallback<HitType>(_buffer, HIT_BUFFER_SIZE)
, _count(0)
{
}
public:
// Computes the number of any hits in this result, blocking or touching.
PX_INLINE PxU32 getNbAnyHits() const
{
return getNbTouches();
}
// Convenience iterator used to access any hits in this result, blocking or touching.
PX_INLINE const HitType& getAnyHit(const PxU32 index) const
{
PX_ASSERT(index < getNbTouches() + PxU32(this->hasBlock));
return index < getNbTouches() ? getTouches()[index] : this->block;
}
PX_INLINE PxU32 getNbTouches() const
{
return _count;
}
PX_INLINE const HitType* getTouches() const
{
return _buffer;
}
PX_INLINE const HitType& getTouch(const PxU32 index) const
{
PX_ASSERT(index < getNbTouches());
return _buffer[index];
}
PX_INLINE PxU32 getMaxNbTouches() const
{
return HIT_BUFFER_SIZE;
}
protected:
PxAgain processTouches(const HitType* buffer, PxU32 nbHits) override
{
nbHits = Math::Min(nbHits, HIT_BUFFER_SIZE - _count);
for (PxU32 i = 0; i < nbHits; i++)
{
_buffer[_count + i] = buffer[i];
}
_count += nbHits;
return true;
}
void finalizeQuery() override
{
if (this->hasBlock)
{
// Blocking hits go to hits
processTouches(&this->block, 1);
}
}
};
#define SCENE_QUERY_SETUP(blockSingle) if (GetScene() == nullptr) return false; \
const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eUV; \
PxQueryFilterData filterData = PxQueryFilterData(); \
filterData.flags |= PxQueryFlag::ePREFILTER; \
filterData.data.word0 = layerMask; \
filterData.data.word1 = blockSingle ? 1 : 0; \
filterData.data.word2 = hitTriggers ? 1 : 0
#define SCENE_QUERY_SETUP_SWEEP_1() SCENE_QUERY_SETUP(true); \
PxSweepBufferN<1> buffer
#define SCENE_QUERY_SETUP_SWEEP() SCENE_QUERY_SETUP(false); \
DynamicHitBuffer<PxSweepHit> buffer
#define SCENE_QUERY_SETUP_OVERLAP_1() SCENE_QUERY_SETUP(false); \
PxOverlapBufferN<1> buffer
#define SCENE_QUERY_SETUP_OVERLAP() SCENE_QUERY_SETUP(false); \
DynamicHitBuffer<PxOverlapHit> buffer
#define SCENE_QUERY_COLLECT_SINGLE() const auto& hit = buffer.getAnyHit(0); \
hitInfo.Gather(hit)
#define SCENE_QUERY_COLLECT_ALL() results.Clear(); \
results.Resize(buffer.getNbAnyHits(), false); \
for (int32 i = 0; i < results.Count(); i++) \
{ \
const auto& hit = buffer.getAnyHit(i); \
results[i].Gather(hit); \
}
#define SCENE_QUERY_COLLECT_OVERLAP() results.Clear(); \
results.Resize(buffer.getNbTouches(), false); \
for (int32 i = 0; i < results.Count(); i++) \
{ \
auto& hitInfo = results[i]; \
const auto& hit = buffer.getTouch(i); \
hitInfo = hit.shape ? static_cast<::PhysicsColliderActor*>(hit.shape->userData) : nullptr; \
}
#define SCENE_QUERY_COLLECT_OVERLAP_COLLIDER() results.Clear(); \
results.Resize(buffer.getNbTouches(), false); \
for (int32 i = 0; i < results.Count(); i++) \
{ \
auto& hitInfo = results[i]; \
const auto& hit = buffer.getTouch(i); \
hitInfo = hit.shape ? static_cast<::Collider*>(hit.shape->userData) : nullptr; \
}
void RayCastHit::Gather(const PxRaycastHit& hit)
{
Point = P2C(hit.position);
Normal = P2C(hit.normal);
Distance = hit.distance;
Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr;
FaceIndex = hit.faceIndex;
UV.X = hit.u;
UV.Y = hit.v;
}
void RayCastHit::Gather(const PxSweepHit& hit)
{
Point = P2C(hit.position);
Normal = P2C(hit.normal);
Distance = hit.distance;
Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr;
FaceIndex = hit.faceIndex;
UV = Vector2::Zero;
}
class QueryFilter : public PxQueryFilterCallback
{
public:
PxQueryHitType::Enum preFilter(const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags) override
{
// Early out to avoid crashing
if (!shape)
return PxQueryHitType::eNONE;
// Check mask
const PxFilterData shapeFilter = shape->getQueryFilterData();
if ((filterData.word0 & shapeFilter.word0) == 0)
{
return PxQueryHitType::eNONE;
}
// Check if skip triggers
const bool hitTriggers = filterData.word2 != 0;
if (!hitTriggers && shape->getFlags() & PxShapeFlag::eTRIGGER_SHAPE)
return PxQueryHitType::eNONE;
const bool blockSingle = filterData.word1 != 0;
return blockSingle ? PxQueryHitType::eBLOCK : PxQueryHitType::eTOUCH;
}
PxQueryHitType::Enum postFilter(const PxFilterData& filterData, const PxQueryHit& hit) override
{
// Not used
return PxQueryHitType::eNONE;
}
};
class CharacterQueryFilter : public PxQueryFilterCallback
{
public:
PxQueryHitType::Enum preFilter(const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags) override
{
// Early out to avoid crashing
if (!shape)
return PxQueryHitType::eNONE;
// Let triggers through
if (PxFilterObjectIsTrigger(shape->getFlags()))
return PxQueryHitType::eNONE;
// Trigger the contact callback for pairs (A,B) where the filtermask of A contains the ID of B and vice versa
const PxFilterData shapeFilter = shape->getQueryFilterData();
if (filterData.word0 & shapeFilter.word1)
return PxQueryHitType::eBLOCK;
return PxQueryHitType::eNONE;
}
PxQueryHitType::Enum postFilter(const PxFilterData& filterData, const PxQueryHit& hit) override
{
// Not used
return PxQueryHitType::eNONE;
}
};
class CharacterControllerFilter : public PxControllerFilterCallback
{
private:
PxShape* getShape(const PxController& controller)
{
PxRigidDynamic* actor = controller.getActor();
// Early out if no actor or no shapes
if (!actor || actor->getNbShapes() < 1)
return nullptr;
// Get first shape only.
PxShape* shape = nullptr;
actor->getShapes(&shape, 1);
return shape;
}
public:
bool filter(const PxController& a, const PxController& b) override
{
// Early out to avoid crashing
PxShape* shapeA = getShape(a);
if (!shapeA)
return false;
PxShape* shapeB = getShape(b);
if (!shapeB)
return false;
// Let triggers through
if (PxFilterObjectIsTrigger(shapeB->getFlags()))
return false;
// Trigger the contact callback for pairs (A,B) where the filtermask of A contains the ID of B and vice versa
const PxFilterData shapeFilterA = shapeA->getQueryFilterData();
const PxFilterData shapeFilterB = shapeB->getQueryFilterData();
if (shapeFilterA.word0 & shapeFilterB.word1)
return true;
return false;
}
};
PxQueryFilterCallback* Physics::GetQueryFilterCallback()
{
static QueryFilter Filter;
return &Filter;
}
PxQueryFilterCallback* Physics::GetCharacterQueryFilterCallback()
{
static CharacterQueryFilter Filter;
return &Filter;
}
PxControllerFilterCallback* Physics::GetCharacterControllerFilterCallback()
{
static CharacterControllerFilter Filter;
return &Filter;
}
bool Physics::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer;
// Perform raycast test
return GetScene()->raycast(C2P(origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
return DefaultScene->RayCast(origin, direction, maxDistance, layerMask, hitTriggers);
}
bool Physics::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer;
// Perform raycast test
if (!GetScene()->raycast(C2P(origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
return DefaultScene->RayCast(origin, direction, hitInfo, maxDistance, layerMask, hitTriggers);
}
bool Physics::RayCastAll(const Vector3& origin, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP(false);
DynamicHitBuffer<PxRaycastHit> buffer;
// Perform raycast test
if (!GetScene()->raycast(C2P(origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
return DefaultScene->RayCastAll(origin, direction, results, maxDistance, layerMask, hitTriggers);
}
bool Physics::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
return DefaultScene->BoxCast(center, halfExtents, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
return DefaultScene->BoxCast(center, halfExtents, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
return DefaultScene->BoxCastAll(center, halfExtents, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::SphereCast(const Vector3& center, const float radius, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
return DefaultScene->SphereCast(center, radius, direction, maxDistance, layerMask, hitTriggers);
}
bool Physics::SphereCast(const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
return DefaultScene->SphereCast(center, radius, direction, hitInfo, maxDistance, layerMask, hitTriggers);
}
bool Physics::SphereCastAll(const Vector3& center, const float radius, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
return DefaultScene->SphereCastAll(center, radius, direction, results, maxDistance, layerMask, hitTriggers);
}
bool Physics::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
return DefaultScene->CapsuleCast(center, radius, height, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
return DefaultScene->CapsuleCast(center, radius, height, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::CapsuleCastAll(const Vector3& center, const float radius, const float height, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
return DefaultScene->CapsuleCastAll(center, radius, height, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
return DefaultScene->ConvexCast(center, convexMesh, scale, direction, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
return DefaultScene->ConvexCast(center, convexMesh, scale, direction, hitInfo, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
return DefaultScene->ConvexCastAll(center, convexMesh, scale, direction, results, rotation, maxDistance, layerMask, hitTriggers);
}
bool Physics::CheckBox(const Vector3& center, const Vector3& halfExtents, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
return DefaultScene->CheckBox(center, halfExtents, rotation, layerMask, hitTriggers);
}
bool Physics::CheckSphere(const Vector3& center, const float radius, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
return DefaultScene->CheckSphere(center, radius, layerMask, hitTriggers);
}
bool Physics::CheckCapsule(const Vector3& center, const float radius, const float height, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
return DefaultScene->CheckCapsule(center, radius, height, rotation, layerMask, hitTriggers);
}
bool Physics::CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
return DefaultScene->CheckConvex(center, convexMesh, scale, rotation, layerMask, hitTriggers);
}
bool Physics::OverlapBox(const Vector3& center, const Vector3& halfExtents, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
return DefaultScene->OverlapBox(center, halfExtents, results, rotation, layerMask, hitTriggers);
}
bool Physics::OverlapSphere(const Vector3& center, const float radius, Array<Collider*>& results, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
return DefaultScene->OverlapSphere(center, radius, results, layerMask, hitTriggers);
}
bool Physics::OverlapCapsule(const Vector3& center, const float radius, const float height, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
return DefaultScene->OverlapCapsule(center, radius, height, results, rotation, layerMask, hitTriggers);
}
bool Physics::OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
return DefaultScene->OverlapConvex(center, convexMesh, scale, results, rotation, layerMask, hitTriggers);
}
bool Physics::OverlapBox(const Vector3& center, const Vector3& halfExtents, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
return DefaultScene->OverlapBox(center, halfExtents, results, rotation, layerMask, hitTriggers);
}
bool Physics::OverlapSphere(const Vector3& center, const float radius, Array<PhysicsColliderActor*>& results, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
return DefaultScene->OverlapSphere(center, radius, results, layerMask, hitTriggers);
}
bool Physics::OverlapCapsule(const Vector3& center, const float radius, const float height, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
return DefaultScene->OverlapCapsule(center, radius, height, results, rotation, layerMask, hitTriggers);
}
bool Physics::OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
return DefaultScene->OverlapConvex(center, convexMesh, scale, results, rotation, layerMask, hitTriggers);
}

View File

@@ -1,36 +1,26 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "Physics.h"
#include "PhysicsScene.h"
#include "PhysicalMaterial.h"
#include "Engine/Core/Log.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Platform/CPUInfo.h"
#include "PhysicsSettings.h"
#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"
#include "Engine/Core/Memory/Memory.h"
#include "Engine/Engine/EngineService.h"
#include "Engine/Serialization/Serialization.h"
#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
// Temporary memory size used by the PhysX during the simulation. Must be multiply of 4kB and 16bit aligned.
#define SCRATCH_BLOCK_SIZE (1024 * 128)
#define PHYSX_VEHICLE_DEBUG_TELEMETRY 0
#if PHYSX_VEHICLE_DEBUG_TELEMETRY
@@ -73,57 +63,6 @@ public:
}
};
PxFilterFlags FilterShader(
PxFilterObjectAttributes attributes0, PxFilterData filterData0,
PxFilterObjectAttributes attributes1, PxFilterData filterData1,
PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
{
// Let triggers through
if (PxFilterObjectIsTrigger(attributes0) || PxFilterObjectIsTrigger(attributes1))
{
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_LOST;
pairFlags |= PxPairFlag::eDETECT_DISCRETE_CONTACT;
return PxFilterFlag::eDEFAULT;
}
// Send events for the kinematic actors but don't solve the contact
if (PxFilterObjectIsKinematic(attributes0) && PxFilterObjectIsKinematic(attributes1))
{
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_PERSISTS;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_LOST;
pairFlags |= PxPairFlag::eDETECT_DISCRETE_CONTACT;
return PxFilterFlag::eSUPPRESS;
}
// Trigger the contact callback 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))
{
pairFlags |= PxPairFlag::eSOLVE_CONTACT;
pairFlags |= PxPairFlag::eDETECT_DISCRETE_CONTACT;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_PERSISTS;
pairFlags |= PxPairFlag::ePOST_SOLVER_VELOCITY;
pairFlags |= PxPairFlag::eNOTIFY_CONTACT_POINTS;
return PxFilterFlag::eDEFAULT;
}
// Ignore pair (no collisions nor events)
return PxFilterFlag::eKILL;
}
enum class ActionType
{
Sleep,
};
struct ActionData
{
ActionType Type;
PxActor* Actor;
};
PxPhysics* CPhysX = nullptr;
#if WITH_PVD
PxPvd* CPVD = nullptr;
@@ -133,48 +72,27 @@ namespace
{
PhysXAllocator PhysXAllocatorCallback;
PhysXError PhysXErrorCallback;
PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader;
PxTolerancesScale ToleranceScale;
SimulationEventCallback EventsCallback;
void* ScratchMemory = nullptr;
FixedStepper* Stepper = nullptr;
CriticalSection FlushLocker;
Array<PxActor*> NewActors;
Array<ActionData> Actions;
Array<PxActor*> DeadActors;
Array<PxMaterial*> DeadMaterials;
Array<PxBase*> _deadObjects;
Array<PhysicsColliderActor*> DeadColliders;
Array<Joint*> DeadJoints;
bool _queriesHitTriggers = true;
bool _isDuringSimulation = false;
PhysicsCombineMode _frictionCombineMode = PhysicsCombineMode::Average;
PhysicsCombineMode _restitutionCombineMode = PhysicsCombineMode::Average;
PxFoundation* _foundation = nullptr;
#if COMPILE_WITH_PHYSICS_COOKING
PxCooking* Cooking = nullptr;
#endif
PxScene* PhysicsScene = nullptr;
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
void reset()
{
Physics::Scenes.Resize(1);
}
}
#if WITH_VEHICLE
Array<WheeledVehicle*> WheelVehicles;
#endif
bool Physics::AutoSimulation = true;
PhysicsScene* Physics::DefaultScene = nullptr;
Array<PhysicsScene*> Physics::Scenes;
uint32 Physics::LayerMasks[32];
class PhysicsService : public EngineService
@@ -233,23 +151,6 @@ void InitVehicleSDK()
}
}
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()
@@ -325,7 +226,8 @@ PhysicalMaterial::~PhysicalMaterial()
{
if (_material)
{
Physics::RemoveMaterial(_material);
for (auto scene : Physics::Scenes)
scene->RemoveMaterial(_material);
}
}
@@ -422,45 +324,8 @@ bool PhysicsService::Init()
}
#endif
// Create scene description
PxSceneDesc sceneDesc(CPhysX->getTolerancesScale());
sceneDesc.gravity = C2P(settings.DefaultGravity);
sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS;
if (!settings.DisableCCD)
sceneDesc.flags |= PxSceneFlag::eENABLE_CCD;
if (settings.EnableAdaptiveForce)
sceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE;
sceneDesc.simulationEventCallback = &EventsCallback;
sceneDesc.filterShader = FilterShader;
sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity;
if (sceneDesc.cpuDispatcher == nullptr)
{
CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp<uint32>(cpuInfo.ProcessorCoreCount - 1, 1, 4));
CHECK_INIT(CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!");
sceneDesc.cpuDispatcher = CpuDispatcher;
}
if (sceneDesc.filterShader == nullptr)
{
sceneDesc.filterShader = PhysXDefaultFilterShader;
}
// 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);
Physics::DefaultScene = new PhysicsScene(String("Default"), settings, cpuInfo);
Physics::Scenes.Add(Physics::DefaultScene);
// Create default resources
DefaultMaterial = CPhysX->createMaterial(0.7f, 0.7f, 0.3f);
@@ -472,15 +337,21 @@ bool PhysicsService::Init()
void PhysicsService::LateUpdate()
{
Physics::FlushRequests();
for (auto scene : Physics::Scenes)
scene->FlushRequests();
}
void PhysicsService::Dispose()
{
// Ensure to finish (wait for simulation end)
Physics::CollectResults();
if (CPhysX)
Physics::FlushRequests();
for (auto scene : Physics::Scenes)
{
scene->CollectResults();
if (CPhysX)
scene->FlushRequests();
}
#if WITH_VEHICLE
if (VehicleSDKInitialized)
@@ -488,21 +359,13 @@ void PhysicsService::Dispose()
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);
ScratchMemory = nullptr;
RELEASE_PHYSX(ControllerManager);
SAFE_DELETE(CpuDispatcher);
Physics::Scenes.Resize(0);
Physics::DefaultScene = nullptr;
// Remove all scenes still registered
const int32 numScenes = CPhysX ? CPhysX->getNbScenes() : 0;
@@ -548,16 +411,6 @@ PxCooking* Physics::GetCooking()
return Cooking;
}
PxScene* Physics::GetScene()
{
return PhysicsScene;
}
PxControllerManager* Physics::GetControllerManager()
{
return ControllerManager;
}
PxTolerancesScale* Physics::GetTolerancesScale()
{
return &ToleranceScale;
@@ -565,415 +418,70 @@ PxTolerancesScale* Physics::GetTolerancesScale()
Vector3 Physics::GetGravity()
{
return PhysicsScene ? P2C(PhysicsScene->getGravity()) : Vector3::Zero;
return DefaultScene ? DefaultScene->GetGravity() : Vector3::Zero;
}
void Physics::SetGravity(const Vector3& value)
{
if (PhysicsScene)
PhysicsScene->setGravity(C2P(value));
if (DefaultScene)
DefaultScene->SetGravity(value);
}
bool Physics::GetEnableCCD()
{
return PhysicsScene ? (PhysicsScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings::Get()->DisableCCD;
return DefaultScene ? DefaultScene->GetEnableCCD() : !PhysicsSettings::Get()->DisableCCD;
}
void Physics::SetEnableCCD(const bool value)
{
if (PhysicsScene)
PhysicsScene->setFlag(PxSceneFlag::eENABLE_CCD, value);
if (DefaultScene)
DefaultScene->SetEnableCCD(value);
}
float Physics::GetBounceThresholdVelocity()
{
return PhysicsScene ? PhysicsScene->getBounceThresholdVelocity() : PhysicsSettings::Get()->BounceThresholdVelocity;
return DefaultScene ? DefaultScene->GetBounceThresholdVelocity() : PhysicsSettings::Get()->BounceThresholdVelocity;
}
void Physics::SetBounceThresholdVelocity(const float value)
{
if (PhysicsScene)
PhysicsScene->setBounceThresholdVelocity(value);
if (DefaultScene)
DefaultScene->SetBounceThresholdVelocity(value);
}
void Physics::Simulate(float dt)
{
ASSERT(IsInMainThread() && !_isDuringSimulation);
ASSERT(CPhysX);
const auto& settings = *PhysicsSettings::Get();
if (DefaultScene)
DefaultScene->Simulate(dt);
}
// Flush the old/new objects and the other requests before the simulation
FlushRequests();
// Clamp delta
dt = Math::Clamp(dt, 0.0f, settings.MaxDeltaTime);
// Prepare util objects
if (ScratchMemory == nullptr)
void Physics::SimulateAll(float dt)
{
for (auto scene : Scenes)
{
ScratchMemory = Allocator::Allocate(SCRATCH_BLOCK_SIZE, 16);
if (scene->GetAutoSimulation())
scene->Simulate(dt);
}
if (Stepper == nullptr)
{
Stepper = New<FixedStepper>();
}
if (settings.EnableSubstepping)
{
// Use substeps
Stepper->Setup(settings.SubstepDeltaTime, settings.MaxSubsteps);
}
else
{
// Use single step
Stepper->Setup(dt);
}
// Start simulation (may not be fired due to too small delta time)
_isDuringSimulation = true;
if (Stepper->advance(PhysicsScene, dt, ScratchMemory, SCRATCH_BLOCK_SIZE) == false)
return;
EventsCallback.Clear();
LastDeltaTime = dt;
// TODO: move this call after rendering done
Stepper->renderDone();
}
void Physics::CollectResults()
{
if (!_isDuringSimulation)
return;
ASSERT(IsInMainThread());
ASSERT(CPhysX && Stepper);
if (DefaultScene)
DefaultScene->CollectResults();
}
{
PROFILE_CPU_NAMED("Physics.Fetch");
// Gather results (with waiting for the end)
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)
{
if (!wheelVehicle->IsActiveInHierarchy())
continue;
auto drive = (PxVehicleWheels*)wheelVehicle->_drive;
ASSERT(drive);
WheelVehiclesCache.Add(drive);
wheelsCount += drive->mWheelsSimData.getNbWheels();
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);
}
// @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
}
};
PxVehicleKeySmoothingData keySmoothing =
{
{
3.0f, // rise rate eANALOG_INPUT_ACCEL
3.0f, // rise rate eANALOG_INPUT_BRAKE
10.0f, // rise rate eANALOG_INPUT_HANDBRAKE
2.5f, // rise rate eANALOG_INPUT_STEER_LEFT
2.5f, // rise rate eANALOG_INPUT_STEER_RIGHT
},
{
5.0f, // fall rate eANALOG_INPUT__ACCEL
5.0f, // fall rate eANALOG_INPUT__BRAKE
10.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
if (wheelVehicle->UseAnalogSteering)
{
switch (wheelVehicle->_driveTypeCurrent)
{
case WheeledVehicle::DriveTypes::Drive4W:
{
PxVehicleDrive4WRawInputData rawInputData;
rawInputData.setAnalogAccel(throttle);
rawInputData.setAnalogBrake(brake);
rawInputData.setAnalogSteer(wheelVehicle->_steering);
rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake);
PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive);
break;
}
case WheeledVehicle::DriveTypes::DriveNW:
{
PxVehicleDriveNWRawInputData rawInputData;
rawInputData.setAnalogAccel(throttle);
rawInputData.setAnalogBrake(brake);
rawInputData.setAnalogSteer(wheelVehicle->_steering);
rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake);
PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive);
break;
}
}
}
else
{
const float deadZone = 0.1f;
switch (wheelVehicle->_driveTypeCurrent)
{
case WheeledVehicle::DriveTypes::Drive4W:
{
PxVehicleDrive4WRawInputData rawInputData;
rawInputData.setDigitalAccel(throttle > deadZone);
rawInputData.setDigitalBrake(brake > deadZone);
rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone);
rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone);
rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone);
PxVehicleDrive4WSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDrive4W*)drive);
break;
}
case WheeledVehicle::DriveTypes::DriveNW:
{
PxVehicleDriveNWRawInputData rawInputData;
rawInputData.setDigitalAccel(throttle > deadZone);
rawInputData.setDigitalBrake(brake > deadZone);
rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone);
rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone);
rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone);
PxVehicleDriveNWSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, LastDeltaTime, false, *(PxVehicleDriveNW*)drive);
break;
}
}
}
}
// Update batches queries cache
if (wheelsCount > WheelQueryResults.Count())
{
if (WheelRaycastBatchQuery)
WheelRaycastBatchQuery->release();
WheelQueryResults.Resize(wheelsCount, false);
WheelHitResults.Resize(wheelsCount, false);
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(WheelVehiclesCache.Count(), false);
WheelVehiclesResultsPerWheel.Resize(wheelsCount, false);
wheelsCount = 0;
for (int32 i = 0, ii = 0; i < WheelVehicles.Count(); i++)
{
auto wheelVehicle = WheelVehicles[i];
if (!wheelVehicle->IsActiveInHierarchy())
continue;
auto drive = (PxVehicleWheels*)WheelVehicles[ii]->_drive;
auto& perVehicle = WheelVehiclesResultsPerVehicle[ii];
ii++;
perVehicle.nbWheelQueryResults = drive->mWheelsSimData.getNbWheels();
perVehicle.wheelQueryResults = WheelVehiclesResultsPerWheel.Get() + wheelsCount;
wheelsCount += perVehicle.nbWheelQueryResults;
}
// Update vehicles
if (WheelVehiclesCache.Count() != 0)
{
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, ii = 0; i < WheelVehicles.Count(); i++)
{
auto wheelVehicle = WheelVehicles[i];
if (!wheelVehicle->IsActiveInHierarchy())
continue;
auto drive = WheelVehiclesCache[ii];
auto& perVehicle = WheelVehiclesResultsPerVehicle[ii];
ii++;
#if PHYSX_VEHICLE_DEBUG_TELEMETRY
LOG(Info, "Vehicle[{}] Gear={}, RPM={}", ii, wheelVehicle->GetCurrentGear(), (int32)wheelVehicle->GetEngineRotationSpeed());
#endif
// Update wheels
for (int32 j = 0; j < wheelVehicle->_wheelsData.Count(); j++)
{
auto& wheelData = wheelVehicle->_wheelsData[j];
auto& perWheel = perVehicle.wheelQueryResults[j];
#if PHYSX_VEHICLE_DEBUG_TELEMETRY
LOG(Info, "Vehicle[{}] Wheel[{}] longitudinalSlip={}, lateralSlip={}, suspSpringForce={}", ii, j, Utilities::RoundTo2DecimalPlaces(perWheel.longitudinalSlip), Utilities::RoundTo2DecimalPlaces(perWheel.lateralSlip), (int32)perWheel.suspSpringForce);
#endif
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;
state.SteerAngle = RadiansToDegrees * perWheel.steerAngle;
state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j);
state.SuspensionOffset = perWheel.suspJounce;
#if USE_EDITOR
state.SuspensionTraceStart = P2C(perWheel.suspLineStart);
state.SuspensionTraceEnd = P2C(perWheel.suspLineStart + perWheel.suspLineDir * perWheel.suspLineLength);
#endif
if (!wheelData.Collider)
continue;
auto shape = wheelData.Collider->GetPxShape();
// Update wheel collider transformation
auto localPose = shape->getLocalPose();
Transform t = wheelData.Collider->GetLocalTransform();
t.Orientation = Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation;
t.Translation = P2C(localPose.p) / wheelVehicle->GetScale() - t.Orientation * wheelData.Collider->GetCenter();
wheelData.Collider->SetLocalTransform(t);
}
}
}
#endif
{
PROFILE_CPU_NAMED("Physics.FlushActiveTransforms");
// Gather change info
PxU32 activeActorsCount;
PxActor** activeActors = PhysicsScene->getActiveActors(activeActorsCount);
if (activeActorsCount > 0)
{
// Update changed transformations
// TODO: use jobs system if amount if huge
for (uint32 i = 0; i < activeActorsCount; i++)
{
const auto pxActor = (PxRigidActor*)*activeActors++;
auto actor = dynamic_cast<IPhysicsActor*>((Actor*)pxActor->userData);
ASSERT(actor);
actor->OnActiveTransformChanged(pxActor->getGlobalPose());
}
}
}
{
PROFILE_CPU_NAMED("Physics.SendEvents");
EventsCallback.CollectResults();
EventsCallback.SendTriggerEvents();
EventsCallback.SendCollisionEvents();
EventsCallback.SendJointEvents();
}
// End
_isDuringSimulation = false;
void Physics::CollectResultsAll()
{
for (auto scene : Scenes)
scene->CollectResults();
}
bool Physics::IsDuringSimulation()
{
return _isDuringSimulation;
if (DefaultScene)
return DefaultScene->IsDuringSimulation();
return false;
}
PxMaterial* Physics::GetDefaultMaterial()
@@ -981,163 +489,60 @@ PxMaterial* Physics::GetDefaultMaterial()
return DefaultMaterial;
}
void Physics::FlushRequests()
bool Physics::GetAutoSimulation()
{
ASSERT(!IsDuringSimulation());
ASSERT(CPhysX);
if (DefaultScene)
return DefaultScene->GetAutoSimulation();
PROFILE_CPU();
FlushLocker.Lock();
// Note: this does not handle case when actor is removed and added to the scene at the same time
if (NewActors.HasItems())
{
GetScene()->addActors(NewActors.Get(), NewActors.Count());
NewActors.Clear();
}
for (int32 i = 0; i < Actions.Count(); i++)
{
const auto action = Actions[i];
switch (action.Type)
{
case ActionType::Sleep:
static_cast<PxRigidDynamic*>(action.Actor)->putToSleep();
break;
}
}
Actions.Clear();
if (DeadActors.HasItems())
{
GetScene()->removeActors(DeadActors.Get(), DeadActors.Count(), true);
for (int32 i = 0; i < DeadActors.Count(); i++)
{
DeadActors[i]->release();
}
DeadActors.Clear();
}
if (DeadColliders.HasItems())
{
for (int32 i = 0; i < DeadColliders.Count(); i++)
{
EventsCallback.OnColliderRemoved(DeadColliders[i]);
}
DeadColliders.Clear();
}
if (DeadJoints.HasItems())
{
for (int32 i = 0; i < DeadJoints.Count(); i++)
{
EventsCallback.OnJointRemoved(DeadJoints[i]);
}
DeadJoints.Clear();
}
for (int32 i = 0; i < DeadMaterials.Count(); i++)
{
auto material = DeadMaterials[i];
// Unlink ref to flax object
material->userData = nullptr;
material->release();
}
DeadMaterials.Clear();
for (int32 i = 0; i < _deadObjects.Count(); i++)
{
_deadObjects[i]->release();
}
_deadObjects.Clear();
FlushLocker.Unlock();
return false;
}
void Physics::RemoveMaterial(PxMaterial* material)
void Physics::FlushRequestsAll()
{
ASSERT(material);
FlushLocker.Lock();
DeadMaterials.Add(material);
FlushLocker.Unlock();
for (auto scene : Scenes)
scene->FlushRequests();
}
void Physics::RemoveObject(PxBase* obj)
void Physics::RemoveJointAll(Joint* joint)
{
ASSERT(obj);
FlushLocker.Lock();
_deadObjects.Add(obj);
FlushLocker.Unlock();
for (auto scene : Scenes)
scene->RemoveJoint(joint);
}
void Physics::AddActor(PxActor* actor)
PhysicsScene* Physics::FindOrCreateScene(String name)
{
ASSERT(actor);
auto scene = FindScene(name);
FlushLocker.Lock();
if (IsInMainThread())
if (scene == nullptr)
{
GetScene()->addActor(*actor);
auto cpuInfo = Platform::GetCPUInfo();
auto& settings = *PhysicsSettings::Get();
scene = new PhysicsScene(name, settings, cpuInfo);
Scenes.Add(scene);
}
else
return scene;
}
PhysicsScene* Physics::FindScene(String name)
{
for (auto scene : Scenes)
{
NewActors.Add(actor);
}
FlushLocker.Unlock();
if (scene->GetName() == name)
return scene;
}
return nullptr;
}
void Physics::AddActor(PxRigidDynamic* actor, bool putToSleep)
void Physics::BeginPlay()
{
ASSERT(actor);
FlushLocker.Lock();
if (IsInMainThread())
{
GetScene()->addActor(*actor);
if (putToSleep)
actor->putToSleep();
}
else
{
NewActors.Add(actor);
if (putToSleep)
Actions.Add({ ActionType::Sleep, actor });
}
FlushLocker.Unlock();
reset();
}
void Physics::RemoveActor(PxActor* actor)
void Physics::EndPlay()
{
ASSERT(actor);
// Unlink ref to flax object
actor->userData = nullptr;
FlushLocker.Lock();
DeadActors.Add(actor);
FlushLocker.Unlock();
}
void Physics::RemoveCollider(PhysicsColliderActor* collider)
{
ASSERT(collider);
FlushLocker.Lock();
DeadColliders.Add(collider);
FlushLocker.Unlock();
}
void Physics::RemoveJoint(Joint* joint)
{
ASSERT(joint);
FlushLocker.Lock();
DeadJoints.Add(joint);
FlushLocker.Unlock();
reset();
}

View File

@@ -2,6 +2,7 @@
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Vector2.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Quaternion.h"
@@ -9,6 +10,7 @@
#include "Types.h"
class PhysicsColliderActor;
class PhysicsScene;
class Joint;
class Collider;
class CollisionData;
@@ -87,47 +89,45 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics);
#endif
/// <summary>
/// Gets PhysX scene object
/// </summary>
static PxScene* GetScene();
/// <summary>
/// Gets PhysX characters controller manager object
/// </summary>
static PxControllerManager* GetControllerManager();
/// <summary>
/// Gets the physics tolerances scale.
/// </summary>
static PxTolerancesScale* GetTolerancesScale();
/// <summary>
/// Gets the default query filter callback used for the scene queries.
/// </summary>
static PxQueryFilterCallback* GetQueryFilterCallback();
/// <summary>
/// Gets the default query filter callback used for the character controller collisions detection.
/// </summary>
static PxQueryFilterCallback* GetCharacterQueryFilterCallback();
/// <summary>
/// Gets the default controller filter callback used for the character controller collisions detection.
/// </summary>
static PxControllerFilterCallback* GetCharacterControllerFilterCallback();
/// <summary>
/// Gets the default physical material.
/// </summary>
static PxMaterial* GetDefaultMaterial();
public:
/// <summary>
/// The default physics scene.
/// </summary>
API_FIELD(ReadOnly)
static PhysicsScene* DefaultScene;
/// <summary>
/// List with all physics scenes (readonly).
/// </summary>
API_FIELD(ReadOnly)
static Array<PhysicsScene*> Scenes;
/// <summary>
/// Finds an existing <see cref="PhysicsScene"/> or creates it if it does not exist.
/// </summary>
API_FUNCTION() static PhysicsScene* FindOrCreateScene(String name);
/// <summary>
///Finds an existing scene.
/// </summary>
API_FUNCTION() static PhysicsScene* FindScene(String name);
public:
/// <summary>
/// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it.
/// </summary>
API_FIELD() static bool AutoSimulation;
API_PROPERTY() static bool GetAutoSimulation();
/// <summary>
/// Gets the current gravity force.
@@ -519,11 +519,22 @@ public:
/// <param name="dt">The delta time (in seconds).</param>
API_FUNCTION() static void Simulate(float dt);
/// <summary>
/// Called during main engine loop to start physic simulation on all registered scenes.
/// </summary>
/// <param name="dt">The delta time (in seconds).</param>
static void SimulateAll(float dt);
/// <summary>
/// Called during main engine loop to collect physic simulation results and apply them as well as fire collision events.
/// </summary>
API_FUNCTION() static void CollectResults();
/// <summary>
/// Called during main engine loop to collect physic simulation results on all registered scenes and apply them as well as fire collision events.
/// </summary>
static void CollectResultsAll();
/// <summary>
/// Checks if physical simulation is running
/// </summary>
@@ -535,7 +546,7 @@ public:
/// <summary>
/// Flushes the async requests to add/remove actors, remove materials, etc..
/// </summary>
static void FlushRequests();
static void FlushRequestsAll();
/// <summary>
/// Removes the material (using safe async request).
@@ -578,5 +589,9 @@ public:
/// Marks that joint has been removed (all collision events should be cleared to prevent leaks of using removed object).
/// </summary>
/// <param name="joint">The joint.</param>
static void RemoveJoint(Joint* joint);
static void RemoveJointAll(Joint* joint);
public:
static void BeginPlay();
static void EndPlay();
};

View File

@@ -0,0 +1,657 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
#include "Physics.h"
#include "PhysicsScene.h"
#include "Utilities.h"
#include "CollisionData.h"
#include "Actors/PhysicsColliderActor.h"
#include <ThirdParty/PhysX/PxPhysicsAPI.h>
#include <ThirdParty/PhysX/PxQueryFiltering.h>
// Temporary result buffer size
#define HIT_BUFFER_SIZE 128
template<typename HitType>
class DynamicHitBuffer : public PxHitCallback<HitType>
{
private:
uint32 _count;
HitType _buffer[HIT_BUFFER_SIZE];
public:
DynamicHitBuffer()
: PxHitCallback<HitType>(_buffer, HIT_BUFFER_SIZE)
, _count(0)
{
}
public:
// Computes the number of any hits in this result, blocking or touching.
PX_INLINE PxU32 getNbAnyHits() const
{
return getNbTouches();
}
// Convenience iterator used to access any hits in this result, blocking or touching.
PX_INLINE const HitType& getAnyHit(const PxU32 index) const
{
PX_ASSERT(index < getNbTouches() + PxU32(this->hasBlock));
return index < getNbTouches() ? getTouches()[index] : this->block;
}
PX_INLINE PxU32 getNbTouches() const
{
return _count;
}
PX_INLINE const HitType* getTouches() const
{
return _buffer;
}
PX_INLINE const HitType& getTouch(const PxU32 index) const
{
PX_ASSERT(index < getNbTouches());
return _buffer[index];
}
PX_INLINE PxU32 getMaxNbTouches() const
{
return HIT_BUFFER_SIZE;
}
protected:
PxAgain processTouches(const HitType* buffer, PxU32 nbHits) override
{
nbHits = Math::Min(nbHits, HIT_BUFFER_SIZE - _count);
for (PxU32 i = 0; i < nbHits; i++)
{
_buffer[_count + i] = buffer[i];
}
_count += nbHits;
return true;
}
void finalizeQuery() override
{
if (this->hasBlock)
{
// Blocking hits go to hits
processTouches(&this->block, 1);
}
}
};
#define SCENE_QUERY_SETUP(blockSingle) if (GetScene() == nullptr) return false; \
const PxHitFlags hitFlags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eUV; \
PxQueryFilterData filterData = PxQueryFilterData(); \
filterData.flags |= PxQueryFlag::ePREFILTER; \
filterData.data.word0 = layerMask; \
filterData.data.word1 = blockSingle ? 1 : 0; \
filterData.data.word2 = hitTriggers ? 1 : 0
#define SCENE_QUERY_SETUP_SWEEP_1() SCENE_QUERY_SETUP(true); \
PxSweepBufferN<1> buffer
#define SCENE_QUERY_SETUP_SWEEP() SCENE_QUERY_SETUP(false); \
DynamicHitBuffer<PxSweepHit> buffer
#define SCENE_QUERY_SETUP_OVERLAP_1() SCENE_QUERY_SETUP(false); \
PxOverlapBufferN<1> buffer
#define SCENE_QUERY_SETUP_OVERLAP() SCENE_QUERY_SETUP(false); \
DynamicHitBuffer<PxOverlapHit> buffer
#define SCENE_QUERY_COLLECT_SINGLE() const auto& hit = buffer.getAnyHit(0); \
hitInfo.Gather(hit)
#define SCENE_QUERY_COLLECT_ALL() results.Clear(); \
results.Resize(buffer.getNbAnyHits(), false); \
for (int32 i = 0; i < results.Count(); i++) \
{ \
const auto& hit = buffer.getAnyHit(i); \
results[i].Gather(hit); \
}
#define SCENE_QUERY_COLLECT_OVERLAP() results.Clear(); \
results.Resize(buffer.getNbTouches(), false); \
for (int32 i = 0; i < results.Count(); i++) \
{ \
auto& hitInfo = results[i]; \
const auto& hit = buffer.getTouch(i); \
hitInfo = hit.shape ? static_cast<::PhysicsColliderActor*>(hit.shape->userData) : nullptr; \
}
#define SCENE_QUERY_COLLECT_OVERLAP_COLLIDER() results.Clear(); \
results.Resize(buffer.getNbTouches(), false); \
for (int32 i = 0; i < results.Count(); i++) \
{ \
auto& hitInfo = results[i]; \
const auto& hit = buffer.getTouch(i); \
hitInfo = hit.shape ? static_cast<::Collider*>(hit.shape->userData) : nullptr; \
}
void RayCastHit::Gather(const PxRaycastHit& hit)
{
Point = P2C(hit.position);
Normal = P2C(hit.normal);
Distance = hit.distance;
Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr;
FaceIndex = hit.faceIndex;
UV.X = hit.u;
UV.Y = hit.v;
}
void RayCastHit::Gather(const PxSweepHit& hit)
{
Point = P2C(hit.position);
Normal = P2C(hit.normal);
Distance = hit.distance;
Collider = hit.shape ? static_cast<PhysicsColliderActor*>(hit.shape->userData) : nullptr;
FaceIndex = hit.faceIndex;
UV = Vector2::Zero;
}
class QueryFilter : public PxQueryFilterCallback
{
public:
PxQueryHitType::Enum preFilter(const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags) override
{
// Early out to avoid crashing
if (!shape)
return PxQueryHitType::eNONE;
// Check mask
const PxFilterData shapeFilter = shape->getQueryFilterData();
if ((filterData.word0 & shapeFilter.word0) == 0)
{
return PxQueryHitType::eNONE;
}
// Check if skip triggers
const bool hitTriggers = filterData.word2 != 0;
if (!hitTriggers && shape->getFlags() & PxShapeFlag::eTRIGGER_SHAPE)
return PxQueryHitType::eNONE;
const bool blockSingle = filterData.word1 != 0;
return blockSingle ? PxQueryHitType::eBLOCK : PxQueryHitType::eTOUCH;
}
PxQueryHitType::Enum postFilter(const PxFilterData& filterData, const PxQueryHit& hit) override
{
// Not used
return PxQueryHitType::eNONE;
}
};
class CharacterQueryFilter : public PxQueryFilterCallback
{
public:
PxQueryHitType::Enum preFilter(const PxFilterData& filterData, const PxShape* shape, const PxRigidActor* actor, PxHitFlags& queryFlags) override
{
// Early out to avoid crashing
if (!shape)
return PxQueryHitType::eNONE;
// Let triggers through
if (PxFilterObjectIsTrigger(shape->getFlags()))
return PxQueryHitType::eNONE;
// Trigger the contact callback for pairs (A,B) where the filtermask of A contains the ID of B and vice versa
const PxFilterData shapeFilter = shape->getQueryFilterData();
if (filterData.word0 & shapeFilter.word1)
return PxQueryHitType::eBLOCK;
return PxQueryHitType::eNONE;
}
PxQueryHitType::Enum postFilter(const PxFilterData& filterData, const PxQueryHit& hit) override
{
// Not used
return PxQueryHitType::eNONE;
}
};
PxQueryFilterCallback* PhysicsScene::GetQueryFilterCallback()
{
static QueryFilter Filter;
return &Filter;
}
PxQueryFilterCallback* PhysicsScene::GetCharacterQueryFilterCallback()
{
static CharacterQueryFilter Filter;
return &Filter;
}
bool PhysicsScene::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer;
// Perform raycast test
return GetScene()->raycast(C2P(origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::RayCast(const Vector3& origin, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP(true);
PxRaycastBuffer buffer;
// Perform raycast test
if (!GetScene()->raycast(C2P(origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
}
bool PhysicsScene::RayCastAll(const Vector3& origin, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP(false);
DynamicHitBuffer<PxRaycastHit> buffer;
// Perform raycast test
if (!GetScene()->raycast(C2P(origin), C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
}
bool PhysicsScene::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
}
bool PhysicsScene::BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
}
bool PhysicsScene::SphereCast(const Vector3& center, const float radius, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::SphereCast(const Vector3& center, const float radius, const Vector3& direction, RayCastHit& hitInfo, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
}
bool PhysicsScene::SphereCastAll(const Vector3& center, const float radius, const Vector3& direction, Array<RayCastHit>& results, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
}
bool PhysicsScene::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::CapsuleCast(const Vector3& center, const float radius, const float height, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
}
bool PhysicsScene::CapsuleCastAll(const Vector3& center, const float radius, const float height, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
}
bool PhysicsScene::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform sweep test
return GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, RayCastHit& hitInfo, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_SWEEP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_SINGLE();
return true;
}
bool PhysicsScene::ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, Array<RayCastHit>& results, const Quaternion& rotation, const float maxDistance, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_SWEEP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform sweep test
if (!GetScene()->sweep(geometry, pose, C2P(direction), maxDistance, buffer, hitFlags, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_ALL();
return true;
}
bool PhysicsScene::CheckBox(const Vector3& center, const Vector3& halfExtents, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::CheckSphere(const Vector3& center, const float radius, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::CheckCapsule(const Vector3& center, const float radius, const float height, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_OVERLAP_1();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform overlap test
return GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback());
}
bool PhysicsScene::OverlapBox(const Vector3& center, const Vector3& halfExtents, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
}
bool PhysicsScene::OverlapSphere(const Vector3& center, const float radius, Array<Collider*>& results, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
}
bool PhysicsScene::OverlapCapsule(const Vector3& center, const float radius, const float height, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
}
bool PhysicsScene::OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array<Collider*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP_COLLIDER();
return true;
}
bool PhysicsScene::OverlapBox(const Vector3& center, const Vector3& halfExtents, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxBoxGeometry geometry(C2P(halfExtents));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
}
bool PhysicsScene::OverlapSphere(const Vector3& center, const float radius, Array<PhysicsColliderActor*>& results, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center));
const PxSphereGeometry geometry(radius);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
}
bool PhysicsScene::OverlapCapsule(const Vector3& center, const float radius, const float height, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxCapsuleGeometry geometry(radius, height * 0.5f);
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
}
bool PhysicsScene::OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, Array<PhysicsColliderActor*>& results, const Quaternion& rotation, uint32 layerMask, bool hitTriggers)
{
CHECK_RETURN(convexMesh && convexMesh->GetOptions().Type == CollisionDataType::ConvexMesh, false)
// Prepare data
SCENE_QUERY_SETUP_OVERLAP();
const PxTransform pose(C2P(center), C2P(rotation));
const PxConvexMeshGeometry geometry(convexMesh->GetConvex(), PxMeshScale(C2P(scale)));
// Perform overlap test
if (!GetScene()->overlap(geometry, pose, buffer, filterData, GetQueryFilterCallback()))
return false;
// Collect results
SCENE_QUERY_COLLECT_OVERLAP();
return true;
}
#if WITH_VEHICLE
void PhysicsScene::AddWheeledVehicle(WheeledVehicle* vehicle)
{
mWheelVehicles.Add(vehicle);
}
void PhysicsScene::RemoveWheeledVehicle(WheeledVehicle* vehicle)
{
mWheelVehicles.Remove(vehicle);
}
#endif

View File

@@ -0,0 +1,770 @@
#include "Physics.h"
#include "PhysicsScene.h"
#include "PhysicsSettings.h"
#include "PhysicsStepper.h"
#include "Utilities.h"
#include "Actors/IPhysicsActor.h"
#include "Engine/Core/Log.h"
#include "Engine/Platform/CPUInfo.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Threading/Threading.h"
#include <ThirdParty/PhysX/PxPhysicsAPI.h>
#if WITH_VEHICLE
#include "Actors/WheeledVehicle.h"
#include <ThirdParty/PhysX/vehicle/PxVehicleUpdate.h>
#endif
// Temporary memory size used by the PhysX during the simulation. Must be multiply of 4kB and 16bit aligned.
#define SCRATCH_BLOCK_SIZE (1024 * 128)
PxFilterFlags FilterShader(
PxFilterObjectAttributes attributes0, PxFilterData filterData0,
PxFilterObjectAttributes attributes1, PxFilterData filterData1,
PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
{
// Let triggers through
if (PxFilterObjectIsTrigger(attributes0) || PxFilterObjectIsTrigger(attributes1))
{
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_LOST;
pairFlags |= PxPairFlag::eDETECT_DISCRETE_CONTACT;
return PxFilterFlag::eDEFAULT;
}
// Send events for the kinematic actors but don't solve the contact
if (PxFilterObjectIsKinematic(attributes0) && PxFilterObjectIsKinematic(attributes1))
{
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_PERSISTS;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_LOST;
pairFlags |= PxPairFlag::eDETECT_DISCRETE_CONTACT;
return PxFilterFlag::eSUPPRESS;
}
// Trigger the contact callback 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))
{
pairFlags |= PxPairFlag::eSOLVE_CONTACT;
pairFlags |= PxPairFlag::eDETECT_DISCRETE_CONTACT;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_FOUND;
pairFlags |= PxPairFlag::eNOTIFY_TOUCH_PERSISTS;
pairFlags |= PxPairFlag::ePOST_SOLVER_VELOCITY;
pairFlags |= PxPairFlag::eNOTIFY_CONTACT_POINTS;
return PxFilterFlag::eDEFAULT;
}
// Ignore pair (no collisions nor events)
return PxFilterFlag::eKILL;
}
enum class ActionType
{
Sleep,
};
struct ActionData
{
ActionType Type;
PxActor* Actor;
};
#if WITH_VEHICLE
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
PhysicsScene::PhysicsScene(String name, PhysicsSettings settings, CPUInfo cpuInfo)
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
{
#define CHECK_INIT(value, msg) if(!value) { LOG(Error, msg); return; }
mName = name;
// Create scene description
PxSceneDesc sceneDesc(CPhysX->getTolerancesScale());
sceneDesc.gravity = C2P(settings.DefaultGravity);
sceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVE_ACTORS;
if (!settings.DisableCCD)
sceneDesc.flags |= PxSceneFlag::eENABLE_CCD;
if (settings.EnableAdaptiveForce)
sceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE;
sceneDesc.simulationEventCallback = &mEventsCallback;
sceneDesc.filterShader = FilterShader;
sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity;
if (sceneDesc.cpuDispatcher == nullptr)
{
mCpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp<uint32>(cpuInfo.ProcessorCoreCount - 1, 1, 4));
CHECK_INIT(mCpuDispatcher, "PxDefaultCpuDispatcherCreate failed!");
sceneDesc.cpuDispatcher = mCpuDispatcher;
}
if (sceneDesc.filterShader == nullptr)
{
sceneDesc.filterShader = mPhysXDefaultFilterShader;
}
// Create scene
mScene = CPhysX->createScene(sceneDesc);
CHECK_INIT(mScene, "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
mControllerManager = PxCreateControllerManager(*mScene);
}
PhysicsScene::~PhysicsScene()
{
#if WITH_VEHICLE
RELEASE_PHYSX(mWheelRaycastBatchQuery);
RELEASE_PHYSX(mWheelTireFrictions);
mWheelQueryResults.Resize(0);
mWheelHitResults.Resize(0);
mWheelVehiclesResultsPerWheel.Resize(0);
mWheelVehiclesResultsPerVehicle.Resize(0);
#endif
RELEASE_PHYSX(mControllerManager);
SAFE_DELETE(mCpuDispatcher);
SAFE_DELETE(mStepper);
Allocator::Free(mScratchMemory);
mScratchMemory = nullptr;
mScene->release();
}
String PhysicsScene::GetName() const
{
return mName;
}
PxScene* PhysicsScene::GetScene()
{
return mScene;
}
bool PhysicsScene::GetAutoSimulation()
{
return mAutoSimulation;
}
void PhysicsScene::SetAutoSimulation(bool value)
{
mAutoSimulation = value;
}
void PhysicsScene::SetGravity(const Vector3& value)
{
if(mScene)
{
mScene->setGravity(C2P(value));
}
}
Vector3 PhysicsScene::GetGravity()
{
return mScene ? P2C(mScene->getGravity()) : Vector3::Zero;
}
bool PhysicsScene::GetEnableCCD()
{
return mScene ? (mScene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings::Get()->DisableCCD;
}
void PhysicsScene::SetEnableCCD(const bool value)
{
if (mScene)
mScene->setFlag(PxSceneFlag::eENABLE_CCD, value);
}
float PhysicsScene::GetBounceThresholdVelocity()
{
return mScene ? mScene->getBounceThresholdVelocity() : PhysicsSettings::Get()->BounceThresholdVelocity;
}
void PhysicsScene::SetBounceThresholdVelocity(const float value)
{
if (mScene)
mScene->setBounceThresholdVelocity(value);
}
void PhysicsScene::Simulate(float dt)
{
ASSERT(IsInMainThread() && !mIsDuringSimulation);
ASSERT(CPhysX);
const auto& settings = *PhysicsSettings::Get();
// Flush the old/new objects and the other requests before the simulation
FlushRequests();
// Clamp delta
dt = Math::Clamp(dt, 0.0f, settings.MaxDeltaTime);
// Prepare util objects
if (mScratchMemory == nullptr)
{
mScratchMemory = Allocator::Allocate(SCRATCH_BLOCK_SIZE, 16);
}
if (mStepper == nullptr)
{
mStepper = New<FixedStepper>();
}
if (settings.EnableSubstepping)
{
// Use substeps
mStepper->Setup(settings.SubstepDeltaTime, settings.MaxSubsteps);
}
else
{
// Use single step
mStepper->Setup(dt);
}
// Start simulation (may not be fired due to too small delta time)
mIsDuringSimulation = true;
if (mStepper->advance(mScene, dt, mScratchMemory, SCRATCH_BLOCK_SIZE) == false)
return;
mEventsCallback.Clear();
mLastDeltaTime = dt;
// TODO: move this call after rendering done
mStepper->renderDone();
}
bool PhysicsScene::IsDuringSimulation()
{
return mIsDuringSimulation;
}
void PhysicsScene::CollectResults()
{
if (!mIsDuringSimulation)
return;
ASSERT(IsInMainThread());
ASSERT(CPhysX && mStepper);
{
PROFILE_CPU_NAMED("Physics.Fetch");
// Gather results (with waiting for the end)
mStepper->wait(mScene);
}
#if WITH_VEHICLE
if (mWheelVehicles.HasItems())
{
PROFILE_CPU_NAMED("Physics.Vehicles");
// Update vehicles steering
mWheelVehiclesCache.Clear();
mWheelVehiclesCache.EnsureCapacity(mWheelVehicles.Count());
int32 wheelsCount = 0;
for (auto wheelVehicle : mWheelVehicles)
{
if (!wheelVehicle->IsActiveInHierarchy())
continue;
auto drive = (PxVehicleWheels*)wheelVehicle->_drive;
ASSERT(drive);
mWheelVehiclesCache.Add(drive);
wheelsCount += drive->mWheelsSimData.getNbWheels();
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);
}
// @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
}
};
PxVehicleKeySmoothingData keySmoothing =
{
{
3.0f, // rise rate eANALOG_INPUT_ACCEL
3.0f, // rise rate eANALOG_INPUT_BRAKE
10.0f, // rise rate eANALOG_INPUT_HANDBRAKE
2.5f, // rise rate eANALOG_INPUT_STEER_LEFT
2.5f, // rise rate eANALOG_INPUT_STEER_RIGHT
},
{
5.0f, // fall rate eANALOG_INPUT__ACCEL
5.0f, // fall rate eANALOG_INPUT__BRAKE
10.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
if (wheelVehicle->UseAnalogSteering)
{
switch (wheelVehicle->_driveTypeCurrent)
{
case WheeledVehicle::DriveTypes::Drive4W:
{
PxVehicleDrive4WRawInputData rawInputData;
rawInputData.setAnalogAccel(throttle);
rawInputData.setAnalogBrake(brake);
rawInputData.setAnalogSteer(wheelVehicle->_steering);
rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake);
PxVehicleDrive4WSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, mLastDeltaTime, false, *(PxVehicleDrive4W*)drive);
break;
}
case WheeledVehicle::DriveTypes::DriveNW:
{
PxVehicleDriveNWRawInputData rawInputData;
rawInputData.setAnalogAccel(throttle);
rawInputData.setAnalogBrake(brake);
rawInputData.setAnalogSteer(wheelVehicle->_steering);
rawInputData.setAnalogHandbrake(wheelVehicle->_handBrake);
PxVehicleDriveNWSmoothAnalogRawInputsAndSetAnalogInputs(padSmoothing, steerVsForwardSpeed, rawInputData, mLastDeltaTime, false, *(PxVehicleDriveNW*)drive);
break;
}
}
}
else
{
const float deadZone = 0.1f;
switch (wheelVehicle->_driveTypeCurrent)
{
case WheeledVehicle::DriveTypes::Drive4W:
{
PxVehicleDrive4WRawInputData rawInputData;
rawInputData.setDigitalAccel(throttle > deadZone);
rawInputData.setDigitalBrake(brake > deadZone);
rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone);
rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone);
rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone);
PxVehicleDrive4WSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, mLastDeltaTime, false, *(PxVehicleDrive4W*)drive);
break;
}
case WheeledVehicle::DriveTypes::DriveNW:
{
PxVehicleDriveNWRawInputData rawInputData;
rawInputData.setDigitalAccel(throttle > deadZone);
rawInputData.setDigitalBrake(brake > deadZone);
rawInputData.setDigitalSteerLeft(wheelVehicle->_steering < -deadZone);
rawInputData.setDigitalSteerRight(wheelVehicle->_steering > deadZone);
rawInputData.setDigitalHandbrake(wheelVehicle->_handBrake > deadZone);
PxVehicleDriveNWSmoothDigitalRawInputsAndSetAnalogInputs(keySmoothing, steerVsForwardSpeed, rawInputData, mLastDeltaTime, false, *(PxVehicleDriveNW*)drive);
break;
}
}
}
}
// Update batches queries cache
if (wheelsCount > mWheelQueryResults.Count())
{
if (mWheelRaycastBatchQuery)
mWheelRaycastBatchQuery->release();
mWheelQueryResults.Resize(wheelsCount, false);
mWheelHitResults.Resize(wheelsCount, false);
PxBatchQueryDesc desc(wheelsCount, 0, 0);
desc.queryMemory.userRaycastResultBuffer = mWheelQueryResults.Get();
desc.queryMemory.userRaycastTouchBuffer = mWheelHitResults.Get();
desc.queryMemory.raycastTouchBufferSize = wheelsCount;
desc.preFilterShader = WheelRaycastPreFilter;
mWheelRaycastBatchQuery = mScene->createBatchQuery(desc);
}
// TODO: expose vehicle tires configuration
if (!mWheelTireFrictions)
{
PxVehicleDrivableSurfaceType surfaceTypes[1];
surfaceTypes[0].mType = 0;
const PxMaterial* surfaceMaterials[1];
surfaceMaterials[0] = Physics::GetDefaultMaterial();
mWheelTireFrictions = PxVehicleDrivableSurfaceToTireFrictionPairs::allocate(1, 1);
mWheelTireFrictions->setup(1, 1, surfaceMaterials, surfaceTypes);
mWheelTireFrictions->setTypePairFriction(0, 0, 5.0f);
}
// Setup cache for wheel states
mWheelVehiclesResultsPerVehicle.Resize(mWheelVehiclesCache.Count(), false);
mWheelVehiclesResultsPerWheel.Resize(wheelsCount, false);
wheelsCount = 0;
for (int32 i = 0, ii = 0; i < mWheelVehicles.Count(); i++)
{
auto wheelVehicle = mWheelVehicles[i];
if (!wheelVehicle->IsActiveInHierarchy())
continue;
auto drive = (PxVehicleWheels*)mWheelVehicles[ii]->_drive;
auto& perVehicle = mWheelVehiclesResultsPerVehicle[ii];
ii++;
perVehicle.nbWheelQueryResults = drive->mWheelsSimData.getNbWheels();
perVehicle.wheelQueryResults = mWheelVehiclesResultsPerWheel.Get() + wheelsCount;
wheelsCount += perVehicle.nbWheelQueryResults;
}
// Update vehicles
if (mWheelVehiclesCache.Count() != 0)
{
PxVehicleSuspensionRaycasts(mWheelRaycastBatchQuery, mWheelVehiclesCache.Count(), mWheelVehiclesCache.Get(), mWheelQueryResults.Count(), mWheelQueryResults.Get());
PxVehicleUpdates(mLastDeltaTime, mScene->getGravity(), *mWheelTireFrictions, mWheelVehiclesCache.Count(), mWheelVehiclesCache.Get(), mWheelVehiclesResultsPerVehicle.Get());
}
// Synchronize state
for (int32 i = 0, ii = 0; i < mWheelVehicles.Count(); i++)
{
auto wheelVehicle = mWheelVehicles[i];
if (!wheelVehicle->IsActiveInHierarchy())
continue;
auto drive = mWheelVehiclesCache[ii];
auto& perVehicle = mWheelVehiclesResultsPerVehicle[ii];
ii++;
#if PHYSX_VEHICLE_DEBUG_TELEMETRY
LOG(Info, "Vehicle[{}] Gear={}, RPM={}", ii, wheelVehicle->GetCurrentGear(), (int32)wheelVehicle->GetEngineRotationSpeed());
#endif
// Update wheels
for (int32 j = 0; j < wheelVehicle->_wheelsData.Count(); j++)
{
auto& wheelData = wheelVehicle->_wheelsData[j];
auto& perWheel = perVehicle.wheelQueryResults[j];
#if PHYSX_VEHICLE_DEBUG_TELEMETRY
LOG(Info, "Vehicle[{}] Wheel[{}] longitudinalSlip={}, lateralSlip={}, suspSpringForce={}", ii, j, Utilities::RoundTo2DecimalPlaces(perWheel.longitudinalSlip), Utilities::RoundTo2DecimalPlaces(perWheel.lateralSlip), (int32)perWheel.suspSpringForce);
#endif
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;
state.SteerAngle = RadiansToDegrees * perWheel.steerAngle;
state.RotationAngle = -RadiansToDegrees * drive->mWheelsDynData.getWheelRotationAngle(j);
state.SuspensionOffset = perWheel.suspJounce;
#if USE_EDITOR
state.SuspensionTraceStart = P2C(perWheel.suspLineStart);
state.SuspensionTraceEnd = P2C(perWheel.suspLineStart + perWheel.suspLineDir * perWheel.suspLineLength);
#endif
if (!wheelData.Collider)
continue;
auto shape = wheelData.Collider->GetPxShape();
// Update wheel collider transformation
auto localPose = shape->getLocalPose();
Transform t = wheelData.Collider->GetLocalTransform();
t.Orientation = Quaternion::Euler(0, state.SteerAngle, state.RotationAngle) * wheelData.LocalOrientation;
t.Translation = P2C(localPose.p) / wheelVehicle->GetScale() - t.Orientation * wheelData.Collider->GetCenter();
wheelData.Collider->SetLocalTransform(t);
}
}
}
#endif
{
PROFILE_CPU_NAMED("Physics.FlushActiveTransforms");
// Gather change info
PxU32 activeActorsCount;
PxActor** activeActors = mScene->getActiveActors(activeActorsCount);
if (activeActorsCount > 0)
{
// Update changed transformations
// TODO: use jobs system if amount if huge
for (uint32 i = 0; i < activeActorsCount; i++)
{
const auto pxActor = (PxRigidActor*)*activeActors++;
auto actor = dynamic_cast<IPhysicsActor*>((Actor*)pxActor->userData);
ASSERT(actor);
actor->OnActiveTransformChanged(pxActor->getGlobalPose());
}
}
}
{
PROFILE_CPU_NAMED("Physics.SendEvents");
mEventsCallback.CollectResults();
mEventsCallback.SendTriggerEvents();
mEventsCallback.SendCollisionEvents();
mEventsCallback.SendJointEvents();
}
// End
mIsDuringSimulation = false;
}
void PhysicsScene::FlushRequests()
{
ASSERT(!IsDuringSimulation());
ASSERT(CPhysX);
PROFILE_CPU();
mFlushLocker.Lock();
// Note: this does not handle case when actor is removed and added to the scene at the same time
if (mNewActors.HasItems())
{
GetScene()->addActors(mNewActors.Get(), mNewActors.Count());
mNewActors.Clear();
}
for (int32 i = 0; i < mActions.Count(); i++)
{
const auto action = mActions[i];
switch (action.Type)
{
case ActionType::Sleep:
static_cast<PxRigidDynamic*>(action.Actor)->putToSleep();
break;
}
}
mActions.Clear();
if (mDeadActors.HasItems())
{
GetScene()->removeActors(mDeadActors.Get(), mDeadActors.Count(), true);
for (int32 i = 0; i < mDeadActors.Count(); i++)
{
mDeadActors[i]->release();
}
mDeadActors.Clear();
}
if (mDeadColliders.HasItems())
{
for (int32 i = 0; i < mDeadColliders.Count(); i++)
{
mEventsCallback.OnColliderRemoved(mDeadColliders[i]);
}
mDeadColliders.Clear();
}
if (mDeadJoints.HasItems())
{
for (int32 i = 0; i < mDeadJoints.Count(); i++)
{
mEventsCallback.OnJointRemoved(mDeadJoints[i]);
}
mDeadJoints.Clear();
}
for (int32 i = 0; i < mDeadMaterials.Count(); i++)
{
auto material = mDeadMaterials[i];
// Unlink ref to flax object
material->userData = nullptr;
material->release();
}
mDeadMaterials.Clear();
for (int32 i = 0; i < mDeadObjects.Count(); i++)
{
mDeadObjects[i]->release();
}
mDeadObjects.Clear();
mFlushLocker.Unlock();
}
void PhysicsScene::RemoveMaterial(PxMaterial* material)
{
ASSERT(material);
mFlushLocker.Lock();
mDeadMaterials.Add(material);
mFlushLocker.Unlock();
}
void PhysicsScene::RemoveObject(PxBase* obj)
{
ASSERT(obj);
mFlushLocker.Lock();
mDeadObjects.Add(obj);
mFlushLocker.Unlock();
}
void PhysicsScene::AddActor(PxActor* actor)
{
ASSERT(actor);
mFlushLocker.Lock();
if (IsInMainThread())
{
GetScene()->addActor(*actor);
}
else
{
mNewActors.Add(actor);
}
mFlushLocker.Unlock();
}
void PhysicsScene::AddActor(PxRigidDynamic* actor, bool putToSleep)
{
ASSERT(actor);
mFlushLocker.Lock();
if (IsInMainThread())
{
GetScene()->addActor(*actor);
if (putToSleep)
actor->putToSleep();
}
else
{
mNewActors.Add(actor);
if (putToSleep)
mActions.Add({ ActionType::Sleep, actor });
}
mFlushLocker.Unlock();
}
void PhysicsScene::UnlinkActor(PxActor* actor)
{
ASSERT(IsInMainThread())
ASSERT(actor);
GetScene()->removeActor(*actor);
}
void PhysicsScene::RemoveActor(PxActor* actor)
{
ASSERT(actor);
// Unlink ref to flax object
actor->userData = nullptr;
mFlushLocker.Lock();
mDeadActors.Add(actor);
mFlushLocker.Unlock();
}
void PhysicsScene::RemoveCollider(PhysicsColliderActor* collider)
{
ASSERT(collider);
mFlushLocker.Lock();
mDeadColliders.Add(collider);
mFlushLocker.Unlock();
}
void PhysicsScene::RemoveJoint(Joint* joint)
{
ASSERT(joint);
mFlushLocker.Lock();
mDeadJoints.Add(joint);
mFlushLocker.Unlock();
}
PxControllerManager* PhysicsScene::GetControllerManager()
{
return mControllerManager;
}

View File

@@ -0,0 +1,560 @@
#pragma once
#include "SimulationEventCallback.h"
#include "Engine/Scripting/ScriptingObject.h"
#include "Engine/Scripting/ScriptingType.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Core/Math/Vector3.h"
#include "Engine/Core/Math/Quaternion.h"
#include "Types.h"
#include <ThirdParty/PhysX/PxPhysicsAPI.h>
#if WITH_VEHICLE
#include "vehicle/PxVehicleUpdate.h"
#include "vehicle/PxVehicleWheels.h"
class WheeledVehicle;
#endif
struct ActionData;
class FixedStepper;
class PhysicsSettings;
class PhysicsColliderActor;
class Joint;
class Collider;
class CollisionData;
/// <summary>
/// Isolated physics scene.
/// </summary>
API_CLASS(NoSpawn) class FLAXENGINE_API PhysicsScene : public PersistentScriptingObject
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(PhysicsScene);
explicit PhysicsScene(String name, PhysicsSettings settings, CPUInfo cpuInfo);
~PhysicsScene();
/// <summary>
/// Gets the name of the scene.
/// </summary>
API_PROPERTY() String GetName() const;
public:
String ToString() const override
{
return GetName();
}
PxScene* GetScene();
/// <summary>
/// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it.
/// </summary>
API_PROPERTY() bool GetAutoSimulation();
API_PROPERTY() void SetAutoSimulation(bool value);
/// <summary>
/// Sets the current gravity force.
/// </summary>
API_PROPERTY() void SetGravity(const Vector3& value);
/// <summary>
/// Gets the current gravity force.
/// </summary>
API_PROPERTY() Vector3 GetGravity();
/// <summary>
/// Gets the CCD feature enable flag.
/// </summary>
API_PROPERTY() bool GetEnableCCD();
/// <summary>
/// Sets the CCD feature enable flag.
/// </summary>
API_PROPERTY() void SetEnableCCD(const bool value);
/// <summary>
/// Gets the minimum relative velocity required for an object to bounce.
/// </summary>
API_PROPERTY() float GetBounceThresholdVelocity();
/// <summary>
/// Sets the minimum relative velocity required for an object to bounce.
/// </summary>
API_PROPERTY() void SetBounceThresholdVelocity(const float value);
/// <summary>
/// Called during main engine loop to start physic simulation. Use CollectResults after.
/// </summary>
/// <param name="dt">The delta time (in seconds).</param>
API_FUNCTION() void Simulate(float dt);
/// <summary>
/// Checks if physical simulation is running
/// </summary>
/// <returns>True if simulation is active, otherwise false</returns>
API_PROPERTY() bool IsDuringSimulation();
/// <summary>
/// Called to collect physic simulation results and apply them as well as fire collision events.
/// </summary>
API_FUNCTION() void CollectResults();
/// <summary>
/// Flushes the async requests to add/remove actors, remove materials, etc..
/// </summary>
void FlushRequests();
/// <summary>
/// Removes the material (using safe async request).
/// </summary>
/// <param name="material">The material.</param>
void RemoveMaterial(PxMaterial* material);
/// <summary>
/// Removes the physX object via calling release() on it (using safe async request).
/// </summary>
/// <param name="obj">The obj.</param>
void RemoveObject(PxBase* obj);
/// <summary>
/// Adds the actor (using safe async request).
/// </summary>
/// <param name="actor">The actor.</param>
void AddActor(PxActor* actor);
/// <summary>
/// Adds the actor (using safe async request).
/// </summary>
/// <param name="actor">The actor.</param>
/// <param name="putToSleep">If set to <c>true</c> will put actor to sleep after spawning.</param>
void AddActor(PxRigidDynamic* actor, bool putToSleep = false);
/// <summary>
/// Removes the actor (using safe async request).
/// </summary>
/// <param name="actor">The actor.</param>
void RemoveActor(PxActor* actor);
/// <summary>
/// Removes the actor from the underlying physics scene without destroying it.
/// </summary>
void UnlinkActor(PxActor* actor);
/// <summary>
/// Marks that collider has been removed (all collision events should be cleared to prevent leaks of using removed object).
/// </summary>
/// <param name="collider">The collider.</param>
void RemoveCollider(PhysicsColliderActor* collider);
/// <summary>
/// Marks that joint has been removed (all collision events should be cleared to prevent leaks of using removed object).
/// </summary>
/// <param name="joint">The joint.</param>
void RemoveJoint(Joint* joint);
/// <summary>
/// Gets PhysX characters controller manager object
/// </summary>
PxControllerManager* GetControllerManager();
public:
/// <summary>
/// Gets the default query filter callback used for the scene queries.
/// </summary>
PxQueryFilterCallback* GetQueryFilterCallback();
/// <summary>
/// Gets the default query filter callback used for the character controller collisions detection.
/// </summary>
PxQueryFilterCallback* GetCharacterQueryFilterCallback();
/// <summary>
/// Performs a raycast against objects in the scene.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a raycast against objects in the scene, returns results in a RayCastHit structure.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a raycast against objects in the scene, returns results in a RayCastHit structure.
/// </summary>
/// <param name="origin">The origin of the ray.</param>
/// <param name="direction">The normalized direction of the ray.</param>
/// <param name="results">The result hits. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if ray hits an matching object, otherwise false.</returns>
API_FUNCTION() bool RayCastAll(const Vector3& origin, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a box geometry.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
/// <param name="direction">The normalized direction in which cast a box.</param>
/// <param name="rotation">The box rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if box hits an matching object, otherwise false.</returns>
API_FUNCTION() bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a box geometry.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
/// <param name="direction">The normalized direction in which cast a box.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="rotation">The box rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if box hits an matching object, otherwise false.</returns>
API_FUNCTION() bool BoxCast(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a box geometry.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
/// <param name="direction">The normalized direction in which cast a box.</param>
/// <param name="results">The result hits. Valid only when method returns true.</param>
/// <param name="rotation">The box rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if box hits an matching object, otherwise false.</returns>
API_FUNCTION() bool BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a sphere geometry.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <param name="direction">The normalized direction in which cast a sphere.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
API_FUNCTION() bool SphereCast(const Vector3& center, float radius, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a sphere geometry.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <param name="direction">The normalized direction in which cast a sphere.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
API_FUNCTION() bool SphereCast(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a sphere geometry.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <param name="direction">The normalized direction in which cast a sphere.</param>
/// <param name="results">The result hits. Valid only when method returns true.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if sphere hits an matching object, otherwise false.</returns>
API_FUNCTION() bool SphereCastAll(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a capsule geometry.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
/// <param name="height">The height of the capsule, excluding the top and bottom spheres.</param>
/// <param name="direction">The normalized direction in which cast a capsule.</param>
/// <param name="rotation">The capsule rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
API_FUNCTION() bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a capsule geometry.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
/// <param name="height">The height of the capsule, excluding the top and bottom spheres.</param>
/// <param name="direction">The normalized direction in which cast a capsule.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="rotation">The capsule rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
API_FUNCTION() bool CapsuleCast(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a capsule geometry.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
/// <param name="height">The height of the capsule, excluding the top and bottom spheres.</param>
/// <param name="direction">The normalized direction in which cast a capsule.</param>
/// <param name="results">The result hits. Valid only when method returns true.</param>
/// <param name="rotation">The capsule rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if capsule hits an matching object, otherwise false.</returns>
API_FUNCTION() bool CapsuleCastAll(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
/// <param name="scale">The scale of the convex mesh.</param>
/// <param name="direction">The normalized direction in which cast a convex mesh.</param>
/// <param name="rotation">The convex mesh rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
API_FUNCTION() bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
/// <param name="scale">The scale of the convex mesh.</param>
/// <param name="direction">The normalized direction in which cast a convex mesh.</param>
/// <param name="hitInfo">The result hit information. Valid only when method returns true.</param>
/// <param name="rotation">The convex mesh rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
API_FUNCTION() bool ConvexCast(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) RayCastHit& hitInfo, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Performs a sweep test against objects in the scene using a convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
/// <param name="scale">The scale of the convex mesh.</param>
/// <param name="direction">The normalized direction in which cast a convex mesh.</param>
/// <param name="results">The result hits. Valid only when method returns true.</param>
/// <param name="rotation">The convex mesh rotation.</param>
/// <param name="maxDistance">The maximum distance the ray should check for collisions.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if convex mesh hits an matching object, otherwise false.</returns>
API_FUNCTION() bool ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) Array<RayCastHit, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Checks whether the given box overlaps with other colliders or not.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
/// <param name="rotation">The box rotation.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if box overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool CheckBox(const Vector3& center, const Vector3& halfExtents, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Checks whether the given sphere overlaps with other colliders or not.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if sphere overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool CheckSphere(const Vector3& center, float radius, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Checks whether the given capsule overlaps with other colliders or not.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
/// <param name="height">The height of the capsule, excluding the top and bottom spheres.</param>
/// <param name="rotation">The capsule rotation.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if capsule overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool CheckCapsule(const Vector3& center, float radius, float height, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Checks whether the given convex mesh overlaps with other colliders or not.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
/// <param name="scale">The scale of the convex mesh.</param>
/// <param name="rotation">The convex mesh rotation.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if convex mesh overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool CheckConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given box.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
/// <param name="rotation">The box rotation.</param>
/// <param name="results">The result colliders that overlap with the given box. Valid only when method returns true.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if box overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given sphere.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <param name="results">The result colliders that overlap with the given sphere. Valid only when method returns true.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if sphere overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given capsule.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
/// <param name="height">The height of the capsule, excluding the top and bottom spheres.</param>
/// <param name="results">The result colliders that overlap with the given capsule. Valid only when method returns true.</param>
/// <param name="rotation">The capsule rotation.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if capsule overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
/// <param name="scale">The scale of the convex mesh.</param>
/// <param name="results">The result colliders that overlap with the given convex mesh. Valid only when method returns true.</param>
/// <param name="rotation">The convex mesh rotation.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if convex mesh overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array<Collider*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given box.
/// </summary>
/// <param name="center">The box center.</param>
/// <param name="halfExtents">The half size of the box in each direction.</param>
/// <param name="rotation">The box rotation.</param>
/// <param name="results">The result colliders that overlap with the given box. Valid only when method returns true.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if box overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given sphere.
/// </summary>
/// <param name="center">The sphere center.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <param name="results">The result colliders that overlap with the given sphere. Valid only when method returns true.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if sphere overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given capsule.
/// </summary>
/// <param name="center">The capsule center.</param>
/// <param name="radius">The radius of the capsule.</param>
/// <param name="height">The height of the capsule, excluding the top and bottom spheres.</param>
/// <param name="results">The result colliders that overlap with the given capsule. Valid only when method returns true.</param>
/// <param name="rotation">The capsule rotation.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if capsule overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
/// <summary>
/// Finds all colliders touching or inside of the given convex mesh.
/// </summary>
/// <param name="center">The convex mesh center.</param>
/// <param name="convexMesh">Collision data of the convex mesh.</param>
/// <param name="scale">The scale of the convex mesh.</param>
/// <param name="results">The result colliders that overlap with the given convex mesh. Valid only when method returns true.</param>
/// <param name="rotation">The convex mesh rotation.</param>
/// <param name="layerMask">The layer mask used to filter the results.</param>
/// <param name="hitTriggers">If set to <c>true</c> triggers will be hit, otherwise will skip them.</param>
/// <returns>True if convex mesh overlaps any matching object, otherwise false.</returns>
API_FUNCTION() bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array<PhysicsColliderActor*, HeapAllocation>& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true);
public:
#if WITH_VEHICLE
void AddWheeledVehicle(WheeledVehicle* vehicle);
void RemoveWheeledVehicle(WheeledVehicle* vehicle);
#endif
private:
String mName;
bool mAutoSimulation = true;
PxScene* mScene;
PxCpuDispatcher* mCpuDispatcher;
PxControllerManager* mControllerManager;
PxSimulationFilterShader mPhysXDefaultFilterShader = PxDefaultSimulationFilterShader;
SimulationEventCallback mEventsCallback;
void* mScratchMemory = nullptr;
FixedStepper* mStepper = nullptr;
float mLastDeltaTime = 0.0f;
bool mIsDuringSimulation = false;
CriticalSection mFlushLocker;
Array<PxActor*> mNewActors;
Array<PxActor*> mDeadActors;
Array<PxMaterial*> mDeadMaterials;
Array<PhysicsColliderActor*> mDeadColliders;
Array<Joint*> mDeadJoints;
Array<ActionData> mActions;
Array<PxBase*> mDeadObjects;
#if WITH_VEHICLE
Array<PxVehicleWheels*> mWheelVehiclesCache;
Array<PxRaycastQueryResult> mWheelQueryResults;
Array<PxRaycastHit> mWheelHitResults;
Array<PxWheelQueryResult> mWheelVehiclesResultsPerWheel;
Array<PxVehicleWheelQueryResult> mWheelVehiclesResultsPerVehicle;
PxBatchQuery* mWheelRaycastBatchQuery = nullptr;
PxVehicleDrivableSurfaceToTireFrictionPairs* mWheelTireFrictions = nullptr;
Array<WheeledVehicle*> mWheelVehicles;
#endif
};

View File

@@ -3,7 +3,6 @@
#pragma once
#include "Types.h"
#include "Engine/Core/Core.h"
#include <ThirdParty/PhysX/task/PxTask.h>
#include <ThirdParty/PhysX/foundation/PxSimpleTypes.h>
#include <ThirdParty/PhysX/foundation/PsSync.h>

View File

@@ -830,6 +830,14 @@ void Terrain::OnActiveInTreeChanged()
}
}
void Terrain::OnPhysicsSceneChanged(PhysicsScene* previous)
{
PhysicsColliderActor::OnPhysicsSceneChanged(previous);
for (auto patch : _patches)
patch->OnPhysicsSceneChanged(previous);
}
void Terrain::BeginPlay(SceneBeginData* data)
{
CacheNeighbors();

View File

@@ -458,6 +458,7 @@ protected:
void OnDisable() override;
void OnTransformChanged() override;
void OnActiveInTreeChanged() override;
void OnPhysicsSceneChanged(PhysicsScene* previous) override;
void BeginPlay(SceneBeginData* data) override;
void EndPlay() override;
};

View File

@@ -7,6 +7,7 @@
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Physics/Utilities.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Physics/PhysicsScene.h"
#include "Engine/Physics/PhysicalMaterial.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Graphics/Async/GPUTask.h"
@@ -1957,7 +1958,7 @@ bool TerrainPatch::UpdateCollision()
_collisionVertices.Resize(0);
// Recreate height field
Physics::RemoveObject(_physicsHeightField);
_terrain->GetPhysicsScene()->RemoveObject(_physicsHeightField);
_physicsHeightField = nullptr;
if (CreateHeightField())
{
@@ -2129,7 +2130,7 @@ void TerrainPatch::UpdatePostManualDeserialization()
_collisionVertices.Resize(0);
// Recreate height field
Physics::RemoveObject(_physicsHeightField);
_terrain->GetPhysicsScene()->RemoveObject(_physicsHeightField);
_physicsHeightField = nullptr;
if (CreateHeightField())
{
@@ -2190,8 +2191,6 @@ void TerrainPatch::CreateCollision()
_physicsActor->setActorFlag(PxActorFlag::eVISUALIZATION, true);
#endif
_physicsActor->attachShape(*_physicsShape);
Physics::AddActor(_physicsActor);
}
bool TerrainPatch::CreateHeightField()
@@ -2243,10 +2242,11 @@ void TerrainPatch::DestroyCollision()
ScopeLock lock(_collisionLocker);
ASSERT(HasCollision());
Physics::RemoveCollider(_terrain);
Physics::RemoveActor(_physicsActor);
Physics::RemoveObject(_physicsShape);
Physics::RemoveObject(_physicsHeightField);
_terrain->GetPhysicsScene()->RemoveCollider(_terrain);
_terrain->GetPhysicsScene()->RemoveActor(_physicsActor);
_terrain->GetPhysicsScene()->RemoveObject(_physicsShape);
_terrain->GetPhysicsScene()->RemoveObject(_physicsHeightField);
_physicsActor = nullptr;
_physicsShape = nullptr;
_physicsHeightField = nullptr;
@@ -2609,3 +2609,9 @@ void TerrainPatch::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
}
}
}
void TerrainPatch::OnPhysicsSceneChanged(PhysicsScene* previous)
{
previous->UnlinkActor(_physicsActor);
_terrain->GetPhysicsScene()->AddActor(_physicsActor);
}

View File

@@ -411,6 +411,7 @@ private:
/// <returns>True if failed, otherwise false.</returns>
bool UpdateCollision();
void OnPhysicsSceneChanged(PhysicsScene* previous);
public:
// [ISerializable]