1use fast_mesh_query::{Fill, Gap2, Plane, Slice, contour_gap, contour_length, signed_area};
34use fast_mesh_store::{ChangeSet, Mesh, StoreError, TriangleId, VertexId, par};
35
36use crate::placement::{Placement, local_to_world, world_to_local};
37use crate::scene::Entry;
38use crate::{MeshId, Result, Scene, SceneError, SectionId, UpdateMode};
39
40#[derive(Debug, Clone, PartialEq)]
43pub struct SectionDesc {
44 pub plane: Plane,
47 pub meshes: Vec<MeshId>,
49 pub fill: bool,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum SectionStatus {
57 Current,
59 Stale,
61}
62
63#[derive(Debug, Clone, Default, PartialEq)]
65pub struct SectionContour {
66 pub points: Vec<[f64; 3]>,
68 pub uv: Vec<[f64; 2]>,
70 pub edges: Vec<(VertexId, VertexId)>,
72 pub triangles: Vec<TriangleId>,
76 pub closed: bool,
78 pub oriented: bool,
82 pub manifold: bool,
84 pub length: f64,
86 pub area: f64,
88}
89
90#[derive(Debug, Clone, PartialEq)]
93pub struct SectionResult {
94 pub contours: Vec<SectionContour>,
96 pub length: f64,
98 pub area: f64,
100 pub fill: Option<Fill>,
104 pub fill_ok: bool,
107}
108
109impl Default for SectionResult {
110 fn default() -> Self {
113 SectionResult {
114 contours: Vec::new(),
115 length: 0.0,
116 area: 0.0,
117 fill: None,
118 fill_ok: true,
119 }
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq)]
126pub struct Gap {
127 pub distance: f64,
130 pub a: [f64; 3],
132 pub b: [f64; 3],
134}
135
136#[cfg(test)]
137thread_local! {
138 static PARALLEL_SECTION_BATCHES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
140}
141
142const PAR_MIN_SECTION_WORK: usize = 8 * 1024;
151
152fn oom() -> SceneError {
153 SceneError::Store(StoreError::OutOfMemory)
154}
155
156fn try_vec<T>(n: usize) -> Result<Vec<T>> {
158 let mut v = Vec::new();
159 v.try_reserve_exact(n).map_err(|_| oom())?;
160 Ok(v)
161}
162
163#[derive(Debug, Clone)]
166pub(crate) struct SectionHook {
167 pub(crate) section: SectionId,
168 pub(crate) changes: Option<ChangeSet>,
170 pub(crate) placement_changed: bool,
171 pub(crate) reaches: bool,
176}
177
178impl SectionHook {
179 fn new(section: SectionId) -> SectionHook {
180 SectionHook {
181 section,
182 changes: Some(ChangeSet::default()),
183 placement_changed: false,
184 reaches: false,
185 }
186 }
187
188 fn is_clean(&self) -> bool {
189 !self.placement_changed && self.changes.as_ref().is_some_and(ChangeSet::is_empty)
190 }
191}
192
193struct SectionEntry {
195 mesh: MeshId,
196 result: SectionResult,
197 revision: u64,
200 stale: bool,
202 computed_once: bool,
203 crossed_vertices: Vec<VertexId>,
205 crossed_triangles: Vec<TriangleId>,
207 plane: Option<Plane>,
209}
210
211impl SectionEntry {
212 fn new(mesh: MeshId) -> SectionEntry {
213 SectionEntry {
214 mesh,
215 result: SectionResult::default(),
216 revision: 0,
217 stale: true,
218 computed_once: false,
219 crossed_vertices: Vec::new(),
220 crossed_triangles: Vec::new(),
221 plane: None,
222 }
223 }
224}
225
226pub(crate) struct Section {
228 id: SectionId,
229 desc: SectionDesc,
230 entries: Vec<SectionEntry>,
232}
233
234struct Computed {
236 result: SectionResult,
237 crossed_vertices: Vec<VertexId>,
238 crossed_triangles: Vec<TriangleId>,
239}
240
241impl Entry {
242 fn section_hook(&self, id: SectionId) -> Option<&SectionHook> {
243 self.section_hooks.iter().find(|h| h.section == id)
244 }
245
246 fn reset_section_hook(&mut self, id: SectionId) {
247 if let Some(h) = self.section_hooks.iter_mut().find(|h| h.section == id) {
248 *h = SectionHook::new(id);
249 }
250 }
251
252 fn remove_section_hook(&mut self, id: SectionId) {
253 self.section_hooks.retain(|h| h.section != id);
254 }
255}
256
257fn rotate_back(rotation: &[[f64; 3]; 3], d: [f64; 3]) -> [f64; 3] {
259 let r = rotation;
260 std::array::from_fn(|i| r[0][i] * d[0] + r[1][i] * d[1] + r[2][i] * d[2])
261}
262
263fn scale_back(p: &Placement, d: [f64; 3]) -> [f64; 3] {
267 let t = rotate_back(&p.rotation, d);
268 std::array::from_fn(|i| p.scale[i] * t[i])
269}
270
271fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
272 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
273}
274
275fn unit(a: [f64; 3]) -> [f64; 3] {
276 let n = dot(a, a).sqrt();
277 a.map(|c| c / n)
278}
279
280fn local_plane(p: &Placement, mesh_origin: [f64; 3], plane: &Plane) -> Result<Plane> {
288 let origin = world_to_local(p, mesh_origin, plane.origin());
289 let u = unit(scale_back(p, plane.u()));
290 let v = scale_back(p, plane.v());
291 let d = dot(v, u);
292 let v = unit(std::array::from_fn(|k| v[k] - d * u[k]));
293 Ok(Plane::with_axes(origin, u, v)?)
294}
295
296fn compute(entry: &Entry, desc: &SectionDesc) -> Result<Computed> {
298 let plane = &desc.plane;
299 let (p, o) = (&entry.placement, entry.mesh.origin());
300 let mut cut = Slice::default();
301 fast_mesh_query::slice(
302 &entry.mesh,
303 &entry.bvh,
304 &local_plane(p, o, plane)?,
305 &mut cut,
306 )?;
307
308 let mut contours = try_vec(cut.contours.len())?;
309 let (mut length, mut area) = (0.0, 0.0);
310 let (mut nv, mut nt) = (0, 0);
311 for c in cut.contours {
312 let mut points = try_vec(c.points.len())?;
313 let mut uv = try_vec(c.points.len())?;
314 for &q in &c.points {
315 let w = local_to_world(p, o, q);
316 points.push(w);
317 uv.push(plane.to_uv(w));
318 }
319 let l = contour_length(&uv, c.closed);
320 let a = if c.closed { signed_area(&uv) } else { 0.0 };
321 length += l;
322 if c.closed && c.oriented {
323 area += a;
324 }
325 nv += 2 * c.edges.len();
326 nt += c.triangles.len();
327 contours.push(SectionContour {
328 points,
329 uv,
330 edges: c.edges,
331 triangles: c.triangles,
332 closed: c.closed,
333 oriented: c.oriented,
334 manifold: c.manifold,
335 length: l,
336 area: a,
337 });
338 }
339
340 let mut crossed_vertices = try_vec(nv)?;
341 let mut crossed_triangles = try_vec(nt)?;
342 for c in &contours {
343 crossed_vertices.extend(c.edges.iter().flat_map(|&(a, b)| [a, b]));
344 crossed_triangles.extend_from_slice(&c.triangles);
345 }
346 crossed_vertices.sort_unstable();
347 crossed_vertices.dedup();
348 crossed_triangles.sort_unstable();
349 crossed_triangles.dedup();
350
351 let (fill, fill_ok) = if desc.fill {
352 let mut closed = try_vec(contours.len())?;
353 closed.extend(
354 contours
355 .iter()
356 .filter(|c| c.closed)
357 .map(|c| (c.uv.as_slice(), c.oriented)),
358 );
359 let mut f = Fill::default();
360 let ok = fast_mesh_query::fill(&closed, &mut f)?;
361 (Some(f), ok)
362 } else {
363 (None, true)
364 };
365
366 Ok(Computed {
367 result: SectionResult {
368 contours,
369 length,
370 area,
371 fill,
372 fill_ok,
373 },
374 crossed_vertices,
375 crossed_triangles,
376 })
377}
378
379fn reaches_plane(mesh: &mut Mesh, plane: &Plane, c: &ChangeSet, se: &SectionEntry) -> bool {
384 let crossed_vertex = |v: &VertexId| se.crossed_vertices.binary_search(v).is_ok();
385 let crossed_triangle = |t: &TriangleId| se.crossed_triangles.binary_search(t).is_ok();
386 if c.vertices_moved
388 .iter()
389 .chain(&c.vertices_removed)
390 .any(crossed_vertex)
391 || c.triangles_removed
392 .iter()
393 .chain(&c.triangles_changed)
394 .any(crossed_triangle)
395 {
396 return true;
397 }
398 let straddles = |mesh: &Mesh, t: TriangleId| {
400 mesh.triangle(t).is_some_and(|tri| {
401 let plus = tri.map(|v| {
402 let p = mesh.positions()[v as usize].map(f64::from);
403 plane.signed_distance(p) >= 0.0
404 });
405 plus[0] != plus[1] || plus[1] != plus[2]
406 })
407 };
408 if c.triangles_added
410 .iter()
411 .chain(&c.triangles_changed)
412 .any(|&t| straddles(mesh, t))
413 {
414 return true;
415 }
416 let mut off_cut = c
418 .vertices_moved
419 .iter()
420 .chain(&c.vertices_added)
421 .filter(|v| !crossed_vertex(v))
422 .peekable();
423 if off_cut.peek().is_none() {
424 return false;
425 }
426 if mesh.ensure_incidence().is_err() {
427 return true;
428 }
429 let mesh = &*mesh;
430 let Some(incidence) = mesh.incidence() else {
431 return true;
432 };
433 off_cut.any(|&v| incidence.triangles(v).iter().any(|&t| straddles(mesh, t)))
434}
435
436type Polyline<'a> = (&'a [[f64; 2]], bool);
438
439fn polylines(r: &SectionResult) -> Result<Vec<Polyline<'_>>> {
441 let mut out = try_vec(r.contours.len())?;
442 out.extend(r.contours.iter().map(|c| (c.uv.as_slice(), c.closed)));
443 Ok(out)
444}
445
446impl Scene {
447 pub fn add_section(&mut self, desc: SectionDesc) -> Result<SectionId> {
468 self.drop_field_unit();
469 self.check_selection(&desc.meshes)?;
470 let next = self
471 .next_section
472 .checked_add(1)
473 .ok_or(SceneError::InvalidInput("too many sections"))?;
474 let mut entries = try_vec(desc.meshes.len())?;
475 for &m in &desc.meshes {
476 self.entries
477 .get_mut(m)
478 .ok_or(SceneError::UnknownMesh)?
479 .section_hooks
480 .try_reserve(1)
481 .map_err(|_| oom())?;
482 entries.push(SectionEntry::new(m));
483 }
484 self.sections.try_reserve(1).map_err(|_| oom())?;
485 let id = SectionId(self.next_section);
487 self.next_section = next;
488 for &m in &desc.meshes {
489 if let Some(e) = self.entries.get_mut(m) {
490 e.section_hooks.push(SectionHook::new(id));
491 }
492 }
493 self.sections.push(Section { id, desc, entries });
494 self.run_section_eager(self.sections.len() - 1);
495 Ok(id)
496 }
497
498 pub fn remove_section(&mut self, id: SectionId) -> Result<()> {
505 self.drop_field_unit();
506 let i = self.section_index(id)?;
507 let s = self.sections.remove(i);
508 for se in &s.entries {
509 if let Some(e) = self.entries.get_mut(se.mesh) {
510 e.remove_section_hook(id);
511 e.report_section(id);
512 }
513 }
514 Ok(())
515 }
516
517 pub fn set_section_plane(&mut self, id: SectionId, plane: Plane) -> Result<()> {
525 self.drop_field_unit();
526 let i = self.section_index(id)?;
527 self.sections[i].desc.plane = plane;
528 self.restale_section(i);
529 Ok(())
530 }
531
532 pub fn set_section_meshes(&mut self, id: SectionId, meshes: Vec<MeshId>) -> Result<()> {
543 self.drop_field_unit();
544 let i = self.section_index(id)?;
545 self.check_selection(&meshes)?;
546 let mut entries = try_vec(meshes.len())?;
547 let old = &self.sections[i].entries;
548 for &m in &meshes {
549 if !old.iter().any(|se| se.mesh == m) {
550 self.entries
551 .get_mut(m)
552 .ok_or(SceneError::UnknownMesh)?
553 .section_hooks
554 .try_reserve(1)
555 .map_err(|_| oom())?;
556 }
557 }
558 let mut old = std::mem::take(&mut self.sections[i].entries);
560 for &m in &meshes {
561 match old.iter().position(|se| se.mesh == m) {
562 Some(k) => entries.push(old.swap_remove(k)),
563 None => {
564 if let Some(e) = self.entries.get_mut(m) {
565 e.section_hooks.push(SectionHook::new(id));
566 }
567 entries.push(SectionEntry::new(m));
568 }
569 }
570 }
571 for se in &old {
572 if let Some(e) = self.entries.get_mut(se.mesh) {
573 e.remove_section_hook(id);
574 e.report_section(id);
575 }
576 }
577 let s = &mut self.sections[i];
578 s.entries = entries;
579 s.desc.meshes = meshes;
580 self.run_section_eager(i);
581 Ok(())
582 }
583
584 pub fn set_section_fill(&mut self, id: SectionId, fill: bool) -> Result<()> {
592 self.drop_field_unit();
593 let i = self.section_index(id)?;
594 self.sections[i].desc.fill = fill;
595 self.restale_section(i);
596 Ok(())
597 }
598
599 pub fn update_sections(&mut self) -> Result<()> {
610 self.drop_field_unit();
611 self.run_sections(|_| true, None)
612 }
613
614 pub fn section(&self, id: SectionId, mesh: MeshId) -> Result<&SectionResult> {
623 let (i, k) = self.section_entry(id, mesh)?;
624 Ok(&self.sections[i].entries[k].result)
625 }
626
627 pub fn section_desc(&self, id: SectionId) -> Result<&SectionDesc> {
634 let i = self.section_index(id)?;
635 Ok(&self.sections[i].desc)
636 }
637
638 pub fn section_status(&self, id: SectionId, mesh: MeshId) -> Result<SectionStatus> {
643 let (i, k) = self.section_entry(id, mesh)?;
644 Ok(if self.entry_current(i, k) {
645 SectionStatus::Current
646 } else {
647 SectionStatus::Stale
648 })
649 }
650
651 pub fn section_plane(&self, id: SectionId, mesh: MeshId) -> Result<Option<Plane>> {
659 let (i, k) = self.section_entry(id, mesh)?;
660 Ok(self.sections[i].entries[k].plane)
661 }
662
663 pub fn section_revision(&self, id: SectionId, mesh: MeshId) -> Result<u64> {
672 let (i, k) = self.section_entry(id, mesh)?;
673 Ok(self.sections[i].entries[k].revision)
674 }
675
676 pub fn section_gap(&self, id: SectionId, a: MeshId, b: MeshId) -> Result<Option<Gap>> {
690 let (i, ka) = self.section_entry(id, a)?;
691 let (_, kb) = self.section_entry(id, b)?;
692 if a == b {
693 return Err(SceneError::InvalidInput(
694 "a section gap needs two different meshes",
695 ));
696 }
697 let s = &self.sections[i];
698 let (ea, eb) = (&s.entries[ka], &s.entries[kb]);
699 let (Some(plane_a), Some(plane_b)) = (ea.plane, eb.plane) else {
701 return Ok(None);
702 };
703 if plane_a != plane_b {
704 return Ok(None);
705 }
706 let pa = polylines(&ea.result)?;
707 let pb = polylines(&eb.result)?;
708 Ok(contour_gap(&pa, &pb)?.map(|g: Gap2| Gap {
709 distance: g.distance,
710 a: plane_a.from_uv(g.a),
711 b: plane_b.from_uv(g.b),
712 }))
713 }
714
715 pub fn section_id(&self, raw: u32) -> Option<SectionId> {
718 self.sections.iter().map(|s| s.id).find(|id| id.0 == raw)
719 }
720
721 fn section_index(&self, id: SectionId) -> Result<usize> {
722 self.sections
723 .iter()
724 .position(|s| s.id == id)
725 .ok_or(SceneError::UnknownSection)
726 }
727
728 fn section_entry(&self, id: SectionId, mesh: MeshId) -> Result<(usize, usize)> {
730 let i = self.section_index(id)?;
731 self.entry(mesh)?;
732 let k = self.sections[i]
733 .entries
734 .iter()
735 .position(|se| se.mesh == mesh)
736 .ok_or(SceneError::InvalidInput(
737 "the section does not select this mesh",
738 ))?;
739 Ok((i, k))
740 }
741
742 fn check_selection(&self, meshes: &[MeshId]) -> Result<()> {
744 for (j, &m) in meshes.iter().enumerate() {
745 self.entry(m)?;
746 if meshes[..j].contains(&m) {
747 return Err(SceneError::InvalidInput(
748 "a section lists a mesh more than once",
749 ));
750 }
751 }
752 Ok(())
753 }
754
755 fn entry_current(&self, i: usize, k: usize) -> bool {
757 let s = &self.sections[i];
758 let se = &s.entries[k];
759 se.computed_once
760 && !se.stale
761 && self
762 .entry(se.mesh)
763 .ok()
764 .and_then(|e| e.section_hook(s.id))
765 .is_some_and(SectionHook::is_clean)
766 }
767
768 fn settle_entry(&mut self, i: usize, k: usize) -> bool {
772 let s = &self.sections[i];
773 let se = &s.entries[k];
774 if se.stale || !se.computed_once {
775 return false;
776 }
777 let Some(Entry {
778 mesh,
779 placement,
780 section_hooks,
781 ..
782 }) = self.entries.get_mut(se.mesh)
783 else {
784 return false;
785 };
786 let Some(h) = section_hooks.iter_mut().find(|h| h.section == s.id) else {
787 return false;
788 };
789 if h.placement_changed || h.reaches {
790 return false;
791 }
792 let Some(c) = &h.changes else {
793 return false;
794 };
795 if !c.is_empty() {
796 let Ok(plane) = local_plane(placement, mesh.origin(), &s.desc.plane) else {
799 return false;
800 };
801 #[cfg(test)]
802 {
803 self.section_reach_checks += 1;
804 }
805 if reaches_plane(mesh, &plane, c, se) {
806 h.reaches = true;
807 return false;
808 }
809 }
810 *h = SectionHook::new(s.id);
811 true
812 }
813
814 fn restale_section(&mut self, i: usize) {
816 for se in &mut self.sections[i].entries {
817 se.stale = true;
818 }
819 self.run_section_eager(i);
820 }
821
822 fn run_section_eager(&mut self, i: usize) {
825 let mut due = Vec::new();
826 for k in 0..self.sections[i].entries.len() {
827 let eager = self
828 .entry(self.sections[i].entries[k].mesh)
829 .is_ok_and(|e| e.mode == UpdateMode::Eager);
830 if eager && !self.entry_current(i, k) {
831 if due.try_reserve(1).is_err() {
832 return;
833 }
834 due.push((i, k));
835 }
836 }
837 let _ = self.compute_entries(&due);
838 }
839
840 pub(crate) fn run_sections(
845 &mut self,
846 want: impl Fn(UpdateMode) -> bool,
847 touched: Option<&[MeshId]>,
848 ) -> Result<()> {
849 let mut due = Vec::new();
850 for i in 0..self.sections.len() {
851 for k in 0..self.sections[i].entries.len() {
852 let mesh = self.sections[i].entries[k].mesh;
853 if touched.is_some_and(|t| !t.contains(&mesh)) {
854 continue;
855 }
856 if self.entry_current(i, k) || self.settle_entry(i, k) {
857 continue;
858 }
859 let Ok(e) = self.entry(mesh) else { continue };
860 if !want(e.mode) {
861 continue;
862 }
863 due.try_reserve(1).map_err(|_| oom())?;
864 due.push((i, k));
865 }
866 }
867 self.compute_entries(&due)
868 }
869
870 pub(crate) fn run_eager_sections(&mut self, touched: Option<&[MeshId]>) {
873 let _ = self.run_sections(|m| m == UpdateMode::Eager, touched);
874 }
875
876 pub(crate) fn drop_sections_of(&mut self, mesh: MeshId) {
879 for s in &mut self.sections {
880 if let Some(k) = s.entries.iter().position(|se| se.mesh == mesh) {
881 s.entries.remove(k);
882 s.desc.meshes.retain(|&m| m != mesh);
883 if let Some(e) = self.entries.get_mut(mesh) {
884 e.remove_section_hook(s.id);
885 e.report_section(s.id);
886 }
887 }
888 }
889 }
890
891 fn compute_entries(&mut self, due: &[(usize, usize)]) -> Result<()> {
909 let mut outcomes: Vec<Result<()>> = try_vec(due.len())?;
910 let mut jobs: Vec<(usize, usize, usize)> = try_vec(due.len())?;
911 for (n, &(i, k)) in due.iter().enumerate() {
912 let r = self.prepare_entry(i, k);
913 if r.is_ok() {
914 jobs.push((n, i, k));
915 }
916 outcomes.push(r);
917 }
918 match self.cut_entries(&jobs) {
919 Ok(cuts) => {
920 for ((n, i, k), c) in jobs.into_iter().zip(cuts) {
921 outcomes[n] = c.map(|c| self.apply_entry(i, k, c));
922 }
923 }
924 Err(e) => {
925 for (n, _, _) in jobs {
926 outcomes[n] = Err(e.clone());
927 }
928 }
929 }
930 outcomes.into_iter().find(Result::is_err).unwrap_or(Ok(()))
931 }
932
933 fn prepare_entry(&mut self, i: usize, k: usize) -> Result<()> {
936 let mesh = self.sections[i].entries[k].mesh;
937 let e = self.entries.get_mut(mesh).ok_or(SceneError::UnknownMesh)?;
938 if e.mode != UpdateMode::Manual {
939 e.update_bvh()?;
940 } else if !e.is_current() {
941 return Err(SceneError::StaleBvh(mesh));
942 }
943 Ok(())
944 }
945
946 fn cut_entries(&self, jobs: &[(usize, usize, usize)]) -> Result<Vec<Result<Computed>>> {
955 let mesh_of = |i: usize, k: usize| self.entry(self.sections[i].entries[k].mesh);
956 let work: usize = jobs
957 .iter()
958 .map(|&(_, i, k)| mesh_of(i, k).map_or(0, |e| e.mesh.triangle_count()))
959 .sum();
960 let parallel = work >= PAR_MIN_SECTION_WORK
961 && jobs.len() > 1
962 && par::enabled()
963 && !par::force_sequential();
964 #[cfg(test)]
965 if parallel {
966 PARALLEL_SECTION_BATCHES.with(|c| c.set(c.get() + 1));
967 }
968 let threshold = if parallel { 0 } else { usize::MAX };
969 par::map_chunks(jobs, 1, threshold, oom, |_, chunk| {
971 let (_, i, k) = chunk[0];
972 Ok(mesh_of(i, k).and_then(|e| compute(e, &self.sections[i].desc)))
973 })
974 }
975
976 fn apply_entry(&mut self, i: usize, k: usize, c: Computed) {
979 #[cfg(test)]
980 {
981 self.section_recomputes += 1;
982 }
983 let s = &mut self.sections[i];
984 let id = s.id;
985 let se = &mut s.entries[k];
986 let changed = !se.computed_once || se.result != c.result;
987 if changed {
988 se.revision += 1;
989 }
990 se.result = c.result;
991 se.crossed_vertices = c.crossed_vertices;
992 se.crossed_triangles = c.crossed_triangles;
993 se.plane = Some(s.desc.plane);
994 se.stale = false;
995 se.computed_once = true;
996 if let Some(e) = self.entries.get_mut(se.mesh) {
997 e.reset_section_hook(id);
998 if changed {
999 e.report_section(id);
1000 }
1001 }
1002 }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use std::cell::Cell;
1008 use std::f64::consts::PI;
1009
1010 use fast_mesh_store::Mesh;
1011
1012 use super::*;
1013 use crate::{MeshChanges, MeshOptions, Placement, SceneOptions, SubscriberId};
1014
1015 fn sphere(stacks: u32, slices: u32, r: f64, centre: [f64; 3]) -> Mesh {
1019 let mut pos = vec![centre[0], centre[1], centre[2] + r];
1020 for i in 1..stacks {
1021 let theta = PI * f64::from(i) / f64::from(stacks);
1022 for j in 0..slices {
1023 let phi = 2.0 * PI * f64::from(j) / f64::from(slices);
1024 let (rs, rc) = (r * theta.sin(), r * theta.cos());
1025 pos.extend([
1026 centre[0] + rs * phi.cos(),
1027 centre[1] + rs * phi.sin(),
1028 centre[2] + rc,
1029 ]);
1030 }
1031 }
1032 pos.extend([centre[0], centre[1], centre[2] - r]);
1033 let south = 1 + (stacks - 1) * slices;
1034 let ring = |i: u32, j: u32| 1 + (i - 1) * slices + j % slices;
1035 let mut idx = Vec::new();
1036 for j in 0..slices {
1037 idx.extend([0, ring(1, j), ring(1, j + 1)]);
1038 for i in 1..stacks - 1 {
1039 let (a, b, c, d) = (
1040 ring(i, j),
1041 ring(i + 1, j),
1042 ring(i + 1, j + 1),
1043 ring(i, j + 1),
1044 );
1045 idx.extend([a, b, c, a, c, d]);
1046 }
1047 idx.extend([ring(stacks - 1, j), south, ring(stacks - 1, j + 1)]);
1048 }
1049 Mesh::from_buffers_f64(&pos, &idx).unwrap()
1050 }
1051
1052 fn unit_sphere() -> Mesh {
1053 sphere(32, 64, 1.0, [0.0; 3])
1054 }
1055
1056 fn with_mode(mode: UpdateMode) -> MeshOptions {
1057 MeshOptions {
1058 mode,
1059 ..MeshOptions::default()
1060 }
1061 }
1062
1063 fn at(x: f64, y: f64, z: f64) -> Placement {
1064 Placement {
1065 translation: [x, y, z],
1066 ..Placement::IDENTITY
1067 }
1068 }
1069
1070 fn z_plane(h: f64) -> Plane {
1071 Plane::new([0.0, 0.0, h], [0.0, 0.0, 1.0]).unwrap()
1072 }
1073
1074 #[test]
1075 fn scaled_section_follows_the_scale() {
1076 let mut scene = Scene::new(SceneOptions::default());
1080 let m = scene
1081 .add_mesh(
1082 unit_sphere(),
1083 MeshOptions {
1084 placement: Placement {
1085 translation: [0.0, 3.0, 0.0],
1086 scale: [2.0, 1.0, 1.0],
1087 ..Placement::IDENTITY
1088 },
1089 ..MeshOptions::default()
1090 },
1091 )
1092 .unwrap();
1093 scene.take_changes();
1094 let plane = Plane::new([1.0, 3.0, 0.0], [1.0, 0.0, 0.0]).unwrap();
1095 let id = scene.add_section(desc(plane, &[m], false)).unwrap();
1096 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1097 let r = scene.section(id, m).unwrap().clone();
1098 assert_eq!(r.contours.len(), 1);
1099 let c = &r.contours[0];
1100 assert!(c.closed && c.oriented && c.manifold);
1101 assert!(
1102 c.points.iter().all(|p| (p[0] - 1.0).abs() < 1e-6),
1103 "{:?}",
1104 &c.points[..4.min(c.points.len())]
1105 );
1106 let want_len = 2.0 * PI * (0.75f64).sqrt();
1107 assert!(
1108 close(r.length, want_len, 2e-2),
1109 "{} vs {want_len}",
1110 r.length
1111 );
1112 assert!(
1113 close(r.area, PI * 0.75, 2e-2),
1114 "{} vs {}",
1115 r.area,
1116 PI * 0.75
1117 );
1118 assert_fresh(&mut scene, id, m, "scaled");
1119 scene
1121 .set_placement(
1122 m,
1123 Placement {
1124 translation: [0.0, 3.0, 2.0],
1125 scale: [2.0, 1.0, 1.0],
1126 ..Placement::IDENTITY
1127 },
1128 )
1129 .unwrap();
1130 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1131 let r = scene.section(id, m).unwrap();
1132 assert!(close(r.length, want_len, 2e-2), "{}", r.length);
1133 assert!(
1134 r.contours[0]
1135 .points
1136 .iter()
1137 .all(|p| (p[0] - 1.0).abs() < 1e-6)
1138 );
1139 }
1140
1141 fn desc(plane: Plane, meshes: &[MeshId], fill: bool) -> SectionDesc {
1142 SectionDesc {
1143 plane,
1144 meshes: meshes.to_vec(),
1145 fill,
1146 }
1147 }
1148
1149 fn close(value: f64, expected: f64, rel: f64) -> bool {
1150 (value - expected).abs() <= rel * expected.abs()
1151 }
1152
1153 fn circle(h: f64) -> f64 {
1155 2.0 * PI * (1.0 - h * h).sqrt()
1156 }
1157
1158 fn one_sphere(mode: UpdateMode) -> (Scene, MeshId) {
1160 let mut scene = Scene::new(SceneOptions::default());
1161 let m = scene.add_mesh(unit_sphere(), with_mode(mode)).unwrap();
1162 scene.take_changes();
1163 (scene, m)
1164 }
1165
1166 fn of(ch: &[MeshChanges], mesh: MeshId) -> Vec<SectionId> {
1168 ch.iter()
1169 .find(|c| c.id == mesh)
1170 .map_or(Vec::new(), |c| c.sections_changed.clone())
1171 }
1172
1173 fn assert_fresh(scene: &mut Scene, id: SectionId, mesh: MeshId, at: &str) {
1176 scene.update_sections().unwrap();
1177 assert_eq!(
1178 scene.section_status(id, mesh),
1179 Ok(SectionStatus::Current),
1180 "{at}"
1181 );
1182 let s = &scene.sections[scene.section_index(id).unwrap()];
1183 let d = desc(s.desc.plane, &[mesh], s.desc.fill);
1184 let fresh = scene.add_section(d).unwrap();
1185 scene.update_sections().unwrap();
1186 let want = scene.section(fresh, mesh).unwrap().clone();
1187 assert_eq!(scene.section(id, mesh).unwrap(), &want, "{at}");
1188 scene.remove_section(fresh).unwrap();
1189 }
1190
1191 fn fill_area(f: &Fill) -> f64 {
1192 f.triangles
1193 .iter()
1194 .map(|t| {
1195 let [a, b, c] = t.map(|i| f.points[i as usize]);
1196 0.5 * ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]))
1197 })
1198 .sum()
1199 }
1200
1201 fn rotation(q: [f64; 4]) -> [[f64; 3]; 3] {
1203 let n = q.iter().map(|c| c * c).sum::<f64>().sqrt();
1204 let [w, x, y, z] = q.map(|c| c / n);
1205 [
1206 [
1207 1.0 - 2.0 * (y * y + z * z),
1208 2.0 * (x * y - w * z),
1209 2.0 * (x * z + w * y),
1210 ],
1211 [
1212 2.0 * (x * y + w * z),
1213 1.0 - 2.0 * (x * x + z * z),
1214 2.0 * (y * z - w * x),
1215 ],
1216 [
1217 2.0 * (x * z - w * y),
1218 2.0 * (y * z + w * x),
1219 1.0 - 2.0 * (x * x + y * y),
1220 ],
1221 ]
1222 }
1223
1224 fn remove_triangles_using(scene: &mut Scene, m: MeshId, v: VertexId) -> Vec<TriangleId> {
1225 let tris: Vec<TriangleId> = scene
1226 .mesh(m)
1227 .unwrap()
1228 .triangles()
1229 .iter()
1230 .enumerate()
1231 .filter(|(t, tri)| {
1232 scene.mesh(m).unwrap().is_triangle_alive(*t as TriangleId) && tri.contains(&v)
1233 })
1234 .map(|(t, _)| t as TriangleId)
1235 .collect();
1236 scene.edit(m, |tx| tx.remove_triangles(&tris)).unwrap();
1237 tris
1238 }
1239
1240 #[test]
1241 fn add_section_eager_computes_immediately() {
1242 let (mut scene, m) = one_sphere(UpdateMode::Eager);
1243 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
1244 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1245 let r = scene.section(id, m).unwrap().clone();
1246 assert_eq!(r.contours.len(), 1);
1247 let c = &r.contours[0];
1248 assert!(c.closed && c.oriented && c.manifold);
1249 assert!(close(r.length, 2.0 * PI * 0.954, 1e-2), "{}", r.length);
1250 assert!(close(r.length, circle(0.3), 1e-2));
1251 assert_eq!(r.length, c.length);
1252 assert!(r.area > 0.0 && r.area == c.area);
1253 assert!(close(r.area, PI * (1.0 - 0.09), 1e-2), "{}", r.area);
1254 assert!(r.fill.is_none() && r.fill_ok);
1255 assert_eq!(c.points.len(), c.uv.len());
1256 assert_eq!(c.points.len(), c.edges.len());
1257 assert_eq!(c.triangles.len(), c.points.len());
1258 assert!(c.points.iter().all(|p| (p[2] - 0.3).abs() < 1e-6));
1259 let ch = scene.take_changes();
1260 assert_eq!(ch.len(), 1);
1261 assert_eq!(ch[0].sections_changed, [id]);
1262 assert_eq!(scene.section_recomputes, 1);
1263 let se = &scene.sections[0].entries[0];
1265 assert_eq!(se.crossed_triangles.len(), c.triangles.len());
1266 assert!(se.crossed_vertices.windows(2).all(|w| w[0] < w[1]));
1267 assert!(
1268 c.edges
1269 .iter()
1270 .all(|&(a, b)| se.crossed_vertices.binary_search(&a).is_ok()
1271 && se.crossed_vertices.binary_search(&b).is_ok())
1272 );
1273 }
1274
1275 #[test]
1276 fn lazy_waits_for_update_sections() {
1277 let (mut scene, m) = one_sphere(UpdateMode::Lazy);
1278 let id = scene.add_section(desc(z_plane(0.3), &[m], true)).unwrap();
1279 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1280 assert_eq!(scene.section(id, m), Ok(&SectionResult::default()));
1281 assert!(scene.take_changes().is_empty());
1282 scene.update_sections().unwrap();
1283 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1284 let r = scene.section(id, m).unwrap();
1285 assert_eq!(r.contours.len(), 1);
1286 assert!(r.fill.is_some() && r.fill_ok);
1287 assert_eq!(scene.take_changes()[0].sections_changed, [id]);
1288 scene.update_sections().unwrap();
1290 scene.flush().unwrap();
1291 assert_eq!(scene.section_recomputes, 1);
1292 assert!(scene.take_changes().is_empty());
1293 scene
1295 .edit(m, |tx| tx.set_positions(&[40], &[[0.0, 0.0, 0.0]]))
1296 .unwrap();
1297 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1298 scene.flush().unwrap();
1299 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1300 assert_fresh(&mut scene, id, m, "after flush");
1301 }
1302
1303 #[test]
1304 fn plane_move_restales_and_recomputes_eager() {
1305 let (mut scene, m) = one_sphere(UpdateMode::Eager);
1306 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
1307 scene.set_section_plane(id, z_plane(-0.5)).unwrap();
1308 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1309 let r = scene.section(id, m).unwrap();
1310 assert!(close(r.length, circle(0.5), 1e-2), "{}", r.length);
1311 assert!(
1312 r.contours[0]
1313 .points
1314 .iter()
1315 .all(|p| (p[2] + 0.5).abs() < 1e-6)
1316 );
1317 assert_eq!(scene.section_recomputes, 2);
1318 assert_fresh(&mut scene, id, m, "eager");
1319
1320 let (mut scene, m) = one_sphere(UpdateMode::Lazy);
1322 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
1323 scene.update_sections().unwrap();
1324 let old = scene.section(id, m).unwrap().clone();
1325 scene.set_section_plane(id, z_plane(-0.5)).unwrap();
1326 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1327 assert_eq!(scene.section(id, m), Ok(&old));
1328 assert_eq!(scene.section_desc(id).unwrap().plane, z_plane(-0.5));
1330 assert_eq!(scene.section_plane(id, m), Ok(Some(z_plane(0.3))));
1331 scene.update_sections().unwrap();
1332 let r = scene.section(id, m).unwrap();
1333 assert!(close(r.length, circle(0.5), 1e-2));
1334 scene.set_section_fill(id, true).unwrap();
1336 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1337 assert!(scene.section(id, m).unwrap().fill.is_none());
1338 scene.update_sections().unwrap();
1339 assert!(scene.section(id, m).unwrap().fill.is_some());
1340 }
1341
1342 #[test]
1343 fn world_points_match_local_slice_with_origin() {
1344 let mut state = 0x2545_f491_4f6c_dd1du64;
1345 let mut next = || {
1346 state = state
1347 .wrapping_mul(6364136223846793005)
1348 .wrapping_add(1442695040888963407);
1349 (state >> 11) as f64 / (1u64 << 53) as f64 - 0.5
1350 };
1351 let centre = [1000.0, -1000.0, 500.0];
1352 for _ in 0..8 {
1353 let mut scene = Scene::new(SceneOptions::default());
1354 let placement = Placement {
1355 rotation: rotation([next(), next(), next(), next()]),
1356 translation: [next() * 50.0, next() * 50.0, next() * 50.0],
1357 ..Placement::IDENTITY
1358 };
1359 let opts = MeshOptions {
1360 placement,
1361 ..MeshOptions::default()
1362 };
1363 let m = scene.add_mesh(sphere(24, 48, 1.0, centre), opts).unwrap();
1364 let o = scene.mesh(m).unwrap().origin();
1365 assert!((0..3).all(|k| (o[k] - centre[k]).abs() < 1e-9), "{o:?}");
1366 let c = placement.apply(centre);
1368 let n = [next(), next(), next()];
1369 let off = next() * 1.2;
1370 let len = n.iter().map(|x| x * x).sum::<f64>().sqrt();
1371 let origin: [f64; 3] = std::array::from_fn(|k| c[k] + off * n[k] / len);
1372 let plane = Plane::new(origin, n).unwrap();
1373 let id = scene.add_section(desc(plane, &[m], false)).unwrap();
1374 let r = scene.section(id, m).unwrap().clone();
1375 assert!(!r.contours.is_empty());
1376
1377 let r_t = |d: [f64; 3]| -> [f64; 3] {
1379 let r = &placement.rotation;
1380 std::array::from_fn(|i| r[0][i] * d[0] + r[1][i] * d[1] + r[2][i] * d[2])
1381 };
1382 let local = Plane::with_axes(
1383 scene.world_to_local(m, plane.origin()).unwrap(),
1384 r_t(plane.u()),
1385 r_t(plane.v()),
1386 )
1387 .unwrap();
1388 let mut cut = Slice::default();
1389 fast_mesh_query::slice(
1390 scene.mesh(m).unwrap(),
1391 scene.bvh(m).unwrap(),
1392 &local,
1393 &mut cut,
1394 )
1395 .unwrap();
1396 assert_eq!(cut.contours.len(), r.contours.len());
1397 let tol = 1e-9 * 1000.0;
1398 for (want, got) in cut.contours.iter().zip(&r.contours) {
1399 assert_eq!(want.edges, got.edges);
1400 assert_eq!(want.triangles, got.triangles);
1401 assert_eq!(
1402 (want.closed, want.oriented, want.manifold),
1403 (got.closed, got.oriented, got.manifold)
1404 );
1405 for ((&l, &w), &uv) in want.points.iter().zip(&got.points).zip(&got.uv) {
1406 let p = scene.local_to_world(m, l).unwrap();
1407 assert!((0..3).all(|k| (p[k] - w[k]).abs() <= tol), "{p:?} {w:?}");
1408 let q = plane.to_uv(w);
1409 assert!((0..2).all(|k| (q[k] - uv[k]).abs() <= tol), "{q:?} {uv:?}");
1410 assert!(plane.signed_distance(w).abs() < 1e-6);
1411 }
1412 }
1413 assert!(r.area > 0.0);
1415 }
1416 }
1417
1418 #[test]
1419 fn plane_missing_mesh_gives_empty_current_result() {
1420 let (mut scene, m) = one_sphere(UpdateMode::Eager);
1421 let id = scene.add_section(desc(z_plane(5.0), &[m], true)).unwrap();
1422 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1423 let r = scene.section(id, m).unwrap();
1424 assert!(r.contours.is_empty());
1425 assert_eq!((r.length, r.area), (0.0, 0.0));
1426 assert_eq!(r.fill, Some(Fill::default()));
1427 assert!(r.fill_ok);
1428 assert_eq!(scene.take_changes()[0].sections_changed, [id]);
1430 scene.set_section_plane(id, z_plane(6.0)).unwrap();
1432 assert_eq!(scene.section_recomputes, 2);
1433 assert!(scene.take_changes().is_empty());
1434 }
1435
1436 #[test]
1437 fn edit_undo_redo_keep_result_equal_to_from_scratch() {
1438 type Op = Box<dyn Fn(&mut Scene, MeshId)>;
1439 let v: VertexId = 1 + 15 * 64 + 3;
1441 let ops: Vec<(&str, Op)> = vec![
1442 (
1443 "move across",
1444 Box::new(move |s, m| {
1445 s.edit(m, |tx| tx.set_positions(&[v], &[[0.9, 0.2, -0.2]]))
1446 .unwrap();
1447 }),
1448 ),
1449 (
1450 "remove triangles",
1451 Box::new(move |s, m| {
1452 remove_triangles_using(s, m, v + 1);
1453 }),
1454 ),
1455 ("undo", Box::new(|s, _| assert!(s.undo().unwrap()))),
1456 ("undo", Box::new(|s, _| assert!(s.undo().unwrap()))),
1457 ("redo", Box::new(|s, _| assert!(s.redo().unwrap()))),
1458 (
1459 "placement",
1460 Box::new(|s, m| s.set_placement(m, at(0.1, 0.0, 0.02)).unwrap()),
1461 ),
1462 (
1463 "cancelled drag",
1464 Box::new(move |s, m| {
1465 s.begin_step();
1466 s.set_placement(m, at(0.0, 0.0, 0.6)).unwrap();
1467 s.edit(m, |tx| tx.set_positions(&[v], &[[0.0, 0.0, 0.0]]))
1468 .unwrap();
1469 s.cancel_step().unwrap();
1470 }),
1471 ),
1472 (
1473 "undo placement",
1474 Box::new(|s, _| assert!(s.undo().unwrap())),
1475 ),
1476 (
1477 "redo placement",
1478 Box::new(|s, _| assert!(s.redo().unwrap())),
1479 ),
1480 ];
1481 for mode in [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual] {
1482 let (mut scene, m) = one_sphere(mode);
1483 let id = scene.add_section(desc(z_plane(0.05), &[m], true)).unwrap();
1484 assert_fresh(&mut scene, id, m, "start");
1485 for (name, op) in &ops {
1486 let before = scene.section(id, m).unwrap().clone();
1487 op(&mut scene, m);
1488 let expect = if mode == UpdateMode::Eager {
1489 SectionStatus::Current
1490 } else {
1491 SectionStatus::Stale
1492 };
1493 assert_eq!(scene.section_status(id, m), Ok(expect), "{mode:?} {name}");
1494 if mode != UpdateMode::Eager {
1495 assert_eq!(scene.section(id, m), Ok(&before), "{mode:?} {name}");
1496 }
1497 if mode == UpdateMode::Manual {
1498 scene.update(m).unwrap();
1499 }
1500 assert_fresh(&mut scene, id, m, &format!("{mode:?} {name}"));
1501 }
1502 }
1503 }
1504
1505 #[test]
1506 fn placement_change_recomputes() {
1507 let (mut scene, m) = one_sphere(UpdateMode::Eager);
1508 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
1509 scene.take_changes();
1510 scene.set_placement(m, at(0.0, 0.0, 0.2)).unwrap();
1511 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1512 let r = scene.section(id, m).unwrap();
1513 assert!(close(r.length, circle(0.1), 1e-2), "{}", r.length);
1514 assert!(
1515 r.contours[0]
1516 .points
1517 .iter()
1518 .all(|p| (p[2] - 0.3).abs() < 1e-6)
1519 );
1520 let ch = scene.take_changes();
1521 assert!(ch[0].placement_changed);
1522 assert_eq!(ch[0].sections_changed, [id]);
1523 assert_fresh(&mut scene, id, m, "moved");
1524 }
1525
1526 #[test]
1527 fn fill_on_gives_area_matching_contours() {
1528 let (mut scene, m) = one_sphere(UpdateMode::Eager);
1529 let id = scene.add_section(desc(z_plane(0.3), &[m], true)).unwrap();
1530 let r = scene.section(id, m).unwrap();
1531 assert!(r.fill_ok);
1532 let f = r.fill.as_ref().unwrap();
1533 assert!(!f.triangles.is_empty());
1534 let a = fill_area(f);
1535 assert!(close(a, r.area, 1e-6), "{a} {}", r.area);
1536 assert!(close(r.area, PI * (1.0 - 0.09), 1e-2));
1537 scene.set_section_fill(id, false).unwrap();
1539 let r = scene.section(id, m).unwrap();
1540 assert!(r.fill.is_none() && r.fill_ok);
1541 }
1542
1543 #[test]
1544 fn gap_between_two_spheres() {
1545 let mut scene = Scene::new(SceneOptions::default());
1546 let a = scene
1547 .add_mesh(unit_sphere(), MeshOptions::default())
1548 .unwrap();
1549 let opts = MeshOptions {
1550 placement: at(3.0, 0.0, 0.0),
1551 ..MeshOptions::default()
1552 };
1553 let b = scene.add_mesh(unit_sphere(), opts).unwrap();
1554 let id = scene
1555 .add_section(desc(z_plane(0.0), &[a, b], false))
1556 .unwrap();
1557 let g = scene.section_gap(id, a, b).unwrap().unwrap();
1558 assert!((g.distance - 1.0).abs() < 1e-3, "{}", g.distance);
1559 assert!((g.a[0] - 1.0).abs() < 1e-3 && (g.b[0] - 2.0).abs() < 1e-3);
1560 for p in [g.a, g.b] {
1561 assert!(p[1].abs() < 1e-3 && p[2].abs() < 1e-9, "{p:?}");
1562 }
1563 let d = (0..3)
1564 .map(|k| (g.a[k] - g.b[k]).powi(2))
1565 .sum::<f64>()
1566 .sqrt();
1567 assert!((d - g.distance).abs() < 1e-9);
1568 let h = scene.section_gap(id, b, a).unwrap().unwrap();
1570 assert!((h.distance - g.distance).abs() < 1e-12);
1571 scene.set_placement(b, at(3.0, 0.0, 5.0)).unwrap();
1572 assert_eq!(scene.section_gap(id, a, b), Ok(None));
1573 }
1574
1575 #[test]
1576 fn error_paths() {
1577 let mut scene = Scene::new(SceneOptions::default());
1578 let a = scene
1579 .add_mesh(unit_sphere(), MeshOptions::default())
1580 .unwrap();
1581 let b = scene
1582 .add_mesh(unit_sphere(), MeshOptions::default())
1583 .unwrap();
1584 let gone = scene
1585 .add_mesh(unit_sphere(), MeshOptions::default())
1586 .unwrap();
1587 scene.remove_mesh(gone).unwrap();
1588 let p = z_plane(0.0);
1589 let invalid = |r: &Result<SectionId>| matches!(r, Err(SceneError::InvalidInput(_)));
1590 assert_eq!(
1591 scene.add_section(desc(p, &[a, gone], false)),
1592 Err(SceneError::UnknownMesh)
1593 );
1594 assert!(invalid(&scene.add_section(desc(p, &[a, b, a], false))));
1595 assert!(scene.sections.is_empty());
1596 let no_hooks = |s: &Scene| {
1597 s.ids()
1598 .all(|m| s.entry(m).unwrap().section_hooks.is_empty())
1599 };
1600 assert!(no_hooks(&scene));
1601
1602 let id = scene.add_section(desc(p, &[a], false)).unwrap();
1603 assert_eq!(
1604 scene.set_section_meshes(id, vec![a, gone]),
1605 Err(SceneError::UnknownMesh)
1606 );
1607 assert!(matches!(
1608 scene.set_section_meshes(id, vec![b, b]),
1609 Err(SceneError::InvalidInput(_))
1610 ));
1611 assert_eq!(scene.sections[0].desc.meshes, [a]);
1612 assert!(scene.entry(b).unwrap().section_hooks.is_empty());
1613 assert!(matches!(
1614 scene.section(id, b),
1615 Err(SceneError::InvalidInput(_))
1616 ));
1617 assert!(matches!(
1618 scene.section_status(id, b),
1619 Err(SceneError::InvalidInput(_))
1620 ));
1621 assert!(matches!(
1622 scene.section_gap(id, a, b),
1623 Err(SceneError::InvalidInput(_))
1624 ));
1625 assert_eq!(scene.section(id, gone), Err(SceneError::UnknownMesh));
1626 assert_eq!(scene.section_gap(id, gone, a), Err(SceneError::UnknownMesh));
1627
1628 assert_eq!(scene.section_id(id.raw()), Some(id));
1629 assert_eq!(scene.section_id(id.raw() + 1), None);
1630 scene.remove_section(id).unwrap();
1631 assert_eq!(scene.section_id(id.raw()), None);
1632 assert!(no_hooks(&scene));
1633 let u = SceneError::UnknownSection;
1634 assert_eq!(scene.remove_section(id), Err(u.clone()));
1635 assert_eq!(scene.set_section_plane(id, p), Err(u.clone()));
1636 assert_eq!(scene.set_section_meshes(id, vec![a]), Err(u.clone()));
1637 assert_eq!(scene.set_section_fill(id, true), Err(u.clone()));
1638 assert_eq!(scene.section(id, a), Err(u.clone()));
1639 assert_eq!(scene.section_status(id, a), Err(u.clone()));
1640 assert_eq!(scene.section_gap(id, a, a), Err(u.clone()));
1641 assert_eq!(u.to_string(), "unknown or removed section id");
1642 let again = scene.add_section(desc(p, &[a], false)).unwrap();
1644 assert_ne!(again, id);
1645 assert_eq!(scene.section(id, a), Err(u));
1646 }
1647
1648 #[test]
1649 fn remove_section_and_mesh_report_once() {
1650 let mut scene = Scene::new(SceneOptions::default());
1651 let a = scene
1652 .add_mesh(unit_sphere(), MeshOptions::default())
1653 .unwrap();
1654 let b = scene
1655 .add_mesh(unit_sphere(), with_mode(UpdateMode::Lazy))
1656 .unwrap();
1657 let s2 = scene.subscribe();
1658 let id = scene
1659 .add_section(desc(z_plane(0.2), &[a, b], false))
1660 .unwrap();
1661 scene.update_sections().unwrap();
1662 scene.take_changes();
1663 scene.take_changes_for(s2).unwrap();
1664 scene.remove_section(id).unwrap();
1665 for s in [SubscriberId::DEFAULT, s2] {
1666 let ch = scene.take_changes_for(s).unwrap();
1667 assert_eq!(ch.len(), 2);
1668 for c in &ch {
1669 assert_eq!(c.sections_changed, [id], "{:?}", c.id);
1670 assert!(c.changes.as_ref().unwrap().is_empty());
1671 }
1672 }
1673
1674 let id2 = scene
1675 .add_section(desc(z_plane(0.2), &[a, b], false))
1676 .unwrap();
1677 scene.take_changes();
1678 scene.take_changes_for(s2).unwrap();
1679 scene.remove_mesh(b).unwrap();
1680 assert_eq!(scene.sections[0].desc.meshes, [a]);
1681 assert_eq!(scene.section(id2, b), Err(SceneError::UnknownMesh));
1682 assert_eq!(scene.section_status(id2, b), Err(SceneError::UnknownMesh));
1683 assert_eq!(scene.section_status(id2, a), Ok(SectionStatus::Current));
1684 for s in [SubscriberId::DEFAULT, s2] {
1685 let ch = scene.take_changes_for(s).unwrap();
1686 assert_eq!(ch.len(), 1);
1687 assert!(ch[0].removed && ch[0].id == b);
1688 assert_eq!(ch[0].sections_changed, [id2]);
1689 }
1690 scene.update_sections().unwrap();
1691 scene.remove_section(id2).unwrap();
1692 assert_eq!(of(&scene.take_changes_for(s2).unwrap(), a), [id2]);
1693 assert_eq!(scene.section(id2, a), Err(SceneError::UnknownSection));
1694 assert_eq!(scene.section_status(id, a), Err(SceneError::UnknownSection));
1695 }
1696
1697 #[test]
1698 fn selection_change_keeps_and_drops_entries() {
1699 let mut scene = Scene::new(SceneOptions::default());
1700 let a = scene
1701 .add_mesh(unit_sphere(), MeshOptions::default())
1702 .unwrap();
1703 let b = scene
1704 .add_mesh(unit_sphere(), with_mode(UpdateMode::Lazy))
1705 .unwrap();
1706 let c = scene
1707 .add_mesh(unit_sphere(), MeshOptions::default())
1708 .unwrap();
1709 let id = scene
1710 .add_section(desc(z_plane(0.2), &[a, b], false))
1711 .unwrap();
1712 scene.take_changes();
1713 let n = scene.section_recomputes;
1714 scene.set_section_meshes(id, vec![c, a]).unwrap();
1715 assert_eq!(scene.section_recomputes, n + 1);
1717 assert_eq!(scene.section_status(id, a), Ok(SectionStatus::Current));
1718 assert_eq!(scene.section_status(id, c), Ok(SectionStatus::Current));
1719 assert!(matches!(
1720 scene.section(id, b),
1721 Err(SceneError::InvalidInput(_))
1722 ));
1723 let ch = scene.take_changes();
1724 assert_eq!(of(&ch, a), []);
1725 assert_eq!(of(&ch, b), [id]);
1726 assert_eq!(of(&ch, c), [id]);
1727 assert!(scene.entry(b).unwrap().section_hooks.is_empty());
1728 scene.set_section_meshes(id, vec![c, a, b]).unwrap();
1730 assert_eq!(scene.section_status(id, b), Ok(SectionStatus::Stale));
1731 assert_fresh(&mut scene, id, b, "reselected");
1732 }
1733
1734 #[test]
1735 fn compact_recomputes_and_reports() {
1736 let (mut scene, m) = one_sphere(UpdateMode::Eager);
1737 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
1738 remove_triangles_using(&mut scene, m, 0);
1740 scene.edit(m, |tx| tx.remove_vertices(&[0])).unwrap();
1741 assert_fresh(&mut scene, id, m, "holed");
1742 scene.take_changes();
1743 let before = scene.section(id, m).unwrap().clone();
1744 let n = scene.section_recomputes;
1745 scene.compact(m).unwrap();
1746 assert_eq!(scene.section_recomputes, n + 1);
1747 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1748 let after = scene.section(id, m).unwrap();
1749 assert_ne!(after.contours[0].edges, before.contours[0].edges);
1750 assert!(close(after.length, before.length, 1e-12));
1751 let ch = scene.take_changes();
1752 assert!(ch[0].changes.is_none());
1753 assert_eq!(ch[0].sections_changed, [id]);
1754 assert_fresh(&mut scene, id, m, "compacted");
1755 }
1756
1757 #[test]
1758 fn manual_stale_bvh_returns_error_and_stays_stale() {
1759 let (mut scene, m) = one_sphere(UpdateMode::Manual);
1760 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
1761 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1763 scene.flush().unwrap();
1764 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1765 scene.update_sections().unwrap();
1766 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1767 scene
1768 .edit(m, |tx| tx.set_positions(&[40], &[[0.0, 0.0, 0.0]]))
1769 .unwrap();
1770 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1771 let old = scene.section(id, m).unwrap().clone();
1772 assert_eq!(scene.update_sections(), Err(SceneError::StaleBvh(m)));
1773 assert_eq!(scene.update_sections(), Err(SceneError::StaleBvh(m)));
1774 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1775 assert_eq!(scene.section(id, m), Ok(&old));
1776 scene.flush().unwrap();
1777 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1778 scene.update(m).unwrap();
1779 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1780 assert_fresh(&mut scene, id, m, "updated");
1781 scene
1783 .edit(m, |tx| tx.set_positions(&[41], &[[0.0, 0.0, 0.0]]))
1784 .unwrap();
1785 let other = scene.add_section(desc(z_plane(0.0), &[m], false)).unwrap();
1786 assert_eq!(scene.section_status(other, m), Ok(SectionStatus::Stale));
1787 assert_eq!(scene.update_sections(), Err(SceneError::StaleBvh(m)));
1788 }
1789
1790 #[test]
1791 fn failed_eager_bvh_update_is_retried_by_the_section() {
1792 let (mut scene, m) = one_sphere(UpdateMode::Eager);
1793 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
1794 crate::scene::fault::fail_bvh_update_on(1);
1797 scene
1798 .edit(m, |tx| tx.set_positions(&[40], &[[0.0, 0.0, 0.0]]))
1799 .unwrap();
1800 assert_eq!(scene.is_current(m), Ok(true));
1801 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Current));
1802 assert_fresh(&mut scene, id, m, "retried");
1803 }
1804
1805 fn ring(slices: u32, i: u32, j: u32) -> VertexId {
1807 1 + (i - 1) * slices + j % slices
1808 }
1809
1810 fn fine_sphere_cut(mode: UpdateMode) -> (Scene, MeshId, SectionId) {
1813 let mut scene = Scene::new(SceneOptions::default());
1814 let m = scene
1815 .add_mesh(sphere(64, 128, 1.0, [0.0; 3]), with_mode(mode))
1816 .unwrap();
1817 let id = scene.add_section(desc(z_plane(0.3), &[m], true)).unwrap();
1818 scene.update_sections().unwrap();
1819 scene.take_changes();
1820 (scene, m, id)
1821 }
1822
1823 fn crossed(scene: &Scene, id: SectionId) -> (Vec<VertexId>, Vec<TriangleId>) {
1824 let se = &scene.sections[scene.section_index(id).unwrap()].entries[0];
1825 (se.crossed_vertices.clone(), se.crossed_triangles.clone())
1826 }
1827
1828 fn nudge(scene: &mut Scene, m: MeshId, v: VertexId, d: [f32; 3]) {
1829 scene
1830 .edit(m, |tx| {
1831 let p = tx.mesh().position(v).unwrap();
1832 tx.set_positions(&[v], &[std::array::from_fn(|k| p[k] + d[k])])
1833 })
1834 .unwrap();
1835 }
1836
1837 fn find_triangle(scene: &Scene, m: MeshId, tri: [VertexId; 3]) -> TriangleId {
1839 let mesh = scene.mesh(m).unwrap();
1840 (0..mesh.triangle_slots() as TriangleId)
1841 .find(|&t| mesh.triangle(t) == Some(tri))
1842 .unwrap()
1843 }
1844
1845 fn flip_quad(scene: &mut Scene, m: MeshId, slices: u32, i: u32, j: u32) -> [TriangleId; 2] {
1847 let (a, b, c, d) = (
1848 ring(slices, i, j),
1849 ring(slices, i + 1, j),
1850 ring(slices, i + 1, j + 1),
1851 ring(slices, i, j + 1),
1852 );
1853 let t = [
1854 find_triangle(scene, m, [a, b, c]),
1855 find_triangle(scene, m, [a, c, d]),
1856 ];
1857 scene
1858 .edit(m, |tx| tx.set_triangles(&t, &[[a, b, d], [b, c, d]]))
1859 .unwrap();
1860 t
1861 }
1862
1863 #[test]
1864 fn far_edit_does_not_recompute() {
1865 for mode in [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual] {
1866 let (mut scene, m, id) = fine_sphere_cut(mode);
1867 let before = scene.section(id, m).unwrap().clone();
1868 let n = scene.section_recomputes;
1869 let v = ring(128, 55, 7);
1871 assert!(scene.mesh(m).unwrap().position(v).unwrap()[2] < -0.9);
1872 nudge(&mut scene, m, v, [0.0, 0.0, 0.01]);
1873 flip_quad(&mut scene, m, 128, 50, 3);
1875 let t = find_triangle(&scene, m, [0, ring(128, 1, 9), ring(128, 1, 10)]);
1876 scene.edit(m, |tx| tx.remove_triangles(&[t])).unwrap();
1877 assert_eq!(
1878 scene.section_status(id, m),
1879 Ok(SectionStatus::Current),
1880 "{mode:?}"
1881 );
1882 scene.update_sections().unwrap();
1883 scene.flush().unwrap();
1884 assert_eq!(scene.section_recomputes, n, "{mode:?}");
1885 assert_eq!(scene.section(id, m), Ok(&before));
1886 let ch = scene.take_changes();
1887 assert_eq!(of(&ch, m), [], "{mode:?}");
1888 if mode == UpdateMode::Manual {
1889 scene.update(m).unwrap();
1890 }
1891 assert_fresh(&mut scene, id, m, &format!("{mode:?} far"));
1892 }
1893 }
1894
1895 #[test]
1896 fn edit_touching_plane_recomputes() {
1897 let (mut scene, m, id) = fine_sphere_cut(UpdateMode::Eager);
1898 let n = scene.section_recomputes;
1899 let (cv, _) = crossed(&scene, id);
1900 nudge(&mut scene, m, cv[cv.len() / 2], [0.0, 0.0, 0.004]);
1901 assert_eq!(scene.section_recomputes, n + 1);
1902 assert_eq!(of(&scene.take_changes(), m), [id]);
1903 assert_fresh(&mut scene, id, m, "touching");
1904 }
1905
1906 #[test]
1907 fn vertex_moved_across_plane_recomputes() {
1908 let (mut scene, m, id) = fine_sphere_cut(UpdateMode::Eager);
1909 let n = scene.section_recomputes;
1910 let v = ring(128, 27, 40);
1912 let (cv, _) = crossed(&scene, id);
1913 assert!(cv.binary_search(&v).is_err());
1914 let p = scene.mesh(m).unwrap().position(v).unwrap();
1915 assert!((p[2] - 0.25).abs() < 0.01, "{p:?}");
1916 scene
1917 .edit(m, |tx| tx.set_positions(&[v], &[[p[0], p[1], 0.35]]))
1918 .unwrap();
1919 assert_eq!(scene.section_recomputes, n + 1);
1920 assert_eq!(of(&scene.take_changes(), m), [id]);
1921 assert_fresh(&mut scene, id, m, "across");
1922 }
1923
1924 #[test]
1925 fn edge_flip_near_plane_recomputes() {
1926 let (mut scene, m, id) = fine_sphere_cut(UpdateMode::Lazy);
1927 let n = scene.section_recomputes;
1928 let before = scene.section(id, m).unwrap().clone();
1929 let (_, ct) = crossed(&scene, id);
1930 let t = flip_quad(&mut scene, m, 128, 25, 11);
1932 assert!(t.iter().all(|t| ct.binary_search(t).is_ok()));
1933 assert_eq!(scene.section_status(id, m), Ok(SectionStatus::Stale));
1934 scene.update_sections().unwrap();
1935 assert_eq!(scene.section_recomputes, n + 1);
1936 assert_ne!(scene.section(id, m), Ok(&before));
1937 assert_eq!(of(&scene.take_changes(), m), [id]);
1938 assert_fresh(&mut scene, id, m, "flip");
1939 }
1940
1941 #[test]
1942 fn far_triangles_added_and_vertices_added_do_not_recompute() {
1943 let (mut scene, m, id) = fine_sphere_cut(UpdateMode::Eager);
1944 let n = scene.section_recomputes;
1945 let (p, q) = (ring(128, 60, 0), ring(128, 60, 1));
1946 scene
1947 .edit(m, |tx| {
1948 let v = tx.add_vertices(&[[0.1, 0.1, -0.95]])?[0];
1949 tx.add_triangles(&[[v, p, q]])
1950 })
1951 .unwrap();
1952 assert_eq!(scene.section_recomputes, n);
1953 scene
1955 .edit(m, |tx| {
1956 let v = tx.add_vertices(&[[0.0, 0.0, 0.9]])?[0];
1957 tx.add_triangles(&[[v, p, q]])
1958 })
1959 .unwrap();
1960 assert_eq!(scene.section_recomputes, n + 1);
1961 assert_fresh(&mut scene, id, m, "added");
1962 }
1963
1964 #[test]
1965 fn reaches_flag_skips_repeated_plane_checks() {
1966 for mode in [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual] {
1973 let (mut scene, m, id) = fine_sphere_cut(mode);
1974 let (cv, _) = crossed(&scene, id);
1975 scene.section_reach_checks = 0;
1976 for (k, &v) in cv.iter().take(10).enumerate() {
1977 nudge(&mut scene, m, v, [0.0, 0.0, 0.001 * (k as f32 + 1.0)]);
1978 let expect = if mode == UpdateMode::Eager {
1979 SectionStatus::Current
1980 } else {
1981 SectionStatus::Stale
1982 };
1983 assert_eq!(scene.section_status(id, m), Ok(expect), "{mode:?}");
1984 }
1985 let expect = if mode == UpdateMode::Eager { 10 } else { 1 };
1986 assert_eq!(scene.section_reach_checks, expect, "{mode:?}");
1987 if mode == UpdateMode::Manual {
1988 scene.update(m).unwrap();
1989 } else {
1990 scene.update_sections().unwrap();
1991 }
1992 assert_eq!(
1993 scene.section_status(id, m),
1994 Ok(SectionStatus::Current),
1995 "{mode:?}"
1996 );
1997 assert_fresh(&mut scene, id, m, &format!("{mode:?} sculpt"));
1998 }
1999 }
2000
2001 #[test]
2002 fn gap_needs_two_meshes_and_one_plane() {
2003 let mut scene = Scene::new(SceneOptions::default());
2004 let a = scene
2005 .add_mesh(unit_sphere(), MeshOptions::default())
2006 .unwrap();
2007 let opts = MeshOptions {
2008 placement: at(3.0, 0.0, 0.0),
2009 mode: UpdateMode::Lazy,
2010 ..MeshOptions::default()
2011 };
2012 let b = scene.add_mesh(unit_sphere(), opts).unwrap();
2013 let id = scene
2014 .add_section(desc(z_plane(0.0), &[a, b], false))
2015 .unwrap();
2016 assert!(matches!(
2017 scene.section_gap(id, a, a),
2018 Err(SceneError::InvalidInput(_))
2019 ));
2020 scene.update_sections().unwrap();
2021 assert!(scene.section_gap(id, a, b).unwrap().is_some());
2022 scene.set_section_plane(id, z_plane(0.1)).unwrap();
2024 assert_eq!(scene.section_gap(id, a, b), Ok(None));
2025 scene.update_sections().unwrap();
2026 let g = scene.section_gap(id, a, b).unwrap().unwrap();
2027 assert!((g.a[2] - 0.1).abs() < 1e-9 && (g.b[2] - 0.1).abs() < 1e-9);
2028 }
2029
2030 #[test]
2031 fn gap_survives_fill_toggle_with_mixed_entries() {
2032 let mut scene = Scene::new(SceneOptions::default());
2036 let a = scene
2037 .add_mesh(unit_sphere(), MeshOptions::default())
2038 .unwrap();
2039 let opts = MeshOptions {
2040 placement: at(3.0, 0.0, 0.0),
2041 mode: UpdateMode::Lazy,
2042 ..MeshOptions::default()
2043 };
2044 let b = scene.add_mesh(unit_sphere(), opts).unwrap();
2045 let id = scene
2046 .add_section(desc(z_plane(0.0), &[a, b], false))
2047 .unwrap();
2048 scene.update_sections().unwrap();
2049 let before = scene.section_gap(id, a, b).unwrap().unwrap();
2050 scene.set_section_fill(id, true).unwrap();
2051 assert_eq!(scene.section_status(id, a), Ok(SectionStatus::Current));
2052 assert_eq!(scene.section_status(id, b), Ok(SectionStatus::Stale));
2053 assert!(scene.section(id, a).unwrap().fill.is_some());
2054 assert!(scene.section(id, b).unwrap().fill.is_none());
2055 assert_eq!(scene.section_plane(id, a), scene.section_plane(id, b));
2056 assert_eq!(scene.section_gap(id, a, b), Ok(Some(before)));
2057 scene.update_sections().unwrap();
2058 assert!(scene.section(id, b).unwrap().fill.is_some());
2059 assert_eq!(
2060 scene.section_gap(id, a, b).unwrap().unwrap().distance,
2061 before.distance
2062 );
2063 }
2064
2065 #[test]
2066 fn section_plane_is_the_plane_computed_with() {
2067 let mut scene = Scene::new(SceneOptions::default());
2068 let a = scene
2069 .add_mesh(unit_sphere(), MeshOptions::default())
2070 .unwrap();
2071 let b = scene
2072 .add_mesh(unit_sphere(), with_mode(UpdateMode::Lazy))
2073 .unwrap();
2074 let c = scene
2075 .add_mesh(unit_sphere(), MeshOptions::default())
2076 .unwrap();
2077 let id = scene
2078 .add_section(desc(z_plane(0.0), &[a, b], true))
2079 .unwrap();
2080 assert_eq!(scene.section_plane(id, a), Ok(Some(z_plane(0.0))));
2082 assert_eq!(scene.section_plane(id, b), Ok(None));
2083 scene.set_section_plane(id, z_plane(0.1)).unwrap();
2084 scene.update_sections().unwrap();
2085 scene.set_section_plane(id, z_plane(0.2)).unwrap();
2086 assert_eq!(scene.section_plane(id, a), Ok(Some(z_plane(0.2))));
2087 assert_eq!(scene.section_plane(id, b), Ok(Some(z_plane(0.1))));
2088 let p = scene.section_plane(id, a).unwrap().unwrap();
2090 let fill = scene.section(id, a).unwrap().fill.clone().unwrap();
2091 assert!(fill.points.iter().all(|&q| p.from_uv(q)[2] == 0.2));
2092 assert!(matches!(
2093 scene.section_plane(id, c),
2094 Err(SceneError::InvalidInput(_))
2095 ));
2096 scene.remove_section(id).unwrap();
2097 assert_eq!(scene.section_plane(id, a), Err(SceneError::UnknownSection));
2098 }
2099
2100 #[test]
2101 fn section_revision_counts_result_changes() {
2102 let (mut scene, m) = one_sphere(UpdateMode::Lazy);
2103 let id = scene.add_section(desc(z_plane(0.3), &[m], false)).unwrap();
2104 assert_eq!(scene.section_revision(id, m), Ok(0));
2106 scene.update_sections().unwrap();
2107 assert_eq!(scene.section_revision(id, m), Ok(1));
2108 let n = scene.section_recomputes;
2110 nudge(&mut scene, m, 0, [0.0, 0.0, 0.01]);
2111 scene.update_sections().unwrap();
2112 assert_eq!(scene.section_recomputes, n);
2113 assert_eq!(scene.section_revision(id, m), Ok(1));
2114 scene.set_section_plane(id, z_plane(-0.5)).unwrap();
2116 scene.update_sections().unwrap();
2117 assert_eq!(scene.section_revision(id, m), Ok(2));
2118 scene.set_section_fill(id, false).unwrap();
2120 scene.update_sections().unwrap();
2121 assert_eq!(scene.section_recomputes, n + 2);
2122 assert_eq!(scene.section_revision(id, m), Ok(2));
2123 let (mut eager, e) = one_sphere(UpdateMode::Eager);
2125 let eid = eager.add_section(desc(z_plane(0.3), &[e], false)).unwrap();
2126 assert_eq!(eager.section_revision(eid, e), Ok(1));
2127 let other = scene
2129 .add_mesh(unit_sphere(), MeshOptions::default())
2130 .unwrap();
2131 assert!(matches!(
2132 scene.section_revision(id, other),
2133 Err(SceneError::InvalidInput(_))
2134 ));
2135 scene.remove_mesh(other).unwrap();
2136 assert_eq!(
2137 scene.section_revision(id, other),
2138 Err(SceneError::UnknownMesh)
2139 );
2140 scene.remove_section(id).unwrap();
2141 assert_eq!(
2142 scene.section_revision(id, m),
2143 Err(SceneError::UnknownSection)
2144 );
2145 }
2146
2147 #[test]
2148 fn sections_changed_exact() {
2149 let mut scene = Scene::new(SceneOptions::default());
2150 let a = scene
2151 .add_mesh(unit_sphere(), MeshOptions::default())
2152 .unwrap();
2153 let b = scene
2154 .add_mesh(unit_sphere(), MeshOptions::default())
2155 .unwrap();
2156 let c = scene
2157 .add_mesh(unit_sphere(), MeshOptions::default())
2158 .unwrap();
2159 let s1 = scene
2160 .add_section(desc(z_plane(0.2), &[a, b], false))
2161 .unwrap();
2162 let s2 = scene
2163 .add_section(desc(z_plane(0.4), &[b, c], false))
2164 .unwrap();
2165 let ch = scene.take_changes();
2166 assert_eq!(of(&ch, a), [s1]);
2167 assert_eq!(of(&ch, b), [s1, s2]);
2168 assert_eq!(of(&ch, c), [s2]);
2169 scene.set_section_plane(s1, z_plane(0.1)).unwrap();
2171 scene.set_section_plane(s1, z_plane(0.0)).unwrap();
2172 let ch = scene.take_changes();
2173 assert_eq!(ch.len(), 2);
2174 assert_eq!(of(&ch, a), [s1]);
2175 assert_eq!(of(&ch, b), [s1]);
2176 assert_eq!(of(&ch, c), []);
2177 scene.set_placement(b, at(0.0, 0.0, 0.05)).unwrap();
2179 let ch = scene.take_changes();
2180 assert_eq!(ch.len(), 1);
2181 assert_eq!(of(&ch, b), [s1, s2]);
2182 }
2183
2184 fn section_drag(stacks: u32, mode: UpdateMode) -> Vec<String> {
2189 let mut scene = Scene::new(SceneOptions::default());
2190 let meshes: Vec<MeshId> = (0..3)
2191 .map(|k| {
2192 let c = [0.6 * k as f64, 0.1 * k as f64, 0.0];
2193 let mesh = sphere(stacks, 2 * stacks, 1.0, c);
2194 scene.add_mesh(mesh, with_mode(mode)).unwrap()
2195 })
2196 .collect();
2197 let normals = [
2198 [0.0, 0.0, 1.0],
2199 [1.0, 0.0, 0.0],
2200 [0.3, 1.0, 0.2],
2201 [1.0, 1.0, 1.0],
2202 ];
2203 let plane = |s: usize, pose: usize| {
2204 let t = 0.05 * pose as f64 - 0.1 + 0.02 * s as f64;
2205 Plane::new([0.6 + t, t, t], normals[s]).unwrap()
2206 };
2207 let sections: Vec<SectionId> = (0..4)
2208 .map(|s| {
2209 let fill = s % 2 == 0;
2210 scene.add_section(desc(plane(s, 0), &meshes, fill)).unwrap()
2211 })
2212 .collect();
2213 scene.update_sections().unwrap();
2214 scene.take_changes();
2215 let mut out = Vec::new();
2216 for pose in 1..5 {
2217 for (s, &id) in sections.iter().enumerate() {
2218 scene.set_section_plane(id, plane(s, pose)).unwrap();
2219 }
2220 scene.update_sections().unwrap();
2221 let mut seen = format!("{:?}", scene.take_changes());
2222 for &id in §ions {
2223 for &m in &meshes {
2224 let r = scene.section(id, m).unwrap();
2225 assert!(!r.contours.is_empty());
2226 seen += &format!("{r:?}");
2227 }
2228 seen += &format!("{:?}", scene.section_gap(id, meshes[0], meshes[2]));
2229 }
2230 out.push(seen);
2231 }
2232 out
2233 }
2234
2235 #[test]
2236 fn sections_bit_identical() {
2237 use fast_mesh_store::par;
2238 for mode in [UpdateMode::Eager, UpdateMode::Lazy] {
2239 par::set_force_sequential(true);
2241 let reference = section_drag(40, mode);
2242 par::set_force_sequential(false);
2243 if !par::enabled() {
2244 continue;
2246 }
2247 for k in [1, 2, 8] {
2248 let got = par::with_threads(k, || section_drag(40, mode));
2249 assert!(got == reference, "{mode:?}: {k} threads");
2250 }
2251 let batches = PARALLEL_SECTION_BATCHES.with(Cell::get);
2252 assert!(section_drag(40, mode) == reference, "{mode:?}: global pool");
2253 let ran = PARALLEL_SECTION_BATCHES.with(Cell::get) > batches;
2254 assert!(ran, "{mode:?}: ran sequentially");
2255 }
2256 }
2257
2258 #[test]
2259 fn small_inputs_stay_sequential() {
2260 use fast_mesh_store::par;
2261 let before = par::parallel_entries();
2262 let batches = PARALLEL_SECTION_BATCHES.with(Cell::get);
2263 for mode in [UpdateMode::Eager, UpdateMode::Lazy] {
2264 section_drag(8, mode);
2265 }
2266 assert_eq!(par::parallel_entries(), before);
2267 assert_eq!(PARALLEL_SECTION_BATCHES.with(Cell::get), batches);
2268 }
2269}