diff options
Diffstat (limited to 'adapters/fparkan-render-vulkan')
| -rw-r--r-- | adapters/fparkan-render-vulkan/Cargo.toml | 1 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/asset_mesh.rs | 145 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi.rs | 4 |
3 files changed, 149 insertions, 1 deletions
diff --git a/adapters/fparkan-render-vulkan/Cargo.toml b/adapters/fparkan-render-vulkan/Cargo.toml index 909ac1f..485249f 100644 --- a/adapters/fparkan-render-vulkan/Cargo.toml +++ b/adapters/fparkan-render-vulkan/Cargo.toml @@ -13,6 +13,7 @@ fparkan-binary = { path = "../../crates/fparkan-binary", version = "0.1.0" } fparkan-msh = { path = "../../crates/fparkan-msh", version = "0.1.0" } fparkan-platform = { path = "../../crates/fparkan-platform", version = "0.1.0" } fparkan-render = { path = "../../crates/fparkan-render", version = "0.1.0" } +fparkan-terrain-format = { path = "../../crates/fparkan-terrain-format", version = "0.1.0" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/adapters/fparkan-render-vulkan/src/asset_mesh.rs b/adapters/fparkan-render-vulkan/src/asset_mesh.rs index d861301..e7bfff4 100644 --- a/adapters/fparkan-render-vulkan/src/asset_mesh.rs +++ b/adapters/fparkan-render-vulkan/src/asset_mesh.rs @@ -3,6 +3,7 @@ use crate::{VulkanStaticDrawRange, VulkanStaticMesh, VulkanStaticVertex}; use fparkan_msh::ModelAsset; use fparkan_render::LegacyPipelineState; +use fparkan_terrain_format::LandMeshDocument; /// Error returned when a validated MSH cannot enter the current static GPU path. #[derive(Clone, Debug, Eq, PartialEq)] @@ -132,10 +133,108 @@ pub fn project_msh_to_static_mesh( }) } +/// Projects validated `Land.msh` terrain geometry into the static Vulkan path. +/// +/// This bridge preserves the source triangle order from `TerrainFace28` and +/// consumes its validated position and UV0 streams. It is deliberately a +/// geometry-only terrain slice: source slot/material selection, camera +/// transforms, fog and surface-mask shading need their own evidence before +/// they can be modeled as renderer state. +/// +/// # Errors +/// +/// Returns [`VulkanAssetMeshError`] if the terrain has no triangles, contains +/// non-finite positions, has no usable XZ extent, or references data outside +/// the current static Vulkan input contract. +pub fn project_land_msh_to_static_mesh( + terrain: &LandMeshDocument, +) -> Result<VulkanStaticMesh, VulkanAssetMeshError> { + let mut indices = Vec::with_capacity( + terrain + .faces + .len() + .checked_mul(3) + .ok_or(VulkanAssetMeshError::IndexOutOfRange)?, + ); + for face in &terrain.faces { + indices.extend(face.vertices); + } + if indices.is_empty() { + return Err(VulkanAssetMeshError::EmptyGeometry); + } + + let (min_x, max_x, min_z, max_z) = static_mesh_xz_bounds(&terrain.positions, &indices)?; + let extent = (max_x - min_x).max(max_z - min_z); + if !extent.is_finite() || extent <= f32::EPSILON { + return Err(VulkanAssetMeshError::DegenerateViewExtent); + } + let center_x = (min_x + max_x) * 0.5; + let center_z = (min_z + max_z) * 0.5; + let scale = 1.6 / extent; + let vertices = terrain + .positions + .iter() + .enumerate() + .map(|(index, position)| VulkanStaticVertex { + position: [ + (position[0] - center_x) * scale, + (position[2] - center_z) * scale, + ], + color: [0.31, 0.58, 0.27], + uv: terrain.uv0.get(index).map_or( + [ + (position[0] - center_x) / extent + 0.5, + (position[2] - center_z) / extent + 0.5, + ], + |uv| [f32::from(uv[0]) / 1024.0, f32::from(uv[1]) / 1024.0], + ), + }) + .collect(); + + Ok(VulkanStaticMesh { + vertices, + indices, + draw_ranges: vec![VulkanStaticDrawRange { + first_index: 0, + index_count: u32::try_from(terrain.faces.len()) + .map_err(|_| VulkanAssetMeshError::IndexOutOfRange)? + .checked_mul(3) + .ok_or(VulkanAssetMeshError::IndexOutOfRange)?, + material_index: 0, + pipeline_state: LegacyPipelineState::default(), + alpha_test_reference: 0, + }], + }) +} + +fn static_mesh_xz_bounds( + positions: &[[f32; 3]], + indices: &[u16], +) -> Result<(f32, f32, f32, f32), VulkanAssetMeshError> { + let mut min_x = f32::INFINITY; + let mut max_x = f32::NEG_INFINITY; + let mut min_z = f32::INFINITY; + let mut max_z = f32::NEG_INFINITY; + for &index in indices { + let position = positions + .get(usize::from(index)) + .ok_or(VulkanAssetMeshError::IndexOutOfRange)?; + if !position.iter().all(|value| value.is_finite()) { + return Err(VulkanAssetMeshError::NonFinitePosition); + } + min_x = min_x.min(position[0]); + max_x = max_x.max(position[0]); + min_z = min_z.min(position[2]); + max_z = max_z.max(position[2]); + } + Ok((min_x, max_x, min_z, max_z)) +} + #[cfg(test)] mod tests { use super::*; use fparkan_msh::{Batch, ModelAsset}; + use fparkan_terrain_format::{FullSurfaceMask, TerrainFace28, TerrainSlotTable}; fn model(positions: Vec<[f32; 3]>, indices: Vec<u16>, batches: Vec<Batch>) -> ModelAsset { ModelAsset { @@ -247,4 +346,50 @@ mod tests { ] ); } + + #[test] + fn projects_land_faces_in_source_order_with_packed_uv0() { + let terrain = LandMeshDocument { + streams: Vec::new(), + nodes_raw: Vec::new(), + slots: TerrainSlotTable { + header_raw: Vec::new(), + slots_raw: Vec::new(), + }, + positions: vec![ + [-2.0, 5.0, -1.0], + [2.0, 3.0, -1.0], + [-2.0, 9.0, 3.0], + [2.0, 1.0, 3.0], + ], + normals: Vec::new(), + uv0: vec![[1024, -512], [0, 2048], [-1024, 512], [512, 0]], + accelerator: Vec::new(), + aux14: Vec::new(), + aux18: Vec::new(), + faces: vec![terrain_face([0, 1, 2]), terrain_face([1, 3, 2])], + }; + + let mesh = project_land_msh_to_static_mesh(&terrain).expect("representable terrain"); + + assert_eq!(mesh.indices, vec![0, 1, 2, 1, 3, 2]); + assert_eq!(mesh.draw_ranges.len(), 1); + assert_eq!(mesh.draw_ranges[0].index_count, 6); + assert_eq!(mesh.vertices[0].position, [-0.8, -0.8]); + assert_eq!(mesh.vertices[3].position, [0.8, 0.8]); + assert_eq!(mesh.vertices[0].uv, [1.0, -0.5]); + assert_eq!(mesh.vertices[2].uv, [-1.0, 0.5]); + } + + fn terrain_face(vertices: [u16; 3]) -> TerrainFace28 { + TerrainFace28 { + flags: FullSurfaceMask(0), + material_tag: 0, + aux_tag: 0, + vertices, + neighbors: [None; 3], + tail_raw: [0; 8], + raw: [0; 28], + } + } } diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs index 04103b4..610401a 100644 --- a/adapters/fparkan-render-vulkan/src/ffi.rs +++ b/adapters/fparkan-render-vulkan/src/ffi.rs @@ -39,7 +39,9 @@ mod swapchain; mod swapchain_resources; mod validation; -pub use self::asset_mesh::{project_msh_to_static_mesh, VulkanAssetMeshError}; +pub use self::asset_mesh::{ + project_land_msh_to_static_mesh, project_msh_to_static_mesh, VulkanAssetMeshError, +}; pub use self::capabilities::{ probe_vulkan_runtime_capabilities, probe_vulkan_runtime_capabilities_for_request, VulkanRuntimeCapabilityError, VulkanRuntimeCapabilityProbe, |
