forked from 1b-agent/opencadstudio-upstream
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:
1 parent
b66b5100bd
commit
4c1ddec646
2 files changed
+35
-6
No files matched your search
+10
-1
@@ -16,6 +16,15 @@ use crate::t;
|
|||||||
|
|
||||||
/// Triangulate a planar polygon through the kernel.
|
/// Triangulate a planar polygon through the kernel.
|
||||||
pub(crate) fn triangulate_planar(poly: &[[f64; 3]]) -> Vec<[f64; 3]> {
|
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 {
|
if poly.len() < 3 {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
@@ -51,7 +60,7 @@ pub(crate) fn triangulate_planar(poly: &[[f64; 3]]) -> Vec<[f64; 3]> {
|
|||||||
}
|
}
|
||||||
triangles
|
triangles
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|triangle| triangle.map(|index| poly[source[index]]))
|
.map(|triangle| triangle.map(|index| source[index]))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-5
@@ -51,11 +51,31 @@ pub fn parse_obj(src: &str, color: [f32; 4]) -> Option<MeshModel> {
|
|||||||
Some((pos_i, norm_i))
|
Some((pos_i, norm_i))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
// Fan-triangulate: (0,1,2), (0,2,3), …
|
// A fan is only right for convex faces; floor-plan n-gons
|
||||||
for k in 1..(descs.len() as isize - 1) {
|
// are concave, so larger faces go through the kernel's
|
||||||
face_verts.push(descs[0]);
|
// triangulator and fall back to the fan if it declines. (#680)
|
||||||
face_verts.push(descs[k as usize]);
|
let corners: Vec<[f64; 3]> = descs
|
||||||
face_verts.push(descs[k as usize + 1]);
|
.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]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
Reference in new issue
Block a user