Commit Graph
100 Commits
Author SHA1 Message Date
emil28092005 ed919edb0a fix: enable back-face culling — removes self-shadowing artifacts
Root cause: CULL_MODE_NONE in both main and shadow pipelines caused
back faces to render, creating self-shadowing acne on all objects:
- Shadow map: back faces written at closer depth → false shadows on front
- Main render: back faces visible through objects → wrong lighting

Fix:
- Main pipeline: CULL_MODE_BACK (was None) — only front faces render
- Shadow pipeline: CULL_MODE_BACK (was None) — only front faces cast shadows
- Removed normal flip hack (if dot(N,V) < 0 N = -N) — no longer needed
  with proper culling + correct CCW winding in cube.obj
- This is the standard approach: correct geometry + back-face culling
2026-06-18 22:00:03 +03:00
emil28092005 d2078f3ee0 fix: cube.obj winding order — all faces now CCW from outside
Root cause: ALL 12 faces in cube.obj had reversed winding order.
This caused every face normal to point inward, breaking:
- Diffuse lighting (NdotL < 0 → no light on any face)
- Shadow bias (slope-dependent bias used wrong NdotL)
- Shadow self-shadowing (both sides rendered with CULL_NONE)

Fix: reversed all face indices to correct CCW winding:
- Back face: f 1 4 3, f 1 3 2 (was f 1 2 3, f 1 3 4)
- Front face: f 5 6 7, f 5 7 8 (was f 5 7 6, f 5 8 7)
- Left/Right/Bottom/Top: all reversed

Now MeshMath.ComputeFaceNormal produces correct outward normals.
Normal flip in shader (dot(N,V) < 0) is still kept as safety for
double-sided rendering, but cubes now have correct normals natively.
2026-06-18 21:57:32 +03:00
emil28092005 6a473bdc53 fix: flip normal only by view direction — fixes both cubes and spheres
Previous fix flipped normal by both light AND view direction:
  if (dot(N,L) < 0) N = -N;
  if (dot(N,V) < 0) N = -N;
This double-flip broke spheres: when light is behind sphere, front faces
get N flipped by light (now facing away from camera), then flipped back
by view — but back faces get flipped once, causing inconsistency.

Correct approach: flip ONLY by view direction:
  if (dot(N, V) < 0.0) N = -N;
This ensures the normal always faces the camera. Faces pointing away
from light naturally get NdotL=0 (no lighting) — correct behavior.
Spheres keep smooth normals, cubes get inward normals fixed.
2026-06-18 21:56:10 +03:00
emil28092005 2f7430e3b1 fix: flip normals when facing away from light/view — fixes cube lighting
Root cause: cube.obj has inconsistent winding order. Many faces have
normals pointing inward (e.g. back face normal = +Z instead of -Z).
With cullMode=None, both sides render, but inward normals cause:
- No diffuse lighting (NdotL < 0 → max() = 0)
- Wrong shadow bias (slope-dependent bias uses wrong NdotL)
- Inconsistent shadow edges on cube faces

Fix: in fragment shader, flip normal if dot(N,L) < 0 or dot(N,V) < 0
This is the standard approach for double-sided rendering with
non-watertight geometry or inconsistent winding order.
2026-06-18 21:53:15 +03:00
emil28092005 203c22fa98 fix: feed mouse input to ImGui — sliders and buttons now clickable
- ImGui.GetIO().MousePos set from IInputState.MouseX/MouseY each frame
- ImGui.GetIO().MouseDown[0-2] set from MouseLeft/Right/Middle
- DisplaySize also updated each frame (was only set at init)
- Done before BeginImGuiFrame() / ImGui.NewFrame()
2026-06-18 21:50:08 +03:00
emil28092005 29a58d204b feat: ImGui shadow parameters panel with clipboard copy
- Push constant block expanded: 160B → 176B (added vec4 shadowParams)
- shadowParams: x=bias, y=sampleRadius, z=farPlane, w=unused
- Fragment shader: bias and sampleRadius now read from push constants
  (dynamic, adjustable at runtime)
- Shadow.frag: uses pc.shadowParams.z for farPlane (dynamic)
- Slope-dependent bias: bias + bias*2*(1-NdotL) — adaptive
- VulkanRenderer: public ShadowBias, ShadowSampleRadius, ShadowFarPlane properties
- Program.cs: ImGui 'Shadow Parameters' panel with sliders:
  - Light Intensity (0-100), Range (5-100), RGB color
  - Shadow Bias (0.001-0.5), Sample Radius (0.001-0.1), Far Plane (10-120)
  - 'Copy Parameters to Clipboard' button — outputs all values as text
- 227 tests, all pass
2026-06-18 21:48:38 +03:00
emil28092005 8918126c58 fix: increase shadow cubemap resolution to 2048 — sharper edges
- ShadowMapSize: 1024 → 2048 (per face, 6 faces)
- 4x more texels per face — less aliasing on cube edges
- Memory: 6 * 2048² * 4B (R32) + 6 * 2048² * 4B (D32) = ~192MB
2026-06-18 21:42:23 +03:00
emil28092005 2454af1c89 fix: balanced shadow bias — no acne, no peter-panning
- Fragment bias: 0.08 + 0.15 * (1 - NdotL) — smaller base, moderate slope
  (was 0.3 + 0.5 — too much → peter-panning, shadows detached from objects)
- vkCmdSetDepthBias: 1.75/0/2.5 (was 2.5/0/3.5 — too aggressive)
- Removed early exit logic — was causing hard shadow edges
- Always run 16-tap PCF for consistent soft shadow quality
- Smaller sample radius: clamp(0.015 * dist/20, 0.003, 0.05)
2026-06-18 21:40:43 +03:00
emil28092005 23b38f8f17 fix: increase shadow bias — slope-dependent + larger depth bias
- Fragment shader: bias = 0.3 + 0.5 * (1 - NdotL) — slope-dependent
  Grazing angles get larger bias to prevent acne on cube faces
- vkCmdSetDepthBias: 2.5/0/3.5 (was 1.25/0/1.75) — more aggressive
  polygon offset for shadow map rendering
- Early exit tolerance reduced from 4x to 3x bias
2026-06-18 21:38:48 +03:00
emil28092005 7e33b64ac9 feat: soft shadows — 16-tap Poisson disk PCF on cubemap
- 16 precomputed Poisson disk offsets on tangent plane
- Tangent basis built from sampling direction (cross product trick)
- Sample radius scales with distance: closer = sharper, farther = softer
  radius = clamp(0.02 * (dist / 20), 0.005, 0.08)
- Early exit: skip PCF if clearly lit or clearly shadowed (performance)
- Bias increased to 0.15 for cubemap distance comparison
- Only triangle.frag changed — no C# or Vulkan resource changes
- 227 tests, all pass
2026-06-18 21:36:30 +03:00
emil28092005 f761973443 fix: Vulkan cubemap face directions — Y-up vectors corrected
Root cause: used OpenGL cubemap conventions (up=+Y) instead of Vulkan (up=-Y)
for X and Z faces. Also applied M22 *= -1 flip to shadow projection which
is wrong — that flip is only for swapchain rendering, not for offscreen
cubemap rendering.

