diff --git a/CORTEX_ENGINE.sln b/CORTEX_ENGINE.sln
index 60959f6..d2e66dd 100644
--- a/CORTEX_ENGINE.sln
+++ b/CORTEX_ENGINE.sln
@@ -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
diff --git a/CORTEX_ENGINE_ARCHITECTURE.md b/CORTEX_ENGINE_ARCHITECTURE.md
index 72fc2cd..b900a9f 100644
--- a/CORTEX_ENGINE_ARCHITECTURE.md
+++ b/CORTEX_ENGINE_ARCHITECTURE.md
@@ -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
diff --git a/src/CortexEngine.App/CortexEngine.App.csproj b/src/CortexEngine.App/CortexEngine.App.csproj
index 366ac77..f04bd6b 100644
--- a/src/CortexEngine.App/CortexEngine.App.csproj
+++ b/src/CortexEngine.App/CortexEngine.App.csproj
@@ -25,6 +25,7 @@
+
diff --git a/src/CortexEngine.App/Program.cs b/src/CortexEngine.App/Program.cs
index c6bc714..9367a93 100644
--- a/src/CortexEngine.App/Program.cs
+++ b/src/CortexEngine.App/Program.cs
@@ -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();
- 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))
diff --git a/src/Engine.Core/Components/RigidBody.cs b/src/Engine.Core/Components/RigidBody.cs
new file mode 100644
index 0000000..a9441d0
--- /dev/null
+++ b/src/Engine.Core/Components/RigidBody.cs
@@ -0,0 +1,72 @@
+using System.Numerics;
+
+namespace Engine.Core.Components;
+
+///
+/// Physics body motion type.
+///
+public enum PhysicsMotionType
+{
+ Static = 0,
+ Kinematic = 1,
+ Dynamic = 2,
+}
+
+///
+/// Collision shape type for the physics body.
+///
+public enum PhysicsShapeType
+{
+ Box = 0,
+ Sphere = 1,
+}
+
+///
+/// RigidBody component — links an ECS entity to a Jolt physics body.
+/// Added to entities that should participate in physics simulation.
+///
+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);
+}
diff --git a/src/Engine.Physics/Engine.Physics.csproj b/src/Engine.Physics/Engine.Physics.csproj
new file mode 100644
index 0000000..075abdb
--- /dev/null
+++ b/src/Engine.Physics/Engine.Physics.csproj
@@ -0,0 +1,20 @@
+
+
+
+ net9.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Engine.Physics/PhysicsWorld.cs b/src/Engine.Physics/PhysicsWorld.cs
new file mode 100644
index 0000000..b381de4
--- /dev/null
+++ b/src/Engine.Physics/PhysicsWorld.cs
@@ -0,0 +1,177 @@
+using System.Numerics;
+using Engine.Core.Components;
+using Flecs.NET.Core;
+using JoltPhysicsSharp;
+
+namespace Engine.Physics;
+
+///
+/// Physics world wrapper — manages Jolt PhysicsSystem, body creation, and ECS sync.
+///
+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 _entityToBody = new();
+ private readonly Dictionary _bodyToEntity = new();
+ private readonly List _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())
+ {
+ var t = entity.Get();
+ 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();
+ }
+}