diff --git a/Source/Editor/States/PlayingState.cs b/Source/Editor/States/PlayingState.cs index c9100dcf9..be2c838c6 100644 --- a/Source/Editor/States/PlayingState.cs +++ b/Source/Editor/States/PlayingState.cs @@ -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; } diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 08465b349..4e411243d 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -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(); diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 54c2a81f4..f2a0a3a62 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -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,28 @@ 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; +} diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index e3fcbc371..a54155e5f 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -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: /// The scene rendering interface. SceneRendering* GetSceneRendering() const; +public: + /// + /// Set the physics world the controller is part of. + /// + API_PROPERTY(Attributes="HideInEditor") void SetPhysicsScene(PhysicsScene* scene); + + /// + /// Get the physics world the controller is part of. + /// + API_PROPERTY(Attributes="HideInEditor") PhysicsScene* GetPhysicsScene(); + +protected: + virtual void OnPhysicsSceneChanged(PhysicsScene* previous) {}; + private: void SetSceneInHierarchy(Scene* scene); diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 496dc2519..3bbf91363 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -181,6 +181,8 @@ bool LevelImpl::spawnActor(Actor* actor, Actor* parent) } if (parent == nullptr) parent = Level::Scenes[0]; + + actor->SetPhysicsScene(parent->GetPhysicsScene()); actor->SetParent(parent, true, true); } diff --git a/Source/Engine/Physics/Actors/PhysicsActor.h b/Source/Engine/Physics/Actors/PhysicsActor.h index 69d239f95..05e015074 100644 --- a/Source/Engine/Physics/Actors/PhysicsActor.h +++ b/Source/Engine/Physics/Actors/PhysicsActor.h @@ -6,6 +6,8 @@ #include "Engine/Physics/Types.h" #include "IPhysicsActor.h" +class PhysicsScene; + /// /// A base class for all physical actors. /// diff --git a/Source/Engine/Physics/Actors/RigidBody.cpp b/Source/Engine/Physics/Actors/RigidBody.cpp index 9eb8dee28..a3047889d 100644 --- a/Source/Engine/Physics/Actors/RigidBody.cpp +++ b/Source/Engine/Physics/Actors/RigidBody.cpp @@ -2,10 +2,13 @@ #include "RigidBody.h" #include "PxMaterial.h" + +#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 #include @@ -476,7 +479,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 +583,7 @@ void RigidBody::EndPlay() if (_actor) { // Remove actor - Physics::RemoveActor(_actor); + GetPhysicsScene()->RemoveActor(_actor); _actor = nullptr; } } @@ -627,3 +630,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); +} diff --git a/Source/Engine/Physics/Actors/RigidBody.h b/Source/Engine/Physics/Actors/RigidBody.h index 8d487b869..8726161da 100644 --- a/Source/Engine/Physics/Actors/RigidBody.h +++ b/Source/Engine/Physics/Actors/RigidBody.h @@ -572,4 +572,5 @@ protected: void EndPlay() override; void OnActiveInTreeChanged() override; void OnTransformChanged() override; + void OnPhysicsSceneChanged(PhysicsScene* previous) override; }; diff --git a/Source/Engine/Physics/Actors/SplineRopeBody.cpp b/Source/Engine/Physics/Actors/SplineRopeBody.cpp index b341f7008..a0c1c6b1e 100644 --- a/Source/Engine/Physics/Actors/SplineRopeBody.cpp +++ b/Source/Engine/Physics/Actors/SplineRopeBody.cpp @@ -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(); diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.cpp b/Source/Engine/Physics/Actors/WheeledVehicle.cpp index 7c6106158..5becca07e 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.cpp +++ b/Source/Engine/Physics/Actors/WheeledVehicle.cpp @@ -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 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; } diff --git a/Source/Engine/Physics/Actors/WheeledVehicle.h b/Source/Engine/Physics/Actors/WheeledVehicle.h index bbc52af5b..cd3ef88f4 100644 --- a/Source/Engine/Physics/Actors/WheeledVehicle.h +++ b/Source/Engine/Physics/Actors/WheeledVehicle.h @@ -6,7 +6,7 @@ #include "Engine/Physics/Colliders/Collider.h" #include "Engine/Scripting/ScriptingObjectReference.h" -class Physics; +class PhysicsScene; /// /// 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; /// 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; diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index c57c0f395..52324c98b 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -7,9 +7,9 @@ #include "Engine/Serialization/Serialization.h" #include "Engine/Engine/Time.h" #include "Engine/Physics/PhysicalMaterial.h" +#include "Engine/Physics/PhysicsScene.h" #include #include -#include #include #include #include @@ -156,7 +156,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,9 +171,9 @@ 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(); + filters.mCCTFilterCallback = GetPhysicsScene()->GetCharacterControllerFilterCallback(); result = (CollisionFlags)(byte)_controller->move(C2P(displacement), _minMoveDistance, deltaTime, filters); _lastFlags = result; @@ -243,7 +243,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 +433,14 @@ void CharacterController::OnTransformChanged() } } +void CharacterController::OnPhysicsSceneChanged(PhysicsScene* previous) +{ + Collider::OnPhysicsSceneChanged(previous); + + DeleteController(); + CreateController(); +} + void CharacterController::Serialize(SerializeStream& stream, const void* otherObj) { // Base diff --git a/Source/Engine/Physics/Colliders/CharacterController.h b/Source/Engine/Physics/Colliders/CharacterController.h index 5fece3195..8a0a79223 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.h +++ b/Source/Engine/Physics/Colliders/CharacterController.h @@ -237,4 +237,5 @@ protected: void OnDisable() override; void OnParentChanged() override; void OnTransformChanged() override; + void OnPhysicsSceneChanged(PhysicsScene* previous) override; }; diff --git a/Source/Engine/Physics/Colliders/Collider.cpp b/Source/Engine/Physics/Colliders/Collider.cpp index 35a4daf7e..73440c5b3 100644 --- a/Source/Engine/Physics/Colliders/Collider.cpp +++ b/Source/Engine/Physics/Colliders/Collider.cpp @@ -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 #include @@ -17,7 +18,6 @@ #include #include #include -#include 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); + } +} diff --git a/Source/Engine/Physics/Colliders/Collider.h b/Source/Engine/Physics/Colliders/Collider.h index 1c78f57ce..479b7c288 100644 --- a/Source/Engine/Physics/Colliders/Collider.h +++ b/Source/Engine/Physics/Colliders/Collider.h @@ -232,4 +232,5 @@ protected: void OnParentChanged() override; void OnTransformChanged() override; void OnLayerChanged() override; + void OnPhysicsSceneChanged(PhysicsScene* previous) override; }; diff --git a/Source/Engine/Physics/Colliders/MeshCollider.cpp b/Source/Engine/Physics/Colliders/MeshCollider.cpp index 113fd7355..5b720860c 100644 --- a/Source/Engine/Physics/Colliders/MeshCollider.cpp +++ b/Source/Engine/Physics/Colliders/MeshCollider.cpp @@ -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) { diff --git a/Source/Engine/Physics/Colliders/SplineCollider.cpp b/Source/Engine/Physics/Colliders/SplineCollider.cpp index 79e49e16a..62516a954 100644 --- a/Source/Engine/Physics/Colliders/SplineCollider.cpp +++ b/Source/Engine/Physics/Colliders/SplineCollider.cpp @@ -8,6 +8,7 @@ #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" @@ -45,7 +46,7 @@ void SplineCollider::ExtractGeometry(Array& vertexBuffer, Array& 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 +173,7 @@ void SplineCollider::EndPlay() // Cleanup if (_triangleMesh) { - Physics::RemoveObject(_triangleMesh); + GetPhysicsScene()->RemoveObject(_triangleMesh); _triangleMesh = nullptr; } } @@ -306,7 +307,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()); diff --git a/Source/Engine/Physics/CollisionData.cpp b/Source/Engine/Physics/CollisionData.cpp index 9de47a0f0..95e5a67ca 100644 --- a/Source/Engine/Physics/CollisionData.cpp +++ b/Source/Engine/Physics/CollisionData.cpp @@ -1,12 +1,13 @@ // Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. -#include "CollisionData.h" #include "Engine/Core/Log.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Physics/Physics.h" +#include "Engine/Physics/PhysicsScene.h" #include "Engine/Physics/Utilities.h" +#include "Engine/Physics/CollisionData.h" #include "Engine/Physics/CollisionCooking.h" #include "Engine/Threading/Threading.h" #include @@ -384,12 +385,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(); diff --git a/Source/Engine/Physics/Physics.Queries.cpp b/Source/Engine/Physics/Physics.Queries.cpp index f7986397e..b8c691c21 100644 --- a/Source/Engine/Physics/Physics.Queries.cpp +++ b/Source/Engine/Physics/Physics.Queries.cpp @@ -3,696 +3,140 @@ #include "Physics.h" #include "Utilities.h" #include "CollisionData.h" +#include "PhysicsScene.h" #include "Actors/PhysicsColliderActor.h" -#include -#include -#include -#include - -// Temporary result buffer size -#define HIT_BUFFER_SIZE 128 - -template -class DynamicHitBuffer : public PxHitCallback -{ -private: - - uint32 _count; - HitType _buffer[HIT_BUFFER_SIZE]; - -public: - - DynamicHitBuffer() - : PxHitCallback(_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 buffer - -#define SCENE_QUERY_SETUP_OVERLAP_1() SCENE_QUERY_SETUP(false); \ - PxOverlapBufferN<1> buffer - -#define SCENE_QUERY_SETUP_OVERLAP() SCENE_QUERY_SETUP(false); \ - DynamicHitBuffer 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(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(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& results, const float maxDistance, uint32 layerMask, bool hitTriggers) { - // Prepare data - SCENE_QUERY_SETUP(false); - DynamicHitBuffer 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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); } diff --git a/Source/Engine/Physics/Physics.cpp b/Source/Engine/Physics/Physics.cpp index 72e1e66ba..6198accfc 100644 --- a/Source/Engine/Physics/Physics.cpp +++ b/Source/Engine/Physics/Physics.cpp @@ -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 -#include -#if WITH_VEHICLE -#include -#endif #if WITH_PVD #include #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 NewActors; - Array Actions; - Array DeadActors; - Array DeadMaterials; - Array _deadObjects; - Array DeadColliders; - Array 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 WheelVehiclesCache; - Array WheelQueryResults; - Array WheelHitResults; - Array WheelVehiclesResultsPerWheel; - Array WheelVehiclesResultsPerVehicle; - PxBatchQuery* WheelRaycastBatchQuery = nullptr; - PxVehicleDrivableSurfaceToTireFrictionPairs* WheelTireFrictions = nullptr; #endif + + void reset() + { + Physics::Scenes.Resize(1); + } } -#if WITH_VEHICLE -Array WheelVehicles; -#endif -bool Physics::AutoSimulation = true; +PhysicsScene* Physics::DefaultScene = nullptr; +Array 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); } } @@ -366,7 +268,6 @@ bool PhysicsService::Init() { #define CHECK_INIT(value, msg) if(!value) { LOG(Error, msg); return true; } - auto cpuInfo = Platform::GetCPUInfo(); auto& settings = *PhysicsSettings::Get(); // Send info @@ -422,45 +323,7 @@ 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(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 = Physics::FindOrCreateScene(TEXT("Default")); // Create default resources DefaultMaterial = CPhysX->createMaterial(0.7f, 0.7f, 0.3f); @@ -472,15 +335,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 +357,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 +409,6 @@ PxCooking* Physics::GetCooking() return Cooking; } -PxScene* Physics::GetScene() -{ - return PhysicsScene; -} - -PxControllerManager* Physics::GetControllerManager() -{ - return ControllerManager; -} - PxTolerancesScale* Physics::GetTolerancesScale() { return &ToleranceScale; @@ -565,415 +416,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(); - } - 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(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((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 +487,48 @@ 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(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(); -} - -void Physics::RemoveObject(PxBase* obj) -{ - ASSERT(obj); - - FlushLocker.Lock(); - _deadObjects.Add(obj); - FlushLocker.Unlock(); -} - -void Physics::AddActor(PxActor* actor) -{ - ASSERT(actor); - - FlushLocker.Lock(); - if (IsInMainThread()) - { - GetScene()->addActor(*actor); - } - else - { - NewActors.Add(actor); - } - FlushLocker.Unlock(); -} - -void Physics::AddActor(PxRigidDynamic* actor, bool putToSleep) -{ - 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(); -} - -void Physics::RemoveActor(PxActor* actor) -{ - 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(); + for (auto scene : Scenes) + scene->FlushRequests(); } void Physics::RemoveJoint(Joint* joint) { - ASSERT(joint); - - FlushLocker.Lock(); - DeadJoints.Add(joint); - FlushLocker.Unlock(); + for (auto scene : Scenes) + scene->RemoveJoint(joint); +} + +PhysicsScene* Physics::FindOrCreateScene(const String& name) +{ + auto scene = FindScene(name); + + if (scene == nullptr) + { + auto& settings = *PhysicsSettings::Get(); + + scene = New(name, settings); + Scenes.Add(scene); + } + + return scene; +} + +PhysicsScene* Physics::FindScene(const String& name) +{ + for (auto scene : Scenes) + { + if (scene->GetName() == name) + return scene; + } + + return nullptr; } diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index 6eb38dcd6..f2317288a 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -9,6 +9,7 @@ #include "Types.h" class PhysicsColliderActor; +class PhysicsScene; class Joint; class Collider; class CollisionData; @@ -87,47 +88,45 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics); #endif - /// - /// Gets PhysX scene object - /// - static PxScene* GetScene(); - - /// - /// Gets PhysX characters controller manager object - /// - static PxControllerManager* GetControllerManager(); - /// /// Gets the physics tolerances scale. /// static PxTolerancesScale* GetTolerancesScale(); - /// - /// Gets the default query filter callback used for the scene queries. - /// - static PxQueryFilterCallback* GetQueryFilterCallback(); - - /// - /// Gets the default query filter callback used for the character controller collisions detection. - /// - static PxQueryFilterCallback* GetCharacterQueryFilterCallback(); - - /// - /// Gets the default controller filter callback used for the character controller collisions detection. - /// - static PxControllerFilterCallback* GetCharacterControllerFilterCallback(); - /// /// Gets the default physical material. /// static PxMaterial* GetDefaultMaterial(); public: + + /// + /// The default physics scene. + /// + API_FIELD(ReadOnly) + static PhysicsScene* DefaultScene; + /// + /// List with all physics scenes (readonly). + /// + API_FIELD(ReadOnly) + static Array Scenes; + + /// + /// Finds an existing or creates it if it does not exist. + /// + API_FUNCTION() static PhysicsScene* FindOrCreateScene(const String& name); + + /// + ///Finds an existing scene. + /// + API_FUNCTION() static PhysicsScene* FindScene(const String& name); + +public: /// /// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it. /// - API_FIELD() static bool AutoSimulation; + API_PROPERTY() static bool GetAutoSimulation(); /// /// Gets the current gravity force. @@ -519,11 +518,22 @@ public: /// The delta time (in seconds). API_FUNCTION() static void Simulate(float dt); + /// + /// Called during main engine loop to start physic simulation on all registered scenes. + /// + /// The delta time (in seconds). + static void SimulateAll(float dt); + /// /// Called during main engine loop to collect physic simulation results and apply them as well as fire collision events. /// API_FUNCTION() static void CollectResults(); + /// + /// Called during main engine loop to collect physic simulation results on all registered scenes and apply them as well as fire collision events. + /// + static void CollectResultsAll(); + /// /// Checks if physical simulation is running /// @@ -535,7 +545,7 @@ public: /// /// Flushes the async requests to add/remove actors, remove materials, etc.. /// - static void FlushRequests(); + static void FlushRequestsAll(); /// /// Removes the material (using safe async request). diff --git a/Source/Engine/Physics/PhysicsScene.Queries.cpp b/Source/Engine/Physics/PhysicsScene.Queries.cpp new file mode 100644 index 000000000..87fa78529 --- /dev/null +++ b/Source/Engine/Physics/PhysicsScene.Queries.cpp @@ -0,0 +1,697 @@ +// 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 +#include + +// Temporary result buffer size +#define HIT_BUFFER_SIZE 128 + +template +class DynamicHitBuffer : public PxHitCallback +{ +private: + + uint32 _count; + HitType _buffer[HIT_BUFFER_SIZE]; + +public: + + DynamicHitBuffer() + : PxHitCallback(_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 buffer + +#define SCENE_QUERY_SETUP_OVERLAP_1() SCENE_QUERY_SETUP(false); \ + PxOverlapBufferN<1> buffer + +#define SCENE_QUERY_SETUP_OVERLAP() SCENE_QUERY_SETUP(false); \ + DynamicHitBuffer 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(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(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* PhysicsScene::GetQueryFilterCallback() +{ + static QueryFilter Filter; + return &Filter; +} + +PxQueryFilterCallback* PhysicsScene::GetCharacterQueryFilterCallback() +{ + static CharacterQueryFilter Filter; + return &Filter; +} + +PxControllerFilterCallback* PhysicsScene::GetCharacterControllerFilterCallback() +{ + static CharacterControllerFilter 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& results, const float maxDistance, uint32 layerMask, bool hitTriggers) +{ + // Prepare data + SCENE_QUERY_SETUP(false); + DynamicHitBuffer 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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& 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; +} diff --git a/Source/Engine/Physics/PhysicsScene.cpp b/Source/Engine/Physics/PhysicsScene.cpp new file mode 100644 index 000000000..7b5520c0a --- /dev/null +++ b/Source/Engine/Physics/PhysicsScene.cpp @@ -0,0 +1,818 @@ +#include "Physics.h" +#include "PhysicsScene.h" +#include "PhysicsSettings.h" +#include "PhysicsStepper.h" +#include "SimulationEventCallback.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 + +#if WITH_VEHICLE +#include "Actors/WheeledVehicle.h" +#include +#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 + +class PhysicsScenePhysX +{ + friend PhysicsScene; + +private: + PxScene* Scene; + PxCpuDispatcher* CpuDispatcher; + PxControllerManager* ControllerManager; + PxSimulationFilterShader PhysXDefaultFilterShader = PxDefaultSimulationFilterShader; + SimulationEventCallback EventsCallback; + CriticalSection FlushLocker; + + Array NewActors; + Array DeadActors; + Array DeadMaterials; + Array DeadColliders; + Array DeadJoints; + Array Actions; + Array DeadObjects; + +#if WITH_VEHICLE + Array WheelVehiclesCache; + Array WheelQueryResults; + Array WheelHitResults; + Array WheelVehiclesResultsPerWheel; + Array WheelVehiclesResultsPerVehicle; + PxBatchQuery* WheelRaycastBatchQuery = nullptr; + PxVehicleDrivableSurfaceToTireFrictionPairs* WheelTireFrictions = nullptr; + Array WheelVehicles; +#endif +}; + +PhysicsScene::PhysicsScene(const String& name, const PhysicsSettings& settings) + : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) +{ +#define CHECK_INIT(value, msg) if(!value) { LOG(Error, msg); return; } + + mName = name; + mPhysxImpl = New(); + + // 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 = &mPhysxImpl->EventsCallback; + sceneDesc.filterShader = FilterShader; + sceneDesc.bounceThresholdVelocity = settings.BounceThresholdVelocity; + if (sceneDesc.cpuDispatcher == nullptr) + { + mPhysxImpl->CpuDispatcher = PxDefaultCpuDispatcherCreate(Math::Clamp(Platform::GetCPUInfo().ProcessorCoreCount - 1, 1, 4)); + CHECK_INIT(mPhysxImpl->CpuDispatcher, "PxDefaultCpuDispatcherCreate failed!"); + sceneDesc.cpuDispatcher = mPhysxImpl->CpuDispatcher; + } + if (sceneDesc.filterShader == nullptr) + { + sceneDesc.filterShader = mPhysxImpl->PhysXDefaultFilterShader; + } + + // Create scene + mPhysxImpl->Scene = CPhysX->createScene(sceneDesc); + CHECK_INIT(mPhysxImpl->Scene, "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 + mPhysxImpl->ControllerManager = PxCreateControllerManager(*mPhysxImpl->Scene); +} + +PhysicsScene::~PhysicsScene() +{ +#if WITH_VEHICLE + RELEASE_PHYSX(mPhysxImpl->WheelRaycastBatchQuery); + RELEASE_PHYSX(mPhysxImpl->WheelTireFrictions); + mPhysxImpl->WheelQueryResults.Resize(0); + mPhysxImpl->WheelHitResults.Resize(0); + mPhysxImpl->WheelVehiclesResultsPerWheel.Resize(0); + mPhysxImpl->WheelVehiclesResultsPerVehicle.Resize(0); +#endif + + RELEASE_PHYSX(mPhysxImpl->ControllerManager); + SAFE_DELETE(mPhysxImpl->CpuDispatcher); + SAFE_DELETE(mStepper); + Allocator::Free(mScratchMemory); + + mScratchMemory = nullptr; + mPhysxImpl->Scene->release(); + + SAFE_DELETE(mPhysxImpl); +} + +String PhysicsScene::GetName() const +{ + return mName; +} + +PxScene* PhysicsScene::GetScene() +{ + return mPhysxImpl->Scene; +} + +bool PhysicsScene::GetAutoSimulation() +{ + return mAutoSimulation; +} + +void PhysicsScene::SetAutoSimulation(bool value) +{ + mAutoSimulation = value; +} + +void PhysicsScene::SetGravity(const Vector3& value) +{ + if(mPhysxImpl->Scene) + { + mPhysxImpl->Scene->setGravity(C2P(value)); + } +} + +Vector3 PhysicsScene::GetGravity() +{ + return mPhysxImpl->Scene ? P2C(mPhysxImpl->Scene->getGravity()) : Vector3::Zero; +} + +bool PhysicsScene::GetEnableCCD() +{ + return mPhysxImpl->Scene ? (mPhysxImpl->Scene->getFlags() & PxSceneFlag::eENABLE_CCD) == PxSceneFlag::eENABLE_CCD : !PhysicsSettings::Get()->DisableCCD; +} + +void PhysicsScene::SetEnableCCD(const bool value) +{ + if (mPhysxImpl->Scene) + mPhysxImpl->Scene->setFlag(PxSceneFlag::eENABLE_CCD, value); +} + +float PhysicsScene::GetBounceThresholdVelocity() +{ + return mPhysxImpl->Scene ? mPhysxImpl->Scene->getBounceThresholdVelocity() : PhysicsSettings::Get()->BounceThresholdVelocity; +} + +void PhysicsScene::SetBounceThresholdVelocity(const float value) +{ + if (mPhysxImpl->Scene) + mPhysxImpl->Scene->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(); + } + 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(mPhysxImpl->Scene, dt, mScratchMemory, SCRATCH_BLOCK_SIZE) == false) + return; + mPhysxImpl->EventsCallback.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(mPhysxImpl->Scene); + } + +#if WITH_VEHICLE + if (mPhysxImpl->WheelVehicles.HasItems()) + { + PROFILE_CPU_NAMED("Physics.Vehicles"); + + // Update vehicles steering + mPhysxImpl->WheelVehiclesCache.Clear(); + mPhysxImpl->WheelVehiclesCache.EnsureCapacity(mPhysxImpl->WheelVehicles.Count()); + int32 wheelsCount = 0; + for (auto wheelVehicle : mPhysxImpl->WheelVehicles) + { + if (!wheelVehicle->IsActiveInHierarchy()) + continue; + auto drive = (PxVehicleWheels*)wheelVehicle->_drive; + ASSERT(drive); + mPhysxImpl->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, 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 > mPhysxImpl->WheelQueryResults.Count()) + { + if (mPhysxImpl->WheelRaycastBatchQuery) + mPhysxImpl->WheelRaycastBatchQuery->release(); + mPhysxImpl->WheelQueryResults.Resize(wheelsCount, false); + mPhysxImpl->WheelHitResults.Resize(wheelsCount, false); + PxBatchQueryDesc desc(wheelsCount, 0, 0); + desc.queryMemory.userRaycastResultBuffer = mPhysxImpl->WheelQueryResults.Get(); + desc.queryMemory.userRaycastTouchBuffer = mPhysxImpl->WheelHitResults.Get(); + desc.queryMemory.raycastTouchBufferSize = wheelsCount; + desc.preFilterShader = WheelRaycastPreFilter; + mPhysxImpl->WheelRaycastBatchQuery = mPhysxImpl->Scene->createBatchQuery(desc); + } + + // TODO: expose vehicle tires configuration + if (!mPhysxImpl->WheelTireFrictions) + { + PxVehicleDrivableSurfaceType surfaceTypes[1]; + surfaceTypes[0].mType = 0; + const PxMaterial* surfaceMaterials[1]; + surfaceMaterials[0] = Physics::GetDefaultMaterial(); + mPhysxImpl->WheelTireFrictions = PxVehicleDrivableSurfaceToTireFrictionPairs::allocate(1, 1); + mPhysxImpl->WheelTireFrictions->setup(1, 1, surfaceMaterials, surfaceTypes); + mPhysxImpl->WheelTireFrictions->setTypePairFriction(0, 0, 5.0f); + } + + // Setup cache for wheel states + mPhysxImpl->WheelVehiclesResultsPerVehicle.Resize(mPhysxImpl->WheelVehiclesCache.Count(), false); + mPhysxImpl->WheelVehiclesResultsPerWheel.Resize(wheelsCount, false); + wheelsCount = 0; + for (int32 i = 0, ii = 0; i < mPhysxImpl->WheelVehicles.Count(); i++) + { + auto wheelVehicle = mPhysxImpl->WheelVehicles[i]; + if (!wheelVehicle->IsActiveInHierarchy()) + continue; + auto drive = (PxVehicleWheels*)mPhysxImpl->WheelVehicles[ii]->_drive; + auto& perVehicle = mPhysxImpl->WheelVehiclesResultsPerVehicle[ii]; + ii++; + perVehicle.nbWheelQueryResults = drive->mWheelsSimData.getNbWheels(); + perVehicle.wheelQueryResults = mPhysxImpl->WheelVehiclesResultsPerWheel.Get() + wheelsCount; + wheelsCount += perVehicle.nbWheelQueryResults; + } + + // Update vehicles + if (mPhysxImpl->WheelVehiclesCache.Count() != 0) + { + PxVehicleSuspensionRaycasts(mPhysxImpl->WheelRaycastBatchQuery, mPhysxImpl->WheelVehiclesCache.Count(), mPhysxImpl->WheelVehiclesCache.Get(), mPhysxImpl->WheelQueryResults.Count(), mPhysxImpl->WheelQueryResults.Get()); + PxVehicleUpdates(mLastDeltaTime, mPhysxImpl->Scene->getGravity(), *mPhysxImpl->WheelTireFrictions, mPhysxImpl->WheelVehiclesCache.Count(), mPhysxImpl->WheelVehiclesCache.Get(), mPhysxImpl->WheelVehiclesResultsPerVehicle.Get()); + } + + // Synchronize state + for (int32 i = 0, ii = 0; i < mPhysxImpl->WheelVehicles.Count(); i++) + { + auto wheelVehicle = mPhysxImpl->WheelVehicles[i]; + if (!wheelVehicle->IsActiveInHierarchy()) + continue; + auto drive = mPhysxImpl->WheelVehiclesCache[ii]; + auto& perVehicle = mPhysxImpl->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(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 = mPhysxImpl->Scene->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((Actor*)pxActor->userData); + ASSERT(actor); + actor->OnActiveTransformChanged(pxActor->getGlobalPose()); + } + } + } + + { + PROFILE_CPU_NAMED("Physics.SendEvents"); + + mPhysxImpl->EventsCallback.CollectResults(); + mPhysxImpl->EventsCallback.SendTriggerEvents(); + mPhysxImpl->EventsCallback.SendCollisionEvents(); + mPhysxImpl->EventsCallback.SendJointEvents(); + } + + // End + mIsDuringSimulation = false; +} + +void PhysicsScene::FlushRequests() +{ + ASSERT(!IsDuringSimulation()); + ASSERT(CPhysX); + + PROFILE_CPU(); + + mPhysxImpl->FlushLocker.Lock(); + + // Note: this does not handle case when actor is removed and added to the scene at the same time + + if (mPhysxImpl->NewActors.HasItems()) + { + GetScene()->addActors(mPhysxImpl->NewActors.Get(), mPhysxImpl->NewActors.Count()); + mPhysxImpl->NewActors.Clear(); + } + + for (int32 i = 0; i < mPhysxImpl->Actions.Count(); i++) + { + const auto action = mPhysxImpl->Actions[i]; + switch (action.Type) + { + case ActionType::Sleep: + static_cast(action.Actor)->putToSleep(); + break; + } + } + mPhysxImpl->Actions.Clear(); + + if (mPhysxImpl->DeadActors.HasItems()) + { + GetScene()->removeActors(mPhysxImpl->DeadActors.Get(), mPhysxImpl->DeadActors.Count(), true); + for (int32 i = 0; i < mPhysxImpl->DeadActors.Count(); i++) + { + mPhysxImpl->DeadActors[i]->release(); + } + mPhysxImpl->DeadActors.Clear(); + } + + if (mPhysxImpl->DeadColliders.HasItems()) + { + for (int32 i = 0; i < mPhysxImpl->DeadColliders.Count(); i++) + { + mPhysxImpl->EventsCallback.OnColliderRemoved(mPhysxImpl->DeadColliders[i]); + } + mPhysxImpl->DeadColliders.Clear(); + } + + if (mPhysxImpl->DeadJoints.HasItems()) + { + for (int32 i = 0; i < mPhysxImpl->DeadJoints.Count(); i++) + { + mPhysxImpl->EventsCallback.OnJointRemoved(mPhysxImpl->DeadJoints[i]); + } + mPhysxImpl->DeadJoints.Clear(); + } + + for (int32 i = 0; i < mPhysxImpl->DeadMaterials.Count(); i++) + { + auto material = mPhysxImpl->DeadMaterials[i]; + + // Unlink ref to flax object + material->userData = nullptr; + + material->release(); + } + mPhysxImpl->DeadMaterials.Clear(); + + for (int32 i = 0; i < mPhysxImpl->DeadObjects.Count(); i++) + { + mPhysxImpl->DeadObjects[i]->release(); + } + mPhysxImpl->DeadObjects.Clear(); + + mPhysxImpl->FlushLocker.Unlock(); +} + +void PhysicsScene::RemoveMaterial(PxMaterial* material) +{ + ASSERT(material); + + mPhysxImpl->FlushLocker.Lock(); + mPhysxImpl->DeadMaterials.Add(material); + mPhysxImpl->FlushLocker.Unlock(); +} + +void PhysicsScene::RemoveObject(PxBase* obj) +{ + ASSERT(obj); + + mPhysxImpl->FlushLocker.Lock(); + mPhysxImpl->DeadObjects.Add(obj); + mPhysxImpl->FlushLocker.Unlock(); +} + +void PhysicsScene::AddActor(PxActor* actor) +{ + ASSERT(actor); + + mPhysxImpl->FlushLocker.Lock(); + if (IsInMainThread()) + { + GetScene()->addActor(*actor); + } + else + { + mPhysxImpl->NewActors.Add(actor); + } + mPhysxImpl->FlushLocker.Unlock(); +} + +void PhysicsScene::AddActor(PxRigidDynamic* actor, bool putToSleep) +{ + ASSERT(actor); + + mPhysxImpl->FlushLocker.Lock(); + if (IsInMainThread()) + { + GetScene()->addActor(*actor); + if (putToSleep) + actor->putToSleep(); + } + else + { + mPhysxImpl->NewActors.Add(actor); + if (putToSleep) + mPhysxImpl->Actions.Add({ ActionType::Sleep, actor }); + } + mPhysxImpl->FlushLocker.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; + + mPhysxImpl->FlushLocker.Lock(); + mPhysxImpl->DeadActors.Add(actor); + mPhysxImpl->FlushLocker.Unlock(); +} + +void PhysicsScene::RemoveCollider(PhysicsColliderActor* collider) +{ + ASSERT(collider); + + mPhysxImpl->FlushLocker.Lock(); + mPhysxImpl->DeadColliders.Add(collider); + mPhysxImpl->FlushLocker.Unlock(); +} + +void PhysicsScene::RemoveJoint(Joint* joint) +{ + ASSERT(joint); + + mPhysxImpl->FlushLocker.Lock(); + mPhysxImpl->DeadJoints.Add(joint); + mPhysxImpl->FlushLocker.Unlock(); +} + +PxControllerManager* PhysicsScene::GetControllerManager() +{ + return mPhysxImpl->ControllerManager; +} + +#if WITH_VEHICLE +void PhysicsScene::AddWheeledVehicle(WheeledVehicle* vehicle) +{ + mPhysxImpl->WheelVehicles.Add(vehicle); +} + +void PhysicsScene::RemoveWheeledVehicle(WheeledVehicle* vehicle) +{ + mPhysxImpl->WheelVehicles.Remove(vehicle); +} +#endif diff --git a/Source/Engine/Physics/PhysicsScene.h b/Source/Engine/Physics/PhysicsScene.h new file mode 100644 index 000000000..1c95e7ece --- /dev/null +++ b/Source/Engine/Physics/PhysicsScene.h @@ -0,0 +1,533 @@ +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Scripting/ScriptingType.h" +#include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Types.h" + +#if WITH_VEHICLE +class WheeledVehicle; +#endif + +struct ActionData; +struct RayCastHit; +class FixedStepper; +class PhysicsSettings; +class PhysicsColliderActor; +class PhysicsScenePhysX; +class Joint; +class Collider; +class CollisionData; + +/// +/// Isolated physics scene. +/// +API_CLASS(NoSpawn) class FLAXENGINE_API PhysicsScene : public PersistentScriptingObject +{ + DECLARE_SCRIPTING_TYPE_NO_SPAWN(PhysicsScene); + + explicit PhysicsScene(const String& name, const PhysicsSettings& settings); + ~PhysicsScene(); + + /// + /// Gets the name of the scene. + /// + API_PROPERTY() String GetName() const; + +public: + String ToString() const override + { + return GetName(); + } + + PxScene* GetScene(); + + /// + /// The automatic simulation feature. True if perform physics simulation after on fixed update by auto, otherwise user should do it. + /// + API_PROPERTY() bool GetAutoSimulation(); + API_PROPERTY() void SetAutoSimulation(bool value); + + /// + /// Sets the current gravity force. + /// + API_PROPERTY() void SetGravity(const Vector3& value); + + /// + /// Gets the current gravity force. + /// + API_PROPERTY() Vector3 GetGravity(); + + /// + /// Gets the CCD feature enable flag. + /// + API_PROPERTY() bool GetEnableCCD(); + + /// + /// Sets the CCD feature enable flag. + /// + API_PROPERTY() void SetEnableCCD(const bool value); + + /// + /// Gets the minimum relative velocity required for an object to bounce. + /// + API_PROPERTY() float GetBounceThresholdVelocity(); + + /// + /// Sets the minimum relative velocity required for an object to bounce. + /// + API_PROPERTY() void SetBounceThresholdVelocity(const float value); + + /// + /// Called during main engine loop to start physic simulation. Use CollectResults after. + /// + /// The delta time (in seconds). + API_FUNCTION() void Simulate(float dt); + + /// + /// Checks if physical simulation is running + /// + /// True if simulation is active, otherwise false + API_PROPERTY() bool IsDuringSimulation(); + + /// + /// Called to collect physic simulation results and apply them as well as fire collision events. + /// + API_FUNCTION() void CollectResults(); + + /// + /// Flushes the async requests to add/remove actors, remove materials, etc.. + /// + void FlushRequests(); + + /// + /// Removes the material (using safe async request). + /// + /// The material. + void RemoveMaterial(PxMaterial* material); + + /// + /// Removes the physX object via calling release() on it (using safe async request). + /// + /// The obj. + void RemoveObject(PxBase* obj); + + /// + /// Adds the actor (using safe async request). + /// + /// The actor. + void AddActor(PxActor* actor); + + /// + /// Adds the actor (using safe async request). + /// + /// The actor. + /// If set to true will put actor to sleep after spawning. + void AddActor(PxRigidDynamic* actor, bool putToSleep = false); + + /// + /// Removes the actor (using safe async request). + /// + /// The actor. + void RemoveActor(PxActor* actor); + + /// + /// Removes the actor from the underlying physics scene without destroying it. + /// + void UnlinkActor(PxActor* actor); + + /// + /// Marks that collider has been removed (all collision events should be cleared to prevent leaks of using removed object). + /// + /// The collider. + void RemoveCollider(PhysicsColliderActor* collider); + + /// + /// Marks that joint has been removed (all collision events should be cleared to prevent leaks of using removed object). + /// + /// The joint. + void RemoveJoint(Joint* joint); + + /// + /// Gets PhysX characters controller manager object + /// + PxControllerManager* GetControllerManager(); + +public: + /// + /// Gets the default query filter callback used for the scene queries. + /// + PxQueryFilterCallback* GetQueryFilterCallback(); + + /// + /// Gets the default query filter callback used for the character controller collisions detection. + /// + PxQueryFilterCallback* GetCharacterQueryFilterCallback(); + + /// + /// Gets the default controller filter callback used for the character controller collisions detection. + /// + static PxControllerFilterCallback* GetCharacterControllerFilterCallback(); + + /// + /// Performs a raycast against objects in the scene. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if ray hits an matching object, otherwise false. + API_FUNCTION() bool RayCast(const Vector3& origin, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a raycast against objects in the scene, returns results in a RayCastHit structure. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The result hit information. Valid only when method returns true. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if ray hits an matching object, otherwise false. + 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); + + /// + /// Performs a raycast against objects in the scene, returns results in a RayCastHit structure. + /// + /// The origin of the ray. + /// The normalized direction of the ray. + /// The result hits. Valid only when method returns true. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if ray hits an matching object, otherwise false. + API_FUNCTION() bool RayCastAll(const Vector3& origin, const Vector3& direction, API_PARAM(Out) Array& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a box geometry. + /// + /// The box center. + /// The half size of the box in each direction. + /// The normalized direction in which cast a box. + /// The box rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if box hits an matching object, otherwise false. + 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); + + /// + /// Performs a sweep test against objects in the scene using a box geometry. + /// + /// The box center. + /// The half size of the box in each direction. + /// The normalized direction in which cast a box. + /// The result hit information. Valid only when method returns true. + /// The box rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if box hits an matching object, otherwise false. + 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); + + /// + /// Performs a sweep test against objects in the scene using a box geometry. + /// + /// The box center. + /// The half size of the box in each direction. + /// The normalized direction in which cast a box. + /// The result hits. Valid only when method returns true. + /// The box rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if box hits an matching object, otherwise false. + API_FUNCTION() bool BoxCastAll(const Vector3& center, const Vector3& halfExtents, const Vector3& direction, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a sphere geometry. + /// + /// The sphere center. + /// The radius of the sphere. + /// The normalized direction in which cast a sphere. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if sphere hits an matching object, otherwise false. + API_FUNCTION() bool SphereCast(const Vector3& center, float radius, const Vector3& direction, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a sphere geometry. + /// + /// The sphere center. + /// The radius of the sphere. + /// The normalized direction in which cast a sphere. + /// The result hit information. Valid only when method returns true. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if sphere hits an matching object, otherwise false. + 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); + + /// + /// Performs a sweep test against objects in the scene using a sphere geometry. + /// + /// The sphere center. + /// The radius of the sphere. + /// The normalized direction in which cast a sphere. + /// The result hits. Valid only when method returns true. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if sphere hits an matching object, otherwise false. + API_FUNCTION() bool SphereCastAll(const Vector3& center, float radius, const Vector3& direction, API_PARAM(Out) Array& results, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a capsule geometry. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The normalized direction in which cast a capsule. + /// The capsule rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule hits an matching object, otherwise false. + 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); + + /// + /// Performs a sweep test against objects in the scene using a capsule geometry. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The normalized direction in which cast a capsule. + /// The result hit information. Valid only when method returns true. + /// The capsule rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule hits an matching object, otherwise false. + 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); + + /// + /// Performs a sweep test against objects in the scene using a capsule geometry. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The normalized direction in which cast a capsule. + /// The result hits. Valid only when method returns true. + /// The capsule rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule hits an matching object, otherwise false. + API_FUNCTION() bool CapsuleCastAll(const Vector3& center, float radius, float height, const Vector3& direction, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Performs a sweep test against objects in the scene using a convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The normalized direction in which cast a convex mesh. + /// The convex mesh rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh hits an matching object, otherwise false. + 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); + + /// + /// Performs a sweep test against objects in the scene using a convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The normalized direction in which cast a convex mesh. + /// The result hit information. Valid only when method returns true. + /// The convex mesh rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh hits an matching object, otherwise false. + 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); + + /// + /// Performs a sweep test against objects in the scene using a convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The normalized direction in which cast a convex mesh. + /// The result hits. Valid only when method returns true. + /// The convex mesh rotation. + /// The maximum distance the ray should check for collisions. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh hits an matching object, otherwise false. + API_FUNCTION() bool ConvexCastAll(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, const Vector3& direction, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, float maxDistance = MAX_float, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Checks whether the given box overlaps with other colliders or not. + /// + /// The box center. + /// The half size of the box in each direction. + /// The box rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if box overlaps any matching object, otherwise false. + API_FUNCTION() bool CheckBox(const Vector3& center, const Vector3& halfExtents, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Checks whether the given sphere overlaps with other colliders or not. + /// + /// The sphere center. + /// The radius of the sphere. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if sphere overlaps any matching object, otherwise false. + API_FUNCTION() bool CheckSphere(const Vector3& center, float radius, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Checks whether the given capsule overlaps with other colliders or not. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The capsule rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule overlaps any matching object, otherwise false. + API_FUNCTION() bool CheckCapsule(const Vector3& center, float radius, float height, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Checks whether the given convex mesh overlaps with other colliders or not. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The convex mesh rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh overlaps any matching object, otherwise false. + 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); + + /// + /// Finds all colliders touching or inside of the given box. + /// + /// The box center. + /// The half size of the box in each direction. + /// The box rotation. + /// The result colliders that overlap with the given box. Valid only when method returns true. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if box overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given sphere. + /// + /// The sphere center. + /// The radius of the sphere. + /// The result colliders that overlap with the given sphere. Valid only when method returns true. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if sphere overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given capsule. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The result colliders that overlap with the given capsule. Valid only when method returns true. + /// The capsule rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The result colliders that overlap with the given convex mesh. Valid only when method returns true. + /// The convex mesh rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given box. + /// + /// The box center. + /// The half size of the box in each direction. + /// The box rotation. + /// The result colliders that overlap with the given box. Valid only when method returns true. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if box overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapBox(const Vector3& center, const Vector3& halfExtents, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given sphere. + /// + /// The sphere center. + /// The radius of the sphere. + /// The result colliders that overlap with the given sphere. Valid only when method returns true. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if sphere overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapSphere(const Vector3& center, float radius, API_PARAM(Out) Array& results, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given capsule. + /// + /// The capsule center. + /// The radius of the capsule. + /// The height of the capsule, excluding the top and bottom spheres. + /// The result colliders that overlap with the given capsule. Valid only when method returns true. + /// The capsule rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if capsule overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapCapsule(const Vector3& center, float radius, float height, API_PARAM(Out) Array& results, const Quaternion& rotation = Quaternion::Identity, uint32 layerMask = MAX_uint32, bool hitTriggers = true); + + /// + /// Finds all colliders touching or inside of the given convex mesh. + /// + /// The convex mesh center. + /// Collision data of the convex mesh. + /// The scale of the convex mesh. + /// The result colliders that overlap with the given convex mesh. Valid only when method returns true. + /// The convex mesh rotation. + /// The layer mask used to filter the results. + /// If set to true triggers will be hit, otherwise will skip them. + /// True if convex mesh overlaps any matching object, otherwise false. + API_FUNCTION() bool OverlapConvex(const Vector3& center, const CollisionData* convexMesh, const Vector3& scale, API_PARAM(Out) Array& 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; + void* mScratchMemory = nullptr; + FixedStepper* mStepper = nullptr; + float mLastDeltaTime = 0.0f; + bool mIsDuringSimulation = false; + + PhysicsScenePhysX* mPhysxImpl; +}; diff --git a/Source/Engine/Physics/PhysicsStepper.h b/Source/Engine/Physics/PhysicsStepper.h index 4bc1ed536..7e394fdcf 100644 --- a/Source/Engine/Physics/PhysicsStepper.h +++ b/Source/Engine/Physics/PhysicsStepper.h @@ -3,7 +3,6 @@ #pragma once #include "Types.h" -#include "Engine/Core/Core.h" #include #include #include diff --git a/Source/Engine/Terrain/Terrain.cpp b/Source/Engine/Terrain/Terrain.cpp index 5e1f4da77..c09e20609 100644 --- a/Source/Engine/Terrain/Terrain.cpp +++ b/Source/Engine/Terrain/Terrain.cpp @@ -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(); diff --git a/Source/Engine/Terrain/Terrain.h b/Source/Engine/Terrain/Terrain.h index 329fcf4ab..a305feebe 100644 --- a/Source/Engine/Terrain/Terrain.h +++ b/Source/Engine/Terrain/Terrain.h @@ -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; }; diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp index 89bcb9123..82b379d39 100644 --- a/Source/Engine/Terrain/TerrainPatch.cpp +++ b/Source/Engine/Terrain/TerrainPatch.cpp @@ -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); +} diff --git a/Source/Engine/Terrain/TerrainPatch.h b/Source/Engine/Terrain/TerrainPatch.h index c98436c5f..7e27220ca 100644 --- a/Source/Engine/Terrain/TerrainPatch.h +++ b/Source/Engine/Terrain/TerrainPatch.h @@ -411,6 +411,7 @@ private: /// True if failed, otherwise false. bool UpdateCollision(); + void OnPhysicsSceneChanged(PhysicsScene* previous); public: // [ISerializable]