1use fast_mesh_bvh::Bvh;
72use fast_mesh_fields::{
73 ColorRule, FieldsError, SignBuild, SignData, SignMode, SignStatus, Target, color_of,
74 curvatures, signed_distances, signed_distances_multi, thicknesses,
75};
76use fast_mesh_query::{NearSearch, Rigid};
77use fast_mesh_store::{ChangeSet, ChannelDesc, Mesh, StoreError, TriangleId, VertexId, par};
78
79use crate::baked::baked_mesh;
80
81use crate::query::round_up;
82use crate::scene::{Entry, FIELD_PREFIX};
83use crate::{FieldId, MeshId, Result, Scene, SceneError, UpdateMode};
84
85#[derive(Debug, Clone, PartialEq)]
88pub struct DistanceField {
89 pub name: String,
91 pub on: MeshId,
93 pub to: Vec<MeshId>,
98 pub max_distance: f64,
101 pub sign: SignMode,
103}
104
105impl DistanceField {
106 pub fn new(
108 name: impl Into<String>,
109 on: MeshId,
110 to: MeshId,
111 max_distance: f64,
112 sign: SignMode,
113 ) -> DistanceField {
114 DistanceField {
115 name: name.into(),
116 on,
117 to: vec![to],
118 max_distance,
119 sign,
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq)]
138pub struct CurvatureField {
139 pub name: String,
141 pub on: MeshId,
143}
144
145#[derive(Debug, Clone, PartialEq)]
157pub struct ThicknessField {
158 pub name: String,
160 pub on: MeshId,
162 pub max_distance: f64,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq)]
170pub struct FieldStatus {
171 pub current: bool,
173 pub sign: SignStatus,
181}
182
183#[derive(Debug, Clone, PartialEq)]
186pub struct Coloring {
187 pub field: FieldId,
189 pub rule: ColorRule,
191}
192
193fn pad(x: f64, m: f64) -> f64 {
198 x * (1.0 + 1e-6) + 8.0 * f64::from(f32::EPSILON) * m
199}
200
201fn box_magnitude(min: [f32; 3], max: [f32; 3]) -> f64 {
203 min.iter()
204 .chain(&max)
205 .map(|c| f64::from(c.abs()))
206 .fold(0.0, f64::max)
207}
208
209fn oom() -> SceneError {
210 SceneError::Store(StoreError::OutOfMemory)
211}
212
213const PAR_MIN_FIELD_WORK: usize = 12 * 1024;
224
225const NEAR_PAIRS_PER_SLOT: usize = 1;
238
239const SIGN_ELEMS_PER_POINT: usize = 32;
249
250const SIGN_ELEMS_PER_REFIT_VERTEX: usize = 4;
255
256const NEAR_TESTS_PER_POINT: usize = 16;
262
263const LIVE_SWEEP_SHARE: usize = 8;
269
270const LIVE_EDIT_SHARE: usize = 4;
273
274const LIVE_FIRST_MARGIN: f64 = 1.0 / 256.0;
277
278const LIVE_GROW: f64 = 1.25;
281
282struct FieldJob {
284 i: usize,
286 full: bool,
288 live: bool,
291 live_travel: f64,
293 moved: Vec<bool>,
295 topology: bool,
297 changes: Option<ChangeSet>,
299 near_margins: Vec<Option<f64>>,
301 b_to_a: Vec<Rigid>,
304 near_limit: usize,
307 work: usize,
313 shared_near: Option<Vec<TriangleId>>,
317 scaled: bool,
322 frame_scale: f64,
325 temp_targets: Vec<Option<TempTarget>>,
328}
329
330struct TempTarget {
333 mesh: Mesh,
334 bvh: Bvh,
335 sign: SignData,
336}
337
338impl FieldJob {
339 fn geometric(&self) -> bool {
341 self.moved.contains(&true)
342 }
343}
344
345struct FieldPlan {
347 margin: f64,
349 candidates: Vec<VertexId>,
351 values: Vec<f32>,
353 nearest: Vec<u32>,
355 changed: Vec<VertexId>,
357 referenced: Option<Vec<bool>>,
359 finite: Vec<VertexId>,
361 #[cfg_attr(not(test), allow(dead_code))]
363 recomputed: usize,
364 #[cfg_attr(not(test), allow(dead_code))]
366 aborted: bool,
367}
368
369#[derive(Default)]
372struct Pick {
373 candidates: Vec<VertexId>,
375 all: bool,
377 aborted: bool,
379 margin: f64,
381}
382
383struct Step {
386 points: usize,
388 left: usize,
390 worked: bool,
393}
394
395impl Step {
396 fn begin_heavy(&mut self) -> bool {
399 if self.unbounded() {
400 return true;
401 }
402 if self.worked || self.left < self.points {
403 return false;
404 }
405 self.worked = true;
406 true
407 }
408
409 fn unbounded(&self) -> bool {
412 self.points == usize::MAX
413 }
414}
415
416#[derive(Debug, Clone, Copy, PartialEq, Eq)]
418#[non_exhaustive]
419pub enum FieldBudget {
420 Points(usize),
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
428#[non_exhaustive]
429pub enum StepState {
430 Idle,
432 Pending,
434 Done,
436}
437
438#[derive(Debug, Clone, Copy, PartialEq, Eq)]
441enum Stage {
442 Sign(usize),
445 Near(usize),
447 Pick,
449 Values,
451 Finish,
453}
454
455struct UnitField {
457 job: FieldJob,
458 stage: Stage,
459 search: Option<NearSearch>,
461 near: Vec<TriangleId>,
463 all: bool,
466 aborted: bool,
468 pick: Pick,
471 values: Vec<f32>,
473 nearest: Vec<u32>,
475 recomputed: usize,
477 plan: Option<FieldPlan>,
480}
481
482pub(crate) struct FieldUnit {
488 only: Option<FieldId>,
491 fields: Vec<UnitField>,
492 next: usize,
494 finished: bool,
497 error: Option<(usize, SceneError)>,
500}
501
502impl FieldUnit {
503 fn fail(&mut self, i: usize, e: SceneError) {
505 if self.error.as_ref().is_none_or(|(j, _)| i < *j) {
506 self.error = Some((i, e));
507 }
508 }
509}
510
511#[derive(Debug, Clone)]
513pub(crate) struct FieldHook {
514 pub(crate) field: FieldId,
515 pub(crate) changes: Option<ChangeSet>,
517 pub(crate) placement_changed: bool,
518 pub(crate) sign_missed_edits: bool,
522}
523
524impl FieldHook {
525 fn new(field: FieldId) -> FieldHook {
526 FieldHook {
527 field,
528 changes: Some(ChangeSet::default()),
529 placement_changed: false,
530 sign_missed_edits: false,
531 }
532 }
533
534 fn refit_changes(&self) -> Option<&ChangeSet> {
537 self.changes.as_ref().filter(|_| !self.sign_missed_edits)
538 }
539
540 fn is_clean(&self) -> bool {
541 !self.placement_changed && self.changes.as_ref().is_some_and(ChangeSet::is_empty)
542 }
543}
544
545pub(crate) struct ColorState {
547 coloring: Coloring,
548 channel: String,
550 colors: Vec<[u8; 4]>,
551}
552
553impl ColorState {
554 fn update(&mut self, values: &[f32], changed: &[VertexId]) -> Vec<VertexId> {
558 let rule = &self.coloring.rule;
559 let old_len = self.colors.len();
560 let mut dirty = Vec::with_capacity(changed.len());
561 for &v in changed {
562 if (v as usize) < old_len {
563 self.colors[v as usize] = color_of(rule, values[v as usize]);
564 dirty.push(v);
565 }
566 }
567 for (v, &x) in values.iter().enumerate().skip(old_len) {
568 self.colors.push(color_of(rule, x));
569 dirty.push(v as VertexId);
570 }
571 dirty
572 }
573}
574
575pub(crate) struct Field {
577 pub(crate) id: FieldId,
578 pub(crate) desc: DistanceField,
579 channel: String,
581 sign: Vec<Option<SignData>>,
584 sign_builds: Vec<Option<SignBuild>>,
587 computed: bool,
589 pub(crate) nearest: Vec<u32>,
593 fresh: bool,
595 finite: Vec<VertexId>,
597 referenced: Vec<bool>,
599 live: LiveState,
601}
602
603#[derive(Default)]
605struct LiveState {
606 stale: bool,
609 margin: f64,
612 sweep: u32,
614 travel: f64,
617 stamps: Vec<f32>,
619 poses: Vec<Rigid>,
621}
622
623impl Field {
624 fn sign_status(&self, k: usize) -> SignStatus {
627 self.sign[k].as_ref().map_or(
628 SignStatus {
629 mode: match self.desc.sign {
630 SignMode::Winding => SignMode::Winding,
631 SignMode::Pseudonormal | SignMode::Auto => SignMode::Pseudonormal,
632 },
633 closed: false,
634 consistent_orientation: false,
635 non_manifold_vertices: 0,
636 degenerate_triangles: 0,
637 signed_volume: 0.0,
638 },
639 SignData::status,
640 )
641 }
642}
643
644pub(crate) struct ThickField {
648 pub(crate) id: FieldId,
649 pub(crate) desc: ThicknessField,
650 channel: String,
652 computed: bool,
654 fresh: bool,
656}
657
658pub(crate) struct CurvField {
662 pub(crate) id: FieldId,
663 pub(crate) desc: CurvatureField,
664 channel: String,
666 computed: bool,
668 fresh: bool,
670}
671
672#[derive(Debug, Clone, Copy, PartialEq, Eq)]
674enum FieldRef {
675 Distance(usize),
676 Thickness(usize),
677 Curvature(usize),
678}
679
680impl Entry {
681 fn hook(&self, id: FieldId) -> Option<&FieldHook> {
682 self.hooks.iter().find(|h| h.field == id)
683 }
684
685 fn reset_hook(&mut self, id: FieldId, baked: bool) {
688 if let Some(h) = self.hooks.iter_mut().find(|h| h.field == id) {
689 let missed =
690 baked && (h.sign_missed_edits || h.changes.as_ref().is_none_or(|c| !c.is_empty()));
691 *h = FieldHook::new(id);
692 h.sign_missed_edits = missed;
693 }
694 }
695
696 pub(crate) fn grow_colors(&mut self, added: &[VertexId]) {
702 let slots = self.mesh.vertex_slots();
703 let Some(cs) = &mut self.coloring else {
704 return;
705 };
706 let old = cs.colors.len();
707 let rule = &cs.coloring.rule;
708 let values = self.mesh.channel(&cs.channel).unwrap_or(&[]);
709 let color = |v: usize| values.get(v).map_or(rule.nan, |&x| color_of(rule, x));
710 let mut dirty: Vec<VertexId> = added
711 .iter()
712 .copied()
713 .filter(|&v| (v as usize) < old)
714 .collect();
715 for &v in &dirty {
716 cs.colors[v as usize] = color(v as usize);
717 }
718 if slots > old {
719 cs.colors.extend((old..slots).map(color));
720 dirty.extend(old as VertexId..slots as VertexId);
721 }
722 if dirty.is_empty() {
723 return;
724 }
725 dirty.sort_unstable();
726 dirty.dedup();
727 self.report_colors(Some(&dirty));
728 }
729
730 pub(crate) fn recolor_all(&mut self) {
733 if let Some(cs) = &mut self.coloring {
734 let rule = &cs.coloring.rule;
735 cs.colors = match self.mesh.channel(&cs.channel) {
736 Some(values) => values.iter().map(|&x| color_of(rule, x)).collect(),
737 None => vec![rule.nan; self.mesh.vertex_slots()],
738 };
739 }
740 }
741}
742
743impl Scene {
744 pub fn add_distance_field(&mut self, desc: DistanceField) -> Result<FieldId> {
774 self.drop_field_unit();
775 let (id, eager) = self.register_field(desc, true)?;
776 if eager {
777 let _ = self.update_field(self.fields.len() - 1);
778 }
779 Ok(id)
780 }
781
782 pub fn add_distance_field_deferred(&mut self, desc: DistanceField) -> Result<FieldId> {
800 self.drop_field_unit();
801 Ok(self.register_field(desc, false)?.0)
802 }
803
804 pub fn withdraw_field(&mut self, id: FieldId) -> Result<bool> {
812 self.drop_field_unit();
813 match self.field_ref(id)? {
814 FieldRef::Distance(i) => {
815 let f = &self.fields[i];
816 let colours = self
817 .entry(f.desc.on)
818 .ok()
819 .and_then(|e| e.coloring.as_ref())
820 .is_some_and(|c| c.coloring.field == id);
821 if f.computed || colours {
822 return Ok(false);
823 }
824 let f = self.fields.remove(i);
825 for &m in std::iter::once(&f.desc.on).chain(&f.desc.to) {
826 if let Some(e) = self.entries.get_mut(m) {
827 e.hooks.retain(|h| h.field != id);
828 }
829 }
830 if let Some(ea) = self.entries.get_mut(f.desc.on) {
831 let _ = ea.mesh.remove_channel(&f.channel);
832 }
833 Ok(true)
834 }
835 FieldRef::Thickness(t) => {
836 let f = &self.thick[t];
837 let colours = self
838 .entry(f.desc.on)
839 .ok()
840 .and_then(|e| e.coloring.as_ref())
841 .is_some_and(|c| c.coloring.field == id);
842 if f.computed || colours {
843 return Ok(false);
844 }
845 let f = self.thick.remove(t);
846 if let Some(ea) = self.entries.get_mut(f.desc.on) {
847 ea.hooks.retain(|h| h.field != id);
848 let _ = ea.mesh.remove_channel(&f.channel);
849 }
850 Ok(true)
851 }
852 FieldRef::Curvature(c) => {
853 let f = &self.curv[c];
854 let colours = self
855 .entry(f.desc.on)
856 .ok()
857 .and_then(|e| e.coloring.as_ref())
858 .is_some_and(|c| c.coloring.field == id);
859 if f.computed || colours {
860 return Ok(false);
861 }
862 let f = self.curv.remove(c);
863 if let Some(ea) = self.entries.get_mut(f.desc.on) {
864 ea.hooks.retain(|h| h.field != id);
865 let _ = ea.mesh.remove_channel(&f.channel);
866 }
867 Ok(true)
868 }
869 }
870 }
871
872 fn register_field(&mut self, desc: DistanceField, sign: bool) -> Result<(FieldId, bool)> {
876 self.entry(desc.on)?;
877 for &to in &desc.to {
878 self.entry(to)?;
879 }
880 if desc.to.is_empty() {
881 return Err(SceneError::InvalidInput("a field needs a target mesh"));
882 }
883 if desc.to.contains(&desc.on) {
884 return Err(SceneError::InvalidInput(
885 "a field needs two different meshes",
886 ));
887 }
888 if (1..desc.to.len()).any(|k| desc.to[..k].contains(&desc.to[k])) {
889 return Err(SceneError::InvalidInput(
890 "a field's target meshes must all differ",
891 ));
892 }
893 if desc.max_distance.is_nan() || desc.max_distance < 0.0 {
894 return Err(SceneError::InvalidInput(
895 "max_distance must be zero or greater, and not NaN",
896 ));
897 }
898 if self
899 .fields
900 .iter()
901 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
902 || self
903 .thick
904 .iter()
905 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
906 || self
907 .curv
908 .iter()
909 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
910 {
911 return Err(SceneError::InvalidInput(
912 "the mesh already has a field of that name",
913 ));
914 }
915 let next = self
916 .next_field
917 .checked_add(1)
918 .ok_or(SceneError::InvalidInput("too many fields"))?;
919 let mut signs = Vec::new();
920 signs.try_reserve_exact(desc.to.len()).map_err(|_| oom())?;
921 let mut sign_builds = Vec::new();
922 sign_builds
923 .try_reserve_exact(desc.to.len())
924 .map_err(|_| oom())?;
925 if sign {
926 self.prepare(Some(&desc.to))?;
927 }
928 for &to in &desc.to {
929 let eb = self.entries.get_mut(to).ok_or(SceneError::UnknownMesh)?;
930 signs.push(if sign {
931 Some(SignData::build(&mut eb.mesh, &eb.bvh, desc.sign)?)
932 } else {
933 None
934 });
935 sign_builds.push(None);
936 eb.hooks.try_reserve(1).map_err(|_| oom())?;
937 }
938 self.fields.try_reserve(1).map_err(|_| oom())?;
939 let channel = format!("{FIELD_PREFIX}{}", desc.name);
940 let ea = self
941 .entries
942 .get_mut(desc.on)
943 .ok_or(SceneError::UnknownMesh)?;
944 ea.hooks.try_reserve(1).map_err(|_| oom())?;
945 ea.mesh
946 .add_channel(ChannelDesc::derived(channel.clone(), 1))?;
947 let id = FieldId(self.next_field);
949 self.next_field = next;
950 ea.hooks.push(FieldHook::new(id));
951 let eager = ea.mode == UpdateMode::Eager;
952 for &to in &desc.to {
953 if let Some(eb) = self.entries.get_mut(to) {
954 eb.hooks.push(FieldHook::new(id));
955 }
956 }
957 self.fields.push(Field {
958 id,
959 desc,
960 channel,
961 sign: signs,
962 sign_builds,
963 computed: false,
964 nearest: Vec::new(),
965 fresh: true,
966 finite: Vec::new(),
967 referenced: Vec::new(),
968 live: LiveState::default(),
969 });
970 Ok((id, eager))
971 }
972
973 pub fn add_thickness_field(&mut self, desc: ThicknessField) -> Result<FieldId> {
996 self.drop_field_unit();
997 let (id, eager) = self.register_thickness(desc)?;
998 if eager {
999 let _ = self.update_thick_field(self.thick.len() - 1);
1000 }
1001 Ok(id)
1002 }
1003
1004 pub fn add_thickness_field_deferred(&mut self, desc: ThicknessField) -> Result<FieldId> {
1016 self.drop_field_unit();
1017 Ok(self.register_thickness(desc)?.0)
1018 }
1019
1020 fn register_thickness(&mut self, desc: ThicknessField) -> Result<(FieldId, bool)> {
1023 self.entry(desc.on)?;
1024 if desc.max_distance.is_nan() || desc.max_distance < 0.0 {
1025 return Err(SceneError::InvalidInput(
1026 "max_distance must be zero or greater, and not NaN",
1027 ));
1028 }
1029 if self
1030 .fields
1031 .iter()
1032 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1033 || self
1034 .thick
1035 .iter()
1036 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1037 || self
1038 .curv
1039 .iter()
1040 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1041 {
1042 return Err(SceneError::InvalidInput(
1043 "the mesh already has a field of that name",
1044 ));
1045 }
1046 let next = self
1047 .next_field
1048 .checked_add(1)
1049 .ok_or(SceneError::InvalidInput("too many fields"))?;
1050 let channel = format!("{FIELD_PREFIX}{}", desc.name);
1051 let ea = self
1052 .entries
1053 .get_mut(desc.on)
1054 .ok_or(SceneError::UnknownMesh)?;
1055 ea.hooks.try_reserve(1).map_err(|_| oom())?;
1056 ea.mesh
1057 .add_channel(ChannelDesc::derived(channel.clone(), 1))?;
1058 let id = FieldId(self.next_field);
1060 self.next_field = next;
1061 ea.hooks.push(FieldHook::new(id));
1062 let eager = ea.mode == UpdateMode::Eager;
1063 self.thick.try_reserve(1).map_err(|_| oom())?;
1064 self.thick.push(ThickField {
1065 id,
1066 desc,
1067 channel,
1068 computed: false,
1069 fresh: true,
1070 });
1071 Ok((id, eager))
1072 }
1073
1074 pub fn add_curvature_field(&mut self, desc: CurvatureField) -> Result<FieldId> {
1098 self.drop_field_unit();
1099 let (id, eager) = self.register_curvature(desc)?;
1100 if eager {
1101 let _ = self.update_curv_field(self.curv.len() - 1);
1102 }
1103 Ok(id)
1104 }
1105
1106 pub fn add_curvature_field_deferred(&mut self, desc: CurvatureField) -> Result<FieldId> {
1118 self.drop_field_unit();
1119 Ok(self.register_curvature(desc)?.0)
1120 }
1121
1122 fn register_curvature(&mut self, desc: CurvatureField) -> Result<(FieldId, bool)> {
1125 self.entry(desc.on)?;
1126 if self
1127 .fields
1128 .iter()
1129 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1130 || self
1131 .thick
1132 .iter()
1133 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1134 || self
1135 .curv
1136 .iter()
1137 .any(|f| f.desc.on == desc.on && f.desc.name == desc.name)
1138 {
1139 return Err(SceneError::InvalidInput(
1140 "the mesh already has a field of that name",
1141 ));
1142 }
1143 let next = self
1144 .next_field
1145 .checked_add(1)
1146 .ok_or(SceneError::InvalidInput("too many fields"))?;
1147 let channel = format!("{FIELD_PREFIX}{}", desc.name);
1148 let ea = self
1149 .entries
1150 .get_mut(desc.on)
1151 .ok_or(SceneError::UnknownMesh)?;
1152 ea.hooks.try_reserve(1).map_err(|_| oom())?;
1153 ea.mesh
1154 .add_channel(ChannelDesc::derived(channel.clone(), 1))?;
1155 let id = FieldId(self.next_field);
1157 self.next_field = next;
1158 ea.hooks.push(FieldHook::new(id));
1159 let eager = ea.mode == UpdateMode::Eager;
1160 self.curv.try_reserve(1).map_err(|_| oom())?;
1161 self.curv.push(CurvField {
1162 id,
1163 desc,
1164 channel,
1165 computed: false,
1166 fresh: true,
1167 });
1168 Ok((id, eager))
1169 }
1170
1171 pub fn remove_field(&mut self, id: FieldId) -> Result<()> {
1179 self.drop_field_unit();
1180 match self.field_ref(id)? {
1181 FieldRef::Distance(i) => self.detach_field(i),
1182 FieldRef::Thickness(t) => self.detach_thick(t),
1183 FieldRef::Curvature(c) => self.detach_curv(c),
1184 }
1185 Ok(())
1186 }
1187
1188 pub fn update_fields(&mut self) -> Result<()> {
1202 self.drop_field_unit();
1203 self.run_fields(|_| true, None)
1204 }
1205
1206 pub fn field_values(&self, id: FieldId) -> Result<&[f32]> {
1216 let (on, channel) = match self.field_ref(id)? {
1217 FieldRef::Distance(i) => {
1218 let f = &self.fields[i];
1219 (f.desc.on, f.channel.as_str())
1220 }
1221 FieldRef::Thickness(t) => {
1222 let f = &self.thick[t];
1223 (f.desc.on, f.channel.as_str())
1224 }
1225 FieldRef::Curvature(c) => {
1226 let f = &self.curv[c];
1227 (f.desc.on, f.channel.as_str())
1228 }
1229 };
1230 self.entry(on)?
1231 .mesh
1232 .channel(channel)
1233 .ok_or(SceneError::Corrupted)
1234 }
1235
1236 pub fn field_nearest(&self, id: FieldId) -> Result<&[u32]> {
1252 match self.field_ref(id)? {
1253 FieldRef::Distance(i) => Ok(&self.fields[i].nearest),
1254 FieldRef::Thickness(_) => Ok(&[]),
1255 FieldRef::Curvature(_) => Ok(&[]),
1256 }
1257 }
1258
1259 pub fn field_targets(&self, id: FieldId) -> Result<&[MeshId]> {
1266 match self.field_ref(id)? {
1267 FieldRef::Distance(i) => Ok(&self.fields[i].desc.to),
1268 FieldRef::Thickness(_) => Ok(&[]),
1269 FieldRef::Curvature(_) => Ok(&[]),
1270 }
1271 }
1272
1273 pub fn field_target_status(&self, id: FieldId) -> Result<Vec<SignStatus>> {
1285 match self.field_ref(id)? {
1286 FieldRef::Distance(i) => {
1287 let f = &self.fields[i];
1288 Ok((0..f.sign.len()).map(|k| f.sign_status(k)).collect())
1289 }
1290 FieldRef::Thickness(_) => Ok(Vec::new()),
1291 FieldRef::Curvature(_) => Ok(Vec::new()),
1292 }
1293 }
1294
1295 pub fn field_status(&self, id: FieldId) -> Result<FieldStatus> {
1304 match self.field_ref(id)? {
1305 FieldRef::Distance(i) => {
1306 let f = &self.fields[i];
1307 let mut sign = f.sign_status(0);
1308 for k in 1..f.sign.len() {
1309 let s = f.sign_status(k);
1310 sign.closed &= s.closed;
1311 sign.consistent_orientation &= s.consistent_orientation;
1312 sign.non_manifold_vertices += s.non_manifold_vertices;
1313 sign.degenerate_triangles += s.degenerate_triangles;
1314 sign.signed_volume += s.signed_volume;
1315 }
1316 Ok(FieldStatus {
1317 current: self.field_current(i),
1318 sign,
1319 })
1320 }
1321 FieldRef::Thickness(t) => Ok(FieldStatus {
1322 current: self.thick_current(t),
1323 sign: SignStatus {
1324 mode: SignMode::Pseudonormal,
1325 closed: false,
1326 consistent_orientation: false,
1327 non_manifold_vertices: 0,
1328 degenerate_triangles: 0,
1329 signed_volume: 0.0,
1330 },
1331 }),
1332 FieldRef::Curvature(c) => Ok(FieldStatus {
1333 current: self.curv_current(c),
1334 sign: SignStatus {
1335 mode: SignMode::Pseudonormal,
1336 closed: false,
1337 consistent_orientation: false,
1338 non_manifold_vertices: 0,
1339 degenerate_triangles: 0,
1340 signed_volume: 0.0,
1341 },
1342 }),
1343 }
1344 }
1345
1346 pub fn set_coloring(&mut self, mesh: MeshId, coloring: Option<Coloring>) -> Result<()> {
1358 self.drop_field_unit();
1359 self.entry(mesh)?;
1360 let state = match coloring {
1361 None => None,
1362 Some(c) => {
1363 let (on, channel) = match self.field_ref(c.field)? {
1364 FieldRef::Distance(i) => {
1365 let f = &self.fields[i];
1366 (f.desc.on, f.channel.clone())
1367 }
1368 FieldRef::Thickness(t) => {
1369 let f = &self.thick[t];
1370 (f.desc.on, f.channel.clone())
1371 }
1372 FieldRef::Curvature(c) => {
1373 let f = &self.curv[c];
1374 (f.desc.on, f.channel.clone())
1375 }
1376 };
1377 if on != mesh {
1378 return Err(SceneError::InvalidInput("the field is not on this mesh"));
1379 }
1380 c.rule.validate().map_err(|e| match e {
1381 FieldsError::InvalidInput(m) => SceneError::InvalidInput(m),
1382 e => SceneError::Fields(e),
1383 })?;
1384 Some(ColorState {
1385 coloring: c,
1386 channel,
1387 colors: Vec::new(),
1388 })
1389 }
1390 };
1391 let e = self.entries.get_mut(mesh).ok_or(SceneError::UnknownMesh)?;
1392 let had = e.coloring.is_some();
1393 e.coloring = state;
1394 e.recolor_all();
1395 if had || e.coloring.is_some() {
1396 e.report_colors(None);
1397 }
1398 Ok(())
1399 }
1400
1401 pub fn colors(&self, mesh: MeshId) -> Result<Option<&[[u8; 4]]>> {
1413 Ok(self
1414 .entry(mesh)?
1415 .coloring
1416 .as_ref()
1417 .map(|c| c.colors.as_slice()))
1418 }
1419
1420 pub fn color_values(&self, mesh: MeshId) -> Result<Option<&[f32]>> {
1432 let e = self.entry(mesh)?;
1433 Ok(e.coloring
1434 .as_ref()
1435 .map(|c| e.mesh.channel(&c.channel).unwrap_or(&[])))
1436 }
1437
1438 pub fn coloring(&self, mesh: MeshId) -> Result<Option<&Coloring>> {
1444 Ok(self.entry(mesh)?.coloring.as_ref().map(|c| &c.coloring))
1445 }
1446
1447 pub fn fields_on(&self, mesh: MeshId) -> Result<Vec<FieldId>> {
1453 self.entry(mesh)?;
1454 Ok(self
1455 .fields
1456 .iter()
1457 .filter(|f| f.desc.on == mesh)
1458 .map(|f| f.id)
1459 .chain(
1460 self.thick
1461 .iter()
1462 .filter(|f| f.desc.on == mesh)
1463 .map(|f| f.id),
1464 )
1465 .chain(self.curv.iter().filter(|f| f.desc.on == mesh).map(|f| f.id))
1466 .collect())
1467 }
1468
1469 pub fn reconfigure_field(
1483 &mut self,
1484 id: FieldId,
1485 max_distance: f64,
1486 sign: SignMode,
1487 ) -> Result<()> {
1488 self.drop_field_unit();
1489 if max_distance.is_nan() || max_distance < 0.0 {
1490 return Err(SceneError::InvalidInput(
1491 "max_distance must be zero or greater, and not NaN",
1492 ));
1493 }
1494 match self.field_ref(id)? {
1495 FieldRef::Distance(i) => {
1496 {
1497 let f = &mut self.fields[i];
1498 if f.desc.max_distance == max_distance && f.desc.sign == sign {
1499 return Ok(());
1500 }
1501 f.desc.max_distance = max_distance;
1502 if f.desc.sign != sign {
1503 f.desc.sign = sign;
1504 for s in f.sign.iter_mut() {
1505 *s = None;
1506 }
1507 for b in f.sign_builds.iter_mut() {
1508 *b = None;
1509 }
1510 }
1511 f.fresh = true;
1512 }
1513 let on = self.fields[i].desc.on;
1514 let eager = self
1515 .entry(on)
1516 .map(|e| e.mode == UpdateMode::Eager)
1517 .unwrap_or(false);
1518 if eager {
1519 let _ = self.update_field(i);
1520 }
1521 Ok(())
1522 }
1523 FieldRef::Thickness(t) => {
1524 {
1525 let f = &mut self.thick[t];
1526 if f.desc.max_distance == max_distance {
1527 return Ok(());
1528 }
1529 f.desc.max_distance = max_distance;
1530 f.fresh = true;
1531 }
1532 let on = self.thick[t].desc.on;
1533 let eager = self
1534 .entry(on)
1535 .map(|e| e.mode == UpdateMode::Eager)
1536 .unwrap_or(false);
1537 if eager {
1538 let _ = self.update_thick_field(t);
1539 }
1540 Ok(())
1541 }
1542 FieldRef::Curvature(c) => {
1543 {
1544 let f = &mut self.curv[c];
1545 f.fresh = true;
1546 }
1547 let on = self.curv[c].desc.on;
1548 let eager = self
1549 .entry(on)
1550 .map(|e| e.mode == UpdateMode::Eager)
1551 .unwrap_or(false);
1552 if eager {
1553 let _ = self.update_curv_field(c);
1554 }
1555 Ok(())
1556 }
1557 }
1558 }
1559
1560 pub fn field_id(&self, raw: u32) -> Option<FieldId> {
1563 self.fields
1564 .iter()
1565 .map(|f| f.id)
1566 .chain(self.thick.iter().map(|f| f.id))
1567 .chain(self.curv.iter().map(|f| f.id))
1568 .find(|id| id.0 == raw)
1569 }
1570
1571 fn field_index(&self, id: FieldId) -> Result<usize> {
1572 self.fields
1573 .iter()
1574 .position(|f| f.id == id)
1575 .ok_or(SceneError::UnknownField)
1576 }
1577
1578 fn field_ref(&self, id: FieldId) -> Result<FieldRef> {
1583 if let Some(i) = self.fields.iter().position(|f| f.id == id) {
1584 return Ok(FieldRef::Distance(i));
1585 }
1586 if let Some(t) = self.thick.iter().position(|f| f.id == id) {
1587 return Ok(FieldRef::Thickness(t));
1588 }
1589 if let Some(c) = self.curv.iter().position(|f| f.id == id) {
1590 return Ok(FieldRef::Curvature(c));
1591 }
1592 Err(SceneError::UnknownField)
1593 }
1594
1595 fn thick_index(&self, id: FieldId) -> Result<usize> {
1601 self.thick
1602 .iter()
1603 .position(|f| f.id == id)
1604 .ok_or(SceneError::UnknownField)
1605 }
1606
1607 fn curv_index(&self, id: FieldId) -> Result<usize> {
1613 self.curv
1614 .iter()
1615 .position(|f| f.id == id)
1616 .ok_or(SceneError::UnknownField)
1617 }
1618
1619 fn field_current(&self, i: usize) -> bool {
1622 let f = &self.fields[i];
1623 let clean = |m: MeshId| {
1624 self.entry(m)
1625 .ok()
1626 .and_then(|e| e.hook(f.id))
1627 .is_some_and(FieldHook::is_clean)
1628 };
1629 let settled = !f.live.stale || self.field_interaction.is_some();
1630 settled && !f.fresh && clean(f.desc.on) && f.desc.to.iter().all(|&m| clean(m))
1631 }
1632
1633 fn thick_current(&self, t: usize) -> bool {
1635 let f = &self.thick[t];
1636 let clean = self
1637 .entry(f.desc.on)
1638 .ok()
1639 .and_then(|e| e.hook(f.id))
1640 .is_some_and(FieldHook::is_clean);
1641 !f.fresh && clean
1642 }
1643
1644 fn curv_current(&self, c: usize) -> bool {
1646 let f = &self.curv[c];
1647 let clean = self
1648 .entry(f.desc.on)
1649 .ok()
1650 .and_then(|e| e.hook(f.id))
1651 .is_some_and(FieldHook::is_clean);
1652 !f.fresh && clean
1653 }
1654
1655 pub(crate) fn run_fields(
1659 &mut self,
1660 want: impl Fn(UpdateMode) -> bool,
1661 touched: Option<&[MeshId]>,
1662 ) -> Result<()> {
1663 let due = self.due_fields(&want, touched)?;
1664 let r = self.update_due(&due);
1665 let due_thick = self.due_thick_fields(&want, touched)?;
1668 let r2 = self.update_thick_due(&due_thick);
1669 let due_curv = self.due_curv_fields(&want, touched)?;
1670 let r3 = self.update_curv_due(&due_curv);
1671 r.and(r2).and(r3)
1672 }
1673
1674 fn due_thick_fields(
1678 &self,
1679 want: impl Fn(UpdateMode) -> bool,
1680 touched: Option<&[MeshId]>,
1681 ) -> Result<Vec<usize>> {
1682 let mut due = Vec::new();
1683 for t in 0..self.thick.len() {
1684 let on = self.thick[t].desc.on;
1685 if touched.is_some_and(|touched| !touched.contains(&on)) {
1686 continue;
1687 }
1688 let Ok(ea) = self.entry(on) else { continue };
1689 if !want(ea.mode) || self.thick_current(t) {
1690 continue;
1691 }
1692 due.try_reserve(1).map_err(|_| oom())?;
1693 due.push(t);
1694 }
1695 Ok(due)
1696 }
1697
1698 fn due_curv_fields(
1702 &self,
1703 want: impl Fn(UpdateMode) -> bool,
1704 touched: Option<&[MeshId]>,
1705 ) -> Result<Vec<usize>> {
1706 let mut due = Vec::new();
1707 for c in 0..self.curv.len() {
1708 let on = self.curv[c].desc.on;
1709 if touched.is_some_and(|touched| !touched.contains(&on)) {
1710 continue;
1711 }
1712 let Ok(ea) = self.entry(on) else { continue };
1713 if !want(ea.mode) || self.curv_current(c) {
1714 continue;
1715 }
1716 due.try_reserve(1).map_err(|_| oom())?;
1717 due.push(c);
1718 }
1719 Ok(due)
1720 }
1721
1722 fn update_curv_field(&mut self, c: usize) -> Result<()> {
1726 self.update_curv_due(&[c])
1727 }
1728
1729 fn update_curv_due(&mut self, due: &[usize]) -> Result<()> {
1733 let mut first: Result<()> = Ok(());
1734 for &c in due {
1735 if let Err(e) = self.compute_curv_field(c)
1736 && first.is_ok()
1737 {
1738 first = Err(e);
1739 }
1740 }
1741 first
1742 }
1743
1744 fn compute_curv_field(&mut self, c: usize) -> Result<()> {
1749 use fast_mesh_bvh::BuildOptions;
1750 use fast_mesh_store::Mesh;
1751
1752 let (on, channel) = {
1753 let f = &self.curv[c];
1754 (f.desc.on, f.channel.clone())
1755 };
1756 self.prepare(Some(&[on]))?;
1759 let ea = self.entry(on)?;
1760 let (placement, origin) = (ea.placement, ea.mesh.origin());
1761 let slots = ea.mesh.vertex_slots();
1762 let mut scale = 1.0f32;
1767 let mut baked: Option<(Mesh, Bvh)> = None;
1768 if placement.is_scaled() {
1769 if let Some(s) = placement.uniform_scale() {
1770 scale = s as f32;
1771 } else {
1772 baked = Some(baked_mesh(
1773 &ea.mesh,
1774 |p| {
1775 placement.apply([
1776 origin[0] + f64::from(p[0]),
1777 origin[1] + f64::from(p[1]),
1778 origin[2] + f64::from(p[2]),
1779 ])
1780 },
1781 &BuildOptions::default(),
1782 )?);
1783 }
1784 }
1785 let mut values = Vec::new();
1786 values.try_reserve_exact(slots).map_err(|_| oom())?;
1787 values.resize(slots, f32::NAN);
1788 match &baked {
1789 Some((mesh, _)) => {
1790 curvatures(mesh, &mut values)?;
1791 }
1792 None => {
1793 let eb = self.entry(on)?;
1794 curvatures(&eb.mesh, &mut values)?;
1795 if scale != 1.0 {
1796 let s = f64::from(scale);
1797 for v in &mut values {
1798 if v.is_finite() {
1799 *v = (*v as f64 / s) as f32;
1800 }
1801 }
1802 }
1803 }
1804 }
1805 drop(baked);
1806 let id = self.curv[c].id;
1808 let ea = self.entries.get_mut(on).ok_or(SceneError::UnknownMesh)?;
1809 let old = ea.mesh.channel(&channel).ok_or(SceneError::Corrupted)?;
1810 let changed: Vec<VertexId> = (0..slots as VertexId)
1811 .filter(|&v| {
1812 old.get(v as usize).map(|o| o.to_bits()) != Some(values[v as usize].to_bits())
1813 })
1814 .collect();
1815 ea.mesh.derived_channel_mut(&channel)?[..slots].copy_from_slice(&values);
1816 ea.reset_hook(id, false);
1817 if !changed.is_empty() {
1818 ea.report_field(id);
1819 }
1820 if let Some(cs) = &mut ea.coloring
1821 && cs.coloring.field == id
1822 {
1823 let vals = ea.mesh.channel(&cs.channel).unwrap_or(&[]);
1824 let dirty = cs.update(vals, &changed);
1825 ea.report_colors(Some(&dirty));
1826 }
1827 let f = &mut self.curv[c];
1828 f.fresh = false;
1829 f.computed = true;
1830 Ok(())
1831 }
1832
1833 fn update_thick_field(&mut self, t: usize) -> Result<()> {
1837 self.update_thick_due(&[t])
1838 }
1839
1840 fn update_thick_due(&mut self, due: &[usize]) -> Result<()> {
1844 let mut first: Result<()> = Ok(());
1845 for &t in due {
1846 if let Err(e) = self.compute_thick_field(t)
1847 && first.is_ok()
1848 {
1849 first = Err(e);
1850 }
1851 }
1852 first
1853 }
1854
1855 fn compute_thick_field(&mut self, t: usize) -> Result<()> {
1860 use fast_mesh_bvh::BuildOptions;
1861 use fast_mesh_store::Mesh;
1862
1863 let (on, max_distance, channel) = {
1864 let f = &self.thick[t];
1865 (f.desc.on, f.desc.max_distance, f.channel.clone())
1866 };
1867 self.prepare(Some(&[on]))?;
1869 let ea = self.entry(on)?;
1870 let (placement, origin) = (ea.placement, ea.mesh.origin());
1871 let slots = ea.mesh.vertex_slots();
1872 let mut scale = 1.0f32;
1876 let mut baked: Option<(Mesh, Bvh)> = None;
1877 if placement.is_scaled() {
1878 if let Some(s) = placement.uniform_scale() {
1879 scale = s as f32;
1880 } else {
1881 baked = Some(baked_mesh(
1882 &ea.mesh,
1883 |p| {
1884 placement.apply([
1885 origin[0] + f64::from(p[0]),
1886 origin[1] + f64::from(p[1]),
1887 origin[2] + f64::from(p[2]),
1888 ])
1889 },
1890 &BuildOptions::default(),
1891 )?);
1892 }
1893 }
1894 let mut values = Vec::new();
1895 values.try_reserve_exact(slots).map_err(|_| oom())?;
1896 values.resize(slots, f32::NAN);
1897 match &baked {
1898 Some((mesh, bvh)) => {
1899 thicknesses(mesh, bvh, max_distance as f32, &mut values)?;
1900 }
1901 None => {
1902 let eb = self.entry(on)?;
1903 let bound = (max_distance / f64::from(scale)) as f32;
1904 thicknesses(&eb.mesh, &eb.bvh, bound, &mut values)?;
1905 if scale != 1.0 {
1906 let s = f64::from(scale);
1907 for v in &mut values {
1908 if v.is_finite() {
1909 *v = (*v as f64 * s) as f32;
1910 }
1911 }
1912 }
1913 }
1914 }
1915 drop(baked);
1916 let id = self.thick[t].id;
1918 let ea = self.entries.get_mut(on).ok_or(SceneError::UnknownMesh)?;
1919 let old = ea.mesh.channel(&channel).ok_or(SceneError::Corrupted)?;
1920 let changed: Vec<VertexId> = (0..slots as VertexId)
1921 .filter(|&v| {
1922 old.get(v as usize).map(|o| o.to_bits()) != Some(values[v as usize].to_bits())
1923 })
1924 .collect();
1925 ea.mesh.derived_channel_mut(&channel)?[..slots].copy_from_slice(&values);
1926 ea.reset_hook(id, false);
1927 if !changed.is_empty() {
1928 ea.report_field(id);
1929 }
1930 if let Some(cs) = &mut ea.coloring
1931 && cs.coloring.field == id
1932 {
1933 let vals = ea.mesh.channel(&cs.channel).unwrap_or(&[]);
1934 let dirty = cs.update(vals, &changed);
1935 ea.report_colors(Some(&dirty));
1936 }
1937 let f = &mut self.thick[t];
1938 f.fresh = false;
1939 f.computed = true;
1940 Ok(())
1941 }
1942
1943 fn due_fields(
1947 &self,
1948 want: impl Fn(UpdateMode) -> bool,
1949 touched: Option<&[MeshId]>,
1950 ) -> Result<Vec<usize>> {
1951 let mut due = Vec::new();
1952 for i in 0..self.fields.len() {
1953 let (on, to) = (self.fields[i].desc.on, &self.fields[i].desc.to);
1954 if touched.is_some_and(|t| !t.contains(&on) && !to.iter().any(|m| t.contains(m))) {
1955 continue;
1956 }
1957 let Ok(ea) = self.entry(on) else { continue };
1958 if !want(ea.mode) || self.field_current(i) {
1959 continue;
1960 }
1961 due.try_reserve(1).map_err(|_| oom())?;
1962 due.push(i);
1963 }
1964 Ok(due)
1965 }
1966
1967 pub(crate) fn run_eager_fields(&mut self, touched: Option<&[MeshId]>) {
1971 if self.defer_fields {
1972 return;
1973 }
1974 let _ = self.run_fields(|m| m == UpdateMode::Eager, touched);
1975 }
1976
1977 pub fn set_defer_fields(&mut self, on: bool) {
1988 self.defer_fields = on;
1989 }
1990
1991 pub fn set_field_interaction(&mut self, points: Option<usize>) -> Result<()> {
2022 if points == Some(0) {
2023 return Err(SceneError::InvalidInput(
2024 "an interaction's budget must be at least one point",
2025 ));
2026 }
2027 if self.field_interaction == points {
2028 return Ok(());
2029 }
2030 self.drop_field_unit();
2031 self.field_interaction = points;
2032 if points.is_none() {
2033 self.run_eager_fields(None);
2034 }
2035 Ok(())
2036 }
2037
2038 pub fn field_interaction(&self) -> Option<usize> {
2041 self.field_interaction
2042 }
2043
2044 pub fn field_step(&mut self, budget: FieldBudget) -> Result<StepState> {
2085 self.step_unit(None, budget)
2086 }
2087
2088 pub fn field_step_field(&mut self, id: FieldId, budget: FieldBudget) -> Result<StepState> {
2100 self.field_ref(id)?;
2101 self.step_unit(Some(id), budget)
2102 }
2103
2104 fn step_unit(&mut self, only: Option<FieldId>, budget: FieldBudget) -> Result<StepState> {
2112 let FieldBudget::Points(points) = budget;
2113 if let Some(id) = only
2116 && let Ok(t) = self.thick_index(id)
2117 {
2118 self.drop_field_unit();
2119 if self.thick_current(t) {
2120 return Ok(StepState::Idle);
2121 }
2122 self.update_thick_field(t)?;
2123 return Ok(StepState::Done);
2124 }
2125 if let Some(id) = only
2126 && let Ok(c) = self.curv_index(id)
2127 {
2128 self.drop_field_unit();
2129 if self.curv_current(c) {
2130 return Ok(StepState::Idle);
2131 }
2132 self.update_curv_field(c)?;
2133 return Ok(StepState::Done);
2134 }
2135 let mut thick_worked = false;
2138 let mut thick_error: Result<()> = Ok(());
2139 if only.is_none() {
2140 match self.due_thick_fields(|m| m == UpdateMode::Eager, None) {
2141 Ok(due) => {
2142 thick_worked = !due.is_empty();
2143 thick_error = self.update_thick_due(&due);
2144 }
2145 Err(e) => thick_error = Err(e),
2146 }
2147 match self.due_curv_fields(|m| m == UpdateMode::Eager, None) {
2148 Ok(due) => {
2149 thick_worked = thick_worked || !due.is_empty();
2150 thick_error = thick_error.and(self.update_curv_due(&due));
2151 }
2152 Err(e) => thick_error = thick_error.and(Err(e)),
2153 }
2154 }
2155 let open = self
2156 .field_unit
2157 .take()
2158 .filter(|u| only.is_none() || u.only == only);
2159 let mut unit = match open {
2160 Some(unit) => unit,
2161 None => match self.open_field_unit(only)? {
2162 Some(unit) => unit,
2163 None => {
2164 thick_error?;
2167 return Ok(if thick_worked {
2168 StepState::Done
2169 } else {
2170 StepState::Idle
2171 });
2172 }
2173 },
2174 };
2175 if !unit.finished {
2176 self.advance_field_unit(&mut unit, points.max(1));
2177 if unit.next < unit.fields.len() {
2178 self.field_unit = Some(unit);
2179 return Ok(StepState::Pending);
2180 }
2181 let written: usize = unit
2184 .fields
2185 .iter()
2186 .filter_map(|f| f.plan.as_ref())
2187 .map(|p| p.candidates.len())
2188 .sum();
2189 if points != usize::MAX && written > points {
2190 unit.finished = true;
2191 self.field_unit = Some(unit);
2192 return Ok(StepState::Pending);
2193 }
2194 }
2195 for f in unit.fields {
2196 if let Some(plan) = f.plan {
2198 let i = f.job.i;
2199 if let Err(e) = self.apply_field(f.job, plan) {
2201 unit.error = unit.error.filter(|(j, _)| *j < i).or(Some((i, e)));
2202 }
2203 }
2204 }
2205 if thick_error.is_err() && unit.error.is_none() {
2208 return thick_error.map(|()| StepState::Done);
2209 }
2210 match unit.error {
2211 Some((_, e)) => Err(e),
2212 None => Ok(StepState::Done),
2213 }
2214 }
2215
2216 pub fn abandon_field_step(&mut self) {
2222 self.drop_field_unit();
2223 }
2224
2225 pub fn field_step_pending(&self) -> bool {
2227 self.field_unit.is_some()
2228 }
2229
2230 pub fn field_step_meshes(&self) -> Vec<MeshId> {
2234 let mut meshes = Vec::new();
2235 for f in self.field_unit.iter().flat_map(|u| &u.fields) {
2236 let desc = &self.fields[f.job.i].desc;
2237 for &m in std::iter::once(&desc.on).chain(&desc.to) {
2238 if !meshes.contains(&m) {
2239 meshes.push(m);
2240 }
2241 }
2242 }
2243 meshes
2244 }
2245
2246 pub(crate) fn drop_field_unit(&mut self) {
2252 self.field_unit = None;
2253 self.drop_stale_sign_builds();
2254 }
2255
2256 fn drop_stale_sign_builds(&mut self) {
2260 let entries = &self.entries;
2261 for f in &mut self.fields {
2262 for (k, slot) in f.sign_builds.iter_mut().enumerate() {
2263 let stale = slot.as_ref().is_some_and(|b| {
2264 entries
2265 .get(f.desc.to[k])
2266 .is_none_or(|e| !b.is_for(&e.mesh, &e.bvh))
2267 });
2268 if stale {
2269 *slot = None;
2270 }
2271 }
2272 }
2273 }
2274
2275 #[cfg(test)]
2278 pub(crate) fn field_step_left(&self) -> usize {
2279 self.field_unit.as_ref().map_or(0, |u| {
2280 u.fields[u.next..]
2281 .iter()
2282 .map(|f| f.pick.candidates.len() - f.values.len())
2283 .sum()
2284 })
2285 }
2286
2287 #[cfg(test)]
2289 fn kept_sign_builds(&self) -> usize {
2290 self.fields
2291 .iter()
2292 .flat_map(|f| &f.sign_builds)
2293 .filter(|b| b.is_some())
2294 .count()
2295 }
2296
2297 #[cfg(test)]
2299 fn field_step_stage(&self) -> Option<Stage> {
2300 let u = self.field_unit.as_ref()?;
2301 u.fields.get(u.next).map(|f| f.stage)
2302 }
2303
2304 fn open_field_unit(&mut self, only: Option<FieldId>) -> Result<Option<FieldUnit>> {
2309 let due = match only {
2310 None => self.due_fields(|m| m == UpdateMode::Eager, None)?,
2311 Some(id) => {
2312 let i = self.field_index(id)?;
2313 if self.field_current(i) {
2314 Vec::new()
2315 } else {
2316 vec![i]
2317 }
2318 }
2319 };
2320 if due.is_empty() {
2321 return Ok(None);
2322 }
2323 let mut unit = FieldUnit {
2324 only,
2325 fields: Vec::new(),
2326 next: 0,
2327 finished: false,
2328 error: None,
2329 };
2330 unit.fields
2331 .try_reserve_exact(due.len())
2332 .map_err(|_| oom())?;
2333 for &i in &due {
2334 match self.prepare_job(i) {
2335 Ok(job) => {
2336 let all = job.full;
2337 unit.fields.push(UnitField {
2338 job,
2339 stage: Stage::Sign(0),
2340 search: None,
2341 near: Vec::new(),
2342 all,
2343 aborted: false,
2344 pick: Pick::default(),
2345 values: Vec::new(),
2346 nearest: Vec::new(),
2347 recomputed: 0,
2348 plan: None,
2349 });
2350 }
2351 Err(e) => unit.fail(i, e),
2352 }
2353 }
2354 if unit.fields.is_empty() {
2355 return Err(unit.error.map_or(SceneError::Corrupted, |(_, e)| e));
2356 }
2357 Ok(Some(unit))
2358 }
2359
2360 fn advance_field_unit(&mut self, unit: &mut FieldUnit, points: usize) {
2365 let mut step = Step {
2366 points,
2367 left: points,
2368 worked: false,
2369 };
2370 while let Some(f) = unit.fields.get_mut(unit.next) {
2371 let i = f.job.i;
2372 match self.advance_field(f, &mut step) {
2373 Ok(false) => return,
2374 Ok(true) => {}
2375 Err(e) => {
2376 f.search = None;
2377 unit.fail(i, e);
2378 }
2379 }
2380 unit.next += 1;
2381 }
2382 }
2383
2384 fn advance_field(&mut self, f: &mut UnitField, step: &mut Step) -> Result<bool> {
2388 let i = f.job.i;
2389 let targets = self.fields[i].desc.to.len();
2390 loop {
2391 match f.stage {
2392 Stage::Sign(k) if k == targets => f.stage = Stage::Near(0),
2393 Stage::Sign(k) => {
2394 if self.sign_current(i, k)? {
2395 f.stage = Stage::Sign(k + 1);
2396 continue;
2397 }
2398 if !step.begin_heavy() {
2399 return Ok(false);
2400 }
2401 let elems = step.points.saturating_mul(SIGN_ELEMS_PER_POINT);
2402 if self.step_sign(i, k, elems)? {
2403 f.stage = Stage::Sign(k + 1);
2404 }
2405 if !step.unbounded() {
2406 return Ok(false);
2407 }
2408 }
2409 Stage::Near(k) if k == targets || f.all => f.stage = Stage::Pick,
2410 Stage::Near(k) => {
2411 let Some(margin) = f.job.near_margins[k] else {
2412 f.stage = Stage::Near(k + 1);
2413 continue;
2414 };
2415 if !step.begin_heavy() {
2416 return Ok(false);
2417 }
2418 let (on, to) = (self.fields[i].desc.on, self.fields[i].desc.to[k]);
2419 let (ea, eb) = (self.entry(on)?, self.entry(to)?);
2420 let search = match &mut f.search {
2421 Some(search) => search,
2422 empty => empty.insert(NearSearch::new(
2423 &ea.mesh,
2424 &ea.bvh,
2425 &eb.mesh,
2426 &eb.bvh,
2427 &f.job.b_to_a[k],
2428 round_up(margin),
2429 f.job.near_limit,
2430 )?),
2431 };
2432 let tests = step.points.saturating_mul(NEAR_TESTS_PER_POINT);
2433 if search.step(&ea.mesh, &ea.bvh, &eb.mesh, &eb.bvh, tests)? {
2434 let done = f.search.take().ok_or(SceneError::Corrupted)?;
2435 let within = done.finish(&mut f.near)?.ok_or(SceneError::Corrupted)?;
2436 f.aborted = !within;
2437 f.all = f.aborted;
2438 f.stage = Stage::Near(k + 1);
2439 }
2440 if !step.unbounded() {
2441 return Ok(false);
2442 }
2443 }
2444 Stage::Pick => {
2445 if (f.all || f.job.topology || f.job.live) && !step.begin_heavy() {
2448 return Ok(false);
2449 }
2450 let near = std::mem::take(&mut f.near);
2451 f.pick = self.assemble_pick(&f.job, &near, f.all, f.aborted)?;
2452 let n = f.pick.candidates.len();
2453 f.values.try_reserve_exact(n).map_err(|_| oom())?;
2454 f.nearest.try_reserve_exact(n).map_err(|_| oom())?;
2455 f.stage = Stage::Values;
2456 }
2457 Stage::Values => {
2458 let (done, total) = (f.values.len(), f.pick.candidates.len());
2459 if done < total {
2460 if (step.worked && !step.unbounded()) || step.left == 0 {
2461 return Ok(false);
2462 }
2463 let end = done + step.left.min(total - done);
2464 let run = &f.pick.candidates[done..end];
2465 f.recomputed +=
2466 self.compute_values(&f.job, run, &mut f.values, &mut f.nearest)?;
2467 step.left -= end - done;
2468 if end < total {
2469 return Ok(false);
2470 }
2471 }
2472 f.stage = Stage::Finish;
2473 }
2474 Stage::Finish => {
2475 if f.all && !step.begin_heavy() {
2477 return Ok(false);
2478 }
2479 let pick = std::mem::take(&mut f.pick);
2480 let values = std::mem::take(&mut f.values);
2481 let nearest = std::mem::take(&mut f.nearest);
2482 f.plan =
2483 Some(self.finish_field(&f.job, pick, values, nearest, f.recomputed)?);
2484 return Ok(true);
2485 }
2486 }
2487 }
2488 }
2489
2490 fn sign_current(&self, i: usize, k: usize) -> Result<bool> {
2494 if self.target_uses_temp(i, k) {
2495 return Ok(true);
2496 }
2497 let f = &self.fields[i];
2498 let eb = self.entry(f.desc.to[k])?;
2499 Ok(f.sign[k]
2500 .as_ref()
2501 .is_some_and(|s| s.is_current(&eb.mesh, &eb.bvh)))
2502 }
2503
2504 fn step_sign(&mut self, i: usize, k: usize, elems: usize) -> Result<bool> {
2509 let f = &mut self.fields[i];
2510 let eb = self
2511 .entries
2512 .get_mut(f.desc.to[k])
2513 .ok_or(SceneError::UnknownMesh)?;
2514 let slot = &mut f.sign_builds[k];
2515 let resuming = slot.as_ref().is_some_and(|b| b.is_for(&eb.mesh, &eb.bvh));
2516 if !resuming
2520 && let (Some(sign), Some(changes)) = (
2521 &mut f.sign[k],
2522 eb.hook(f.id).and_then(FieldHook::refit_changes),
2523 )
2524 && changes.vertices_moved.len() <= elems / SIGN_ELEMS_PER_REFIT_VERTEX
2525 && sign.try_refit(&eb.mesh, &eb.bvh, changes)?
2526 {
2527 *slot = None;
2528 return Ok(true);
2529 }
2530 let build = match slot {
2531 Some(b) if b.is_for(&eb.mesh, &eb.bvh) => b,
2532 _ => slot.insert(SignBuild::new(&eb.mesh, &eb.bvh, f.desc.sign)?),
2533 };
2534 #[cfg(test)]
2535 counter::add_sign_step();
2536 match build.step(&mut eb.mesh, &eb.bvh, elems) {
2537 Ok(false) => Ok(false),
2538 Ok(true) => {
2539 f.sign[k] = slot.take().and_then(SignBuild::finish);
2540 Ok(true)
2541 }
2542 Err(e) => {
2543 *slot = None;
2544 Err(e.into())
2545 }
2546 }
2547 }
2548
2549 pub(crate) fn drop_fields_of(&mut self, mesh: MeshId) -> Vec<MeshId> {
2554 let mut kept_on = Vec::new();
2555 let mut i = 0;
2556 while i < self.fields.len() {
2557 let f = &mut self.fields[i];
2558 let target = f.desc.to.iter().position(|&m| m == mesh);
2559 if f.desc.on == mesh || (target.is_some() && f.desc.to.len() == 1) {
2560 self.detach_field(i);
2561 continue;
2562 }
2563 if let Some(k) = target {
2564 f.desc.to.remove(k);
2565 f.sign.remove(k);
2566 f.sign_builds.remove(k);
2567 f.fresh = true;
2568 let k = k as u32;
2570 for n in &mut f.nearest {
2571 if *n == k {
2572 *n = u32::MAX;
2573 } else if *n != u32::MAX && *n > k {
2574 *n -= 1;
2575 }
2576 }
2577 let (id, on) = (f.id, f.desc.on);
2578 if let Some(e) = self.entries.get_mut(mesh) {
2579 e.hooks.retain(|h| h.field != id);
2580 }
2581 if !kept_on.contains(&on) {
2582 kept_on.push(on);
2583 }
2584 }
2585 i += 1;
2586 }
2587 let mut t = 0;
2590 while t < self.thick.len() {
2591 if self.thick[t].desc.on == mesh {
2592 self.detach_thick(t);
2593 } else {
2594 t += 1;
2595 }
2596 }
2597 let mut c = 0;
2598 while c < self.curv.len() {
2599 if self.curv[c].desc.on == mesh {
2600 self.detach_curv(c);
2601 } else {
2602 c += 1;
2603 }
2604 }
2605 kept_on
2606 }
2607
2608 fn detach_field(&mut self, i: usize) {
2609 let f = self.fields.remove(i);
2610 for &to in &f.desc.to {
2611 if let Some(eb) = self.entries.get_mut(to) {
2612 eb.hooks.retain(|h| h.field != f.id);
2613 }
2614 }
2615 if let Some(ea) = self.entries.get_mut(f.desc.on) {
2616 ea.hooks.retain(|h| h.field != f.id);
2617 let _ = ea.mesh.remove_channel(&f.channel);
2618 if ea
2619 .coloring
2620 .as_ref()
2621 .is_some_and(|c| c.coloring.field == f.id)
2622 {
2623 ea.coloring = None;
2624 ea.report_colors(None);
2625 }
2626 ea.report_field(f.id);
2627 }
2628 }
2629
2630 fn detach_thick(&mut self, t: usize) {
2633 let f = self.thick.remove(t);
2634 if let Some(ea) = self.entries.get_mut(f.desc.on) {
2635 ea.hooks.retain(|h| h.field != f.id);
2636 let _ = ea.mesh.remove_channel(&f.channel);
2637 if ea
2638 .coloring
2639 .as_ref()
2640 .is_some_and(|c| c.coloring.field == f.id)
2641 {
2642 ea.coloring = None;
2643 ea.report_colors(None);
2644 }
2645 ea.report_field(f.id);
2646 }
2647 }
2648
2649 fn detach_curv(&mut self, c: usize) {
2652 let f = self.curv.remove(c);
2653 if let Some(ea) = self.entries.get_mut(f.desc.on) {
2654 ea.hooks.retain(|h| h.field != f.id);
2655 let _ = ea.mesh.remove_channel(&f.channel);
2656 if ea
2657 .coloring
2658 .as_ref()
2659 .is_some_and(|c| c.coloring.field == f.id)
2660 {
2661 ea.coloring = None;
2662 ea.report_colors(None);
2663 }
2664 ea.report_field(f.id);
2665 }
2666 }
2667
2668 fn pair_magnitude(&self, on: MeshId, to: MeshId) -> Result<f64> {
2674 let xf = self.relative_transform(on, to)?;
2675 let root = |m: MeshId| -> Result<Option<_>> {
2676 Ok(self.entry(m)?.bvh.root_bounds().filter(|b| !b.is_empty()))
2677 };
2678 let mut m = root(on)?.map_or(0.0, |b| box_magnitude(b.min, b.max));
2679 if let Some(b) = root(to)? {
2680 m = m.max(box_magnitude(b.min, b.max));
2681 for k in 0..8 {
2682 let corner =
2683 std::array::from_fn(|d| if k >> d & 1 == 0 { b.min[d] } else { b.max[d] });
2684 let p = xf.apply(corner);
2685 m = p.iter().fold(m, |m, c| m.max(c.abs()));
2686 }
2687 }
2688 Ok(m)
2689 }
2690
2691 fn roots_within(&self, on: MeshId, to: MeshId, b_to_a: &Rigid, margin: f64) -> Result<bool> {
2696 let root = |m: MeshId| -> Result<Option<_>> {
2697 Ok(self.entry(m)?.bvh.root_bounds().filter(|b| !b.is_empty()))
2698 };
2699 let (Some(a), Some(b)) = (root(on)?, root(to)?) else {
2700 return Ok(false);
2701 };
2702 let (mut lo, mut hi) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
2703 for k in 0..8 {
2704 let corner = std::array::from_fn(|d| if k >> d & 1 == 0 { b.min[d] } else { b.max[d] });
2705 let p = b_to_a.apply(corner);
2706 for d in 0..3 {
2707 lo[d] = lo[d].min(p[d]);
2708 hi[d] = hi[d].max(p[d]);
2709 }
2710 }
2711 let gap2: f64 = (0..3)
2712 .map(|d| {
2713 let g = (f64::from(a.min[d]) - hi[d])
2714 .max(lo[d] - f64::from(a.max[d]))
2715 .max(0.0);
2716 g * g
2717 })
2718 .sum();
2719 Ok(gap2.sqrt() <= margin)
2720 }
2721
2722 fn update_field(&mut self, i: usize) -> Result<()> {
2726 self.update_due(&[i])
2727 }
2728
2729 fn update_due(&mut self, due: &[usize]) -> Result<()> {
2742 let mut outcomes: Vec<Result<()>> = Vec::new();
2743 outcomes.try_reserve_exact(due.len()).map_err(|_| oom())?;
2744 let mut jobs: Vec<(usize, FieldJob)> = Vec::new();
2745 jobs.try_reserve_exact(due.len()).map_err(|_| oom())?;
2746 for (k, &i) in due.iter().enumerate() {
2747 match self.prepare_field(i) {
2748 Ok(job) => {
2749 jobs.push((k, job));
2750 outcomes.push(Ok(()));
2751 }
2752 Err(e) => outcomes.push(Err(e)),
2753 }
2754 }
2755 self.share_pair_queries(&mut jobs)?;
2756 match self.compute_fields(&jobs) {
2757 Ok(plans) => {
2758 for ((k, job), plan) in jobs.into_iter().zip(plans) {
2759 outcomes[k] = plan.and_then(|plan| self.apply_field(job, plan));
2760 }
2761 }
2762 Err(e) => {
2763 for (k, _) in jobs {
2764 outcomes[k] = Err(e.clone());
2765 }
2766 }
2767 }
2768 outcomes.into_iter().find(Result::is_err).unwrap_or(Ok(()))
2769 }
2770
2771 fn share_pair_queries(&self, jobs: &mut [(usize, FieldJob)]) -> Result<()> {
2779 use std::collections::HashMap;
2780 let mut groups: HashMap<(MeshId, MeshId), Vec<usize>> = HashMap::new();
2781 for (j, (_, job)) in jobs.iter().enumerate() {
2782 if job.full || job.shared_near.is_some() {
2783 continue;
2784 }
2785 let f = &self.fields[job.i];
2786 if f.desc.to.len() != 1 || job.near_margins.len() != 1 {
2787 continue;
2788 }
2789 if job.near_margins[0].is_none() {
2790 continue;
2791 }
2792 let (a, b) = (f.desc.on, f.desc.to[0]);
2793 let key = if a < b { (a, b) } else { (b, a) };
2794 groups.entry(key).or_default().push(j);
2795 }
2796 for js in groups.into_values() {
2797 let mut used = vec![false; js.len()];
2798 for (x, &jx) in js.iter().enumerate() {
2799 if used[x] {
2800 continue;
2801 }
2802 let fx = &self.fields[jobs[jx].1.i];
2803 for (y, &jy) in js.iter().enumerate().skip(x + 1) {
2804 if used[y] {
2805 continue;
2806 }
2807 let fy = &self.fields[jobs[jy].1.i];
2808 if fx.desc.on != fy.desc.to[0] || fx.desc.to[0] != fy.desc.on {
2809 continue;
2810 }
2811 if fx.desc.max_distance != fy.desc.max_distance {
2812 continue;
2813 }
2814 let (Some(m1), Some(m2)) =
2815 (jobs[jx].1.near_margins[0], jobs[jy].1.near_margins[0])
2816 else {
2817 continue;
2818 };
2819 let margin = m1.max(m2);
2820 let ea = self.entry(fx.desc.on)?;
2821 let eb = self.entry(fx.desc.to[0])?;
2822 let mut near_a = Vec::new();
2823 let mut near_b = Vec::new();
2824 fast_mesh_query::triangles_near(
2825 &ea.mesh,
2826 &ea.bvh,
2827 &eb.mesh,
2828 &eb.bvh,
2829 &jobs[jx].1.b_to_a[0],
2830 round_up(margin),
2831 &mut near_a,
2832 &mut near_b,
2833 )?;
2834 jobs[jx].1.shared_near = Some(near_a);
2835 jobs[jy].1.shared_near = Some(near_b);
2836 #[cfg(test)]
2837 counter::add_shared();
2838 used[x] = true;
2839 used[y] = true;
2840 break;
2841 }
2842 }
2843 }
2844 Ok(())
2845 }
2846
2847 fn prepare_field(&mut self, i: usize) -> Result<FieldJob> {
2852 let job = self.prepare_job(i)?;
2853 let mode = self.fields[i].desc.sign;
2854 for k in 0..self.fields[i].desc.to.len() {
2855 if job.scaled && job.temp_targets[k].is_some() {
2858 continue;
2859 }
2860 let f = &mut self.fields[i];
2861 let eb = self
2862 .entries
2863 .get_mut(f.desc.to[k])
2864 .ok_or(SceneError::UnknownMesh)?;
2865 if f.sign[k]
2866 .as_ref()
2867 .is_some_and(|s| s.is_current(&eb.mesh, &eb.bvh))
2868 {
2869 f.sign_builds[k] = None;
2870 continue;
2871 }
2872 let id = f.id;
2875 let refitted = match (
2876 &mut f.sign[k],
2877 eb.hook(id).and_then(FieldHook::refit_changes),
2878 ) {
2879 (Some(s), Some(changes)) => s.try_refit(&eb.mesh, &eb.bvh, changes)?,
2880 _ => false,
2881 };
2882 if !refitted {
2883 let f = &mut self.fields[i];
2884 let eb = self
2885 .entries
2886 .get_mut(f.desc.to[k])
2887 .ok_or(SceneError::UnknownMesh)?;
2888 f.sign[k] = Some(SignData::build(&mut eb.mesh, &eb.bvh, mode)?);
2889 }
2890 self.fields[i].sign_builds[k] = None;
2892 }
2893 Ok(job)
2894 }
2895
2896 fn live_margin(&self, i: usize) -> Result<f64> {
2899 let margin = self.fields[i].live.margin;
2900 if margin > 0.0 {
2901 return Ok(margin);
2902 }
2903 let ea = self.entry(self.fields[i].desc.on)?;
2904 let diagonal = ea
2905 .bvh
2906 .root_bounds()
2907 .filter(|b| !b.is_empty())
2908 .map_or(0.0, |b| {
2909 let d = [0, 1, 2].map(|c| f64::from(b.max[c]) - f64::from(b.min[c]));
2910 (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt()
2911 });
2912 Ok(LIVE_FIRST_MARGIN * diagonal)
2913 }
2914
2915 fn live_travel(&self, i: usize, b_to_a: &[Rigid]) -> Result<f64> {
2919 let f = &self.fields[i];
2920 let mut step = 0.0f64;
2921 if f.live.poses.len() == b_to_a.len()
2922 && let Some(b) = self
2923 .entry(f.desc.on)?
2924 .bvh
2925 .root_bounds()
2926 .filter(|b| !b.is_empty())
2927 {
2928 for (old, new) in f.live.poses.iter().zip(b_to_a) {
2929 let (old, new) = (old.inverse(), new.inverse());
2930 for k in 0..8 {
2931 let corner =
2932 std::array::from_fn(|d| if k >> d & 1 == 0 { b.min[d] } else { b.max[d] });
2933 let (p, q) = (old.apply(corner), new.apply(corner));
2934 let d: f64 = (0..3).map(|c| (p[c] - q[c]) * (p[c] - q[c])).sum();
2935 step = step.max(d.sqrt());
2936 }
2937 }
2938 }
2939 Ok(f.live.travel + step)
2940 }
2941
2942 fn prepare_job(&mut self, i: usize) -> Result<FieldJob> {
2945 let (id, on) = (self.fields[i].id, self.fields[i].desc.on);
2946 let mut to = Vec::new();
2947 to.try_reserve_exact(self.fields[i].desc.to.len() + 1)
2948 .map_err(|_| oom())?;
2949 to.extend_from_slice(&self.fields[i].desc.to);
2950 let n = to.len();
2951 let md = self.fields[i].desc.max_distance;
2952 let hook = |m: MeshId| -> Result<FieldHook> {
2953 self.entry(m)?
2954 .hook(id)
2955 .cloned()
2956 .ok_or(SceneError::Corrupted)
2957 };
2958 let hook_a = hook(on)?;
2959 if self.is_scaled_field(on, &to)? {
2960 self.prepare(Some(&to))?;
2967 return self.prepare_scaled_job(i, on, to, hook_a);
2968 }
2969 let mut whole = self.fields[i].fresh || hook_a.changes.is_none();
2970 let mut moved = Vec::new();
2971 moved.try_reserve_exact(n).map_err(|_| oom())?;
2972 for &m in &to {
2973 let hook_b = hook(m)?;
2974 whole |= hook_b.changes.is_none();
2975 moved.push(
2976 hook_a.placement_changed
2977 || hook_b.placement_changed
2978 || hook_b.changes.as_ref().is_some_and(|c| !c.is_empty()),
2979 );
2980 }
2981 let live = !whole
2983 && self
2984 .field_interaction
2985 .is_some_and(|points| self.fields[i].finite.len() > points);
2986 let full = !live && (whole || md.is_infinite() || self.fields[i].live.stale);
2987 let need_near = !full && !live && moved.contains(&true);
2989 let edited = live
2991 && to.iter().any(|&m| {
2992 self.entry(m)
2993 .ok()
2994 .and_then(|e| e.hook(self.fields[i].id))
2995 .and_then(|h| h.changes.as_ref())
2996 .is_some_and(|c| !c.is_empty())
2997 });
2998 if need_near || edited {
2999 to.push(on);
3000 }
3001 self.prepare(Some(&to))?;
3002 to.truncate(n);
3003 let mut near_margins = Vec::new();
3004 near_margins.try_reserve_exact(n).map_err(|_| oom())?;
3005 for (k, &m) in to.iter().enumerate() {
3006 near_margins.push(if need_near && moved[k] {
3007 Some(pad(md, self.pair_magnitude(on, m)?))
3008 } else {
3009 None
3010 });
3011 }
3012 let mut b_to_a = Vec::new();
3013 b_to_a.try_reserve_exact(n).map_err(|_| oom())?;
3014 for &m in &to {
3015 b_to_a.push(self.relative_transform(on, m)?);
3016 }
3017 let live_travel = if live {
3018 self.live_travel(i, &b_to_a)?
3019 } else {
3020 0.0
3021 };
3022 let ma = &self.entry(on)?.mesh;
3023 let changes = hook_a.changes;
3024 let topology = changes.as_ref().is_some_and(ChangeSet::topology_changed);
3025 let work = if full {
3026 ma.vertex_slots().saturating_mul(n)
3027 } else {
3028 let listed = changes.as_ref().map_or(0, |c| {
3029 c.vertices_added.len() + c.vertices_moved.len() + c.vertices_removed.len()
3030 });
3031 let known = if live {
3032 self.field_interaction.unwrap_or(0)
3033 } else if moved.contains(&true) {
3034 self.fields[i].finite.len()
3035 } else {
3036 0
3037 };
3038 let mut near = 0;
3039 for (k, &m) in to.iter().enumerate() {
3040 if let Some(margin) = near_margins[k]
3041 && self.roots_within(on, m, &b_to_a[k], margin)?
3042 {
3043 near += ma.triangle_count();
3044 }
3045 }
3046 let flips = if topology { ma.vertex_slots() } else { 0 };
3047 (listed + known).saturating_mul(n) + near + flips
3048 };
3049 Ok(FieldJob {
3050 i,
3051 full,
3052 moved,
3053 topology,
3054 changes,
3055 near_margins,
3056 b_to_a,
3057 near_limit: near_pair_limit(ma.vertex_slots()),
3058 work,
3059 shared_near: None,
3060 live,
3061 live_travel,
3062 scaled: false,
3063 frame_scale: 1.0,
3064 temp_targets: Vec::new(),
3065 })
3066 }
3067
3068 fn is_scaled_field(&self, on: MeshId, to: &[MeshId]) -> Result<bool> {
3071 if self.entry(on)?.placement.is_scaled() {
3072 return Ok(true);
3073 }
3074 for &m in to {
3075 if self.entry(m)?.placement.is_scaled() {
3076 return Ok(true);
3077 }
3078 }
3079 Ok(false)
3080 }
3081
3082 fn target_uses_temp(&self, i: usize, k: usize) -> bool {
3085 self.entry(self.fields[i].desc.to[k])
3086 .is_ok_and(|e| e.placement.is_scaled() && e.placement.uniform_scale().is_none())
3087 }
3088
3089 fn prepare_scaled_job(
3093 &mut self,
3094 i: usize,
3095 on: MeshId,
3096 to: Vec<MeshId>,
3097 hook_a: FieldHook,
3098 ) -> Result<FieldJob> {
3099 let n = to.len();
3100 let pa = self.entry(on)?.placement;
3101 let in_a_frame = pa.uniform_scale().is_some();
3105 let frame_scale = pa.uniform_scale().unwrap_or(1.0);
3106 let mut temp_targets: Vec<Option<TempTarget>> = Vec::new();
3107 temp_targets.try_reserve_exact(n).map_err(|_| oom())?;
3108 for &m in &to {
3109 let (pb, ob, options) = {
3110 let eb = self.entry(m)?;
3111 (eb.placement, eb.mesh.origin(), *eb.bvh.options())
3112 };
3113 if pb.is_scaled() && pb.uniform_scale().is_none() {
3114 let map = |l: [f32; 3]| {
3115 let full = [
3116 ob[0] + f64::from(l[0]),
3117 ob[1] + f64::from(l[1]),
3118 ob[2] + f64::from(l[2]),
3119 ];
3120 if in_a_frame {
3121 pa.inverse_apply(pb.apply(full))
3122 } else {
3123 pb.apply(full)
3124 }
3125 };
3126 let eb = self.entry(m)?;
3127 let (mut mesh, bvh) = baked_mesh(&eb.mesh, map, &options)?;
3128 let mode = self.fields[i].desc.sign;
3129 let sign = SignData::build(&mut mesh, &bvh, mode)?;
3130 temp_targets.push(Some(TempTarget { mesh, bvh, sign }));
3131 } else {
3132 temp_targets.push(None);
3133 }
3134 }
3135 let ma = &self.entry(on)?.mesh;
3136 let changes = hook_a.changes;
3137 let topology = changes.as_ref().is_some_and(ChangeSet::topology_changed);
3138 let slots = ma.vertex_slots();
3139 let mut moved = Vec::new();
3140 moved.try_reserve_exact(n).map_err(|_| oom())?;
3141 moved.resize(n, true);
3142 Ok(FieldJob {
3143 i,
3144 full: true,
3145 moved,
3146 topology,
3147 changes,
3148 near_margins: Vec::new(),
3149 b_to_a: Vec::new(),
3150 near_limit: near_pair_limit(slots),
3151 work: slots.saturating_mul(n),
3153 shared_near: None,
3154 live: false,
3155 live_travel: 0.0,
3156 scaled: true,
3157 frame_scale,
3158 temp_targets,
3159 })
3160 }
3161
3162 fn compute_fields(&self, jobs: &[(usize, FieldJob)]) -> Result<Vec<Result<FieldPlan>>> {
3171 let threshold = field_threshold(jobs.iter().map(|(_, job)| job));
3172 par::map_chunks(jobs, 1, threshold, oom, |_, chunk| {
3174 Ok(self.compute_field(&chunk[0].1))
3175 })
3176 }
3177
3178 fn compute_field(&self, job: &FieldJob) -> Result<FieldPlan> {
3184 let pick = self.pick_candidates(job)?;
3185 let mut values = Vec::with_capacity(pick.candidates.len());
3186 let mut nearest = Vec::with_capacity(pick.candidates.len());
3187 let recomputed = self.compute_values(job, &pick.candidates, &mut values, &mut nearest)?;
3188 self.finish_field(job, pick, values, nearest, recomputed)
3189 }
3190
3191 fn pick_candidates(&self, job: &FieldJob) -> Result<Pick> {
3194 if job.scaled {
3195 return self.assemble_pick(job, &[], true, false);
3198 }
3199 let f = &self.fields[job.i];
3200 let ea = self.entry(f.desc.on)?;
3201 let ma = &ea.mesh;
3202
3203 if let Some(shared) = &job.shared_near {
3206 return self.assemble_pick(job, shared, job.full, false);
3207 }
3208 let mut near = Vec::new();
3212 let mut all = job.full;
3213 let mut aborted = false;
3214 for (k, margin) in job.near_margins.iter().enumerate() {
3215 let Some(margin) = margin.filter(|_| !all) else {
3216 continue;
3217 };
3218 let eb = self.entry(f.desc.to[k])?;
3219 aborted = !fast_mesh_query::triangles_near_limited(
3220 ma,
3221 &ea.bvh,
3222 &eb.mesh,
3223 &eb.bvh,
3224 &job.b_to_a[k],
3225 round_up(margin),
3226 job.near_limit,
3227 &mut near,
3228 )?;
3229 all = aborted;
3230 }
3231 self.assemble_pick(job, &near, all, aborted)
3232 }
3233
3234 fn assemble_pick(
3238 &self,
3239 job: &FieldJob,
3240 near: &[TriangleId],
3241 all: bool,
3242 aborted: bool,
3243 ) -> Result<Pick> {
3244 if job.live {
3245 return self.assemble_live_pick(job);
3246 }
3247 let f = &self.fields[job.i];
3248 let ma = &self.entry(f.desc.on)?.mesh;
3249 let slots = ma.vertex_slots();
3250 let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3251
3252 let candidates: Vec<VertexId> = if all {
3254 (0..slots as VertexId).collect()
3255 } else {
3256 let mut c = Vec::new();
3257 for &t in near {
3258 if let Some(tri) = ma.triangle(t) {
3259 c.extend_from_slice(&tri);
3260 }
3261 }
3262 if job.geometric() {
3263 let from_moved = |v: VertexId| match f.nearest.get(v as usize) {
3265 Some(&k) => job.moved.get(k as usize).copied().unwrap_or(true),
3266 None => true,
3267 };
3268 c.extend(f.finite.iter().copied().filter(|&v| from_moved(v)));
3269 }
3270 if let Some(ch) = &job.changes {
3271 c.extend_from_slice(&ch.vertices_added);
3272 c.extend_from_slice(&ch.vertices_moved);
3273 c.extend_from_slice(&ch.vertices_removed);
3274 }
3275 if job.topology {
3276 for v in 0..slots as VertexId {
3277 let was = f.referenced.get(v as usize).copied().unwrap_or(false);
3278 if was != defined(v) {
3279 c.push(v);
3280 }
3281 }
3282 }
3283 c.sort_unstable();
3284 c.dedup();
3285 c
3286 };
3287 Ok(Pick {
3288 candidates,
3289 all,
3290 aborted,
3291 margin: 0.0,
3292 })
3293 }
3294
3295 fn assemble_live_pick(&self, job: &FieldJob) -> Result<Pick> {
3313 let f = &self.fields[job.i];
3314 let ea = self.entry(f.desc.on)?;
3315 let ma = &ea.mesh;
3316 let slots = ma.vertex_slots();
3317 let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3318 let values = ma.channel(&f.channel).ok_or(SceneError::Corrupted)?;
3319 let points = self.field_interaction.unwrap_or(1);
3320 let margin = self.live_margin(job.i)?;
3321 let md = f.desc.max_distance;
3322 let stamps = &f.live.stamps;
3323 let reach = |v: usize| -> f64 {
3326 let moved = stamps
3327 .get(v)
3328 .map_or(job.live_travel, |&t| job.live_travel - f64::from(t)); f64::from(values[v].abs()).min(md) - moved
3330 };
3331 let mut c: Vec<VertexId> = (0..values.len().min(slots))
3332 .filter(|&v| !values[v].is_nan() && reach(v) <= margin)
3333 .map(|v| v as VertexId)
3334 .collect();
3335 let mut next = margin;
3336 if c.len() > points {
3337 let key = |&v: &VertexId| (reach(v as usize), v);
3338 let (_, last, _) = c.select_nth_unstable_by(points - 1, |x, y| {
3339 let (a, b) = (key(x), key(y));
3340 a.0.total_cmp(&b.0).then(a.1.cmp(&b.1))
3341 });
3342 let band = reach(*last as usize);
3343 c.truncate(points);
3344 if band > 0.0 {
3346 next = band;
3347 }
3348 } else if c.len() < points / 2 {
3349 next = margin * LIVE_GROW;
3350 }
3351 let edits = (points / LIVE_EDIT_SHARE).max(1);
3352 let pad = margin as f32;
3353 let mut boxed = Vec::new();
3354 for (k, &m) in f.desc.to.iter().enumerate() {
3355 let eb = self.entry(m)?;
3356 let Some(ch) = eb.hook(f.id).and_then(|h| h.changes.as_ref()) else {
3357 continue;
3358 };
3359 let (mut lo, mut hi) = ([f32::INFINITY; 3], [f32::NEG_INFINITY; 3]);
3360 for &v in ch.vertices_moved.iter().chain(&ch.vertices_added) {
3361 if let Some(p) = eb.mesh.position(v) {
3362 let q = job.b_to_a[k].apply(p);
3363 for d in 0..3 {
3364 lo[d] = lo[d].min(q[d] as f32 - pad);
3365 hi[d] = hi[d].max(q[d] as f32 + pad);
3366 }
3367 }
3368 }
3369 if lo[0] <= hi[0] {
3370 boxed.clear();
3371 ea.bvh.query_aabb(ma, lo, hi, &mut boxed);
3372 boxed.sort_unstable();
3373 let every = boxed.len().div_ceil(edits).max(1);
3374 let tris = boxed.iter().step_by(every).filter_map(|&t| ma.triangle(t));
3375 c.extend(tris.flatten());
3376 }
3377 }
3378 if let Some(ch) = &job.changes {
3379 c.extend_from_slice(&ch.vertices_added);
3380 c.extend_from_slice(&ch.vertices_removed);
3381 let mut moved = ch.vertices_moved.clone();
3385 if moved.len() > edits {
3386 let key = |&v: &VertexId| (values.get(v as usize).map_or(0.0, |x| x.abs()), v);
3387 moved.select_nth_unstable_by(edits - 1, |x, y| {
3388 let (a, b) = (key(x), key(y));
3389 a.0.total_cmp(&b.0).then(a.1.cmp(&b.1))
3390 });
3391 moved.truncate(edits);
3392 }
3393 c.extend_from_slice(&moved);
3394 }
3395 if job.topology {
3396 for v in 0..slots as VertexId {
3397 let was = f.referenced.get(v as usize).copied().unwrap_or(false);
3398 if was != defined(v) {
3399 c.push(v);
3400 }
3401 }
3402 }
3403 let (start, count) = self.live_sweep(job.i, slots);
3404 c.extend((0..count).map(|k| ((start as usize + k) % slots) as VertexId));
3405 c.sort_unstable();
3406 c.dedup();
3407 Ok(Pick {
3408 candidates: c,
3409 all: false,
3410 aborted: false,
3411 margin: next,
3412 })
3413 }
3414
3415 fn live_sweep(&self, i: usize, slots: usize) -> (u32, usize) {
3418 if slots == 0 {
3419 return (0, 0);
3420 }
3421 let count = self
3422 .field_interaction
3423 .map_or(0, |points| (points / LIVE_SWEEP_SHARE).max(1))
3424 .min(slots);
3425 (self.fields[i].live.sweep % slots as u32, count)
3426 }
3427
3428 fn compute_values(
3434 &self,
3435 job: &FieldJob,
3436 candidates: &[VertexId],
3437 values: &mut Vec<f32>,
3438 nearest: &mut Vec<u32>,
3439 ) -> Result<usize> {
3440 if job.scaled {
3441 return self.compute_values_scaled(job, candidates, values, nearest);
3442 }
3443 let f = &self.fields[job.i];
3444 let ma = &self.entry(f.desc.on)?.mesh;
3445 let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3446 #[cfg(test)]
3447 if counter::fails(f.id) {
3448 return Err(oom());
3449 }
3450 let mut targets = Vec::new();
3451 targets
3452 .try_reserve_exact(f.desc.to.len())
3453 .map_err(|_| oom())?;
3454 for (k, &m) in f.desc.to.iter().enumerate() {
3455 let eb = self.entry(m)?;
3456 targets.push(Target {
3457 mesh: &eb.mesh,
3458 bvh: &eb.bvh,
3459 sign: f.sign[k].as_ref().ok_or(SceneError::Corrupted)?,
3460 to_local: job.b_to_a[k].inverse(),
3463 });
3464 }
3465
3466 let mut ids = Vec::new();
3467 let mut points = Vec::new();
3468 for &v in candidates {
3469 if let Some(p) = ma.position(v).filter(|_| defined(v)) {
3470 ids.push(v);
3471 points.push(p);
3472 }
3473 }
3474 let mut out = vec![0.0f32; points.len()];
3475 let mut from = vec![0u32; points.len()];
3476 let md = f.desc.max_distance;
3477 signed_distances_multi(&targets, &points, md, &mut out, &mut from)?;
3478
3479 let mut k = 0;
3480 for &v in candidates {
3481 let (x, n) = if ids.get(k) == Some(&v) {
3482 k += 1;
3483 (out[k - 1], from[k - 1])
3484 } else {
3485 (f32::NAN, u32::MAX)
3486 };
3487 values.push(x);
3488 nearest.push(n);
3489 }
3490 Ok(points.len())
3491 }
3492
3493 fn compute_values_scaled(
3516 &self,
3517 job: &FieldJob,
3518 candidates: &[VertexId],
3519 values: &mut Vec<f32>,
3520 nearest: &mut Vec<u32>,
3521 ) -> Result<usize> {
3522 let f = &self.fields[job.i];
3523 #[cfg(test)]
3524 if counter::fails(f.id) {
3525 return Err(oom());
3526 }
3527 if f.desc.to.len() >= u32::MAX as usize {
3528 return Err(SceneError::InvalidInput("too many targets"));
3529 }
3530 let ea = self.entry(f.desc.on)?;
3531 let (pa, oa) = (ea.placement, ea.mesh.origin());
3532 let in_a_frame = pa.uniform_scale().is_some();
3533 let md = f.desc.max_distance;
3534 let mut frame: Vec<Option<[f64; 3]>> = Vec::new();
3537 frame
3538 .try_reserve_exact(candidates.len())
3539 .map_err(|_| oom())?;
3540 for &v in candidates {
3541 frame.push(
3542 ea.mesh
3543 .position(v)
3544 .filter(|_| ea.mesh.is_vertex_alive(v) && ea.mesh.corner_count(v) > 0)
3545 .map(|p| {
3546 if in_a_frame {
3547 [f64::from(p[0]), f64::from(p[1]), f64::from(p[2])]
3548 } else {
3549 pa.apply([
3550 oa[0] + f64::from(p[0]),
3551 oa[1] + f64::from(p[1]),
3552 oa[2] + f64::from(p[2]),
3553 ])
3554 }
3555 }),
3556 );
3557 }
3558 let mut at: Vec<usize> = Vec::new();
3559 at.try_reserve_exact(candidates.len()).map_err(|_| oom())?;
3560 let mut fpts: Vec<[f64; 3]> = Vec::new();
3561 fpts.try_reserve_exact(candidates.len())
3562 .map_err(|_| oom())?;
3563 for (ci, q) in frame.iter().enumerate() {
3564 if let Some(q) = q {
3565 at.push(ci);
3566 fpts.push(*q);
3567 }
3568 }
3569 let mut best: Vec<(f32, u32)> = Vec::new();
3570 best.try_reserve_exact(candidates.len())
3571 .map_err(|_| oom())?;
3572 best.resize(candidates.len(), (f32::INFINITY, u32::MAX));
3573 for (k, &m) in f.desc.to.iter().enumerate() {
3574 let eb = self.entry(m)?;
3577 let (mesh, bvh, sign, bound, to_world) = match &job.temp_targets[k] {
3578 Some(t) => (
3579 &t.mesh,
3580 &t.bvh,
3581 &t.sign,
3582 round_up(md / job.frame_scale),
3583 job.frame_scale,
3584 ),
3585 None => {
3586 let s = eb.placement.uniform_scale().unwrap_or(1.0);
3587 (
3588 &eb.mesh,
3589 &eb.bvh,
3590 f.sign[k].as_ref().ok_or(SceneError::Corrupted)?,
3591 round_up(md / s),
3592 s,
3593 )
3594 }
3595 };
3596 let (pb, ob) = (eb.placement, eb.mesh.origin());
3597 let temp = job.temp_targets[k].is_some();
3598 let mut points: Vec<[f32; 3]> = Vec::new();
3599 points.try_reserve_exact(fpts.len()).map_err(|_| oom())?;
3600 for &q in &fpts {
3601 let l = if temp {
3607 q
3608 } else {
3609 let w = if in_a_frame {
3610 pa.apply([oa[0] + q[0], oa[1] + q[1], oa[2] + q[2]])
3611 } else {
3612 q
3613 };
3614 let m = pb.inverse_apply(w);
3615 [m[0] - ob[0], m[1] - ob[1], m[2] - ob[2]]
3616 };
3617 points.push([l[0] as f32, l[1] as f32, l[2] as f32]);
3618 }
3619 let mut out: Vec<f32> = Vec::new();
3620 out.try_reserve_exact(points.len()).map_err(|_| oom())?;
3621 out.resize(points.len(), 0.0);
3622 signed_distances(mesh, bvh, sign, &points, bound, &mut out)?;
3623 for (j, &ci) in at.iter().enumerate() {
3624 let w = to_world * f64::from(out[j]);
3625 let x = if w.abs() > md {
3626 f32::INFINITY
3627 } else {
3628 w as f32
3629 };
3630 if x < best[ci].0 {
3632 best[ci] = (x, k as u32);
3633 }
3634 }
3635 }
3636 values.try_reserve(candidates.len()).map_err(|_| oom())?;
3637 nearest.try_reserve(candidates.len()).map_err(|_| oom())?;
3638 for (ci, q) in frame.iter().enumerate() {
3639 let (x, n) = if q.is_some() {
3640 best[ci]
3641 } else {
3642 (f32::NAN, u32::MAX)
3643 };
3644 values.push(x);
3645 nearest.push(n);
3646 }
3647 Ok(fpts.len())
3648 }
3649
3650 fn finish_field(
3654 &self,
3655 job: &FieldJob,
3656 pick: Pick,
3657 values: Vec<f32>,
3658 nearest: Vec<u32>,
3659 recomputed: usize,
3660 ) -> Result<FieldPlan> {
3661 let f = &self.fields[job.i];
3662 let ma = &self.entry(f.desc.on)?.mesh;
3663 let slots = ma.vertex_slots();
3664 let defined = |v: VertexId| ma.is_vertex_alive(v) && ma.corner_count(v) > 0;
3665 let Pick {
3666 candidates,
3667 all,
3668 aborted,
3669 margin,
3670 } = pick;
3671 if values.len() != candidates.len() || nearest.len() != candidates.len() {
3672 return Err(SceneError::Corrupted);
3673 }
3674
3675 let old = ma.channel(&f.channel).ok_or(SceneError::Corrupted)?;
3676 let changed: Vec<VertexId> = candidates
3677 .iter()
3678 .zip(&values)
3679 .filter(|&(&v, x)| old.get(v as usize).map(|o| o.to_bits()) != Some(x.to_bits()))
3680 .map(|(&v, _)| v)
3681 .collect();
3682 let referenced = (job.full || job.topology || f.referenced.len() != slots)
3683 .then(|| (0..slots as VertexId).map(defined).collect::<Vec<bool>>());
3684 let finite: Vec<VertexId> = {
3685 let now = candidates
3686 .iter()
3687 .zip(&values)
3688 .filter(|(_, x)| x.is_finite())
3689 .map(|(&v, _)| v);
3690 if job.live {
3691 Vec::new()
3693 } else if all {
3694 now.collect()
3695 } else {
3696 let mut kept: Vec<VertexId> = f
3697 .finite
3698 .iter()
3699 .copied()
3700 .filter(|v| candidates.binary_search(v).is_err())
3701 .chain(now)
3702 .collect();
3703 kept.sort_unstable();
3704 kept
3705 }
3706 };
3707 Ok(FieldPlan {
3708 candidates,
3709 values,
3710 nearest,
3711 changed,
3712 referenced,
3713 finite,
3714 recomputed,
3715 aborted,
3716 margin,
3717 })
3718 }
3719
3720 fn apply_field(&mut self, job: FieldJob, plan: FieldPlan) -> Result<()> {
3724 let i = job.i;
3725 if job.live {
3726 let slots = self.entry(self.fields[i].desc.on)?.mesh.vertex_slots();
3727 let (start, swept) = self.live_sweep(i, slots);
3728 let f = &mut self.fields[i];
3729 if slots > 0 {
3730 f.live.sweep = ((start as usize + swept) % slots) as u32;
3731 }
3732 f.live.margin = plan.margin;
3733 f.live.stamps.resize(slots, f.live.travel as f32);
3735 f.live.travel = job.live_travel;
3736 let stamp = job.live_travel as f32;
3737 for &v in &plan.candidates {
3738 f.live.stamps[v as usize] = stamp;
3739 }
3740 }
3741 let (id, on) = (self.fields[i].id, self.fields[i].desc.on);
3742 let f = &mut self.fields[i];
3743 let ea = self.entries.get_mut(on).ok_or(SceneError::UnknownMesh)?;
3744 let slots = ea.mesh.vertex_slots();
3745 f.nearest
3746 .try_reserve(slots.saturating_sub(f.nearest.len()))
3747 .map_err(|_| oom())?;
3748 let channel = ea.mesh.derived_channel_mut(&f.channel)?;
3749 #[cfg(test)]
3751 {
3752 counter::add(plan.recomputed);
3753 counter::add_aborted(plan.aborted);
3754 counter::add_applied(id, plan.recomputed);
3755 }
3756 for (&v, &x) in plan.candidates.iter().zip(&plan.values) {
3757 channel[v as usize] = x;
3758 }
3759 f.nearest.resize(slots, u32::MAX);
3760 for (&v, &n) in plan.candidates.iter().zip(&plan.nearest) {
3761 f.nearest[v as usize] = n;
3762 }
3763 ea.reset_hook(id, false);
3764 if !plan.changed.is_empty() {
3765 ea.report_field(id);
3766 }
3767 if let Some(cs) = &mut ea.coloring
3768 && cs.coloring.field == id
3769 {
3770 let values = ea.mesh.channel(&cs.channel).unwrap_or(&[]);
3771 let dirty = cs.update(values, &plan.changed);
3772 ea.report_colors(Some(&dirty));
3773 }
3774 for (k, &m) in f.desc.to.iter().enumerate() {
3775 if let Some(eb) = self.entries.get_mut(m) {
3776 let baked = job.temp_targets.get(k).is_some_and(Option::is_some);
3777 eb.reset_hook(id, baked);
3778 }
3779 }
3780 f.fresh = false;
3781 f.computed = true;
3782 if job.live {
3783 f.live.stale = true;
3784 } else {
3785 f.live.stale = false;
3786 f.finite = plan.finite;
3787 f.live.travel = 0.0;
3788 f.live.stamps = Vec::new();
3789 }
3790 f.live.poses = job.b_to_a;
3791 if let Some(r) = plan.referenced {
3792 f.referenced = r;
3793 }
3794 Ok(())
3795 }
3796}
3797
3798fn field_threshold<'a>(jobs: impl Iterator<Item = &'a FieldJob>) -> usize {
3803 let (n, work) = jobs.fold((0, 0), |(n, w), job| (n + 1, w + job.work));
3804 let parallel =
3805 work >= PAR_MIN_FIELD_WORK && n > 1 && par::enabled() && !par::force_sequential();
3806 #[cfg(test)]
3807 if parallel {
3808 counter::add_parallel();
3809 }
3810 if parallel { 0 } else { usize::MAX }
3811}
3812
3813fn near_pair_limit(slots: usize) -> usize {
3817 #[cfg(test)]
3818 if let Some(limit) = counter::near_limit() {
3819 return limit;
3820 }
3821 slots.saturating_mul(NEAR_PAIRS_PER_SLOT)
3822}
3823
3824#[cfg(test)]
3827pub(crate) mod counter {
3828 use std::cell::Cell;
3829
3830 thread_local! {
3833 static RECOMPUTED: Cell<usize> = const { Cell::new(0) };
3834 static PARALLEL: Cell<usize> = const { Cell::new(0) };
3835 static NEAR_LIMIT: Cell<Option<usize>> = const { Cell::new(None) };
3836 static ABORTED: Cell<usize> = const { Cell::new(0) };
3837 static FAILING: Cell<Option<super::FieldId>> = const { Cell::new(None) };
3838 static SIGN_STEPS: Cell<usize> = const { Cell::new(0) };
3839 static SHARED: Cell<usize> = const { Cell::new(0) };
3840 }
3841
3842 pub(crate) fn add_shared() {
3844 SHARED.with(|c| c.set(c.get() + 1));
3845 }
3846
3847 pub(crate) fn take_shared() -> usize {
3849 SHARED.with(|c| c.replace(0))
3850 }
3851
3852 pub(crate) fn add_sign_step() {
3854 SIGN_STEPS.with(|c| c.set(c.get() + 1));
3855 }
3856
3857 pub(crate) fn take_sign_steps() -> usize {
3859 SIGN_STEPS.with(|c| c.replace(0))
3860 }
3861
3862 pub(crate) fn set_failing(field: Option<super::FieldId>) {
3865 FAILING.with(|c| c.set(field));
3866 }
3867
3868 pub(crate) fn fails(field: super::FieldId) -> bool {
3869 FAILING.with(Cell::get) == Some(field)
3870 }
3871
3872 thread_local! {
3873 static APPLIED: std::cell::RefCell<Vec<(super::FieldId, usize)>> =
3874 const { std::cell::RefCell::new(Vec::new()) };
3875 }
3876
3877 pub(crate) fn add_applied(field: super::FieldId, points: usize) {
3880 APPLIED.with(|c| c.borrow_mut().push((field, points)));
3881 }
3882
3883 pub(crate) fn take_applied() -> Vec<super::FieldId> {
3885 take_applied_counts().into_iter().map(|(f, _)| f).collect()
3886 }
3887
3888 pub(crate) fn take_applied_counts() -> Vec<(super::FieldId, usize)> {
3890 APPLIED.with(|c| std::mem::take(&mut *c.borrow_mut()))
3891 }
3892
3893 pub(crate) fn add_aborted(aborted: bool) {
3895 ABORTED.with(|c| c.set(c.get() + usize::from(aborted)));
3896 }
3897
3898 pub(crate) fn take_aborted() -> usize {
3900 ABORTED.with(|c| c.replace(0))
3901 }
3902
3903 pub(crate) fn set_near_limit(limit: Option<usize>) {
3906 NEAR_LIMIT.with(|c| c.set(limit));
3907 }
3908
3909 pub(crate) fn near_limit() -> Option<usize> {
3910 NEAR_LIMIT.with(Cell::get)
3911 }
3912
3913 pub(crate) fn add(n: usize) {
3915 RECOMPUTED.with(|c| c.set(c.get() + n));
3916 }
3917
3918 pub(crate) fn take() -> usize {
3920 RECOMPUTED.with(|c| c.replace(0))
3921 }
3922
3923 pub(crate) fn add_parallel() {
3925 PARALLEL.with(|c| c.set(c.get() + 1));
3926 }
3927
3928 pub(crate) fn take_parallel() -> usize {
3931 PARALLEL.with(|c| c.replace(0))
3932 }
3933}
3934
3935#[cfg(test)]
3936mod tests {
3937 use fast_mesh_fields::{Colormap, OutOfRange, SignData, color_of, signed_distances};
3938 use fast_mesh_store::{Mesh, VertexId};
3939
3940 use super::*;
3941 use crate::query::tests::cube;
3942 use crate::{MeshOptions, Placement, Scene, SceneError, SceneOptions, UpdateMode};
3943
3944 struct Lcg(u64);
3946
3947 impl Lcg {
3948 fn next(&mut self) -> f64 {
3949 self.0 = self
3950 .0
3951 .wrapping_mul(6364136223846793005)
3952 .wrapping_add(1442695040888963407);
3953 (self.0 >> 11) as f64 / (1u64 << 53) as f64
3954 }
3955
3956 fn range(&mut self, lo: f64, hi: f64) -> f64 {
3957 lo + (hi - lo) * self.next()
3958 }
3959
3960 fn below(&mut self, n: usize) -> usize {
3961 ((self.next() * n as f64) as usize).min(n - 1)
3962 }
3963 }
3964
3965 fn sheet(n: u32, h: f32) -> Mesh {
3967 let mut pos = Vec::new();
3968 for y in 0..=n {
3969 for x in 0..=n {
3970 pos.extend_from_slice(&[x as f32 * h, y as f32 * h, 0.0]);
3971 }
3972 }
3973 let mut idx = Vec::new();
3974 for y in 0..n {
3975 for x in 0..n {
3976 let a = y * (n + 1) + x;
3977 let b = a + 1;
3978 let c = a + n + 1;
3979 idx.extend_from_slice(&[a, b, c, b, c + 1, c]);
3980 }
3981 }
3982 Mesh::from_buffers(&pos, &idx).unwrap()
3983 }
3984
3985 fn with_mode(mode: UpdateMode) -> MeshOptions {
3986 MeshOptions {
3987 mode,
3988 ..MeshOptions::default()
3989 }
3990 }
3991
3992 fn at(x: f64, y: f64, z: f64) -> Placement {
3993 Placement {
3994 translation: [x, y, z],
3995 ..Placement::IDENTITY
3996 }
3997 }
3998
3999 fn turned(deg: f64, t: [f64; 3]) -> Placement {
4001 let (s, c) = deg.to_radians().sin_cos();
4002 Placement {
4003 rotation: [[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]],
4004 translation: t,
4005 ..Placement::IDENTITY
4006 }
4007 }
4008
4009 fn field(name: &str, on: MeshId, to: MeshId, max_distance: f64) -> DistanceField {
4010 DistanceField {
4011 name: name.to_owned(),
4012 on,
4013 to: vec![to],
4014 max_distance,
4015 sign: SignMode::Auto,
4016 }
4017 }
4018
4019 fn thick(name: &str, on: MeshId, max_distance: f64) -> ThicknessField {
4020 ThicknessField {
4021 name: name.to_owned(),
4022 on,
4023 max_distance,
4024 }
4025 }
4026
4027 fn curv(name: &str, on: MeshId) -> CurvatureField {
4028 CurvatureField {
4029 name: name.to_owned(),
4030 on,
4031 }
4032 }
4033
4034 fn plate(n: u32) -> Mesh {
4036 let h = 0.25;
4037 let w = n + 1;
4038 let mut pos = Vec::new();
4039 for z in [h, -h] {
4040 for y in 0..=n {
4041 for x in 0..=n {
4042 pos.extend_from_slice(&[x as f32 / n as f32, y as f32 / n as f32, z]);
4043 }
4044 }
4045 }
4046 let t = |x: u32, y: u32| y * w + x;
4047 let b = |x: u32, y: u32| w * w + y * w + x;
4048 let mut idx = Vec::new();
4049 for y in 0..n {
4050 for x in 0..n {
4051 idx.extend_from_slice(&[
4052 t(x, y),
4053 t(x + 1, y),
4054 t(x + 1, y + 1),
4055 t(x, y),
4056 t(x + 1, y + 1),
4057 t(x, y + 1),
4058 b(x, y),
4059 b(x + 1, y + 1),
4060 b(x + 1, y),
4061 b(x, y),
4062 b(x, y + 1),
4063 b(x + 1, y + 1),
4064 ]);
4065 }
4066 }
4067 for x in 0..n {
4068 idx.extend_from_slice(&[
4069 t(x, 0),
4070 b(x, 0),
4071 b(x + 1, 0),
4072 t(x, 0),
4073 b(x + 1, 0),
4074 t(x + 1, 0),
4075 t(x, n),
4076 t(x + 1, n),
4077 b(x + 1, n),
4078 t(x, n),
4079 b(x + 1, n),
4080 b(x, n),
4081 t(0, x),
4082 t(0, x + 1),
4083 b(0, x + 1),
4084 t(0, x),
4085 b(0, x + 1),
4086 b(0, x),
4087 t(n, x),
4088 b(n, x),
4089 b(n, x + 1),
4090 t(n, x),
4091 b(n, x + 1),
4092 t(n, x + 1),
4093 ]);
4094 }
4095 Mesh::from_buffers(&pos, &idx).unwrap()
4096 }
4097
4098 fn ball() -> Mesh {
4101 let mut pos: Vec<[f64; 3]> = vec![
4102 [1.0, 0.0, 0.0],
4103 [-1.0, 0.0, 0.0],
4104 [0.0, 1.0, 0.0],
4105 [0.0, -1.0, 0.0],
4106 [0.0, 0.0, 1.0],
4107 [0.0, 0.0, -1.0],
4108 ];
4109 let mut tris: Vec<[u32; 3]> = vec![
4110 [4, 0, 2],
4111 [4, 2, 1],
4112 [4, 1, 3],
4113 [4, 3, 0],
4114 [5, 2, 0],
4115 [5, 1, 2],
4116 [5, 3, 1],
4117 [5, 0, 3],
4118 ];
4119 for _ in 0..2 {
4120 let mut mid = std::collections::HashMap::new();
4121 let mut midpoint = |a: u32, b: u32, pos: &mut Vec<[f64; 3]>| {
4122 *mid.entry((a.min(b), a.max(b))).or_insert_with(|| {
4123 let (pa, pb) = (pos[a as usize], pos[b as usize]);
4124 pos.push(std::array::from_fn(|k| (pa[k] + pb[k]) / 2.0));
4125 (pos.len() - 1) as u32
4126 })
4127 };
4128 let mut next = Vec::new();
4129 for [a, b, c] in tris {
4130 let ab = midpoint(a, b, &mut pos);
4131 let bc = midpoint(b, c, &mut pos);
4132 let ca = midpoint(c, a, &mut pos);
4133 next.extend([[a, ab, ca], [b, bc, ab], [c, ca, bc], [ab, bc, ca]]);
4134 }
4135 tris = next;
4136 }
4137 let flat: Vec<f32> = pos
4138 .iter()
4139 .flat_map(|p| {
4140 let n = (p[0] * p[0] + p[1] * p[1] + p[2] * p[2]).sqrt();
4141 [p[0] / n, p[1] / n, p[2] / n].map(|c| c as f32)
4142 })
4143 .collect();
4144 let idx: Vec<u32> = tris.iter().flat_map(|t| *t).collect();
4145 Mesh::from_buffers(&flat, &idx).unwrap()
4146 }
4147
4148 fn saddle(n: u32, s: f32) -> Mesh {
4151 let mut pos = Vec::new();
4152 for y in 0..=n {
4153 for x in 0..=n {
4154 let (fx, fy) = (
4155 x as f32 / n as f32 * 2.0 - 1.0,
4156 y as f32 / n as f32 * 2.0 - 1.0,
4157 );
4158 pos.extend_from_slice(&[fx, fy, (2.0 * fx * fx - fy * fy) / s]);
4159 }
4160 }
4161 let mut idx = Vec::new();
4162 for y in 0..n {
4163 for x in 0..n {
4164 let a = y * (n + 1) + x;
4165 idx.extend_from_slice(&[a, a + 1, a + n + 2, a, a + n + 2, a + n + 1]);
4166 }
4167 }
4168 Mesh::from_buffers(&pos, &idx).unwrap()
4169 }
4170
4171 fn thick_rule(lo: f32, hi: f32) -> ColorRule {
4173 ColorRule {
4174 map: Colormap::Turbo,
4175 range: (lo, hi),
4176 below: OutOfRange::Clamp,
4177 above: OutOfRange::Clamp,
4178 center: None,
4179 base: [200, 200, 200, 255],
4180 nan: [128, 128, 128, 255],
4181 }
4182 }
4183
4184 fn sheet_and_cube(a_mode: UpdateMode) -> (Scene, MeshId, MeshId) {
4186 let mut scene = Scene::new(SceneOptions::default());
4187 let a = scene.add_mesh(sheet(10, 0.1), with_mode(a_mode)).unwrap();
4188 let b = scene
4189 .add_mesh(cube([0.3, 0.3, -0.2], 0.4), MeshOptions::default())
4190 .unwrap();
4191 (scene, a, b)
4192 }
4193
4194 fn reference(scene: &mut Scene, id: FieldId) -> Vec<f32> {
4200 let f = scene.fields.iter().find(|f| f.id == id).unwrap();
4201 let (on, to, md, mode) = (f.desc.on, f.desc.to[0], f.desc.max_distance, f.desc.sign);
4202 let ma = scene.mesh(on).unwrap();
4203 let slots = ma.vertex_slots();
4204 let defined: Vec<bool> = (0..slots as VertexId)
4205 .map(|v| ma.is_vertex_alive(v) && ma.corner_count(v) > 0)
4206 .collect();
4207 let points: Vec<[f32; 3]> = (0..slots as VertexId)
4208 .filter(|&v| defined[v as usize])
4209 .map(|v| {
4210 let p = ma.position(v).unwrap().map(f64::from);
4211 let w = scene.local_to_world(on, p).unwrap();
4212 scene.world_to_local(to, w).unwrap().map(|c| c as f32)
4213 })
4214 .collect();
4215 let eb = scene.entries.get_mut(to).unwrap();
4216 assert!(eb.is_current(), "the reference needs B's BVH current");
4217 let sign = SignData::build(&mut eb.mesh, &eb.bvh, mode).unwrap();
4218 let mut out = vec![0.0; points.len()];
4219 let bound = crate::query::round_up(md);
4220 signed_distances(&eb.mesh, &eb.bvh, &sign, &points, bound, &mut out).unwrap();
4221 let mut values = out.into_iter().map(|v| {
4222 if f64::from(v.abs()) > md {
4223 f32::INFINITY
4224 } else {
4225 v
4226 }
4227 });
4228 defined
4229 .iter()
4230 .map(|&d| if d { values.next().unwrap() } else { f32::NAN })
4231 .collect()
4232 }
4233
4234 fn bits(v: &[f32]) -> Vec<u32> {
4235 v.iter().map(|x| x.to_bits()).collect()
4236 }
4237
4238 const TOL: f32 = 1e-5;
4241
4242 fn close(g: f32, w: f32, md: f64) -> bool {
4246 if g.to_bits() == w.to_bits() {
4247 return true;
4248 }
4249 if g.is_nan() || w.is_nan() {
4250 return false;
4251 }
4252 match (g.is_finite(), w.is_finite()) {
4253 (true, true) => (g - w).abs() <= TOL,
4254 (false, false) => g == w,
4255 _ => {
4256 let (finite, infinite) = if g.is_finite() { (g, w) } else { (w, g) };
4257 infinite == f32::INFINITY && f64::from(finite.abs()) >= md - f64::from(TOL)
4258 }
4259 }
4260 }
4261
4262 fn assert_matches(scene: &mut Scene, id: FieldId) {
4263 let want = reference(scene, id);
4264 let md = scene
4265 .fields
4266 .iter()
4267 .find(|f| f.id == id)
4268 .unwrap()
4269 .desc
4270 .max_distance;
4271 let got = scene.field_values(id).unwrap().to_vec();
4272 assert_eq!(got.len(), want.len());
4273 for (v, (g, w)) in got.iter().zip(&want).enumerate() {
4274 assert!(close(*g, *w, md), "vertex {v}: got {g}, want {w}");
4275 }
4276 assert!(scene.field_status(id).unwrap().current);
4277 }
4278
4279 fn assert_colors(scene: &Scene, mesh: MeshId, rule: &ColorRule, id: FieldId) {
4280 let values = scene.field_values(id).unwrap();
4281 let colors = scene.colors(mesh).unwrap().unwrap();
4282 assert_eq!(colors.len(), values.len());
4283 for (v, (c, &x)) in colors.iter().zip(values).enumerate() {
4284 assert_eq!(*c, color_of(rule, x), "vertex {v}, value {x}");
4285 }
4286 }
4287
4288 fn contact_rule(d: f32) -> ColorRule {
4289 ColorRule {
4290 map: Colormap::Turbo,
4291 range: (-d, 0.0),
4292 below: OutOfRange::Clamp,
4293 above: OutOfRange::Base,
4294 center: None,
4295 base: [200, 200, 200, 255],
4296 nan: [255, 0, 255, 255],
4297 }
4298 }
4299
4300 fn distance_rule(d: f32, g: f32) -> ColorRule {
4301 ColorRule {
4302 map: Colormap::Coolwarm,
4303 range: (-d, g),
4304 below: OutOfRange::Clamp,
4305 above: OutOfRange::Base,
4306 center: Some(0.0),
4307 base: [200, 200, 200, 255],
4308 nan: [255, 0, 255, 255],
4309 }
4310 }
4311
4312 fn finite_count(scene: &Scene, id: FieldId) -> usize {
4313 scene
4314 .field_values(id)
4315 .unwrap()
4316 .iter()
4317 .filter(|v| v.is_finite())
4318 .count()
4319 }
4320
4321 fn colors_dirty(scene: &mut Scene, mesh: MeshId) -> Option<Vec<VertexId>> {
4322 scene
4323 .take_changes()
4324 .into_iter()
4325 .find(|c| c.id == mesh)
4326 .map_or(Some(Vec::new()), |c| c.colors_dirty)
4327 }
4328
4329 fn alive_vertex(scene: &Scene, m: MeshId, rng: &mut Lcg) -> Option<VertexId> {
4330 let mesh = scene.mesh(m).unwrap();
4331 let alive: Vec<VertexId> = (0..mesh.vertex_slots() as VertexId)
4332 .filter(|&v| mesh.is_vertex_alive(v))
4333 .collect();
4334 (!alive.is_empty()).then(|| alive[rng.below(alive.len())])
4335 }
4336
4337 fn alive_triangle(scene: &Scene, m: MeshId, rng: &mut Lcg) -> Option<u32> {
4338 let mesh = scene.mesh(m).unwrap();
4339 let alive: Vec<u32> = (0..mesh.triangle_slots() as u32)
4340 .filter(|&t| mesh.triangle(t).is_some())
4341 .collect();
4342 (!alive.is_empty()).then(|| alive[rng.below(alive.len())])
4343 }
4344
4345 fn random_mode(rng: &mut Lcg) -> UpdateMode {
4346 [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual][rng.below(3)]
4347 }
4348
4349 fn random_sign(rng: &mut Lcg) -> SignMode {
4350 [SignMode::Pseudonormal, SignMode::Winding, SignMode::Auto][rng.below(3)]
4351 }
4352
4353 fn random_max_distance(rng: &mut Lcg) -> f64 {
4354 if rng.below(5) == 0 {
4355 f64::INFINITY
4356 } else {
4357 rng.range(0.03, 0.25)
4358 }
4359 }
4360
4361 fn random_op(scene: &mut Scene, a: MeshId, b: MeshId, rng: &mut Lcg) {
4365 let m = if rng.below(2) == 0 { a } else { b };
4366 match rng.below(16) {
4367 0 | 1 => {
4368 let p = turned(
4369 rng.range(-25.0, 25.0),
4370 [
4371 rng.range(-0.1, 0.1),
4372 rng.range(-0.1, 0.1),
4373 rng.range(-0.1, 0.1),
4374 ],
4375 );
4376 scene.set_placement(m, p).unwrap();
4377 }
4378 2 => {
4379 if let Some(v) = alive_vertex(scene, a, rng) {
4380 let z = rng.range(-0.2, 0.2) as f32;
4381 scene
4382 .edit(a, |tx| {
4383 let p = tx.mesh().position(v).unwrap();
4384 tx.set_positions(&[v], &[[p[0], p[1], z]])
4385 })
4386 .unwrap();
4387 }
4388 }
4389 3 => {
4390 if let Some(v) = alive_vertex(scene, b, rng) {
4391 let s = rng.range(0.95, 1.05) as f32;
4392 scene
4393 .edit(b, |tx| {
4394 let p = tx.mesh().position(v).unwrap();
4395 tx.set_positions(&[v], &[p.map(|c| c * s)])
4396 })
4397 .unwrap();
4398 }
4399 }
4400 4 => {
4401 if let Some(t) = alive_triangle(scene, m, rng) {
4402 scene.edit(m, |tx| tx.remove_triangles(&[t])).unwrap();
4403 }
4404 }
4405 5 => {
4406 let (Some(p), Some(q)) = (alive_vertex(scene, a, rng), alive_vertex(scene, a, rng))
4408 else {
4409 return;
4410 };
4411 if p == q {
4412 return;
4413 }
4414 let x = [rng.range(0.0, 1.0) as f32, rng.range(0.0, 1.0) as f32, 0.02];
4415 scene
4416 .edit(a, |tx| {
4417 let v = tx.add_vertices(&[x])?;
4418 tx.add_triangles(&[[v[0], p, q]])
4419 })
4420 .unwrap();
4421 }
4422 6 => {
4423 let mesh = scene.mesh(m).unwrap();
4425 let unused = (0..mesh.vertex_slots() as VertexId)
4426 .find(|&v| mesh.is_vertex_alive(v) && mesh.corner_count(v) == 0);
4427 if let Some(v) = unused {
4428 scene.edit(m, |tx| tx.remove_vertices(&[v])).unwrap();
4429 }
4430 }
4431 7 => {
4432 if let Some(t) = alive_triangle(scene, m, rng) {
4435 scene
4436 .edit(m, |tx| {
4437 let [i, j, k] = tx.mesh().triangle(t).unwrap();
4438 let ps = [i, j, k].map(|v| tx.mesh().position(v).unwrap());
4439 let c = std::array::from_fn(|d| (ps[0][d] + ps[1][d] + ps[2][d]) / 3.0);
4440 let v = tx.add_vertices(&[c])?[0];
4441 tx.remove_triangles(&[t])?;
4442 tx.add_triangles(&[[i, j, v], [j, k, v], [k, i, v]])
4443 })
4444 .unwrap();
4445 }
4446 }
4447 8 | 9 => {
4448 scene.undo().unwrap();
4449 }
4450 10 => {
4451 scene.redo().unwrap();
4452 }
4453 11 => {
4454 scene.begin_step();
4456 for k in 1..=3 {
4457 let d = 0.01 * k as f64;
4458 scene
4459 .set_placement(m, turned(d * 50.0, [d, -d, d]))
4460 .unwrap();
4461 }
4462 scene.end_step().unwrap();
4463 }
4464 12 => {
4465 scene.begin_step();
4467 scene.set_placement(m, at(0.03, 0.02, -0.04)).unwrap();
4468 if let Some(v) = alive_vertex(scene, a, rng) {
4469 scene
4470 .edit(a, |tx| {
4471 let p = tx.mesh().position(v).unwrap();
4472 tx.set_positions(&[v], &[[p[0], p[1], 0.1]])
4473 })
4474 .unwrap();
4475 }
4476 scene.cancel_step().unwrap();
4477 }
4478 13 => {
4479 scene.compact(m).unwrap();
4480 }
4481 14 => {
4482 scene.set_mode(m, random_mode(rng)).unwrap();
4483 }
4484 _ => {
4485 let x = [rng.range(0.0, 1.0) as f32, rng.range(0.0, 1.0) as f32, 0.0];
4489 let Some(p) = alive_vertex(scene, a, rng) else {
4490 return;
4491 };
4492 let Some(q) = alive_vertex(scene, a, rng).filter(|&q| q != p) else {
4493 return;
4494 };
4495 scene
4496 .edit(a, |tx| {
4497 let v = tx.add_vertices(&[x])?;
4498 tx.add_triangles(&[[v[0], p, q]])
4499 })
4500 .unwrap();
4501 scene.undo().unwrap();
4502 }
4503 }
4504 }
4505
4506 fn update_all(scene: &mut Scene) {
4508 for _ in 0..8 {
4509 match scene.update_fields() {
4510 Ok(()) => return,
4511 Err(SceneError::StaleBvh(id)) => scene.update(id).unwrap(),
4512 Err(e) => panic!("update_fields failed: {e}"),
4513 }
4514 }
4515 panic!("update_fields kept failing");
4516 }
4517
4518 fn check_colors_reported(
4519 scene: &Scene,
4520 mesh: MeshId,
4521 before: &[[u8; 4]],
4522 dirty: Option<&Vec<VertexId>>,
4523 at: &str,
4524 ) {
4525 let now = scene.colors(mesh).unwrap().unwrap();
4526 assert_eq!(now.len(), scene.mesh(mesh).unwrap().vertex_slots(), "{at}");
4527 if let Some(dirty) = dirty {
4528 for (v, c) in now.iter().enumerate() {
4529 if before.get(v) != Some(c) {
4530 assert!(
4531 dirty.binary_search(&(v as VertexId)).is_ok(),
4532 "{at}: vertex {v} not reported"
4533 );
4534 }
4535 }
4536 }
4537 }
4538
4539 #[test]
4540 fn field_matches_from_scratch() {
4541 let mut rng = Lcg(42);
4542 for round in 0..24 {
4543 let mut scene = Scene::new(SceneOptions::default());
4544 let a_mode = [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual][round % 3];
4545 let a = scene.add_mesh(sheet(10, 0.1), with_mode(a_mode)).unwrap();
4546 let b = scene
4547 .add_mesh(
4548 cube([0.3, 0.3, -0.2], 0.4),
4549 with_mode(random_mode(&mut rng)),
4550 )
4551 .unwrap();
4552 scene.update(b).unwrap();
4553 let fa = scene
4554 .add_distance_field(DistanceField {
4555 sign: random_sign(&mut rng),
4556 ..field("d", a, b, random_max_distance(&mut rng))
4557 })
4558 .unwrap();
4559 let fb = scene
4560 .add_distance_field(DistanceField {
4561 sign: random_sign(&mut rng),
4562 ..field("d", b, a, random_max_distance(&mut rng))
4563 })
4564 .unwrap();
4565 let (rule_a, rule_b) = (distance_rule(0.2, 0.1), contact_rule(0.1));
4566 for (m, f, rule) in [(a, fa, &rule_a), (b, fb, &rule_b)] {
4567 let rule = rule.clone();
4568 scene
4569 .set_coloring(m, Some(Coloring { field: f, rule }))
4570 .unwrap();
4571 }
4572 update_all(&mut scene);
4573 scene.take_changes();
4574 for step in 0..40 {
4575 let at = format!("round {round} step {step}");
4576 let before_a = scene.colors(a).unwrap().unwrap().to_vec();
4577 let before_b = scene.colors(b).unwrap().unwrap().to_vec();
4578 let values_a = bits_of(scene.color_values(a).unwrap().unwrap());
4579 let values_b = bits_of(scene.color_values(b).unwrap().unwrap());
4580 let ops = if a_mode == UpdateMode::Eager {
4582 1
4583 } else {
4584 1 + rng.below(5)
4585 };
4586 for _ in 0..ops {
4587 random_op(&mut scene, a, b, &mut rng);
4588 for m in [a, b] {
4590 let n = scene.mesh(m).unwrap().vertex_slots();
4591 assert_eq!(scene.colors(m).unwrap().unwrap().len(), n, "{at}");
4592 }
4593 }
4594 update_all(&mut scene);
4595 for m in [a, b] {
4596 scene.update(m).unwrap();
4599 }
4600 assert_matches(&mut scene, fa);
4601 assert_matches(&mut scene, fb);
4602 assert_colors(&scene, a, &rule_a, fa);
4603 assert_colors(&scene, b, &rule_b, fb);
4604 let ch = scene.take_changes();
4605 let dirty = |m: MeshId| {
4606 ch.iter()
4607 .find(|c| c.id == m)
4608 .map_or(Some(Vec::new()), |c| c.colors_dirty.clone())
4609 };
4610 check_colors_reported(&scene, a, &before_a, dirty(a).as_ref(), &at);
4611 check_colors_reported(&scene, b, &before_b, dirty(b).as_ref(), &at);
4612 for (m, f, before) in [(a, fa, &values_a), (b, fb, &values_b)] {
4613 let now = scene.color_values(m).unwrap().unwrap();
4614 assert_eq!(
4615 bits_of(now),
4616 bits_of(scene.field_values(f).unwrap()),
4617 "{at}"
4618 );
4619 check_values_reported(before, now, dirty(m).as_ref(), &at);
4620 }
4621 }
4622 }
4623 }
4624
4625 #[test]
4628 fn colors_cover_slots_after_cancelled_add() {
4629 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
4630 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4631 let rule = contact_rule(0.1);
4632 let coloring = Coloring {
4633 field: f,
4634 rule: rule.clone(),
4635 };
4636 scene.set_coloring(a, Some(coloring)).unwrap();
4637 scene.update_fields().unwrap();
4638 scene.take_changes();
4639 scene
4640 .edit(a, |tx| {
4641 let v = tx.add_vertices(&[[0.5, 0.5, 0.0]])?;
4642 tx.add_triangles(&[[v[0], 0, 1]])
4643 })
4644 .unwrap();
4645 assert_eq!(scene.colors(a).unwrap().unwrap().len(), 122);
4646 assert_eq!(scene.colors(a).unwrap().unwrap()[121], rule.nan);
4647 scene.undo().unwrap();
4648 scene.update_fields().unwrap();
4649 assert!(scene.field_status(f).unwrap().current);
4650 assert_eq!(scene.colors(a).unwrap().unwrap().len(), 122);
4651 assert_colors(&scene, a, &rule, f);
4652 assert_eq!(colors_dirty(&mut scene, a), Some(vec![121]));
4653 }
4654
4655 fn bits_of(values: &[f32]) -> Vec<u32> {
4656 values.iter().map(|x| x.to_bits()).collect()
4657 }
4658
4659 fn check_values_reported(before: &[u32], now: &[f32], dirty: Option<&Vec<VertexId>>, at: &str) {
4662 if let Some(dirty) = dirty {
4663 for (v, x) in now.iter().enumerate() {
4664 if before.get(v) != Some(&x.to_bits()) {
4665 assert!(
4666 dirty.binary_search(&(v as VertexId)).is_ok(),
4667 "{at}: value of vertex {v} not reported"
4668 );
4669 }
4670 }
4671 }
4672 }
4673
4674 #[test]
4677 fn color_values_view_the_coloring_field() {
4678 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
4679 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4680 assert_eq!(scene.color_values(a), Ok(None));
4681 let rule = contact_rule(0.1);
4682 scene
4683 .set_coloring(
4684 a,
4685 Some(Coloring {
4686 field: f,
4687 rule: rule.clone(),
4688 }),
4689 )
4690 .unwrap();
4691 let view = |s: &Scene| bits_of(s.color_values(a).unwrap().unwrap());
4692 assert_eq!(view(&scene), bits_of(scene.field_values(f).unwrap()));
4693 assert_eq!(colors_dirty(&mut scene, a), None);
4694 let before = view(&scene);
4696 scene.set_placement(b, at(0.0, 0.0, 0.03)).unwrap();
4697 assert_eq!(view(&scene), bits_of(scene.field_values(f).unwrap()));
4698 assert_ne!(view(&scene), before);
4699 let dirty = colors_dirty(&mut scene, a);
4700 check_values_reported(
4701 &before,
4702 scene.color_values(a).unwrap().unwrap(),
4703 dirty.as_ref(),
4704 "drag",
4705 );
4706 scene.set_coloring(a, None).unwrap();
4708 assert_eq!(scene.color_values(a), Ok(None));
4709
4710 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
4713 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4714 scene
4715 .set_coloring(
4716 a,
4717 Some(Coloring {
4718 field: f,
4719 rule: rule.clone(),
4720 }),
4721 )
4722 .unwrap();
4723 scene
4724 .edit(a, |tx| {
4725 let v = tx.add_vertices(&[[0.5, 0.5, 0.0]])?;
4726 tx.add_triangles(&[[v[0], 0, 1]])
4727 })
4728 .unwrap();
4729 scene.update_fields().unwrap();
4730 scene.undo().unwrap();
4731 scene
4733 .edit(a, |tx| {
4734 let p = tx.mesh().position(0).unwrap();
4735 tx.set_positions(&[0], &[p])
4736 })
4737 .unwrap();
4738 scene.take_changes();
4739 let before = view(&scene);
4740 assert!(!f32::from_bits(before[121]).is_nan());
4741 scene
4742 .edit(a, |tx| tx.add_vertices(&[[5.0, 5.0, 5.0]]))
4743 .unwrap();
4744 assert!(
4745 scene.mesh(a).unwrap().is_vertex_alive(121),
4746 "the released slot is reused"
4747 );
4748 assert_eq!(scene.mesh(a).unwrap().vertex_slots(), 122);
4749 assert_eq!(view(&scene), bits_of(scene.field_values(f).unwrap()));
4750 let dirty = colors_dirty(&mut scene, a);
4751 check_values_reported(
4752 &before,
4753 scene.color_values(a).unwrap().unwrap(),
4754 dirty.as_ref(),
4755 "reuse",
4756 );
4757 assert_eq!(scene.colors(a).unwrap().unwrap()[121], rule.nan);
4759 }
4760
4761 #[test]
4766 fn color_values_view_a_multi_target_field() {
4767 let rule = contact_rule(0.1);
4768 let colour = |scene: &mut Scene, a: MeshId, f: FieldId| {
4769 scene
4770 .set_coloring(
4771 a,
4772 Some(Coloring {
4773 field: f,
4774 rule: rule.clone(),
4775 }),
4776 )
4777 .unwrap();
4778 };
4779 let view = |s: &Scene, a: MeshId| bits_of(s.color_values(a).unwrap().unwrap());
4780
4781 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
4782 let c = scene
4783 .add_mesh(cube([0.7, 0.7, -0.15], 0.2), MeshOptions::default())
4784 .unwrap();
4785 let f = scene
4786 .add_distance_field(DistanceField {
4787 to: vec![b, c],
4788 ..field("d", a, b, 0.1)
4789 })
4790 .unwrap();
4791 colour(&mut scene, a, f);
4792 assert_eq!(view(&scene, a), bits_of(scene.field_values(f).unwrap()));
4793 let nearest = scene.field_nearest(f).unwrap();
4794 assert!(
4795 nearest.contains(&0) && nearest.contains(&1),
4796 "both targets win vertices"
4797 );
4798 assert_eq!(colors_dirty(&mut scene, a), None);
4799 assert_colors(&scene, a, &rule, f);
4800 for (m, p) in [(b, at(0.0, 0.0, 0.03)), (c, at(-0.02, 0.0, 0.02))] {
4802 let before = view(&scene, a);
4803 scene.set_placement(m, p).unwrap();
4804 assert_eq!(view(&scene, a), bits_of(scene.field_values(f).unwrap()));
4805 assert_ne!(view(&scene, a), before);
4806 let dirty = colors_dirty(&mut scene, a);
4807 check_values_reported(
4808 &before,
4809 scene.color_values(a).unwrap().unwrap(),
4810 dirty.as_ref(),
4811 "move",
4812 );
4813 assert_colors(&scene, a, &rule, f);
4814 }
4815 assert_eq!(shown(&scene, f), min_of_singles(&mut scene, f));
4816
4817 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
4819 let c = scene
4820 .add_mesh(cube([0.7, 0.7, -0.15], 0.2), MeshOptions::default())
4821 .unwrap();
4822 let f = scene
4823 .add_distance_field(DistanceField {
4824 to: vec![b, c],
4825 ..field("d", a, b, 0.1)
4826 })
4827 .unwrap();
4828 colour(&mut scene, a, f);
4829 scene
4830 .edit(a, |tx| {
4831 let v = tx.add_vertices(&[[0.5, 0.5, 0.0]])?;
4832 tx.add_triangles(&[[v[0], 0, 1]])
4833 })
4834 .unwrap();
4835 scene.update_fields().unwrap();
4836 scene.undo().unwrap();
4837 scene
4838 .edit(a, |tx| {
4839 let p = tx.mesh().position(0).unwrap();
4840 tx.set_positions(&[0], &[p])
4841 })
4842 .unwrap();
4843 scene.take_changes();
4844 let before = view(&scene, a);
4845 assert!(!f32::from_bits(before[121]).is_nan());
4846 scene
4847 .edit(a, |tx| tx.add_vertices(&[[5.0, 5.0, 5.0]]))
4848 .unwrap();
4849 assert!(
4850 scene.mesh(a).unwrap().is_vertex_alive(121),
4851 "the released slot is reused"
4852 );
4853 assert_eq!(view(&scene, a), bits_of(scene.field_values(f).unwrap()));
4854 let dirty = colors_dirty(&mut scene, a);
4855 check_values_reported(
4856 &before,
4857 scene.color_values(a).unwrap().unwrap(),
4858 dirty.as_ref(),
4859 "reuse",
4860 );
4861 assert_eq!(scene.colors(a).unwrap().unwrap()[121], rule.nan);
4862 assert_eq!(shown(&scene, f).1[121], u32::MAX);
4863 }
4864
4865 #[test]
4866 fn small_drag_recomputes_only_near_vertices() {
4867 let mut scene = Scene::new(SceneOptions::default());
4868 let a = scene
4870 .add_mesh(sheet(60, 1.0 / 60.0), MeshOptions::default())
4871 .unwrap();
4872 let b = scene
4873 .add_mesh(cube([0.1, 0.1, -0.05], 0.1), MeshOptions::default())
4874 .unwrap();
4875 let f = scene.add_distance_field(field("d", a, b, 0.02)).unwrap();
4876 scene.update_fields().unwrap();
4877 assert_matches(&mut scene, f);
4878 assert!(finite_count(&scene, f) > 0);
4879 counter::take();
4880 scene.set_placement(b, at(0.002, 0.001, 0.0)).unwrap();
4881 scene.update_fields().unwrap();
4882 let recomputed = counter::take();
4883 assert!(recomputed > 0);
4884 assert!(recomputed < 300, "recomputed {recomputed} of 3721");
4885 assert_matches(&mut scene, f);
4886 let g = scene
4888 .add_distance_field(field("all", a, b, f64::INFINITY))
4889 .unwrap();
4890 scene.update_fields().unwrap();
4891 counter::take();
4892 scene.set_placement(b, at(0.003, 0.001, 0.0)).unwrap();
4893 scene.update_fields().unwrap();
4894 assert!(counter::take() >= 3721);
4895 assert_matches(&mut scene, g);
4896 assert!(scene.field_values(g).unwrap().iter().all(|v| v.is_finite()));
4897 }
4898
4899 #[test]
4900 fn reciprocal_fields_share_one_pair_query() {
4901 let mut scene = Scene::new(SceneOptions::default());
4904 let a = scene
4905 .add_mesh(sheet(20, 0.05), MeshOptions::default())
4906 .unwrap();
4907 let b = scene
4908 .add_mesh(cube([0.2, 0.2, -0.05], 0.2), MeshOptions::default())
4909 .unwrap();
4910 let fa = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
4911 let fb = scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
4912 scene.update_fields().unwrap();
4913 scene.take_changes();
4914 counter::take_shared();
4915 scene.set_placement(b, at(0.01, 0.005, 0.0)).unwrap();
4916 scene.update_fields().unwrap();
4917 assert_eq!(counter::take_shared(), 1);
4918 assert_matches(&mut scene, fa);
4919 assert_matches(&mut scene, fb);
4920 }
4921
4922 fn six_sheets(n: u32, gap: f64, mode: UpdateMode) -> (Scene, Vec<MeshId>) {
4926 let mut scene = Scene::new(SceneOptions::default());
4927 let ids: Vec<MeshId> = (0..6)
4928 .map(|k| {
4929 let opts = MeshOptions {
4930 mode,
4931 placement: at(0.0, 0.0, k as f64 * gap),
4932 ..MeshOptions::default()
4933 };
4934 scene.add_mesh(sheet(n, 1.0 / n as f32), opts).unwrap()
4935 })
4936 .collect();
4937 for (i, &a) in ids.iter().enumerate() {
4938 for (j, &b) in ids.iter().enumerate() {
4939 if i != j {
4940 let id = scene
4941 .add_distance_field(field(&format!("to{j}"), a, b, 8.0 * gap))
4942 .unwrap();
4943 if j == (i + 1) % 6 {
4944 let rule = distance_rule(8.0 * gap as f32, 8.0 * gap as f32);
4945 let coloring = Coloring { field: id, rule };
4946 scene.set_coloring(a, Some(coloring)).unwrap();
4947 }
4948 }
4949 }
4950 }
4951 scene.update_fields().unwrap();
4952 (scene, ids)
4953 }
4954
4955 type Drag = Vec<(Vec<crate::MeshChanges>, Vec<Vec<u32>>, Vec<Vec<[u8; 4]>>)>;
4958
4959 fn drag(n: u32, mode: UpdateMode) -> Drag {
4960 let gap = 0.01;
4961 let (mut scene, ids) = six_sheets(n, gap, mode);
4962 scene.take_changes();
4963 let poses = [
4964 turned(0.5, [0.003, 0.001, 0.002]),
4965 turned(-1.0, [0.01, -0.004, 0.025]),
4966 turned(2.0, [0.0, 0.002, 0.051]),
4967 at(0.0, 0.0, 0.0),
4968 ];
4969 let mut out = Vec::new();
4970 for p in poses {
4971 scene.set_placement(ids[0], p).unwrap();
4972 scene.update_fields().unwrap();
4973 let changes = scene.take_changes();
4974 let ids_f: Vec<FieldId> = scene.fields.iter().map(|f| f.id).collect();
4975 let values = ids_f
4976 .iter()
4977 .map(|&f| bits(scene.field_values(f).unwrap()))
4978 .collect();
4979 let colors = ids
4980 .iter()
4981 .map(|&m| scene.colors(m).unwrap().unwrap().to_vec())
4982 .collect();
4983 out.push((changes, values, colors));
4984 }
4985 out
4986 }
4987
4988 #[test]
4989 fn parallel_field_updates_report_in_order() {
4990 use fast_mesh_store::par;
4991 for mode in [UpdateMode::Eager, UpdateMode::Lazy] {
4993 par::set_force_sequential(true);
4994 let reference = drag(24, mode);
4995 par::set_force_sequential(false);
4996 assert!(
4997 reference.iter().all(|(c, _, _)| c.len() == 6),
4998 "{mode:?}: every mesh reports"
4999 );
5000 if !par::enabled() {
5001 continue;
5003 }
5004 for k in [1, 2, 8] {
5005 let got = par::with_threads(k, || drag(24, mode));
5006 assert!(got == reference, "{mode:?}: {k} threads");
5007 }
5008 counter::take_parallel();
5009 assert!(drag(24, mode) == reference, "{mode:?}: global pool");
5010 assert!(counter::take_parallel() > 0, "{mode:?}: ran sequentially");
5011 }
5012 }
5013
5014 #[test]
5015 fn small_inputs_stay_sequential() {
5016 use fast_mesh_store::par;
5017 let before = par::parallel_entries();
5018 counter::take_parallel();
5019 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5020 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5021 scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
5022 for k in 0..4 {
5023 scene
5024 .set_placement(b, at(0.01 * k as f64, 0.0, 0.0))
5025 .unwrap();
5026 }
5027 scene.update_fields().unwrap();
5028 assert_matches(&mut scene, f);
5029 assert_eq!(par::parallel_entries(), before);
5030 assert_eq!(counter::take_parallel(), 0);
5031 }
5032
5033 #[test]
5034 fn near_work_counts_only_meshes_in_reach() {
5035 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
5036 scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5037 scene.update_fields().unwrap();
5038 let tris = scene.mesh(a).unwrap().triangle_count();
5039 assert!(scene.mesh(a).unwrap().vertex_count() < tris);
5042 for (x, in_reach) in [(0.01, true), (100.0, false)] {
5043 scene.set_placement(b, at(x, 0.0, 0.0)).unwrap();
5044 let job = scene.prepare_field(0).unwrap();
5045 assert!(job.near_margins[0].is_some());
5046 assert_eq!(job.work >= tris, in_reach, "x = {x}");
5047 }
5048 }
5049
5050 #[test]
5051 fn leaving_contact_restores_far() {
5052 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5053 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5054 assert_matches(&mut scene, f);
5055 let touching = scene.field_values(f).unwrap().to_vec();
5056 assert!(touching.iter().any(|v| *v < 0.0));
5057 assert!(touching.iter().any(|v| *v > 0.0 && v.is_finite()));
5058 scene.set_placement(b, at(10.0, 0.0, 0.0)).unwrap();
5059 assert_matches(&mut scene, f);
5060 assert!(
5061 scene
5062 .field_values(f)
5063 .unwrap()
5064 .iter()
5065 .all(|v| *v == f32::INFINITY)
5066 );
5067 scene.set_placement(b, Placement::IDENTITY).unwrap();
5068 assert_matches(&mut scene, f);
5069 assert_eq!(bits(scene.field_values(f).unwrap()), bits(&touching));
5070 }
5071
5072 #[test]
5073 fn undo_keeps_field_consistent() {
5074 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5075 let f = scene.add_distance_field(field("d", a, b, 0.15)).unwrap();
5076 let g = scene.add_distance_field(field("d", b, a, 0.15)).unwrap();
5077 let initial = bits(scene.field_values(f).unwrap());
5078 scene.edit(a, |tx| tx.remove_triangles(&[0])).unwrap();
5080 assert!(scene.field_values(f).unwrap()[0].is_nan());
5081 scene.edit(a, |tx| tx.remove_vertices(&[0])).unwrap();
5082 scene
5083 .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.3]]))
5084 .unwrap();
5085 scene
5086 .edit(a, |tx| {
5087 let v = tx.add_vertices(&[[0.45, 0.45, -0.05]])?;
5088 tx.add_triangles(&[[v[0], 44, 45]])
5089 })
5090 .unwrap();
5091 scene.set_placement(b, at(0.05, 0.0, 0.02)).unwrap();
5092 for _ in 0..5 {
5093 assert_matches(&mut scene, f);
5094 assert_matches(&mut scene, g);
5095 scene.undo().unwrap();
5096 }
5097 assert_matches(&mut scene, f);
5098 assert_matches(&mut scene, g);
5099 let back = scene.field_values(f).unwrap();
5101 assert_eq!(bits(&back[..initial.len()]), initial);
5102 assert!(back[initial.len()..].iter().all(|v| v.is_nan()));
5103 for _ in 0..5 {
5104 scene.redo().unwrap();
5105 assert_matches(&mut scene, f);
5106 assert_matches(&mut scene, g);
5107 }
5108 scene.begin_step();
5110 scene.set_placement(b, at(0.0, 0.0, 0.5)).unwrap();
5111 scene.cancel_step().unwrap();
5112 assert_matches(&mut scene, f);
5113 }
5114
5115 #[test]
5116 fn manual_bvh_blocks_field_update() {
5117 let mut scene = Scene::new(SceneOptions::default());
5118 let a = scene
5119 .add_mesh(sheet(10, 0.1), MeshOptions::default())
5120 .unwrap();
5121 let b = scene
5122 .add_mesh(cube([0.3, 0.3, -0.2], 0.4), with_mode(UpdateMode::Manual))
5123 .unwrap();
5124 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5125 assert_matches(&mut scene, f);
5126 scene
5127 .edit(b, |tx| tx.set_positions(&[7], &[[0.8, 0.8, 0.3]]))
5128 .unwrap();
5129 assert!(!scene.field_status(f).unwrap().current);
5131 assert_eq!(scene.update_fields(), Err(SceneError::StaleBvh(b)));
5132 assert_eq!(scene.flush(), Err(SceneError::StaleBvh(b)));
5133 assert!(!scene.field_status(f).unwrap().current);
5134 scene.update(b).unwrap();
5135 scene.update_fields().unwrap();
5136 assert_matches(&mut scene, f);
5137 scene
5139 .edit(b, |tx| tx.set_positions(&[7], &[[0.7, 0.7, 0.2]]))
5140 .unwrap();
5141 assert_eq!(
5142 scene.add_distance_field(field("e", a, b, 0.1)),
5143 Err(SceneError::StaleBvh(b))
5144 );
5145 }
5146
5147 #[test]
5148 fn eager_vs_lazy_field_scheduling() {
5149 for mode in [UpdateMode::Eager, UpdateMode::Lazy, UpdateMode::Manual] {
5150 let (mut scene, a, b) = sheet_and_cube(mode);
5151 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5152 assert_eq!(
5153 scene.field_status(f).unwrap().current,
5154 mode == UpdateMode::Eager
5155 );
5156 scene.update_fields().unwrap();
5157 scene.set_placement(b, at(0.05, 0.0, 0.0)).unwrap();
5158 assert_eq!(
5159 scene.field_status(f).unwrap().current,
5160 mode == UpdateMode::Eager
5161 );
5162 scene.flush().unwrap();
5163 assert_eq!(
5164 scene.field_status(f).unwrap().current,
5165 mode != UpdateMode::Manual
5166 );
5167 scene.update_fields().unwrap();
5168 assert_matches(&mut scene, f);
5169 scene
5171 .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.05]]))
5172 .unwrap();
5173 assert_eq!(
5174 scene.field_status(f).unwrap().current,
5175 mode == UpdateMode::Eager
5176 );
5177 if mode == UpdateMode::Manual {
5178 scene.update(a).unwrap();
5179 }
5180 let c = scene
5182 .add_mesh(sheet(1, 1.0), MeshOptions::default())
5183 .unwrap();
5184 scene.update_fields().unwrap();
5185 scene.set_placement(c, at(3.0, 0.0, 0.0)).unwrap();
5186 assert!(scene.field_status(f).unwrap().current);
5187 assert_matches(&mut scene, f);
5188 }
5189 }
5190
5191 #[test]
5192 fn colors_follow_values_and_report_dirty() {
5193 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5194 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5195 let s = scene.subscribe();
5196 scene.take_changes();
5197 assert_eq!(scene.colors(a), Ok(None));
5198 let rule = contact_rule(0.1);
5199 scene
5200 .set_coloring(
5201 a,
5202 Some(Coloring {
5203 field: f,
5204 rule: rule.clone(),
5205 }),
5206 )
5207 .unwrap();
5208 assert_colors(&scene, a, &rule, f);
5209 assert_eq!(colors_dirty(&mut scene, a), None);
5210 let before = scene.field_values(f).unwrap().to_vec();
5211 scene.set_placement(b, at(0.0, 0.0, 0.03)).unwrap();
5212 assert_colors(&scene, a, &rule, f);
5213 let after = scene.field_values(f).unwrap().to_vec();
5214 let changed: Vec<VertexId> = (0..after.len() as VertexId)
5215 .filter(|&v| before[v as usize].to_bits() != after[v as usize].to_bits())
5216 .collect();
5217 assert!(!changed.is_empty());
5218 let ch = scene.take_changes();
5219 let mine = ch.iter().find(|c| c.id == a).unwrap();
5220 assert_eq!(mine.colors_dirty.as_deref(), Some(&changed[..]));
5221 assert_eq!(mine.fields_changed, [f]);
5222 let theirs = scene.take_changes_for(s).unwrap();
5223 let theirs = theirs.iter().find(|c| c.id == a).unwrap();
5224 assert_eq!(theirs.colors_dirty, None, "the colouring was set since");
5225 assert_eq!(theirs.fields_changed, [f]);
5226 scene.update_fields().unwrap();
5228 assert!(scene.take_changes().is_empty());
5229 let other = distance_rule(0.1, 0.05);
5231 scene
5232 .set_coloring(
5233 a,
5234 Some(Coloring {
5235 field: f,
5236 rule: other.clone(),
5237 }),
5238 )
5239 .unwrap();
5240 assert_colors(&scene, a, &other, f);
5241 assert_eq!(colors_dirty(&mut scene, a), None);
5242 let mut bad = other.clone();
5244 bad.range = (1.0, -1.0);
5245 assert!(matches!(
5246 scene.set_coloring(
5247 a,
5248 Some(Coloring {
5249 field: f,
5250 rule: bad
5251 })
5252 ),
5253 Err(SceneError::InvalidInput(_))
5254 ));
5255 assert!(matches!(
5256 scene.set_coloring(
5257 b,
5258 Some(Coloring {
5259 field: f,
5260 rule: other.clone()
5261 })
5262 ),
5263 Err(SceneError::InvalidInput(_))
5264 ));
5265 assert_colors(&scene, a, &other, f);
5266 assert!(scene.take_changes().is_empty());
5267 scene.set_coloring(a, None).unwrap();
5268 assert_eq!(scene.colors(a), Ok(None));
5269 assert_eq!(colors_dirty(&mut scene, a), None);
5270 }
5271
5272 #[test]
5273 fn late_subscriber_gets_full_colour_report() {
5274 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5277 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5278 let rule = contact_rule(0.1);
5279 scene
5280 .set_coloring(
5281 a,
5282 Some(Coloring {
5283 field: f,
5284 rule: rule.clone(),
5285 }),
5286 )
5287 .unwrap();
5288 scene.take_changes();
5289 let s = scene.subscribe();
5290 let changes = scene.take_changes_for(s).unwrap();
5291 let mine = changes.iter().find(|c| c.id == a).unwrap();
5292 assert_eq!(mine.colors_dirty, None);
5293 assert!(changes.iter().find(|c| c.id == b).is_none());
5295 }
5296
5297 #[test]
5298 fn coloring_getter_and_field_list() {
5299 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5300 assert_eq!(scene.coloring(a).unwrap(), None);
5301 assert_eq!(scene.fields_on(a).unwrap(), vec![]);
5302 let f1 = scene.add_distance_field(field("d1", a, b, 0.1)).unwrap();
5303 let f2 = scene.add_distance_field(field("d2", a, b, 0.2)).unwrap();
5304 assert_eq!(scene.fields_on(a).unwrap(), vec![f1, f2]);
5305 assert_eq!(scene.fields_on(b).unwrap(), vec![]);
5306 let rule = contact_rule(0.1);
5307 let coloring = Coloring {
5308 field: f1,
5309 rule: rule.clone(),
5310 };
5311 scene.set_coloring(a, Some(coloring.clone())).unwrap();
5312 assert_eq!(scene.coloring(a).unwrap(), Some(&coloring));
5313 scene.set_coloring(a, None).unwrap();
5314 assert_eq!(scene.coloring(a).unwrap(), None);
5315 scene.remove_field(f1).unwrap();
5316 assert_eq!(scene.fields_on(a).unwrap(), vec![f2]);
5317 }
5318
5319 #[test]
5320 fn reconfigure_field_keeps_id_and_colouring() {
5321 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5322 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5323 let rule = contact_rule(0.1);
5324 scene
5325 .set_coloring(
5326 a,
5327 Some(Coloring {
5328 field: f,
5329 rule: rule.clone(),
5330 }),
5331 )
5332 .unwrap();
5333 scene.take_changes();
5334 let before = scene.field_values(f).unwrap().to_vec();
5335 scene.reconfigure_field(f, 0.5, SignMode::Auto).unwrap();
5338 assert_eq!(scene.fields_on(a).unwrap(), vec![f]);
5339 assert!(scene.coloring(a).unwrap().is_some());
5340 assert!(scene.field_status(f).unwrap().current);
5342 let after = scene.field_values(f).unwrap().to_vec();
5343 assert_ne!(before, after);
5344 let finite_before = before.iter().filter(|v| v.is_finite()).count();
5345 let finite_after = after.iter().filter(|v| v.is_finite()).count();
5346 assert!(finite_after >= finite_before);
5347 assert!(matches!(
5349 scene.reconfigure_field(f, f64::NAN, SignMode::Auto),
5350 Err(SceneError::InvalidInput(_))
5351 ));
5352 assert!(scene.field_status(f).unwrap().current);
5353 let values = scene.field_values(f).unwrap().to_vec();
5355 scene.reconfigure_field(f, 0.5, SignMode::Auto).unwrap();
5356 assert!(scene.field_status(f).unwrap().current);
5357 assert_eq!(scene.field_values(f).unwrap(), &values[..]);
5358 }
5359
5360 #[test]
5361 fn one_sided_and_two_sided_coloring() {
5362 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5363 let d = 0.1;
5364 let fa = scene.add_distance_field(field("d", a, b, 0.2)).unwrap();
5365 let rule = contact_rule(d as f32);
5366 scene
5367 .set_coloring(
5368 a,
5369 Some(Coloring {
5370 field: fa,
5371 rule: rule.clone(),
5372 }),
5373 )
5374 .unwrap();
5375 assert_eq!(scene.colors(b), Ok(None));
5376 assert_colors(&scene, a, &rule, fa);
5377 let values = scene.field_values(fa).unwrap().to_vec();
5379 let colors = scene.colors(a).unwrap().unwrap().to_vec();
5380 for (v, c) in values.iter().zip(&colors) {
5381 if *v > 0.0 {
5382 assert_eq!(*c, rule.base);
5383 }
5384 }
5385 assert!(values.iter().any(|v| *v < 0.0));
5386 let fb = scene.add_distance_field(field("d", b, a, 0.2)).unwrap();
5388 let other = distance_rule(0.1, 0.1);
5389 scene
5390 .set_coloring(
5391 b,
5392 Some(Coloring {
5393 field: fb,
5394 rule: other.clone(),
5395 }),
5396 )
5397 .unwrap();
5398 scene.set_placement(b, at(0.02, 0.0, 0.05)).unwrap();
5399 assert_colors(&scene, a, &rule, fa);
5400 assert_colors(&scene, b, &other, fb);
5401 assert_matches(&mut scene, fa);
5402 assert_matches(&mut scene, fb);
5403 }
5404
5405 #[test]
5406 fn compact_recomputes_everything() {
5407 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Lazy);
5408 let f = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5409 let rule = contact_rule(0.1);
5410 scene
5411 .set_coloring(
5412 a,
5413 Some(Coloring {
5414 field: f,
5415 rule: rule.clone(),
5416 }),
5417 )
5418 .unwrap();
5419 scene.edit(a, |tx| tx.remove_triangles(&[0])).unwrap();
5420 scene.edit(a, |tx| tx.remove_vertices(&[0])).unwrap();
5421 scene.update_fields().unwrap();
5422 scene.take_changes();
5423 scene.compact(a).unwrap();
5424 let ch = scene.take_changes();
5425 let mine = ch.iter().find(|c| c.id == a).unwrap();
5426 assert!(mine.changes.is_none());
5427 assert_eq!(mine.colors_dirty, None);
5428 assert_eq!(scene.mesh(a).unwrap().vertex_slots(), 120);
5430 assert_colors(&scene, a, &rule, f);
5431 assert!(!scene.field_status(f).unwrap().current);
5432 counter::take();
5433 scene.update_fields().unwrap();
5434 assert_eq!(counter::take(), 120);
5435 assert_matches(&mut scene, f);
5436 assert_colors(&scene, a, &rule, f);
5437 scene.compact(b).unwrap();
5439 scene.update_fields().unwrap();
5440 assert_eq!(counter::take(), 120);
5441 assert_matches(&mut scene, f);
5442 }
5443
5444 #[test]
5445 fn removing_a_mesh_removes_its_fields() {
5446 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5447 let fa = scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5448 let fb = scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
5449 scene
5450 .set_coloring(
5451 a,
5452 Some(Coloring {
5453 field: fa,
5454 rule: contact_rule(0.1),
5455 }),
5456 )
5457 .unwrap();
5458 scene.take_changes();
5459 scene.remove_mesh(b).unwrap();
5460 assert_eq!(scene.field_values(fa), Err(SceneError::UnknownField));
5461 assert_eq!(scene.field_values(fb), Err(SceneError::UnknownField));
5462 assert_eq!(scene.field_status(fa), Err(SceneError::UnknownField));
5463 assert!(scene.mesh(a).unwrap().channel("field:d").is_none());
5464 assert_eq!(scene.colors(a), Ok(None));
5465 let ch = scene.take_changes();
5466 let mine = ch.iter().find(|c| c.id == a).unwrap();
5467 assert_eq!(mine.fields_changed, [fa]);
5468 assert_eq!(mine.colors_dirty, None);
5469 assert_eq!(scene.remove_field(fa), Err(SceneError::UnknownField));
5470 let c = scene
5472 .add_mesh(cube([0.3, 0.3, -0.2], 0.4), MeshOptions::default())
5473 .unwrap();
5474 let g = scene.add_distance_field(field("d", a, c, 0.1)).unwrap();
5475 assert_eq!(scene.field_id(g.raw()), Some(g));
5476 assert_eq!(scene.field_id(fa.raw()), None);
5477 scene.remove_field(g).unwrap();
5478 assert_eq!(scene.field_id(g.raw()), None);
5479 assert_eq!(scene.field_values(g), Err(SceneError::UnknownField));
5480 assert!(scene.mesh(a).unwrap().channel("field:d").is_none());
5481 scene.add_distance_field(field("d", a, c, 0.1)).unwrap();
5482 scene
5484 .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.05]]))
5485 .unwrap();
5486 scene.update_fields().unwrap();
5487 }
5488
5489 type Seen = (
5492 Vec<Vec<u32>>,
5493 Vec<Option<Vec<[u8; 4]>>>,
5494 Vec<crate::MeshChanges>,
5495 );
5496
5497 fn seen(scene: &mut Scene, meshes: [MeshId; 2]) -> Seen {
5498 let ids: Vec<FieldId> = scene.fields.iter().map(|f| f.id).collect();
5499 let values = ids
5500 .iter()
5501 .map(|&f| bits(scene.field_values(f).unwrap()))
5502 .collect();
5503 let colors = meshes
5504 .iter()
5505 .map(|&m| scene.colors(m).unwrap().map(<[_]>::to_vec))
5506 .collect();
5507 (values, colors, scene.take_changes())
5508 }
5509
5510 fn twin_scene(rng: &mut Lcg) -> (Scene, MeshId, MeshId) {
5513 let mut scene = Scene::new(SceneOptions::default());
5514 let a = scene
5515 .add_mesh(sheet(10, 0.1), with_mode(random_mode(rng)))
5516 .unwrap();
5517 let b = scene
5518 .add_mesh(cube([0.3, 0.3, -0.2], 0.4), with_mode(random_mode(rng)))
5519 .unwrap();
5520 for (on, to, rule) in [(a, b, distance_rule(0.2, 0.1)), (b, a, contact_rule(0.1))] {
5521 let id = scene
5522 .add_distance_field(DistanceField {
5523 sign: random_sign(rng),
5524 ..field("d", on, to, random_max_distance(rng))
5525 })
5526 .unwrap();
5527 scene
5528 .set_coloring(on, Some(Coloring { field: id, rule }))
5529 .unwrap();
5530 }
5531 (scene, a, b)
5532 }
5533
5534 fn assert_twins_agree(
5541 seed: u64,
5542 rounds: usize,
5543 on: usize,
5544 off: usize,
5545 ) -> Vec<([usize; 2], bool)> {
5546 let mut rng = Lcg(seed);
5547 let mut stats = Vec::new();
5548 for round in 0..rounds {
5549 let state = rng.0;
5550 let mut aborted = [0; 2];
5551 let mut twins = [(on, Lcg(state)), (off, Lcg(state))].map(|(limit, mut r)| {
5552 counter::set_near_limit(Some(limit));
5553 let (scene, a, b) = twin_scene(&mut r);
5554 (limit, r, scene, a, b)
5555 });
5556 rng.0 = twins[0].1.0;
5557 let finite = twins[0]
5558 .2
5559 .fields
5560 .iter()
5561 .any(|f| f.desc.max_distance.is_finite());
5562 counter::take_aborted();
5563 for step in 0..30 {
5564 let ops = 1 + rng.below(4);
5565 let mut out = Vec::new();
5566 for (k, (limit, r, scene, a, b)) in twins.iter_mut().enumerate() {
5567 counter::set_near_limit(Some(*limit));
5568 for _ in 0..ops {
5569 random_op(scene, *a, *b, r);
5570 }
5571 update_all(scene);
5572 out.push(seen(scene, [*a, *b]));
5573 aborted[k] += counter::take_aborted();
5574 }
5575 assert_eq!(out[0], out[1], "round {round} step {step}");
5576 }
5577 stats.push((aborted, finite));
5578 }
5579 counter::set_near_limit(None);
5580 stats
5581 }
5582
5583 #[test]
5584 fn near_limit_matches_near_path() {
5585 let stats = assert_twins_agree(0xb0b, 12, 0, usize::MAX);
5588 let ran: Vec<bool> = stats.iter().map(|&([on, _], _)| on > 0).collect();
5591 let searching: Vec<bool> = stats.iter().map(|&(_, finite)| finite).collect();
5592 assert_eq!(ran, searching);
5593 let off: Vec<usize> = stats.iter().map(|&([_, off], _)| off).collect();
5594 assert_eq!(off, vec![0; stats.len()]);
5595 assert!(
5596 searching.iter().filter(|&&f| f).count() >= 10,
5597 "{searching:?}"
5598 );
5599 }
5600
5601 #[test]
5602 fn wide_reach_recomputes_every_vertex() {
5603 let mut scene = Scene::new(SceneOptions::default());
5604 let a = scene
5606 .add_mesh(sheet(60, 1.0 / 60.0), MeshOptions::default())
5607 .unwrap();
5608 let b = scene
5609 .add_mesh(cube([0.4, 0.4, -0.05], 0.1), MeshOptions::default())
5610 .unwrap();
5611 let f = scene.add_distance_field(field("d", a, b, 2.0)).unwrap();
5612 scene.update_fields().unwrap();
5613 counter::take();
5614 scene.set_placement(b, at(0.002, 0.001, 0.0)).unwrap();
5615 scene.update_fields().unwrap();
5616 assert_eq!(counter::take(), 3721);
5617 assert_matches(&mut scene, f);
5618 }
5619
5620 #[test]
5621 fn invalid_field_descs_rejected() {
5622 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5623 let bad = |e| matches!(e, Err(SceneError::InvalidInput(_)));
5624 assert!(bad(scene.add_distance_field(field("d", a, a, 0.1))));
5625 assert!(bad(scene.add_distance_field(field("d", a, b, -1.0))));
5626 assert!(bad(scene.add_distance_field(field("d", a, b, f64::NAN))));
5627 scene.add_distance_field(field("d", a, b, 0.1)).unwrap();
5628 assert!(bad(scene.add_distance_field(field("d", a, b, 0.2))));
5629 scene.add_distance_field(field("d", b, a, 0.1)).unwrap();
5631 let gone = scene
5632 .add_mesh(sheet(1, 1.0), MeshOptions::default())
5633 .unwrap();
5634 scene.remove_mesh(gone).unwrap();
5635 assert_eq!(
5636 scene.add_distance_field(field("e", a, gone, 0.1)),
5637 Err(SceneError::UnknownMesh)
5638 );
5639 assert_eq!(
5640 scene.add_distance_field(field("e", gone, a, 0.1)),
5641 Err(SceneError::UnknownMesh)
5642 );
5643 assert_eq!(scene.fields.len(), 2);
5644 assert!(matches!(
5646 scene.derived_channel_mut(a, "field:d"),
5647 Err(SceneError::InvalidInput(_))
5648 ));
5649 }
5650
5651 fn step_all(scene: &mut Scene, budget: usize) -> usize {
5655 let mut pending = 0;
5656 for _ in 0..1_000_000 {
5657 match scene.field_step(FieldBudget::Points(budget)) {
5658 Ok(StepState::Idle) => {
5659 assert!(!scene.field_step_pending());
5660 return pending;
5661 }
5662 Ok(StepState::Pending) => {
5663 assert!(scene.field_step_pending());
5664 pending += 1;
5665 }
5666 Ok(StepState::Done) => assert!(!scene.field_step_pending()),
5667 Err(SceneError::StaleBvh(id)) => {
5668 assert!(!scene.field_step_pending());
5669 scene.update(id).unwrap();
5670 }
5671 Err(e) => panic!("field_step failed: {e}"),
5672 }
5673 }
5674 panic!("field_step never went idle");
5675 }
5676
5677 fn step_partway(scene: &mut Scene, points: usize) -> usize {
5681 let state = scene.field_step(FieldBudget::Points(1)).unwrap();
5682 assert_eq!(state, StepState::Pending);
5683 let mut done = 1;
5684 while done < points && scene.field_step_left() > 1 {
5685 let state = scene.field_step(FieldBudget::Points(1)).unwrap();
5686 assert_eq!(state, StepState::Pending);
5687 done += 1;
5688 }
5689 assert!(scene.field_step_pending());
5690 done
5691 }
5692
5693 #[test]
5694 fn stepped_matches_one_shot() {
5695 let budgets = [1, 37, usize::MAX];
5699 let mut rng = Lcg(0x57e9);
5700 let mut spread = [0usize; 3];
5701 for round in 0..10 {
5702 let state = rng.0;
5703 let mut twins: Vec<_> = (0..4)
5704 .map(|_| {
5705 let mut r = Lcg(state);
5706 let (mut scene, a, b) = twin_scene(&mut r);
5707 scene.set_defer_fields(true);
5708 scene.set_mode(a, UpdateMode::Eager).unwrap();
5709 scene.set_mode(b, UpdateMode::Eager).unwrap();
5710 update_all(&mut scene);
5711 scene.take_changes();
5712 (r, scene, a, b)
5713 })
5714 .collect();
5715 rng.0 = twins[0].0.0;
5716 for step in 0..30 {
5717 let ops = 1 + rng.below(4);
5718 let mut out = Vec::new();
5719 for (k, (r, scene, a, b)) in twins.iter_mut().enumerate() {
5720 for _ in 0..ops {
5721 random_op(scene, *a, *b, r);
5722 }
5723 if let Some(&budget) = budgets.get(k) {
5724 spread[k] += step_all(scene, budget);
5725 }
5726 update_all(scene);
5727 out.push(seen(scene, [*a, *b]));
5728 }
5729 for k in 0..3 {
5730 assert_eq!(
5731 out[k], out[3],
5732 "round {round} step {step} budget {}",
5733 budgets[k]
5734 );
5735 }
5736 }
5737 }
5738 assert!(spread[0] > spread[1] && spread[1] > 0, "{spread:?}");
5740 assert_eq!(spread[2], 0);
5741 }
5742
5743 fn step_all_abandoning(scene: &mut Scene, budget: usize, rng: &mut Lcg) -> usize {
5747 let mut abandoned = 0;
5748 for _ in 0..1_000_000 {
5749 match scene.field_step(FieldBudget::Points(budget)) {
5750 Ok(StepState::Idle) => return abandoned,
5751 Ok(StepState::Pending) => {
5752 if abandoned < 2 && rng.below(5) == 0 {
5753 scene.abandon_field_step();
5754 assert!(!scene.field_step_pending());
5755 abandoned += 1;
5756 }
5757 }
5758 Ok(StepState::Done) => {}
5759 Err(SceneError::StaleBvh(id)) => scene.update(id).unwrap(),
5760 Err(e) => panic!("field_step failed: {e}"),
5761 }
5762 }
5763 panic!("field_step never went idle");
5764 }
5765
5766 #[test]
5767 fn stepped_with_abandons_matches_one_shot() {
5768 let budgets = [1, 37];
5771 let mut rng = Lcg(0xab4d);
5772 let mut abandoned = [0usize; 2];
5773 for round in 0..8 {
5774 let state = rng.0;
5775 let mut twins: Vec<_> = (0..3)
5776 .map(|k| {
5777 let mut r = Lcg(state);
5778 let (mut scene, a, b) = twin_scene(&mut r);
5779 scene.set_defer_fields(true);
5780 scene.set_mode(a, UpdateMode::Eager).unwrap();
5781 scene.set_mode(b, UpdateMode::Eager).unwrap();
5782 update_all(&mut scene);
5783 scene.take_changes();
5784 (r, Lcg(state ^ (k as u64 + 1)), scene, a, b)
5787 })
5788 .collect();
5789 rng.0 = twins[0].0.0;
5790 for step in 0..30 {
5791 let ops = 1 + rng.below(4);
5792 let mut out = Vec::new();
5793 for (k, (r, when, scene, a, b)) in twins.iter_mut().enumerate() {
5794 for _ in 0..ops {
5795 random_op(scene, *a, *b, r);
5796 }
5797 if let Some(&budget) = budgets.get(k) {
5798 abandoned[k] += step_all_abandoning(scene, budget, when);
5799 }
5800 update_all(scene);
5801 out.push(seen(scene, [*a, *b]));
5802 }
5803 for k in 0..2 {
5804 assert_eq!(
5805 out[k], out[2],
5806 "round {round} step {step} budget {}",
5807 budgets[k]
5808 );
5809 }
5810 }
5811 }
5812 assert!(abandoned.iter().all(|&n| n >= 20), "{abandoned:?}");
5813 }
5814
5815 #[test]
5816 fn step_error_stays_with_its_field() {
5817 let (mut scene, a, b, [fa, fb]) = deferred_pair(0.15);
5818 let (mut twin, ..) = deferred_pair(0.15);
5819 for s in [&mut scene, &mut twin] {
5820 s.set_placement(b, turned(6.0, [0.03, 0.02, 0.01])).unwrap();
5821 s.take_changes();
5822 }
5823 let before = bits(scene.field_values(fa).unwrap());
5824 counter::set_failing(Some(fa));
5826 let mut states = Vec::new();
5827 let last = loop {
5828 match scene.field_step(FieldBudget::Points(7)) {
5829 Ok(StepState::Pending) => states.push(StepState::Pending),
5830 other => break other,
5831 }
5832 };
5833 counter::set_failing(None);
5834 assert!(!states.is_empty());
5835 assert_eq!(last, Err(SceneError::Store(StoreError::OutOfMemory)));
5836 assert!(!scene.field_step_pending());
5837 assert_eq!(bits(scene.field_values(fa).unwrap()), before);
5839 assert!(!scene.field_status(fa).unwrap().current);
5840 assert!(scene.field_status(fb).unwrap().current);
5841 twin.update_fields().unwrap();
5842 assert_eq!(
5843 bits(scene.field_values(fb).unwrap()),
5844 bits(twin.field_values(fb).unwrap())
5845 );
5846 let ch = scene.take_changes();
5847 assert!(ch.iter().all(|c| c.id != a), "A's field reported");
5848 assert!(step_all(&mut scene, 7) > 0);
5850 twin.take_changes();
5851 assert_eq!(seen_values(&scene, [a, b]), seen_values(&twin, [a, b]));
5852 for s in [&mut scene, &mut twin] {
5855 s.set_placement(b, turned(-4.0, [0.01, 0.0, 0.02])).unwrap();
5856 }
5857 counter::set_failing(Some(fb));
5858 assert_eq!(
5859 scene.field_step(FieldBudget::Points(usize::MAX)),
5860 Err(SceneError::Store(StoreError::OutOfMemory))
5861 );
5862 counter::set_failing(None);
5863 assert!(scene.field_status(fa).unwrap().current);
5864 assert!(!scene.field_status(fb).unwrap().current);
5865 step_all(&mut scene, usize::MAX);
5866 twin.update_fields().unwrap();
5867 assert_eq!(seen(&mut scene, [a, b]).0, seen(&mut twin, [a, b]).0);
5868 }
5869
5870 #[test]
5871 fn stepping_runs_parallel_paths() {
5872 use fast_mesh_store::par;
5873 let make = || {
5877 let mut scene = Scene::new(SceneOptions::default());
5878 let a = scene
5879 .add_mesh(sheet(80, 1.0 / 80.0), MeshOptions::default())
5880 .unwrap();
5881 let opts = MeshOptions {
5882 placement: at(0.0, 0.0, 0.05),
5883 ..MeshOptions::default()
5884 };
5885 let b = scene.add_mesh(sheet(80, 1.0 / 80.0), opts).unwrap();
5886 for (on, to) in [(a, b), (b, a)] {
5887 let id = scene
5888 .add_distance_field(field("d", on, to, f64::INFINITY))
5889 .unwrap();
5890 let rule = distance_rule(0.1, 0.1);
5891 scene
5892 .set_coloring(on, Some(Coloring { field: id, rule }))
5893 .unwrap();
5894 }
5895 scene.set_defer_fields(true);
5896 scene
5897 .set_placement(b, turned(3.0, [0.01, 0.0, 0.04]))
5898 .unwrap();
5899 scene.take_changes();
5900 (scene, a, b)
5901 };
5902 let (mut scene, a, b) = make();
5903 let (mut twin, ..) = make();
5904 counter::take_parallel();
5905 let entries = par::parallel_entries();
5906 assert_eq!(
5907 scene.field_step(FieldBudget::Points(1)),
5908 Ok(StepState::Pending)
5909 );
5910 assert_eq!(counter::take_parallel(), 0);
5914 let opened = par::parallel_entries();
5915 assert_eq!(opened, entries);
5916 assert_eq!(scene.field_step_left(), 6561);
5917 assert_eq!(scene.field_step_stage(), Some(Stage::Values));
5918 assert_eq!(
5919 scene.field_step(FieldBudget::Points(2048)),
5920 Ok(StepState::Pending)
5921 );
5922 assert_eq!(scene.field_step_left(), 6561 - 2048);
5923 let chunked = par::parallel_entries() - opened;
5924 if par::enabled() {
5925 assert!(chunked > 0, "values computed sequentially");
5926 } else {
5927 assert_eq!(chunked, 0);
5928 }
5929 assert!(step_all(&mut scene, 2048) > 0);
5930 twin.update_fields().unwrap();
5931 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
5932 }
5933
5934 fn deferred_pair(max_distance: f64) -> (Scene, MeshId, MeshId, [FieldId; 2]) {
5937 deferred_pair_signed(max_distance, SignMode::Auto)
5938 }
5939
5940 fn deferred_pair_signed(
5943 max_distance: f64,
5944 sign: SignMode,
5945 ) -> (Scene, MeshId, MeshId, [FieldId; 2]) {
5946 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
5947 let signed = |mut d: DistanceField| {
5948 d.sign = sign;
5949 d
5950 };
5951 let fa = scene
5952 .add_distance_field(signed(field("d", a, b, max_distance)))
5953 .unwrap();
5954 let fb = scene
5955 .add_distance_field(signed(field("d", b, a, max_distance)))
5956 .unwrap();
5957 for (m, f, rule) in [(a, fa, distance_rule(0.2, 0.1)), (b, fb, contact_rule(0.1))] {
5958 scene
5959 .set_coloring(m, Some(Coloring { field: f, rule }))
5960 .unwrap();
5961 }
5962 scene.set_defer_fields(true);
5963 scene.take_changes();
5964 (scene, a, b, [fa, fb])
5965 }
5966
5967 #[test]
5968 fn abandon_leaves_no_trace() {
5969 for md in [0.15, f64::INFINITY] {
5972 for points in [1, 60, 121, 125] {
5973 let at_ = format!("max_distance {md}, {points} points");
5974 let (mut scene, a, b, ids) = deferred_pair(md);
5975 let (mut twin, ..) = deferred_pair(md);
5976 for s in [&mut scene, &mut twin] {
5977 s.set_placement(b, turned(10.0, [0.05, -0.02, 0.03]))
5978 .unwrap();
5979 s.take_changes();
5980 }
5981 let before = seen(&mut scene, [a, b]);
5982 step_partway(&mut scene, points);
5983 assert_eq!(scene.field_step_meshes(), vec![a, b], "{at_}");
5984 assert_eq!(
5986 seen_values(&scene, [a, b]),
5987 (before.0.clone(), before.1.clone())
5988 );
5989 assert!(scene.field_step_pending(), "{at_}");
5990 scene.abandon_field_step();
5991 assert!(!scene.field_step_pending(), "{at_}");
5992 assert!(scene.field_step_meshes().is_empty(), "{at_}");
5993 scene.abandon_field_step();
5995 assert_eq!(seen(&mut scene, [a, b]), before, "{at_}");
5996 for id in ids {
5997 assert!(!scene.field_status(id).unwrap().current, "{at_}");
5998 }
5999 scene.update_fields().unwrap();
6000 twin.update_fields().unwrap();
6001 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]), "{at_}");
6002 }
6003 }
6004 }
6005
6006 type Shown = (Vec<Vec<u32>>, Vec<Option<Vec<[u8; 4]>>>);
6008
6009 fn seen_values(scene: &Scene, meshes: [MeshId; 2]) -> Shown {
6011 let values = scene
6012 .fields
6013 .iter()
6014 .map(|f| bits(scene.field_values(f.id).unwrap()))
6015 .collect();
6016 let colors = meshes
6017 .iter()
6018 .map(|&m| scene.colors(m).unwrap().map(<[_]>::to_vec))
6019 .collect();
6020 (values, colors)
6021 }
6022
6023 #[test]
6024 fn abandon_then_edit_matches_one_shot() {
6025 let edits: [fn(&mut Scene, MeshId, MeshId); 3] = [
6026 |s, a, _| {
6027 s.edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, -0.05]]))
6028 .unwrap();
6029 },
6030 |s, _, b| {
6031 s.edit(b, |tx| tx.set_positions(&[7], &[[0.75, 0.75, 0.25]]))
6032 .unwrap();
6033 },
6034 |s, a, _| {
6035 s.edit(a, |tx| tx.remove_triangles(&[0, 1, 2])).unwrap();
6036 },
6037 ];
6038 for md in [0.15, f64::INFINITY] {
6039 for (e, edit) in edits.iter().enumerate() {
6040 for by_hand in [true, false] {
6042 let at_ = format!("max_distance {md}, edit {e}, by hand {by_hand}");
6043 let (mut scene, a, b, _) = deferred_pair(md);
6044 let (mut twin, ..) = deferred_pair(md);
6045 let p1 = turned(-8.0, [0.04, 0.03, 0.02]);
6046 scene.set_placement(b, p1).unwrap();
6047 step_partway(&mut scene, 70);
6048 if by_hand {
6049 scene.abandon_field_step();
6050 }
6051 edit(&mut scene, a, b);
6052 assert!(!scene.field_step_pending(), "{at_}");
6053 step_all(&mut scene, 37);
6054 twin.set_placement(b, p1).unwrap();
6055 edit(&mut twin, a, b);
6056 twin.update_fields().unwrap();
6057 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]), "{at_}");
6058 for m in [a, b] {
6061 scene.update(m).unwrap();
6062 }
6063 for f in scene.fields.iter().map(|f| f.id).collect::<Vec<_>>() {
6064 assert_matches(&mut scene, f);
6065 }
6066 }
6067 }
6068 }
6069 }
6070
6071 #[test]
6072 fn thickness_plate_reads_depth() {
6073 let mut scene = Scene::new(SceneOptions::default());
6075 let m = scene.add_mesh(plate(4), MeshOptions::default()).unwrap();
6076 let id = scene
6077 .add_thickness_field(thick("t", m, f64::INFINITY))
6078 .unwrap();
6079 assert!(scene.field_status(id).unwrap().current);
6080 let values = scene.field_values(id).unwrap();
6081 assert_eq!(values.len(), scene.mesh(m).unwrap().vertex_slots());
6082 let w = 5;
6083 for y in 1..4 {
6084 for x in 1..4 {
6085 for &v in &[y * w + x, w * w + y * w + x] {
6086 assert!((values[v as usize] - 0.5).abs() < 1e-3, "{values:?}");
6087 }
6088 }
6089 }
6090 assert_eq!(scene.field_nearest(id).unwrap(), &[]);
6093 assert_eq!(scene.field_targets(id).unwrap(), &[]);
6094 assert!(scene.field_target_status(id).unwrap().is_empty());
6095 assert_eq!(scene.fields_on(m).unwrap(), vec![id]);
6096 let rule = thick_rule(0.0, 1.0);
6098 scene
6099 .set_coloring(
6100 m,
6101 Some(Coloring {
6102 field: id,
6103 rule: rule.clone(),
6104 }),
6105 )
6106 .unwrap();
6107 assert_colors(&scene, m, &rule, id);
6108 }
6109
6110 #[test]
6111 fn thickness_open_sheet_reads_nan() {
6112 let mut scene = Scene::new(SceneOptions::default());
6114 let m = scene
6115 .add_mesh(sheet(4, 0.5), MeshOptions::default())
6116 .unwrap();
6117 let id = scene
6118 .add_thickness_field(thick("t", m, f64::INFINITY))
6119 .unwrap();
6120 let values = scene.field_values(id).unwrap();
6121 assert!(!values.is_empty());
6122 assert!(values.iter().all(|v| v.is_nan()), "{values:?}");
6123 let rule = thick_rule(0.0, 1.0);
6125 scene
6126 .set_coloring(
6127 m,
6128 Some(Coloring {
6129 field: id,
6130 rule: rule.clone(),
6131 }),
6132 )
6133 .unwrap();
6134 assert_colors(&scene, m, &rule, id);
6135 for c in scene.colors(m).unwrap().unwrap() {
6136 assert_eq!(*c, rule.nan);
6137 }
6138 }
6139
6140 #[test]
6141 fn thickness_max_distance_reads_far() {
6142 let mut scene = Scene::new(SceneOptions::default());
6143 let m = scene.add_mesh(plate(2), MeshOptions::default()).unwrap();
6144 let id = scene.add_thickness_field(thick("t", m, 0.1)).unwrap();
6145 assert!(
6146 scene
6147 .field_values(id)
6148 .unwrap()
6149 .iter()
6150 .all(|&v| v == f32::INFINITY)
6151 );
6152 scene
6155 .reconfigure_field(id, f64::INFINITY, SignMode::Winding)
6156 .unwrap();
6157 let values = scene.field_values(id).unwrap();
6158 assert!(values.iter().all(|v| v.is_finite()), "{values:?}");
6159 scene
6161 .reconfigure_field(id, 0.1, SignMode::Pseudonormal)
6162 .unwrap();
6163 assert!(
6164 scene
6165 .field_values(id)
6166 .unwrap()
6167 .iter()
6168 .all(|&v| v == f32::INFINITY)
6169 );
6170 assert!(scene.field_status(id).unwrap().current);
6171 }
6172
6173 #[test]
6174 fn thickness_validation() {
6175 let mut scene = Scene::new(SceneOptions::default());
6176 let m = scene.add_mesh(plate(1), MeshOptions::default()).unwrap();
6177 scene.add_thickness_field(thick("t", m, 1.0)).unwrap();
6178 assert!(matches!(
6179 scene.add_thickness_field(thick("t", m, 1.0)),
6180 Err(SceneError::InvalidInput(_))
6181 ));
6182 assert!(matches!(
6183 scene.add_distance_field(field("t", m, m, 1.0)),
6184 Err(SceneError::InvalidInput(_))
6185 ));
6186 assert!(matches!(
6187 scene.add_thickness_field(ThicknessField {
6188 name: "u".into(),
6189 on: m,
6190 max_distance: -1.0,
6191 }),
6192 Err(SceneError::InvalidInput(_))
6193 ));
6194 assert!(matches!(
6195 scene.add_thickness_field(ThicknessField {
6196 name: "u".into(),
6197 on: m,
6198 max_distance: f64::NAN,
6199 }),
6200 Err(SceneError::InvalidInput(_))
6201 ));
6202 let id = scene.field_id(0).unwrap();
6204 let rule = thick_rule(0.0, 1.0);
6205 scene
6206 .set_coloring(m, Some(Coloring { field: id, rule }))
6207 .unwrap();
6208 assert!(!scene.withdraw_field(id).unwrap());
6209 scene.set_coloring(m, None).unwrap();
6210 assert!(!scene.withdraw_field(id).unwrap());
6211 scene.remove_field(id).unwrap();
6212 assert!(matches!(
6213 scene.field_values(id),
6214 Err(SceneError::UnknownField)
6215 ));
6216 let late = scene
6218 .add_thickness_field_deferred(thick("late", m, 1.0))
6219 .unwrap();
6220 assert!(scene.withdraw_field(late).unwrap());
6221 assert!(matches!(
6222 scene.field_values(late),
6223 Err(SceneError::UnknownField)
6224 ));
6225 }
6226
6227 #[test]
6228 fn thickness_follows_edits_and_placements() {
6229 let mut scene = Scene::new(SceneOptions::default());
6230 let m = scene.add_mesh(plate(2), MeshOptions::default()).unwrap();
6231 let id = scene
6232 .add_thickness_field(thick("t", m, f64::INFINITY))
6233 .unwrap();
6234 let interior = [4, 9 + 4];
6236 for &v in &interior {
6237 assert!((scene.field_values(id).unwrap()[v as usize] - 0.5).abs() < 1e-3);
6238 }
6239 scene
6241 .set_placement(
6242 m,
6243 Placement {
6244 scale: [2.0, 2.0, 2.0],
6245 ..Placement::IDENTITY
6246 },
6247 )
6248 .unwrap();
6249 for &v in &interior {
6250 assert!((scene.field_values(id).unwrap()[v as usize] - 1.0).abs() < 1e-3);
6251 }
6252 scene
6255 .set_placement(
6256 m,
6257 Placement {
6258 scale: [1.0, 1.0, 2.0],
6259 ..Placement::IDENTITY
6260 },
6261 )
6262 .unwrap();
6263 for &v in &interior {
6264 assert!((scene.field_values(id).unwrap()[v as usize] - 1.0).abs() < 1e-3);
6265 }
6266 scene.set_placement(m, Placement::IDENTITY).unwrap();
6267 let before = scene.field_values(id).unwrap().to_vec();
6269 scene
6270 .edit(m, |tx| tx.set_positions(&[1], &[[1.0 / 2.0, 0.0, 1.0]]))
6271 .unwrap();
6272 assert!(scene.field_status(id).unwrap().current);
6273 let after = scene.field_values(id).unwrap().to_vec();
6274 assert_ne!(before, after);
6275 assert!(after.iter().all(|v| v.is_finite()));
6276 }
6277
6278 #[test]
6279 fn thickness_steps_in_full() {
6280 let mut scene = Scene::new(SceneOptions::default());
6281 let m = scene.add_mesh(plate(2), MeshOptions::default()).unwrap();
6282 let id = scene
6283 .add_thickness_field_deferred(thick("t", m, f64::INFINITY))
6284 .unwrap();
6285 assert!(!scene.field_status(id).unwrap().current);
6286 assert_eq!(
6287 scene.field_step_field(id, FieldBudget::Points(1)),
6288 Ok(StepState::Done)
6289 );
6290 assert!(scene.field_status(id).unwrap().current);
6291 assert!(
6292 scene
6293 .field_values(id)
6294 .unwrap()
6295 .iter()
6296 .all(|v| v.is_finite())
6297 );
6298 assert_eq!(
6300 scene.field_step_field(id, FieldBudget::Points(1)),
6301 Ok(StepState::Idle)
6302 );
6303 let mut lazy = Scene::new(SceneOptions::default());
6305 let lm = lazy
6306 .add_mesh(
6307 plate(2),
6308 MeshOptions {
6309 mode: UpdateMode::Lazy,
6310 ..MeshOptions::default()
6311 },
6312 )
6313 .unwrap();
6314 let lid = lazy
6315 .add_thickness_field(thick("t", lm, f64::INFINITY))
6316 .unwrap();
6317 assert!(!lazy.field_status(lid).unwrap().current);
6318 lazy.flush().unwrap();
6319 assert!(lazy.field_status(lid).unwrap().current);
6320 assert!(
6321 lazy.field_values(lid)
6322 .unwrap()
6323 .iter()
6324 .all(|v| v.is_finite())
6325 );
6326 }
6327
6328 #[test]
6329 fn curvature_sphere_reads_one_over_r() {
6330 let mut scene = Scene::new(SceneOptions::default());
6333 let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6334 let id = scene.add_curvature_field(curv("k", m)).unwrap();
6335 assert!(scene.field_status(id).unwrap().current);
6336 let values = scene.field_values(id).unwrap();
6337 assert_eq!(values.len(), scene.mesh(m).unwrap().vertex_slots());
6338 for &v in values.iter() {
6339 assert!(v.is_finite(), "{values:?}");
6340 assert!(v > 0.0, "{values:?}");
6341 }
6342 let mean = values.iter().sum::<f32>() / values.len() as f32;
6343 assert!((mean - 1.0).abs() < 0.3, "{mean}");
6344 assert_eq!(scene.field_nearest(id).unwrap(), &[]);
6347 assert_eq!(scene.field_targets(id).unwrap(), &[]);
6348 assert!(scene.field_target_status(id).unwrap().is_empty());
6349 assert_eq!(scene.fields_on(m).unwrap(), vec![id]);
6350 let rule = thick_rule(-1.0, 2.0);
6352 scene
6353 .set_coloring(
6354 m,
6355 Some(Coloring {
6356 field: id,
6357 rule: rule.clone(),
6358 }),
6359 )
6360 .unwrap();
6361 assert_colors(&scene, m, &rule, id);
6362 }
6363
6364 #[test]
6365 fn curvature_plane_reads_zero_and_saddle_negative() {
6366 let mut scene = Scene::new(SceneOptions::default());
6367 let flat = scene
6369 .add_mesh(sheet(8, 0.25), MeshOptions::default())
6370 .unwrap();
6371 let fid = scene.add_curvature_field(curv("k", flat)).unwrap();
6372 for &v in scene.field_values(fid).unwrap().iter() {
6373 assert!(v.is_finite());
6374 assert!(v.abs() < 1e-5, "{v}");
6375 }
6376 let m = scene
6379 .add_mesh(saddle(32, 4.0), MeshOptions::default())
6380 .unwrap();
6381 let id = scene.add_curvature_field(curv("k", m)).unwrap();
6382 let values = scene.field_values(id).unwrap();
6383 assert!(values.iter().all(|v| v.is_finite()), "{values:?}");
6384 let c = (16 * 33 + 16) as usize;
6385 assert!((values[c] + 0.25).abs() < 0.15, "{:?}", values[c]);
6386 assert!(values.iter().any(|&v| v < -0.1), "{values:?}");
6387 let rule = thick_rule(-1.0, 1.0);
6390 scene
6391 .set_coloring(
6392 m,
6393 Some(Coloring {
6394 field: id,
6395 rule: rule.clone(),
6396 }),
6397 )
6398 .unwrap();
6399 assert_colors(&scene, m, &rule, id);
6400 }
6401
6402 #[test]
6403 fn curvature_follows_scales() {
6404 let mut scene = Scene::new(SceneOptions::default());
6405 let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6406 let id = scene.add_curvature_field(curv("k", m)).unwrap();
6407 let local = scene.field_values(id).unwrap().to_vec();
6408 scene
6410 .set_placement(
6411 m,
6412 Placement {
6413 scale: [2.0, 2.0, 2.0],
6414 ..Placement::IDENTITY
6415 },
6416 )
6417 .unwrap();
6418 let scaled = scene.field_values(id).unwrap().to_vec();
6419 assert_eq!(scaled.len(), local.len());
6420 for (&x, &y) in scaled.iter().zip(&local) {
6421 assert!((x - y / 2.0).abs() < 1e-4, "{x} vs {y}");
6422 }
6423 assert!(scaled.iter().all(|v| v.is_finite()));
6424 scene
6427 .set_placement(
6428 m,
6429 Placement {
6430 scale: [1.0, 1.0, 2.0],
6431 ..Placement::IDENTITY
6432 },
6433 )
6434 .unwrap();
6435 assert!(scene.field_status(id).unwrap().current);
6436 let baked = scene.field_values(id).unwrap().to_vec();
6437 assert!(baked.iter().all(|v| v.is_finite()), "{baked:?}");
6438 assert_ne!(baked, scaled);
6439 scene.set_placement(m, Placement::IDENTITY).unwrap();
6441 assert_eq!(scene.field_values(id).unwrap(), &local);
6442 }
6443
6444 #[test]
6445 fn curvature_validation() {
6446 let mut scene = Scene::new(SceneOptions::default());
6447 let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6448 scene.add_thickness_field(thick("t", m, 1.0)).unwrap();
6449 scene.add_curvature_field(curv("k", m)).unwrap();
6450 assert!(matches!(
6452 scene.add_curvature_field(curv("t", m)),
6453 Err(SceneError::InvalidInput(_))
6454 ));
6455 assert!(matches!(
6456 scene.add_curvature_field(curv("k", m)),
6457 Err(SceneError::InvalidInput(_))
6458 ));
6459 assert!(matches!(
6460 scene.add_thickness_field(thick("k", m, 1.0)),
6461 Err(SceneError::InvalidInput(_))
6462 ));
6463 let id = scene.field_id(1).unwrap();
6466 scene.reconfigure_field(id, 0.5, SignMode::Winding).unwrap();
6467 assert!(scene.field_status(id).unwrap().current);
6468 assert!(
6469 scene
6470 .field_values(id)
6471 .unwrap()
6472 .iter()
6473 .all(|v| v.is_finite())
6474 );
6475 scene.remove_mesh(m).unwrap();
6477 assert!(matches!(
6478 scene.field_values(id),
6479 Err(SceneError::UnknownField)
6480 ));
6481 let m2 = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6483 let late = scene
6484 .add_curvature_field_deferred(curv("late", m2))
6485 .unwrap();
6486 assert!(scene.withdraw_field(late).unwrap());
6487 assert!(matches!(
6488 scene.field_values(late),
6489 Err(SceneError::UnknownField)
6490 ));
6491 }
6492
6493 #[test]
6494 fn curvature_steps_in_full() {
6495 let mut scene = Scene::new(SceneOptions::default());
6496 let m = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
6497 let id = scene.add_curvature_field_deferred(curv("k", m)).unwrap();
6498 assert!(!scene.field_status(id).unwrap().current);
6499 assert_eq!(
6500 scene.field_step_field(id, FieldBudget::Points(1)),
6501 Ok(StepState::Done)
6502 );
6503 assert!(scene.field_status(id).unwrap().current);
6504 assert!(
6505 scene
6506 .field_values(id)
6507 .unwrap()
6508 .iter()
6509 .all(|v| v.is_finite())
6510 );
6511 assert_eq!(
6513 scene.field_step_field(id, FieldBudget::Points(1)),
6514 Ok(StepState::Idle)
6515 );
6516 let mut lazy = Scene::new(SceneOptions::default());
6518 let lm = lazy
6519 .add_mesh(
6520 ball(),
6521 MeshOptions {
6522 mode: UpdateMode::Lazy,
6523 ..MeshOptions::default()
6524 },
6525 )
6526 .unwrap();
6527 let lid = lazy.add_curvature_field(curv("k", lm)).unwrap();
6528 assert!(!lazy.field_status(lid).unwrap().current);
6529 lazy.flush().unwrap();
6530 assert!(lazy.field_status(lid).unwrap().current);
6531 assert!(
6532 lazy.field_values(lid)
6533 .unwrap()
6534 .iter()
6535 .all(|v| v.is_finite())
6536 );
6537 }
6538
6539 #[test]
6540 fn mutation_abandons_open_unit() {
6541 let (mut scene, a, b, _) = deferred_pair(0.15);
6542 let (mut twin, ..) = deferred_pair(0.15);
6543 let far = MeshOptions {
6544 placement: at(5.0, 0.0, 0.0),
6545 ..MeshOptions::default()
6546 };
6547 let c = scene.add_mesh(sheet(2, 0.5), far).unwrap();
6548 twin.add_mesh(sheet(2, 0.5), far).unwrap();
6549 let p1 = turned(5.0, [0.02, 0.01, 0.0]);
6552 scene.set_placement(b, p1).unwrap();
6553 step_partway(&mut scene, 30);
6554 scene.set_placement(c, at(6.0, 0.0, 0.0)).unwrap();
6555 assert!(!scene.field_step_pending());
6556 step_all(&mut scene, 37);
6557 twin.set_placement(b, p1).unwrap();
6558 twin.set_placement(c, at(6.0, 0.0, 0.0)).unwrap();
6559 twin.update_fields().unwrap();
6560 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
6561 let (p2, p3) = (at(0.1, 0.0, 0.05), at(-0.05, 0.02, -0.03));
6564 scene.set_placement(b, p2).unwrap();
6565 step_partway(&mut scene, 100);
6566 scene.set_placement(b, p3).unwrap();
6567 assert!(!scene.field_step_pending());
6568 step_all(&mut scene, 37);
6569 twin.set_placement(b, p2).unwrap();
6570 twin.set_placement(b, p3).unwrap();
6571 twin.update_fields().unwrap();
6572 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
6573 }
6574
6575 struct Ctx {
6577 a: MeshId,
6578 b: MeshId,
6579 c: MeshId,
6580 field: FieldId,
6581 section: crate::SectionId,
6582 subscriber: crate::SubscriberId,
6583 }
6584
6585 fn open_unit() -> (Scene, Ctx) {
6589 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
6590 let field = scene
6591 .add_distance_field(field("d", a, b, f64::INFINITY))
6592 .unwrap();
6593 let opts = MeshOptions {
6594 placement: at(5.0, 0.0, 0.0),
6595 ..MeshOptions::default()
6596 };
6597 let c = scene.add_mesh(sheet(2, 0.5), opts).unwrap();
6598 scene.add_channel(c, ChannelDesc::derived("x", 1)).unwrap();
6599 let plane = crate::Plane::new([0.0, 0.0, 0.0], [0.0, 0.0, 1.0]).unwrap();
6600 let section = scene
6601 .add_section(crate::SectionDesc {
6602 plane,
6603 meshes: vec![c],
6604 fill: false,
6605 })
6606 .unwrap();
6607 let subscriber = scene.subscribe();
6608 scene.set_defer_fields(true);
6609 scene.set_placement(b, at(0.01, 0.0, 0.0)).unwrap();
6610 step_partway(&mut scene, 1);
6611 let ctx = Ctx {
6612 a,
6613 b,
6614 c,
6615 field,
6616 section,
6617 subscriber,
6618 };
6619 (scene, ctx)
6620 }
6621
6622 #[test]
6623 fn mutating_calls_abandon_open_unit() {
6624 use crate::{ContactFilter, Plane, SceneClosestOptions, SceneRayOptions, SectionDesc};
6625 type Call = fn(&mut Scene, &Ctx);
6626 let calls: [(&str, Call); 43] = [
6629 ("add_mesh", |s, _| {
6630 s.add_mesh(sheet(1, 1.0), MeshOptions::default()).unwrap();
6631 }),
6632 ("add_mesh_built", |s, _| {
6633 let build = crate::MeshBuild::new(sheet(1, 1.0), MeshOptions::default()).unwrap();
6634 s.add_mesh_built(build).unwrap();
6635 }),
6636 ("remove_mesh", |s, x| {
6637 s.remove_mesh(x.c).unwrap();
6638 }),
6639 ("edit", |s, x| {
6640 s.edit(x.c, |tx| tx.set_positions(&[0], &[[0.0, 0.0, 0.1]]))
6641 .unwrap();
6642 }),
6643 ("begin_step", |s, _| s.begin_step()),
6644 ("end_step", |s, _| {
6645 let _ = s.end_step();
6646 }),
6647 ("cancel_step", |s, _| {
6648 let _ = s.cancel_step();
6649 }),
6650 ("undo", |s, _| {
6651 s.undo().unwrap();
6652 }),
6653 ("redo", |s, _| {
6654 s.redo().unwrap();
6655 }),
6656 ("clear_history", |s, _| s.clear_history().unwrap()),
6657 ("set_mode", |s, x| {
6658 s.set_mode(x.c, UpdateMode::Lazy).unwrap()
6659 }),
6660 ("update", |s, x| s.update(x.c).unwrap()),
6661 ("flush", |s, _| s.flush().unwrap()),
6662 ("take_changes", |s, _| {
6663 s.take_changes();
6664 }),
6665 ("subscribe", |s, _| {
6666 s.subscribe();
6667 }),
6668 ("unsubscribe", |s, x| s.unsubscribe(x.subscriber).unwrap()),
6669 ("take_changes_for", |s, x| {
6670 s.take_changes_for(x.subscriber).unwrap();
6671 }),
6672 ("add_channel", |s, x| {
6673 s.add_channel(x.c, ChannelDesc::derived("y", 1)).unwrap();
6674 }),
6675 ("remove_channel", |s, x| {
6676 s.remove_channel(x.c, "x").unwrap();
6677 }),
6678 ("derived_channel_mut", |s, x| {
6679 s.derived_channel_mut(x.c, "x").unwrap();
6680 }),
6681 ("compact", |s, x| {
6682 s.compact(x.c).unwrap();
6683 }),
6684 ("set_placement", |s, x| {
6685 s.set_placement(x.c, at(6.0, 0.0, 0.0)).unwrap();
6686 }),
6687 ("add_distance_field", |s, x| {
6688 s.add_distance_field(field("e", x.c, x.b, 0.1)).unwrap();
6689 }),
6690 ("add_distance_field_deferred", |s, x| {
6691 s.add_distance_field_deferred(field("e", x.c, x.b, 0.1))
6692 .unwrap();
6693 }),
6694 ("add_thickness_field", |s, x| {
6695 s.add_thickness_field(thick("e", x.c, f64::INFINITY))
6696 .unwrap();
6697 }),
6698 ("add_thickness_field_deferred", |s, x| {
6699 s.add_thickness_field_deferred(thick("e", x.c, f64::INFINITY))
6700 .unwrap();
6701 }),
6702 ("withdraw_field", |s, x| {
6703 assert!(!s.withdraw_field(x.field).unwrap());
6704 }),
6705 ("remove_field", |s, x| s.remove_field(x.field).unwrap()),
6706 ("update_fields", |s, _| s.update_fields().unwrap()),
6707 ("set_coloring", |s, x| s.set_coloring(x.c, None).unwrap()),
6708 ("set_normals", |s, x| s.set_normals(x.c, true).unwrap()),
6709 ("raycast", |s, _| {
6710 let opts = SceneRayOptions::default();
6711 s.raycast([0.5, 0.5, 1.0], [0.0, 0.0, -1.0], &opts).unwrap();
6712 }),
6713 ("closest_point", |s, _| {
6714 let opts = SceneClosestOptions::default();
6715 s.closest_point([0.5, 0.5, 1.0], &opts).unwrap();
6716 }),
6717 ("closest_point_on", |s, x| {
6718 let opts = SceneClosestOptions::default();
6719 s.closest_point_on(x.c, [0.5, 0.5, 1.0], &opts).unwrap();
6720 }),
6721 ("intersects", |s, x| {
6722 s.intersects(x.c, x.b).unwrap();
6723 }),
6724 ("mesh_distance", |s, x| {
6725 s.mesh_distance(x.c, x.b, 0.1).unwrap();
6726 }),
6727 ("triangles_near", |s, x| {
6728 let (mut ta, mut tb) = (Vec::new(), Vec::new());
6729 s.triangles_near(x.c, x.b, 0.1, &mut ta, &mut tb).unwrap();
6730 }),
6731 ("find_contacts", |s, _| {
6732 s.find_contacts(0.1, &ContactFilter::default(), &mut Vec::new())
6733 .unwrap();
6734 }),
6735 ("add_section", |s, x| {
6736 let plane = Plane::new([0.0, 0.0, 0.1], [0.0, 0.0, 1.0]).unwrap();
6737 let desc = SectionDesc {
6738 plane,
6739 meshes: vec![x.c],
6740 fill: true,
6741 };
6742 s.add_section(desc).unwrap();
6743 }),
6744 ("remove_section", |s, x| {
6745 s.remove_section(x.section).unwrap()
6746 }),
6747 ("set_section_plane", |s, x| {
6748 let plane = Plane::new([0.0, 0.0, 0.2], [0.0, 0.0, 1.0]).unwrap();
6749 s.set_section_plane(x.section, plane).unwrap();
6750 }),
6751 ("set_section_meshes", |s, x| {
6752 s.set_section_meshes(x.section, vec![x.a, x.c]).unwrap();
6753 }),
6754 ("set_section_fill", |s, x| {
6755 s.set_section_fill(x.section, true).unwrap();
6756 }),
6757 ];
6758 for (name, call) in calls {
6759 let (mut scene, ctx) = open_unit();
6760 assert!(scene.field_step_pending(), "{name}");
6761 call(&mut scene, &ctx);
6762 assert!(!scene.field_step_pending(), "{name} left the unit open");
6763 }
6764 let (mut scene, _) = open_unit();
6766 scene.update_sections().unwrap();
6767 assert!(!scene.field_step_pending(), "update_sections");
6768 let (mut scene, x) = open_unit();
6770 scene.mesh(x.a).unwrap();
6771 scene.placement(x.b).unwrap();
6772 scene.is_current(x.a).unwrap();
6773 scene.field_values(x.field).unwrap();
6774 scene.field_status(x.field).unwrap();
6775 scene.colors(x.a).unwrap();
6776 assert_eq!(scene.field_step_meshes(), vec![x.a, x.b]);
6777 scene.set_defer_fields(false);
6778 scene.set_defer_fields(true);
6779 assert!(scene.field_step_pending(), "a read or deferral abandoned");
6780 assert_eq!(step_all(&mut scene, usize::MAX), 0);
6781 assert!(scene.field_status(x.field).unwrap().current);
6782 }
6783
6784 #[test]
6785 fn defer_leaves_fields_due() {
6786 let (mut scene, a, b, ids) = deferred_pair(0.15);
6787 assert_eq!(
6788 scene.field_step(FieldBudget::Points(1)),
6789 Ok(StepState::Idle)
6790 );
6791 counter::take();
6792 scene.set_placement(b, at(0.02, 0.0, 0.01)).unwrap();
6793 scene
6794 .edit(a, |tx| tx.set_positions(&[45], &[[0.5, 0.4, 0.05]]))
6795 .unwrap();
6796 assert_eq!(counter::take(), 0);
6797 for id in ids {
6798 assert!(!scene.field_status(id).unwrap().current);
6799 }
6800 assert!(!scene.field_step_pending());
6801 let before = seen_values(&scene, [a, b]);
6803 assert_eq!(
6804 scene.field_step(FieldBudget::Points(5)),
6805 Ok(StepState::Pending)
6806 );
6807 assert_eq!(seen_values(&scene, [a, b]), before);
6808 assert_eq!(counter::take(), 0);
6809 assert!(step_all(&mut scene, usize::MAX) == 0);
6810 assert!(counter::take() > 0);
6811 for id in ids {
6812 assert_matches(&mut scene, id);
6813 }
6814 scene.set_defer_fields(false);
6816 scene.set_placement(b, at(0.03, 0.0, 0.01)).unwrap();
6817 assert!(counter::take() > 0);
6818 for id in ids {
6819 assert!(scene.field_status(id).unwrap().current);
6820 }
6821 scene.set_mode(a, UpdateMode::Lazy).unwrap();
6823 scene.set_mode(b, UpdateMode::Lazy).unwrap();
6824 scene.set_placement(b, at(0.04, 0.0, 0.01)).unwrap();
6825 assert_eq!(
6826 scene.field_step(FieldBudget::Points(1)),
6827 Ok(StepState::Idle)
6828 );
6829 assert!(!scene.field_status(ids[0]).unwrap().current);
6830 }
6831
6832 fn group_scene(mode: UpdateMode) -> (Scene, [MeshId; 4], [FieldId; 4]) {
6837 let mut scene = Scene::new(SceneOptions::default());
6838 let a = scene.add_mesh(sheet(10, 0.1), with_mode(mode)).unwrap();
6839 let b = scene
6840 .add_mesh(cube([0.1, 0.1, -0.2], 0.3), with_mode(mode))
6841 .unwrap();
6842 let c = scene
6843 .add_mesh(cube([0.3, 0.2, -0.1], 0.3), with_mode(mode))
6844 .unwrap();
6845 let d = scene
6846 .add_mesh(cube([0.75, 0.7, -0.1], 0.2), with_mode(mode))
6847 .unwrap();
6848 let mut add = |on: MeshId, to: Vec<MeshId>| {
6849 scene
6850 .add_distance_field(DistanceField {
6851 to,
6852 ..field("g", on, on, 0.12)
6853 })
6854 .unwrap()
6855 };
6856 let fields = [
6857 add(a, vec![b, c, d]),
6858 add(b, vec![a, c]),
6859 add(c, vec![a, b]),
6860 add(d, vec![a]),
6861 ];
6862 (scene, [a, b, c, d], fields)
6863 }
6864
6865 fn applied(scene: &mut Scene, f: impl FnOnce(&mut Scene)) -> Vec<FieldId> {
6867 counter::take_applied();
6868 f(scene);
6869 counter::take_applied()
6870 }
6871
6872 #[test]
6873 fn group_recomputes_only_related_fields() {
6874 counter::set_near_limit(Some(usize::MAX));
6877 let (mut scene, [a, b, c, d], [fa, fb, fc, fd]) = group_scene(UpdateMode::Eager);
6878 for f in [fa, fb, fc, fd] {
6880 assert!(finite_count(&scene, f) > 0);
6881 }
6882 let a_slots = scene.mesh(a).unwrap().vertex_slots();
6883 counter::take();
6884 let got = applied(&mut scene, |s| {
6886 s.set_placement(d, at(0.01, 0.0, 0.0)).unwrap()
6887 });
6888 assert_eq!(got, [fa, fd]);
6889 let recomputed = counter::take();
6891 assert!(recomputed > 0 && recomputed < a_slots, "{recomputed}");
6892 let got = applied(&mut scene, |s| {
6894 s.set_placement(a, at(0.0, 0.01, 0.0)).unwrap()
6895 });
6896 assert_eq!(got, [fa, fb, fc, fd]);
6897 let got = applied(&mut scene, |s| {
6899 s.set_placement(b, at(0.0, 0.0, 0.01)).unwrap()
6900 });
6901 assert_eq!(got, [fa, fb, fc]);
6902 let got = applied(&mut scene, |s| {
6903 s.edit(b, |tx| tx.set_positions(&[0], &[[0.12, 0.1, -0.2]]))
6904 .unwrap();
6905 });
6906 assert_eq!(got, [fa, fb, fc]);
6907 let got = applied(&mut scene, |s| {
6909 s.set_placement(c, at(0.01, 0.0, 0.0)).unwrap()
6910 });
6911 assert_eq!(got, [fa, fb, fc]);
6912 let got = applied(&mut scene, |s| {
6914 s.edit(d, |tx| tx.set_positions(&[0], &[[0.76, 0.7, -0.1]]))
6915 .unwrap();
6916 });
6917 assert_eq!(got, [fa, fd]);
6918 scene.set_defer_fields(true);
6920 scene.set_placement(d, at(0.02, 0.0, 0.0)).unwrap();
6921 let current: Vec<bool> = [fa, fb, fc, fd]
6922 .iter()
6923 .map(|&f| scene.field_status(f).unwrap().current)
6924 .collect();
6925 assert_eq!(current, [false, true, true, false]);
6926 assert_eq!(
6927 scene.field_step(FieldBudget::Points(1)),
6928 Ok(StepState::Pending)
6929 );
6930 assert_eq!(scene.field_step_meshes(), [a, b, c, d,]);
6931 let got = applied(&mut scene, |s| {
6932 step_all(s, usize::MAX);
6933 });
6934 assert_eq!(got, [fa, fd]);
6935 counter::set_near_limit(None);
6936 }
6937
6938 fn min_of_singles(scene: &mut Scene, id: FieldId) -> (Vec<u32>, Vec<u32>) {
6941 let desc = scene
6942 .fields
6943 .iter()
6944 .find(|f| f.id == id)
6945 .unwrap()
6946 .desc
6947 .clone();
6948 let singles: Vec<FieldId> = desc
6949 .to
6950 .iter()
6951 .enumerate()
6952 .map(|(k, &m)| {
6953 scene
6954 .add_distance_field(DistanceField {
6955 name: format!("single{k}"),
6956 to: vec![m],
6957 ..desc.clone()
6958 })
6959 .unwrap()
6960 })
6961 .collect();
6962 update_all(scene);
6963 let slots = scene.mesh(desc.on).unwrap().vertex_slots();
6964 let (mut values, mut nearest) = (Vec::new(), Vec::new());
6965 for v in 0..slots {
6966 let parts: Vec<f32> = singles
6967 .iter()
6968 .map(|&f| scene.field_values(f).unwrap()[v])
6969 .collect();
6970 let (mut best, mut at) = (parts[0], u32::MAX);
6971 if !best.is_nan() {
6972 best = f32::INFINITY;
6973 for (k, &x) in parts.iter().enumerate() {
6974 if x < best {
6975 (best, at) = (x, k as u32);
6976 }
6977 }
6978 }
6979 values.push(best.to_bits());
6980 nearest.push(at);
6981 }
6982 for f in singles {
6983 scene.remove_field(f).unwrap();
6984 }
6985 (values, nearest)
6986 }
6987
6988 fn shown(scene: &Scene, id: FieldId) -> (Vec<u32>, Vec<u32>) {
6991 let values = bits(scene.field_values(id).unwrap());
6992 let nearest = scene.field_nearest(id).unwrap();
6993 let nearest = (0..values.len())
6994 .map(|v| nearest.get(v).copied().unwrap_or(u32::MAX))
6995 .collect();
6996 (values, nearest)
6997 }
6998
6999 #[test]
7000 fn group_fields_match_single_targets() {
7001 let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7002 let mut rng = Lcg(0x6709);
7003 for step in 0..30 {
7004 let m = meshes[rng.below(4)];
7005 let t = [
7006 rng.range(-0.05, 0.05),
7007 rng.range(-0.05, 0.05),
7008 rng.range(-0.05, 0.05),
7009 ];
7010 scene
7011 .set_placement(m, turned(rng.range(-10.0, 10.0), t))
7012 .unwrap();
7013 for &f in &fields {
7014 let got = shown(&scene, f);
7015 assert_eq!(got, min_of_singles(&mut scene, f), "step {step}");
7016 }
7017 }
7018 let (_, nearest) = shown(&scene, fields[0]);
7020 for k in 0..3 {
7021 assert!(nearest.contains(&k), "target {k}");
7022 }
7023 }
7024
7025 #[test]
7026 fn group_near_limit_matches_near_path() {
7027 let mut runs = Vec::new();
7030 for limit in [Some(0), Some(usize::MAX)] {
7031 counter::set_near_limit(limit);
7032 counter::take_aborted();
7033 let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7034 let mut rng = Lcg(0x11);
7035 let mut out = Vec::new();
7036 for _ in 0..20 {
7037 let m = meshes[rng.below(4)];
7038 let t = [rng.range(-0.05, 0.05), rng.range(-0.05, 0.05), 0.0];
7039 scene
7040 .set_placement(m, turned(rng.range(-5.0, 5.0), t))
7041 .unwrap();
7042 out.push((fields.map(|f| shown(&scene, f)), scene.take_changes()));
7043 }
7044 runs.push((out, counter::take_aborted()));
7045 }
7046 counter::set_near_limit(None);
7047 assert!(runs[0].0 == runs[1].0);
7048 assert!(runs[0].1 > 0);
7049 assert_eq!(runs[1].1, 0);
7050 }
7051
7052 type SeenGroup = (Vec<(Vec<u32>, Vec<u32>)>, Vec<crate::MeshChanges>);
7055
7056 fn seen_group(scene: &mut Scene, fields: &[FieldId; 4]) -> SeenGroup {
7057 let shown = fields.iter().map(|&f| shown(scene, f)).collect();
7058 (shown, scene.take_changes())
7059 }
7060
7061 #[test]
7062 fn group_stepped_matches_one_shot() {
7063 let budgets = [1, 23, usize::MAX];
7066 let mut twins: Vec<_> = (0..4)
7067 .map(|_| {
7068 let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7069 scene.set_defer_fields(true);
7070 scene.take_changes();
7071 (scene, meshes, fields)
7072 })
7073 .collect();
7074 let mut rng = Lcg(0x5e9);
7075 let mut abandons = Lcg(3);
7076 let mut spread = [0usize; 3];
7077 for step in 0..25 {
7078 let m = rng.below(4);
7079 let p = turned(
7080 rng.range(-8.0, 8.0),
7081 [
7082 rng.range(-0.05, 0.05),
7083 rng.range(-0.05, 0.05),
7084 rng.range(-0.03, 0.03),
7085 ],
7086 );
7087 let edit = rng.below(3) == 0;
7088 let z = rng.range(-0.1, 0.1) as f32;
7089 let mut out = Vec::new();
7090 for (k, (scene, meshes, fields)) in twins.iter_mut().enumerate() {
7091 scene.set_placement(meshes[m], p).unwrap();
7092 if edit {
7093 scene
7094 .edit(meshes[0], |tx| tx.set_positions(&[12], &[[0.2, 0.1, z]]))
7095 .unwrap();
7096 }
7097 match k {
7098 0 => spread[0] += step_all(scene, budgets[0]),
7099 1 => spread[1] += step_all_abandoning(scene, budgets[1], &mut abandons),
7100 2 => spread[2] += step_all(scene, budgets[2]),
7101 _ => update_all(scene),
7102 }
7103 out.push(seen_group(scene, fields));
7104 }
7105 for k in 0..3 {
7106 assert!(out[k] == out[3], "step {step}, twin {k}");
7107 }
7108 }
7109 assert!(spread[0] > 0 && spread[1] > 0, "{spread:?}");
7110 assert_eq!(spread[2], 0);
7111 }
7112
7113 #[test]
7114 fn removing_a_target_keeps_the_field() {
7115 let (mut scene, [a, b, c, d], [fa, fb, fc, fd]) = group_scene(UpdateMode::Eager);
7116 scene
7117 .set_coloring(
7118 a,
7119 Some(Coloring {
7120 field: fa,
7121 rule: contact_rule(0.05),
7122 }),
7123 )
7124 .unwrap();
7125 scene.take_changes();
7126 scene.remove_mesh(c).unwrap();
7127 assert_eq!(scene.field_values(fc), Err(SceneError::UnknownField));
7129 assert_eq!(scene.field_targets(fa), Ok(&[b, d][..]));
7130 assert_eq!(scene.field_targets(fb), Ok(&[a][..]));
7131 assert_eq!(scene.field_target_status(fa).unwrap().len(), 2);
7132 for f in [fa, fb, fd] {
7133 assert!(scene.field_status(f).unwrap().current);
7134 }
7135 let got = shown(&scene, fa);
7136 assert_eq!(got, min_of_singles(&mut scene, fa));
7137 assert!(got.1.iter().all(|&n| n < 2 || n == u32::MAX));
7138 assert!(got.1.contains(&1));
7139 assert_colors(&scene, a, &contact_rule(0.05), fa);
7140 let ch = scene.take_changes();
7141 assert!(
7142 ch.iter()
7143 .find(|c| c.id == a)
7144 .unwrap()
7145 .fields_changed
7146 .contains(&fa)
7147 );
7148 scene.set_defer_fields(true);
7150 scene.remove_mesh(d).unwrap();
7151 assert_eq!(scene.field_targets(fa), Ok(&[b][..]));
7152 assert!(!scene.field_status(fa).unwrap().current);
7153 assert!(
7154 scene
7155 .field_nearest(fa)
7156 .unwrap()
7157 .iter()
7158 .all(|&n| n == 0 || n == u32::MAX)
7159 );
7160 counter::take();
7161 step_all(&mut scene, usize::MAX);
7162 assert_eq!(counter::take(), scene.mesh(a).unwrap().vertex_count());
7163 assert_eq!(shown(&scene, fa), min_of_singles(&mut scene, fa));
7164 scene.remove_mesh(b).unwrap();
7166 assert_eq!(scene.field_values(fa), Err(SceneError::UnknownField));
7167 assert_eq!(scene.field_values(fb), Err(SceneError::UnknownField));
7168 assert_eq!(scene.colors(a), Ok(None));
7169 assert!(scene.fields.is_empty());
7170 }
7171
7172 #[test]
7173 fn compact_remaps_nearest() {
7174 let (mut scene, [a, ..], [fa, ..]) = group_scene(UpdateMode::Eager);
7175 scene
7177 .edit(a, |tx| {
7178 let tris: Vec<u32> = (0..tx.mesh().triangle_slots() as u32)
7179 .filter(|&t| tx.mesh().triangle(t).is_some_and(|tri| tri.contains(&0)))
7180 .collect();
7181 tx.remove_triangles(&tris)?;
7182 tx.remove_vertices(&[0])
7183 })
7184 .unwrap();
7185 let before = scene.field_nearest(fa).unwrap().to_vec();
7186 assert!(before.iter().any(|&n| n != u32::MAX));
7187 scene.set_defer_fields(true);
7188 let remap = scene.compact(a).unwrap();
7189 let after = scene.field_nearest(fa).unwrap();
7190 assert_eq!(after.len(), scene.mesh(a).unwrap().vertex_slots());
7191 for (v, &n) in before.iter().enumerate() {
7192 if let Some(w) = remap.vertex(v as VertexId) {
7193 assert_eq!(after[w as usize], n, "vertex {v}");
7194 }
7195 }
7196 step_all(&mut scene, usize::MAX);
7197 assert_eq!(shown(&scene, fa), min_of_singles(&mut scene, fa));
7198 }
7199
7200 #[test]
7201 fn invalid_target_lists_rejected() {
7202 let (mut scene, [a, b, c, _], _) = group_scene(UpdateMode::Eager);
7203 let bad = |e| matches!(e, Err(SceneError::InvalidInput(_)));
7204 let desc = |to: Vec<MeshId>| DistanceField {
7205 to,
7206 ..field("x", a, b, 0.1)
7207 };
7208 assert!(bad(scene.add_distance_field(desc(vec![]))));
7209 assert!(bad(scene.add_distance_field(desc(vec![b, c, b]))));
7210 assert!(bad(scene.add_distance_field(desc(vec![b, a]))));
7211 assert_eq!(scene.fields.len(), 4);
7212 let id = scene.add_distance_field(desc(vec![c, b])).unwrap();
7213 assert_eq!(scene.field_targets(id), Ok(&[c, b][..]));
7214 let one = DistanceField::new("y", a, b, 0.1, SignMode::Auto);
7216 assert_eq!(one.to, [b]);
7217 scene.add_distance_field(one).unwrap();
7218 }
7219
7220 #[test]
7221 fn moving_one_target_spares_vertices_another_won() {
7222 counter::set_near_limit(Some(usize::MAX));
7226 let mut scene = Scene::new(SceneOptions::default());
7227 let b = scene
7228 .add_mesh(sheet(10, 0.1), MeshOptions::default())
7229 .unwrap();
7230 let a = scene
7231 .add_mesh(cube([0.05, 0.05, -0.1], 0.2), MeshOptions::default())
7232 .unwrap();
7233 let c = scene
7234 .add_mesh(cube([0.3, 0.3, -0.74], 0.7), MeshOptions::default())
7235 .unwrap();
7236 let fb = scene
7237 .add_distance_field(DistanceField {
7238 to: vec![a, c],
7239 ..field("d", b, b, 0.05)
7240 })
7241 .unwrap();
7242 let (_, nearest) = shown(&scene, fb);
7243 let by = |k: u32| nearest.iter().filter(|&&n| n == k).count();
7244 let (won_by_a, won_by_c) = (by(0), by(1));
7245 assert!(won_by_a > 0 && won_by_c > 0);
7246 counter::take_applied_counts();
7247 scene.set_placement(a, at(0.01, 0.0, 0.0)).unwrap();
7248 let counts = counter::take_applied_counts();
7249 assert_eq!(counts.len(), 1);
7250 let (f, recomputed) = counts[0];
7251 assert_eq!(f, fb);
7252 let slots = scene.mesh(b).unwrap().vertex_slots();
7253 assert!(recomputed > 0, "nothing recomputed");
7254 assert!(
7255 recomputed + won_by_c <= slots,
7256 "{recomputed} + {won_by_c} > {slots}"
7257 );
7258 assert_eq!(shown(&scene, fb), min_of_singles(&mut scene, fb));
7260 counter::set_near_limit(None);
7261 }
7262
7263 #[test]
7264 fn status_aggregates_targets() {
7265 let (scene, [a, ..], [_, fb, fc, _]) = group_scene(UpdateMode::Eager);
7266 let each = scene.field_target_status(fb).unwrap();
7268 assert_eq!(each.len(), 2);
7269 assert!(!each[0].closed && each[1].closed);
7270 let all = scene.field_status(fb).unwrap().sign;
7271 assert!(!all.closed);
7272 assert_eq!(
7273 all.consistent_orientation,
7274 each.iter().all(|s| s.consistent_orientation)
7275 );
7276 assert_eq!(
7277 all.non_manifold_vertices,
7278 each.iter().map(|s| s.non_manifold_vertices).sum::<usize>()
7279 );
7280 assert_eq!(
7281 all.degenerate_triangles,
7282 each.iter().map(|s| s.degenerate_triangles).sum::<usize>()
7283 );
7284 assert_eq!(all.mode, each[0].mode);
7285 assert!(!scene.field_status(fc).unwrap().sign.closed);
7287 let _ = a;
7288 }
7289
7290 #[test]
7291 fn group_walk_with_limits_steps_and_removal() {
7292 for seed in 0..4u64 {
7297 let mut runs = Vec::new();
7298 for limit in [Some(0), None] {
7299 for budget in [Some(1), Some(17), None] {
7300 counter::set_near_limit(limit);
7301 let (mut scene, meshes, fields) = group_scene(UpdateMode::Eager);
7302 scene.set_defer_fields(budget.is_some());
7303 scene.take_changes();
7304 let mut rng = Lcg(seed);
7305 let mut out = Vec::new();
7306 for step in 0..16 {
7307 if step == 8 {
7308 scene.remove_mesh(meshes[2]).unwrap();
7309 }
7310 let k = [0, 1, 3, 0][rng.below(4)];
7311 let t = [
7312 rng.range(-0.04, 0.04),
7313 rng.range(-0.04, 0.04),
7314 rng.range(-0.03, 0.03),
7315 ];
7316 scene
7317 .set_placement(meshes[k], turned(rng.range(-6.0, 6.0), t))
7318 .unwrap();
7319 if rng.below(3) == 0 {
7320 let z = rng.range(-0.1, 0.1) as f32;
7321 scene
7322 .edit(meshes[0], |tx| tx.set_positions(&[23], &[[0.3, 0.2, z]]))
7323 .unwrap();
7324 }
7325 if let Some(budget) = budget {
7326 step_all(&mut scene, budget);
7327 }
7328 let live: Vec<FieldId> = fields
7329 .iter()
7330 .copied()
7331 .filter(|&f| scene.field_targets(f).is_ok())
7332 .collect();
7333 let shownall: Vec<_> = live.iter().map(|&f| shown(&scene, f)).collect();
7334 out.push((live, shownall, scene.take_changes()));
7335 }
7336 counter::set_near_limit(None);
7337 for f in fields {
7338 if scene.field_targets(f).is_ok() {
7339 assert_eq!(
7340 shown(&scene, f),
7341 min_of_singles(&mut scene, f),
7342 "seed {seed}"
7343 );
7344 }
7345 }
7346 runs.push(out);
7347 }
7348 }
7349 for (k, run) in runs.iter().enumerate().skip(1) {
7350 assert!(*run == runs[0], "seed {seed}, run {k}");
7351 }
7352 }
7353 }
7354
7355 fn bare_pair(rng: &mut Lcg) -> (Scene, MeshId, MeshId) {
7358 let mut scene = Scene::new(SceneOptions::default());
7359 let a = scene
7360 .add_mesh(sheet(10, 0.1), MeshOptions::default())
7361 .unwrap();
7362 let b = scene
7363 .add_mesh(cube([0.3, 0.3, -0.2], 0.4), MeshOptions::default())
7364 .unwrap();
7365 scene.set_defer_fields(true);
7366 let p = turned(rng.range(-20.0, 20.0), [0.0, 0.0, rng.range(-0.1, 0.1)]);
7367 scene.set_placement(b, p).unwrap();
7368 scene.take_changes();
7369 (scene, a, b)
7370 }
7371
7372 fn step_field_all(
7376 scene: &mut Scene,
7377 id: FieldId,
7378 budget: usize,
7379 abandon_one_in: usize,
7380 rng: &mut Lcg,
7381 ) -> usize {
7382 let (mut pending, mut abandoned) = (0, 0);
7383 for _ in 0..1_000_000 {
7384 match scene
7385 .field_step_field(id, FieldBudget::Points(budget))
7386 .unwrap()
7387 {
7388 StepState::Idle => return pending,
7389 StepState::Pending => {
7390 pending += 1;
7391 if abandon_one_in > 0 && abandoned < 3 && rng.below(abandon_one_in) == 0 {
7393 scene.abandon_field_step();
7394 abandoned += 1;
7395 }
7396 }
7397 StepState::Done => {}
7398 }
7399 }
7400 panic!("field_step_field never went idle");
7401 }
7402
7403 #[test]
7404 fn deferred_add_matches_eager_add() {
7405 let mut rng = Lcg(0xadd5);
7409 let mut spread = 0;
7410 for round in 0..12 {
7411 let state = rng.0;
7412 let md = random_max_distance(&mut rng);
7413 let sign = random_sign(&mut rng);
7414 let budget = [1, 13, 500, usize::MAX][round % 4];
7415 let abandon = if round % 3 == 0 { 4 } else { 0 };
7416 let mut out = Vec::new();
7417 for deferred in [false, true] {
7418 let mut r = Lcg(state);
7419 let (mut scene, a, b) = bare_pair(&mut r);
7420 let desc = DistanceField {
7421 sign,
7422 ..field("d", a, b, md)
7423 };
7424 let id = if deferred {
7425 let id = scene.add_distance_field_deferred(desc).unwrap();
7426 assert!(!scene.field_status(id).unwrap().current);
7427 assert!(
7428 scene
7429 .take_changes()
7430 .iter()
7431 .all(|c| c.fields_changed.is_empty())
7432 );
7433 spread += step_field_all(&mut scene, id, budget, abandon, &mut r);
7434 id
7435 } else {
7436 scene.add_distance_field(desc).unwrap()
7437 };
7438 assert!(scene.field_status(id).unwrap().current, "round {round}");
7439 scene
7440 .set_coloring(
7441 a,
7442 Some(Coloring {
7443 field: id,
7444 rule: distance_rule(0.2, 0.1),
7445 }),
7446 )
7447 .unwrap();
7448 let status = scene.field_target_status(id).unwrap();
7449 let nearest = scene.field_nearest(id).unwrap().to_vec();
7450 out.push((seen(&mut scene, [a, b]), status, nearest));
7451 }
7452 assert_eq!(out[0], out[1], "round {round} budget {budget}");
7453 }
7454 assert!(spread > 100, "{spread}");
7455 }
7456
7457 #[test]
7458 fn deferred_add_of_several_targets_matches() {
7459 let make = || {
7462 let (mut scene, [a, b, c, d], fields) = group_scene(UpdateMode::Eager);
7463 for f in fields {
7464 scene.remove_field(f).unwrap();
7465 }
7466 scene.set_defer_fields(true);
7467 scene.take_changes();
7468 (scene, [a, b, c, d])
7469 };
7470 let (mut eager, [a, b, c, d]) = make();
7471 let (mut stepped, _) = make();
7472 let desc = DistanceField {
7473 name: "multi".to_owned(),
7474 on: a,
7475 to: vec![b, c, d],
7476 max_distance: 0.3,
7477 sign: SignMode::Auto,
7478 };
7479 let fe = eager.add_distance_field(desc.clone()).unwrap();
7480 let fs = stepped.add_distance_field_deferred(desc).unwrap();
7481 let before = stepped.field_target_status(fs).unwrap();
7482 assert!(
7483 before
7484 .iter()
7485 .all(|s| !s.closed && s.mode == SignMode::Pseudonormal)
7486 );
7487 assert!(step_field_all(&mut stepped, fs, 1, 0, &mut Lcg(1)) > 50);
7488 assert_eq!(
7489 bits(eager.field_values(fe).unwrap()),
7490 bits(stepped.field_values(fs).unwrap())
7491 );
7492 assert_eq!(
7493 eager.field_nearest(fe).unwrap(),
7494 stepped.field_nearest(fs).unwrap()
7495 );
7496 assert_eq!(
7497 eager.field_target_status(fe).unwrap(),
7498 stepped.field_target_status(fs).unwrap()
7499 );
7500 assert_eq!(eager.take_changes(), stepped.take_changes());
7501 }
7502
7503 #[test]
7504 fn sign_data_builds_in_steps_and_resumes() {
7505 let (mut scene, a, b, [fa, fb]) = deferred_pair_signed(0.2, SignMode::Winding);
7509 let (mut twin, ..) = deferred_pair_signed(0.2, SignMode::Winding);
7510 let (mut probe, ..) = deferred_pair_signed(0.2, SignMode::Winding);
7511 let edit = |s: &mut Scene| {
7512 s.edit(b, |tx| tx.set_positions(&[0], &[[0.31, 0.29, -0.21]]))
7513 .unwrap();
7514 };
7515 edit(&mut scene);
7516 edit(&mut twin);
7517 edit(&mut probe);
7518 counter::take_sign_steps();
7519 assert!(step_all(&mut probe, 1) > 0);
7520 let unbroken = counter::take_sign_steps();
7521 assert!(unbroken >= 4, "{unbroken}");
7522 assert_eq!(
7525 scene.field_step(FieldBudget::Points(1)),
7526 Ok(StepState::Pending)
7527 );
7528 assert_eq!(scene.field_step_stage(), Some(Stage::Sign(0)));
7529 for _ in 0..unbroken / 2 {
7530 assert_eq!(
7531 scene.field_step(FieldBudget::Points(1)),
7532 Ok(StepState::Pending)
7533 );
7534 }
7535 scene.abandon_field_step();
7536 scene.set_placement(a, at(0.0, 0.0, 0.01)).unwrap();
7537 twin.set_placement(a, at(0.0, 0.0, 0.01)).unwrap();
7538 step_all(&mut scene, 1);
7539 let total = counter::take_sign_steps();
7540 twin.update_fields().unwrap();
7541 assert!(
7542 total >= unbroken && total <= unbroken + 2,
7543 "{total} against {unbroken}"
7544 );
7545 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
7546 edit(&mut scene);
7549 edit(&mut twin);
7550 for _ in 0..5 {
7551 assert_eq!(
7552 scene.field_step(FieldBudget::Points(1)),
7553 Ok(StepState::Pending)
7554 );
7555 }
7556 let moved = |s: &mut Scene| {
7557 s.edit(b, |tx| tx.set_positions(&[1], &[[0.7, 0.31, -0.2]]))
7558 .unwrap();
7559 };
7560 moved(&mut scene);
7561 moved(&mut twin);
7562 step_all(&mut scene, 3);
7563 twin.update_fields().unwrap();
7564 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
7565 assert!(scene.field_status(fa).unwrap().current);
7566 assert!(scene.field_status(fb).unwrap().current);
7567 }
7568
7569 #[test]
7570 fn near_search_runs_in_steps() {
7571 let make = || {
7574 let mut scene = Scene::new(SceneOptions::default());
7575 let a = scene
7576 .add_mesh(sheet(120, 1.0 / 120.0), MeshOptions::default())
7577 .unwrap();
7578 let opts = MeshOptions {
7579 placement: at(0.0, 0.0, 0.01),
7580 ..MeshOptions::default()
7581 };
7582 let b = scene.add_mesh(sheet(120, 1.0 / 120.0), opts).unwrap();
7583 let f = scene.add_distance_field(field("d", a, b, 0.02)).unwrap();
7584 scene.set_defer_fields(true);
7585 scene
7586 .set_placement(b, turned(1.0, [0.0, 0.0, 0.012]))
7587 .unwrap();
7588 scene.take_changes();
7589 (scene, a, b, f)
7590 };
7591 counter::set_near_limit(Some(usize::MAX));
7592 let (mut scene, a, b, _) = make();
7593 let (mut twin, ..) = make();
7594 let mut near_steps = 0;
7595 for _ in 0..1_000_000 {
7596 if scene.field_step_stage() == Some(Stage::Near(0)) {
7597 near_steps += 1;
7598 }
7599 if scene.field_step(FieldBudget::Points(1)).unwrap() != StepState::Pending {
7600 break;
7601 }
7602 }
7603 twin.update_fields().unwrap();
7604 counter::set_near_limit(None);
7605 assert!(near_steps > 3, "{near_steps}");
7606 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
7607 }
7608
7609 #[test]
7610 fn withdraw_field_only_before_it_is_computed() {
7611 let (mut scene, a, b) = sheet_and_cube(UpdateMode::Eager);
7612 scene.take_changes();
7613 let f = scene
7614 .add_distance_field_deferred(field("d", a, b, 0.1))
7615 .unwrap();
7616 assert_eq!(scene.withdraw_field(f), Ok(true));
7617 assert_eq!(scene.field_values(f), Err(SceneError::UnknownField));
7618 assert!(
7619 scene.take_changes().is_empty(),
7620 "a withdrawn field reported"
7621 );
7622 assert_eq!(scene.withdraw_field(f), Err(SceneError::UnknownField));
7623 let f = scene
7625 .add_distance_field_deferred(field("d", a, b, 0.1))
7626 .unwrap();
7627 scene
7628 .field_step_field(f, FieldBudget::Points(usize::MAX))
7629 .unwrap();
7630 assert_eq!(scene.withdraw_field(f), Ok(false));
7631 assert!(scene.field_status(f).unwrap().current);
7632 let g = scene
7634 .add_distance_field_deferred(field("g", a, b, 0.1))
7635 .unwrap();
7636 let rule = contact_rule(0.1);
7637 scene
7638 .set_coloring(a, Some(Coloring { field: g, rule }))
7639 .unwrap();
7640 assert_eq!(scene.withdraw_field(g), Ok(false));
7641 let h = scene.add_distance_field(field("h", a, b, 0.1)).unwrap();
7643 assert_eq!(scene.withdraw_field(h), Ok(false));
7644 }
7645
7646 #[test]
7647 fn field_step_field_steps_one_field() {
7648 let (mut scene, a, b, [fa, fb]) = deferred_pair(0.2);
7649 scene
7650 .set_placement(b, turned(5.0, [0.02, 0.0, 0.0]))
7651 .unwrap();
7652 scene.take_changes();
7653 assert_eq!(
7655 scene.field_step(FieldBudget::Points(1)),
7656 Ok(StepState::Pending)
7657 );
7658 assert_eq!(
7659 scene.field_step_field(fb, FieldBudget::Points(1)),
7660 Ok(StepState::Pending)
7661 );
7662 assert_eq!(scene.field_step_meshes(), vec![b, a]);
7663 assert_eq!(
7665 scene.field_step(FieldBudget::Points(1)),
7666 Ok(StepState::Pending)
7667 );
7668 assert_eq!(scene.field_step_meshes(), vec![b, a]);
7669 while scene.field_step_field(fb, FieldBudget::Points(7)).unwrap() == StepState::Pending {}
7670 assert!(scene.field_status(fb).unwrap().current);
7671 assert!(!scene.field_status(fa).unwrap().current);
7672 assert_eq!(
7673 scene.field_step_field(fb, FieldBudget::Points(7)),
7674 Ok(StepState::Idle)
7675 );
7676 let changed: Vec<MeshId> = scene.take_changes().iter().map(|c| c.id).collect();
7677 assert!(!changed.contains(&a) || scene.colors(a).is_ok());
7678 assert_eq!(
7679 scene.field_step_field(FieldId(999), FieldBudget::Points(1)),
7680 Err(SceneError::UnknownField)
7681 );
7682 }
7683
7684 #[test]
7685 fn sign_build_survives_edit_and_undo_of_a_manual_target() {
7686 let make = || {
7690 let mut scene = Scene::new(SceneOptions::default());
7691 let a = scene
7692 .add_mesh(sheet(10, 0.1), MeshOptions::default())
7693 .unwrap();
7694 let b = scene
7695 .add_mesh(sheet(30, 1.0 / 30.0), with_mode(UpdateMode::Manual))
7696 .unwrap();
7697 scene.set_placement(b, at(0.0, 0.0, 0.05)).unwrap();
7698 let f = scene
7699 .add_distance_field_deferred(field("d", a, b, 0.2))
7700 .unwrap();
7701 scene.take_changes();
7702 (scene, a, b, f)
7703 };
7704 let (mut scene, a, b, f) = make();
7705 let (mut twin, ..) = make();
7706 for _ in 0..3 {
7707 assert_eq!(
7708 scene.field_step_field(f, FieldBudget::Points(1)),
7709 Ok(StepState::Pending)
7710 );
7711 }
7712 assert_eq!(scene.field_step_stage(), Some(Stage::Sign(0)));
7713 let grow = |s: &mut Scene| {
7714 s.edit(b, |tx| {
7715 let vs = tx.add_vertices(&[[5.0, 5.0, 0.0], [6.0, 5.0, 0.0], [5.0, 6.0, 0.0]])?;
7716 tx.add_triangles(&[[vs[0], vs[1], vs[2]]])
7717 })
7718 .unwrap();
7719 s.undo().unwrap();
7720 };
7721 grow(&mut scene);
7722 grow(&mut twin);
7723 while scene.field_step_field(f, FieldBudget::Points(1)).unwrap() != StepState::Idle {}
7724 twin.update_fields().unwrap();
7725 assert_eq!(seen(&mut scene, [a, b]).0, seen(&mut twin, [a, b]).0);
7726 assert!(scene.field_status(f).unwrap().current);
7727 let g = twin.field_id(f.raw()).unwrap();
7728 assert_eq!(scene.field_target_status(f), twin.field_target_status(g));
7729 }
7730
7731 #[test]
7732 fn stale_partial_sign_builds_are_dropped() {
7733 let (mut scene, _, b, [fa, _]) = deferred_pair_signed(0.2, SignMode::Winding);
7737 let edit = |s: &mut Scene, z: f32| {
7738 s.edit(b, |tx| tx.set_positions(&[0], &[[0.31, 0.29, z]]))
7739 .unwrap();
7740 };
7741 let partial = |s: &mut Scene| {
7742 for _ in 0..2 {
7743 assert_eq!(s.field_step(FieldBudget::Points(1)), Ok(StepState::Pending));
7744 }
7745 s.abandon_field_step();
7746 };
7747 edit(&mut scene, -0.21);
7748 partial(&mut scene);
7749 assert_eq!(scene.kept_sign_builds(), 1);
7750 edit(&mut scene, -0.22);
7752 scene.set_defer_fields(true);
7753 scene.take_changes();
7754 scene.abandon_field_step();
7755 scene.set_placement(b, at(0.0, 0.0, 0.0)).unwrap();
7756 assert_eq!(scene.kept_sign_builds(), 0);
7757 partial(&mut scene);
7759 assert_eq!(scene.kept_sign_builds(), 1);
7760 scene.update_fields().unwrap();
7761 assert_eq!(scene.kept_sign_builds(), 0);
7762 edit(&mut scene, -0.23);
7764 partial(&mut scene);
7765 assert_eq!(scene.kept_sign_builds(), 1);
7766 scene.remove_field(fa).unwrap();
7767 assert_eq!(scene.kept_sign_builds(), 0);
7768 }
7769
7770 #[test]
7771 fn stepped_unit_bit_identical_across_pools() {
7772 use fast_mesh_store::par;
7777 let run = |threads: Option<usize>| {
7778 let go = || {
7779 let mut scene = Scene::new(SceneOptions::default());
7780 let a = scene
7781 .add_mesh(sheet(90, 1.0 / 90.0), MeshOptions::default())
7782 .unwrap();
7783 let opts = MeshOptions {
7784 placement: at(0.0, 0.0, 0.04),
7785 ..MeshOptions::default()
7786 };
7787 let b = scene.add_mesh(sheet(90, 1.0 / 90.0), opts).unwrap();
7788 scene.set_defer_fields(true);
7789 let f = scene
7790 .add_distance_field_deferred(field("d", a, b, 0.1))
7791 .unwrap();
7792 let one_call = threads.is_none();
7793 let settle = |s: &mut Scene| {
7794 if one_call {
7795 s.update_fields().unwrap();
7796 } else {
7797 for k in 0.. {
7798 match s.field_step(FieldBudget::Points(97)).unwrap() {
7799 StepState::Pending if k == 3 => s.abandon_field_step(),
7800 StepState::Pending | StepState::Done => {}
7801 _ => break,
7802 }
7803 }
7804 }
7805 };
7806 if one_call {
7807 scene.update_fields().unwrap();
7808 } else {
7809 while scene.field_step_field(f, FieldBudget::Points(97)).unwrap()
7810 != StepState::Idle
7811 {}
7812 }
7813 let mut out = vec![seen(&mut scene, [a, b])];
7814 scene
7815 .set_placement(b, turned(2.0, [0.01, 0.0, 0.035]))
7816 .unwrap();
7817 settle(&mut scene);
7818 out.push(seen(&mut scene, [a, b]));
7819 scene
7820 .edit(b, |tx| tx.set_positions(&[100], &[[0.5, 0.5, -0.01]]))
7821 .unwrap();
7822 settle(&mut scene);
7823 out.push(seen(&mut scene, [a, b]));
7824 out
7825 };
7826 match threads {
7827 Some(k) => par::with_threads(k, go),
7828 None => {
7829 par::set_force_sequential(true);
7830 let out = go();
7831 par::set_force_sequential(false);
7832 out
7833 }
7834 }
7835 };
7836 let want = run(None);
7837 for k in [1, 8] {
7838 let got = run(Some(k));
7839 for (g, w) in got.iter().zip(&want) {
7842 assert_eq!((&g.0, &g.1), (&w.0, &w.1), "{k} threads");
7843 }
7844 }
7845 }
7846
7847 fn bx(min: [f32; 3], size: [f32; 3]) -> Mesh {
7849 let mut pos = Vec::new();
7850 for i in 0..8u32 {
7851 let b = |bit: u32, s: f32| if i & bit != 0 { s } else { 0.0 };
7852 pos.extend_from_slice(&[
7853 min[0] + b(1, size[0]),
7854 min[1] + b(2, size[1]),
7855 min[2] + b(4, size[2]),
7856 ]);
7857 }
7858 let idx = [
7859 0, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 0, 1, 4, 1, 5, 4, 2, 6, 3, 3, 6, 7, 0, 4, 2, 2, 4,
7860 6, 1, 3, 5, 3, 7, 5,
7861 ];
7862 Mesh::from_buffers(&pos, &idx).unwrap()
7863 }
7864
7865 fn scaled_placement(translation: [f64; 3], scale: [f64; 3]) -> MeshOptions {
7866 MeshOptions {
7867 placement: Placement {
7868 rotation: Placement::IDENTITY.rotation,
7869 translation,
7870 scale,
7871 },
7872 ..MeshOptions::default()
7873 }
7874 }
7875
7876 fn scaled_pair(place_b: MeshOptions) -> (Scene, MeshId, MeshId) {
7879 let mut scene = Scene::new(SceneOptions::default());
7880 let a = scene
7881 .add_mesh(cube([3.0, 0.0, 0.0], 1.0), MeshOptions::default())
7882 .unwrap();
7883 let b = scene.add_mesh(cube([0.0; 3], 1.0), place_b).unwrap();
7884 (scene, a, b)
7885 }
7886
7887 fn scaled_values(scene: &mut Scene, on: MeshId, to: &[MeshId]) -> (Vec<f32>, Vec<u32>) {
7888 let desc = DistanceField {
7889 name: "d".to_owned(),
7890 on,
7891 to: to.to_vec(),
7892 max_distance: f64::INFINITY,
7893 sign: SignMode::Auto,
7894 };
7895 let id = scene.add_distance_field(desc).unwrap();
7896 (
7897 scene.field_values(id).unwrap().to_vec(),
7898 scene.field_nearest(id).unwrap().to_vec(),
7899 )
7900 }
7901
7902 #[test]
7903 fn scaled_targets_match_rigid_twins() {
7904 for scale in [[2.0, 2.0, 2.0], [2.0, 1.0, 0.5], [0.5, 2.0, 1.5]] {
7907 let (mut scaled, a, b) = scaled_pair(scaled_placement([0.0; 3], scale));
7908 let (mut rigid, ra, rb) = scaled_pair(MeshOptions::default());
7909 let twin = rigid
7911 .add_mesh(
7912 bx([0.0; 3], scale.map(|s| s as f32)),
7913 MeshOptions::default(),
7914 )
7915 .unwrap();
7916 rigid.remove_mesh(rb).unwrap();
7917 let (got, got_near) = scaled_values(&mut scaled, a, &[b]);
7918 let (want, want_near) = scaled_values(&mut rigid, ra, &[twin]);
7919 assert_eq!(got.len(), want.len());
7920 assert_eq!(got_near, want_near, "scale {scale:?}");
7921 for (v, (g, w)) in got.iter().zip(&want).enumerate() {
7922 assert!(
7923 close(*g, *w, f64::INFINITY),
7924 "scale {scale:?} vertex {v}: got {g}, want {w}"
7925 );
7926 }
7927 assert!(got.iter().all(|&x| x > 0.0), "scale {scale:?}: {got:?}");
7929 let ma = scaled.mesh(a).unwrap();
7931 for (v, &x) in got.iter().enumerate() {
7932 let p = ma.position(v as VertexId).unwrap();
7933 let w = scaled.local_to_world(a, p.map(f64::from)).unwrap();
7934 if (w[0] - 3.0).abs() < 1e-9 && w[2] < scale[2] - 0.25 {
7936 assert!((x - (3.0 - scale[0]) as f32).abs() < 1e-4, "v{v}: {x}");
7937 }
7938 }
7939 }
7940 }
7941
7942 #[test]
7943 fn scaled_a_matches_rigid_twin() {
7944 let scale = [2.0, 1.0, 1.0];
7948 let mut scaled = Scene::new(SceneOptions::default());
7949 let a = scaled
7950 .add_mesh(
7951 cube([0.0; 3], 1.0),
7952 scaled_placement([1.5, 0.0, 0.0], scale),
7953 )
7954 .unwrap();
7955 let b = scaled
7956 .add_mesh(cube([0.0; 3], 1.0), MeshOptions::default())
7957 .unwrap();
7958 let mut rigid = Scene::new(SceneOptions::default());
7959 let ra = rigid
7960 .add_mesh(bx([1.5, 0.0, 0.0], [2.0, 1.0, 1.0]), MeshOptions::default())
7961 .unwrap();
7962 let rb = rigid
7963 .add_mesh(cube([0.0; 3], 1.0), MeshOptions::default())
7964 .unwrap();
7965 let (got, got_near) = scaled_values(&mut scaled, a, &[b]);
7966 let (want, want_near) = scaled_values(&mut rigid, ra, &[rb]);
7967 assert_eq!(got_near, want_near);
7968 for (v, (g, w)) in got.iter().zip(&want).enumerate() {
7969 assert!(
7970 close(*g, *w, f64::INFINITY),
7971 "vertex {v}: got {g}, want {w}"
7972 );
7973 }
7974 let ma = scaled.mesh(a).unwrap();
7976 for (v, &x) in got.iter().enumerate() {
7977 let p = ma.position(v as VertexId).unwrap();
7978 let w = scaled.local_to_world(a, p.map(f64::from)).unwrap();
7979 if (w[0] - 1.5).abs() < 1e-9 {
7980 assert!((x - 0.5).abs() < 1e-4, "v{v}: {x}");
7981 }
7982 }
7983 }
7984
7985 #[test]
7986 fn scaled_multi_target_and_stepped_updates_match() {
7987 let (mut scaled, a, b) = scaled_pair(scaled_placement([0.0; 3], [2.0, 1.0, 1.0]));
7990 let c = scaled
7991 .add_mesh(cube([3.0, 2.0, 0.0], 1.0), MeshOptions::default())
7992 .unwrap();
7993 let (mut rigid, ra, _) = scaled_pair(MeshOptions::default());
7994 let twin = rigid
7995 .add_mesh(bx([0.0; 3], [2.0, 1.0, 1.0]), MeshOptions::default())
7996 .unwrap();
7997 let rc = rigid
7998 .add_mesh(cube([3.0, 2.0, 0.0], 1.0), MeshOptions::default())
7999 .unwrap();
8000 let (got, got_near) = scaled_values(&mut scaled, a, &[b, c]);
8001 let (want, want_near) = scaled_values(&mut rigid, ra, &[twin, rc]);
8002 assert_eq!(got_near, want_near);
8003 for (v, (g, w)) in got.iter().zip(&want).enumerate() {
8004 assert!(
8005 close(*g, *w, f64::INFINITY),
8006 "vertex {v}: got {g}, want {w}"
8007 );
8008 }
8009 let (mut eager, ea, eb) = scaled_pair(scaled_placement([0.0; 3], [2.0, 1.0, 0.5]));
8012 let (mut stepped, sa, sb) = scaled_pair(scaled_placement([0.0; 3], [2.0, 1.0, 0.5]));
8013 for scene in [&mut eager, &mut stepped] {
8014 scene.set_defer_fields(true);
8015 }
8016 let desc = DistanceField {
8017 name: "d".to_owned(),
8018 on: sa,
8019 to: vec![sb],
8020 max_distance: f64::INFINITY,
8021 sign: SignMode::Auto,
8022 };
8023 let id = stepped.add_distance_field(desc).unwrap();
8026 assert!(stepped.field_status(id).unwrap().current);
8027 let nudge = Placement {
8028 translation: [0.01, 0.0, 0.0],
8029 ..stepped.placement(sb).unwrap()
8030 };
8031 stepped.set_placement(sb, nudge).unwrap();
8032 assert!(!stepped.field_status(id).unwrap().current);
8033 loop {
8034 match stepped.field_step(FieldBudget::Points(3)).unwrap() {
8035 StepState::Done => break,
8036 StepState::Pending => {}
8037 StepState::Idle => panic!("a due field idles"),
8038 }
8039 }
8040 eager.set_placement(eb, nudge).unwrap();
8041 let want = scaled_values(&mut eager, ea, &[eb]).0;
8042 assert_eq!(bits(stepped.field_values(id).unwrap()), bits(&want));
8043 }
8044
8045 #[test]
8048 fn stepped_position_edit_refits_sign_data_like_a_rebuild() {
8049 let (mut scene, a, b, ids) = deferred_pair(f64::INFINITY);
8053 let (mut twin, ..) = deferred_pair(f64::INFINITY);
8054 twin.set_defer_fields(false);
8055 let edit = |s: &mut Scene, y: f32| {
8056 s.edit(b, |tx| tx.set_positions(&[0], &[[0.31, y, -0.21]]))
8057 .unwrap();
8058 };
8059 for y in [0.29, 0.27, 0.33] {
8060 edit(&mut scene, y);
8061 edit(&mut twin, y);
8062 counter::take_sign_steps();
8063 step_all(&mut scene, 1);
8064 assert_eq!(counter::take_sign_steps(), 0, "a refit, not a build");
8065 assert_eq!(seen(&mut scene, [a, b]), seen(&mut twin, [a, b]));
8066 }
8067 scene.set_defer_fields(false);
8068 let again = scene
8069 .add_distance_field(field("again", a, b, f64::INFINITY))
8070 .unwrap();
8071 assert_eq!(
8072 bits(scene.field_values(again).unwrap()),
8073 bits(scene.field_values(ids[0]).unwrap())
8074 );
8075 scene.set_defer_fields(true);
8077 scene.edit(b, |tx| tx.remove_triangles(&[0])).unwrap();
8078 counter::take_sign_steps();
8079 step_all(&mut scene, 1);
8080 assert!(counter::take_sign_steps() > 0);
8081 }
8082
8083 fn interacting_pair(md: f64) -> (Scene, MeshId, MeshId, [FieldId; 2]) {
8086 let mut scene = Scene::new(SceneOptions::default());
8087 let a = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8088 let b = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8089 scene.set_placement(b, at(1.2, 0.0, 0.0)).unwrap();
8090 let fa = scene.add_distance_field(field("d", a, b, md)).unwrap();
8091 let fb = scene.add_distance_field(field("d", b, a, md)).unwrap();
8092 for (m, f) in [(a, fa), (b, fb)] {
8093 let rule = distance_rule(0.5, 0.5);
8094 scene
8095 .set_coloring(m, Some(Coloring { field: f, rule }))
8096 .unwrap();
8097 }
8098 scene.take_changes();
8099 (scene, a, b, [fa, fb])
8100 }
8101
8102 #[test]
8103 fn interaction_recomputes_a_budget_then_settles_to_the_exact_field() {
8104 for md in [f64::INFINITY, 10.0] {
8106 for stepped in [false, true] {
8107 let (mut scene, a, b, [fa, fb]) = interacting_pair(md);
8108 let slots = scene.field_values(fa).unwrap().len();
8109 if stepped {
8110 scene.set_defer_fields(true);
8111 }
8112 scene.set_field_interaction(Some(slots / 8)).unwrap();
8113 assert_eq!(scene.field_interaction(), Some(slots / 8));
8114 for i in 1..=12 {
8115 counter::take();
8116 scene
8117 .set_placement(b, at(1.2 + 0.01 * f64::from(i), 0.0, 0.0))
8118 .unwrap();
8119 if stepped {
8120 step_all(&mut scene, 16);
8121 }
8122 let recomputed = counter::take();
8123 if i > 6 {
8126 assert!(recomputed < 2 * slots, "{recomputed} of {}", 2 * slots);
8127 }
8128 for f in [fa, fb] {
8130 assert!(scene.field_values(f).unwrap().iter().all(|x| x.is_finite()));
8131 }
8132 }
8133 scene.set_field_interaction(None).unwrap();
8135 assert_eq!(scene.field_interaction(), None);
8136 if stepped {
8137 assert!(!scene.field_status(fa).unwrap().current);
8138 step_all(&mut scene, 16);
8139 scene.set_defer_fields(false);
8140 }
8141 let exact_a = scene.add_distance_field(field("exact", a, b, md)).unwrap();
8142 let exact_b = scene.add_distance_field(field("exact", b, a, md)).unwrap();
8143 assert_eq!(
8144 bits(scene.field_values(fa).unwrap()),
8145 bits(scene.field_values(exact_a).unwrap())
8146 );
8147 assert_eq!(
8148 bits(scene.field_values(fb).unwrap()),
8149 bits(scene.field_values(exact_b).unwrap())
8150 );
8151 assert_matches(&mut scene, fa);
8153 assert_matches(&mut scene, fb);
8154 let rule = distance_rule(0.5, 0.5);
8155 assert_colors(&scene, a, &rule, fa);
8156 assert_colors(&scene, b, &rule, fb);
8157 }
8158 }
8159 }
8160
8161 #[test]
8162 fn interaction_without_a_change_costs_nothing_when_it_ends() {
8163 let (mut scene, _, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8164 scene.set_field_interaction(Some(4)).unwrap();
8165 scene.set_field_interaction(None).unwrap();
8166 assert!(scene.field_status(fa).unwrap().current);
8167 assert!(scene.field_status(fb).unwrap().current);
8168 scene.set_field_interaction(Some(1 << 20)).unwrap();
8170 scene.set_placement(b, at(1.25, 0.0, 0.0)).unwrap();
8171 assert_matches(&mut scene, fa);
8172 scene.set_field_interaction(None).unwrap();
8173 assert!(scene.field_status(fa).unwrap().current);
8174 }
8175
8176 #[test]
8177 fn interaction_covers_an_edit_of_either_mesh_and_settles() {
8178 let (mut scene, a, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8179 scene.set_field_interaction(Some(16)).unwrap();
8180 for (m, s) in [(a, 1.01f32), (b, 1.02), (a, 1.03)] {
8181 scene
8182 .edit(m, |tx| tx.set_positions(&[0], &[[s, 0.0, 0.0]]))
8183 .unwrap();
8184 }
8185 scene.set_field_interaction(None).unwrap();
8187 assert_matches(&mut scene, fa);
8188 assert_matches(&mut scene, fb);
8189 }
8190
8191 fn ball_dab(strength: f32) -> crate::Dab {
8193 crate::Dab::Inflate(fast_mesh_ops::InflateOptions {
8194 brush: fast_mesh_ops::Brush {
8195 center: [0.0, 0.0, 1.0],
8196 radius: 0.8,
8197 falloff: fast_mesh_ops::Falloff::Smooth,
8198 strength,
8199 },
8200 })
8201 }
8202
8203 #[test]
8204 fn a_dab_marks_fields_due_and_a_stepped_update_refits() {
8205 let (mut scene, a, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8208 scene.set_defer_fields(true);
8209 for (m, s) in [(b, 0.02), (a, 0.03), (b, -0.01)] {
8210 assert!(scene.dab(m, &ball_dab(s)).unwrap().moved > 0);
8211 assert!(!scene.field_status(fa).unwrap().current);
8212 assert!(!scene.field_status(fb).unwrap().current);
8213 counter::take_sign_steps();
8214 step_all(&mut scene, 16);
8215 assert_eq!(counter::take_sign_steps(), 0, "a refit, not a build");
8216 assert!(scene.field_status(fa).unwrap().current);
8217 assert!(scene.field_status(fb).unwrap().current);
8218 }
8219 scene.set_defer_fields(false);
8220 let exact_a = scene
8221 .add_distance_field(field("exact", a, b, f64::INFINITY))
8222 .unwrap();
8223 let exact_b = scene
8224 .add_distance_field(field("exact", b, a, f64::INFINITY))
8225 .unwrap();
8226 assert_eq!(
8227 bits(scene.field_values(fa).unwrap()),
8228 bits(scene.field_values(exact_a).unwrap())
8229 );
8230 assert_eq!(
8231 bits(scene.field_values(fb).unwrap()),
8232 bits(scene.field_values(exact_b).unwrap())
8233 );
8234 }
8235
8236 #[test]
8237 fn dabs_during_an_interaction_settle_to_the_exact_field() {
8238 for stepped in [false, true] {
8239 let (mut scene, a, b, [fa, fb]) = interacting_pair(f64::INFINITY);
8240 let before = scene.field_values(fa).unwrap().to_vec();
8241 scene.set_defer_fields(stepped);
8242 scene.set_field_interaction(Some(4)).unwrap();
8243 for (m, s) in [(a, 0.02), (b, 0.03), (a, 0.02)] {
8244 scene.dab(m, &ball_dab(s)).unwrap();
8245 if stepped {
8246 step_all(&mut scene, 16);
8247 }
8248 }
8249 assert_ne!(bits(scene.field_values(fa).unwrap()), bits(&before));
8250 scene.set_field_interaction(None).unwrap();
8251 if stepped {
8252 step_all(&mut scene, 16);
8253 scene.set_defer_fields(false);
8254 }
8255 assert_matches(&mut scene, fa);
8256 assert_matches(&mut scene, fb);
8257 }
8258 }
8259
8260 #[test]
8261 fn interaction_rejects_a_zero_budget() {
8262 let (mut scene, ..) = interacting_pair(f64::INFINITY);
8263 assert!(matches!(
8264 scene.set_field_interaction(Some(0)),
8265 Err(SceneError::InvalidInput(_))
8266 ));
8267 assert_eq!(scene.field_interaction(), None);
8268 }
8269
8270 #[test]
8271 fn interaction_is_bit_identical_for_any_thread_count() {
8272 let run = |threads: Option<usize>| {
8273 let go = || {
8274 let (mut scene, a, b, [fa, _]) = interacting_pair(f64::INFINITY);
8275 let slots = scene.field_values(fa).unwrap().len();
8276 scene.set_defer_fields(true);
8277 scene.set_field_interaction(Some(slots / 8)).unwrap();
8278 let mut out = Vec::new();
8279 for i in 1..=8 {
8280 scene
8281 .set_placement(b, at(1.2 + 0.015 * f64::from(i), 0.0, 0.0))
8282 .unwrap();
8283 step_all(&mut scene, 64);
8284 out.push(seen_values(&scene, [a, b]));
8285 }
8286 scene.set_field_interaction(None).unwrap();
8287 step_all(&mut scene, 64);
8288 out.push(seen_values(&scene, [a, b]));
8289 out
8290 };
8291 match threads {
8292 Some(k) => par::with_threads(k, go),
8293 None => {
8294 par::set_force_sequential(true);
8295 let out = go();
8296 par::set_force_sequential(false);
8297 out
8298 }
8299 }
8300 };
8301 let want = run(None);
8302 for k in [1, 8] {
8303 assert_eq!(run(Some(k)), want, "{k} threads");
8304 }
8305 }
8306
8307 fn assert_like_a_new_field(scene: &mut Scene, id: FieldId, step: &str) {
8310 let (on, to, md, sign) = {
8311 let f = scene.fields.iter().find(|f| f.id == id).unwrap();
8312 (
8313 f.desc.on,
8314 f.desc.to.clone(),
8315 f.desc.max_distance,
8316 f.desc.sign,
8317 )
8318 };
8319 let defer = scene.defer_fields;
8320 scene.set_defer_fields(false);
8321 let fresh = scene
8322 .add_distance_field(DistanceField {
8323 name: "fresh".to_owned(),
8324 on,
8325 to,
8326 max_distance: md,
8327 sign,
8328 })
8329 .unwrap();
8330 let got = bits(scene.field_values(id).unwrap());
8331 let want = bits(scene.field_values(fresh).unwrap());
8332 scene.remove_field(fresh).unwrap();
8333 scene.set_defer_fields(defer);
8334 let off = got.iter().zip(&want).filter(|(g, w)| g != w).count();
8335 assert_eq!(off, 0, "{step}: {off} of {} values differ", got.len());
8336 }
8337
8338 fn mirror(scene: &mut Scene, m: MeshId) {
8341 let mesh = &scene.entry(m).unwrap().mesh;
8342 let ids: Vec<VertexId> = (0..mesh.vertex_slots() as VertexId).collect();
8343 let at: Vec<[f32; 3]> = ids
8344 .iter()
8345 .map(|&v| {
8346 let p = mesh.position(v).unwrap();
8347 [p[0], p[1], -p[2]]
8348 })
8349 .collect();
8350 scene.edit(m, |tx| tx.set_positions(&ids, &at)).unwrap();
8351 }
8352
8353 fn bring_up_to_date(scene: &mut Scene, stepped: bool) {
8355 if stepped {
8356 step_all(scene, 16);
8357 } else {
8358 scene.update_fields().unwrap();
8359 }
8360 }
8361
8362 #[test]
8363 fn refit_after_a_baked_target_is_a_rebuild() {
8364 for stepped in [false, true] {
8368 let (mut scene, _, b, [fa, _]) = interacting_pair(f64::INFINITY);
8369 scene.set_defer_fields(stepped);
8370 let rigid = at(1.2, 0.0, 0.0);
8371 let scaled = Placement {
8372 scale: [2.0, 1.0, 1.0],
8373 ..rigid
8374 };
8375 scene.set_placement(b, scaled).unwrap();
8376 bring_up_to_date(&mut scene, stepped);
8377 mirror(&mut scene, b);
8378 bring_up_to_date(&mut scene, stepped);
8379 scene.set_placement(b, rigid).unwrap();
8380 bring_up_to_date(&mut scene, stepped);
8381 assert_like_a_new_field(&mut scene, fa, "rigid again");
8382 scene
8384 .edit(b, |tx| tx.set_positions(&[0], &[[1.1, 0.0, 0.0]]))
8385 .unwrap();
8386 bring_up_to_date(&mut scene, stepped);
8387 assert_like_a_new_field(&mut scene, fa, "an edit after it");
8388 }
8389 }
8390
8391 #[test]
8392 fn sequences_of_edits_match_a_new_field() {
8393 let mut rng = Lcg(0x5eed);
8397 for stepped in [false, true] {
8398 for round in 0..24 {
8399 let (mut scene, a, b, ids) = interacting_pair(f64::INFINITY);
8400 if round % 2 == 1 {
8401 let p = scene.entry(b).unwrap().mesh.position(7).unwrap();
8403 scene.edit(b, |tx| tx.set_positions(&[6], &[p])).unwrap();
8404 }
8405 scene.set_defer_fields(stepped);
8406 let meshes = [a, b];
8407 let mut interacting = false;
8408 for op in 0..16 {
8409 let r = (rng.next() * 1e9) as usize;
8410 let m = meshes[(r >> 4) % 2];
8411 let slots = scene.field_values(ids[0]).unwrap().len() as u32;
8412 let v = (rng.next() * f64::from(slots)) as u32;
8413 let what = r % 9;
8414 let s = 0.8 + rng.next() as f32 / 2.0;
8415 match what {
8416 0..=2 => {
8417 let _ = scene
8418 .edit(m, |tx| tx.set_positions(&[v], &[[s, s - 1.0, 1.1 - s]]));
8419 }
8420 3 => {
8421 scene.dab(m, &ball_dab(0.02)).unwrap();
8422 }
8423 8 => mirror(&mut scene, m),
8424 4 => {
8425 if scene.can_undo() {
8426 scene.undo().unwrap();
8427 }
8428 }
8429 5 => {
8430 let scale = if r & 256 == 0 {
8431 [1.0, 1.0, 1.0]
8432 } else {
8433 [1.5, 1.0, 1.0]
8434 };
8435 scene
8436 .set_placement(
8437 b,
8438 Placement {
8439 scale,
8440 ..at(1.2, 0.0, f64::from(s) - 1.0)
8441 },
8442 )
8443 .unwrap();
8444 }
8445 6 => {
8446 interacting = !interacting;
8447 let points = interacting.then_some(4);
8448 scene.set_field_interaction(points).unwrap();
8449 }
8450 _ => {
8451 if stepped {
8452 let _ = scene.field_step(FieldBudget::Points(2));
8454 scene.abandon_field_step();
8455 }
8456 }
8457 }
8458 bring_up_to_date(&mut scene, stepped);
8459 if !interacting {
8460 let step = format!("stepped {stepped}, round {round}, op {op} ({what})");
8461 assert_like_a_new_field(&mut scene, ids[0], &step);
8462 assert_like_a_new_field(&mut scene, ids[1], &step);
8463 }
8464 }
8465 }
8466 }
8467 }
8468
8469 #[test]
8470 fn interaction_counts_the_motion_before_its_first_update() {
8471 let mut scene = Scene::new(SceneOptions::default());
8474 let a = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8475 let b = scene.add_mesh(ball(), MeshOptions::default()).unwrap();
8476 scene.set_placement(b, at(4.0, 0.0, 0.0)).unwrap();
8477 let fa = scene
8478 .add_distance_field(field("d", a, b, f64::INFINITY))
8479 .unwrap();
8480 let slots = scene.field_values(fa).unwrap().len();
8481 assert!(scene.field_values(fa).unwrap()[0] > 1.5);
8482 scene.fields[0].live.sweep = slots as u32 / 2;
8483 scene.set_field_interaction(Some(slots / 4)).unwrap();
8484 scene.set_placement(b, at(2.0, 0.0, 0.0)).unwrap();
8485 assert!(scene.field_values(fa).unwrap()[0].abs() < 1e-3);
8487 let stamps = &scene.fields[0].live.stamps;
8490 let left = stamps.iter().filter(|&&t| t == 0.0).count();
8491 assert!(
8492 left > 0,
8493 "every vertex stamped as computed after the motion"
8494 );
8495 }
8496}