Fix:
- Faces 0,1,4,5 (+X,-X,+Z,-Z): up = (0,-1,0) — Vulkan cubemap convention
- Face 2 (+Y): up = (0,0,1) — was (0,0,-1), now correct
- Face 3 (-Y): up = (0,0,-1) — was (0,0,1), now correct
- Removed M22 *= -1 from shadow projection (not needed for offscreen cube)
- 227 tests, all pass
2026-06-18 21:32:18 +03:00
emil28092005 a2000b55b5 feat: cubemap shadows with R32_SFLOAT color attachment — linear distance
Root cause of broken shadows: depth buffer stores non-linear NDC depth,
not linear distance. closestDepth * 60.0 was wrong conversion.

Fix: switch from depth-only to R32_SFLOAT color attachment approach:
- Shadow vertex shader outputs world position to fragment
- Shadow fragment shader writes length(fragPos - lightPos) / farPlane
- Main fragment shader samples cubemap, multiplies by FAR_PLANE=60
- Separate color cube (R32_SFLOAT, sampled) + depth cube (D32_SFLOAT, depth test)
- Shadow pipeline: 1 color attachment (R) + depth attachment
- Color clear = 1.0 (max distance), depth clear = 1.0

Tests: 227 total, all pass
- ShadowMapFaceDirectionTests: 6 face directions, 90° FOV, up vectors,
  valid matrices, far plane consistency
- ShadowShaderTests: all 6 SPIR-V shaders exist
2026-06-18 21:27:48 +03:00
emil28092005 a572f9d360 fix: transition all 6 cubemap layers — shadows now work on floor
Root cause: TransitionImageLayoutDepth used LayerCount=1, so only
layer 0 of the 6-layer cubemap was transitioned to ShaderReadOnlyOptimal.
Layers 1-5 stayed in Undefined/DepthStencilAttachmentOptimal → sampling
returned garbage → no shadows on surfaces facing those directions.

Fix: added layerCount parameter to TransitionImageLayoutDepth (default=1).
Shadow cubemap transitions use layerCount=6.
2026-06-18 21:15:50 +03:00
emil28092005 fa937ecd94 feat: cubemap shadow mapping — true omnidirectional point light shadows
- VulkanShadowMap: 1024x1024x6 layer D32_SFLOAT cube image
  - CubeCompatible flag, cube view for sampling, 6 face views for rendering
  - GetFaceViewProj: 6 directions (+X, -X, +Y, -Y, +Z, -Z) with correct up vectors
- 6 shadow render passes per frame (one per cube face)
- Fragment shader: samplerCube instead of sampler2D
  - Shadow: direction from light to fragment, distance comparison
  - No more perspective frustum limitation — omnidirectional shadows
