using System.Diagnostics; using System.Numerics; using Engine.AI; using Engine.Core; using Engine.Core.Components; using Engine.Graphics; using Engine.Graphics.Loaders; using Engine.Graphics.Vulkan; using Engine.Physics; using Flecs.NET.Core; using ImGuiNET; #if !RELEASE_AOT using Engine.AI.Mcp; using Microsoft.AspNetCore.Builder; #endif namespace CortexEngine.App; class Program { private enum DemoScene { KineticGallery, SolarSanctuary, ArenaStrike, } static async Task Main(string[] args) { Console.WriteLine("Cortex Engine — Vulkan + Physics + AI/MCP (pure P/Invoke)..."); try { var mcpPort = ParseMcpPort(args); var scene = ParseScene(args); using var world = World.Create(); using var physicsWorld = new PhysicsWorld(); VulkanBackendRegistrar.EnsureRegistered(); using var renderContext = RenderBackendFactory.Create("vulkan", 1280, 720, enableValidation: true); var window = renderContext.Window; var input = window.Input; using var renderer = renderContext.CreateRenderer(); var processor = new AiCommandProcessor(world, p => LoadMesh(p, new Vector3(0.7f, 0.6f, 0.5f)), path => renderer.RequestScreenshot(path)); var queue = new AiCommandQueue(processor, new DummyScreenshotProvider()); #if !RELEASE_AOT WebApplication? mcpApp = null; var mcpStarted = false; var frameCount = 0; #endif var cameraEntity = world.Entity("Camera") .Set(new Camera( new Vector3(0, 5, -15), new Vector3(0, 0.5f, 0), Vector3.UnitY, MathF.PI / 4f, 1280f / 720f, 0.1f, 100f)); FpsShooterGame? shooter = scene == DemoScene.ArenaStrike ? new FpsShooterGame(world, LoadMesh) : null; Func? playerPositionConstraint = shooter is null ? null : shooter.ConstrainPlayerPosition; CreateScene(world, scene, shooter); ConfigureCamera(cameraEntity, scene); var cameraController = new FreeFlyCameraController(cameraEntity, playerPositionConstraint); Console.WriteLine("Camera: FreeFly (WASD + right-click mouse look, Q/E up/down, Shift boost)"); Console.WriteLine($"[Scene] Active demo: {GetSceneName(scene)}"); var hasImGui = renderer.GetType().Name == "VulkanRenderer"; VulkanRenderer? vkRenderer = hasImGui ? (VulkanRenderer)renderer : null; if (hasImGui) { ImGui.GetIO().DisplaySize = new System.Numerics.Vector2(window.Width, window.Height); Console.WriteLine("[App] ImGui initialized."); } var lastWidth = window.Width; var lastHeight = window.Height; var frames = 0; var lastFpsTime = 0.0; var timing = new Timing(); var totalTime = 0.0f; bool isRecording = false; Process? ffmpegProcess = null; string? recordingPath = null; int recordedFrames = 0; const double renderFps = 60.0; var renderFrameTime = 1.0 / renderFps; var renderTimer = 0.0; bool physicsEnabled = true; while (!window.ShouldClose) { timing.Tick(); renderTimer += timing.DeltaTime; input.BeginFrame(); // Physics + input run at full speed (uncapped). Edge input must be cleared // before SDL events are pumped so IsKeyPressed works for gameplay controls. window.PumpEvents(); #if !RELEASE_AOT frameCount++; if (mcpPort > 0 && !mcpStarted && frameCount > 10) { mcpStarted = true; var port = mcpPort; var q = queue; Task.Run(() => { try { mcpApp = McpEngineServerHost.Create(Array.Empty(), q, port: port); Console.WriteLine($"[App] MCP HTTP server listening on http://localhost:{port}/ (SSE)"); mcpApp.Run(); } catch (Exception ex) { Console.WriteLine($"[App] MCP server error: {ex.Message}"); } }); } #endif if (window.Width != lastWidth || window.Height != lastHeight) { lastWidth = window.Width; lastHeight = window.Height; if (lastWidth > 0 && lastHeight > 0) { renderContext.Resize(lastWidth, lastHeight); ref var cam = ref cameraEntity.Ensure(); cam.AspectRatio = (float)lastWidth / lastHeight; cameraEntity.Set(cam); } } cameraController.Update(input, (float)timing.DeltaTime); totalTime += (float)timing.DeltaTime; var allowGameplayInput = !hasImGui || (!ImGui.GetIO().WantCaptureMouse && !ImGui.GetIO().WantCaptureKeyboard); shooter?.Update(input, cameraEntity, totalTime, (float)timing.DeltaTime, allowGameplayInput); var toInit = new List<(Entity, RigidBody, Transform)>(); world.Each((Entity e, ref RigidBody rb, ref Transform t) => { if (!rb.IsInitialized && e.IsAlive()) toInit.Add((e, rb, t)); }); foreach (var (e, rb, t) in toInit) { if (!e.IsAlive()) continue; var rbInit = rb; rbInit.IsInitialized = true; e.Set(rbInit); physicsWorld.CreateBody(e, rbInit, t); } if (physicsEnabled) { physicsWorld.Update((float)timing.DeltaTime); physicsWorld.SyncTransforms(world); } // Move lights in chaotic circles var mainLight = world.Lookup("MainLight"); if ((ulong)mainLight.Id != 0) { var t = totalTime; var lx = MathF.Sin(t * 0.7f) * 8f + MathF.Cos(t * 0.3f) * 3f; var ly = 12f + MathF.Sin(t * 0.5f) * 5f; var lz = MathF.Cos(t * 0.6f) * 8f + MathF.Sin(t * 0.4f) * 3f; var lt = mainLight.Get(); lt.Position = new Vector3(lx, ly, lz); mainLight.Set(lt); } var secondLight = world.Lookup("SecondLight"); if ((ulong)secondLight.Id != 0) { var t = totalTime + MathF.PI; var lx = MathF.Cos(t * 0.5f) * 10f + MathF.Sin(t * 0.2f) * 4f; var ly = 8f + MathF.Cos(t * 0.4f) * 4f; var lz = MathF.Sin(t * 0.6f) * 10f + MathF.Cos(t * 0.3f) * 3f; var lt = secondLight.Get(); lt.Position = new Vector3(lx, ly, lz); secondLight.Set(lt); } var thirdLight = world.Lookup("ThirdLight"); if ((ulong)thirdLight.Id != 0) { var t = totalTime + MathF.PI * 1.5f; var lx = MathF.Sin(t * 0.4f) * 12f; var ly = 10f + MathF.Sin(t * 0.3f) * 3f; var lz = MathF.Cos(t * 0.5f) * 12f; var lt = thirdLight.Get(); lt.Position = new Vector3(lx, ly, lz); thirdLight.Set(lt); } var processed = queue.ProcessPending(); if (processed > 0) Console.WriteLine($"[App] Processed {processed} AI command(s)"); if (hasImGui) { var io = ImGui.GetIO(); io.DisplaySize = new System.Numerics.Vector2(window.Width, window.Height); io.MousePos = new System.Numerics.Vector2(input.MouseX, input.MouseY); io.MouseDown[0] = input.MouseLeft; io.MouseDown[1] = input.MouseRight; io.MouseDown[2] = input.MouseMiddle; renderer.BeginImGuiFrame(); ImGui.Begin("Cortex Engine Debug"); ImGui.Text($"FPS: {frames}"); ImGui.Text($"Delta: {timing.DeltaTime * 1000.0:F2} ms"); var cam = cameraEntity.Get(); ImGui.Separator(); ImGui.Text($"Camera Pos: ({cam.Position.X:F2}, {cam.Position.Y:F2}, {cam.Position.Z:F2})"); ImGui.Text($"Camera Target: ({cam.Target.X:F2}, {cam.Target.Y:F2}, {cam.Target.Z:F2})"); ImGui.Separator(); var entityCount = 0; world.Each((Entity e, ref Transform _) => entityCount++); ImGui.Text($"Entities: {entityCount}"); ImGui.Text($"Scene: {GetSceneName(scene)}"); ImGui.Text($"MCP: {(mcpPort > 0 ? $"port {mcpPort}" : "disabled")}"); ImGui.Text("WASD: move | RMB: look | Q/E: up/down | Shift: boost"); ImGui.Separator(); if (ImGui.Button(physicsEnabled ? "Pause Physics" : "Resume Physics")) { physicsEnabled = !physicsEnabled; Console.WriteLine($"[App] Physics: {(physicsEnabled ? "enabled" : "paused")}"); } ImGui.SameLine(); if (ImGui.Button("Reset Scene")) { // Pause physics during reset physicsEnabled = false; // Delete all entities except Camera, lights, Floor var toDelete = new List(); world.Each((Entity e, ref Transform t) => { var name = e.Name(); if (name != "Camera" && name != "MainLight" && name != "SecondLight" && name != "ThirdLight" && name != "Floor") toDelete.Add(e); }); // Remove RigidBody from physics before destructing foreach (var ent in toDelete) { if (ent.IsAlive()) { if (ent.Has()) physicsWorld.RemoveBody(ent); ent.Destruct(); } } CreateSceneObjects(world, scene, shooter); ConfigureCamera(cameraEntity, scene); cameraController = new FreeFlyCameraController(cameraEntity, playerPositionConstraint); physicsEnabled = true; Console.WriteLine($"[App] Scene reset: {toDelete.Count} entities deleted"); } ImGui.End(); shooter?.DrawHud(window.Width, window.Height); // Shadow parameters panel ImGui.Begin("Shadow & Light Parameters"); // Main light controls var mainLight2 = world.Lookup("MainLight"); if ((ulong)mainLight2.Id != 0) { var lc1 = mainLight2.Get(); var i1 = lc1.Intensity; var r1 = lc1.Range; var cr1 = lc1.Color.X; var cg1 = lc1.Color.Y; var cb1 = lc1.Color.Z; ImGui.Text("Main Light (warm)"); ImGui.SliderFloat("1 Intensity", ref i1, 0.0f, 50.0f, "%.1f"); ImGui.SliderFloat("1 Range", ref r1, 5.0f, 100.0f, "%.1f"); ImGui.SliderFloat("1 R", ref cr1, 0.0f, 1.0f, "%.2f"); ImGui.SliderFloat("1 G", ref cg1, 0.0f, 1.0f, "%.2f"); ImGui.SliderFloat("1 B", ref cb1, 0.0f, 1.0f, "%.2f"); lc1.Intensity = i1; lc1.Range = r1; lc1.Color = new Vector3(cr1, cg1, cb1); mainLight2.Set(lc1); } ImGui.Separator(); // Second light controls var secondLight2 = world.Lookup("SecondLight"); if ((ulong)secondLight2.Id != 0) { var lc2 = secondLight2.Get(); var i2 = lc2.Intensity; var r2 = lc2.Range; var cr2 = lc2.Color.X; var cg2 = lc2.Color.Y; var cb2 = lc2.Color.Z; ImGui.Text("Second Light (cool)"); ImGui.SliderFloat("2 Intensity", ref i2, 0.0f, 50.0f, "%.1f"); ImGui.SliderFloat("2 Range", ref r2, 5.0f, 100.0f, "%.1f"); ImGui.SliderFloat("2 R", ref cr2, 0.0f, 1.0f, "%.2f"); ImGui.SliderFloat("2 G", ref cg2, 0.0f, 1.0f, "%.2f"); ImGui.SliderFloat("2 B", ref cb2, 0.0f, 1.0f, "%.2f"); lc2.Intensity = i2; lc2.Range = r2; lc2.Color = new Vector3(cr2, cg2, cb2); secondLight2.Set(lc2); } ImGui.Separator(); // Third light controls var thirdLight2 = world.Lookup("ThirdLight"); if ((ulong)thirdLight2.Id != 0) { var lc3 = thirdLight2.Get(); var i3 = lc3.Intensity; var r3 = lc3.Range; var cr3 = lc3.Color.X; var cg3 = lc3.Color.Y; var cb3 = lc3.Color.Z; ImGui.Text("Third Light (red)"); ImGui.SliderFloat("3 Intensity", ref i3, 0.0f, 50.0f, "%.1f"); ImGui.SliderFloat("3 Range", ref r3, 5.0f, 100.0f, "%.1f"); ImGui.SliderFloat("3 R", ref cr3, 0.0f, 1.0f, "%.2f"); ImGui.SliderFloat("3 G", ref cg3, 0.0f, 1.0f, "%.2f"); ImGui.SliderFloat("3 B", ref cb3, 0.0f, 1.0f, "%.2f"); lc3.Intensity = i3; lc3.Range = r3; lc3.Color = new Vector3(cr3, cg3, cb3); thirdLight2.Set(lc3); } ImGui.Separator(); var bias = vkRenderer.ShadowBias; var sampleRadius = vkRenderer.ShadowSampleRadius; var farPlane = vkRenderer.ShadowFarPlane; var ambient = vkRenderer.AmbientColor; ImGui.SliderFloat("Shadow Bias", ref bias, 0.0001f, 0.1f, "%.4f"); ImGui.SliderFloat("Sample Radius", ref sampleRadius, 0.001f, 0.1f, "%.4f"); ImGui.SliderFloat("Far Plane", ref farPlane, 10.0f, 120.0f, "%.1f"); ImGui.Separator(); ImGui.Text("Ambient Light"); ImGui.SliderFloat("Ambient R", ref ambient.X, 0.0f, 0.5f, "%.3f"); ImGui.SliderFloat("Ambient G", ref ambient.Y, 0.0f, 0.5f, "%.3f"); ImGui.SliderFloat("Ambient B", ref ambient.Z, 0.0f, 0.5f, "%.3f"); vkRenderer.ShadowBias = bias; vkRenderer.ShadowSampleRadius = sampleRadius; vkRenderer.ShadowFarPlane = farPlane; vkRenderer.AmbientColor = ambient; ImGui.Text($"Current: bias={bias:F4} radius={sampleRadius:F4} far={farPlane:F1}"); if (ImGui.Button("Copy Parameters to Clipboard")) { var sb = new System.Text.StringBuilder(); sb.AppendLine($"shadowBias={bias:F4}"); sb.AppendLine($"shadowSampleRadius={sampleRadius:F4}"); sb.AppendLine($"shadowFarPlane={farPlane:F1}"); sb.AppendLine($"shadowMapSize=1024"); sb.AppendLine($"numLights=3"); for (int li = 1; li <= 3; li++) { var le = world.Lookup(li == 1 ? "MainLight" : li == 2 ? "SecondLight" : "ThirdLight"); if ((ulong)le.Id != 0) { var lc = le.Get(); sb.AppendLine($"light{li}Intensity={lc.Intensity:F1}"); sb.AppendLine($"light{li}Range={lc.Range:F1}"); sb.AppendLine($"light{li}Color=({lc.Color.X:F2},{lc.Color.Y:F2},{lc.Color.Z:F2})"); } } var paramsText = sb.ToString(); ImGui.SetClipboardText(paramsText); Console.WriteLine("[App] Parameters copied to clipboard:"); Console.WriteLine(paramsText); } ImGui.End(); // Video recording panel ImGui.Begin("Video Recording"); if (!isRecording) { if (ImGui.Button("Start Recording")) { var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); recordingPath = $"Videos/cortex_{timestamp}.mp4"; Directory.CreateDirectory("Videos"); var w = window.Width; var h = window.Height; var outputPath = recordingPath!; // ArgumentList avoids shell-specific quoting rules and works // for paths on both Windows and Linux. var psi = new ProcessStartInfo { FileName = "ffmpeg", UseShellExecute = false, RedirectStandardInput = true, CreateNoWindow = true, }; psi.ArgumentList.Add("-y"); psi.ArgumentList.Add("-f"); psi.ArgumentList.Add("rawvideo"); psi.ArgumentList.Add("-pixel_format"); psi.ArgumentList.Add("bgra"); psi.ArgumentList.Add("-video_size"); psi.ArgumentList.Add($"{w}x{h}"); psi.ArgumentList.Add("-framerate"); psi.ArgumentList.Add("60"); psi.ArgumentList.Add("-i"); psi.ArgumentList.Add("-"); psi.ArgumentList.Add("-c:v"); psi.ArgumentList.Add("libx264"); psi.ArgumentList.Add("-preset"); psi.ArgumentList.Add("fast"); psi.ArgumentList.Add("-crf"); psi.ArgumentList.Add("23"); psi.ArgumentList.Add("-pix_fmt"); psi.ArgumentList.Add("yuv420p"); psi.ArgumentList.Add("-vf"); psi.ArgumentList.Add("fps=30"); psi.ArgumentList.Add(outputPath); try { ffmpegProcess = Process.Start(psi); isRecording = true; recordedFrames = 0; Console.WriteLine($"[App] Recording started: {recordingPath}"); } catch (Exception ex) { Console.WriteLine($"[App] Failed to start FFmpeg: {ex.Message}"); Console.WriteLine("[App] Install FFmpeg and ensure the ffmpeg executable is available on PATH."); } } } else { if (ImGui.Button("Stop Recording")) { isRecording = false; if (ffmpegProcess != null && !ffmpegProcess.HasExited) { ffmpegProcess.StandardInput.BaseStream.Flush(); ffmpegProcess.StandardInput.BaseStream.Close(); ffmpegProcess.WaitForExit(5000); if (!ffmpegProcess.HasExited) ffmpegProcess.Kill(); } ffmpegProcess?.Dispose(); ffmpegProcess = null; Console.WriteLine($"[App] Recording stopped: {recordingPath} ({recordedFrames} frames)"); } ImGui.SameLine(); ImGui.Text($"Recording... {recordedFrames} frames"); } ImGui.End(); renderer.EndImGuiFrame(); } // Fixed render rate: 60 FPS if (renderTimer >= renderFrameTime) { renderTimer -= renderFrameTime; if (renderTimer > renderFrameTime) renderTimer = 0; // prevent spiral of death // Set recording flag before render so renderer can capture vkRenderer!.IsRecording = isRecording; renderer.RenderWorld(world); queue.CompletePendingScreenshots(); // If recording, get captured frame and write to FFmpeg if (isRecording && ffmpegProcess != null && !ffmpegProcess.HasExited) { var frameData = vkRenderer.CapturedFrame; if (frameData != null && frameData.Length > 0) { try { ffmpegProcess.StandardInput.BaseStream.Write(frameData, 0, frameData.Length); ffmpegProcess.StandardInput.BaseStream.Flush(); recordedFrames++; } catch (Exception ex) { Console.WriteLine($"[App] FFmpeg write error: {ex.Message}"); isRecording = false; } } } frames++; } if (timing.TotalTime - lastFpsTime >= 1.0) { Console.WriteLine($"FPS: {frames}, Delta: {timing.DeltaTime * 1000.0:F2} ms"); frames = 0; lastFpsTime = timing.TotalTime; } } Console.WriteLine("Shutting down..."); #if !RELEASE_AOT if (mcpApp != null) await mcpApp.StopAsync(); #endif } catch (Exception ex) { Console.WriteLine($"Fatal error: {ex}"); Environment.Exit(1); } await Task.CompletedTask; } static int ParseMcpPort(string[] args) { for (var i = 0; i < args.Length; i++) { if (args[i] == "--mcp-port" && i + 1 < args.Length && int.TryParse(args[i + 1], out var port)) return port; } return 0; } static DemoScene ParseScene(string[] args) { for (var i = 0; i < args.Length; i++) { if (args[i] != "--scene" || i + 1 >= args.Length) continue; return args[++i].ToLowerInvariant() switch { "gallery" or "kinetic-gallery" => DemoScene.KineticGallery, "sanctuary" or "solar-sanctuary" => DemoScene.SolarSanctuary, "shooter" or "arena" or "arena-strike" => DemoScene.ArenaStrike, var name => throw new ArgumentException( $"Unknown scene '{name}'. Use 'gallery', 'sanctuary', or 'shooter'.") }; } return DemoScene.KineticGallery; } static string GetSceneName(DemoScene scene) => scene switch { DemoScene.KineticGallery => "Kinetic Gallery", DemoScene.SolarSanctuary => "Solar Sanctuary", DemoScene.ArenaStrike => "Arena Strike", _ => scene.ToString(), }; static void ConfigureCamera(Entity cameraEntity, DemoScene scene) { var camera = cameraEntity.Get(); if (scene == DemoScene.SolarSanctuary) { camera.Position = new Vector3(18, 12, -22); camera.Target = new Vector3(0, 5, 0); } else if (scene == DemoScene.ArenaStrike) { camera.Position = new Vector3(0, 2.3f, -17f); camera.Target = new Vector3(0, 2.3f, -5f); } else { camera.Position = new Vector3(0, 5, -15); camera.Target = new Vector3(0, 0.5f, 0); } cameraEntity.Set(camera); } static void CreateScene(World world, DemoScene scene, FpsShooterGame? shooter) { // Floor world.Entity("Floor") .Set(new Transform(new Vector3(0, -0.5f, 0), Quaternion.Identity, new Vector3(20, 0.5f, 20))) .Set(LoadMesh("Content/cube.obj", new Vector3(0.3f, 0.3f, 0.35f))) .Set(RigidBody.StaticBox(new Vector3(20, 0.5f, 20))); CreateSceneObjects(world, scene, shooter); // Lights (3 dynamic point lights, all cast shadows) world.Entity("MainLight") .Set(new Transform(new Vector3(0, 20, 0), Quaternion.Identity, Vector3.One)) .Set(Light.Point(new Vector3(0, 20, 0), new Vector3(1.0f, 0.95f, 0.85f), intensity: 8.0f, range: 15.5f)); world.Entity("SecondLight") .Set(new Transform(new Vector3(-12, 10, 8), Quaternion.Identity, Vector3.One)) .Set(Light.Point(new Vector3(-12, 10, 8), new Vector3(0.3f, 0.6f, 1.0f), intensity: 8.0f, range: 15.5f)); world.Entity("ThirdLight") .Set(new Transform(new Vector3(10, 8, -10), Quaternion.Identity, Vector3.One)) .Set(Light.Point(new Vector3(10, 8, -10), new Vector3(0.9f, 0.2f, 0.3f), intensity: 8.0f, range: 15.5f)); var entityCount = 0; world.Each((Entity e, ref Transform _) => entityCount++); Console.WriteLine($"[Scene] {entityCount} entities"); } static void CreateSceneObjects(World world, DemoScene scene, FpsShooterGame? shooter = null) { if (scene == DemoScene.ArenaStrike) (shooter ?? throw new InvalidOperationException("Shooter scene requires its game state.")).CreateArena(); else if (scene == DemoScene.SolarSanctuary) CreateSolarSanctuary(world); else CreateKineticGallery(world); } // The original scene remains available as the default demo. static void CreateKineticGallery(World world) { // Central torus knot (floating, no physics) var torusKnot = LoadMesh("Content/torusknot.obj", new Vector3(0.9f, 0.7f, 0.3f)); world.Entity("TorusKnot") .Set(new Transform(new Vector3(0, 8, 0), Quaternion.Identity, new Vector3(1.5f))) .Set(torusKnot); // Sphere pyramid (7 layers, physics) var sphereMesh = LoadMesh("Content/sphere.obj", new Vector3(0.5f, 0.6f, 0.9f)); for (var layer = 0; layer < 7; layer++) { var count = 7 - layer; var y = layer * 1.2f + 1; for (var i = 0; i < count; i++) { var x = (i - (count - 1) * 0.5f) * 1.2f; world.Entity($"Pyramid_{layer}_{i}") .Set(new Transform(new Vector3(x, y, 0), Quaternion.Identity, new Vector3(0.8f))) .Set(sphereMesh) .Set(RigidBody.DynamicSphere(0.4f, mass: 0.5f)); } } // Diamond rain (30 octahedrons, physics) var diamondMesh = LoadMesh("Content/diamond.obj", new Vector3(0.9f, 0.1f, 0.9f)); var rand = new Random(123); for (var i = 0; i < 30; i++) { var x = (float)(rand.NextDouble() * 20 - 10); var y = (float)(rand.NextDouble() * 15 + 15); var z = (float)(rand.NextDouble() * 20 - 10); world.Entity($"Diamond{i}") .Set(new Transform(new Vector3(x, y, z), Quaternion.Identity, new Vector3(0.8f))) .Set(diamondMesh) .Set(RigidBody.DynamicBox(new Vector3(0.4f), mass: 0.6f)); } // Floating torus rings (decorative, no physics) var torusMesh = LoadMesh("Content/torus.obj", new Vector3(0.2f, 0.8f, 0.3f)); for (var i = 0; i < 8; i++) { var angle = i * MathF.PI * 2 / 8; var x = MathF.Cos(angle) * 8; var z = MathF.Sin(angle) * 8; var y = (i % 3) + 3; world.Entity($"Torus{i}") .Set(new Transform(new Vector3(x, y, z), Quaternion.CreateFromAxisAngle(Vector3.UnitY, angle), new Vector3(2f))) .Set(torusMesh); } // Pyramid decorations (4 corners, static) var pyramidMesh = LoadMesh("Content/pyramid.obj", new Vector3(0.8f, 0.5f, 0.2f)); for (var i = 0; i < 4; i++) { var angle = i * MathF.PI * 2 / 4; var x = MathF.Cos(angle) * 12; var z = MathF.Sin(angle) * 12; world.Entity($"Pyramid{i}") .Set(new Transform(new Vector3(x, 0, z), Quaternion.Identity, new Vector3(2.5f))) .Set(pyramidMesh); } // Cone pillars (4 diagonals, static) var coneMesh = LoadMesh("Content/cone.obj", new Vector3(0.2f, 0.3f, 0.8f)); for (var i = 0; i < 4; i++) { var angle = i * MathF.PI * 2 / 4 + MathF.PI / 4; var x = MathF.Cos(angle) * 10; var z = MathF.Sin(angle) * 10; world.Entity($"Cone{i}") .Set(new Transform(new Vector3(x, 0, z), Quaternion.Identity, new Vector3(2f, 3f, 2f))) .Set(coneMesh); } } // A calmer architectural contrast to the physics-heavy Kinetic Gallery: // a luminous core, three tilted orbital rings, a circle of obelisks, and a // small stream of physical "comets" falling into the sanctuary. static void CreateSolarSanctuary(World world) { var goldKnot = LoadMesh("Content/torusknot.obj", new Vector3(1.0f, 0.48f, 0.08f)); world.Entity("SolarCore") .Set(new Transform(new Vector3(0, 7, 0), Quaternion.CreateFromAxisAngle(Vector3.UnitY, 0.35f), new Vector3(2.4f))) .Set(goldKnot); var ringMesh = LoadMesh("Content/torus.obj", new Vector3(0.08f, 0.62f, 1.0f)); var ringRotations = new[] { Quaternion.CreateFromYawPitchRoll(0.15f, 0.55f, 0.05f), Quaternion.CreateFromYawPitchRoll(1.25f, -0.38f, 0.45f), Quaternion.CreateFromYawPitchRoll(2.15f, 0.18f, -0.62f), }; for (var i = 0; i < ringRotations.Length; i++) { world.Entity($"SolarOrbit_{i}") .Set(new Transform(new Vector3(0, 7, 0), ringRotations[i], new Vector3(4.5f + i * 1.15f))) .Set(ringMesh); } var obeliskMesh = LoadMesh("Content/pyramid.obj", new Vector3(0.82f, 0.16f, 0.06f)); for (var i = 0; i < 10; i++) { var angle = i * MathF.Tau / 10f; var position = new Vector3(MathF.Cos(angle) * 13f, 2.6f, MathF.Sin(angle) * 13f); world.Entity($"SunObelisk_{i}") .Set(new Transform(position, Quaternion.CreateFromAxisAngle(Vector3.UnitY, -angle), new Vector3(1.8f, 4.8f, 1.8f))) .Set(obeliskMesh) .Set(RigidBody.StaticBox(new Vector3(0.9f, 2.4f, 0.9f))); } var portalMesh = LoadMesh("Content/cone.obj", new Vector3(0.18f, 0.08f, 0.5f)); for (var i = 0; i < 4; i++) { var angle = i * MathF.Tau / 4f + MathF.PI / 4f; var position = new Vector3(MathF.Cos(angle) * 8.5f, 2f, MathF.Sin(angle) * 8.5f); world.Entity($"SanctuarySpire_{i}") .Set(new Transform(position, Quaternion.CreateFromAxisAngle(Vector3.UnitY, -angle), new Vector3(2.2f, 3.5f, 2.2f))) .Set(portalMesh); } var moonMesh = LoadMesh("Content/sphere.obj", new Vector3(0.38f, 0.75f, 1.0f)); for (var i = 0; i < 12; i++) { var angle = i * MathF.Tau / 12f; var radius = 5.6f + (i % 3) * 0.8f; world.Entity($"SanctuaryMoon_{i}") .Set(new Transform( new Vector3(MathF.Cos(angle) * radius, 4.2f + (i % 2) * 1.4f, MathF.Sin(angle) * radius), Quaternion.Identity, new Vector3(0.7f))) .Set(moonMesh); } var cometMesh = LoadMesh("Content/diamond.obj", new Vector3(1.0f, 0.12f, 0.35f)); var random = new Random(20260719); for (var i = 0; i < 18; i++) { var angle = (float)random.NextDouble() * MathF.Tau; var radius = 3.0f + (float)random.NextDouble() * 8f; world.Entity($"SolarComet_{i}") .Set(new Transform( new Vector3(MathF.Cos(angle) * radius, 12f + i * 0.75f, MathF.Sin(angle) * radius), Quaternion.CreateFromYawPitchRoll(angle, angle * 0.5f, 0), new Vector3(0.52f))) .Set(cometMesh) .Set(RigidBody.DynamicBox(new Vector3(0.28f), mass: 0.35f)); } } static Mesh LoadMesh(string path, Vector3 color) { if (!File.Exists(path)) { var altPath = Path.Combine(AppContext.BaseDirectory, path); if (!File.Exists(altPath)) { altPath = Path.Combine(AppContext.BaseDirectory, "Content", Path.GetFileName(path)); if (!File.Exists(altPath)) throw new FileNotFoundException($"Mesh file not found: {path}"); } return ObjLoader.Load(altPath, color); } return ObjLoader.Load(path, color); } class DummyScreenshotProvider : Engine.Core.IScreenshotProvider { public Task CaptureAsync(string outputPath) => Task.FromResult(Array.Empty()); } }