1use ogeom_core::{FastMap, OgeomResult, parallel};
4use ogeom_math::{Point, Vector};
5use ogeom_mesh::{Bvh, Hit};
6
7use super::{Closest, DistanceOptions, MeshFeature, signed, unit, unit_or_zero};
8
9pub fn to_mesh(
29 index: &Bvh,
30 points: &[Point],
31 normals: Option<&[Vector]>,
32 options: &DistanceOptions,
33) -> OgeomResult<Vec<Option<Closest<MeshFeature>>>> {
34 to_mesh_with(index, &Pseudonormals::of(index), points, normals, options)
35}
36
37pub(crate) fn to_mesh_with(
40 index: &Bvh,
41 pseudo: &Pseudonormals,
42 points: &[Point],
43 normals: Option<&[Vector]>,
44 options: &DistanceOptions,
45) -> OgeomResult<Vec<Option<Closest<MeshFeature>>>> {
46 let facing = options.checked(points.len(), normals)?;
47 let hits = match (facing, normals) {
48 (Some(cos), Some(normals)) => {
49 let wanted: Vec<Option<Vector>> = normals.iter().map(|&n| unit(n)).collect();
50 index.nearest_many_where(points, options.max, |i, t| {
51 wanted[i].is_some_and(|m| pseudo.faces(t, m, cos))
52 })
53 }
54 _ => index.nearest_many(points, options.max),
55 };
56 Ok(parallel::map_ordered(&hits, |i, hit| {
57 hit.map(|hit| pseudo.closest(points[i], &hit, options.signed))
58 }))
59}
60
61pub(crate) struct Pseudonormals {
64 face: Vec<Vector>,
66 edge: Vec<[Vector; 3]>,
69 corner: Vec<[u32; 3]>,
71 vertex: Vec<Vector>,
74}
75
76const CORNERS: [u8; 3] = [0, 1, 2];
78
79fn key(p: Point) -> [u64; 3] {
82 [p.x, p.y, p.z].map(|c| (c + 0.0).to_bits())
83}
84
85impl Pseudonormals {
86 pub(crate) fn of(index: &Bvh) -> Self {
87 let count = index.triangle_count();
88 let triangles: Vec<[Point; 3]> = (0..count)
89 .filter_map(|t| index.triangle(u32::try_from(t).ok()?))
90 .collect();
91 let mut welded: FastMap<[u64; 3], u32> = FastMap::default();
92 let mut vertex: Vec<Vector> = Vec::new();
93 let mut face = Vec::with_capacity(count);
94 let mut corner = Vec::with_capacity(count);
95 for t in &triangles {
96 let ids = t.map(|p| {
97 let next = u32::try_from(vertex.len()).unwrap_or(u32::MAX);
98 let id = *welded.entry(key(p)).or_insert(next);
99 if id == next {
100 vertex.push(Vector::ZERO);
101 }
102 id
103 });
104 let normal = unit_or_zero((t[1] - t[0]).cross(t[2] - t[0]));
105 for k in 0..3 {
106 let (a, b, c) = (t[k], t[(k + 1) % 3], t[(k + 2) % 3]);
107 let (u, v) = (b - a, c - a);
108 let angle = u.cross(v).magnitude().atan2(u.dot(v));
109 vertex[ids[k] as usize] += normal * angle;
110 }
111 face.push(normal);
112 corner.push(ids);
113 }
114 let mut edges: FastMap<(u32, u32), Vector> = FastMap::default();
115 let edge_of = |ids: &[u32; 3], k: usize| {
116 let (a, b) = (ids[k], ids[(k + 1) % 3]);
117 (a.min(b), a.max(b))
118 };
119 for (ids, &normal) in corner.iter().zip(&face) {
120 for k in 0..3 {
121 let sum = edges.entry(edge_of(ids, k)).or_insert(Vector::ZERO);
122 *sum += normal;
123 }
124 }
125 let edge = corner
126 .iter()
127 .map(|ids| [0, 1, 2].map(|k| edges[&edge_of(ids, k)]))
128 .collect();
129 Self {
130 face,
131 edge,
132 corner,
133 vertex,
134 }
135 }
136
137 fn faces(&self, t: u32, m: Vector, cos: f64) -> bool {
140 let n = self.face[t as usize];
141 n != Vector::ZERO && n.dot(m) >= cos
142 }
143
144 fn closest(&self, p: Point, hit: &Hit, sign: bool) -> Closest<MeshFeature> {
148 let t = hit.triangle;
149 let at = t as usize;
150 let zero = hit.barycentric.map(|w| w == 0.0);
151 let (feature, normal) = match zero {
152 [false, false, false] => (MeshFeature::Face(t), self.face[at]),
153 [true, true, _] | [true, _, true] | [_, true, true] => {
154 let k = zero.iter().position(|z| !z).unwrap_or(0);
155 (
156 MeshFeature::Vertex(t, CORNERS[k]),
157 self.vertex[self.corner[at][k] as usize],
158 )
159 }
160 _ => {
161 let j = zero.iter().position(|&z| z).unwrap_or(0);
162 let k = (j + 1) % 3;
163 (MeshFeature::Edge(t, CORNERS[k]), self.edge[at][k])
164 }
165 };
166 Closest {
167 distance: signed(hit.distance, p, hit.point, normal, sign),
168 point: hit.point,
169 feature,
170 normal: unit_or_zero(normal),
171 }
172 }
173}