diff options
Diffstat (limited to 'adapters')
| -rw-r--r-- | adapters/fparkan-render-vulkan/Cargo.toml | 1 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/asset_mesh.rs | 161 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi.rs | 3 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs | 4 |
4 files changed, 168 insertions, 1 deletions
diff --git a/adapters/fparkan-render-vulkan/Cargo.toml b/adapters/fparkan-render-vulkan/Cargo.toml index bb7dbd0..909ac1f 100644 --- a/adapters/fparkan-render-vulkan/Cargo.toml +++ b/adapters/fparkan-render-vulkan/Cargo.toml @@ -10,6 +10,7 @@ build = "build.rs" ash = "0.38" ash-window = "0.13" 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" } serde = { version = "1.0", features = ["derive"] } diff --git a/adapters/fparkan-render-vulkan/src/asset_mesh.rs b/adapters/fparkan-render-vulkan/src/asset_mesh.rs new file mode 100644 index 0000000..8e45f08 --- /dev/null +++ b/adapters/fparkan-render-vulkan/src/asset_mesh.rs @@ -0,0 +1,161 @@ +//! Format-to-GPU geometry bridge for the initial static asset renderer. + +use crate::{VulkanStaticMesh, VulkanStaticVertex}; +use fparkan_msh::ModelAsset; + +/// Error returned when a validated MSH cannot enter the current static GPU path. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum VulkanAssetMeshError { + /// The model has no triangle batches. + EmptyGeometry, + /// A position used by a batch is non-finite. + NonFinitePosition, + /// The view-plane extent is zero or otherwise not usable for normalization. + DegenerateViewExtent, + /// The indexed model exceeds the current 16-bit Vulkan input contract. + IndexOutOfRange, +} + +impl std::fmt::Display for VulkanAssetMeshError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EmptyGeometry => write!(f, "MSH contains no triangle geometry"), + Self::NonFinitePosition => write!(f, "MSH contains a non-finite position"), + Self::DegenerateViewExtent => write!(f, "MSH has a degenerate XZ view extent"), + Self::IndexOutOfRange => write!(f, "MSH index exceeds the static Vulkan u16 contract"), + } + } +} + +impl std::error::Error for VulkanAssetMeshError {} + +/// Projects validated MSH geometry into the current static Vulkan clip-space path. +/// +/// The original engine's node transforms, camera and material pipeline are not +/// substituted here. This bridge is intentionally a static asset-viewer step: +/// it preserves every batch's `index_start`, `index_count` and `base_vertex`, +/// projects the conventional `Iron3D` XZ ground plane into the current XY shader +/// input, and scales the used bounds uniformly into the visible clip rectangle. +/// +/// # Errors +/// +/// Returns [`VulkanAssetMeshError`] if the source cannot be represented by the +/// current 16-bit, triangle-list viewer input. +pub fn project_msh_to_static_mesh( + model: &ModelAsset, +) -> Result<VulkanStaticMesh, VulkanAssetMeshError> { + let mut indices = Vec::new(); + for batch in &model.batches { + let start = usize::try_from(batch.index_start) + .map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?; + let end = start + .checked_add(usize::from(batch.index_count)) + .ok_or(VulkanAssetMeshError::IndexOutOfRange)?; + for &raw_index in model + .indices + .get(start..end) + .ok_or(VulkanAssetMeshError::IndexOutOfRange)? + { + let index = batch + .base_vertex + .checked_add(u32::from(raw_index)) + .ok_or(VulkanAssetMeshError::IndexOutOfRange)?; + indices.push(u16::try_from(index).map_err(|_| VulkanAssetMeshError::IndexOutOfRange)?); + } + } + if indices.is_empty() { + return Err(VulkanAssetMeshError::EmptyGeometry); + } + + 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 = model + .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]); + } + 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 = model + .positions + .iter() + .map(|position| VulkanStaticVertex { + position: [ + (position[0] - center_x) * scale, + (position[2] - center_z) * scale, + ], + color: [0.82, 0.72, 0.31], + }) + .collect(); + + Ok(VulkanStaticMesh { vertices, indices }) +} + +#[cfg(test)] +mod tests { + use super::*; + use fparkan_msh::{Batch, ModelAsset}; + + fn model(positions: Vec<[f32; 3]>, indices: Vec<u16>, batches: Vec<Batch>) -> ModelAsset { + ModelAsset { + node_stride: 0, + node_count: 0, + nodes_raw: Vec::new(), + slots: Vec::new(), + positions, + normals: None, + uv0: None, + indices, + batches, + node_names: None, + } + } + + fn batch(index_start: u32, index_count: u16, base_vertex: u32) -> Batch { + Batch { + batch_flags: 0, + material_index: 0, + opaque4: 0, + opaque6: 0, + index_count, + index_start, + opaque14: 0, + base_vertex, + } + } + + #[test] + fn projects_xz_geometry_and_applies_base_vertex() { + let mesh = project_msh_to_static_mesh(&model( + vec![ + [99.0, 0.0, 99.0], + [-2.0, 4.0, -1.0], + [2.0, 8.0, -1.0], + [-2.0, 1.0, 3.0], + ], + vec![0, 1, 2], + vec![batch(0, 3, 1)], + )) + .expect("representable MSH"); + + assert_eq!(mesh.indices, vec![1, 2, 3]); + assert_eq!(mesh.vertices[1].position, [-0.8, -0.8]); + assert_eq!(mesh.vertices[2].position, [0.8, -0.8]); + assert_eq!(mesh.vertices[3].position, [-0.8, 0.8]); + } +} diff --git a/adapters/fparkan-render-vulkan/src/ffi.rs b/adapters/fparkan-render-vulkan/src/ffi.rs index 0b6f1f2..9433759 100644 --- a/adapters/fparkan-render-vulkan/src/ffi.rs +++ b/adapters/fparkan-render-vulkan/src/ffi.rs @@ -26,6 +26,8 @@ //! //! This crate is the declared low-level Vulkan boundary. +#[path = "asset_mesh.rs"] +mod asset_mesh; mod capabilities; mod instance; mod resources; @@ -37,6 +39,7 @@ mod swapchain; mod swapchain_resources; mod validation; +pub use self::asset_mesh::{project_msh_to_static_mesh, VulkanAssetMeshError}; pub use self::capabilities::{ probe_vulkan_runtime_capabilities, probe_vulkan_runtime_capabilities_for_request, VulkanRuntimeCapabilityError, VulkanRuntimeCapabilityProbe, diff --git a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs index 41dfc0f..dd68ef4 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/swapchain_resources.rs @@ -337,7 +337,9 @@ fn create_graphics_pipeline( .rasterizer_discard_enable(false) .polygon_mode(vk::PolygonMode::FILL) .line_width(1.0) - .cull_mode(vk::CullModeFlags::BACK) + // Static MSH viewer mode has no original material/cull state yet; draw + // both windings so every source batch is visible during the bridge. + .cull_mode(vk::CullModeFlags::NONE) .front_face(vk::FrontFace::CLOCKWISE) .depth_bias_enable(false); let multisample_state = vk::PipelineMultisampleStateCreateInfo::default() |
