feat: materials, floor grid, specular lighting, orbit camera
- Add Material component with Albedo, Roughness, Metallic. - MeshRenderer reads Material and tints vertex color; falls back to default. - Add Blinn-Phong specular using camera position pushed via push constants. - Add floor plane and grid mesh in Program.cs. - Add OrbitCameraController with right-mouse orbit and mouse-wheel zoom. - Forward SDL events to InputMapping from Sdl3Window.PumpEvents. - Update main loop to update camera and aspect ratio on resize. - Recompile SPIR-V shaders. - Update CORTEX_ENGINE_ARCHITECTURE.md with Material, lighting, and orbit camera.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Engine.AI;
|
||||
@@ -17,12 +18,12 @@ class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Cortex Engine Step 6 — MCP integration...");
|
||||
Console.WriteLine("Cortex Engine — Materials, Grid, Lighting, Orbit Camera...");
|
||||
|
||||
try
|
||||
{
|
||||
using var world = World.Create();
|
||||
using var window = new Sdl3Window("Cortex Engine — Step 6", 1280, 720);
|
||||
using var window = new Sdl3Window("Cortex Engine", 1280, 720);
|
||||
var timing = new Timing();
|
||||
var input = new InputMapping();
|
||||
using var vulkan = new VulkanContext(window, enableValidation: false);
|
||||
@@ -35,13 +36,9 @@ class Program
|
||||
var processor = new AiCommandProcessor(world, LoadModel, path => renderer.RequestScreenshot(path));
|
||||
var queue = new AiCommandQueue(processor);
|
||||
|
||||
var model = world.Entity("Model")
|
||||
.Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, new Vector3(0.5f)))
|
||||
.Set(mesh);
|
||||
|
||||
var camera = world.Entity("Camera")
|
||||
var cameraEntity = world.Entity("Camera")
|
||||
.Set(new Camera(
|
||||
new Vector3(0.0f, 0.0f, -2.0f),
|
||||
new Vector3(0.0f, 1.5f, -3.0f),
|
||||
Vector3.Zero,
|
||||
Vector3.UnitY,
|
||||
MathF.PI / 4.0f,
|
||||
@@ -49,12 +46,34 @@ class Program
|
||||
0.1f,
|
||||
100.0f));
|
||||
|
||||
var orbit = new OrbitCameraController(cameraEntity, Vector3.Zero);
|
||||
|
||||
var model = world.Entity("Model")
|
||||
.Set(new Transform(new Vector3(0.0f, 0.5f, 0.0f), Quaternion.Identity, new Vector3(0.5f)))
|
||||
.Set(mesh)
|
||||
.Set(new Material(new Vector3(0.9f, 0.6f, 0.3f), roughness: 0.4f, metallic: 0.1f));
|
||||
|
||||
var floor = world.Entity("Floor")
|
||||
.Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f), Quaternion.Identity, Vector3.One))
|
||||
.Set(CreateFloorMesh(20.0f, new Vector3(0.08f, 0.08f, 0.1f)))
|
||||
.Set(new Material(new Vector3(0.08f, 0.08f, 0.1f), roughness: 0.9f, metallic: 0.0f));
|
||||
|
||||
var grid = world.Entity("Grid")
|
||||
.Set(new Transform(new Vector3(0.0f, 0.01f, 0.0f), Quaternion.Identity, Vector3.One))
|
||||
.Set(CreateGridMesh(20, 0.5f, new Vector3(0.5f, 0.5f, 0.55f)))
|
||||
.Set(new Material(new Vector3(0.5f, 0.5f, 0.55f), roughness: 0.9f, metallic: 0.0f));
|
||||
|
||||
// Demo: local AI commands processed on the main thread.
|
||||
Console.WriteLine("AI demo commands:");
|
||||
Console.WriteLine(processor.Process("""{ "type": "list_entities" }""").Message);
|
||||
Console.WriteLine(processor.Process("""{ "type": "spawn_model", "name": "SecondCube", "modelPath": "Content/cube.obj", "position": [0.8, 0, 0], "scale": [0.3, 0.3, 0.3] }""").Message);
|
||||
Console.WriteLine(processor.Process("""{ "type": "list_entities" }""").Message);
|
||||
Console.WriteLine(processor.Process("""{ "type": "set_transform", "name": "SecondCube", "position": [0.8, 0.5, 0], "rotation": [0, 0, 0, 1], "scale": [0.3, 0.3, 0.3] }""").Message);
|
||||
|
||||
var secondCube = world.Lookup("SecondCube");
|
||||
if ((ulong)secondCube.Id != 0)
|
||||
secondCube.Set(new Material(new Vector3(0.3f, 0.7f, 0.9f), roughness: 0.3f, metallic: 0.2f));
|
||||
|
||||
Console.WriteLine(processor.Process("""{ "type": "list_entities" }""").Message);
|
||||
Console.WriteLine(processor.Process("""{ "type": "capture_screenshot", "outputPath": "Screenshots/demo.png" }""").Message);
|
||||
|
||||
#if !RELEASE_AOT
|
||||
@@ -81,7 +100,7 @@ class Program
|
||||
while (!window.ShouldClose)
|
||||
{
|
||||
timing.Tick();
|
||||
window.PumpEvents();
|
||||
window.PumpEvents(input);
|
||||
input.BeginFrame();
|
||||
|
||||
// Drain any commands that arrived from the MCP server.
|
||||
@@ -94,12 +113,17 @@ class Program
|
||||
lastWidth = window.Width;
|
||||
lastHeight = window.Height;
|
||||
swapchain.Recreate(lastWidth, lastHeight);
|
||||
ref var camera = ref cameraEntity.Ensure<Camera>();
|
||||
camera.AspectRatio = (float)lastWidth / lastHeight;
|
||||
}
|
||||
|
||||
// Update orbit camera from mouse input.
|
||||
orbit.Update(input, (float)timing.DeltaTime);
|
||||
|
||||
// Slowly rotate the model so we can see it in 3D.
|
||||
ref var transform = ref model.Ensure<Transform>();
|
||||
transform.Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)timing.TotalTime * 0.5f)
|
||||
* Quaternion.CreateFromAxisAngle(Vector3.UnitX, (float)timing.TotalTime * 0.25f);
|
||||
ref var modelTransform = ref model.Ensure<Transform>();
|
||||
modelTransform.Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitY, (float)timing.TotalTime * 0.5f)
|
||||
* Quaternion.CreateFromAxisAngle(Vector3.UnitX, (float)timing.TotalTime * 0.25f);
|
||||
|
||||
renderer.RenderWorld(world);
|
||||
|
||||
@@ -135,6 +159,57 @@ class Program
|
||||
: ObjLoader.Load(path, new Vector3(0.7f, 0.6f, 0.5f));
|
||||
}
|
||||
|
||||
private static Mesh CreateGridMesh(int lines, float spacing, Vector3 color)
|
||||
{
|
||||
var vertices = new List<Vertex>();
|
||||
var indices = new List<uint>();
|
||||
var extent = lines * spacing;
|
||||
var normal = Vector3.UnitY;
|
||||
var halfWidth = 0.02f;
|
||||
|
||||
for (var i = -lines; i <= lines; i++)
|
||||
{
|
||||
var offset = i * spacing;
|
||||
|
||||
// Line parallel to X axis as a thin quad.
|
||||
var baseIndex = (uint)vertices.Count;
|
||||
vertices.Add(new Vertex(new Vector3(-extent, 0, offset - halfWidth), color, normal));
|
||||
vertices.Add(new Vertex(new Vector3(extent, 0, offset - halfWidth), color, normal));
|
||||
vertices.Add(new Vertex(new Vector3(extent, 0, offset + halfWidth), color, normal));
|
||||
vertices.Add(new Vertex(new Vector3(-extent, 0, offset + halfWidth), color, normal));
|
||||
indices.Add(baseIndex); indices.Add(baseIndex + 1); indices.Add(baseIndex + 2);
|
||||
indices.Add(baseIndex); indices.Add(baseIndex + 2); indices.Add(baseIndex + 3);
|
||||
|
||||
// Line parallel to Z axis as a thin quad.
|
||||
baseIndex = (uint)vertices.Count;
|
||||
vertices.Add(new Vertex(new Vector3(offset - halfWidth, 0, -extent), color, normal));
|
||||
vertices.Add(new Vertex(new Vector3(offset + halfWidth, 0, -extent), color, normal));
|
||||
vertices.Add(new Vertex(new Vector3(offset + halfWidth, 0, extent), color, normal));
|
||||
vertices.Add(new Vertex(new Vector3(offset - halfWidth, 0, extent), color, normal));
|
||||
indices.Add(baseIndex); indices.Add(baseIndex + 1); indices.Add(baseIndex + 2);
|
||||
indices.Add(baseIndex); indices.Add(baseIndex + 2); indices.Add(baseIndex + 3);
|
||||
}
|
||||
|
||||
return new Mesh(vertices.ToArray(), indices.ToArray());
|
||||
}
|
||||
|
||||
private static Mesh CreateFloorMesh(float size, Vector3 color)
|
||||
{
|
||||
var half = size / 2.0f;
|
||||
var normal = Vector3.UnitY;
|
||||
|
||||
var vertices = new Vertex[]
|
||||
{
|
||||
new(new Vector3(-half, 0, -half), color, normal),
|
||||
new(new Vector3(half, 0, -half), color, normal),
|
||||
new(new Vector3(half, 0, half), color, normal),
|
||||
new(new Vector3(-half, 0, half), color, normal)
|
||||
};
|
||||
|
||||
var indices = new uint[] { 0, 1, 2, 0, 2, 3 };
|
||||
return new Mesh(vertices, indices);
|
||||
}
|
||||
|
||||
private static (string modelPath, int mcpPort) ParseArgs(string[] args)
|
||||
{
|
||||
var modelPath = FindModelPath(args);
|
||||
|
||||
Reference in New Issue
Block a user