1use std::fmt;
52use std::ops::Range;
53
54use ranim_core::components::width::Width;
55use ranim_core::transformed::Transformed;
56use ranim_core::{
57 anchor::{Aabb, Centroid, Locate},
58 color::{AlphaColor, Srgb, palette::css},
59 glam::{DAffine3, DVec3},
60 traits::{
61 Alignable, Empty, FillColor, Interpolatable, Opacity, Partial, StrokeColor, StrokeWidth,
62 TransformGroup,
63 },
64 utils::resize_preserving_order_with_repeated_indices,
65};
66use tracing::warn;
67
68#[cfg(feature = "render")]
69use ranim_render::extract::{Extract, ExtractContext};
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
530#[cfg(feature = "render")]
533impl<I, G> Extract for Node<I, G>
534where
535 I: Extract,
536 G: Clone + Into<DAffine3>,
537{
538 fn extract(&self, ctx: &mut ExtractContext<'_>) {
539 if let Some(item) = &self.item {
540 item.extract(ctx);
541 }
542 for child in &self.children {
544 child.extract(ctx);
545 }
546 }
547}
548
549impl<I, G> Interpolatable for Node<I, G>
552where
553 I: Interpolatable,
554 G: Interpolatable,
555{
556 fn lerp(&self, target: &Self, t: f64) -> Self {
563 Self {
564 id: if t < 0.5 {
565 self.id.clone()
566 } else {
567 target.id.clone()
568 },
569 item: lerp_items(&self.item, &target.item, t),
570 children: self
571 .children
572 .iter()
573 .zip(target.children.iter())
574 .map(|(current, target)| current.lerp(target, t))
575 .collect(),
576 }
577 }
578}
579
580fn lerp_items<I: Interpolatable>(current: &Option<I>, target: &Option<I>, t: f64) -> Option<I> {
583 match (current, target) {
584 (Some(current), Some(target)) => Some(current.lerp(target, t)),
585 (None, None) => None,
586 _ => panic!("interpolating unaligned hierarchies: align them with Alignable first"),
587 }
588}
589
590impl<I, G> Alignable for Node<I, G>
593where
594 I: Alignable + Opacity,
595 G: Clone,
596{
597 fn is_aligned(&self, other: &Self) -> bool {
604 let items_aligned = match (&self.item, &other.item) {
605 (Some(current), Some(target)) => current.is_aligned(target),
606 (None, None) => true,
607 _ => false,
608 };
609 items_aligned
610 && self.children.len() == other.children.len()
611 && self
612 .children
613 .iter()
614 .zip(other.children.iter())
615 .all(|(current, target)| current.is_aligned(target))
616 }
617
618 fn align_with(&mut self, other: &mut Self) {
635 match (self.item.as_mut(), other.item.as_mut()) {
636 (None, Some(target_item)) => {
637 let mut fill = target_item.clone();
638 fill.set_opacity(0.0);
639 self.item = Some(fill);
640 }
641 (Some(current_item), None) => {
642 let mut fill = current_item.clone();
643 fill.set_opacity(0.0);
644 other.item = Some(fill);
645 }
646 _ => {}
647 }
648 if let (Some(current), Some(target)) = (&mut self.item, &mut other.item) {
649 current.align_with(target);
650 }
651 let len = self.children.len().max(other.children.len());
652 match (self.children.len(), other.children.len()) {
653 (0, 0) => {}
654 (0, _) => self.children = transparent_clones(&other.children, len),
655 (_, 0) => other.children = transparent_clones(&self.children, len),
656 _ => {
657 expand_with_transparent_repeats(&mut self.children, len);
658 expand_with_transparent_repeats(&mut other.children, len);
659 }
660 }
661 self.children
662 .iter_mut()
663 .zip(other.children.iter_mut())
664 .for_each(|(current, target)| current.align_with(target));
665 }
666}
667
668fn expand_with_transparent_repeats<I, G>(nodes: &mut Vec<Transformed<Node<I, G>, G>>, len: usize)
672where
673 I: Opacity + Clone,
674 G: Clone,
675{
676 if nodes.len() != len {
677 let (mut expanded, repeated_idxs) =
678 resize_preserving_order_with_repeated_indices(nodes, len);
679 for idx in repeated_idxs {
680 expanded[idx].set_opacity(0.0);
681 }
682 *nodes = expanded;
683 }
684}
685
686fn transparent_clones<I, G>(
690 source: &[Transformed<Node<I, G>, G>],
691 len: usize,
692) -> Vec<Transformed<Node<I, G>, G>>
693where
694 I: Opacity + Clone,
695 G: Clone,
696{
697 source
698 .iter()
699 .cycle()
700 .take(len)
701 .map(|child| {
702 let mut fill = child.clone();
703 fill.set_opacity(0.0);
704 fill
705 })
706 .collect()
707}
708
709impl<I, G> Partial for Node<I, G>
712where
713 I: Partial,
714 G: Clone,
715{
716 fn get_partial(&self, range: Range<f64>) -> Self {
717 Self {
718 id: self.id.clone(),
719 item: self
720 .item
721 .as_ref()
722 .map(|item| item.get_partial(range.clone())),
723 children: self
724 .children
725 .iter()
726 .map(|child| child.get_partial(range.clone()))
727 .collect(),
728 }
729 }
730
731 fn get_partial_closed(&self, range: Range<f64>) -> Self {
732 Self {
733 id: self.id.clone(),
734 item: self
735 .item
736 .as_ref()
737 .map(|item| item.get_partial_closed(range.clone())),
738 children: self
739 .children
740 .iter()
741 .map(|child| child.get_partial_closed(range.clone()))
742 .collect(),
743 }
744 }
745}
746
747impl<I, G> Empty for Node<I, G>
750where
751 I: Empty,
752{
753 fn empty() -> Self {
754 Node {
755 id: None,
756 item: Some(I::empty()),
760 children: Vec::new(),
761 }
762 }
763}
764
765impl<I, G> Aabb for Node<I, G>
768where
769 I: Aabb,
770 G: Clone + Into<DAffine3>,
771{
772 fn aabb(&self) -> [DVec3; 2] {
778 let mut inner_box: Option<[DVec3; 2]> = self.item.as_ref().map(Aabb::aabb);
779 for child in &self.children {
780 let [lo, hi] = child.aabb();
781 inner_box = Some(match inner_box {
782 Some([acc_lo, acc_hi]) => [acc_lo.min(lo), acc_hi.max(hi)],
783 None => [lo, hi],
784 });
785 }
786 inner_box.unwrap_or_else(|| {
787 warn!("Empty bounding box, is the tree empty?");
788 [DVec3::ZERO, DVec3::ZERO]
789 })
790 }
791}
792
793impl<I, G> Locate<Node<I, G>> for Centroid
800where
801 Centroid: Locate<I>,
802 G: Clone + Into<DAffine3>,
803{
804 fn locate(&self, target: &Node<I, G>) -> DVec3 {
805 let mut sum = DVec3::ZERO;
806 let mut count = 0usize;
807 for (affine, leaf) in target.leaves() {
808 sum += affine.transform_point3(self.locate(leaf));
809 count += 1;
810 }
811 if count == 0 {
812 warn!("Locating the centroid of an empty tree, returning zero");
813 return DVec3::ZERO;
814 }
815 sum / count as f64
816 }
817}
818
819impl<I, G> Opacity for Node<I, G>
822where
823 I: Opacity,
824{
825 fn set_opacity(&mut self, opacity: f32) -> &mut Self {
826 for leaf in self.leaves_mut() {
827 leaf.set_opacity(opacity);
828 }
829 self
830 }
831}
832
833impl<I, G> FillColor for Node<I, G>
836where
837 I: FillColor,
838{
839 fn fill_color(&self) -> AlphaColor<Srgb> {
842 self.first_leaf()
843 .map(FillColor::fill_color)
844 .unwrap_or_else(|| {
845 warn!("Accessing the fill color of an empty tree, returning white");
846 css::WHITE
847 })
848 }
849
850 fn set_fill_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
851 for leaf in self.leaves_mut() {
852 leaf.set_fill_color(color);
853 }
854 self
855 }
856
857 fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
858 for leaf in self.leaves_mut() {
859 leaf.set_fill_opacity(opacity);
860 }
861 self
862 }
863}
864
865impl<I, G> StrokeColor for Node<I, G>
868where
869 I: StrokeColor,
870{
871 fn stroke_color(&self) -> AlphaColor<Srgb> {
874 self.first_leaf()
875 .map(StrokeColor::stroke_color)
876 .unwrap_or_else(|| {
877 warn!("Accessing the stroke color of an empty tree, returning white");
878 css::WHITE
879 })
880 }
881
882 fn set_stroke_color(&mut self, color: AlphaColor<Srgb>) -> &mut Self {
883 for leaf in self.leaves_mut() {
884 leaf.set_stroke_color(color);
885 }
886 self
887 }
888
889 fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
890 for leaf in self.leaves_mut() {
891 leaf.set_stroke_opacity(opacity);
892 }
893 self
894 }
895}
896
897impl<I, G> StrokeWidth for Node<I, G>
900where
901 I: StrokeWidth,
902{
903 fn stroke_width(&self) -> f32 {
906 self.first_leaf()
907 .map(StrokeWidth::stroke_width)
908 .unwrap_or_else(|| {
909 warn!("Accessing the stroke width of an empty tree, returning 0");
910 0.0
911 })
912 }
913
914 fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
916 for leaf in self.leaves_mut() {
917 leaf.apply_stroke_func(&f);
918 }
919 self
920 }
921}
922
923#[cfg(test)]
924mod tests {
925 use super::*;
926 use crate::hierarchy::PlacedLeaves;
927 use ranim_core::frame::EvaluatedFrame;
928 use ranim_core::glam::{DQuat, Vec4, dvec3};
929 use ranim_core::traits::Translation;
930 use ranim_core::transformed::{Transformed, TransformedExt};
931
932 type VItemNode<G = DAffine3> = Node<HierarchyVItem, G>;
933 type HierarchyVItem = crate::vitem::VItem;
934
935 fn marked_vitem(marker: f64) -> HierarchyVItem {
938 HierarchyVItem::from_vpoints(vec![dvec3(marker, 0.0, 0.0)])
939 }
940
941 fn stroked_vitem(marker: f64) -> HierarchyVItem {
943 let mut vitem = HierarchyVItem::from_vpoints(vec![
944 dvec3(marker, 0.0, 0.0),
945 dvec3(marker + 0.5, 0.0, 0.0),
946 dvec3(marker + 1.0, 0.0, 0.0),
947 ]);
948 vitem.set_stroke_width(0.04);
949 vitem
950 }
951
952 fn assert_affine_eq(actual: DAffine3, expected: DAffine3) {
953 assert!(actual.matrix3.abs_diff_eq(expected.matrix3, 1e-9));
954 assert!(actual.translation.abs_diff_eq(expected.translation, 1e-9));
955 }
956
957 #[test]
958 fn raw_frame_keeps_nested_hierarchy_placement_and_local_points() {
959 let scale = DAffine3::from_scale(dvec3(2.0, 3.0, 4.0));
960 let rotate = DAffine3::from_rotation_translation(
961 DQuat::from_rotation_z(std::f64::consts::FRAC_PI_2),
962 DVec3::ZERO,
963 );
964 let translate = DAffine3::from_translation(DVec3::X);
965 let tree = VItemNode::group(vec![
966 VItemNode::group(vec![
967 VItemNode::leaf(marked_vitem(1.0)).transformed(translate),
968 ])
969 .transformed(rotate),
970 ])
971 .transformed(scale);
972 let (world, _) = tree.leaves().next().unwrap();
973 assert_affine_eq(world, scale * rotate * translate);
974 assert!(
975 world
976 .transform_point3(DVec3::ZERO)
977 .abs_diff_eq(dvec3(0.0, 3.0, 0.0), 1e-9)
978 );
979 let mut frame = EvaluatedFrame::new();
980 frame.push((0, 0), tree);
981 let stored = &frame.items::<Transformed<VItemNode, DAffine3>>()[0].value;
982 let (placement, item) = stored.leaves().next().unwrap();
983 assert_eq!(item.vpoints[0], DVec3::X);
984 assert_affine_eq(placement, scale * rotate * translate);
985 assert!(
986 placement
987 .transform_point3(DVec3::X)
988 .abs_diff_eq(dvec3(0.0, 6.0, 0.0), 1e-9)
989 );
990 }
991
992 #[test]
993 fn stored_hierarchy_retains_painters_order_on_three_levels() {
994 let tree = VItemNode::<DAffine3>::group(vec![
1002 VItemNode::<DAffine3>::group(vec![VItemNode::<DAffine3>::group(vec![
1003 VItemNode::leaf(marked_vitem(1.0)),
1004 VItemNode::leaf(marked_vitem(3.0)),
1005 ])]),
1006 VItemNode::leaf(marked_vitem(2.0)),
1007 ]);
1008
1009 let mut frame = EvaluatedFrame::new();
1010 frame.push((0, 0), tree);
1011 let markers: Vec<f64> = frame.items::<VItemNode>()[0]
1012 .value
1013 .leaves()
1014 .map(|(_, item)| item.vpoints[0].x)
1015 .collect();
1016 assert_eq!(markers, [1.0, 3.0, 2.0]);
1018 }
1019
1020 #[cfg(feature = "render")]
1021 #[test]
1022 fn render_extraction_preserves_order_pose_and_removes_missing_leaves() {
1023 use crate::vitem::render::RenderVItem;
1024 use bevy_app::SubApp;
1025 use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
1026 use ranim_core::glam::DMat4;
1027 use ranim_render::{extract::ExtractPlugin, schedule::RenderSchedule, world::SourceOrder};
1028
1029 type Input = Transformed<VItemNode, DAffine3>;
1030
1031 let outer = DAffine3::from_scale(dvec3(2.0, 3.0, 1.0));
1032 let first_pose =
1033 DAffine3::from_translation(DVec3::X) * DAffine3::from_translation(DVec3::Z);
1034 let second_pose = DAffine3::from_translation(DVec3::Y);
1035 let first = marked_vitem(1.0);
1036 let input = VItemNode::group(vec![
1037 VItemNode::leaf(first.clone()).transformed(first_pose),
1038 VItemNode::leaf(marked_vitem(2.0)).transformed(second_pose),
1039 ])
1040 .transformed(outer);
1041 let mut frame = EvaluatedFrame::new();
1042 frame.push((7, 0), input);
1043
1044 let mut app = SubApp::new();
1045 app.update_schedule = Some(RenderSchedule::Extract.intern());
1046 app.insert_resource(frame)
1047 .add_plugins(ExtractPlugin::<Input>::default());
1048 app.update();
1049
1050 let world = app.world_mut();
1051 let mut outputs = world
1052 .query::<(Entity, &SourceOrder, &RenderVItem)>()
1053 .iter(world)
1054 .map(|(entity, order, item)| (entity, *order, item.clone()))
1055 .collect::<Vec<_>>();
1056 outputs.sort_by_key(|(_, order, _)| *order);
1057 assert_eq!(outputs.len(), 2);
1058 assert_eq!(outputs[0].1, SourceOrder { order: 0, part: 0 });
1059 assert_eq!(outputs[1].1, SourceOrder { order: 0, part: 1 });
1060 assert_eq!(outputs[0].2.points, RenderVItem::from(first.clone()).points);
1061 assert_eq!(
1062 outputs[0].2.transform,
1063 DMat4::from(outer * first_pose).as_mat4()
1064 );
1065 assert_eq!(
1066 outputs[1].2.transform,
1067 DMat4::from(outer * second_pose).as_mat4()
1068 );
1069 let retained = outputs[0].0;
1070 let removed = outputs[1].0;
1071
1072 let smaller = VItemNode::group(vec![VItemNode::leaf(first).transformed(first_pose)])
1073 .transformed(outer);
1074 world
1075 .resource_mut::<EvaluatedFrame>()
1076 .update([((7, 0), smaller)]);
1077 app.update();
1078 assert!(app.world().entities().contains(retained));
1079 assert!(!app.world().entities().contains(removed));
1080 }
1081
1082 #[test]
1083 fn aabb_follows_node_rotation_and_empty_groups_degenerate() {
1084 let rect = HierarchyVItem::from_vpoints(vec![
1087 dvec3(-1.0, -1.0, 0.0),
1088 dvec3(1.0, -1.0, 0.0),
1089 dvec3(3.0, -1.0, 0.0),
1090 dvec3(3.0, 0.0, 0.0),
1091 dvec3(3.0, 1.0, 0.0),
1092 dvec3(1.0, 1.0, 0.0),
1093 dvec3(-1.0, 1.0, 0.0),
1094 dvec3(-1.0, 0.0, 0.0),
1095 dvec3(-1.0, -1.0, 0.0),
1096 ]);
1097 let rotated: Transformed<Node<HierarchyVItem>, DAffine3> =
1098 Node::leaf(rect).transformed(DAffine3::from_rotation_translation(
1099 DQuat::from_rotation_z(std::f64::consts::FRAC_PI_2),
1100 DVec3::ZERO,
1101 ));
1102
1103 let [lo, hi] = rotated.aabb();
1104 assert!(lo.abs_diff_eq(dvec3(-1.0, -1.0, 0.0), 1e-9), "lo is {lo:?}");
1107 assert!(hi.abs_diff_eq(dvec3(1.0, 3.0, 0.0), 1e-9), "hi is {hi:?}");
1108
1109 let empty = Node::<HierarchyVItem>::frame();
1111 assert_eq!(empty.aabb(), [DVec3::ZERO, DVec3::ZERO]);
1112 }
1113
1114 #[test]
1115 fn centroid_weights_each_flattened_leaf_equally() {
1116 let asymmetric = Node::<DVec3>::group(vec![
1120 Node::group(vec![
1121 Node::leaf(dvec3(3.0, 0.0, 0.0)),
1122 Node::leaf(dvec3(4.0, 0.0, 0.0)),
1123 Node::leaf(dvec3(5.0, 0.0, 0.0)),
1124 ]),
1125 Node::leaf(dvec3(-36.0, 0.0, 0.0)),
1126 ]);
1127
1128 let centroid = Centroid.locate(&asymmetric);
1129 assert!(
1130 centroid.abs_diff_eq(dvec3(-6.0, 0.0, 0.0), 1e-9),
1131 "centroid is {centroid:?}"
1132 );
1133
1134 let empty = Node::<DVec3>::frame();
1135 assert_eq!(Centroid.locate(&empty), DVec3::ZERO);
1136 }
1137
1138 #[test]
1139 fn alignment_fills_missing_nodes_with_transparent_clones() {
1140 let left = Node::leaf(stroked_vitem(0.0));
1143 let right = Node::<HierarchyVItem>::group(vec![
1144 Node::leaf(stroked_vitem(2.0)).transformed(DAffine3::from(Translation(DVec3::Y))),
1145 ]);
1146
1147 assert!(!left.is_aligned(&right));
1148 let mut left = left;
1149 let mut right = right;
1150 left.align_with(&mut right);
1151 assert!(left.is_aligned(&right));
1152 assert_eq!(left.children().len(), 1);
1153 assert_eq!(right.children().len(), 1);
1154 assert_eq!(right.item().unwrap().stroke_rgbas[0].0.w, 0.0);
1155 assert_eq!(left.item().unwrap().stroke_rgbas[0].0.w, 1.0);
1156
1157 let mid = left.lerp(&right, 0.5);
1160 assert_eq!(mid.children().len(), 1);
1161 let mid_item = mid.item().unwrap();
1162 assert!((mid_item.vpoints[0].x - 0.0).abs() < 1e-6);
1163 assert!((mid_item.stroke_rgbas[0].0.w - 0.5).abs() < 1e-6);
1164 let (mid_world, mid_leaf) = mid.leaves().nth(1).unwrap();
1165 assert!((mid_leaf.vpoints[0].x - 2.0).abs() < 1e-6);
1166 assert!((mid_leaf.stroke_rgbas[0].0.w - 0.5).abs() < 1e-6);
1167 assert_affine_eq(mid_world, DAffine3::from_translation(DVec3::Y));
1168
1169 let big = Node::<HierarchyVItem>::group(vec![
1172 Node::leaf(stroked_vitem(0.0)),
1173 Node::leaf(stroked_vitem(10.0)),
1174 ]);
1175 let small = Node::<HierarchyVItem>::group(vec![Node::leaf(stroked_vitem(20.0))]);
1176 assert!(!big.is_aligned(&small));
1177 let mut big = big;
1178 let mut small = small;
1179 small.align_with(&mut big);
1180 assert!(big.is_aligned(&small));
1181 assert_eq!(small.children().len(), 2);
1182 let stand_in = small.children()[1].inner.item().unwrap();
1183 assert_eq!(stand_in.stroke_rgbas[0].0.w, 0.0);
1184 assert_eq!(stand_in.fill_rgbas[0].0.w, 0.0);
1185 let original = small.children()[0].inner.item().unwrap();
1186 assert_eq!(original.stroke_rgbas[0].0.w, 1.0);
1187 }
1188
1189 #[test]
1190 fn interpolation_moves_poses_only() {
1191 let geometry =
1192 || HierarchyVItem::from_vpoints(vec![dvec3(0.0, 0.0, 0.0), dvec3(1.0, 0.0, 0.0)]);
1193 let start: Transformed<Node<HierarchyVItem>, DAffine3> = Node::leaf(geometry())
1194 .with_id("start")
1195 .transformed(DAffine3::from(Translation(dvec3(1.0, 0.0, 0.0))));
1196 let end: Transformed<Node<HierarchyVItem>, DAffine3> = Node::leaf(geometry())
1197 .with_id("end")
1198 .transformed(DAffine3::from(Translation(dvec3(3.0, 2.0, 0.0))));
1199
1200 assert!(start.is_aligned(&end));
1201 let mid = start.lerp(&end, 0.5);
1202 assert_affine_eq(
1203 mid.transform,
1204 DAffine3::from_translation(dvec3(2.0, 1.0, 0.0)),
1205 );
1206 assert_eq!(mid.inner.id.as_deref(), Some("end"));
1207 }
1208
1209 #[test]
1210 #[should_panic(expected = "aligned")]
1211 fn unaligned_kind_lerp_panics_with_guidance() {
1212 let leaf: Node<u32> = Node::leaf(1);
1213 let group: Node<u32> = Node::group(vec![Node::leaf(2)]);
1214 drop(leaf.lerp(&group, 0.5));
1215 }
1216
1217 #[test]
1218 fn unequal_sibling_counts_follow_vecs_truncating_zip() {
1219 let few = Node::<u32>::group(vec![Node::leaf(1)]);
1220 let many = Node::<u32>::group(vec![Node::leaf(1), Node::leaf(2), Node::leaf(3)]);
1221 let mid = many.lerp(&few, 0.5);
1222 assert_eq!(mid.children().len(), 1);
1223 }
1224
1225 #[test]
1226 fn partial_forwards_ranges_down_recursively() {
1227 let base = stroked_vitem(0.0);
1228 let tree = Node::<HierarchyVItem>::group(vec![Node::leaf(base.clone()); 2])
1229 .transformed(DAffine3::from_translation(DVec3::X));
1230 for (partial, expected) in [
1231 (tree.get_partial(0.25..0.75), base.get_partial(0.25..0.75)),
1232 (
1233 tree.get_partial_closed(0.25..0.75),
1234 base.get_partial_closed(0.25..0.75),
1235 ),
1236 ] {
1237 assert_eq!(partial.transform, tree.transform);
1238 assert_eq!(
1239 partial
1240 .inner
1241 .leaves()
1242 .map(|(_, leaf)| leaf)
1243 .collect::<Vec<_>>(),
1244 [&expected; 2]
1245 );
1246 }
1247 }
1248
1249 #[test]
1250 fn empty_is_a_bare_leaf_with_empty_geometry() {
1251 let empty = Node::<HierarchyVItem>::empty();
1254 assert!(empty.is_leaf());
1255 assert_eq!(empty.children().len(), 0);
1256 let leaf = empty.item().unwrap();
1257 assert_eq!(leaf.stroke_widths[0].0, 0.0);
1258 assert!(leaf.fill_rgbas.iter().all(|rgba| rgba.0 == Vec4::ZERO));
1259
1260 let placed: Transformed<Node<HierarchyVItem>, DAffine3> = empty.into();
1262 assert_eq!(placed.transform, DAffine3::IDENTITY);
1263 }
1264
1265 #[test]
1266 fn map_inner_maps_payloads_and_keeps_the_shape() {
1267 let tree = Node::<u32, Translation>::group(vec![
1268 Node::leaf(1)
1269 .with_id("one")
1270 .transformed(Translation(DVec3::X)),
1271 Node::group(vec![Node::leaf(2)]).into(),
1272 ]);
1273 let mapped = tree.map_inner(|payload| payload * 10);
1274
1275 assert_eq!(
1276 mapped.get(&[0]).unwrap().inner.item(),
1277 Some(&10),
1278 "ids and shape survive mapping"
1279 );
1280 assert_eq!(mapped.get(&[0]).unwrap().inner.id.as_deref(), Some("one"));
1281 assert_eq!(mapped.get(&[0]).unwrap().transform, Translation(DVec3::X));
1282 assert_eq!(mapped.get(&[1, 0]).unwrap().inner.item(), Some(&20));
1283 }
1284
1285 #[test]
1286 fn id_and_index_lookups_address_placements() {
1287 let root = Node::<(), DAffine3>::leaf(()).with_id("root");
1290 assert!(root.by_id("root").is_none());
1291 assert_eq!(root.by_id_path("root"), None);
1292
1293 let tree = Node::<u32>::group(vec![
1295 Node::group(vec![Node::leaf(1), Node::leaf(2)]),
1296 Node::leaf(3),
1297 ]);
1298 assert!(tree.get(&[]).is_none());
1299 assert_eq!(tree.get(&[0]).unwrap().inner.children().len(), 2);
1300 assert_eq!(tree.get(&[0, 1]).unwrap().inner.item(), Some(&2));
1301 assert_eq!(tree.get(&[1]).unwrap().inner.item(), Some(&3));
1302 assert!(tree.get(&[2]).is_none());
1303 assert!(tree.get(&[1, 0]).is_none());
1305
1306 let mut tree = tree;
1307 let target = tree.get_mut(&[0, 0]).unwrap();
1308 assert_eq!(target.inner.item(), Some(&1));
1309 target.transform = DAffine3::from(Translation(DVec3::X));
1310 assert_eq!(
1311 tree.get(&[0, 0]).unwrap().transform,
1312 DAffine3::from(Translation(DVec3::X))
1313 );
1314
1315 let tree = Node::<(), DAffine3>::group(vec![
1318 Node::leaf(()).with_id("dup"),
1319 Node::group(vec![
1320 Node::leaf(()).with_id("dup"),
1321 Node::leaf(()).with_id("other"),
1322 ]),
1323 ]);
1324 assert!(tree.by_id("dup").unwrap().inner.is_leaf());
1325 assert_eq!(tree.by_ids("dup").len(), 2);
1326 assert_eq!(tree.by_id_path("dup"), Some(vec![0]));
1327 assert_eq!(tree.by_id_path("other"), Some(vec![1, 1]));
1328 assert!(tree.by_id("missing").is_none());
1329
1330 let mut tree = tree;
1331 tree.by_id_mut("other").unwrap().inner.id = Some("renamed".into());
1332 assert!(tree.by_id("other").is_none());
1333 assert!(tree.by_id("renamed").is_some());
1334 }
1335
1336 #[test]
1337 fn styling_traits_read_first_leaf_and_write_every_leaf() {
1338 let mut tree = Node::<HierarchyVItem>::group(vec![
1339 Node::leaf(stroked_vitem(0.0)),
1340 Node::group(vec![
1341 Node::leaf(stroked_vitem(5.0)),
1342 Node::leaf(stroked_vitem(9.0)),
1343 ]),
1344 ]);
1345
1346 assert_eq!(tree.stroke_width(), 0.04);
1348 tree.set_opacity(0.5);
1349 let opacities: Vec<f32> = tree
1350 .leaves_mut()
1351 .map(|leaf| leaf.stroke_rgbas[0].0.w)
1352 .collect();
1353 assert_eq!(opacities, [0.5, 0.5, 0.5]);
1354
1355 tree.set_stroke_width(1.0);
1357 let widths: Vec<f32> = tree
1358 .leaves_mut()
1359 .map(|leaf| leaf.stroke_widths[0].0)
1360 .collect();
1361 assert_eq!(widths, [1.0, 1.0, 1.0]);
1362
1363 let empty = Node::<HierarchyVItem>::frame();
1365 assert_eq!(empty.stroke_width(), 0.0);
1366 assert_eq!(empty.fill_color(), css::WHITE);
1367 }
1368}