From f6586eed49ed3a0b4e0ff4818d4943cb78fc565f Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sun, 18 Oct 2026 11:34:48 +0400 Subject: fix(terrain): preserve zero-area welded foundation faces --- crates/fparkan-terrain/src/lib.rs | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) (limited to 'crates/fparkan-terrain/src/lib.rs') diff --git a/crates/fparkan-terrain/src/lib.rs b/crates/fparkan-terrain/src/lib.rs index db55506..3af89a3 100644 --- a/crates/fparkan-terrain/src/lib.rs +++ b/crates/fparkan-terrain/src/lib.rs @@ -341,7 +341,7 @@ impl TerrainWorld { let mut surfaces = Vec::with_capacity(foundation.fragments.len()); let mut generated = 0_usize; for (fragment_index, fragment) in foundation.fragments.iter().enumerate() { - if !valid_foundation_triangle(fragment.positions) { + if !valid_foundation_triangle(fragment.positions, fragment.source_face.is_none()) { return Err(TerrainError::InvalidFoundationFragment { fragment: fragment_index, }); @@ -1031,7 +1031,7 @@ fn build_surfaces(mesh: &LandMeshDocument) -> Result, Terra Ok(triangles) } -fn valid_foundation_triangle(vertices: [[f32; 3]; 3]) -> bool { +fn valid_foundation_triangle(vertices: [[f32; 3]; 3], allow_zero_xy_area: bool) -> bool { if !vertices.iter().flatten().all(|value| value.is_finite()) { return false; } @@ -1043,7 +1043,9 @@ fn valid_foundation_triangle(vertices: [[f32; 3]; 3]) -> bool { f64::from(vertices[2][0]) - f64::from(vertices[0][0]), f64::from(vertices[2][1]) - f64::from(vertices[0][1]), ]; - first[0] * second[1] - first[1] * second[0] != 0.0 + let cross = first[0] * second[1] - first[1] * second[0]; + // Native vertex welding can collapse generated band faces; query math naturally misses zero area. + cross != 0.0 || allow_zero_xy_area } fn surface_vertex( @@ -1364,6 +1366,54 @@ mod tests { .is_some()); } + #[test] + fn foundation_install_keeps_finite_welded_zero_area_band_for_queries() { + let mesh = synthetic_land_mesh(); + let positions = [ + [1644.398, 1118.5295, 132.54382], + [1644.4183, 1118.5342, 132.54755], + [1644.4183, 1118.5342, 132.54755], + ]; + let foundation = BasementFoundation { + fragments: vec![BasementFoundationFragment { + source_face: None, + surface_mask: FullSurfaceMask(0x8300), + positions, + }], + placements: Vec::new(), + }; + let world = TerrainWorld::from_land_msh(&mesh) + .expect("source world") + .with_basement_foundation(foundation) + .expect("finite generated fragment retained after native vertex welding"); + + assert_eq!(world.surface_count(), 1); + assert_eq!( + world + .basement_foundation() + .expect("installed foundation") + .fragments[0] + .positions, + positions + ); + assert_eq!( + world + .height_at([positions[1][0], positions[1][1]]) + .expect("degenerate height query"), + None + ); + assert_eq!( + world + .raycast( + [positions[1][0], positions[1][1], 200.0], + [0.0, 0.0, -1.0], + FullSurfaceMask(0), + ) + .expect("degenerate ray query"), + None + ); + } + #[test] fn foundation_install_rejects_invalid_source_identity_masks_and_generated_flags() { let mesh = synthetic_land_mesh(); @@ -1385,6 +1435,11 @@ mod tests { surface_mask: FullSurfaceMask(0x0300), positions: generated_triangle, }, + BasementFoundationFragment { + source_face: None, + surface_mask: FullSurfaceMask(0x8300), + positions: [[0.0, 0.0, 0.0], [0.25, 0.0, 0.0], [0.0, f32::NAN, 0.0]], + }, ]; for (index, fragment) in invalid.into_iter().enumerate() { -- cgit v1.2.3