feat: JoltPhysicsSharp integration — physics simulation

- Engine.Physics project with JoltPhysicsSharp 2.21.0
- PhysicsWorld: PhysicsSystem init/update/dispose, body creation/removal,
  transform sync (Jolt body → ECS Transform)
- RigidBody ECS component: MotionType (Static/Kinematic/Dynamic),
  ShapeType (Box/Sphere), mass, friction, restitution, damping
- Factory methods: DynamicBox, DynamicSphere, StaticBox, StaticPlane
- Main loop integration: create bodies on first frame, step physics,
  sync transforms back to ECS
- Demo scene: 6 dynamic cubes + 3 dynamic spheres fall from height,
  static floor plane. Torus knot and textured cube remain static.
- Collision layers: NonMoving (floor), Moving (dynamic objects)
- JobSystemThreadPool for multi-threaded physics
- 66/66 tests passing, 0 warnings, 0 errors
This commit is contained in:
emil28092005
2026-06-17 16:45:43 +03:00
parent 9bee806a6c
commit cca89040c3
7 changed files with 422 additions and 30 deletions
+119
View File
@@ -1,3 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
@@ -16,52 +17,170 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Graphics.Raylib", "s
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Tests", "tests\Engine.Tests\Engine.Tests.csproj", "{55555555-5555-5555-5555-555555555555}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Physics", "src\Engine.Physics\Engine.Physics.csproj", "{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
ReleaseAOT|Any CPU = ReleaseAOT|Any CPU
ReleaseAOT|x64 = ReleaseAOT|x64
ReleaseAOT|x86 = ReleaseAOT|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{11111111-1111-1111-1111-111111111111}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{11111111-1111-1111-1111-111111111111}.Debug|Any CPU.Build.0 = Debug|Any CPU
{11111111-1111-1111-1111-111111111111}.Debug|x64.ActiveCfg = Debug|Any CPU
{11111111-1111-1111-1111-111111111111}.Debug|x64.Build.0 = Debug|Any CPU
{11111111-1111-1111-1111-111111111111}.Debug|x86.ActiveCfg = Debug|Any CPU
{11111111-1111-1111-1111-111111111111}.Debug|x86.Build.0 = Debug|Any CPU
{11111111-1111-1111-1111-111111111111}.Release|Any CPU.ActiveCfg = Release|Any CPU
{11111111-1111-1111-1111-111111111111}.Release|Any CPU.Build.0 = Release|Any CPU
{11111111-1111-1111-1111-111111111111}.Release|x64.ActiveCfg = Release|Any CPU
{11111111-1111-1111-1111-111111111111}.Release|x64.Build.0 = Release|Any CPU
{11111111-1111-1111-1111-111111111111}.Release|x86.ActiveCfg = Release|Any CPU
{11111111-1111-1111-1111-111111111111}.Release|x86.Build.0 = Release|Any CPU
{11111111-1111-1111-1111-111111111111}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{11111111-1111-1111-1111-111111111111}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{11111111-1111-1111-1111-111111111111}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
{11111111-1111-1111-1111-111111111111}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{11111111-1111-1111-1111-111111111111}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{11111111-1111-1111-1111-111111111111}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{22222222-2222-2222-2222-222222222222}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22222222-2222-2222-2222-222222222222}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22222222-2222-2222-2222-222222222222}.Debug|x64.ActiveCfg = Debug|Any CPU
{22222222-2222-2222-2222-222222222222}.Debug|x64.Build.0 = Debug|Any CPU
{22222222-2222-2222-2222-222222222222}.Debug|x86.ActiveCfg = Debug|Any CPU
{22222222-2222-2222-2222-222222222222}.Debug|x86.Build.0 = Debug|Any CPU
{22222222-2222-2222-2222-222222222222}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22222222-2222-2222-2222-222222222222}.Release|Any CPU.Build.0 = Release|Any CPU
{22222222-2222-2222-2222-222222222222}.Release|x64.ActiveCfg = Release|Any CPU
{22222222-2222-2222-2222-222222222222}.Release|x64.Build.0 = Release|Any CPU
{22222222-2222-2222-2222-222222222222}.Release|x86.ActiveCfg = Release|Any CPU
{22222222-2222-2222-2222-222222222222}.Release|x86.Build.0 = Release|Any CPU
{22222222-2222-2222-2222-222222222222}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{22222222-2222-2222-2222-222222222222}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{22222222-2222-2222-2222-222222222222}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
{22222222-2222-2222-2222-222222222222}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{22222222-2222-2222-2222-222222222222}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{22222222-2222-2222-2222-222222222222}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{33333333-3333-3333-3333-333333333333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33333333-3333-3333-3333-333333333333}.Debug|Any CPU.Build.0 = Debug|Any CPU
{33333333-3333-3333-3333-333333333333}.Debug|x64.ActiveCfg = Debug|Any CPU
{33333333-3333-3333-3333-333333333333}.Debug|x64.Build.0 = Debug|Any CPU
{33333333-3333-3333-3333-333333333333}.Debug|x86.ActiveCfg = Debug|Any CPU
{33333333-3333-3333-3333-333333333333}.Debug|x86.Build.0 = Debug|Any CPU
{33333333-3333-3333-3333-333333333333}.Release|Any CPU.ActiveCfg = Release|Any CPU
{33333333-3333-3333-3333-333333333333}.Release|Any CPU.Build.0 = Release|Any CPU
{33333333-3333-3333-3333-333333333333}.Release|x64.ActiveCfg = Release|Any CPU
{33333333-3333-3333-3333-333333333333}.Release|x64.Build.0 = Release|Any CPU
{33333333-3333-3333-3333-333333333333}.Release|x86.ActiveCfg = Release|Any CPU
{33333333-3333-3333-3333-333333333333}.Release|x86.Build.0 = Release|Any CPU
{33333333-3333-3333-3333-333333333333}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{33333333-3333-3333-3333-333333333333}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{33333333-3333-3333-3333-333333333333}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
{33333333-3333-3333-3333-333333333333}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{33333333-3333-3333-3333-333333333333}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{33333333-3333-3333-3333-333333333333}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{44444444-4444-4444-4444-444444444444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44444444-4444-4444-4444-444444444444}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44444444-4444-4444-4444-444444444444}.Debug|x64.ActiveCfg = Debug|Any CPU
{44444444-4444-4444-4444-444444444444}.Debug|x64.Build.0 = Debug|Any CPU
{44444444-4444-4444-4444-444444444444}.Debug|x86.ActiveCfg = Debug|Any CPU
{44444444-4444-4444-4444-444444444444}.Debug|x86.Build.0 = Debug|Any CPU
{44444444-4444-4444-4444-444444444444}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44444444-4444-4444-4444-444444444444}.Release|Any CPU.Build.0 = Release|Any CPU
{44444444-4444-4444-4444-444444444444}.Release|x64.ActiveCfg = Release|Any CPU
{44444444-4444-4444-4444-444444444444}.Release|x64.Build.0 = Release|Any CPU
{44444444-4444-4444-4444-444444444444}.Release|x86.ActiveCfg = Release|Any CPU
{44444444-4444-4444-4444-444444444444}.Release|x86.Build.0 = Release|Any CPU
{44444444-4444-4444-4444-444444444444}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{44444444-4444-4444-4444-444444444444}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{44444444-4444-4444-4444-444444444444}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
{44444444-4444-4444-4444-444444444444}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{44444444-4444-4444-4444-444444444444}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{44444444-4444-4444-4444-444444444444}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Debug|x64.ActiveCfg = Debug|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Debug|x64.Build.0 = Debug|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Debug|x86.ActiveCfg = Debug|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Debug|x86.Build.0 = Debug|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Release|Any CPU.Build.0 = Release|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Release|x64.ActiveCfg = Release|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Release|x64.Build.0 = Release|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Release|x86.ActiveCfg = Release|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.Release|x86.Build.0 = Release|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{43C6A648-4F0C-4440-95E3-733BD8D29BCE}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x64.ActiveCfg = Debug|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x64.Build.0 = Debug|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x86.ActiveCfg = Debug|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Debug|x86.Build.0 = Debug|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|Any CPU.Build.0 = Release|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x64.ActiveCfg = Release|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x64.Build.0 = Release|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x86.ActiveCfg = Release|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.Release|x86.Build.0 = Release|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|Any CPU.ActiveCfg = ReleaseAOT|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|Any CPU.Build.0 = ReleaseAOT|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{31447693-7B61-4B22-95BB-47FF08C1CB2A}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|x64.ActiveCfg = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|x64.Build.0 = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|x86.ActiveCfg = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Debug|x86.Build.0 = Debug|Any CPU
{55555555-5555-5555-5555-555555555555}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55555555-5555-5555-5555-555555555555}.Release|Any CPU.Build.0 = Release|Any CPU
{55555555-5555-5555-5555-555555555555}.Release|x64.ActiveCfg = Release|Any CPU
{55555555-5555-5555-5555-555555555555}.Release|x64.Build.0 = Release|Any CPU
{55555555-5555-5555-5555-555555555555}.Release|x86.ActiveCfg = Release|Any CPU
{55555555-5555-5555-5555-555555555555}.Release|x86.Build.0 = Release|Any CPU
{55555555-5555-5555-5555-555555555555}.ReleaseAOT|x64.ActiveCfg = ReleaseAOT|Any CPU
{55555555-5555-5555-5555-555555555555}.ReleaseAOT|x64.Build.0 = ReleaseAOT|Any CPU
{55555555-5555-5555-5555-555555555555}.ReleaseAOT|x86.ActiveCfg = ReleaseAOT|Any CPU
{55555555-5555-5555-5555-555555555555}.ReleaseAOT|x86.Build.0 = ReleaseAOT|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Debug|x64.ActiveCfg = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Debug|x64.Build.0 = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Debug|x86.ActiveCfg = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Debug|x86.Build.0 = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Release|Any CPU.Build.0 = Release|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Release|x64.ActiveCfg = Release|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Release|x64.Build.0 = Release|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Release|x86.ActiveCfg = Release|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.Release|x86.Build.0 = Release|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|Any CPU.ActiveCfg = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|Any CPU.Build.0 = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|x64.ActiveCfg = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|x64.Build.0 = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|x86.ActiveCfg = Debug|Any CPU
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883}.ReleaseAOT|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{3F5F10AA-A4CD-4E37-B4CE-B37BC60C3883} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
EndGlobal
+2 -1
View File
@@ -723,7 +723,8 @@ In Release (NativeAOT), the MCP server and ASP.NET Core are excluded. The AI can
- [ ] **Vulkan backend reintegration** — see §3.3 checklist. Compiles but untested.
Kept architecturally compatible via `IWindow` / `IRenderContext` / `IRenderer`.
Deferred because Raylib covers all current needs with far less complexity.
- [ ] Physics (JoltPhysicsSharp)
- [x] Physics (JoltPhysicsSharp 2.21.0) — Engine.Physics project, PhysicsWorld wrapper,
RigidBody ECS component, dynamic boxes/spheres with gravity, static floor, transform sync
- [ ] AI hot-reload of C# scripts (Roslyn — conflicts with NativeAOT)
- [ ] Semantic segmentation maps for MMLM vision input
@@ -25,6 +25,7 @@
<ProjectReference Include="..\Engine.Graphics.Raylib\Engine.Graphics.Raylib.csproj" />
<ProjectReference Include="..\Engine.Graphics.Vulkan\Engine.Graphics.Vulkan.csproj" />
<ProjectReference Include="..\Engine.AI\Engine.AI.csproj" />
<ProjectReference Include="..\Engine.Physics\Engine.Physics.csproj" />
</ItemGroup>
<ItemGroup>
+31 -29
View File
@@ -13,6 +13,7 @@ using Engine.Graphics;
using Engine.Graphics.Loaders;
using Engine.Graphics.RaylibBackend;
using Engine.Graphics.Vulkan;
using Engine.Physics;
using Flecs.NET.Core;
namespace CortexEngine.App;
@@ -38,6 +39,7 @@ class Program
using var world = World.Create();
var timing = new Timing();
using var physicsWorld = new PhysicsWorld();
RaylibBackendRegistrar.EnsureRegistered();
VulkanBackendRegistrar.EnsureRegistered();
@@ -238,15 +240,21 @@ class Program
}
}
// Slowly rotate the model so we can see it in 3D, unless the camera tour or test scene is active.
if (!cameraTour && !testScene)
// Physics: create bodies, step, sync transforms
if (!cameraTour)
{
var modelEntity = world.Lookup("CubeCenter");
if (modelEntity.Id != 0)
world.Each((Entity e, ref RigidBody rb, ref Transform t) =>
{
ref var modelTransform = ref modelEntity.Ensure<Transform>();
modelTransform.Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)timing.TotalTime * 0.5f);
}
if (!rb.IsInitialized)
{
physicsWorld.CreateBody(e, rb, t);
rb.IsInitialized = true;
e.Set(rb);
}
});
physicsWorld.Update((float)timing.DeltaTime);
physicsWorld.SyncTransforms(world);
}
// Capture a demo screenshot after the scene warms up (non-tour mode only).
@@ -295,14 +303,12 @@ class Program
var cubes = new (string name, Vector3 pos, Vector3 color, float scale, float rough, float metal)[]
{
("CubeCenter", new Vector3(0, 0.5f, 0), new Vector3(0.9f, 0.6f, 0.3f), 0.5f, 0.3f, 0.1f),
("CubeRed", new Vector3(2, 0.5f, 0), new Vector3(0.85f, 0.15f, 0.15f), 0.5f, 0.4f, 0.2f),
("CubeGreen", new Vector3(-2, 0.5f, 0), new Vector3(0.2f, 0.8f, 0.3f), 0.5f, 0.5f, 0.0f),
("CubeBlue", new Vector3(0, 0.5f, 3), new Vector3(0.2f, 0.4f, 0.9f), 0.6f, 0.2f, 0.3f),
("CubeYellow", new Vector3(0, 0.5f, -3), new Vector3(0.95f, 0.85f, 0.2f), 0.5f, 0.6f, 0.0f),
("CubeOrange", new Vector3(-3, 0.5f, 3), new Vector3(0.95f, 0.5f, 0.1f), 0.45f, 0.5f, 0.1f),
("CubeWide", new Vector3(-1.5f, 0.5f, -1.5f), new Vector3(0.5f, 0.5f, 0.6f), 0.8f, 0.8f, 0.0f),
("CubeSmallGold", new Vector3(5, 0.3f, -2), new Vector3(1.0f, 0.8f, 0.3f), 0.3f, 0.15f, 1.0f),
("CubeCenter", new Vector3(0, 5f, 0), new Vector3(0.9f, 0.6f, 0.3f), 0.5f, 0.3f, 0.1f),
("CubeRed", new Vector3(0.3f, 7f, 0.3f), new Vector3(0.85f, 0.15f, 0.15f), 0.5f, 0.4f, 0.2f),
("CubeGreen", new Vector3(-0.3f, 9f, -0.3f), new Vector3(0.2f, 0.8f, 0.3f), 0.5f, 0.5f, 0.0f),
("CubeBlue", new Vector3(0.1f, 11f, 0.1f), new Vector3(0.2f, 0.4f, 0.9f), 0.6f, 0.2f, 0.3f),
("CubeYellow", new Vector3(-0.2f, 13f, 0.2f), new Vector3(0.95f, 0.85f, 0.2f), 0.5f, 0.6f, 0.0f),
("CubeOrange", new Vector3(0.15f, 15f, -0.1f), new Vector3(0.95f, 0.5f, 0.1f), 0.45f, 0.5f, 0.1f),
};
foreach (var (name, pos, color, scale, rough, metal) in cubes)
@@ -310,18 +316,15 @@ class Program
world.Entity(name)
.Set(new Transform(pos, Quaternion.Identity, new Vector3(scale)))
.Set(mesh)
.Set(new Material(color, roughness: rough, metallic: metal));
.Set(new Material(color, roughness: rough, metallic: metal))
.Set(RigidBody.DynamicBox(new Vector3(scale), mass: scale * 2f));
}
var spheres = new (string name, Vector3 pos, Vector3 color, float scale, float rough, float metal)[]
{
("SphereGold", new Vector3(-5, 0.5f, -2), new Vector3(1.0f, 0.85f, 0.4f), 1.0f, 0.1f, 1.0f),
("SphereChrome", new Vector3(-6, 0.5f, 0), new Vector3(0.9f, 0.9f, 0.95f), 1.0f, 0.05f, 1.0f),
("SphereRed", new Vector3(-5, 0.5f, 2), new Vector3(0.9f, 0.1f, 0.1f), 1.0f, 0.4f, 0.0f),
("SphereBlue", new Vector3(5, 0.5f, 2), new Vector3(0.1f, 0.3f, 0.9f), 1.0f, 0.2f, 0.5f),
("SphereGreen", new Vector3(6, 0.5f, 0), new Vector3(0.1f, 0.8f, 0.3f), 1.0f, 0.7f, 0.0f),
("SphereWhite", new Vector3(5, 0.5f, -4), new Vector3(0.95f, 0.95f, 0.95f), 1.0f, 0.3f, 0.0f),
("SphereRough", new Vector3(3, 0.5f, 5), new Vector3(0.6f, 0.4f, 0.2f), 1.0f, 0.9f, 0.0f),
("SphereGold", new Vector3(3, 6f, -2), new Vector3(1.0f, 0.85f, 0.4f), 1.0f, 0.1f, 1.0f),
("SphereChrome", new Vector3(-3, 8f, 0), new Vector3(0.9f, 0.9f, 0.95f), 1.0f, 0.05f, 1.0f),
("SphereRed", new Vector3(3, 10f, 2), new Vector3(0.9f, 0.1f, 0.1f), 1.0f, 0.4f, 0.0f),
};
foreach (var (name, pos, color, scale, rough, metal) in spheres)
@@ -329,26 +332,25 @@ class Program
world.Entity(name)
.Set(new Transform(pos, Quaternion.Identity, new Vector3(scale)))
.Set(sphere)
.Set(new Material(color, roughness: rough, metallic: metal));
.Set(new Material(color, roughness: rough, metallic: metal))
.Set(RigidBody.DynamicSphere(scale * 0.5f, mass: scale));
}
// Torus knot with checker texture
world.Entity("TorusKnot")
.Set(new Transform(new Vector3(0, 2.0f, 0), Quaternion.Identity, new Vector3(1.5f)))
.Set(new Transform(new Vector3(0, 0.5f, -6), Quaternion.Identity, new Vector3(1.5f)))
.Set(torusKnot)
.Set(new Material(new Vector3(0.9f, 0.9f, 0.9f), roughness: 0.25f, metallic: 0.6f, texturePath: "Content/checker.png"));
// Textured cube
world.Entity("CubeTextured")
.Set(new Transform(new Vector3(-4, 0.5f, -3), Quaternion.Identity, new Vector3(0.7f)))
.Set(mesh)
.Set(new Material(new Vector3(0.8f, 0.8f, 0.85f), roughness: 0.4f, metallic: 0.0f, texturePath: "Content/checker.png"));
// Floor plane for shadow visibility
world.Entity("Floor")
.Set(new Transform(new Vector3(0, -0.02f, 0), Quaternion.Identity, new Vector3(20, 1, 20)))
.Set(mesh)
.Set(new Material(new Vector3(0.45f, 0.45f, 0.5f), roughness: 0.8f, metallic: 0.0f));
.Set(new Material(new Vector3(0.45f, 0.45f, 0.5f), roughness: 0.8f, metallic: 0.0f))
.Set(RigidBody.StaticPlane(20f));
world.Entity("Grid")
.Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, Vector3.One))
+72
View File
@@ -0,0 +1,72 @@
using System.Numerics;
namespace Engine.Core.Components;
/// <summary>
/// Physics body motion type.
/// </summary>
public enum PhysicsMotionType
{
Static = 0,
Kinematic = 1,
Dynamic = 2,
}
/// <summary>
/// Collision shape type for the physics body.
/// </summary>
public enum PhysicsShapeType
{
Box = 0,
Sphere = 1,
}
/// <summary>
/// RigidBody component — links an ECS entity to a Jolt physics body.
/// Added to entities that should participate in physics simulation.
/// </summary>
public record struct RigidBody
{
public PhysicsMotionType MotionType;
public PhysicsShapeType ShapeType;
public Vector3 ShapeSize;
public float Mass;
public float Friction;
public float Restitution;
public float LinearDamping;
public float AngularDamping;
public bool IsInitialized;
public RigidBody(
PhysicsMotionType motionType = PhysicsMotionType.Dynamic,
PhysicsShapeType shapeType = PhysicsShapeType.Box,
Vector3? shapeSize = null,
float mass = 1.0f,
float friction = 0.5f,
float restitution = 0.3f,
float linearDamping = 0.1f,
float angularDamping = 0.1f)
{
MotionType = motionType;
ShapeType = shapeType;
ShapeSize = shapeSize ?? new Vector3(0.5f);
Mass = mass;
Friction = friction;
Restitution = restitution;
LinearDamping = linearDamping;
AngularDamping = angularDamping;
IsInitialized = false;
}
public static RigidBody DynamicBox(Vector3 halfExtent, float mass = 1.0f) =>
new(PhysicsMotionType.Dynamic, PhysicsShapeType.Box, halfExtent, mass);
public static RigidBody DynamicSphere(float radius, float mass = 1.0f) =>
new(PhysicsMotionType.Dynamic, PhysicsShapeType.Sphere, new Vector3(radius), mass);
public static RigidBody StaticBox(Vector3 halfExtent) =>
new(PhysicsMotionType.Static, PhysicsShapeType.Box, halfExtent, friction: 0.8f);
public static RigidBody StaticPlane(float size = 50f) =>
new(PhysicsMotionType.Static, PhysicsShapeType.Box, new Vector3(size, 0.5f, size), friction: 0.8f);
}
+20
View File
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JoltPhysicsSharp" Version="2.21.0" />
<PackageReference Include="Flecs.NET.Debug" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Flecs.NET.Release" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Release' OR '$(Configuration)' == 'ReleaseAOT'" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Engine.Core\Engine.Core.csproj" />
</ItemGroup>
</Project>
+177
View File
@@ -0,0 +1,177 @@
using System.Numerics;
using Engine.Core.Components;
using Flecs.NET.Core;
using JoltPhysicsSharp;
namespace Engine.Physics;
/// <summary>
/// Physics world wrapper — manages Jolt PhysicsSystem, body creation, and ECS sync.
/// </summary>
public sealed class PhysicsWorld : IDisposable
{
private const int MaxBodies = 65536;
private const int MaxBodyPairs = 65536;
private const int MaxContactConstraints = 65536;
private static class Layers
{
public static readonly ObjectLayer NonMoving = 0;
public static readonly ObjectLayer Moving = 1;
}
private readonly PhysicsSystem _physicsSystem;
private readonly BodyInterface _bodyInterface;
private readonly JobSystem _jobSystem;
private readonly Dictionary<Entity, BodyID> _entityToBody = new();
private readonly Dictionary<BodyID, Entity> _bodyToEntity = new();
private readonly List<Body> _bodies = new();
private bool _disposed;
public Vector3 Gravity
{
get => _physicsSystem.Gravity;
set => _physicsSystem.Gravity = value;
}
public PhysicsWorld()
{
Foundation.Init(false);
var broadPhaseLayerInterface = new BroadPhaseLayerInterfaceTable(2, 2);
broadPhaseLayerInterface.MapObjectToBroadPhaseLayer(Layers.NonMoving, new BroadPhaseLayer(0));
broadPhaseLayerInterface.MapObjectToBroadPhaseLayer(Layers.Moving, new BroadPhaseLayer(1));
var objectLayerPairFilter = new ObjectLayerPairFilterTable(2);
var objectVsBroadPhaseLayerFilter = new ObjectVsBroadPhaseLayerFilterTable(
broadPhaseLayerInterface, 2, objectLayerPairFilter, 2);
var settings = new PhysicsSystemSettings
{
MaxBodies = MaxBodies,
MaxBodyPairs = MaxBodyPairs,
MaxContactConstraints = MaxContactConstraints,
BroadPhaseLayerInterface = broadPhaseLayerInterface,
ObjectLayerPairFilter = objectLayerPairFilter,
ObjectVsBroadPhaseLayerFilter = objectVsBroadPhaseLayerFilter,
};
_physicsSystem = new PhysicsSystem(settings);
_physicsSystem.Gravity = new Vector3(0, -9.81f, 0);
_bodyInterface = _physicsSystem.BodyInterface;
_jobSystem = new JobSystemThreadPool();
}
public void CreateBody(Entity entity, in RigidBody rigidBody, in Transform transform)
{
if (_entityToBody.ContainsKey(entity))
return;
var shape = CreateShape(rigidBody);
var motionType = ToMotionType(rigidBody.MotionType);
var layer = rigidBody.MotionType == PhysicsMotionType.Static ? Layers.NonMoving : Layers.Moving;
var creationSettings = new BodyCreationSettings(
shape,
transform.Position,
transform.Rotation,
motionType,
layer);
var activation = rigidBody.MotionType == PhysicsMotionType.Static
? Activation.DontActivate
: Activation.Activate;
var body = _bodyInterface.CreateBody(creationSettings);
_bodyInterface.AddBody(body.ID, activation);
if (rigidBody.MotionType != PhysicsMotionType.Static)
{
body.SetFriction(rigidBody.Friction);
body.SetRestitution(rigidBody.Restitution);
}
_entityToBody[entity] = body.ID;
_bodyToEntity[body.ID] = entity;
_bodies.Add(body);
}
public void RemoveBody(Entity entity)
{
if (!_entityToBody.TryGetValue(entity, out var bodyId))
return;
_bodyInterface.RemoveBody(bodyId);
_bodyInterface.DestroyBody(bodyId);
_entityToBody.Remove(entity);
_bodyToEntity.Remove(bodyId);
}
public void Update(float deltaTime, int collisionSteps = 1)
{
_physicsSystem.Update(deltaTime, collisionSteps, _jobSystem);
}
public void SyncTransforms(World world)
{
foreach (var (entity, bodyId) in _entityToBody)
{
if ((ulong)entity.Id == 0)
continue;
var pos = _bodyInterface.GetPosition(bodyId);
var rot = _bodyInterface.GetRotation(bodyId);
if (entity.Has<Transform>())
{
var t = entity.Get<Transform>();
t.Position = pos;
t.Rotation = rot;
entity.Set(t);
}
}
}
public void SyncToPhysics(Entity entity, in Transform transform)
{
if (!_entityToBody.TryGetValue(entity, out var bodyId))
return;
_bodyInterface.SetPositionAndRotation(bodyId, transform.Position, transform.Rotation, Activation.Activate);
}
private static Shape CreateShape(RigidBody rb)
{
return rb.ShapeType switch
{
PhysicsShapeType.Box => new BoxShape(rb.ShapeSize),
PhysicsShapeType.Sphere => new SphereShape(rb.ShapeSize.X),
_ => new BoxShape(rb.ShapeSize),
};
}
private static MotionType ToMotionType(PhysicsMotionType type) => type switch
{
PhysicsMotionType.Static => MotionType.Static,
PhysicsMotionType.Kinematic => MotionType.Kinematic,
PhysicsMotionType.Dynamic => MotionType.Dynamic,
_ => MotionType.Dynamic,
};
public void Dispose()
{
if (_disposed) return;
_disposed = true;
foreach (var body in _bodies)
body.Dispose();
_bodies.Clear();
_entityToBody.Clear();
_bodyToEntity.Clear();
_jobSystem.Dispose();
_physicsSystem.Dispose();
Foundation.Shutdown();
}
}