diff options
| author | Valentin Popov <valentin@popov.link> | 2026-07-18 14:50:52 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-07-18 14:50:52 +0300 |
| commit | fea984ddf51ba1e56ce800b9f370ac4208dcfcab (patch) | |
| tree | 96345c489f8c8d80ab0cbeb9d5dcf054217d6859 /apps | |
| parent | 2a793f1854231be6da930a3692dce691a6426211 (diff) | |
| download | fparkan-fea984ddf51ba1e56ce800b9f370ac4208dcfcab.tar.xz fparkan-fea984ddf51ba1e56ce800b9f370ac4208dcfcab.zip | |
feat(render): diagnose captured camera clipping
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/fparkan-game/src/main.rs | 89 |
1 files changed, 85 insertions, 4 deletions
diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs index 1d9fecd..32e7e44 100644 --- a/apps/fparkan-game/src/main.rs +++ b/apps/fparkan-game/src/main.rs @@ -31,7 +31,8 @@ use fparkan_render::{ RenderSnapshotDraw, }; use fparkan_render_vulkan::{ - project_land_msh_to_static_mesh_in_world_space, project_land_msh_to_static_mesh_in_xy_frame, + project_land_msh_to_static_mesh_in_legacy_world_space, + project_land_msh_to_static_mesh_in_xy_frame, project_msh_to_static_mesh_in_world_space_with_transform, project_msh_to_static_mesh_in_xy_frame, VulkanPlanningBackend, VulkanSmokeFrameOutcome, VulkanSmokeRenderer, VulkanSmokeRendererCreateInfo, VulkanStaticCamera, VulkanStaticMaterial, @@ -226,6 +227,7 @@ struct StaticPreviewScene { materials: Vec<VulkanStaticMaterial>, camera: VulkanStaticCamera, camera_mode: &'static str, + clip_visible_vertices: usize, mesh_components: usize, terrain_components: usize, } @@ -264,7 +266,7 @@ fn static_preview_mesh_and_materials( }, }]; let terrain_component = if legacy_camera.is_some() { - project_land_msh_to_static_mesh_in_world_space(terrain_mesh) + project_land_msh_to_static_mesh_in_legacy_world_space(terrain_mesh) } else { let frame = xy_frame.ok_or_else(|| "missing diagnostic XY frame".to_string())?; project_land_msh_to_static_mesh_in_xy_frame(terrain_mesh, frame) @@ -313,10 +315,12 @@ fn static_preview_mesh_and_materials( if mesh_components == 0 { return Err("selected static preview roots have no mesh-backed visual".to_string()); } + let camera = legacy_camera.unwrap_or_default(); Ok(StaticPreviewScene { + clip_visible_vertices: static_preview_clip_visible_vertices(&mesh, camera), mesh, materials, - camera: legacy_camera.unwrap_or_default(), + camera, camera_mode: if legacy_camera.is_some() { "legacy-d3d7-capture" } else { @@ -327,6 +331,46 @@ fn static_preview_mesh_and_materials( }) } +/// Counts source vertices inside the Vulkan clip volume for a static preview. +/// +/// The static vertex shader receives row-major D3D7 matrix data as a GLSL +/// column-major `mat4`; its multiplication is consequently equivalent to this +/// row-vector calculation. This remains CPU-only diagnostic evidence and does +/// not change the renderer's clipping rules. +fn static_preview_clip_visible_vertices( + mesh: &VulkanStaticMesh, + camera: VulkanStaticCamera, +) -> usize { + mesh.vertices + .iter() + .filter(|vertex| { + let [x, y, z] = vertex.position; + let matrix = camera.clip_from_world; + let clip_x = x.mul_add( + matrix[0], + y.mul_add(matrix[4], z.mul_add(matrix[8], matrix[12])), + ); + let clip_y = x.mul_add( + matrix[1], + y.mul_add(matrix[5], z.mul_add(matrix[9], matrix[13])), + ); + let clip_z = x.mul_add( + matrix[2], + y.mul_add(matrix[6], z.mul_add(matrix[10], matrix[14])), + ); + let clip_w = x.mul_add( + matrix[3], + y.mul_add(matrix[7], z.mul_add(matrix[11], matrix[15])), + ); + clip_w.is_finite() + && clip_w > 0.0 + && (-clip_w..=clip_w).contains(&clip_x) + && (-clip_w..=clip_w).contains(&clip_y) + && (0.0..=clip_w).contains(&clip_z) + }) + .count() +} + fn static_preview_xy_frame( assets: &MissionAssets, terrain: &TerrainWorld, @@ -558,6 +602,7 @@ struct StaticVulkanApp { materials: Vec<VulkanStaticMaterial>, camera: VulkanStaticCamera, camera_mode: &'static str, + clip_visible_vertices: usize, mesh_components: usize, terrain_components: usize, preview_roots: usize, @@ -585,6 +630,7 @@ impl StaticVulkanApp { materials: preview.materials, camera: preview.camera, camera_mode: preview.camera_mode, + clip_visible_vertices: preview.clip_visible_vertices, mesh_components: preview.mesh_components, terrain_components: preview.terrain_components, preview_roots, @@ -630,7 +676,7 @@ impl StaticVulkanApp { return; } self.output = Some(format!( - "{{\"report_kind\":\"rendered-static-mission\",\"backend\":\"vulkan-static\",\"camera_mode\":{},\"window\":\"native\",\"mission\":{},\"objects\":{},\"frames\":{},\"preview_roots\":{},\"mesh_components\":{},\"terrain_components\":{},\"material_descriptors\":{},\"swapchain\":[{},{}],\"swapchain_images\":{},\"validation_warnings\":{},\"validation_errors\":{},\"readback_bytes\":{},\"readback_hash\":{}}}", + "{{\"report_kind\":\"rendered-static-mission\",\"backend\":\"vulkan-static\",\"camera_mode\":{},\"window\":\"native\",\"mission\":{},\"objects\":{},\"frames\":{},\"preview_roots\":{},\"mesh_components\":{},\"terrain_components\":{},\"clip_visible_vertices\":{},\"material_descriptors\":{},\"swapchain\":[{},{}],\"swapchain_images\":{},\"validation_warnings\":{},\"validation_errors\":{},\"readback_bytes\":{},\"readback_hash\":{}}}", json_string(self.camera_mode), json_string(&self.mission), self.object_count, @@ -638,6 +684,7 @@ impl StaticVulkanApp { self.preview_roots, self.mesh_components, self.terrain_components, + self.clip_visible_vertices, self.materials.len(), report.renderer_report.swapchain_extent.0, report.renderer_report.swapchain_extent.1, @@ -1062,6 +1109,40 @@ mod tests { } #[test] + fn static_preview_clip_diagnostic_matches_vulkan_clip_bounds() { + let mesh = VulkanStaticMesh { + vertices: vec![ + fparkan_render_vulkan::VulkanStaticVertex { + position: [0.0, 0.0, 0.0], + color: [0.0; 3], + uv: [0.0; 2], + }, + fparkan_render_vulkan::VulkanStaticVertex { + position: [1.0, -1.0, 1.0], + color: [0.0; 3], + uv: [0.0; 2], + }, + fparkan_render_vulkan::VulkanStaticVertex { + position: [1.01, 0.0, 0.0], + color: [0.0; 3], + uv: [0.0; 2], + }, + fparkan_render_vulkan::VulkanStaticVertex { + position: [0.0, 0.0, -0.01], + color: [0.0; 3], + uv: [0.0; 2], + }, + ], + indices: Vec::new(), + draw_ranges: Vec::new(), + }; + assert_eq!( + static_preview_clip_visible_vertices(&mesh, VulkanStaticCamera::default()), + 2 + ); + } + + #[test] fn parses_required_args() { assert_eq!( Args::parse(&strings(&[ |
