fix(obj): concave OBJ faces are triangulated, not fanned

A fan only covers convex faces; architectural n-gons filled well outside
their outline (the sample's faces covered 63% extra area).

Fixes #680

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-09-28 19:33:40 +03:00
1 parent b66b5100bd
commit 4c1ddec646
2 files changed
+35 -6

No files matched your search

+10 -1
View File
@@ -16,6 +16,15 @@ use crate::t;
/// Triangulate a planar polygon through the kernel.
pub(crate) fn triangulate_planar(poly: &[[f64; 3]]) -> Vec<[f64; 3]> {
triangulate_planar_indices(poly)
.into_iter()
.flat_map(|triangle| triangle.map(|index| poly[index]))
.collect()
}
/// Triangles of a planar polygon as indices into `poly` — for callers that
/// carry per-vertex data (normals) alongside the positions.
pub(crate) fn triangulate_planar_indices(poly: &[[f64; 3]]) -> Vec<[usize; 3]> {
if poly.len() < 3 {
return Vec::new();
}
@@ -51,7 +60,7 @@ pub(crate) fn triangulate_planar(poly: &[[f64; 3]]) -> Vec<[f64; 3]> {
}
triangles
.into_iter()
.flat_map(|triangle| triangle.map(|index| poly[source[index]]))
.map(|triangle| triangle.map(|index| source[index]))
.collect()
}
+25 -5
View File
@@ -51,11 +51,31 @@ pub fn parse_obj(src: &str, color: [f32; 4]) -> Option<MeshModel> {
Some((pos_i, norm_i))
})
.collect();
// Fan-triangulate: (0,1,2), (0,2,3), …
for k in 1..(descs.len() as isize - 1) {
face_verts.push(descs[0]);
face_verts.push(descs[k as usize]);
face_verts.push(descs[k as usize + 1]);
// A fan is only right for convex faces; floor-plan n-gons
// are concave, so larger faces go through the kernel's
// triangulator and fall back to the fan if it declines. (#680)
let corners: Vec<[f64; 3]> = descs
.iter()
.map(|(pos_i, _)| {
let p = positions.get(*pos_i).copied().unwrap_or([0.0; 3]);
[p[0] as f64, p[1] as f64, p[2] as f64]
})
.collect();
let triangles = if descs.len() > 3 {
crate::entities::mesh::triangulate_planar_indices(&corners)
} else {
Vec::new()
};
if triangles.is_empty() {
for k in 1..(descs.len() as isize - 1) {
face_verts.push(descs[0]);
face_verts.push(descs[k as usize]);
face_verts.push(descs[k as usize + 1]);
}
} else {
for triangle in triangles {
face_verts.extend(triangle.map(|index| descs[index]));
}
}
}
_ => {}