diff --git a/MagicPhysX-Test.csproj b/MagicPhysX-Test.csproj
new file mode 100644
index 0000000..63290ed
--- /dev/null
+++ b/MagicPhysX-Test.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ net7.0
+ MagicPhysX_Test
+ enable
+ enable
+ true
+ true
+
+
+
+
+
+
+
diff --git a/MagicPhysX-Test.sln b/MagicPhysX-Test.sln
new file mode 100644
index 0000000..25aa9af
--- /dev/null
+++ b/MagicPhysX-Test.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34031.279
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MagicPhysX-Test", "MagicPhysX-Test.csproj", "{4B4B5682-0E3B-4A7E-8687-58DCF5D3CCC4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4B4B5682-0E3B-4A7E-8687-58DCF5D3CCC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B4B5682-0E3B-4A7E-8687-58DCF5D3CCC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B4B5682-0E3B-4A7E-8687-58DCF5D3CCC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B4B5682-0E3B-4A7E-8687-58DCF5D3CCC4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {68E3A554-F7A0-4F36-97E2-6D312CC7BC5A}
+ EndGlobalSection
+EndGlobal
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..b3a7cd2
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,226 @@
+using MagicPhysX;
+using MagicPhysX.Toolkit;
+using static MagicPhysX.NativeMethods;
+using System.Numerics;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX_Test
+{
+ internal class Program
+ {
+ static Physics physics = new Physics();
+ static Thread? thread;
+ static bool cleaning = false;
+
+ static void Main(string[] args)
+ {
+ physics.Initialize();
+
+ Console.WriteLine("Hello, World!");
+
+ thread = new Thread(Update);
+ thread.Start();
+
+ while (true)
+ {
+ string? command = Console.ReadLine();
+
+ if (!string.IsNullOrEmpty(command))
+ {
+ {
+ cleaning = true;
+ thread.Interrupt();
+ thread.Join();
+ physics.Cleanup();
+
+ return;
+ }
+ }
+ }
+ }
+
+ static void Update()
+ {
+ while (!cleaning)
+ physics.Update();
+ }
+
+ public static void Print(string message)
+ {
+ Console.WriteLine(message);
+ }
+ }
+
+ public unsafe class Physics
+ {
+ Stopwatch stopwatch = new Stopwatch();
+ Stopwatch fpsStopwatch = new Stopwatch();
+
+ Random random = new Random();
+
+ PxFoundation* foundation;
+ PxPhysics* physics;
+ PxDefaultCpuDispatcher* dispatcher;
+ PxScene* scene;
+ PxMaterial* material;
+
+ int fps = 0;
+
+ public void Initialize()
+ {
+
+ foundation = physx_create_foundation();
+
+ PxPvd* pvd = phys_PxCreatePvd(foundation);
+
+ fixed (byte* bytePointer = "127.0.0.1"u8.ToArray())
+ {
+ var transport = phys_PxDefaultPvdSocketTransportCreate(bytePointer, 5425, 10);
+ pvd->ConnectMut(transport, PxPvdInstrumentationFlags.All);
+ }
+
+ uint PX_PHYSICS_VERSION_MAJOR = 5;
+ uint PX_PHYSICS_VERSION_MINOR = 1;
+ uint PX_PHYSICS_VERSION_BUGFIX = 3;
+ uint versionNumber = (PX_PHYSICS_VERSION_MAJOR << 24) + (PX_PHYSICS_VERSION_MINOR << 16) + (PX_PHYSICS_VERSION_BUGFIX << 8);
+
+ var tolerancesScale = new PxTolerancesScale { length = 1, speed = 10 };
+
+ physics = phys_PxCreatePhysics(versionNumber, foundation, &tolerancesScale, true, pvd, null);
+ phys_PxInitExtensions(physics, pvd);
+
+ PxSceneDesc sceneDesc = PxSceneDesc_new(PxPhysics_getTolerancesScale(physics));
+ sceneDesc.gravity = new PxVec3 { x = 0f, y = -9.81f, z = 0f };
+
+ dispatcher = phys_PxDefaultCpuDispatcherCreate(1, null, PxDefaultCpuDispatcherWaitForWorkMode.WaitForWork, 0);
+ sceneDesc.cpuDispatcher = (PxCpuDispatcher*)dispatcher;
+ sceneDesc.filterShader = get_default_simulation_filter_shader();
+
+ scene = physics->CreateSceneMut(&sceneDesc);
+ material = physics->CreateMaterialMut(0.5f, 0.5f, 0.6f);
+
+ scene->GetScenePvdClientMut()->SetScenePvdFlagMut(PxPvdSceneFlag.TransmitScenequeries, true);
+
+ var pvdClient = scene->GetScenePvdClientMut();
+ if (pvdClient != null)
+ {
+ pvdClient->SetScenePvdFlagMut(PxPvdSceneFlag.TransmitConstraints, true);
+ pvdClient->SetScenePvdFlagMut(PxPvdSceneFlag.TransmitContacts, true);
+ pvdClient->SetScenePvdFlagMut(PxPvdSceneFlag.TransmitScenequeries, true);
+ }
+
+ PxPlane plane = PxPlane_new_1(0f, 1f, 0f, 0f);
+ PxRigidStatic* groundPlane = physics->PhysPxCreatePlane(&plane, material);
+ scene->AddActorMut((PxActor*)groundPlane, null);
+
+ PxSphereGeometry sphereGeo = PxSphereGeometry_new(0.4f);
+ PxVec3 zero = new PxVec3 { x = 0f, y = 0f, z = 0f };
+ PxTransform shapeOffset = PxTransform_new_1(&zero);
+
+ for (int i = 0; i < 100; i++)
+ {
+ //PxVec3 vec3 = new PxVec3 { x = random.NextSingle() * 10f, y = random.NextSingle() * 10f, z = random.NextSingle() * 10f };
+ PxVec3 vec3 = new PxVec3 { x = 0f, y = 1f, z = -50 + i * 1f };
+ PxTransform transform = PxTransform_new_1(&vec3);
+ PxTransform identity = PxTransform_new_2(PxIDENTITY.PxIdentity);
+ PxRigidStatic* sphere = physics->PhysPxCreateStatic(&transform, (PxGeometry*)&sphereGeo, material, &shapeOffset);
+ //PxRigidBody_setAngularDamping_mut((PxRigidBody*)sphere, 0.5f);
+ scene->AddActorMut((PxActor*)sphere, null);
+ }
+
+ for (int i = 0; i < 10; i++)
+ {
+ PxVec3 vec3 = new PxVec3 { x = random.NextSingle() * 10f, y = random.NextSingle() * 10f, z = random.NextSingle() * 10f };
+ //PxVec3 vec3 = new PxVec3 { x = 0f, y = 1f, z = -50 + i * 1f };
+ PxTransform transform = PxTransform_new_1(&vec3);
+ PxTransform identity = PxTransform_new_2(PxIDENTITY.PxIdentity);
+ //PxRigidStatic* sphere = physics->PhysPxCreateStatic(&transform, (PxGeometry*)&sphereGeo, material, &shapeOffset);
+ //PxRigidBody_setAngularDamping_mut((PxRigidBody*)sphere, 0.5f);
+ PxRigidDynamic* sphere = physics->PhysPxCreateDynamic(&transform, (PxGeometry*)&sphereGeo, material, 10f, &shapeOffset);
+ scene->AddActorMut((PxActor*)sphere, null);
+ }
+
+ stopwatch.Start();
+ fpsStopwatch.Start();
+ }
+
+ public void Cleanup()
+ {
+ PxScene_release_mut(scene);
+ PxDefaultCpuDispatcher_release_mut(dispatcher);
+ PxPhysics_release_mut(physics);
+ }
+
+ public void Update()
+ {
+ fps++;
+
+ if (fpsStopwatch.Elapsed.TotalSeconds >= 1d)
+ {
+ Program.Print($"FPS : {fps}");
+
+ fps = 0;
+ fpsStopwatch.Restart();
+
+ RaycastTest();
+ }
+
+ if (stopwatch.Elapsed.TotalSeconds < 0.02d)
+ return;
+
+ float delta = (float)stopwatch.Elapsed.TotalSeconds;
+ stopwatch.Restart();
+
+ scene->SimulateMut(delta, null, null, 0, true);
+ uint error = 0;
+ scene->FetchResultsMut(true, &error);
+ }
+
+ public void RaycastTest()
+ {
+ //PxVec3 origin = new PxVec3 { x = random.NextSingle() * 10f, y = random.NextSingle() * 10f, z = random.NextSingle() * 10f };
+ //PxVec3 direction = new PxVec3 { x = random.NextSingle() * 10f, y = random.NextSingle() * 10f, z = random.NextSingle() * 10f };
+ //PxVec3 origin = new PxVec3 { x = 0f, y = 1f, z = -55f };
+ PxVec3 origin = new PxVec3 { x = -1f, y = 1f, z = 0f };
+ PxVec3 direction = new PxVec3 { x = random.NextSingle(), y = 0f, z = random.NextSingle() };
+ //PxVec3 direction = new PxVec3 { x = 0f, y = 0f, z = 1f };
+ direction = direction.GetNormalized();
+
+ PxHitFlags outputFlags1 = PxHitFlags.Position;
+ PxQueryFilterData filterData1 = PxQueryFilterData_new();
+ //filterData1.flags = PxQueryFlags.NoBlock;
+
+ PxRaycastHit[] hitInfo = new PxRaycastHit[128];
+ bool block = true;
+ void* blockPtr = █
+
+ fixed (PxRaycastHit* hitInfoPtr = &hitInfo[0])
+ {
+ int result1 = scene->QueryExtRaycastMultiple((PxVec3*)Unsafe.AsPointer(ref origin), (PxVec3*)Unsafe.AsPointer(ref direction), 128f, outputFlags1, hitInfoPtr, 128u, (bool*)blockPtr, &filterData1, null, null);
+ //bool result1 = scene->QueryExtRaycastSingle((PxVec3*)Unsafe.AsPointer(ref origin), (PxVec3*)Unsafe.AsPointer(ref direction), 100f, outputFlags1, &hitInfo, &filterData1, null, null);
+ Program.Print($"Raycast result1: {result1}");
+ Program.Print($"Block: {block}");
+
+ for (int i = 0; i < result1; i++)
+ {
+ PxRaycastHit hit = hitInfo[i];
+ Program.Print($"[{i}] hit.position=x:{hit.position.x}\ty:{hit.position.y}\tz:{hit.position.z}");
+
+ if (i > 0)
+ {
+ PxRaycastHit prvHit = hitInfo[i - 1];
+
+ if (prvHit.position.x == hit.position.x && prvHit.position.y == hit.position.y && prvHit.position.z == hit.position.z)
+ {
+ Program.Print("");
+ Program.Print($"Same Position !!!!!!!!!!!!!!!!!!!");
+ Program.Print("");
+ Thread.Sleep(5000);
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Toolkit/ColliderType.cs b/Toolkit/ColliderType.cs
new file mode 100644
index 0000000..d01f6a2
--- /dev/null
+++ b/Toolkit/ColliderType.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MagicPhysX.Toolkit;
+
+public enum ColliderType
+{
+ Box,
+ Capsule,
+ //CharacterController,
+ //Mesh,
+ Sphere,
+ Plane
+}
diff --git a/Toolkit/Colliders/BoxCollider.cs b/Toolkit/Colliders/BoxCollider.cs
new file mode 100644
index 0000000..11a2c75
--- /dev/null
+++ b/Toolkit/Colliders/BoxCollider.cs
@@ -0,0 +1,40 @@
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX.Toolkit.Colliders
+{
+ ///
+ /// A box-shaped primitive collider.
+ ///
+ public unsafe class BoxCollider : Collider
+ {
+ ref PxBoxGeometry GetGeometry() => ref Unsafe.AsRef(shape->GetGeometry());
+
+ internal BoxCollider(PxShape* shape, ColliderType type) : base(shape, type)
+ {
+ }
+
+ ///
+ /// The center of the box, measured in the object's local space.
+ ///
+ public Vector3 center
+ {
+ get => shape->GetLocalPose().p;
+ set
+ {
+ var pose = shape->GetLocalPose();
+ pose.p = value;
+ shape->SetLocalPoseMut(&pose);
+ }
+ }
+
+ ///
+ /// The size of the box, measured in the object's local space.
+ ///
+ public Vector3 size
+ {
+ get => GetGeometry().halfExtents;
+ set => GetGeometry().halfExtents = value;
+ }
+ }
+}
diff --git a/Toolkit/Colliders/CapsuleCollider.cs b/Toolkit/Colliders/CapsuleCollider.cs
new file mode 100644
index 0000000..e4ebd5a
--- /dev/null
+++ b/Toolkit/Colliders/CapsuleCollider.cs
@@ -0,0 +1,60 @@
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX.Toolkit.Colliders
+{
+ ///
+ /// A capsule-shaped primitive collider.
+ ///
+ public unsafe class CapsuleCollider : Collider
+ {
+ ref PxCapsuleGeometry GetGeometry() => ref Unsafe.AsRef(shape->GetGeometry());
+
+ internal CapsuleCollider(PxShape* shape, ColliderType type) : base(shape, type)
+ {
+ }
+
+ ///
+ /// The center of the capsule, measured in the object's local space.
+ ///
+ public Vector3 center
+ {
+ get => shape->GetLocalPose().p;
+ set
+ {
+ var pose = shape->GetLocalPose();
+ pose.p = value;
+ shape->SetLocalPoseMut(&pose);
+ }
+ }
+
+ ///
+ /// The radius of the sphere, measured in the object's local space.
+ ///
+ public float radius
+ {
+ get => GetGeometry().radius;
+ set => GetGeometry().radius = value;
+ }
+
+ ///
+ /// The height of the capsule measured in the object's local space.
+ ///
+ public float height
+ {
+ get => GetGeometry().halfHeight * 2f;
+ set => GetGeometry().halfHeight = value / 2f;
+ }
+
+ /////
+ ///// The direction of the capsule.
+ /////
+ //public int direction
+ //{
+ // // TODO:
+ // get => throw new NotImplementedException();
+ // // TODO:
+ // set => throw new NotImplementedException();
+ //}
+ }
+}
diff --git a/Toolkit/Colliders/Collider.cs b/Toolkit/Colliders/Collider.cs
new file mode 100644
index 0000000..02d2ca5
--- /dev/null
+++ b/Toolkit/Colliders/Collider.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX.Toolkit.Colliders
+{
+ ///
+ /// A base class of all colliders.
+ ///
+ public unsafe abstract class Collider
+ {
+ internal PxShape* shape;
+ readonly ColliderType type;
+
+ internal Collider(PxShape* shape, ColliderType type)
+ {
+ this.shape = shape;
+ this.type = type;
+ }
+
+ public PxShape* GetShapeHandler() => shape;
+
+ public static Collider Create(PxShape* shape, ColliderType type)
+ {
+ switch (type)
+ {
+ case ColliderType.Box:
+ return new BoxCollider(shape, type);
+ case ColliderType.Capsule:
+ return new CapsuleCollider(shape, type);
+ case ColliderType.Sphere:
+ return new SphereCollider(shape, type);
+ case ColliderType.Plane:
+ return new PlaneCollider(shape, type);
+ default:
+ throw new ArgumentException();
+ }
+ }
+
+ public ColliderType Type => type;
+ }
+}
diff --git a/Toolkit/Colliders/PlaneCollider.cs b/Toolkit/Colliders/PlaneCollider.cs
new file mode 100644
index 0000000..3f2ba82
--- /dev/null
+++ b/Toolkit/Colliders/PlaneCollider.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX.Toolkit.Colliders
+{
+ // PlaneCollider is not exists in Unity
+
+ public unsafe class PlaneCollider : Collider
+ {
+ ref PxPlaneGeometry GetGeometry() => ref Unsafe.AsRef(shape->GetGeometry());
+
+ internal PlaneCollider(PxShape* shape, ColliderType type) : base(shape, type)
+ {
+ }
+
+ ///
+ /// The center of the plane in the object's local space.
+ ///
+ public Vector3 center
+ {
+ get => shape->GetLocalPose().p;
+ set
+ {
+ var pose = shape->GetLocalPose();
+ pose.p = value;
+ shape->SetLocalPoseMut(&pose);
+ }
+ }
+ }
+}
diff --git a/Toolkit/Colliders/SphereCollider.cs b/Toolkit/Colliders/SphereCollider.cs
new file mode 100644
index 0000000..e7a664a
--- /dev/null
+++ b/Toolkit/Colliders/SphereCollider.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX.Toolkit.Colliders
+{
+ ///
+ /// A sphere-shaped primitive collider.
+ ///
+ public unsafe class SphereCollider : Collider
+ {
+ ref PxSphereGeometry GetGeometry() => ref Unsafe.AsRef(shape->GetGeometry());
+
+ internal SphereCollider(PxShape* shape, ColliderType type) : base(shape, type)
+ {
+ }
+
+ ///
+ /// The center of the sphere in the object's local space.
+ ///
+ public Vector3 center
+ {
+ get => shape->GetLocalPose().p;
+ set
+ {
+ var pose = shape->GetLocalPose();
+ pose.p = value;
+ shape->SetLocalPoseMut(&pose);
+ }
+ }
+
+ ///
+ /// The radius of the sphere measured in the object's local space.
+ ///
+ public float radius
+ {
+ get => GetGeometry().radius;
+ set => GetGeometry().radius = value;
+ }
+ }
+}
diff --git a/Toolkit/CollisionDetectionMode.cs b/Toolkit/CollisionDetectionMode.cs
new file mode 100644
index 0000000..78bd3b2
--- /dev/null
+++ b/Toolkit/CollisionDetectionMode.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace MagicPhysX.Toolkit
+{
+ ///
+ /// The collision detection mode constants used for Rigidbody.collisionDetectionMode.
+ ///
+ public enum CollisionDetectionMode : int
+ {
+ ///
+ /// Continuous collision detection is off for this Rigidbody.
+ ///
+ Discrete = 0,
+ ///
+ /// Continuous collision detection is on for colliding with static mesh geometry.
+ ///
+ Continuous = 1,
+ ///
+ /// Continuous collision detection is on for colliding with static and dynamic geometry.
+ ///
+ ContinuousDynamic = 2,
+ ///
+ /// Speculative continuous collision detection is on for static and dynamic geometries
+ ///
+ ContinuousSpeculative = 3,
+ }
+}
diff --git a/Toolkit/ForceMode.cs b/Toolkit/ForceMode.cs
new file mode 100644
index 0000000..98a0e11
--- /dev/null
+++ b/Toolkit/ForceMode.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace MagicPhysX.Toolkit
+{
+ ///
+ /// Use ForceMode to specify how to apply a force using Rigidbody.AddForce or ArticulationBody.AddForce.
+ ///
+ public enum ForceMode : int
+ {
+ ///
+ /// Add a continuous force to the rigidbody, using its mass.
+ ///
+ Force = 0,
+ ///
+ /// Add an instant force impulse to the rigidbody, using its mass.
+ ///
+ Impulse = 1,
+ ///
+ /// Add an instant velocity change to the rigidbody, ignoring its mass.
+ ///
+ VelocityChange = 2,
+ ///
+ /// Add a continuous acceleration to the rigidbody, ignoring its mass.
+ ///
+ Acceleration = 5,
+ }
+}
diff --git a/Toolkit/Internal/Extensions.cs b/Toolkit/Internal/Extensions.cs
new file mode 100644
index 0000000..9c90eca
--- /dev/null
+++ b/Toolkit/Internal/Extensions.cs
@@ -0,0 +1,9 @@
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX.Toolkit.Internal;
+
+internal static class Extensions
+{
+ internal static unsafe PxVec3* AsPxPointer(this Vector3 v) => (PxVec3*)Unsafe.AsPointer(ref Unsafe.AsRef(v));
+}
diff --git a/Toolkit/Internal/PhysicsFoundation.cs b/Toolkit/Internal/PhysicsFoundation.cs
new file mode 100644
index 0000000..9c0cb2a
--- /dev/null
+++ b/Toolkit/Internal/PhysicsFoundation.cs
@@ -0,0 +1,35 @@
+using static MagicPhysX.NativeMethods;
+
+namespace MagicPhysX.Toolkit.Internal;
+
+public static unsafe class PhysicsFoundation
+{
+ static readonly object gate = new object();
+ static PxFoundation* staticFoundation;
+
+ public static PxFoundation* GetFoundation()
+ {
+ lock (gate)
+ {
+ if (staticFoundation == null)
+ {
+ staticFoundation = physx_create_foundation();
+ }
+ return staticFoundation;
+ }
+ }
+
+ // foundation always be single instance per application
+ // sometimes doesn't match app-lifetime and native-lifetime(for example, Unity Editor)
+ // you can release foundation manually but be careful to use
+ public static void ReleaseFoundtaion()
+ {
+ lock (gate)
+ {
+ if (staticFoundation != null)
+ {
+ PxFoundation_release_mut(staticFoundation);
+ }
+ }
+ }
+}
diff --git a/Toolkit/Internal/PxRigidDynamicLockFlagsExtensions.cs b/Toolkit/Internal/PxRigidDynamicLockFlagsExtensions.cs
new file mode 100644
index 0000000..199255b
--- /dev/null
+++ b/Toolkit/Internal/PxRigidDynamicLockFlagsExtensions.cs
@@ -0,0 +1,42 @@
+using MagicPhysX;
+
+namespace MagicPhysX.Toolkit.Internal;
+
+internal static class PxRigidDynamicLockFlagsExtensions
+{
+ public static RigidbodyConstraints AsRigidbodyConstraints(this PxRigidDynamicLockFlags flags)
+ {
+ var result = RigidbodyConstraints.None;
+ if ((flags & PxRigidDynamicLockFlags.LockLinearX) == PxRigidDynamicLockFlags.LockLinearX)
+ {
+ result |= RigidbodyConstraints.FreezePositionX;
+ }
+
+ if ((flags & PxRigidDynamicLockFlags.LockLinearY) == PxRigidDynamicLockFlags.LockLinearY)
+ {
+ result |= RigidbodyConstraints.FreezePositionY;
+ }
+
+ if ((flags & PxRigidDynamicLockFlags.LockLinearZ) == PxRigidDynamicLockFlags.LockLinearZ)
+ {
+ result |= RigidbodyConstraints.FreezePositionZ;
+ }
+
+ if ((flags & PxRigidDynamicLockFlags.LockAngularX) == PxRigidDynamicLockFlags.LockAngularX)
+ {
+ result |= RigidbodyConstraints.FreezeRotationX;
+ }
+
+ if ((flags & PxRigidDynamicLockFlags.LockAngularY) == PxRigidDynamicLockFlags.LockAngularY)
+ {
+ result |= RigidbodyConstraints.FreezeRotationY;
+ }
+
+ if ((flags & PxRigidDynamicLockFlags.LockAngularZ) == PxRigidDynamicLockFlags.LockAngularZ)
+ {
+ result |= RigidbodyConstraints.FreezeRotationZ;
+ }
+
+ return result;
+ }
+}
diff --git a/Toolkit/Internal/RigidbodyConstraintsExtensions.cs b/Toolkit/Internal/RigidbodyConstraintsExtensions.cs
new file mode 100644
index 0000000..9d56df6
--- /dev/null
+++ b/Toolkit/Internal/RigidbodyConstraintsExtensions.cs
@@ -0,0 +1,42 @@
+using MagicPhysX;
+
+namespace MagicPhysX.Toolkit.Internal;
+
+internal static class RigidbodyConstraintsExtensions
+{
+ public static PxRigidDynamicLockFlags AsPxRigidDynamicLockFlags(this RigidbodyConstraints constraints)
+ {
+ PxRigidDynamicLockFlags result = 0;
+ if ((constraints & RigidbodyConstraints.FreezePositionX) == RigidbodyConstraints.FreezePositionX)
+ {
+ result |= PxRigidDynamicLockFlags.LockLinearX;
+ }
+
+ if ((constraints & RigidbodyConstraints.FreezePositionY) == RigidbodyConstraints.FreezePositionY)
+ {
+ result |= PxRigidDynamicLockFlags.LockLinearY;
+ }
+
+ if ((constraints & RigidbodyConstraints.FreezePositionZ) == RigidbodyConstraints.FreezePositionZ)
+ {
+ result |= PxRigidDynamicLockFlags.LockLinearZ;
+ }
+
+ if ((constraints & RigidbodyConstraints.FreezeRotationX) == RigidbodyConstraints.FreezeRotationX)
+ {
+ result |= PxRigidDynamicLockFlags.LockAngularX;
+ }
+
+ if ((constraints & RigidbodyConstraints.FreezeRotationY) == RigidbodyConstraints.FreezeRotationY)
+ {
+ result |= PxRigidDynamicLockFlags.LockAngularY;
+ }
+
+ if ((constraints & RigidbodyConstraints.FreezeRotationZ) == RigidbodyConstraints.FreezeRotationZ)
+ {
+ result |= PxRigidDynamicLockFlags.LockAngularZ;
+ }
+
+ return result;
+ }
+}
diff --git a/Toolkit/Internal/UnorderedKeyedCollection.cs b/Toolkit/Internal/UnorderedKeyedCollection.cs
new file mode 100644
index 0000000..66b7bad
--- /dev/null
+++ b/Toolkit/Internal/UnorderedKeyedCollection.cs
@@ -0,0 +1,59 @@
+using System.Runtime.InteropServices;
+
+namespace MagicPhysX.Toolkit.Internal;
+
+internal sealed class UnorderedKeyedCollection
+ where T : class
+{
+ readonly Dictionary dict; // Value is index of list.
+ readonly List list;
+
+ public UnorderedKeyedCollection()
+ : this(4)
+ {
+ }
+
+ public UnorderedKeyedCollection(int capacity)
+ {
+ dict = new Dictionary(capacity, ReferenceEqualityComparer.Instance);
+ list = new List(capacity);
+ }
+
+ public int Count => list.Count;
+
+ public void Add(T value)
+ {
+ var index = list.Count;
+ dict.Add(value, index);
+ list.Add(value);
+ }
+
+ public void Clear()
+ {
+ dict.Clear();
+ list.Clear();
+ }
+
+ public void Remove(T value)
+ {
+ if (dict.Remove(value, out var index))
+ {
+ // swap remove and update new-index
+ var span = CollectionsMarshal.AsSpan(list);
+ (span[index], span[span.Length - 1]) = (span[span.Length - 1], span[index]);
+
+ dict[span[index]] = index;
+ list.RemoveAt(list.Count - 1);
+ }
+ }
+
+ public ReadOnlySpan AsSpan()
+ {
+ return CollectionsMarshal.AsSpan(list);
+ }
+
+ public T[] ToArray()
+ {
+ return list.ToArray();
+ }
+}
diff --git a/Toolkit/PhysicsScene.RigidActorFactory.cs b/Toolkit/PhysicsScene.RigidActorFactory.cs
new file mode 100644
index 0000000..b364379
--- /dev/null
+++ b/Toolkit/PhysicsScene.RigidActorFactory.cs
@@ -0,0 +1,63 @@
+using MagicPhysX;
+using System.Numerics;
+using static MagicPhysX.NativeMethods;
+
+namespace MagicPhysX.Toolkit;
+
+public sealed unsafe partial class PhysicsScene
+{
+ public Rigidbody AddDynamicSphere(float radius, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = PxSphereGeometry_new(radius);
+ return AddDynamicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.Sphere);
+ }
+
+ public Rigidbody AddKinematicSphere(float radius, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = PxSphereGeometry_new(radius);
+ return AddKinematicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.Sphere);
+ }
+
+ public Rigidstatic AddStaticSphere(float radius, Vector3 position, Quaternion rotation, PxMaterial* material = null)
+ {
+ var geometry = PxSphereGeometry_new(radius);
+ return AddStaticGeometry((PxGeometry*)&geometry, position, rotation, material, ColliderType.Sphere);
+ }
+
+ public Rigidbody AddDynamicBox(Vector3 halfExtent, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = PxBoxGeometry_new_1(halfExtent);
+ return AddDynamicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.Box);
+ }
+
+ public Rigidbody AddKinematicBox(Vector3 halfExtent, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = PxBoxGeometry_new_1(halfExtent);
+ return AddKinematicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.Box);
+ }
+
+ public Rigidstatic AddStaticBox(Vector3 halfExtent, Vector3 position, Quaternion rotation, PxMaterial* material = null)
+ {
+ var geometry = PxBoxGeometry_new_1(halfExtent);
+ return AddStaticGeometry((PxGeometry*)&geometry, position, rotation, material, ColliderType.Box);
+ }
+
+ public Rigidbody AddDynamicCapsule(float radius, float halfHeight, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = PxCapsuleGeometry_new(radius, halfHeight);
+ return AddDynamicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.Capsule);
+ }
+
+ public Rigidbody AddKinematicCapsule(float radius, float halfHeight, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = PxCapsuleGeometry_new(radius, halfHeight);
+ return AddKinematicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.Capsule);
+ }
+
+ public Rigidstatic AddStaticCapsule(float radius, float halfHeight, Vector3 position, Quaternion rotation, PxMaterial* material = null)
+ {
+ var geometry = PxCapsuleGeometry_new(radius, halfHeight);
+ return AddStaticGeometry((PxGeometry*)&geometry, position, rotation, material, ColliderType.Capsule);
+ }
+
+}
diff --git a/Toolkit/PhysicsScene.RigidActorFactory.tt b/Toolkit/PhysicsScene.RigidActorFactory.tt
new file mode 100644
index 0000000..da8b4ee
--- /dev/null
+++ b/Toolkit/PhysicsScene.RigidActorFactory.tt
@@ -0,0 +1,43 @@
+<#@ template debug="false" hostspecific="false" language="C#" #>
+<#@ assembly name="System.Core" #>
+<#@ import namespace="System.Linq" #>
+<#@ import namespace="System.Text" #>
+<#@ import namespace="System.Collections.Generic" #>
+<#@ output extension=".cs" #>
+<#
+ var bodyTypes = new[]{
+ new { Name = "Sphere", New = "new", Parameters = new[] { ("float", "radius") } },
+ new { Name = "Box", New = "new_1", Parameters = new[] { ("Vector3", "halfExtent") } },
+ new { Name = "Capsule", New = "new", Parameters = new[] { ("float", "radius"), ("float", "halfHeight") } },
+ };
+ var joinParameter1 = ((string, string)[] xs) => string.Join(", ", xs.Select(x => x.Item1 + " " + x.Item2));
+ var joinParameter2 = ((string, string)[] xs) => string.Join(", ", xs.Select(x => x.Item2));
+#>
+using MagicPhysX;
+using static MagicPhysX.NativeMethods;
+
+namespace MagicPhysX.Toolkit;
+
+public sealed unsafe partial class PhysicsScene
+{
+<# foreach(var item in bodyTypes) { #>
+ public Rigidbody AddDynamic<#= item.Name #>(<#= joinParameter1(item.Parameters) #>, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = Px<#= item.Name #>Geometry_<#= item.New #>(<#= joinParameter2(item.Parameters) #>);
+ return AddDynamicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.<#= item.Name #>);
+ }
+
+ public Rigidbody AddKinematic<#= item.Name #>(<#= joinParameter1(item.Parameters) #>, Vector3 position, Quaternion rotation, float density, PxMaterial* material = null)
+ {
+ var geometry = Px<#= item.Name #>Geometry_<#= item.New #>(<#= joinParameter2(item.Parameters) #>);
+ return AddKinematicGeometry((PxGeometry*)&geometry, position, rotation, density, material, ColliderType.<#= item.Name #>);
+ }
+
+ public Rigidstatic AddStatic<#= item.Name #>(<#= joinParameter1(item.Parameters) #>, Vector3 position, Quaternion rotation, PxMaterial* material = null)
+ {
+ var geometry = Px<#= item.Name #>Geometry_<#= item.New #>(<#= joinParameter2(item.Parameters) #>);
+ return AddStaticGeometry((PxGeometry*)&geometry, position, rotation, material, ColliderType.<#= item.Name #>);
+ }
+
+<# } #>
+}
diff --git a/Toolkit/PhysicsScene.cs b/Toolkit/PhysicsScene.cs
new file mode 100644
index 0000000..cdae556
--- /dev/null
+++ b/Toolkit/PhysicsScene.cs
@@ -0,0 +1,210 @@
+using MagicPhysX.Toolkit.Internal;
+using MagicPhysX;
+using static MagicPhysX.NativeMethods;
+using System.Numerics;
+using MagicPhysX.Toolkit.Colliders;
+
+namespace MagicPhysX.Toolkit;
+
+public sealed unsafe partial class PhysicsScene : IDisposable
+{
+ PhysicsSystem physicsSystem;
+ PxScene* scene;
+ bool disposedValue;
+ int frameCount;
+
+ UnorderedKeyedCollection activeActors = new UnorderedKeyedCollection();
+
+ public PhysicsScene(PhysicsSystem physicsSystem, PxScene* scene)
+ {
+ this.physicsSystem = physicsSystem;
+ this.scene = scene;
+ this.frameCount = 0;
+
+ // NOTE: set contact manifold
+ // https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/AdvancedCollisionDetection.html#persistent-contact-manifold-pcm
+
+ // NOTE: set broadphase type(see:Broad-phase Algorithms)
+ // https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/RigidBodyCollision.html
+ }
+
+ PxPhysics* physics => physicsSystem.physics;
+ public int FrameCount => frameCount;
+
+ public event Action? Updating;
+ public event Action? Updated;
+ public event Action? Disposing;
+
+ public PhysicsSystem PhysicsSystem => physicsSystem;
+
+ public RigidActor[] GetActiveActors()
+ {
+ lock (activeActors)
+ {
+ return activeActors.ToArray();
+ }
+ }
+
+ public void ForEachActiveActors(Action action)
+ {
+ lock (activeActors)
+ {
+ foreach (var item in activeActors.AsSpan())
+ {
+ action(item);
+ }
+ }
+ }
+
+ public bool TryCopyActiveActors(Span dest)
+ {
+ lock (activeActors)
+ {
+ var span = activeActors.AsSpan();
+ return span.TryCopyTo(dest);
+ }
+ }
+
+ public int GetActiveActorCount()
+ {
+ lock (activeActors)
+ {
+ return activeActors.Count;
+ }
+ }
+
+ public PxMaterial* CreateMaterial(float staticFriction, float dynamicFriction, float restitution)
+ {
+ return physicsSystem.CreateMaterial(staticFriction, @dynamicFriction, restitution);
+ }
+
+ public void Destroy(RigidActor actor)
+ {
+ scene->RemoveActorMut((PxActor*)actor.handler, wakeOnLostTouch: true);
+ actor.handler = null;
+ lock (activeActors)
+ {
+ activeActors.Remove(actor);
+ }
+ }
+
+ public Rigidstatic AddStaticPlane(float x, float y, float z, float distance, Vector3 position, Quaternion rotation, PxMaterial* material = null)
+ {
+ var plane = PxPlane_new_1(x, y, z, distance);
+ var transform = phys_PxTransformFromPlaneEquation(&plane);
+ var geometry = PxPlaneGeometry_new();
+
+ lock (activeActors)
+ {
+ var actor = AddStaticGeometry((PxGeometry*)&geometry, transform.p, transform.q, material, ColliderType.Plane);
+
+ var shape = actor.GetComponent().shape;
+ var shapeOffset = new Transform { position = position, rotation = rotation };
+ shape->SetLocalPoseMut(shapeOffset.AsPxPointer());
+
+ return actor;
+ }
+ }
+
+ Rigidstatic AddStaticGeometry(PxGeometry* geometry, Vector3 position, Quaternion rotation, PxMaterial* material, ColliderType type)
+ {
+ var shape = physics->CreateShapeMut(geometry, material, isExclusive: true, PxShapeFlags.Visualization | PxShapeFlags.SceneQueryShape | PxShapeFlags.SimulationShape);
+
+ var transform = new Transform { position = position, rotation = rotation };
+
+ var rigidStatic = physics->PhysPxCreateStatic1(transform.AsPxPointer(), shape);
+
+ scene->AddActorMut((PxActor*)rigidStatic, bvh: null);
+
+ var collider = Collider.Create(shape, type);
+ var actor = new Rigidstatic(rigidStatic, collider);
+ lock (activeActors)
+ {
+ activeActors.Add(actor);
+ }
+ return actor;
+ }
+
+ Rigidbody AddDynamicGeometry(PxGeometry* geometry, Vector3 position, Quaternion rotation, float density, PxMaterial* material, ColliderType type)
+ {
+ // use default option parameter in C++
+ var shape = physics->CreateShapeMut(geometry, material, isExclusive: false, PxShapeFlags.Visualization | PxShapeFlags.SceneQueryShape | PxShapeFlags.SimulationShape);
+
+ var transform = new Transform { position = position, rotation = rotation };
+ var rigidDynamic = physics->PhysPxCreateDynamic1(transform.AsPxPointer(), shape, density);
+
+ scene->AddActorMut((PxActor*)rigidDynamic, bvh: null);
+
+ var collider = Collider.Create(shape, type);
+ var actor = new Rigidbody(rigidDynamic, collider, scene);
+ lock (activeActors)
+ {
+ activeActors.Add(actor);
+ }
+ return actor;
+ }
+
+ Rigidbody AddKinematicGeometry(PxGeometry* geometry, Vector3 position, Quaternion rotation, float density, PxMaterial* material, ColliderType type)
+ {
+ // use default option parameter in C++
+ var shape = physics->CreateShapeMut(geometry, material, isExclusive: false, PxShapeFlags.Visualization | PxShapeFlags.SceneQueryShape | PxShapeFlags.SimulationShape);
+
+ var transform = new Transform { position = position, rotation = rotation };
+ var rigidDynamic = physics->PhysPxCreateKinematic1(transform.AsPxPointer(), shape, density);
+
+ scene->AddActorMut((PxActor*)rigidDynamic, bvh: null);
+
+ var collider = Collider.Create(shape, type);
+ var actor = new Rigidbody(rigidDynamic, collider, scene);
+ lock (activeActors)
+ {
+ activeActors.Add(actor);
+ }
+ return actor;
+ }
+
+ public void Update(float elapsedTime = 1.0f / 60.0f)
+ {
+ Updating?.Invoke(frameCount);
+
+ scene->SimulateMut(elapsedTime, null, null, 0, controlSimulation: true);
+ uint error = 0;
+ PxScene_fetchResults_mut(scene, true, &error);
+ this.frameCount++;
+
+ Updated?.Invoke(frameCount);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ Disposing?.Invoke();
+
+ if (disposing)
+ {
+ // cleanup managed code
+ physicsSystem.RemoveScene(this);
+ physicsSystem = null!;
+ activeActors.Clear();
+ activeActors = null!;
+ }
+
+ // cleanup unmanaged resource
+ PxScene_release_mut(scene);
+ scene = null;
+ disposedValue = true;
+ }
+ }
+
+ ~PhysicsScene()
+ {
+ Dispose(disposing: false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Toolkit/PhysicsSettingsEnums.cs b/Toolkit/PhysicsSettingsEnums.cs
new file mode 100644
index 0000000..524ec5a
--- /dev/null
+++ b/Toolkit/PhysicsSettingsEnums.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MagicPhysX.Toolkit;
+
+// https://docs.unity3d.com/Manual/class-PhysicsManager.html
+
+public enum ContactsGeneration
+{
+ LegacyContactsGeneration,
+ PersistentContactManifold, // default
+}
+
+public enum ContactPairsMode
+{
+ ///
+ /// Receive collision and trigger events from all contact pairs except kinematic-kinematic and kinematic-static pairs.
+ ///
+ DefaultContactPairs,
+ ///
+ /// Receive collision and trigger events from kinematic-kinematic contact pairs.
+ ///
+ EnableKinematicKinematicPairs,
+ ///
+ /// Receive collision and trigger events from kinematic-static contact pairs.
+ ///
+ EnableKinematicStaticPairs,
+ ///
+ /// Receive collision and trigger events from all contact pairs.
+ ///
+ EnableAllContactPairs,
+}
+
+// map for PxBroadPhaseType::Enum https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/apireference/files/structPxBroadPhaseType.html
+
+
+public enum BroadphaseType
+{
+ SweepAndPruneBroadphase, // eSAP
+ MultiboxPruningBroadphase, // MBP
+ AutomaticBoxPruning, // ???
+}
+
+
+// map for PxFrictionType
+public enum FrictionType : int
+{
+ ///
+ /// A basic strong friction algorithm which typically leads to the most stable results at low solver iteration counts. This method uses only up to four scalar solver constraints per pair of touching objects.
+ ///
+ PatchFrictionType = 0,
+
+ ///
+ /// A simplification of the Coulomb friction model, in which the friction for a given point of contact is applied in the alternating tangent directions of the contact’s normal. This requires more solver iterations than patch friction but is not as accurate as the two-directional model. For Articulation bodies to work with this friction type, set the Solver Type to Temporal Gauss Seidel.
+ ///
+ OneDirectionalFrictionType = 1,
+
+ ///
+ /// Like the one-directional model, but applies friction in both tangent directions simultaneously. This requires more solver iterations but is more accurate. More expensive than patch friction for scenarios with many contact points because it is applied at every contact point. For Articulation bodies to work with this friction type, set the Solver Type to Temporal Gauss Seidel.
+ ///
+ TwoDirectionalFrictionType = 2,
+}
+
+public enum SolverType
+{
+ ///
+ /// The default PhysX solver.
+ ///
+ ProjectedGaussSeidel,
+
+ ///
+ /// This solver offers a better convergence and a better handling of high-mass ratios, minimizes energy introduced when correcting penetrations and improves the resistance of joints
+ /// to overstretch. It usually helps when you experience some erratic behavior during simulation with the default solver.
+ ///
+ TemporalGaussSeidel
+}
diff --git a/Toolkit/PhysicsSystem.cs b/Toolkit/PhysicsSystem.cs
new file mode 100644
index 0000000..6a94052
--- /dev/null
+++ b/Toolkit/PhysicsSystem.cs
@@ -0,0 +1,191 @@
+using MagicPhysX.Toolkit.Internal;
+using MagicPhysX;
+using System.Text;
+using static MagicPhysX.NativeMethods;
+using System.Numerics;
+
+namespace MagicPhysX.Toolkit;
+
+public sealed unsafe class PhysicsSystem : IDisposable
+{
+ static readonly uint VersionNumber = (5 << 24) + (1 << 16) + (3 << 8);
+ PxDefaultCpuDispatcher* dispatcher;
+ internal PxPhysics* physics;
+
+ bool disposedValue;
+ bool enablePvd;
+
+ UnorderedKeyedCollection scenes = new UnorderedKeyedCollection();
+
+ public PhysicsSystem()
+ : this(false)
+ {
+ }
+
+ public PhysicsSystem(bool enablePvd)
+ : this(enablePvd, "127.0.0.1", 5425)
+ {
+ }
+
+ public PhysicsSystem(string pvdIp, int pvdPort)
+ : this(true, pvdIp, pvdPort)
+ {
+ }
+
+ PhysicsSystem(bool enablePvd, string pvdIp, int pvdPort)
+ {
+ this.enablePvd = enablePvd;
+
+ if (!enablePvd)
+ {
+ this.physics = physx_create_physics(PhysicsFoundation.GetFoundation());
+ this.dispatcher = phys_PxDefaultCpuDispatcherCreate(1, null, PxDefaultCpuDispatcherWaitForWorkMode.WaitForWork, 0);
+ return;
+ }
+
+ PxPvd* pvd = default;
+
+ // create pvd
+ pvd = phys_PxCreatePvd(PhysicsFoundation.GetFoundation());
+
+ fixed (byte* bytePointer = Encoding.UTF8.GetBytes(pvdIp))
+ {
+ var transport = phys_PxDefaultPvdSocketTransportCreate(bytePointer, pvdPort, 10);
+ pvd->ConnectMut(transport, PxPvdInstrumentationFlags.All);
+ }
+
+ var tolerancesScale = new PxTolerancesScale
+ {
+ length = 1,
+ speed = 10
+ };
+
+ this.physics = phys_PxCreatePhysics(
+ VersionNumber,
+ PhysicsFoundation.GetFoundation(),
+ &tolerancesScale,
+ true,
+ pvd,
+ null);
+ phys_PxInitExtensions(physics, pvd);
+
+ this.dispatcher = phys_PxDefaultCpuDispatcherCreate(1, null, PxDefaultCpuDispatcherWaitForWorkMode.WaitForWork, 0);
+ }
+
+ public event Action? SceneCreated;
+
+ public PhysicsScene CreateScene()
+ {
+ var scene = CreateScene(new Vector3(0.0f, -9.81f, 0.0f));
+ lock (scenes)
+ {
+ scenes.Add(scene);
+ }
+
+ SceneCreated?.Invoke(scene);
+ return scene;
+ }
+
+ public PhysicsScene CreateScene(Vector3 gravity)
+ {
+ var sceneDesc = PxSceneDesc_new(PxPhysics_getTolerancesScale(physics));
+ sceneDesc.gravity = gravity;
+ sceneDesc.cpuDispatcher = (PxCpuDispatcher*)dispatcher;
+ sceneDesc.filterShader = get_default_simulation_filter_shader();
+ sceneDesc.solverType = PxSolverType.Pgs;
+
+ var scene = physics->CreateSceneMut(&sceneDesc);
+
+ if (enablePvd)
+ {
+ var pvdClient = scene->GetScenePvdClientMut();
+
+ if (pvdClient != null)
+ {
+ pvdClient->SetScenePvdFlagMut(PxPvdSceneFlag.TransmitConstraints, true);
+ pvdClient->SetScenePvdFlagMut(PxPvdSceneFlag.TransmitContacts, true);
+ pvdClient->SetScenePvdFlagMut(PxPvdSceneFlag.TransmitScenequeries, true);
+ }
+ }
+
+ return new PhysicsScene(this, scene);
+ }
+
+ internal void RemoveScene(PhysicsScene scene)
+ {
+ lock (scenes)
+ {
+ scenes.Remove(scene);
+ }
+ }
+
+ public PhysicsScene[] GetPhysicsScenes()
+ {
+ lock (scenes)
+ {
+ return scenes.ToArray();
+ }
+ }
+
+ public void ForEachPhysicsScenes(Action action)
+ {
+ lock (scenes)
+ {
+ foreach (var item in scenes.AsSpan())
+ {
+ action(item);
+ }
+ }
+ }
+
+ public bool TryCopyPhysicsScenes(Span dest)
+ {
+ lock (scenes)
+ {
+ var span = scenes.AsSpan();
+ return span.TryCopyTo(dest);
+ }
+ }
+
+ public PxMaterial* CreateMaterial(float staticFriction, float dynamicFriction, float restitution)
+ {
+ return physics->CreateMaterialMut(staticFriction, @dynamicFriction, restitution);
+ }
+
+ void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // cleanup managed code
+ foreach (var item in scenes.AsSpan())
+ {
+ item.Dispose();
+ }
+ scenes.Clear();
+ scenes = null!;
+ }
+
+ // cleanup unmanaged resource
+ PxDefaultCpuDispatcher_release_mut(dispatcher);
+ PxPhysics_release_mut(physics);
+
+ dispatcher = null;
+ physics = null;
+
+ disposedValue = true;
+ }
+ }
+
+ ~PhysicsSystem()
+ {
+ Dispose(disposing: false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/Toolkit/Rigidbody.cs b/Toolkit/Rigidbody.cs
new file mode 100644
index 0000000..af89a5c
--- /dev/null
+++ b/Toolkit/Rigidbody.cs
@@ -0,0 +1,569 @@
+using MagicPhysX.Toolkit.Colliders;
+using MagicPhysX.Toolkit.Internal;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace MagicPhysX.Toolkit;
+
+// PxBase <- PxActor <- PxRigidActor <- PxRigidStatic
+// <- PxRidigBody <- PxArticulationLink
+// <- PxRigidDynamic
+
+public unsafe abstract class RigidActor
+{
+ internal void* handler;
+ protected Collider collider;
+
+ public ref PxRigidActor RigidActorHandler => ref Unsafe.AsRef(handler);
+
+ protected unsafe RigidActor(void* handler, Collider collider)
+ {
+ this.handler = handler;
+ this.collider = collider;
+ }
+
+ public Collider GetComponent() => collider;
+
+ public T GetComponent()
+ where T : Collider
+ {
+ return (T)collider;
+ }
+
+ public Transform transform
+ {
+ get => RigidActorHandler.GetGlobalPose();
+ }
+}
+
+public unsafe class Rigidstatic : RigidActor
+{
+ public ref PxRigidStatic RigidStatic => ref Unsafe.AsRef(handler);
+
+ internal Rigidstatic(PxRigidStatic* handler, Collider collider)
+ : base(handler, collider)
+ {
+ }
+}
+
+///
+/// Control of an object's position through physics simulation.
+///
+public unsafe class Rigidbody : RigidActor
+{
+ PxScene* scene;
+
+ public ref PxRigidDynamic RigidDynamic => ref Unsafe.AsRef(handler);
+ public ref PxRigidBody RigidBody => ref Unsafe.AsRef(handler);
+ public ref PxRigidActor RigidActor => ref Unsafe.AsRef(handler);
+ public ref PxActor Actor => ref Unsafe.AsRef(handler);
+
+ static readonly PxRigidDynamicLockFlags PxRigidDynamicLockFlagsLockAngular =
+ PxRigidDynamicLockFlags.LockAngularX |
+ PxRigidDynamicLockFlags.LockAngularY |
+ PxRigidDynamicLockFlags.LockAngularZ;
+
+ internal Rigidbody(PxRigidDynamic* handler, Collider collider, PxScene* scene)
+ : base(handler, collider)
+ {
+ this.scene = scene;
+ }
+
+ ///
+ /// The velocity vector of the rigidbody. It represents the rate of change of Rigidbody position.
+ ///
+ public Vector3 velocity
+ {
+ get => RigidDynamic.GetLinearVelocity();
+ set => RigidDynamic.SetLinearVelocityMut(value.AsPxPointer(), autowake: true);
+ }
+
+ ///
+ /// The angular velocity vector of the rigidbody measured in radians per second.
+ ///
+ public Vector3 angularVelocity
+ {
+ get => RigidDynamic.GetAngularVelocity();
+ set => RigidDynamic.SetAngularVelocityMut(value.AsPxPointer(), autowake: true);
+ }
+
+ ///
+ /// The drag of the object.
+ ///
+ public float drag
+ {
+ get => RigidBody.GetLinearDamping();
+ set => RigidBody.SetLinearDampingMut(value);
+ }
+
+ ///
+ /// The angular drag of the object.
+ ///
+ public float angularDrag
+ {
+ get => RigidBody.GetAngularDamping();
+ set => RigidBody.SetAngularDampingMut(value);
+ }
+
+ ///
+ /// The mass of the rigidbody.
+ ///
+ public float mass
+ {
+ get => RigidBody.GetMass();
+ set => RigidBody.SetMassMut(value);
+ }
+
+ ///
+ /// Controls whether gravity affects this rigidbody.
+ ///
+ public bool useGravity
+ {
+ get => (Actor.GetActorFlags() & PxActorFlags.DisableGravity) == 0;
+ set => Actor.SetActorFlagMut(PxActorFlag.DisableGravity, !value);
+ }
+
+ ///
+ /// Maximum velocity of a rigidbody when moving out of penetrating state.
+ ///
+ public float maxDepenetrationVelocity
+ {
+ get => RigidBody.GetMaxDepenetrationVelocity();
+ set => RigidBody.SetMaxDepenetrationVelocityMut(value);
+ }
+
+ ///
+ /// Controls whether physics affects the rigidbody.
+ ///
+ public bool isKinematic
+ {
+ get => (RigidBody.GetRigidBodyFlags() & PxRigidBodyFlags.Kinematic) == PxRigidBodyFlags.Kinematic;
+ set => RigidBody.SetRigidBodyFlagMut(PxRigidBodyFlag.Kinematic, value);
+ }
+
+ ///
+ /// Controls whether physics will change the rotation of the object.
+ ///
+ public bool freezeRotation
+ {
+ get => (RigidDynamic.GetRigidDynamicLockFlags() & PxRigidDynamicLockFlagsLockAngular) ==
+ PxRigidDynamicLockFlagsLockAngular;
+ set => RigidDynamic.SetRigidDynamicLockFlagsMut(value ?
+ RigidDynamic.GetRigidDynamicLockFlags() | PxRigidDynamicLockFlagsLockAngular :
+ RigidDynamic.GetRigidDynamicLockFlags() & ~PxRigidDynamicLockFlagsLockAngular);
+ }
+
+ ///
+ /// Controls which degrees of freedom are allowed for the simulation of this Rigidbody.
+ ///
+ public RigidbodyConstraints constraints
+ {
+ get => RigidDynamic.GetRigidDynamicLockFlags().AsRigidbodyConstraints();
+ set => RigidDynamic.SetRigidDynamicLockFlagsMut(constraints.AsPxRigidDynamicLockFlags());
+ }
+
+ ///
+ /// The Rigidbody's collision detection mode.
+ ///
+ public CollisionDetectionMode collisionDetectionMode
+ {
+ get
+ {
+ var flags = RigidBody.GetRigidBodyFlags();
+ if ((flags & PxRigidBodyFlags.EnableSpeculativeCcd) == PxRigidBodyFlags.EnableSpeculativeCcd)
+ {
+ return CollisionDetectionMode.ContinuousSpeculative;
+ }
+
+ if ((flags & PxRigidBodyFlags.EnableCcd) == PxRigidBodyFlags.EnableCcd)
+ {
+ return CollisionDetectionMode.ContinuousDynamic;
+ }
+
+ return CollisionDetectionMode.Discrete;
+ }
+ set
+ {
+ switch (value)
+ {
+ case CollisionDetectionMode.ContinuousDynamic:
+ RigidBody.SetRigidBodyFlagMut(PxRigidBodyFlag.EnableCcd, true);
+ RigidBody.SetRigidBodyFlagMut(PxRigidBodyFlag.EnableSpeculativeCcd, false);
+ break;
+ case CollisionDetectionMode.ContinuousSpeculative:
+ RigidBody.SetRigidBodyFlagMut(PxRigidBodyFlag.EnableCcd, false);
+ RigidBody.SetRigidBodyFlagMut(PxRigidBodyFlag.EnableSpeculativeCcd, true);
+ break;
+ case CollisionDetectionMode.Continuous:
+ // ???
+ break;
+ case CollisionDetectionMode.Discrete:
+ RigidBody.SetRigidBodyFlagMut(PxRigidBodyFlag.EnableCcd, false);
+ RigidBody.SetRigidBodyFlagMut(PxRigidBodyFlag.EnableSpeculativeCcd, false);
+ break;
+ }
+ }
+ }
+
+ ///
+ /// The center of mass relative to the transform's origin.
+ ///
+ public Vector3 centerOfMass
+ {
+ get => RigidBody.GetCMassLocalPose().p;
+ set
+ {
+ var pose = RigidBody.GetCMassLocalPose();
+ pose.p = value;
+ RigidBody.SetCMassLocalPoseMut(&pose);
+ }
+ }
+
+ ///
+ /// The center of mass of the rigidbody in world space (Read Only).
+ ///
+ public Vector3 worldCenterOfMass
+ {
+ get
+ {
+ var globalPose = RigidActor.GetGlobalPose();
+ return globalPose.Transform(centerOfMass.AsPxPointer());
+ }
+ }
+
+ ///
+ /// The inertia tensor of this body, defined as a diagonal matrix in a reference frame positioned at this body's center of mass and rotated by Rigidbody.inertiaTensorRotation.
+ ///
+ public Vector3 inertiaTensor
+ {
+ get => RigidBody.GetMassSpaceInertiaTensor();
+ set => RigidBody.SetMassSpaceInertiaTensorMut(value.AsPxPointer());
+ }
+
+ ///
+ /// Should collision detection be enabled? (By default always enabled).
+ ///
+ public bool detectCollisions
+ {
+ get
+ {
+ var num = RigidActor.GetNbShapes();
+ if (num > 0)
+ {
+ PxShape*[] shapes = new PxShape*[num];
+ fixed (PxShape** p = shapes)
+ {
+ RigidActor.GetShapes(p, num, 0);
+ ref PxShape shape = ref Unsafe.AsRef(shapes[0]);
+ return (shape.GetFlags() & PxShapeFlags.SimulationShape) == PxShapeFlags.SimulationShape;
+ }
+ }
+
+ return false;
+ }
+
+ set
+ {
+ var num = RigidActor.GetNbShapes();
+ if (num > 0)
+ {
+ PxShape*[] shapes = new PxShape*[num];
+ fixed (PxShape** p = shapes)
+ {
+ RigidActor.GetShapes(p, num, 0);
+ for (var i = 0; i < num; i++)
+ {
+ ref PxShape shape = ref Unsafe.AsRef(shapes[i]);
+ shape.SetFlagMut(PxShapeFlag.SimulationShape, value);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// The position of the rigidbody.
+ ///
+ public Vector3 position
+ {
+ get => RigidActor.GetGlobalPose().p;
+ set
+ {
+ var pose = RigidActor.GetGlobalPose();
+ pose.p = value;
+ RigidActor.SetGlobalPoseMut(&pose, autowake: true);
+ }
+ }
+
+ ///
+ /// The rotation of the Rigidbody.
+ ///
+ public Quaternion rotation
+ {
+ get => RigidActor.GetGlobalPose().q;
+ set
+ {
+ var pose = RigidActor.GetGlobalPose();
+ pose.q = value;
+ RigidActor.SetGlobalPoseMut(&pose, autowake: true);
+ }
+ }
+
+ ///
+ /// The solverIterations determines how accurately Rigidbody joints and collision contacts are resolved. Overrides Physics.defaultSolverIterations. Must be positive.
+ ///
+ public int solverIterations
+ {
+ get
+ {
+ uint minPositionIters;
+ uint* p = &minPositionIters;
+
+ uint minVelocityIters;
+ uint* _ = &minVelocityIters;
+
+ RigidDynamic.GetSolverIterationCounts(p, _);
+
+ return (int)*p;
+ }
+ set => RigidDynamic.SetSolverIterationCountsMut((uint)value, (uint)solverVelocityIterations);
+ }
+
+ ///
+ /// The mass-normalized energy threshold, below which objects start going to sleep.
+ ///
+ public float sleepThreshold
+ {
+ get => RigidDynamic.GetSleepThreshold();
+ set => RigidDynamic.SetSleepThresholdMut(value);
+ }
+
+ ///
+ /// The maximimum angular velocity of the rigidbody measured in radians per second. (Default 7) range { 0, infinity }.
+ ///
+ public float maxAngularVelocity
+ {
+ get => RigidBody.GetMaxAngularVelocity();
+ set => RigidBody.SetMaxAngularVelocityMut(value);
+ }
+
+ ///
+ /// The solverVelocityIterations affects how how accurately Rigidbody joints and collision contacts are resolved. Overrides Physics.defaultSolverVelocityIterations. Must be positive.
+ ///
+ public int solverVelocityIterations
+ {
+ get
+ {
+ uint minPositionIters;
+ uint* _ = &minPositionIters;
+
+ uint minVelocityIters;
+ uint* v = &minVelocityIters;
+
+ RigidDynamic.GetSolverIterationCounts(_, v);
+
+ return (int)*v;
+ }
+ set => RigidDynamic.SetSolverIterationCountsMut((uint)solverIterations, (uint)value);
+ }
+
+ ///
+ /// Sets the mass based on the attached colliders assuming a constant density.
+ ///
+ ///
+ public void SetDensity(float density)
+ {
+ RigidBody.ExtUpdateMassAndInertia(&density, 1, null, false);
+ }
+
+ ///
+ /// Forces a rigidbody to sleep at least one frame.
+ ///
+ public void Sleep()
+ {
+ RigidDynamic.PutToSleepMut();
+ }
+
+ ///
+ /// Is the rigidbody sleeping?
+ ///
+ public bool IsSleeping()
+ {
+ return RigidDynamic.IsSleeping();
+ }
+
+ ///
+ /// Forces a rigidbody to wake up.
+ ///
+ public void WakeUp()
+ {
+ RigidDynamic.WakeUpMut();
+ }
+
+ ///
+ /// Reset the center of mass of the rigidbody.
+ ///
+ public void ResetCenterOfMass()
+ {
+ centerOfMass = Vector3.Zero;
+ }
+
+ ///
+ /// Reset the inertia tensor value and rotation.
+ ///
+ public void ResetInertiaTensor()
+ {
+ inertiaTensor = Vector3.Zero;
+ }
+
+ ///
+ /// Adds a force to the Rigidbody.
+ ///
+ /// Force vector in world coordinates.
+ /// Type of force to apply.
+ public void AddForce(Vector3 force, ForceMode mode)
+ {
+ RigidBody.AddForceMut(force.AsPxPointer(), (PxForceMode)mode, autowake: true);
+ }
+
+ ///
+ /// Adds a force to the Rigidbody.
+ ///
+ /// Force vector in world coordinates.
+ public void AddForce(Vector3 force)
+ {
+ RigidBody.AddForceMut(force.AsPxPointer(), PxForceMode.Force, autowake: true);
+ }
+
+
+ public void AddForce(float x, float y, float z, ForceMode mode)
+ {
+ AddForce(new Vector3(x, y, z), mode);
+ }
+
+
+ public void AddForce(float x, float y, float z)
+ {
+ AddForce(new Vector3(x, y, z));
+ }
+
+ ///
+ /// Adds a force to the rigidbody relative to its coordinate system.
+ ///
+ /// Force vector in local coordinates.
+ /// Type of force to apply.
+ public void AddRelativeForce(Vector3 force, ForceMode mode)
+ {
+ var globalPose = RigidActor.GetGlobalPose();
+ var globalForce = globalPose.Transform(force.AsPxPointer());
+
+ RigidBody.AddForceMut(&globalForce, (PxForceMode)mode, autowake: true);
+ }
+
+ ///
+ /// Adds a force to the rigidbody relative to its coordinate system.
+ ///
+ /// Force vector in local coordinates.
+ public void AddRelativeForce(Vector3 force)
+ {
+ AddRelativeForce(force, ForceMode.Force);
+ }
+
+
+ public void AddRelativeForce(float x, float y, float z, ForceMode mode)
+ {
+ AddRelativeForce(new Vector3(x, y, z), mode);
+ }
+
+
+ public void AddRelativeForce(float x, float y, float z)
+ {
+ AddRelativeForce(new Vector3(x, y, z));
+ }
+
+ ///
+ /// Adds a torque to the rigidbody.
+ ///
+ /// Torque vector in world coordinates.
+ /// The type of torque to apply.
+ public void AddTorque(Vector3 torque, ForceMode mode)
+ {
+ RigidBody.AddTorqueMut(torque.AsPxPointer(), (PxForceMode)mode, autowake: true);
+ }
+
+ ///
+ /// Adds a torque to the rigidbody.
+ ///
+ /// Torque vector in world coordinates.
+ public void AddTorque(Vector3 torque)
+ {
+ RigidBody.AddTorqueMut(torque.AsPxPointer(), PxForceMode.Force, autowake: true);
+ }
+
+
+ public void AddTorque(float x, float y, float z, ForceMode mode)
+ {
+ AddTorque(new Vector3(x, y, z), mode);
+ }
+
+
+ public void AddTorque(float x, float y, float z)
+ {
+ AddTorque(new Vector3(x, y, z));
+ }
+
+ ///
+ /// Adds a torque to the rigidbody relative to its coordinate system.
+ ///
+ /// Torque vector in local coordinates.
+ /// Type of force to apply.
+ public void AddRelativeTorque(Vector3 torque, ForceMode mode)
+ {
+ var globalPose = RigidActor.GetGlobalPose();
+ var globalToque = globalPose.Transform(torque.AsPxPointer());
+
+ RigidBody.AddTorqueMut(&globalToque, (PxForceMode)mode, autowake: true);
+ }
+
+ ///
+ /// Adds a torque to the rigidbody relative to its coordinate system.
+ ///
+ /// Torque vector in local coordinates.
+ public void AddRelativeTorque(Vector3 torque)
+ {
+ AddRelativeTorque(torque, ForceMode.Force);
+ }
+
+
+ public void AddRelativeTorque(float x, float y, float z, ForceMode mode)
+ {
+ AddRelativeTorque(new Vector3(x, y, z), mode);
+ }
+
+
+ public void AddRelativeTorque(float x, float y, float z)
+ {
+ AddRelativeTorque(new Vector3(x, y, z));
+ }
+
+ ///
+ /// Applies force at position. As a result this will apply a torque and force on the object.
+ ///
+ /// Force vector in world coordinates.
+ /// Position in world coordinates.
+ /// Type of force to apply.
+ public void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode)
+ {
+ RigidBody.ExtAddForceAtPos(force.AsPxPointer(), position.AsPxPointer(), (PxForceMode)mode, wakeup: true);
+ }
+
+ ///
+ /// Applies force at position. As a result this will apply a torque and force on the object.
+ ///
+ /// Force vector in world coordinates.
+ /// Position in world coordinates.
+ public void AddForceAtPosition(Vector3 force, Vector3 position)
+ {
+ RigidBody.ExtAddForceAtPos(force.AsPxPointer(), position.AsPxPointer(), PxForceMode.Force, wakeup: true);
+ }
+}
diff --git a/Toolkit/RigidbodyConstraints.cs b/Toolkit/RigidbodyConstraints.cs
new file mode 100644
index 0000000..88b590f
--- /dev/null
+++ b/Toolkit/RigidbodyConstraints.cs
@@ -0,0 +1,51 @@
+using System;
+
+namespace MagicPhysX.Toolkit
+{
+ ///
+ /// Use these flags to constrain motion of Rigidbodies.
+ ///
+ public enum RigidbodyConstraints : int
+ {
+ ///
+ /// No constraints.
+ ///
+ None = 0,
+ ///
+ /// Freeze motion along the X-axis.
+ ///
+ FreezePositionX = 2,
+ ///
+ /// Freeze motion along the Y-axis.
+ ///
+ FreezePositionY = 4,
+ ///
+ /// Freeze motion along the Z-axis.
+ ///
+ FreezePositionZ = 8,
+ ///
+ /// Freeze motion along all axes.
+ ///
+ FreezePosition = 14,
+ ///
+ /// Freeze rotation along the X-axis.
+ ///
+ FreezeRotationX = 16,
+ ///
+ /// Freeze rotation along the Y-axis.
+ ///
+ FreezeRotationY = 32,
+ ///
+ /// Freeze rotation along the Z-axis.
+ ///
+ FreezeRotationZ = 64,
+ ///
+ /// Freeze rotation along all axes.
+ ///
+ FreezeRotation = 112,
+ ///
+ /// Freeze rotation and motion along all axes.
+ ///
+ FreezeAll = 126,
+ }
+}
diff --git a/Toolkit/Transform.cs b/Toolkit/Transform.cs
new file mode 100644
index 0000000..8563bf6
--- /dev/null
+++ b/Toolkit/Transform.cs
@@ -0,0 +1,19 @@
+// compatible for PxTransform
+using MagicPhysX;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace MagicPhysX.Toolkit;
+
+[StructLayout(LayoutKind.Sequential)]
+public partial struct Transform
+{
+ public Quaternion rotation;
+ public Vector3 position;
+
+ public static implicit operator PxTransform(Transform v) => Unsafe.As(ref v);
+ public static implicit operator Transform(PxTransform v) => Unsafe.As(ref v);
+
+ internal unsafe PxTransform* AsPxPointer() => (PxTransform*)Unsafe.AsPointer(ref Unsafe.AsRef(this));
+}