1use geop_core_geometry::nurb_curve::NurbCurve3D;
37use geop_core_math::{
38 geop_error::{GeopError, GeopResult, WithContext},
39 plain,
40 primitives::{Motion, Pose, Quaternion},
41 scalars::Scalar,
42 vector::Vector3,
43};
44use geop_core_program::{element::ElementReference, reader::Reader};
45use geop_core_topology::{FaceId, Model};
46use geop_ops_model::placed::placed_parts;
47use geop_ops_model::{NamedModel, Threads, model, threads};
48
49use crate::{
50 drawing::drawn_faces,
51 hidden_lines::{
52 COARSE_SUBDIVISION, Occluder, ProjectedView, ViewLine, ViewOptions, bounds, boxes_meet,
53 cut_points, drop_drawn, meetings, occluders_bounds, project_view, ray_length, seen,
54 },
55 view::{ViewAxis, ViewFrame},
56};
57
58pub struct Body<'p> {
60 pub path: String,
64 pub named: &'p NamedModel,
66 pub threads: Option<&'p Threads>,
68 pub faces: Vec<FaceId>,
69 pub pose: Option<Pose>,
71}
72
73impl<'p> Body<'p> {
74 pub fn model(&self) -> &'p Model {
75 self.named.topology()
76 }
77
78 fn turn(&self) -> Option<Quaternion> {
80 self.pose
81 .as_ref()
82 .map(|p| p.rotation())
83 .filter(|r| !r.is_identity())
84 }
85}
86
87#[derive(Clone, Copy)]
90pub(crate) struct Reach {
91 paper: [f64; 4],
92 near: f64,
93}
94
95pub struct Scene<'p> {
97 pub bodies: Vec<Body<'p>>,
98 there: Vec<Option<Motion>>,
101 back: Vec<Option<Motion>>,
102 occluders: Vec<Vec<Occluder>>,
105 occluders_of: Vec<usize>,
106 corners: Vec<[Vector3; 8]>,
108 length: Scalar,
110}
111
112impl<'p> Scene<'p> {
113 pub fn new(bodies: Vec<Body<'p>>) -> GeopResult<Self> {
115 let mut occluders: Vec<Vec<Occluder>> = Vec::new();
116 let mut models: Vec<&Model> = Vec::new();
117 let mut occluders_of = Vec::new();
118 let mut corners = Vec::new();
119 let (mut there, mut back) = (Vec::new(), Vec::new());
120 for body in &bodies {
121 let model = body.model();
122 let index = match models.iter().position(|m| std::ptr::eq(*m, model)) {
123 Some(index) => index,
124 None => {
125 occluders.push(
126 body.faces
127 .iter()
128 .map(|&f| Occluder::of(model, f))
129 .collect::<GeopResult<_>>()?,
130 );
131 models.push(model);
132 models.len() - 1
133 }
134 };
135 occluders_of.push(index);
136 let motion = body.pose.map(|p| p.motion());
137 let (lo, hi) = occluders_bounds(&occluders[index]);
138 corners.push(std::array::from_fn(|i| {
139 let c = Vector3::from_array(std::array::from_fn(|k| {
140 Scalar::from_f64(if (i >> k) & 1 == 1 { hi[k] } else { lo[k] })
141 }));
142 match &motion {
143 Some(m) => m.apply(&c),
144 None => c,
145 }
146 }));
147 there.push(motion);
148 back.push(body.pose.map(|p| p.inverse().motion()));
149 }
150 let mut lo = [f64::INFINITY; 3];
151 let mut hi = [f64::NEG_INFINITY; 3];
152 for c in corners.iter().flatten() {
153 for k in 0..3 {
154 lo[k] = lo[k].min(c[k].lower().to_f64());
155 hi[k] = hi[k].max(c[k].upper().to_f64());
156 }
157 }
158 let diagonal = plain::diagonal(lo, hi);
159 let length = match bodies.len() {
160 1 => ray_length(&occluders[0]),
162 _ => Scalar::from_f64(2.0 * diagonal + 1.0),
163 };
164 Ok(Scene {
165 bodies,
166 there,
167 back,
168 occluders,
169 occluders_of,
170 corners,
171 length,
172 })
173 }
174
175 pub fn side_of(&self, k: usize, origin: &Vector3, normal: &Vector3) -> Option<bool> {
180 let heights: Vec<Scalar> = self.corners[k]
181 .iter()
182 .map(|c| c.sub(origin).prod_dot(normal))
183 .collect();
184 if heights.iter().all(|h| h.definitely_greater(Scalar::ZERO)) {
185 Some(true)
186 } else if heights.iter().all(|h| h.definitely_less(Scalar::ZERO)) {
187 Some(false)
188 } else {
189 None
190 }
191 }
192
193 pub fn groups(&self) -> Vec<Vec<usize>> {
197 let mut groups: Vec<Vec<usize>> = Vec::new();
198 for (k, body) in self.bodies.iter().enumerate() {
199 let alike = |g: &&mut Vec<usize>| {
200 let first = &self.bodies[g[0]];
201 std::ptr::eq(first.model(), body.model()) && same_turn(first.turn(), body.turn())
202 };
203 match groups.iter_mut().find(alike) {
204 Some(group) => group.push(k),
205 None => groups.push(vec![k]),
206 }
207 }
208 groups
209 }
210
211 fn local_frame(&self, k: usize, frame: &ViewFrame) -> ViewFrame {
213 match (&self.back[k], self.bodies[k].turn()) {
214 (Some(back), Some(_)) => {
215 let turn = |axis: &ViewAxis| ViewAxis::General(back.rotate(&axis.vector()));
216 ViewFrame {
217 direction: turn(&frame.direction),
218 right: turn(&frame.right),
219 up: turn(&frame.up),
220 }
221 }
222 _ => *frame,
223 }
224 }
225
226 fn placed_line(&self, k: usize, line: &ViewLine, frame: &ViewFrame) -> GeopResult<ViewLine> {
229 let (curve3, curve) = match &self.there[k] {
230 Some(motion) => {
231 let curve3 = line.curve3.transform(motion);
232 let curve = frame.project_curve(&curve3)?;
233 (curve3, curve)
234 }
235 None => (line.curve3.clone(), line.curve.clone()),
236 };
237 Ok(ViewLine {
238 curve,
239 curve3,
240 kind: line.kind,
241 visible: line.visible,
242 edge: line.edge,
243 body: k,
244 source: line.source,
245 range: line.range,
246 })
247 }
248
249 pub fn project(&self, frame: &ViewFrame, options: &ViewOptions) -> GeopResult<ProjectedView> {
252 let mut lines = Vec::new();
253 let mut behind = vec![false; self.bodies.len()];
254 for group in self.groups() {
255 self.mark_copies_behind(&group, frame, &mut behind);
256 let first = &self.bodies[group[0]];
257 let view = project_view(
258 first.model(),
259 &first.faces,
260 &self.local_frame(group[0], frame),
261 options,
262 )
263 .map_err(|e| e.with_context(format!("the part placed as {:?}", first.path)))?;
264 for &k in group.iter().filter(|&&k| !behind[k]) {
265 for line in &view.lines {
266 lines.push(self.placed_line(k, line, frame)?);
267 }
268 }
269 }
270 if self.bodies.len() > 1 {
271 lines = self.together(lines, &behind, frame, options)?;
272 }
273 lines.sort_by_key(|l| l.body);
274 Ok(ProjectedView {
275 frame: *frame,
276 lines,
277 })
278 }
279
280 fn mark_copies_behind(&self, group: &[usize], frame: &ViewFrame, behind: &mut [bool]) {
289 let toward_eye = frame.toward_eye();
290 let at: Vec<Vector3> = group
291 .iter()
292 .map(|&k| {
293 self.bodies[k]
294 .pose
295 .map(|p| p.position())
296 .unwrap_or_else(Vector3::zero)
297 })
298 .collect();
299 for (i, &k) in group.iter().enumerate() {
300 let (paper, depth) = (frame.project_point(&at[i]), toward_eye.prod_dot(&at[i]));
301 behind[k] = group.iter().enumerate().any(|(j, _)| {
302 j != i
303 && frame.project_point(&at[j]).could_be_equal(&paper)
304 && match toward_eye.prod_dot(&at[j]) {
305 d if d.definitely_greater(depth) => true,
306 d if d.could_be_equal(depth) => j < i,
307 _ => false,
308 }
309 });
310 }
311 }
312
313 fn reach(&self, frame: &ViewFrame) -> Vec<Reach> {
315 let toward_eye = frame.toward_eye();
316 self.corners
317 .iter()
318 .map(|corners| {
319 let mut paper = [
320 f64::INFINITY,
321 f64::INFINITY,
322 f64::NEG_INFINITY,
323 f64::NEG_INFINITY,
324 ];
325 let mut near = f64::NEG_INFINITY;
326 for c in corners {
327 let p = frame.project_point(c);
328 for k in 0..2 {
329 paper[k] = paper[k].min(p[k].lower().to_f64());
330 paper[k + 2] = paper[k + 2].max(p[k].upper().to_f64());
331 }
332 near = near.max(toward_eye.prod_dot(c).upper().to_f64());
333 }
334 Reach { paper, near }
335 })
336 .collect()
337 }
338
339 fn hidden_by(
343 &self,
344 bodies: impl IntoIterator<Item = usize>,
345 reach: &[Reach],
346 frame: &ViewFrame,
347 point: Vector3,
348 ) -> GeopResult<bool> {
349 let toward_eye = frame.toward_eye();
350 let depth = toward_eye.prod_dot(&point).lower().to_f64();
351 let on_paper = frame.project_point(&point);
352 let (x, y) = (on_paper[0], on_paper[1]);
353 for b in bodies {
354 let r = &reach[b];
355 if r.near < depth
356 || x.upper().to_f64() < r.paper[0]
357 || r.paper[2] < x.lower().to_f64()
358 || y.upper().to_f64() < r.paper[1]
359 || r.paper[3] < y.lower().to_f64()
360 {
361 continue;
362 }
363 let (p, d) = match &self.back[b] {
364 Some(back) => (back.apply(&point), back.rotate(&toward_eye)),
365 None => (point, toward_eye),
366 };
367 let model = self.bodies[b].model();
368 let occluders = &self.occluders[self.occluders_of[b]];
369 let ctx = |e: GeopError| {
370 e.with_context(format!(
371 "whether the part placed as {:?} hides {point:?}",
372 self.bodies[b].path
373 ))
374 };
375 if !seen(model, occluders, p, &d, self.length).with_context(&ctx)? {
376 return Ok(true);
377 }
378 }
379 Ok(false)
380 }
381
382 pub fn point_seen(&self, frame: &ViewFrame, point: Vector3) -> GeopResult<bool> {
386 let reach = self.reach(frame);
387 Ok(!self.hidden_by(0..self.bodies.len(), &reach, frame, point)?)
388 }
389
390 fn together(
405 &self,
406 lines: Vec<ViewLine>,
407 behind: &[bool],
408 frame: &ViewFrame,
409 options: &ViewOptions,
410 ) -> GeopResult<Vec<ViewLine>> {
411 let n = self.bodies.len();
412 let reach = self.reach(frame);
413 let boxes: Vec<_> = lines.iter().map(|l| bounds(&l.curve)).collect();
414 let mut shown: Vec<Vec<usize>> = vec![Vec::new(); n];
415 for (i, line) in lines.iter().enumerate() {
416 if line.visible {
417 shown[line.body].push(i);
418 }
419 }
420 let area = |b: usize| {
421 let p = &reach[b].paper;
422 (p[2] - p[0]) * (p[3] - p[1])
423 };
424 let mut order: Vec<usize> = (0..n).filter(|&b| !behind[b]).collect();
425 order.sort_by(|&a, &b| area(b).total_cmp(&area(a)));
426
427 let toward_eye = frame.toward_eye();
428 let farthest = |curve3: &NurbCurve3D| -> GeopResult<f64> {
431 let mut depth = f64::INFINITY;
432 for q in &curve3.control_points {
433 let p = Vector3::from_array([q[0].div(q[3])?, q[1].div(q[3])?, q[2].div(q[3])?]);
434 depth = depth.min(toward_eye.prod_dot(&p).lower().to_f64());
435 }
436 Ok(depth)
437 };
438 let mut out = Vec::new();
439 for (i, line) in lines.iter().enumerate() {
440 if !line.visible {
441 out.push(line.clone());
442 continue;
443 }
444 let domain = line.curve.domain();
445 let (mut seen, mut hidden) = (vec![domain], Vec::new());
446 for &b in &order {
447 if b == line.body || !boxes_meet(&boxes[i], &reach[b].paper) {
448 continue;
449 }
450 let mut still = Vec::new();
451 for (lo, hi) in seen {
452 let whole = lo.could_be_equal(domain.0) && hi.could_be_equal(domain.1);
453 let (curve, curve3) = match whole {
454 true => (line.curve.clone(), line.curve3.clone()),
455 false => (
456 line.curve.sub_curve(lo, hi)?,
457 line.curve3.sub_curve(lo, hi)?,
458 ),
459 };
460 let here = bounds(&curve);
461 if !boxes_meet(&here, &reach[b].paper) || reach[b].near < farthest(&curve3)? {
462 still.push((lo, hi));
463 continue;
464 }
465 let mut cuts = Vec::new();
466 for &j in shown[b].iter().filter(|&&j| boxes_meet(&here, &boxes[j])) {
467 let other = &lines[j];
468 let (on_line, _) = meetings(
469 (&curve, &curve3),
470 (&other.curve, &other.curve3),
471 Scalar::from_f64(COARSE_SUBDIVISION),
472 )
473 .map_err(|e| {
474 e.with_context(format!(
475 "where the {:?} of edge {:?} of the part placed as {:?} \
476 and the {:?} of edge {:?} of the part placed as {:?} \
477 cross on the paper",
478 line.kind,
479 line.edge,
480 self.bodies[line.body].path,
481 other.kind,
482 other.edge,
483 self.bodies[b].path
484 ))
485 })?;
486 cuts.extend(on_line);
487 }
488 let mut ends = vec![lo];
489 ends.extend(cut_points(cuts, (lo, hi)));
490 ends.push(hi);
491 for (a, z, visible) in self.decide(line, &ends, b, &reach, frame)? {
492 match visible {
493 true => still.push((a, z)),
494 false => hidden.push((a, z)),
495 }
496 }
497 }
498 seen = still;
499 if seen.is_empty() {
500 break;
501 }
502 }
503 let mut pieces: Vec<(Scalar, Scalar, bool)> = seen
505 .into_iter()
506 .map(|(a, z)| (a, z, true))
507 .chain(hidden.into_iter().map(|(a, z)| (a, z, false)))
508 .collect();
509 pieces.sort_by(|p, q| p.0.to_f64().total_cmp(&q.0.to_f64()));
510 let mut decided: Vec<(Scalar, Scalar, bool)> = Vec::new();
511 for (a, z, visible) in pieces {
512 match decided.last_mut() {
513 Some(last) if last.2 == visible => last.1 = z,
514 _ => decided.push((a, z, visible)),
515 }
516 }
517 if let [(_, _, visible)] = decided.as_slice() {
518 out.push(ViewLine {
519 visible: *visible,
520 ..line.clone()
521 });
522 continue;
523 }
524 for (a, z, visible) in decided {
525 out.push(ViewLine {
526 curve: line.curve.sub_curve(a, z)?,
527 curve3: line.curve3.sub_curve(a, z)?,
528 visible,
529 range: (a, z),
530 ..line.clone()
531 });
532 }
533 }
534 if !options.hidden_lines {
535 out.retain(|l| l.visible);
536 }
537 drop_drawn(out, |a, b| a.body != b.body)
540 }
541
542 fn decide(
548 &self,
549 line: &ViewLine,
550 ends: &[Scalar],
551 b: usize,
552 reach: &[Reach],
553 frame: &ViewFrame,
554 ) -> GeopResult<Vec<(Scalar, Scalar, bool)>> {
555 let mut pieces: Vec<(Scalar, Scalar, GeopResult<bool>)> = Vec::new();
556 for pair in ends.windows(2) {
557 let (lo, hi) = (pair[0], pair[1]);
558 let at = |f: f64| -> GeopResult<bool> {
559 let t = Scalar::fraction(lo, hi, Scalar::from_f64(f));
560 let point = line.curve3.evaluate(t)?;
561 Ok(!self.hidden_by([b], reach, frame, point)?)
562 };
563 let mut outcome = Err(GeopError::new("no point tried"));
564 for f in [0.5, 0.25, 0.75] {
565 outcome = at(f).map_err(|e| {
566 e.with_context(format!(
567 "the piece [{lo:?}, {hi:?}] of the {:?} of edge {:?} of the part \
568 placed as {:?}",
569 line.kind, line.edge, self.bodies[line.body].path
570 ))
571 });
572 if outcome.is_ok() {
573 break;
574 }
575 }
576 pieces.push((lo, hi, outcome));
577 }
578 let known: Vec<Option<bool>> = pieces.iter().map(|p| p.2.as_ref().ok().copied()).collect();
579 pieces
580 .into_iter()
581 .enumerate()
582 .map(|(k, (lo, hi, outcome))| {
583 let visible = match outcome {
584 Ok(v) => v,
585 Err(e) => (1..known.len())
586 .find_map(|step| {
587 let before = k.checked_sub(step).and_then(|i| known[i]);
588 before.or_else(|| known.get(k + step).copied().flatten())
589 })
590 .ok_or(e)?,
591 };
592 Ok((lo, hi, visible))
593 })
594 .collect()
595 }
596}
597
598fn same_turn(a: Option<Quaternion>, b: Option<Quaternion>) -> bool {
602 match (a, b) {
603 (None, None) => true,
604 (Some(a), Some(b)) => a.components().iter().zip(b.components()).all(|(x, y)| {
605 x.lower().to_f64() == y.lower().to_f64() && x.upper().to_f64() == y.upper().to_f64()
606 }),
607 _ => false,
608 }
609}
610
611impl<'p> Scene<'p> {
612 pub fn of(reader: &Reader<'p>) -> GeopResult<Self> {
617 let mut bodies = Vec::new();
618 push(reader, String::new(), None, &mut bodies)?;
619 for placed in placed_parts(reader)? {
620 let inner = placed.located.part.clone();
621 push(&inner, placed.name, Some(placed.located.world), &mut bodies)?;
622 }
623 Self::new(bodies)
624 }
625}
626
627fn push<'p>(
630 reader: &Reader<'p>,
631 path: String,
632 pose: Option<Pose>,
633 bodies: &mut Vec<Body<'p>>,
634) -> GeopResult<()> {
635 if !reader.defines(&model()) {
637 return Ok(());
638 }
639 let whole = ElementReference::<NamedModel, NamedModel>::whole(model());
640 let named = reader.retrieve_element(&whole)?;
641 let faces = drawn_faces(named.topology());
642 if !faces.is_empty() {
643 let threads = match reader.defines(&threads()) {
644 true => {
645 let whole = ElementReference::<Threads, Threads>::whole(threads());
646 Some(reader.retrieve_element(&whole)?)
647 }
648 false => None,
649 };
650 bodies.push(Body {
651 path,
652 named,
653 threads,
654 faces,
655 pose,
656 });
657 }
658 Ok(())
659}