1use std::collections::HashMap;
22
23use geop_core_geometry::{
24 nurb_curve::NurbCurve,
25 nurb_surface::{NurbSurface, NurbSurface3D},
26};
27use geop_core_math::{
28 geop_error::{GeopError, GeopResult},
29 scalars::Scalar,
30 vector::Vector3,
31};
32use geop_core_topology::{
33 Curve2, Curve3, Sense,
34 build::{BodySpec, CoedgeOn, CoedgeSpec, EdgeSpec, FaceSpec},
35};
36use geop_ops_model::{BodyNames, Namer};
37
38use crate::{
39 cage::{Mesh, undirected},
40 limit::{ControlNet, control_net},
41 subdivide::subdivide,
42};
43
44fn cubic_knots(spans: usize) -> Vec<Scalar> {
46 let mut knots = vec![Scalar::ZERO; 4];
47 for s in 1..spans {
48 knots.extend([Scalar::from_i64(s as i64); 3]);
49 }
50 knots.extend([Scalar::from_i64(spans as i64); 4]);
51 knots
52}
53
54fn cubic(points: &[Vector3]) -> GeopResult<Curve3> {
56 NurbCurve::try_new(
57 3,
58 points.iter().map(|p| p.homogeneous(Scalar::ONE)).collect(),
59 cubic_knots((points.len() - 1) / 3),
60 )
61}
62
63fn line(a: [i64; 2], b: [i64; 2], length: i64) -> GeopResult<Curve2> {
65 let point = |p: [i64; 2]| {
66 geop_core_math::vector::Vector3::from_array([
67 Scalar::from_i64(p[0]),
68 Scalar::from_i64(p[1]),
69 Scalar::ONE,
70 ])
71 };
72 NurbCurve::try_new(
73 1,
74 vec![point(a), point(b)],
75 vec![
76 Scalar::ZERO,
77 Scalar::ZERO,
78 Scalar::from_i64(length),
79 Scalar::from_i64(length),
80 ],
81 )
82}
83
84#[derive(Clone, Copy, Debug)]
86enum EdgeParts {
87 Whole(usize),
89 Split(usize, usize),
92}
93
94struct Builder<'a> {
96 net: &'a ControlNet,
97 namer: &'a Namer,
98 spec: BodySpec,
99 names: BodyNames,
100 vertex: HashMap<usize, usize>,
103}
104
105impl Builder<'_> {
106 fn add_vertex(&mut self, sub_vertex: usize, name: &[&str]) {
107 self.vertex.insert(sub_vertex, self.spec.vertices.len());
108 self.spec.vertices.push(self.net.corner[sub_vertex]);
109 self.names.vertices.push(self.namer.name(name));
110 }
111
112 fn add_edge(&mut self, path: &[usize], name: &[&str]) -> GeopResult<usize> {
115 let mut points = vec![self.net.corner[path[0]]];
116 for w in path.windows(2) {
117 points.push(self.net.edge[&(w[0], w[1])]);
118 points.push(self.net.edge[&(w[1], w[0])]);
119 points.push(self.net.corner[w[1]]);
120 }
121 self.spec.edges.push(EdgeSpec {
122 curve: cubic(&points)?,
123 start: self.vertex[&path[0]],
124 end: self.vertex[&path[path.len() - 1]],
125 });
126 self.names.edges.push(self.namer.name(name));
127 Ok(self.spec.edges.len() - 1)
128 }
129}
130
131fn side(
135 parts: EdgeParts,
136 from: usize,
137 to: usize,
138 [start, middle, end]: [[i64; 2]; 3],
139) -> GeopResult<Vec<CoedgeSpec>> {
140 let forward = from < to;
141 let sense = Sense::from_forward(forward);
142 Ok(match parts {
143 EdgeParts::Whole(e) => vec![CoedgeSpec {
144 on: CoedgeOn::Edge(e, sense),
145 pcurve: line(start, end, 2)?,
146 }],
147 EdgeParts::Split(first, second) => {
148 let (near, far) = if forward {
149 (first, second)
150 } else {
151 (second, first)
152 };
153 vec![
154 CoedgeSpec {
155 on: CoedgeOn::Edge(near, sense),
156 pcurve: line(start, middle, 1)?,
157 },
158 CoedgeSpec {
159 on: CoedgeOn::Edge(far, sense),
160 pcurve: line(middle, end, 1)?,
161 },
162 ]
163 }
164 })
165}
166
167fn check_outward(mesh: &Mesh) -> GeopResult<()> {
170 let p = |v: usize| Vector3::from_array(mesh.positions[v].map(Scalar::from_f64));
171 let mut volume = Scalar::ZERO;
172 for face in &mesh.faces {
173 for k in 1..face.len() - 1 {
174 let (a, b, c) = (p(face[0]), p(face[k]), p(face[k + 1]));
175 volume = volume.add(a.prod_dot(&b.prod_cross(&c)));
176 }
177 }
178 if volume.definitely_greater(Scalar::ZERO) {
179 Ok(())
180 } else {
181 Err(GeopError::new(format!(
182 "the cage encloses no volume, or is turned inside out — its faces must run counter-clockwise seen from outside (six times its volume: {volume:?})"
183 )))
184 }
185}
186
187pub fn face_names(mesh: &Mesh, namer: &Namer) -> Vec<String> {
190 let mut names = Vec::new();
191 for (f, face) in mesh.faces.iter().enumerate() {
192 if face.len() == 4 {
193 names.push(namer.name(&[&mesh.face_names[f]]));
194 } else {
195 for &v in face {
196 names.push(namer.name(&[&mesh.face_names[f], &mesh.vertex_names[v]]));
197 }
198 }
199 }
200 names
201}
202
203pub fn limit_body(mesh: &Mesh, namer: &Namer) -> GeopResult<(BodySpec, BodyNames)> {
206 let topology = mesh.topology()?;
207 let edges = topology.edges();
208 let closed = edges.values().all(|faces| faces.len() == 2);
209 if closed {
210 check_outward(mesh)?;
211 }
212 let sub = subdivide(mesh, &topology)?;
213 let net = control_net(&sub)?;
214 let mut b = Builder {
215 net: &net,
216 namer,
217 spec: BodySpec::new(closed),
218 names: BodyNames {
219 solid: closed.then(|| namer.root()),
220 ..BodyNames::default()
221 },
222 vertex: HashMap::new(),
223 };
224
225 for v in 0..mesh.positions.len() {
226 b.add_vertex(v, &[&mesh.vertex_names[v]]);
227 }
228 let is_quad = |f: usize| mesh.faces[f].len() == 4;
229 for (f, face) in mesh.faces.iter().enumerate() {
230 if face.len() != 4 {
231 b.add_vertex(sub.face_point[f], &[&mesh.face_names[f], "centre"]);
232 }
233 }
234 let mut parts: HashMap<[usize; 2], EdgeParts> = HashMap::new();
235 for (&[a, c], faces) in &edges {
236 let name = mesh.edge_name(a, c);
237 let m = sub.edge_point[&[a, c]];
238 let part = if faces.iter().all(|&f| is_quad(f)) {
239 EdgeParts::Whole(b.add_edge(&[a, m, c], &[&name])?)
240 } else {
241 b.add_vertex(m, &[&name, "mid"]);
242 EdgeParts::Split(
243 b.add_edge(&[a, m], &[&name, &mesh.vertex_names[a]])?,
244 b.add_edge(&[m, c], &[&name, &mesh.vertex_names[c]])?,
245 )
246 };
247 parts.insert([a, c], part);
248 }
249 let part = |a: usize, c: usize| parts[&undirected(a, c)];
250
251 for (f, face) in mesh.faces.iter().enumerate() {
252 let n = face.len();
253 if n == 4 {
254 let mut grid = [Vector3::zero(); 49];
255 for k in 0..4 {
256 let patch = net.patch(&sub, sub.face_start[f] + k);
257 for (i, row) in patch.iter().enumerate() {
258 for (j, point) in row.iter().enumerate() {
259 let (u, v) = match k {
260 0 => (i, j),
261 1 => (6 - j, i),
262 2 => (6 - i, 6 - j),
263 _ => (j, 6 - i),
264 };
265 grid[u * 7 + v] = *point;
266 }
267 }
268 }
269 let surface = NurbSurface::try_new(
270 3,
271 3,
272 grid.iter().map(|p| p.homogeneous(Scalar::ONE)).collect(),
273 cubic_knots(2),
274 cubic_knots(2),
275 )?;
276 let corners = [[0, 0], [2, 0], [2, 2], [0, 2]];
277 let mut outer = Vec::new();
278 for k in 0..4 {
279 let (from, to) = (face[k], face[(k + 1) % 4]);
280 let (s, e) = (corners[k], corners[(k + 1) % 4]);
281 let middle = [(s[0] + e[0]) / 2, (s[1] + e[1]) / 2];
282 outer.extend(side(part(from, to), from, to, [s, middle, e])?);
283 }
284 b.spec.faces.push(FaceSpec {
285 surface,
286 outer,
287 holes: Vec::new(),
288 });
289 } else {
290 let centre = sub.face_point[f];
291 let spokes = (0..n)
292 .map(|k| {
293 let (a, c) = (face[k], face[(k + 1) % n]);
294 let m = sub.edge_point[&undirected(a, c)];
295 b.add_edge(&[centre, m], &[&mesh.face_names[f], &mesh.edge_name(a, c)])
296 })
297 .collect::<GeopResult<Vec<_>>>()?;
298 for k in 0..n {
299 let (v, after, before) = (face[k], face[(k + 1) % n], face[(k + n - 1) % n]);
300 let patch = net.patch(&sub, sub.face_start[f] + k);
301 let surface: NurbSurface3D = NurbSurface::try_new(
302 3,
303 3,
304 patch
305 .iter()
306 .flatten()
307 .map(|p| p.homogeneous(Scalar::ONE))
308 .collect(),
309 cubic_knots(1),
310 cubic_knots(1),
311 )?;
312 let mut outer = Vec::new();
313 let ahead = side(part(v, after), v, after, [[0, 0], [1, 0], [2, 0]])?;
316 outer.push(ahead[0].clone());
317 outer.push(CoedgeSpec {
318 on: CoedgeOn::Edge(spokes[k], Sense::Reversed),
319 pcurve: line([1, 0], [1, 1], 1)?,
320 });
321 outer.push(CoedgeSpec {
322 on: CoedgeOn::Edge(spokes[(k + n - 1) % n], Sense::Forward),
323 pcurve: line([1, 1], [0, 1], 1)?,
324 });
325 let behind = side(part(before, v), before, v, [[0, 2], [0, 1], [0, 0]])?;
328 outer.push(behind[1].clone());
329 b.spec.faces.push(FaceSpec {
330 surface,
331 outer,
332 holes: Vec::new(),
333 });
334 }
335 }
336 }
337 b.names.faces = face_names(mesh, namer);
338 b.spec.shells = vec![(0..b.spec.faces.len()).collect()];
339 Ok((b.spec, b.names))
340}