1use std::fmt;
53use std::ops::Range;
54
55use ranim_core::components::width::Width;
56use ranim_core::core_item::transformed::Transformed;
57use ranim_core::{
58 Extract,
59 anchor::{Aabb, Centroid, Locate},
60 color::{AlphaColor, Srgb, palette::css},
61 core_item::CoreItem,
62 glam::{DAffine3, DVec3},
63 traits::{
64 Alignable, Empty, FillColor, Interpolatable, Opacity, Partial, StrokeColor, StrokeWidth,
65 TransformGroup,
66 },
67 utils::resize_preserving_order_with_repeated_indices,
68};
69use tracing::warn;
70
71pub struct Node<I, G = DAffine3> {
108 pub id: Option<String>,
113 pub item: Option<I>,
115 pub children: Vec<Transformed<Node<I, G>, G>>,
120}
121
122impl<I: Clone, G: Clone> Clone for Node<I, G> {
129 fn clone(&self) -> Self {
130 Self {
131 id: self.id.clone(),
132 item: self.item.clone(),
133 children: self.children.clone(),
134 }
135 }
136}
137
138impl<I: PartialEq, G: PartialEq> PartialEq for Node<I, G> {
139 fn eq(&self, other: &Self) -> bool {
140 self.id == other.id && self.item == other.item && self.children == other.children
141 }
142}
143
144impl<I: Eq, G: Eq> Eq for Node<I, G> {}
145
146impl<I: fmt::Debug, G: fmt::Debug> fmt::Debug for Node<I, G> {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 f.debug_struct("Node")
149 .field("id", &self.id)
150 .field("item", &self.item)
151 .field("children", &self.children)
152 .finish()
153 }
154}
155
156impl<I, G> Node<I, G> {
159 pub fn new(item: Option<I>, children: Vec<Transformed<Self, G>>) -> Self {
161 Self {
162 id: None,
163 item,
164 children,
165 }
166 }
167
168 #[must_use]
170 pub fn with_id(mut self, id: impl Into<String>) -> Self {
171 self.id = Some(id.into());
172 self
173 }
174
175 pub fn is_leaf(&self) -> bool {
177 self.item.is_some() && self.children.is_empty()
178 }
179
180 pub fn is_group(&self) -> bool {
183 self.item.is_none()
184 }
185
186 pub fn item(&self) -> Option<&I> {
188 self.item.as_ref()
189 }
190
191 pub fn item_mut(&mut self) -> Option<&mut I> {
193 self.item.as_mut()
194 }
195
196 pub fn children(&self) -> &[Transformed<Self, G>] {
198 &self.children
199 }
200
201 pub fn children_mut(&mut self) -> &mut [Transformed<Self, G>] {
203 &mut self.children
204 }
205
206 pub fn first_leaf(&self) -> Option<&I> {
210 let mut stack = vec![self];
211 while let Some(node) = stack.pop() {
212 if let Some(item) = &node.item {
213 return Some(item);
214 }
215 stack.extend(node.children.iter().rev().map(|child| &child.inner));
216 }
217 None
218 }
219
220 pub fn get(&self, path: &[usize]) -> Option<&Transformed<Self, G>> {
226 let (first, rest) = path.split_first()?;
227 let child = self.children.get(*first)?;
228 if rest.is_empty() {
229 Some(child)
230 } else {
231 child.inner.get(rest)
232 }
233 }
234
235 pub fn get_mut(&mut self, path: &[usize]) -> Option<&mut Transformed<Self, G>> {
237 let (first, rest) = path.split_first()?;
238 let child = self.children.get_mut(*first)?;
239 if rest.is_empty() {
240 Some(child)
241 } else {
242 child.inner.get_mut(rest)
243 }
244 }
245
246 pub fn by_id(&self, id: &str) -> Option<&Transformed<Self, G>> {
255 self.children.iter().find_map(|child| {
256 if child.inner.id.as_deref() == Some(id) {
257 Some(child)
258 } else {
259 child.inner.by_id(id)
260 }
261 })
262 }
263
264 pub fn by_id_mut(&mut self, id: &str) -> Option<&mut Transformed<Self, G>> {
266 self.children.iter_mut().find_map(|child| {
267 if child.inner.id.as_deref() == Some(id) {
268 Some(child)
269 } else {
270 child.inner.by_id_mut(id)
271 }
272 })
273 }
274
275 pub fn by_ids(&self, id: &str) -> Vec<&Transformed<Self, G>> {
277 let mut matches = Vec::new();
278 self.collect_by_id(id, &mut matches);
279 matches
280 }
281
282 fn collect_by_id<'a>(&'a self, id: &str, matches: &mut Vec<&'a Transformed<Self, G>>) {
283 for child in &self.children {
284 if child.inner.id.as_deref() == Some(id) {
285 matches.push(child);
286 }
287 child.inner.collect_by_id(id, matches);
288 }
289 }
290
291 pub fn by_id_path(&self, id: &str) -> Option<Vec<usize>> {
295 for (index, child) in self.children.iter().enumerate() {
296 if child.inner.id.as_deref() == Some(id) {
297 return Some(vec![index]);
298 }
299 if let Some(mut path) = child.inner.by_id_path(id) {
300 path.insert(0, index);
301 return Some(path);
302 }
303 }
304 None
305 }
306
307 pub fn first_leaf_mut(&mut self) -> Option<&mut I> {
309 if let Some(item) = self.item.as_mut() {
310 return Some(item);
311 }
312 self.children
313 .iter_mut()
314 .find_map(|child| child.inner.first_leaf_mut())
315 }
316
317 pub fn leaves(&self) -> Leaves<'_, I, G>
328 where
329 G: Clone + Into<DAffine3>,
330 {
331 Leaves {
332 stack: vec![(DAffine3::IDENTITY, self)],
333 }
334 }
335
336 pub fn leaves_mut(&mut self) -> LeavesMut<'_, I, G> {
340 LeavesMut { stack: vec![self] }
341 }
342
343 pub fn map_inner<U>(self, f: impl FnMut(I) -> U) -> Node<U, G> {
347 fn map_inner_rec<I, U, G>(node: Node<I, G>, mut f: &mut impl FnMut(I) -> U) -> Node<U, G> {
348 let Node { id, item, children } = node;
349 Node {
350 id,
351 item: item.map(&mut f),
352 children: children
353 .into_iter()
354 .map(|child| Transformed::new(map_inner_rec(child.inner, f), child.transform))
355 .collect(),
356 }
357 }
358 let mut f = f;
359 map_inner_rec(self, &mut f)
360 }
361
362 pub fn map_transform<H>(self, f: impl FnMut(G) -> H) -> Node<I, H> {
368 fn map_transform_rec<I, G, H>(node: Node<I, G>, f: &mut impl FnMut(G) -> H) -> Node<I, H> {
369 let Node { id, item, children } = node;
370 Node {
371 id,
372 item,
373 children: children
374 .into_iter()
375 .map(|child| {
376 Transformed::new(map_transform_rec(child.inner, f), f(child.transform))
377 })
378 .collect(),
379 }
380 }
381 let mut f = f;
382 map_transform_rec(self, &mut f)
383 }
384}
385
386impl<I, G: TransformGroup> Node<I, G> {
387 pub fn leaf(item: I) -> Self {
389 Self {
390 id: None,
391 item: Some(item),
392 children: Vec::new(),
393 }
394 }
395
396 pub fn group(children: impl IntoIterator<Item = impl Into<Transformed<Self, G>>>) -> Self {
399 Self {
400 id: None,
401 item: None,
402 children: children.into_iter().map(Into::into).collect(),
403 }
404 }
405
406 pub fn branch(
410 item: I,
411 children: impl IntoIterator<Item = impl Into<Transformed<Self, G>>>,
412 ) -> Self {
413 Self {
414 id: None,
415 item: Some(item),
416 children: children.into_iter().map(Into::into).collect(),
417 }
418 }
419
420 pub fn frame() -> Self {
422 Self {
423 id: None,
424 item: None,
425 children: Vec::new(),
426 }
427 }
428}
429
430impl<I, G: TransformGroup> From<Node<I, G>> for Transformed<Node<I, G>, G> {
435 fn from(inner: Node<I, G>) -> Self {
436 Transformed::new(inner, G::identity())
437 }
438}
439
440pub struct Leaves<'a, I, G = DAffine3> {
448 stack: Vec<(DAffine3, &'a Node<I, G>)>,
449}
450
451impl<'a, I, G> Iterator for Leaves<'a, I, G>
452where
453 G: Clone + Into<DAffine3>,
454{
455 type Item = (DAffine3, &'a I);
456
457 fn next(&mut self) -> Option<Self::Item> {
458 while let Some((acc, node)) = self.stack.pop() {
459 for child in node.children.iter().rev() {
464 let acc_child = acc.compose(&child.transform.clone().into());
465 self.stack.push((acc_child, &child.inner));
466 }
467 if let Some(item) = &node.item {
468 return Some((acc, item));
469 }
470 }
471 None
472 }
473}
474
475pub struct LeavesMut<'a, I, G = DAffine3> {
478 stack: Vec<&'a mut Node<I, G>>,
479}
480
481impl<'a, I, G> Iterator for LeavesMut<'a, I, G> {
482 type Item = &'a mut I;
483
484 fn next(&mut self) -> Option<Self::Item> {
485 while let Some(node) = self.stack.pop() {
486 self.stack
487 .extend(node.children.iter_mut().rev().map(|child| &mut child.inner));
488 if let Some(item) = node.item.as_mut() {
489 return Some(item);
490 }
491 }
492 None
493 }
494}
495
496pub trait PlacedLeaves<I, G>
502where
503 G: Clone + Into<DAffine3>,
504{
505 fn leaves(&self) -> Leaves<'_, I, G>;
508
509 fn leaves_mut(&mut self) -> LeavesMut<'_, I, G>;
511}
512
513impl<I, G> PlacedLeaves<I, G> for Transformed<Node<I, G>, G>
514where
515 G: Clone + Into<DAffine3>,
516{
517 fn leaves(&self) -> Leaves<'_, I, G> {
518 Leaves {
519 stack: vec![(self.transform.clone().into(), &self.inner)],
520 }
521 }
522
523 fn leaves_mut(&mut self) -> LeavesMut<'_, I, G> {
524 LeavesMut {
525 stack: vec![&mut self.inner],
526 }
527 }
528}
529
530impl<I, G> Extract for Node<I, G>
533where
534 I: Extract<Target = CoreItem>,
535 G: Clone + Into<DAffine3>,
536{
537 type Target = CoreItem;
538
539 fn extract_into(&self, buf: &mut Vec<Self::Target>) {
540 if let Some(item) = &self.item {
541 item.extract_into(buf);
542 }
543 for child in &self.children {
549 child.extract_into(buf);
550 }
551 }
552}
553
554impl<I, G> Interpolatable for Node<I, G>
557where
558 I: Interpolatable,
559 G: Interpolatable,
560{
561 fn lerp(&self, target: &Self, t: f64) -> Self {
568 Self {
569 id: if t < 0.5 {
570 self.id.clone()
571 } else {
572 target.id.clone()
573 },
574 item: lerp_items(&self.item, &target.item, t),
575 children: self
576 .children
577 .iter()
578 .zip(target.children.iter())
579 .map(|(current, target)| current.lerp(target, t))
580 .collect(),
581 }
582 }
583}
584
585fn lerp_items<I: Interpolatable>(current: &Option<I>, target: &Option<I>, t: f64) -> Option<I> {
588 match (current, target) {
589 (Some(current), Some(target)) => Some(current.lerp(target, t)),
590 (None, None) => None,
591 _ => panic!("interpolating unaligned hierarchies: align them with Alignable first"),
592 }
593}
594
595impl<I, G> Alignable for Node<I, G>
598where
599 I: Alignable + Opacity,
600 G: Clone,
601{
602 fn is_aligned(&self, other: &Self) -> bool {
609 let items_aligned = match (&self.item, &other.item) {
610 (Some(current), Some(target)) => current.is_aligned(target),
611 (None, None) => true,
612 _ => false,
613 };
614 items_aligned
615 && self.children.len() == other.children.len()
616 && self
617 .children
618 .iter()
619 .zip(other.children.iter())
620 .all(|(current, target)| current.is_aligned(target))
621 }
622
623 fn align_with(&mut self, other: &mut Self) {
640 match (self.item.as_mut(), other.item.as_mut()) {
641 (None, Some(target_item)) => {
642 let mut fill = target_item.clone();
643 fill.set_opacity(0.0);
644 self.item = Some(fill);
645 }
646 (Some(current_item), None) => {
647 let mut fill = current_item.clone();
648 fill.set_opacity(0.0);
649 other.item = Some(fill);
650 }
651 _ => {}
652 }
653 if let (Some(current), Some(target)) = (&mut self.item, &mut other.item) {
654 current.align_with(target);
655 }
656 let len = self.children.len().max(other.children.len());
657 match (self.children.len(), other.children.len()) {
658 (0, 0) => {}
659 (0, _) => self.children = transparent_clones(&other.children, len),
660 (_, 0) => other.children = transparent_clones(&self.children, len),
661 _ => {
662 expand_with_transparent_repeats(&mut self.children, len);
663 expand_with_transparent_repeats(&mut other.children, len);
664 }
665 }
666 self.children
667 .iter_mut()
668 .zip(other.children.iter_mut())
669 .for_each(|(current, target)| current.align_with(target));
670 }
671}
672
673fn expand_with_transparent_repeats<I, G>(nodes: &mut Vec<Transformed<Node<I, G>, G>>, len: usize)
677where
678 I: Opacity + Clone,
679 G: Clone,
680{
681 if nodes.len() != len {
682 let (mut expanded, repeated_idxs) =
683 resize_preserving_order_with_repeated_indices(nodes, len);
684 for idx in repeated_idxs {
685 expanded[idx].set_opacity(0.0);
686 }
687 *nodes = expanded;
688 }
689}
690
691fn transparent_clones<I, G>(
695 source: &[Transformed<Node<I, G>, G>],
696 len: usize,
697) -> Vec<Transformed<Node<I, G>, G>>
698where
699 I: Opacity + Clone,
700 G: Clone,
701{
702 source
703 .iter()
704 .cycle()
705 .take(len)
706 .map(|child| {
707 let mut fill = child.clone();
708 fill.set_opacity(0.0);
709 fill
710 })
711 .collect()
712}
713
714impl<I, G> Partial for Node<I, G>
717where
718 I: Partial,
719 G: Clone,
720{
721 fn get_partial(&self, range: Range<f64>) -> Self {
722 Self {
723 id: self.id.clone(),
724 item: self
725 .item
726 .as_ref()
727 .map(|item| item.get_partial(range.clone())),
728 children: self
729 .children
730 .iter()
731 .map(|child| child.get_partial(range.clone()))
732 .collect(),
733 }
734 }
735
736 fn get_partial_closed(&self, range: Range<f64>) -> Self {
737 Self {
738 id: self.id.clone(),
739 item: self
740 .item
741 .as_ref()
742 .map(|item| item.get_partial_closed(range.clone())),
743 children: self
744 .children
745 .iter()
746 .map(|child| child.get_partial_closed(range.clone()))
747 .collect(),
748 }
749 }
750}
751
752impl<I, G> Empty for Node<I, G>
755where
756 I: Empty,
757{
758 fn empty() -> Self {
759 Node {
760 id: None,
761 item: Some(I::empty()),
765 children: Vec::new(),
766 }
767 }
768}
769
770impl<I, G> Aabb for Node<I, G>
773where
774 I: Aabb,
775 G: Clone + Into<DAffine3>,
776{
777 fn aabb(&self) -> [DVec3; 2] {
783 let mut inner_box: Option<[DVec3; 2]> = self.item.as_ref().map(Aabb::aabb);
784 for child in &self.children {
785 let [lo, hi] = child.aabb();
786 inner_box = Some(match inner_box {
787 Some([acc_lo, acc_hi]) => [acc_lo.min(lo), acc_hi.max(hi)],
788 None => [lo, hi],
789 });
790 }
791 inner_box.unwrap_or_else(|| {
792 warn!("Empty bounding box, is the tree empty?");
793 [DVec3::ZERO, DVec3::ZERO]
794 })
795 }
796}
797
798impl<I, G> Locate<Node<I, G>> for Centroid
805where
806 Centroid: Locate<I>,
807 G: Clone + Into<DAffine3>,
808{
809 fn locate(&self, target: &Node<I, G>) -> DVec3 {
810 let mut sum = DVec3::ZERO;
811 let mut count = 0usize;
812 for (affine, leaf) in target.leaves() {
813 sum += affine.transform_point3(self.locate(leaf));
814 count += 1;
815 }
816 if count == 0 {
817 warn!("Locating the centroid of an empty tree, returning zero");
818 return DVec3::ZERO;
819 }
820 sum / count as f64
821 }
822}
823
824impl<I, G> Opacity for Node<I, G>
827where
828 I: Opacity,
829{
830 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
831 for leaf in self.leaves_mut() {
832 leaf.set_opacity(opacity);
833 }
834 self
835 }
836}
837
838impl<I, G> FillColor for Node<I, G>
841where
842 I: FillColor,
843{
844 fn fill_color(&self) -> AlphaColor<Srgb> {
847 self.first_leaf()
848 .map(FillColor::fill_color)
849 .unwrap_or_else(|| {
850 warn!("Accessing the fill color of an empty tree, returning white");
851 css::WHITE
852 })
853 }
854
855 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
856 for leaf in self.leaves_mut() {
857 leaf.set_fill_color(color);
858 }
859 self
860 }
861
862 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
863 for leaf in self.leaves_mut() {
864 leaf.set_fill_opacity(opacity);
865 }
866 self
867 }
868}
869
870impl<I, G> StrokeColor for Node<I, G>
873where
874 I: StrokeColor,
875{
876 fn stroke_color(&self) -> AlphaColor<Srgb> {
879 self.first_leaf()
880 .map(StrokeColor::stroke_color)
881 .unwrap_or_else(|| {
882 warn!("Accessing the stroke color of an empty tree, returning white");
883 css::WHITE
884 })
885 }
886
887 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
888 for leaf in self.leaves_mut() {
889 leaf.set_stroke_color(color);
890 }
891 self
892 }
893
894 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
895 for leaf in self.leaves_mut() {
896 leaf.set_stroke_opacity(opacity);
897 }
898 self
899 }
900}
901
902impl<I, G> StrokeWidth for Node<I, G>
905where
906 I: StrokeWidth,
907{
908 fn stroke_width(&self) -> f32 {
911 self.first_leaf()
912 .map(StrokeWidth::stroke_width)
913 .unwrap_or_else(|| {
914 warn!("Accessing the stroke width of an empty tree, returning 0");
915 0.0
916 })
917 }
918
919 fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
921 for leaf in self.leaves_mut() {
922 leaf.apply_stroke_func(&f);
923 }
924 self
925 }
926}
927
928#[cfg(test)]
929mod tests {
930 use super::*;
931 use crate::hierarchy::PlacedLeaves;
932 use ranim_core::Extract;
933 use ranim_core::core_item::transformed::{Transformed, TransformedExt};
934 use ranim_core::core_item::vitem::VItem as CoreVItem;
935 use ranim_core::glam::{DQuat, Mat4, Quat, Vec3, Vec4, dvec3};
936 use ranim_core::traits::{ApplyTransform, Rigid, Similarity, Translation};
937
938 type CoreNode<G = DAffine3> = Node<CoreVItem, G>;
939 type HierarchyVItem = crate::vitem::VItem;
940
941 fn marked_core_vitem(marker: f32) -> CoreVItem {
944 CoreVItem {
945 points: vec![Vec4::new(marker, 0.0, 0.0, 0.0)],
946 ..Default::default()
947 }
948 }
949
950 fn stroked_vitem(marker: f64) -> HierarchyVItem {
952 let mut vitem = HierarchyVItem::from_vpoints(vec![
953 dvec3(marker, 0.0, 0.0),
954 dvec3(marker + 0.5, 0.0, 0.0),
955 dvec3(marker + 1.0, 0.0, 0.0),
956 ]);
957 vitem.set_stroke_width(0.04);
958 vitem
959 }
960
961 fn assert_affine_eq(actual: DAffine3, expected: DAffine3) {
962 assert!(
963 actual
964 .transform_point3(dvec3(0.3, -0.7, 1.1))
965 .abs_diff_eq(expected.transform_point3(dvec3(0.3, -0.7, 1.1)), 1e-9)
966 );
967 for i in 0..3 {
968 assert!(
969 actual
970 .matrix3
971 .col(i)
972 .abs_diff_eq(expected.matrix3.col(i), 1e-9),
973 "matrix3 column {i} diverges"
974 );
975 }
976 assert!(actual.translation.abs_diff_eq(expected.translation, 1e-9));
977 }
978
979 #[test]
980 fn nested_extract_composes_matrices_and_keeps_points_local() {
981 let inner_similarity = Similarity {
984 scale: 2.0,
985 rotation: DQuat::IDENTITY,
986 translation: dvec3(10.0, 20.0, 30.0),
987 };
988 let tree = Transformed::new(
989 CoreNode::new(
990 None,
991 vec![Transformed::new(
992 CoreNode::new(
993 Some(CoreVItem {
994 points: vec![Vec4::new(1.0, 0.0, 0.0, 0.0)],
995 ..Default::default()
996 }),
997 Vec::new(),
998 ),
999 DAffine3::from(Translation(dvec3(3.0, 4.0, 5.0))),
1000 )],
1001 ),
1002 DAffine3::from(inner_similarity),
1003 );
1004
1005 let expected_world =
1006 DAffine3::from(inner_similarity) * DAffine3::from(Translation(dvec3(3.0, 4.0, 5.0)));
1007 assert_affine_eq(tree.leaves().next().unwrap().0, expected_world);
1008
1009 match &tree.extract()[0] {
1010 CoreItem::VItem(extracted) => {
1011 assert_eq!(extracted.points[0], Vec4::new(1.0, 0.0, 0.0, 0.0));
1013 assert_eq!(
1014 extracted.transform,
1015 Mat4::from_scale_rotation_translation(
1016 Vec3::splat(2.0),
1017 Quat::IDENTITY,
1018 Vec3::new(16.0, 28.0, 40.0),
1019 )
1020 );
1021 }
1022 _ => panic!("expected a VItem"),
1023 }
1024 }
1025
1026 #[test]
1027 fn dfs_emission_matches_painters_order_on_three_levels() {
1028 let tree = CoreNode::<DAffine3>::group(vec![
1036 CoreNode::<DAffine3>::group(vec![CoreNode::<DAffine3>::group(vec![
1037 CoreNode::leaf(marked_core_vitem(1.0)),
1038 CoreNode::leaf(marked_core_vitem(3.0)),
1039 ])]),
1040 CoreNode::leaf(marked_core_vitem(2.0)),
1041 ]);
1042
1043 let markers: Vec<f32> = tree
1044 .extract()
1045 .into_iter()
1046 .map(|item| match item {
1047 CoreItem::VItem(vitem) => vitem.points[0].x,
1048 _ => panic!("expected a VItem"),
1049 })
1050 .collect();
1051 assert_eq!(markers, [1.0, 3.0, 2.0]);
1053 }
1054
1055 #[test]
1056 fn aabb_follows_node_rotation_and_empty_groups_degenerate() {
1057 let rect = HierarchyVItem::from_vpoints(vec![
1060 dvec3(-1.0, -1.0, 0.0),
1061 dvec3(1.0, -1.0, 0.0),
1062 dvec3(3.0, -1.0, 0.0),
1063 dvec3(3.0, 0.0, 0.0),
1064 dvec3(3.0, 1.0, 0.0),
1065 dvec3(1.0, 1.0, 0.0),
1066 dvec3(-1.0, 1.0, 0.0),
1067 dvec3(-1.0, 0.0, 0.0),
1068 dvec3(-1.0, -1.0, 0.0),
1069 ]);
1070 let rotated: Transformed<Node<HierarchyVItem>, DAffine3> =
1071 Node::leaf(rect).transformed(DAffine3::from_rotation_translation(
1072 DQuat::from_rotation_z(std::f64::consts::FRAC_PI_2),
1073 DVec3::ZERO,
1074 ));
1075
1076 let [lo, hi] = rotated.aabb();
1077 assert!(lo.abs_diff_eq(dvec3(-1.0, -1.0, 0.0), 1e-9), "lo is {lo:?}");
1080 assert!(hi.abs_diff_eq(dvec3(1.0, 3.0, 0.0), 1e-9), "hi is {hi:?}");
1081
1082 let empty = Node::<HierarchyVItem>::frame();
1084 assert_eq!(empty.aabb(), [DVec3::ZERO, DVec3::ZERO]);
1085 }
1086
1087 #[test]
1088 fn centroid_weights_each_flattened_leaf_equally() {
1089 let asymmetric = Node::<DVec3>::group(vec![
1093 Node::group(vec![
1094 Node::leaf(dvec3(3.0, 0.0, 0.0)),
1095 Node::leaf(dvec3(4.0, 0.0, 0.0)),
1096 Node::leaf(dvec3(5.0, 0.0, 0.0)),
1097 ]),
1098 Node::leaf(dvec3(-36.0, 0.0, 0.0)),
1099 ]);
1100
1101 let centroid = Centroid.locate(&asymmetric);
1102 assert!(
1103 centroid.abs_diff_eq(dvec3(-6.0, 0.0, 0.0), 1e-9),
1104 "centroid is {centroid:?}"
1105 );
1106 assert_ne!(centroid, dvec3(-16.0, 0.0, 0.0));
1107
1108 let empty = Node::<DVec3>::frame();
1109 assert_eq!(Centroid.locate(&empty), DVec3::ZERO);
1110 }
1111
1112 #[test]
1113 fn alignment_fills_missing_nodes_with_transparent_clones() {
1114 let left = Node::leaf(stroked_vitem(0.0));
1117 let right = Node::<HierarchyVItem>::group(vec![
1118 Node::leaf(stroked_vitem(2.0)).transformed(DAffine3::from(Translation(DVec3::Y))),
1119 ]);
1120
1121 assert!(!left.is_aligned(&right));
1122 let mut left = left;
1123 let mut right = right;
1124 left.align_with(&mut right);
1125 assert!(left.is_aligned(&right));
1126 assert_eq!(left.children().len(), 1);
1127 assert_eq!(right.children().len(), 1);
1128 assert_eq!(right.item().unwrap().stroke_rgbas[0].0.w, 0.0);
1129 assert_eq!(left.item().unwrap().stroke_rgbas[0].0.w, 1.0);
1130
1131 let mid = left.lerp(&right, 0.5);
1134 assert_eq!(mid.children().len(), 1);
1135 let mid_item = mid.item().unwrap();
1136 assert!((mid_item.vpoints[0].x - 0.0).abs() < 1e-6);
1137 assert!((mid_item.stroke_rgbas[0].0.w - 0.5).abs() < 1e-6);
1138 let (mid_world, mid_leaf) = mid.leaves().nth(1).unwrap();
1139 assert!((mid_leaf.vpoints[0].x - 2.0).abs() < 1e-6);
1140 assert!((mid_leaf.stroke_rgbas[0].0.w - 0.5).abs() < 1e-6);
1141 assert_affine_eq(mid_world, DAffine3::from_translation(DVec3::Y));
1142
1143 let big = Node::<HierarchyVItem>::group(vec![
1146 Node::leaf(stroked_vitem(0.0)),
1147 Node::leaf(stroked_vitem(10.0)),
1148 ]);
1149 let small = Node::<HierarchyVItem>::group(vec![Node::leaf(stroked_vitem(20.0))]);
1150 assert!(!big.is_aligned(&small));
1151 let mut big = big;
1152 let mut small = small;
1153 small.align_with(&mut big);
1154 assert!(big.is_aligned(&small));
1155 assert_eq!(small.children().len(), 2);
1156 let stand_in = small.children()[1].inner.item().unwrap();
1157 assert_eq!(stand_in.stroke_rgbas[0].0.w, 0.0);
1158 assert_eq!(stand_in.fill_rgbas[0].0.w, 0.0);
1159 let original = small.children()[0].inner.item().unwrap();
1160 assert_eq!(original.stroke_rgbas[0].0.w, 1.0);
1161 }
1162
1163 #[test]
1164 fn root_apply_transform_poses_without_baking_points() {
1165 let tree = CoreNode::leaf(CoreVItem {
1166 points: vec![Vec4::new(1.0, 0.0, 0.0, 0.0)],
1167 ..Default::default()
1168 });
1169 let mut tree: Transformed<CoreNode, DAffine3> =
1170 Transformed::new(tree, DAffine3::from(Translation(DVec3::X)));
1171 tree.apply(Rigid::from_translation(DVec3::Y));
1172
1173 match &tree.extract()[0] {
1174 CoreItem::VItem(extracted) => {
1175 assert_eq!(extracted.points[0], Vec4::new(1.0, 0.0, 0.0, 0.0));
1177 assert_eq!(
1178 extracted.transform,
1179 Mat4::from_translation(Vec3::new(1.0, 1.0, 0.0))
1180 );
1181 }
1182 _ => panic!("expected a VItem"),
1183 }
1184 }
1185
1186 #[test]
1187 fn interpolation_moves_poses_only() {
1188 let geometry =
1189 || HierarchyVItem::from_vpoints(vec![dvec3(0.0, 0.0, 0.0), dvec3(1.0, 0.0, 0.0)]);
1190 let start: Transformed<Node<HierarchyVItem>, DAffine3> = Node::leaf(geometry())
1191 .with_id("start")
1192 .transformed(DAffine3::from(Translation(dvec3(1.0, 0.0, 0.0))));
1193 let end: Transformed<Node<HierarchyVItem>, DAffine3> = Node::leaf(geometry())
1194 .with_id("end")
1195 .transformed(DAffine3::from(Translation(dvec3(3.0, 2.0, 0.0))));
1196
1197 assert!(start.is_aligned(&end));
1198 let mid = start.lerp(&end, 0.5);
1199 assert_affine_eq(
1200 mid.transform,
1201 DAffine3::from_translation(dvec3(2.0, 1.0, 0.0)),
1202 );
1203 assert_eq!(mid.inner.id.as_deref(), Some("end"));
1204 }
1205
1206 #[test]
1207 #[should_panic(expected = "aligned")]
1208 fn unaligned_kind_lerp_panics_with_guidance() {
1209 let leaf: Node<u32> = Node::leaf(1);
1210 let group: Node<u32> = Node::group(vec![Node::leaf(2)]);
1211 drop(leaf.lerp(&group, 0.5));
1212 }
1213
1214 #[test]
1215 fn unequal_sibling_counts_follow_vecs_truncating_zip() {
1216 let few = Node::<u32>::group(vec![Node::leaf(1)]);
1217 let many = Node::<u32>::group(vec![Node::leaf(1), Node::leaf(2), Node::leaf(3)]);
1218 let mid = many.lerp(&few, 0.5);
1219 assert_eq!(mid.children().len(), 1);
1220 }
1221
1222 #[test]
1223 fn partial_forwards_ranges_down_recursively() {
1224 let base = stroked_vitem(0.0);
1225 let tree: Transformed<Node<HierarchyVItem>, DAffine3> =
1226 Node::leaf(base.clone()).transformed(DAffine3::from(Translation(DVec3::X)));
1227
1228 let partial = tree.get_partial(0.25..0.75);
1229 assert_eq!(partial.transform, DAffine3::from(Translation(DVec3::X)));
1230 assert_eq!(
1231 partial.inner.item().unwrap(),
1232 &base.get_partial(0.25..0.75),
1233 "the range must be forwarded verbatim"
1234 );
1235
1236 let closed = tree.get_partial_closed(0.25..0.75);
1237 assert_eq!(
1238 closed.inner.item().unwrap(),
1239 &base.get_partial_closed(0.25..0.75)
1240 );
1241
1242 let grouped = Node::<HierarchyVItem>::group(vec![Node::leaf(base.clone()); 3]);
1243 let partial = grouped.get_partial(0.0..0.5);
1244 assert_eq!(partial.children().len(), 3);
1245 }
1246
1247 #[test]
1248 fn empty_is_a_bare_leaf_with_empty_geometry() {
1249 let empty = Node::<HierarchyVItem>::empty();
1252 assert!(empty.is_leaf());
1253 assert_eq!(empty.children().len(), 0);
1254 let leaf = empty.item().unwrap();
1255 assert_eq!(leaf.stroke_widths[0].0, 0.0);
1256 assert!(leaf.fill_rgbas.iter().all(|rgba| rgba.0 == Vec4::ZERO));
1257
1258 let placed: Transformed<Node<HierarchyVItem>, DAffine3> = empty.into();
1260 assert_eq!(placed.transform, DAffine3::IDENTITY);
1261 }
1262
1263 #[test]
1264 fn leaves_accumulate_correctly_over_skewed_affine_chains() {
1265 let scale = DAffine3::from_scale(dvec3(2.0, 3.0, 4.0));
1266 let rotate = DAffine3::from_rotation_translation(
1267 DQuat::from_rotation_z(std::f64::consts::FRAC_PI_2),
1268 DVec3::ZERO,
1269 );
1270 let translate = DAffine3::from_translation(DVec3::X);
1271
1272 let tree = Transformed::new(
1274 CoreNode::new(
1275 None,
1276 vec![Transformed::new(
1277 CoreNode::new(
1278 None,
1279 vec![Transformed::new(
1280 CoreNode::new(
1281 Some(CoreVItem {
1282 points: vec![Vec4::ZERO],
1283 ..Default::default()
1284 }),
1285 Vec::new(),
1286 ),
1287 translate,
1288 )],
1289 ),
1290 rotate,
1291 )],
1292 ),
1293 scale,
1294 );
1295
1296 let expected = scale * rotate * translate;
1297 let (world, _) = tree.leaves().next().unwrap();
1298
1299 assert!(
1302 world
1303 .transform_point3(DVec3::ZERO)
1304 .abs_diff_eq(dvec3(0.0, 3.0, 0.0), 1e-9)
1305 );
1306 assert_affine_eq(world, expected);
1307 }
1308
1309 #[test]
1310 fn wrapped_payloads_lift_into_placed_leaf_nodes() {
1311 let similarity = Similarity {
1312 scale: 2.0,
1313 rotation: DQuat::IDENTITY,
1314 translation: dvec3(1.0, 2.0, 3.0),
1315 };
1316 let wrapped = Transformed::new(stroked_vitem(0.0), similarity);
1317
1318 let placed = wrapped.map_inner(Node::<HierarchyVItem, Similarity>::leaf);
1320 assert!(placed.inner.is_leaf());
1321 assert_eq!(placed.inner.id, None);
1322 assert_eq!(placed.transform, similarity);
1323
1324 let tree = Node::<HierarchyVItem, Rigid>::group(vec![
1327 Node::leaf(stroked_vitem(1.0)).transformed(Rigid::from_translation(DVec3::X)),
1328 Node::leaf(stroked_vitem(2.0)).into(),
1329 ]);
1330 assert_eq!(tree.children[0].transform.translation, DVec3::X);
1331 assert_eq!(tree.children[1].transform, Rigid::IDENTITY);
1332 }
1333
1334 #[test]
1335 fn map_inner_maps_payloads_and_keeps_the_shape() {
1336 let tree = Node::<u32, Translation>::group(vec![
1337 Node::leaf(1)
1338 .with_id("one")
1339 .transformed(Translation(DVec3::X)),
1340 Node::group(vec![Node::leaf(2)]).into(),
1341 ]);
1342 let mapped = tree.map_inner(|payload| payload * 10);
1343
1344 assert_eq!(
1345 mapped.get(&[0]).unwrap().inner.item(),
1346 Some(&10),
1347 "ids and shape survive mapping"
1348 );
1349 assert_eq!(mapped.get(&[0]).unwrap().inner.id.as_deref(), Some("one"));
1350 assert_eq!(mapped.get(&[0]).unwrap().transform, Translation(DVec3::X));
1351 assert_eq!(mapped.get(&[1, 0]).unwrap().inner.item(), Some(&20));
1352 }
1353
1354 #[test]
1355 fn id_and_index_lookups_address_placements() {
1356 let root = Node::<(), DAffine3>::leaf(()).with_id("root");
1359 assert!(root.by_id("root").is_none());
1360 assert_eq!(root.by_id_path("root"), None);
1361
1362 let tree = Node::<u32>::group(vec![
1364 Node::group(vec![Node::leaf(1), Node::leaf(2)]),
1365 Node::leaf(3),
1366 ]);
1367 assert!(tree.get(&[]).is_none());
1368 assert_eq!(tree.get(&[0]).unwrap().inner.children().len(), 2);
1369 assert_eq!(tree.get(&[0, 1]).unwrap().inner.item(), Some(&2));
1370 assert_eq!(tree.get(&[1]).unwrap().inner.item(), Some(&3));
1371 assert!(tree.get(&[2]).is_none());
1372 assert!(tree.get(&[1, 0]).is_none());
1374
1375 let mut tree = tree;
1376 let target = tree.get_mut(&[0, 0]).unwrap();
1377 assert_eq!(target.inner.item(), Some(&1));
1378 target.transform = DAffine3::from(Translation(DVec3::X));
1379 assert_eq!(
1380 tree.get(&[0, 0]).unwrap().transform,
1381 DAffine3::from(Translation(DVec3::X))
1382 );
1383
1384 let tree = Node::<(), DAffine3>::group(vec![
1387 Node::leaf(()).with_id("dup"),
1388 Node::group(vec![
1389 Node::leaf(()).with_id("dup"),
1390 Node::leaf(()).with_id("other"),
1391 ]),
1392 ]);
1393 assert!(tree.by_id("dup").unwrap().inner.is_leaf());
1394 assert_eq!(tree.by_ids("dup").len(), 2);
1395 assert_eq!(tree.by_id_path("dup"), Some(vec![0]));
1396 assert_eq!(tree.by_id_path("other"), Some(vec![1, 1]));
1397 assert!(tree.by_id("missing").is_none());
1398
1399 let mut tree = tree;
1400 tree.by_id_mut("other").unwrap().inner.id = Some("renamed".into());
1401 assert!(tree.by_id("other").is_none());
1402 assert!(tree.by_id("renamed").is_some());
1403 }
1404
1405 #[test]
1406 fn styling_traits_read_first_leaf_and_write_every_leaf() {
1407 let mut tree = Node::<HierarchyVItem>::group(vec![
1408 Node::leaf(stroked_vitem(0.0)),
1409 Node::group(vec![
1410 Node::leaf(stroked_vitem(5.0)),
1411 Node::leaf(stroked_vitem(9.0)),
1412 ]),
1413 ]);
1414
1415 assert_eq!(tree.stroke_width(), 0.04);
1417 tree.set_opacity(0.5);
1418 let opacities: Vec<f32> = tree
1419 .leaves_mut()
1420 .map(|leaf| leaf.stroke_rgbas[0].0.w)
1421 .collect();
1422 assert_eq!(opacities, [0.5, 0.5, 0.5]);
1423
1424 tree.set_stroke_width(1.0);
1426 let widths: Vec<f32> = tree
1427 .leaves_mut()
1428 .map(|leaf| leaf.stroke_widths[0].0)
1429 .collect();
1430 assert_eq!(widths, [1.0, 1.0, 1.0]);
1431
1432 let empty = Node::<HierarchyVItem>::frame();
1434 assert_eq!(empty.stroke_width(), 0.0);
1435 assert_eq!(empty.fill_color(), css::WHITE);
1436 }
1437}