- shadow.vert: same push constant block (160B), uses lightViewProj per face
- triangle.vert: removed fragLightSpacePos (not needed for cubemap)
- Reduced shadow map size to 1024 (6x memory vs single 2048)
- Floor/Grid excluded from shadow casting
2026-06-18 20:35:01 +03:00
emil28092005 c32c75cd69 fix: single push constant range (0-160, Vertex|Fragment) — fixes validation errors
- Vulkan spec: each stage can only appear in ONE push constant range
- Merged 3 ranges into 1: offset=0, size=160, stageFlags=Vertex|Fragment
- Both main and shadow pipelines use same single range
- Push constants packed into 160-byte buffer and sent in one call
- Shadow pass: restored vertex/index buffer binding + push constants
- Fixes: device lost, validation errors, missing shadow geometry
2026-06-18 20:21:54 +03:00
emil28092005 dd86f6e9ad fix: exclude Floor and Grid from shadow pass — no more blocked light
- drawCalls now include castShadow flag (bool)
- Floor and Grid entities: castShadow=false (skip in shadow pass)
- Shadow pass: foreach with 'if (!dc.castShadow) continue;'
- Main pass: renders all objects (floor receives shadows but doesn't cast)
2026-06-18 20:19:16 +03:00
emil28092005 df4b30ecc3 feat: light moves in chaotic circle — dynamic shadows
- Light position animated with sin/cos combinations at different frequencies
- XZ plane: figure-8 pattern (sin*0.7 + cos*0.3, cos*0.6 + sin*0.4)
- Y: oscillates 7-17 with sin*0.5
- Shadows move dynamically as light orbits the scene
2026-06-18 20:15:36 +03:00
emil28092005 d4eafe1f31 fix: reduce light intensity from 50 to 15 2026-06-18 20:13:46 +03:00
emil28092005 7c33a52ff7 feat: static light at top (y=20) instead of physics ball
- Light moved to (0, 20, 0) — static, no physics body
- Intensity 50, range 60, warm white color
- Shadow map renders from this fixed position
- LightBall removed, replaced with MainLight entity
2026-06-18 20:12:59 +03:00
emil28092005 e6e305dfa9 feat: shadow mapping — depth-only render pass from light POV + PCF
- VulkanShadowMap.cs: 2048x2048 D32_SFLOAT image, sampler (clamp-to-border),
  separate shadow pipeline (depth-only, no color, depth bias enabled, CULL_NONE)
- Shadow shaders: shadow.vert (lightViewProj * model * pos), shadow.frag (empty)
- Main shaders updated: fragLightSpacePos output from vertex, PCF 3x3 in fragment
- Push constants expanded to 160B: model(64) + lightPos(16) + lightColor(16) + lightViewProj(64)
- 3 push constant ranges: Vertex(model), Fragment(light), Vertex|Fragment(lightViewProj)
- Descriptor set: 2 bindings — UBO(vp) + CombinedImageSampler(shadowMap)
- Shadow pass: before main pass, renders scene depth from light position
- Image transitions: shadow map UNDEFINED→DEPTH→SHADER_READ_ONLY each frame
- vkCmdSetDepthBias(1.25, 0, 1.75) for acne prevention
- Light VP: CreateLookAt(lightPos, origin, up) * Perspective(60°, 1.0, 0.1, 60)
- 0 validation errors on build
2026-06-18 20:09:32 +03:00
emil28092005 1dde764342 fix: move light data from UBO to push constants — fixes std140 layout issue
Root cause: GLSL std140 layout adds padding after mat4 for vec4 fields,
causing light data to be read at wrong offsets → NaN/artifacts.

Fix:
- UBO back to 64 bytes (mat4 vp only)
- Light data (pos + color, 32 bytes) sent via push constants at offset 64
- Two push constant ranges: Vertex (0-64, model) + Fragment (64-96, light)
- Both vertex and fragment shaders read from same push_constant block
- PBR shader fully restored with Cook-Torrance BRDF
- Dynamic point light follows physics ball
2026-06-18 19:53:45 +03:00
emil28092005 ad8a14a4f7 debug: hardcoded light in fragment shader — no UBO light data
Light at (0,10,0) hardcoded in GLSL. Tests if lighting math works.
If light works: problem is UBO layout for light data (std140 padding).
If artifacts: problem is in fragWorldPos or fragNormal.
2026-06-18 19:50:20 +03:00
emil28092005 7ed81040ed debug: simple diffuse point light — test if light data reaches fragment 2026-06-18 19:47:29 +03:00
emil28092005 b162d681aa debug: albedo-only fragment shader — tests if vp matrix works with 128B UBO
If objects are colored: vp matrix is fine, problem is in lighting
If objects are black: vp matrix is broken by 128B UBO change
2026-06-18 19:46:05 +03:00
emil28092005 4771371021 fix: restore MemoryCopy for UBO + PBR shader with point light
- UBO packing: MemoryCopy for vp (64B) + lightPos (16B) + lightColor (16B)
- PBR fragment shader with Cook-Torrance BRDF + point light
- No directional light, near-zero ambient — only point light illuminates
- Guards against div-by-zero in attenuation and specular
- Depth buffer properly configured in pipeline + rendering info
2026-06-18 19:43:31 +03:00
emil28092005 e37e7a4eed debug: fragment shader outputs light position as color — test UBO
- If objects are colored (not black/artifacts), UBO data reaches fragment shader
- Also: manual float-by-float UBO packing instead of MemoryCopy
- vp matrix packed as M11..M44 (row-major, no row_major in GLSL = transpose)
2026-06-18 19:36:57 +03:00
emil28092005 ff8bec00f6 fix: use MemoryCopy instead of Marshal.StructureToPtr for UBO packing
- Marshal.StructureToPtr may not handle Matrix4x4 correctly (record struct)
- Replaced with MemoryCopy from local copies of vp, lightPos, lightColor
- Removed per-frame light debug logging
- Simple diffuse shader still active for testing
2026-06-18 19:35:44 +03:00
emil28092005 321b1f62cc debug: simple diffuse fragment shader to test point light UBO 2026-06-18 19:33:37 +03:00
emil28092005 aa214cb777 fix: UBO descriptor visible to fragment shader — point light now works
Root cause: descriptor set layout binding had stageFlags=Vertex only.
Fragment shader needs pointLightPos/pointLightColor from UBO but couldn't
access it. Changed to Vertex|Fragment.
2026-06-18 19:29:57 +03:00
emil28092005 905dd839c8 fix: remove static MainLight, increase LightBall intensity to 30, range 40
- Only one light source: the physics LightBall
- Intensity 30 (was 20), range 40 (was 30) for stronger illumination
- Static MainLight removed so renderer picks the dynamic ball
2026-06-18 19:28:17 +03:00
emil28092005 6a7d6ee3eb feat: single point light only — dark scene, dynamic light ball visible
- Removed directional light from fragment shader
- Ambient reduced to near-zero (0.01) — scene is dark
- Only light source is the physics LightBall falling from y=15
- Objects near the ball are illuminated, far objects are in darkness
- Dramatic contrast shows dynamic lighting clearly
2026-06-18 19:27:05 +03:00
emil28092005 e6cc9e6c4e feat: dynamic point light on a physics ball — falls and illuminates
- LightBall entity: sphere mesh + DynamicSphere physics + Light.Point
- Renderer reads light position from Transform (not static Light.Position)
  so the light moves with the physics body
- Light intensity 20, range 30, warm color (1.0, 0.9, 0.7)
- Ball falls from y=15, bounces on floor, light follows it
- Renderer: prefers Light+Transform pair for dynamic position, falls back to Light only
2026-06-18 19:25:02 +03:00
emil28092005 f718e6de49 feat: PBR point light — Unity-style attenuation, warm glow
- UBO expanded from 64 to 128 bytes: vp(64) + lightPos(16) + lightColor(16)
- Vertex shader: passes point light UBO data through
- Fragment shader: refactored calcLight() function, shared by directional + point
  - Point light: Unity-style attenuation pow(1 - dist/range, 2)
  - Directional light reduced intensity (0.4) to balance with point light
  - Point light: position (0,8,0), warm color (1,0.9,0.7), intensity 15, range 25
- Renderer: reads Light component from ECS, packs into UBO with Marshal.StructureToPtr
- Scene: MainLight entity with Light.Point at (0,8,0)
- 0 C# struct changes — pure UBO layout + shader upgrade
2026-06-18 19:23:00 +03:00
emil28092005 22748ed9ba feat: PBR shading — Cook-Torrance BRDF with ACES tonemapping
- Vertex shader: passes world position, world normal, albedo to fragment
- Fragment shader: full PBR implementation
  - D term: Trowbridge-Reitz GGX distribution
  - G term: Smith geometry with Schlick-GGX
  - F term: Schlick Fresnel approximation
  - kD/kS split based on metallic
  - Directional light (0.5, 0.8, 0.3) with warm color
  - Ambient term (0.15, 0.18, 0.22) for fill light
  - ACES filmic tonemapping
  - Gamma 2.2 correction
- Fixed roughness=0.5, metallic=0.1 (per-object materials = future)
- No C# code changes — pure shader upgrade
2026-06-18 19:18:01 +03:00
emil28092005 50e77e705b fix: MCP server starts after 3rd frame to avoid blocking Vulkan init
- MCP server (Kestrel) launched on background thread after 3 frames
  to avoid conflicting with Vulkan/SDL3 initialization
- --mcp-port 0 (default) = disabled, scene works as before
- --mcp-port 5000 = MCP starts after 3rd frame
- Reduced extra balls from 30 to 10 for stability
- Engine.AI/McpEngineServerHost reverted to original (no WebApplicationOptions)
2026-06-18 19:12:11 +03:00
emil28092005 1f7538383e feat: integrate AI/MCP — 7 tools available via --mcp-port flag
- Engine.AI referenced in CortexEngine.App.csproj
- AiCommandProcessor created with world + mesh loader + screenshot callback
- AiCommandQueue marshals commands from MCP thread to main thread
- MCP HTTP server (Kestrel) started on separate background thread
- --mcp-port N flag (default 0 = disabled) controls MCP server
- queue.ProcessPending() called before render each frame
- queue.CompletePendingScreenshots() called after render
- ImGui overlay shows MCP status (port or disabled)
- DummyScreenshotProvider placeholder (real capture = future checkpoint)
- 7 MCP tools: spawn_model, set_transform, set_material, delete_entity,
  list_entities, get_world_state, capture_screenshot
- MCP server has 2s startup delay to avoid blocking Vulkan init
- Default: MCP disabled (--mcp-port 0), scene + physics + ImGui work
2026-06-18 18:56:53 +03:00
emil28092005 bfde04b381 feat: ImGui debug overlay — FPS, camera position, entity count
- ImGui.NET 1.91.6.1 integrated into Engine.Graphics.Vulkan
- VulkanImGui.cs: font atlas upload (staging → VkImage), sampler,
  descriptor set (COMBINED_IMAGE_SAMPLER), separate pipeline with
  alpha blending, no depth write, dynamic scissor
- ImGui shaders: imgui.vert (ortho MVP push constant) + imgui.frag
  (font texture sampler)
- Dynamic vertex/index buffers (HOST_VISIBLE, grow on demand)
- IRenderer interface: BeginImGuiFrame/EndImGuiFrame default methods
- Program.cs: debug window with FPS, camera pos/target, entity count
- 0 critical validation errors, ~750 FPS with physics + ImGui
2026-06-18 17:34:42 +03:00
emil28092005 e67e312d28 feat: Jolt physics — cubes and spheres fall with gravity, hit floor
- Engine.Physics (JoltPhysicsSharp) integrated into CortexEngine.App
- 4 cubes: DynamicBox, mass proportional to scale, fall from height 5-10
- 3 spheres: DynamicSphere, mass 1.5, fall from height 7-12
- Floor: StaticBox (20x0.5x20), friction 0.8
- Torus knot + grid: no physics (visual only)
- Physics bodies initialized lazily on first frame (IsInitialized flag)
- e.Set() moved outside world.Each to avoid mutation during iteration
- physicsWorld.Update + SyncTransforms every frame
- Removed manual rotation — physics handles movement now
- Camera at (0, 5, -15) for better view of falling objects
- 0 validation errors, ~1300 FPS
2026-06-18 17:18:59 +03:00
emil28092005 609b11e3a3 feat: full engine scene — torus knot + 4 cubes + 3 spheres + floor + grid
- 10 entities in ECS World with Transform + Mesh components
- Torus knot (center, scale 1.5) rotating around Y
- 4 cubes (left/right/front/back) rotating at different speeds
- 3 spheres (procedural, 32x16) rotating around X
- Floor (20x20 flat cube) + grid (ProceduralMesh.CreateGrid)
- Camera at (0, 3, -12) looking at (0, 0.5, 0)
- Mesh cache: buffers created lazily per entity ID on first frame
- Diffuse lighting on all objects
- 0 validation errors, ~2300 FPS
2026-06-18 17:10:35 +03:00
emil28092005 e36cbc7774 feat: diffuse lighting — normals passed to fragment shader
- Vertex shader: pass normal through mat3(model) to fragment
- Fragment shader: directional light (0.5, 0.8, 0.3) with 0.25 ambient
  + 0.75 diffuse = faces now have depth and 3D form
- Torus knot has smooth normals (vn in OBJ), cubes have face normals
- 0 validation errors, ~2300 FPS
2026-06-18 17:03:15 +03:00
emil28092005 a4914f28c5 feat: ECS scene with 7 objects — torus knot + 6 rotating cubes
- VulkanRenderer: mesh cache by entity ID, lazy buffer creation
- RenderWorld iterates entities with Transform+Mesh, draws each with
  per-entity model matrix via push constants
- Program.cs: creates scene with 1 torus knot (center, scale 1.5) +
  6 cubes at different positions, all rotating around Y at different speeds
- Camera at (0, 2, -8) looking at origin, FreeFly controls
- 0 validation errors, ~2100 FPS with 7 objects
2026-06-18 17:00:14 +03:00
emil28092005 398cc88ae2 fix: remove row_major from GLSL — fixes fish-eye and broken projection
Root cause: System.Numerics.Matrix4x4 uses row-major storage with row-vector
convention (v * M). GLSL with layout(row_major) does column-vector (M * v),
which effectively transposes the matrix, breaking the projection.

Fix: remove row_major from layout qualifiers. GLSL then interprets the
row-major data as column-major (effectively transposing it), so M * v in
GLSL equals v * M in System.Numerics — correct result.

The Y-flip (M22 *= -1) is still needed for Vulkan's Y-down clip space.
2026-06-18 16:56:44 +03:00
emil28092005 44bae31495 fix: flip projection Y for Vulkan (M22 *= -1) — fixes fish-eye distortion
System.Numerics.CreatePerspectiveFieldOfView uses DirectX convention (Y up).
Vulkan clip space has Y down. Negating M22 corrects the projection.
Depth is already [0,1] in System.Numerics (DirectX-style), no remap needed.
2026-06-18 16:53:48 +03:00
emil28092005 fcbc42959e feat: FreeFlyCameraController — interactive WASD + mouse look
- Program.cs: create Camera entity in ECS World, FreeFlyCameraController
  with WASD movement, Q/E up/down, Shift boost, right-click mouse look
- VulkanRenderer.RenderWorld: queries Camera from World, computes VP
  from Camera.GetViewMatrix() * GetProjectionMatrix()
- Camera aspect ratio auto-updated on window resize
- Fallback to hardcoded VP if no Camera entity found
- Torus knot still rotating via push constant model matrix
- 0 validation errors
2026-06-18 16:49:54 +03:00
emil28092005 2886dd77f2 feat: render torus knot (4800 verts, 9600 faces, smooth normals)
- Load Content/torusknot.obj instead of cube.obj
- 28800 vertices (duplicated per-face), 28800 indices
- OBJ has vn lines so smooth normals are used (not face normals)
- Removed slide animation, kept Y-axis rotation
- Camera z=-6, FOV 45°
- 0 validation errors, ~3000 FPS
2026-06-18 16:46:27 +03:00
emil28092005 0978b82322 fix: remove projection matrix hacks — back to clean FOV 45° + z=-5
- Removed M22 Y-flip and M33/M43 depth remap that caused edge distortion
- System.Numerics.CreatePerspectiveFieldOfView works as-is with row_major
- FOV 45°, camera z=-5, 1 cube sliding left-right + rotating
2026-06-18 13:29:26 +03:00
emil28092005 00f23187d5 feat: 1 cube slowly sliding left-right (sin wave) + rotating 2026-06-18 13:27:36 +03:00
emil28092005 a801e7fb16 fix: Vulkan projection matrix — Y-flip + depth [0,1] remap
- Negate proj.M22 for Vulkan Y-down coordinate system
- Remap depth from OpenGL [-1,1] to Vulkan [0,1]: M33*0.5+0.5, M43*0.5+0.5
- FOV 45°, camera z=-5, cubes at x=±2
- 0 validation errors
2026-06-18 13:25:59 +03:00
emil28092005 ce3dc8c41b fix: FOV 72°, camera z=-10, cubes 6 units apart 2026-06-18 13:24:26 +03:00
emil28092005 1a277abc7a fix: wider FOV (81°) for more distance 2026-06-18 13:23:19 +03:00
emil28092005 c9358bf080 fix: wider FOV (60°) + camera z=-6 for better view 2026-06-18 13:22:41 +03:00
emil28092005 51f94b8923 fix: move camera to z=-12 2026-06-18 13:21:39 +03:00
emil28092005 45fcccbd34 fix: move camera to z=-7 for more distance 2026-06-18 13:20:50 +03:00
emil28092005 edfcf33578 feat: render 2 rotating cubes side by side
- Push constant changed from float angle (4B) to mat4 model (64B)
- Two draw calls with different model matrices (x=-1.5 and x=+1.5)
- Both cubes rotate around Y axis at 0.2 rad/s
- 0 validation errors
2026-06-18 13:20:22 +03:00
emil28092005 567d88825d fix: slow down rotation to 0.2 rad/s 2026-06-18 13:18:45 +03:00
emil28092005 0541f5d13c feat: rotate cube around Y axis instead of Z 2026-06-18 13:18:07 +03:00
emil28092005 f8cbacb422 feat: slow cube rotation (0.5 rad/s) 2026-06-18 13:16:46 +03:00
emil28092005 441ce1f511 fix: move camera to z=-4 so cube is further away 2026-06-18 13:15:17 +03:00
emil28092005 bdd4d3eaf8 feat: cube from OBJ with depth buffer, no rotation
- Load Content/cube.obj via ObjLoader (36 vertices, 36 indices)
- Depth buffer re-enabled: depth test + write, CompareOp=Less
- Depth image transition + depth attachment in rendering info
- Push constant angle=0 (no rotation)
- Camera at z=-2, perspective FOV 45°
- UINT32 index type (fixed in previous commit)
- Face normals computed by ObjLoader (no vn in cube.obj)
- 0 validation errors
2026-06-18 13:14:13 +03:00
emil28092005 237474014c test: 206 tests covering Vulkan types, struct sizes, enum values, OBJ normals
New test files:
- VulkanStructSizeTests.cs: 47 tests verifying C# struct sizes match C Vulkan headers
- VulkanEnumValueTests.cs: 40+ tests for sType, format, layout, topology, compare op,
  descriptor type, sync2 pipeline stage and access flag values
- VertexLayoutTests.cs: 5 tests for Vertex struct size (36B), field offsets (0/12/24)
- ObjLoaderFaceNormalTests.cs: 5 tests for face normal computation when OBJ has no vn lines

Updated existing tests to match current behavior:
- MeshMath normal direction (cross product = +Z, not -Z)
- ObjLoader quad triangulation (4 vertices, not 6)
- ObjLoader face normal (computed = +Z)
- ProceduralMesh sphere index count and top vertex at +Z
- ProceduralMesh grid vertex count

All 206 tests pass. Tests would have caught:
- VkPhysicalDeviceMemoryProperties size (was 264, should be 520)
- VkPhysicalDeviceLimits fields (size_t fields were uint, not ulong)
- Synchronization2Features sType (was 1000257000, should be 1000314007)
- Access flag values (COLOR_ATTACHMENT_WRITE was 0x800, should be 0x100)
- Index type mismatch (UINT16 vs UINT32)
2026-06-18 13:09:51 +03:00
emil28092005 233041ab00 fix: VK_INDEX_TYPE_UINT32 (was UINT16) + remove unused depth from pipeline
- vkCmdBindIndexBuffer indexType changed from 0 (UINT16) to 1 (UINT32)
  to match uint[] index data — this was causing black screen
- Removed depth stencil state, depth attachment format, and depth
  rendering attachment from pipeline/renderer (depth buffer still
  created in swapchain but unused)
2026-06-18 12:59:01 +03:00
emil28092005 aca8d1ab4d fix: correct matrix order (proj*view) + Z depth range for Vulkan
- VP matrix order: proj*view (GLSL column-vector convention)
- Z correction in vertex shader: gl_Position.z = (z+w)*0.5 (OpenGL [-1,1] → Vulkan [0,1])
- Depth test temporarily disabled for debugging
2026-06-18 12:47:20 +03:00
emil28092005 d620bd537f revert: back to triangle with 3D rotation + depth buffer
- Hardcoded triangle (RGB vertices) with index buffer
- 3D rotation: RotateZ * RotateX
- Depth buffer + UBO + push constant model matrix retained
- Camera at z=-3, perspective FOV 45°
2026-06-18 12:44:06 +03:00
emil28092005 e740cf1214 fix: compute face normals when OBJ has no vn lines
- ObjLoader.ParseFace: if no normals in OBJ, compute face normal from
  first triangle using MeshMath.ComputeFaceNormal and apply to all face vertices
- cube.obj has no vn lines, so all faces previously had normal (0,1,0)
- Now each face has correct outward-facing normal for proper lighting
2026-06-18 12:42:13 +03:00
emil28092005 d6ca80c678 fix: add normal-based diffuse lighting so cube is visible as 3D
- Fragment shader: directional light dot(normal, lightDir) with 0.2 ambient
- Vertex shader: pass normal through mat3(model) to fragment
- Same-color faces now have different shading based on orientation
2026-06-18 12:39:51 +03:00
emil28092005 d6bd1807cf feat: depth buffer + 3D model matrix — rotating cube now looks correct
- Depth image (D32_SFLOAT) + image view created in VulkanSwapchain
- VkPipelineDepthStencilStateCreateInfo: depth test + write enabled, CompareOp=Less
- Push constant changed from float angle (4B) to mat4 model (64B)
- 3D rotation: RotateY * RotateX in renderer, applied via push constant
- Vertex shader: gl_Position = vp * model * vec4(pos, 1.0)
- Depth attachment in VkRenderingInfo + depth clear (1.0)
- Depth image layout transition (Undefined → DepthStencilAttachmentOptimal)
- New Vulkan functions: vkCreateImage, vkDestroyImage, vkGetImageMemoryRequirements, vkBindImageMemory
- New structs: VkPipelineDepthStencilStateCreateInfo, VkStencilOpState, VkImageCreateInfo
- New enums: VkCompareOp, VkImageType, VkImageTiling
- 0 validation errors
2026-06-18 02:14:54 +03:00
emil28092005 fde8b0f63a feat: index buffer + OBJ cube rendering
- VulkanIndexBuffer.cs: staging → device-local index buffer (uint16 indices)
- vkCmdBindIndexBuffer + vkCmdDrawIndexed added to Vk.cs
- VulkanRenderer loads Content/cube.obj via ObjLoader (36 vertices, 12 faces)
- Replaced hardcoded triangle with rotating cube
- 0 validation errors, ~3800 FPS
2026-06-18 02:05:15 +03:00
emil28092005 4f3810010a feat: push constants + UBO descriptor sets — rotating triangle with VP matrix
- Push constants: rotation angle (float) sent to vertex shader via vkCmdPushConstants
- UBO + descriptor sets: VP matrix (mat4) in uniform buffer, bound via VkDescriptorSet
- New Vulkan functions: vkCreateDescriptorSetLayout, vkCreateDescriptorPool,
  vkAllocateDescriptorSets, vkUpdateDescriptorSets, vkCmdBindDescriptorSets,
  vkCmdPushConstants
- New structs: VkPushConstantRange, VkDescriptorSetLayoutBinding,
  VkDescriptorSetLayoutCreateInfo, VkDescriptorPoolCreateInfo,
  VkDescriptorPoolSize, VkDescriptorSetAllocateInfo, VkWriteDescriptorSet,
  VkDescriptorBufferInfo
- Per-frame UBO buffers (HOST_VISIBLE|HOST_COHERENT, 64 bytes each)
- Perspective projection + look-at camera (static, no ECS yet)
- Cleaned up debug logging in VulkanContext
- Zero validation errors
2026-06-18 01:59:41 +03:00
emil28092005 2e0970e769 feat: rebuild Vulkan renderer from scratch — pure P/Invoke triangle (Vulkan 1.3)
- Complete rewrite of Engine.Graphics.Vulkan with pure P/Invoke (no wrapper libs)
- Vulkan 1.3: dynamic rendering (vkCmdBeginRendering/vkCmdEndRendering),
  synchronization2 (vkQueueSubmit2, vkCmdPipelineBarrier2)
- Split types into VulkanHandles.cs, VulkanEnums.cs, VulkanStructs.cs
- Staging buffer → device-local vertex buffer pattern
- Correct swapchain semaphore indexing (per-image, not per-frame-in-flight)
- VK_EXT_debug_utils debug messenger with validation layer fallback
- Dynamic viewport/scissor (no pipeline recreation on resize)
- Simplified Program.cs to triangle-only rendering
- Removed old Silk.NET renderer, ImGui, PBR shaders, screenshot code
- Updated VULKAN_IMPLEMENTATION_PLAN.md with full architecture decisions
2026-06-18 01:49:25 +03:00
emil28092005 ee98e4ad08 fix(window): ensure SDL3/Wayland window is mapped by pumping events after ShowWindow
- Remove ALWAYS_ON_TOP and debug print hacks.
- Pump SDL events after SDL_ShowWindow so the Wayland compositor maps the window.
- Clean run.sh: do not force DISPLAY or SDL_VIDEODRIVER, let SDL3 auto-detect.
2026-06-18 00:38:16 +03:00
emil28092005 aa93b55a74 fix: window now visible — SDL_ShowWindow + Wayland support in run.sh
- Added SDL_ShowWindow + SDL_RaiseWindow after window creation
- run.sh: auto-detect Wayland vs X11, set SDL_VIDEODRIVER accordingly
- Wayland session uses SDL_VIDEODRIVER=wayland (bundled SDL3 lacks X11 support)
- Engine runs at 1600+ FPS, window visible on Wayland desktop
- Screenshots confirm 3D geometry rendering correctly
2026-06-18 00:22:27 +03:00
emil28092005 46a04850e3 fix: rendering now works — three critical bugs fixed
1. VkColorComponentFlags: wrong bit values (0x10-0x80 instead of 0x01-0x08)
   → colorWriteMask was 0xF0 instead of 0x0F → no color channels written

2. VkClearValue: was Sequential layout instead of Explicit (union)
   → depthStencil clear value written at wrong offset → depth buffer
   cleared to 0.0 instead of 1.0 → all fragments failed depth test

3. Vulkan clip space correction in vertex shader:
   gl_Position.y = -gl_Position.y (Vulkan Y-down vs OpenGL Y-up)
   gl_Position.z = (gl_Position.z + gl_Position.w) * 0.5 (Z [0,1] vs [-1,1])

Also:
- PushConstantSize fixed from 128 to 144 (two mat4 + vec4)
- Dynamic viewport/scissor state added to pipeline
- Push constants use column_major (default) to match System.Numerics row-major layout
- Camera.GetProjectionMatrix reverted to pure OpenGL-style (correction in shader)
- Screenshots show actual 3D geometry (calibration cubes visible)
- 66/66 tests pass
2026-06-18 00:14:40 +03:00
emil28092005 749fbc8df4 feat: ImGui integration + real screenshot capture via Vulkan
ImGui:
- ImGui.NET 1.91.6.1 NuGet package
- VulkanImGui: font texture upload, ImGui pipeline (blending, no depth), vertex/index buffer streaming
- GLSL 450 ImGui shaders (vert: scale/translate push constants, frag: font texture sampler)
- Compiled to SPIR-V via @webgpu/glslang
- Debug overlay: FPS, camera position, entity count, light list
- 1600+ FPS with ImGui active

Screenshot:
- vkCmdCopyImageToBuffer embedded in main command buffer
- Layout transition PresentSrcKHR→TransferSrcOptimal→PresentSrcKHR
- Deferred read: staging buffer read on next frame after fence
- BMP format written directly to FileStream (row by row)
- All 16 camera tour screenshots captured (1280x720x32bpp)

All 66 tests pass.
2026-06-17 23:39:38 +03:00
emil28092005 544f6b59c0 feat: real screenshot capture — BMP from swapchain image via vkCmdCopyImageToBuffer
- Added vkCmdCopyImageToBuffer to P/Invoke layer
- Screenshot embedded in main command buffer (barrier PresentSrcKHR→TransferSrc, copy, barrier back)
- Deferred read: staging buffer read on next frame after fence signaled
- BMP format written directly to FileStream (row by row, avoids large heap allocations)
- BGRA→BGRA direct copy for B8G8R8A8 swapchain format
- All 16 camera tour screenshots captured successfully (1280x720x32bpp)
- 66/66 tests pass
2026-06-17 23:27:58 +03:00
emil28092005 80238b08f5 feat: pure Vulkan P/Invoke renderer — no wrappers, SDL3 window, SPIR-V shaders
- Engine.Graphics: restored interfaces (IRenderContext, IRenderer, RenderBackendFactory),
  loaders (ObjLoader, GltfLoader), ProceduralMesh, MeshMath, SceneSerializer
- Engine.Graphics.Vulkan: pure P/Invoke to libvulkan.so.1/vulkan-1.dll
  - VulkanNative: library loading, function pointer loading via vkGetInstanceProcAddr
  - Vk: static function cache for ~80 Vulkan functions, all delegate types
  - VulkanTypes: ~50 structs, ~20 enums matching Vulkan C headers
  - VulkanContext: instance, physical device, logical device, surface, memory types
  - VulkanSwapchain: swapchain, image views, depth image, render pass, framebuffers
  - VulkanPipeline: graphics pipeline, descriptor set layout, shader modules
  - VulkanBuffer: vertex/index/uniform buffers, staging, memory allocation
  - VulkanRenderer: command buffers, sync (semaphores/fences), render loop, 2 frames in flight
  - GLSL 450 shaders: PBR lighting (Fresnel, ACES, gamma), directional+point lights
  - Compiled to SPIR-V via @webgpu/glslang WASM
- CortexEngine.App: switched to vulkan backend, removed Raylib/OpenTK/ImGui refs
- Tests: all 66 pass (ObjLoader, MeshMath, ProceduralMesh, SceneSerializer, etc.)
- Camera tour: 16 poses, screenshots captured, clean shutdown
2026-06-17 23:03:24 +03:00
emil28092005 dd105ea4af chore: remove all graphics backends (Raylib, OpenTK, Vulkan/Silk.NET) — prepare for pure Vulkan P/Invoke rewrite 2026-06-17 22:12:51 +03:00
emil28092005 ff4a3faae9 revert: switch back to Raylib default, keep OpenTK for future
OpenTK renderer has matrix/swap issues causing garbled output.
Raylib restored as default. OpenTK project preserved for debugging.
Normal PBR shader restored in OpenTKRenderer.
2026-06-17 21:35:20 +03:00
emil28092005 f0af0a6b5e fix: rewrite OpenTK renderer from scratch — correct matrix transpose
- SetUniformMat4: copies Matrix4 to float[16] row-major, passes with
  transpose=true (OpenGL transposes row-major → column-major)
- Previous version used transpose=false with manual column-major copy
  which caused double-transpose → garbage rendering (strobe)
- VertexAttribPointer with stride=0 (tightly packed per-attribute buffers)
- MakeCurrent() explicitly in constructor to ensure GL context is ready
- 66/66 tests, 1400+ FPS
2026-06-17 21:28:53 +03:00
emil28092005 270806a189 feat: OpenTK 4.x OpenGL backend — replaces Silk.NET, default renderer
- Deleted Engine.Graphics.OpenGL (Silk.NET) project
- Created Engine.Graphics.OpenTK with OpenTK 4.9.4
- OpenTKWindow: GameWindow + ProcessEvents() (non-blocking), SwapBuffers()
- OpenTKInputState: KeyboardState/MouseState → Engine.Core.Key
- OpenTKRenderer: raw OpenGL 3.3 Core, managed arrays (no unsafe),
  OpenTK.Mathematics.Matrix4 (column-major, no transpose needed),
  shadow FBO with depth texture, PCF 3x3, full PBR shader
- OpenTKRenderContext + OpenTKBackendRegistrar ('opentk')
- Program.cs: default backend is 'opentk'
- 66/66 tests, 0 errors, 1600 FPS (VSync off)
2026-06-17 21:24:33 +03:00
emil28092005 5687e97a09 revert: switch back to Raylib as default backend
OpenGL backend has rendering issues (stripes) that need visual debugging.
Raylib restored as default. OpenGL backend code preserved for future work.
2026-06-17 21:05:59 +03:00
emil28092005 a306439f22 wip: OpenGL backend — pinned buffers, void* offsets, manual swap
- GCHandle.Alloc(Pinned) for BufferData to prevent GC relocation
- void* null for VertexAttribPointer offsets
- ShouldSwapAutomatically=false, manual SwapBuffers
- Solid color debug shader active
- Still has rendering issues — needs visual debugging
2026-06-17 21:05:17 +03:00
emil28092005 3c67790947 fix: column-major matrix copy for OpenGL
CopyMatrixToBuffer now transposes during copy (column-major output),
UniformMatrix4 uses transpose=false. Fixes garbage rendering.
2026-06-17 20:59:15 +03:00
emil28092005 07754a31bf fix: transpose matrices for OpenGL (row-major → column-major)
System.Numerics.Matrix4x4 is row-major, OpenGL UniformMatrix4 with
transpose=false expects column-major. Changed all UniformMatrix4 calls
to transpose=true. Also explicitly enable depth test + disable cull face
for main pass. Fixes strobe/flickering issue.
2026-06-17 20:55:59 +03:00
emil28092005 417915e861 feat: switch default backend to Silk.NET OpenGL
- OpenGLRenderContext: Initialize() + CreateOpenGL() for non-blocking
- OpenGLWindow: Initialize() instead of Run(), CreateGL() method
- Program.cs: default backend is now 'opengl', SwapBuffers per frame
- ImGui guarded: only initialized for RaylibRenderer, ObjectManipulator
  checks _imGuiAvailable before calling ImGui.GetIO()
- 66/66 tests, 145 FPS with OpenGL backend
2026-06-17 20:14:15 +03:00
emil28092005 cc9a1a4602 feat: Silk.NET OpenGL backend compiles — full shadow mapping support
- Fixed all Silk.NET 2.21 API issues:
  - BufferData with void* + fixed blocks
  - UniformMatrix4 with float[] buffer (CopyMatrixToBuffer helper)
  - DrawBuffer/ReadBuffer with DrawBufferMode/ReadBufferMode
  - Clear with (uint) cast
  - Input delegates: KeyDown(IKeyboard,Key,int), MouseDown(IMouse,MouseButton), Scroll(IMouse,ScrollWheel)
  - GetProgram with ProgramPropertyARB
  - IWindow alias SilkWindow to avoid ambiguity
- OpenGLRenderer: shadow FBO with depth texture, PCF 3x3, full PBR shader
- OpenGLRenderContext: Silk.NET window + GL context
- OpenGLBackendRegistrar registered as 'opengl'
- 66/66 tests, 0 errors
2026-06-17 18:59:36 +03:00
emil28092005 6e65eedf1d wip: Silk.NET OpenGL backend — project structure, partial implementation
- Engine.Graphics.OpenGL project with Silk.NET.OpenGL, Windowing, Input
- OpenGLWindow (Silk.NET.Windowing), OpenGLInputState (Silk.NET.Input)
- OpenGLRenderer: full shader pipeline with shadow mapping, VAO/VBO upload
- OpenGLRenderContext, OpenGLBackendRegistrar
- Build errors due to Silk.NET 2.21 API differences — needs API investigation
- Raylib and Vulkan backends unchanged
2026-06-17 18:48:36 +03:00
emil28092005 e452a294cc revert: remove shadow mapping attempts, restore clean renderer
Shadow mapping via Raylib doesn't work reliably:
- Rlgl.GetMatrixModelview returns zeros with custom FBO
- MaterialMapIndex binding doesn't map to expected texture units
- DrawModelEx resets texture state internally
Restored to last working state without shadows.
Consider direct OpenGL backend for shadow mapping.
2026-06-17 18:40:30 +03:00
emil28092005 573872298f fix: shadow map binding before draw + correct matrix transpose
- Use BeginShaderMode/EndShaderMode around DrawModelEx to keep shader active
- Bind shadow map on texture unit 1 BEFORE DrawModelEx (not after)
- Remove manual Matrix4x4.Transpose — SetShaderValueMatrix already handles
  row-major to column-major conversion via glUniformMatrix4fv(transpose=false)
- 66/66 tests, 145 FPS
2026-06-17 18:11:08 +03:00
emil28092005 6d16287bcb fix: shadow map re-bind after DrawModelEx + transpose lightViewProj
- Bind shadow map on MaterialMapIndex.Emission (unit 1) per entity
- Re-bind via Rlgl.ActiveTextureSlot(1)+EnableTexture after DrawModelEx
  (DrawModelEx resets texture state internally)
- Transpose lightViewProj matrix (Raylib row-major → OpenGL column-major)
- 66/66 tests, 145 FPS
2026-06-17 18:07:13 +03:00
emil28092005 2e73a0b52c feat: shadow mapping via rlgl depth FBO (official raylib approach)
- Depth-only FBO via Rlgl.LoadFramebuffer + Rlgl.LoadTextureDepth (2048x2048)
- Shadow pass: BeginMode3D with orthographic light camera, grab
  ModelView/Projection matrices via Rlgl.GetMatrixModelview/Projection
- Main pass: Rlgl.ActiveTextureSlot(1) + Rlgl.EnableTexture(depth) +
  SetShaderValue(shadowMapLoc, 1) to bind shadow map on texture unit 1
- Main shader: uniform mat4 lightViewProj + uniform sampler2D shadowMap
  with PCF 3x3 soft shadows, bias 0.005
- Shadow factor applied to first directional light only
- Shadow shader: empty fragment (depth written automatically)
- 66/66 tests, all shaders compile, 145 FPS
2026-06-17 18:03:45 +03:00
emil28092005 de262732bd feat: Unity-like object manipulation + physics fixes
- ObjectManipulator: left-click to pick, drag on camera-orthogonal plane
- Screen-space movement (like Unity), not ground-plane
- Skip dragged entity in SyncTransforms so physics doesn't fight drag
- Zero velocity on grab to prevent momentum accumulation
- ImGui selection sync with manipulator
- Floor lowered to y=-0.5, visual thickness corrected
- Physics shape sizes fixed (BoxShape uses half-extents)
- Collision pairs explicitly enabled in ObjectLayerPairFilterTable
2026-06-17 17:59:32 +03:00
emil28092005 a9d8af48e5 fix: enable collision pairs, correct physics shape sizes, lower floor
- ObjectLayerPairFilterTable blocks all collisions by default in C# binding;
  explicitly EnableCollision for all layer pairs (NonMoving/Moving)
- BoxShape takes half-extents, not full size: DynamicBox(scale * 0.5f)
- Floor visual thickness reduced (scale Y 1→0.5), top at y=0 matching collider
- Floor lowered to y=-0.5
- Physics works: cubes and spheres fall and land on floor, no clipping
2026-06-17 17:31:17 +03:00
emil28092005 4a5893dc61 fix: physics segfault — keep layer filter objects alive as fields
BroadPhaseLayerInterfaceTable, ObjectLayerPairFilterTable, and
ObjectVsBroadPhaseLayerFilterTable were local variables in the
constructor. After GC collected them, Jolt crashed with dangling
native pointers during PhysicsSystem.Update(). Now stored as readonly
fields with proper Dispose in cleanup. Also fixed Flecs table lock
assertion by collecting RigidBody init list before calling entity.Set().
2026-06-17 17:25:29 +03:00
emil28092005 cca89040c3 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
2026-06-17 16:45:43 +03:00
emil28092005 9bee806a6c docs: update roadmap — shadow mapping deferred 2026-06-17 16:39:07 +03:00
emil28092005 23cac8052c fix: remove shadow mapping, keep all other improvements
Shadow mapping removed — Raylib's DrawModelEx doesn't support
multi-texture-unit binding needed for shadow map sampling.
Kept: linear gamma, per-channel Fresnel, point/directional lights,
correct CW normals, backface culling, floor entity.
66/66 tests passing.
2026-06-17 16:33:12 +03:00
emil28092005 c82ff48119 fix: rlgl depth FBO shadows, correct normals, linear gamma, per-channel Fresnel
- Shadow mapping via rlgl: custom depth FBO (2048x2048, 24-bit depth texture)
  instead of color-attachment approach. Proper depth-only render pass.
- Shadow map bound via MaterialMapIndex.Emission (texture unit 1), sampled
  in shadow receiver shader with PCF 3x3 soft shadows.
- Fixed inverted normals: Cross(ac, ab) for CW winding in OBJ files.
- Linear workflow: pow(albedo, 2.2) before lighting, pow(result, 1/2.2) after.
- Per-channel Fresnel: vec3 F0 + (1-F0)*pow(1-HdotV,5) instead of F0.x.
- Single CollectLights call after BeginMode3D.
- Shadow pass after BeginDrawing (inside frame).
- Backface culling disabled globally for mixed-winding meshes.
- Point light support: Light.Point/Directional factory methods, attenuation,
  ImGui inspector with type combo, position/range for point lights.
- Floor rendered for both light types (shadow receiver or main shader).
- 66/66 tests passing.
2026-06-17 16:13:46 +03:00
emil28092005 f905fe3040 fix: restore rendering after shadow mapping attempt
Shadow mapping via DrawModelEx doesn't work — Raylib's material system
only supports texture unit 0, shadow map sampling needs unit 1.
Additional uniforms in fragment shader also broke NVIDIA GLSL uniform
layout, causing render artifacts. Reverted shader to pre-shadow state.
Shadow pass infrastructure (RenderShadowPass, shadow shader, RT) kept
but disabled. 66/66 tests passing.
2026-06-17 14:44:49 +03:00
emil28092005 2d30dec01c feat: Dear ImGui editor, GLTF materials, scene serialization
- ImGuiLayer: entity inspector (Transform/Material/Light/Camera editing),
  hierarchy panel, debug overlay with FPS graph (rlImgui-cs + ImGui.NET)
- GltfLoader.LoadWithMaterials: extracts PBR albedo, roughness, metallic,
  base color texture from glTF files
- SceneSerializer: save/load ECS world to/from JSON with custom
  Vector3/Quaternion converters, 6 tests
- ImGui disabled in tour/test mode to keep screenshots clean
- 66/66 tests passing
2026-06-17 14:12:02 +03:00
emil28092005 fb6e26a268 feat: modular HAL, Raylib backend, PBR shading, textures, 60 unit tests
- Replace hardcoded SDL3 windowing with IWindow/IInputState/Key abstractions
- Each render backend owns its window (Raylib GLFW, SDL3 for Vulkan)
- Raylib backend: DrawModelEx, custom GLSL shader with Fresnel, ACES
  tonemapping, gamma correction, hemisphere ambient
- Fix backface culling, mesh memory (NativeMemory.Alloc), texture loading
- Camera controllers use backend-agnostic Key enum (inverted yaw/strafe)
- Demo scene: 8 cubes, 7 spheres, torus knot OBJ with checker texture
- Extract ProceduralMesh + MeshMath from Program.cs to Engine.Graphics
- Vulkan backend deferred (compiles, untested, IWindow-compatible)
- 60 unit tests: ObjLoader, camera controllers, AiCommandProcessor,
  RenderBackendFactory, Timing, ProceduralMesh, MeshMath, Transform
- AGENTS.md for opencode integration
2026-06-17 13:49:12 +03:00