diff --git a/main/opengeometry/src/tessellation/tests/edge_on_triangles.rs b/main/opengeometry/src/tessellation/tests/edge_on_triangles.rs new file mode 100644 index 0000000..bedd70c --- /dev/null +++ b/main/opengeometry/src/tessellation/tests/edge_on_triangles.rs @@ -0,0 +1,55 @@ +use super::super::mesh::Tessellation; +use super::super::trimmed_face::general_curved_trimmed_face; +use crate::brep::{fine_accuracy, Frame3, Surface, SurfaceGeometry}; +use crate::primitives; +use std::collections::BTreeSet; + +const SLIVER: [[f64; 2]; 6] = [ + [0.0, 0.4315522348698053], + [-2.4066236448435014e-16, 0.0], + [0.0970789271307487, 0.0], + [0.1941578542615101, 2.2648549702353193e-14], + [0.12598686852943008, 0.13848252444618137], + [0.061224770648327764, 0.2824284296054027], +]; + +#[test] +fn an_ear_of_end_samples_rounded_off_their_line_turns_like_its_face() { + let body = + primitives::cylinder("c".into(), Frame3::IDENTITY, 3.35, 1.0, fine_accuracy()).unwrap(); + let face = body + .topology + .faces + .iter() + .find(|face| { + matches!( + body.geometry.surfaces[face.surface as usize], + SurfaceGeometry::Cylinder { .. } + ) + }) + .unwrap(); + let surface = body.geometry.surface(face.surface).unwrap(); + let ring = SLIVER + .into_iter() + .map(|uv| { + let on_end = [uv[0], if uv[1].abs() < 1e-12 { 0.0 } else { uv[1] }]; + (uv, surface.point_at(on_end).unwrap()) + }) + .collect::>(); + let mut mesh = Tessellation::new(0, 0.02); + general_curved_trimmed_face(&body, face, [0, 0], &[ring], 0.02, &mut mesh, 1000).unwrap(); + let mut directed = BTreeSet::new(); + for triangle in mesh.indices.chunks_exact(3) { + for edge in [ + (triangle[0], triangle[1]), + (triangle[1], triangle[2]), + (triangle[2], triangle[0]), + ] { + assert!( + directed.insert(edge), + "edge {edge:?} runs the same way twice" + ); + } + } + assert!(mesh.indices.len() >= 12); +} diff --git a/main/opengeometry/src/tessellation/tests/mod.rs b/main/opengeometry/src/tessellation/tests/mod.rs index 404d549..a205550 100644 --- a/main/opengeometry/src/tessellation/tests/mod.rs +++ b/main/opengeometry/src/tessellation/tests/mod.rs @@ -1,4 +1,5 @@ mod display; +mod edge_on_triangles; mod seams; mod short_loops; mod smooth_edges; diff --git a/main/opengeometry/src/tessellation/trimmed_face.rs b/main/opengeometry/src/tessellation/trimmed_face.rs index acb8528..553edfb 100644 --- a/main/opengeometry/src/tessellation/trimmed_face.rs +++ b/main/opengeometry/src/tessellation/trimmed_face.rs @@ -5,7 +5,7 @@ use super::trim::{ }; use crate::brep::{BrepEnvelope, Face, GeometryError, Surface, SurfaceGeometry}; use crate::geom2d::{point_in_ring2, Pt2}; -use crate::math::{scale, Interval, Point3}; +use crate::math::{cross, dot, norm, scale, sub, Interval, Point3}; use std::collections::{BTreeSet, HashMap}; const COUNT_ROUNDING: f64 = 1e-9; @@ -194,7 +194,7 @@ fn shift_periodic_holes( Ok(()) } -fn general_curved_trimmed_face( +pub(super) fn general_curved_trimmed_face( brep: &BrepEnvelope, face: &Face, _desired: [usize; 2], @@ -252,17 +252,47 @@ fn general_curved_trimmed_face( let normal = trim_normal(brep, face, surface, *coordinate)?; mesh.vertex(point, normal)?; } - for triangle in triangles { - let center = std::array::from_fn(|axis| { - (uv[triangle[0]][axis] + uv[triangle[1]][axis] + uv[triangle[2]][axis]) / 3.0 - }); + let triangles = triangles + .into_iter() + .map(|triangle| { + ( + triangle.map(|index| (start + index) as u32), + triangle.map(|index| uv[index]), + ) + }) + .collect::>(); + push_triangles_with_one_winding(brep, face, surface, &triangles, mesh, max_triangles) +} + +fn push_triangles_with_one_winding( + brep: &BrepEnvelope, + face: &Face, + surface: &SurfaceGeometry, + triangles: &[([u32; 3], [[f64; 2]; 3])], + mesh: &mut Tessellation, + max_triangles: usize, +) -> Result<(), GeometryError> { + let mut oriented = Vec::with_capacity(triangles.len()); + let mut flip = None; + for (ids, coordinates) in triangles { + let center = + std::array::from_fn(|axis| coordinates.iter().map(|uv| uv[axis]).sum::() / 3.0); let normal = trim_normal(brep, face, surface, center)?; - mesh.triangle( - triangle.map(|index| (start + index) as u32), - face.id, - normal, - max_triangles, - )?; + let [a, b, c] = ids.map(|id| mesh.point(id)); + let direction = cross(sub(b, a), sub(c, a)); + let along = dot(direction, normal); + let edge_on = along.abs() <= 1e-6 * norm(direction); + if !edge_on && flip.is_none() { + flip = Some(along < 0.0); + } + oriented.push((*ids, normal, direction, edge_on)); + } + for (ids, normal, direction, edge_on) in oriented { + let normal = match (edge_on, flip) { + (true, Some(flip)) => scale(direction, if flip { -1.0 } else { 1.0 }), + _ => normal, + }; + mesh.triangle(ids, face.id, normal, max_triangles)?; } Ok(()) }