fix(terrain): preserve zero-area welded foundation faces
Docs Deploy / Build and Deploy MkDocs (push) Successful in 32s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 32s
This commit is contained in:
1 file changed
+58
-3
@@ -341,7 +341,7 @@ impl TerrainWorld {
|
|||||||
let mut surfaces = Vec::with_capacity(foundation.fragments.len());
|
let mut surfaces = Vec::with_capacity(foundation.fragments.len());
|
||||||
let mut generated = 0_usize;
|
let mut generated = 0_usize;
|
||||||
for (fragment_index, fragment) in foundation.fragments.iter().enumerate() {
|
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 {
|
return Err(TerrainError::InvalidFoundationFragment {
|
||||||
fragment: fragment_index,
|
fragment: fragment_index,
|
||||||
});
|
});
|
||||||
@@ -1031,7 +1031,7 @@ fn build_surfaces(mesh: &LandMeshDocument) -> Result<Vec<RuntimeTriangle>, Terra
|
|||||||
Ok(triangles)
|
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()) {
|
if !vertices.iter().flatten().all(|value| value.is_finite()) {
|
||||||
return false;
|
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][0]) - f64::from(vertices[0][0]),
|
||||||
f64::from(vertices[2][1]) - f64::from(vertices[0][1]),
|
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(
|
fn surface_vertex(
|
||||||
@@ -1364,6 +1366,54 @@ mod tests {
|
|||||||
.is_some());
|
.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]
|
#[test]
|
||||||
fn foundation_install_rejects_invalid_source_identity_masks_and_generated_flags() {
|
fn foundation_install_rejects_invalid_source_identity_masks_and_generated_flags() {
|
||||||
let mesh = synthetic_land_mesh();
|
let mesh = synthetic_land_mesh();
|
||||||
@@ -1385,6 +1435,11 @@ mod tests {
|
|||||||
surface_mask: FullSurfaceMask(0x0300),
|
surface_mask: FullSurfaceMask(0x0300),
|
||||||
positions: generated_triangle,
|
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() {
|
for (index, fragment) in invalid.into_iter().enumerate() {
|
||||||
|
|||||||
Reference in new issue
Block a user