1use crate::engine::pose_graph::{PoseGraph, PoseGraphError};
6use std::collections::{HashMap, HashSet};
7
8#[derive(Clone, Debug, PartialEq)]
10pub struct Attachment {
11 pub id: String,
12 pub target: String,
13 pub dx: f64,
14 pub dy: f64,
15 pub frame: u32,
16 pub confidence: f64,
17 pub node: String,
19}
20
21pub type Attachments = HashMap<String, Attachment>;
23
24pub fn resolve_target<'a>(attachments: &'a Attachments, mut id: &'a str) -> &'a str {
26 let mut seen = HashSet::new();
27 while let Some(a) = attachments.get(id) {
28 if !seen.insert(id) {
29 break;
30 }
31 id = &a.target;
32 }
33 id
34}
35
36pub fn attachment_shift<'a>(attachments: &'a Attachments, mut id: &'a str) -> (f64, f64) {
38 let (mut x, mut y) = (0.0, 0.0);
39 let mut seen = HashSet::new();
40 while let Some(a) = attachments.get(id) {
41 if !seen.insert(id) {
42 break;
43 }
44 x += a.dx;
45 y += a.dy;
46 id = &a.target;
47 }
48 (x, y)
49}
50
51pub fn render_shift<'a>(
55 attachments: &'a Attachments,
56 mut id: &'a str,
57 graph: &PoseGraph,
58) -> Result<(f64, f64), PoseGraphError> {
59 let (mut x, mut y) = (0.0, 0.0);
60 let mut seen = HashSet::new();
61 while let Some(a) = attachments.get(id) {
62 if !seen.insert(id) {
63 break;
64 }
65 let (cx, cy) = graph.correction(&a.node, a.frame as f64)?;
66 x += a.dx + cx;
67 y += a.dy + cy;
68 id = &a.target;
69 }
70 Ok((x, y))
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 fn attach(id: &str, target: &str, dx: f64, dy: f64) -> (String, Attachment) {
78 (
79 id.to_string(),
80 Attachment {
81 id: id.to_string(),
82 target: target.to_string(),
83 dx,
84 dy,
85 frame: 0,
86 confidence: 1.0,
87 node: String::new(),
88 },
89 )
90 }
91
92 #[test]
93 fn an_unattached_canvas_resolves_to_itself_unshifted() {
94 let map = Attachments::new();
95 assert_eq!(resolve_target(&map, "a"), "a");
96 assert_eq!(attachment_shift(&map, "a"), (0.0, 0.0));
97 }
98
99 #[test]
100 fn a_chain_resolves_to_its_end_and_sums_its_shifts() {
101 let map: Attachments = [attach("a", "b", 1.0, 2.0), attach("b", "c", 10.0, 20.0)]
102 .into_iter()
103 .collect();
104 assert_eq!(resolve_target(&map, "a"), "c");
105 assert_eq!(attachment_shift(&map, "a"), (11.0, 22.0));
106 assert_eq!(resolve_target(&map, "b"), "c");
107 }
108
109 #[test]
110 fn the_render_shift_adds_how_far_each_anchor_node_moved() {
111 use crate::engine::pose_graph::PoseNode;
112 let node = |id: &str, moved: f64| PoseNode {
113 id: id.into(),
114 canvas_id: "t".into(),
115 frame: 0,
116 x: moved,
117 y: 0.0,
118 original_x: 0.0,
119 original_y: 0.0,
120 pinned: false,
121 next: None,
122 };
123 let mut map: Attachments = [attach("a", "b", 1.0, 2.0), attach("b", "c", 10.0, 20.0)]
124 .into_iter()
125 .collect();
126 map.get_mut("a").unwrap().node = "na".into();
127 map.get_mut("b").unwrap().node = "nb".into();
128 let mut g = PoseGraph::new();
129 g.hydrate_node(node("na", 3.0));
130 g.hydrate_node(node("nb", 0.5));
131 assert_eq!(
132 render_shift(&map, "a", &g),
133 Ok((1.0 + 3.0 + (10.0 + 0.5), 22.0))
134 );
135 assert_eq!(render_shift(&map, "c", &g), Ok((0.0, 0.0)));
136 g = PoseGraph::new();
137 assert_eq!(
138 render_shift(&map, "a", &g),
139 Err(PoseGraphError::MissingPose)
140 );
141 }
142
143 #[test]
144 fn a_cycle_stops_at_the_first_repeat() {
145 let map: Attachments = [attach("a", "b", 1.0, 0.0), attach("b", "a", 2.0, 0.0)]
146 .into_iter()
147 .collect();
148 assert_eq!(resolve_target(&map, "a"), "a");
149 assert_eq!(attachment_shift(&map, "a"), (3.0, 0.0));
150 }
151}