1use crate::constants::{HAND_SIDE_LEFT, HAND_SIDE_RIGHT};
4
5#[derive(Clone, Copy, Debug, Default, PartialEq)]
7pub struct Quaternion {
8 pub w: f64,
9 pub x: f64,
10 pub y: f64,
11 pub z: f64,
12}
13
14#[derive(Clone, Copy, Debug, Default, PartialEq)]
16pub struct Landmark {
17 pub x: f64,
18 pub y: f64,
19 pub z: f64,
20 pub confidence: f64,
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
25pub enum HandSide {
26 Right,
27 Left,
28}
29
30impl HandSide {
31 pub const fn from_byte(byte: u8) -> Option<Self> {
33 match byte {
34 HAND_SIDE_RIGHT => Some(Self::Right),
35 HAND_SIDE_LEFT => Some(Self::Left),
36 _ => None,
37 }
38 }
39
40 pub const fn as_byte(self) -> u8 {
42 match self {
43 Self::Right => HAND_SIDE_RIGHT,
44 Self::Left => HAND_SIDE_LEFT,
45 }
46 }
47
48 pub const fn as_str(self) -> &'static str {
50 match self {
51 Self::Right => "right",
52 Self::Left => "left",
53 }
54 }
55}
56
57macro_rules! name_enum {
60 (
61 $(#[$attr:meta])*
62 $name:ident { $( $variant:ident => $label:literal ),+ $(,)? }
63 ) => {
64 $(#[$attr])*
65 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
66 pub enum $name {
67 $( $variant ),+
68 }
69
70 impl $name {
71 pub const ALL: &'static [$name] = &[ $( $name::$variant ),+ ];
73
74 pub const fn as_str(self) -> &'static str {
76 match self {
77 $( $name::$variant => $label ),+
78 }
79 }
80
81 pub const fn index(self) -> usize {
83 self as usize
84 }
85
86 pub const fn from_index(index: usize) -> Option<$name> {
88 let all: &[$name] = $name::ALL;
89 if index < all.len() {
90 Some(all[index])
91 } else {
92 None
93 }
94 }
95 }
96 };
97}
98
99name_enum! {
100 Joint {
102 Hips => "hips",
103 Spine => "spine",
104 Neck => "neck",
105 RightArm => "right_arm",
106 RightForearm => "right_forearm",
107 LeftArm => "left_arm",
108 LeftForearm => "left_forearm",
109 RightUpLeg => "right_upleg",
110 RightLeg => "right_leg",
111 RightFoot => "right_foot",
112 LeftUpLeg => "left_upleg",
113 LeftLeg => "left_leg",
114 LeftFoot => "left_foot",
115 }
116}
117
118name_enum! {
119 LandmarkName {
121 SacroiliacJoint => "sacroiliac_joint",
122 SuprasternalNotch => "suprasternal_notch",
123 Nose => "nose",
124 LeftEar => "left_ear",
125 RightEar => "right_ear",
126 LeftShoulder => "left_shoulder",
127 RightShoulder => "right_shoulder",
128 LeftElbow => "left_elbow",
129 RightElbow => "right_elbow",
130 LeftWrist => "left_wrist",
131 RightWrist => "right_wrist",
132 LeftHip => "left_hip",
133 RightHip => "right_hip",
134 LeftKnee => "left_knee",
135 RightKnee => "right_knee",
136 LeftAnkle => "left_ankle",
137 RightAnkle => "right_ankle",
138 LeftFootIndex => "left_foot_index",
139 RightFootIndex => "right_foot_index",
140 }
141}
142
143name_enum! {
144 HandJoint {
146 Wrist => "wrist",
147 ThumbMcp => "thumb_mcp",
148 ThumbPip => "thumb_pip",
149 ThumbDip => "thumb_dip",
150 IndexMcp => "index_mcp",
151 IndexPip => "index_pip",
152 IndexDip => "index_dip",
153 MiddleMcp => "middle_mcp",
154 MiddlePip => "middle_pip",
155 MiddleDip => "middle_dip",
156 RingMcp => "ring_mcp",
157 RingPip => "ring_pip",
158 RingDip => "ring_dip",
159 PinkyMcp => "pinky_mcp",
160 PinkyPip => "pinky_pip",
161 PinkyDip => "pinky_dip",
162 }
163}
164
165name_enum! {
166 IsbAngleName {
171 ThoraxLateralBend => "thorax_lateral_bend",
172 ThoraxAxialRotation => "thorax_axial_rotation",
173 NeckFlexion => "neck_flexion",
174 NeckLateralBend => "neck_lateral_bend",
175 NeckAxialRotation => "neck_axial_rotation",
176 RightShoulderPlaneOfElevation => "right_shoulder_plane_of_elevation",
177 RightShoulderElevation => "right_shoulder_elevation",
178 LeftShoulderPlaneOfElevation => "left_shoulder_plane_of_elevation",
179 LeftShoulderElevation => "left_shoulder_elevation",
180 RightElbowFlexion => "right_elbow_flexion",
181 LeftElbowFlexion => "left_elbow_flexion",
182 RightHipFlexion => "right_hip_flexion",
183 RightHipAdduction => "right_hip_adduction",
184 RightHipInternalRotation => "right_hip_internal_rotation",
185 LeftHipFlexion => "left_hip_flexion",
186 LeftHipAdduction => "left_hip_adduction",
187 LeftHipInternalRotation => "left_hip_internal_rotation",
188 RightKneeFlexion => "right_knee_flexion",
189 LeftKneeFlexion => "left_knee_flexion",
190 RightAnkleDorsiflexion => "right_ankle_dorsiflexion",
191 LeftAnkleDorsiflexion => "left_ankle_dorsiflexion",
192 }
193}
194
195name_enum! {
196 HandLandmarkName {
198 Wrist => "wrist",
199 ThumbCmc => "thumb_cmc",
200 ThumbMcp => "thumb_mcp",
201 ThumbIp => "thumb_ip",
202 ThumbTip => "thumb_tip",
203 IndexMcp => "index_mcp",
204 IndexPip => "index_pip",
205 IndexDip => "index_dip",
206 IndexTip => "index_tip",
207 MiddleMcp => "middle_mcp",
208 MiddlePip => "middle_pip",
209 MiddleDip => "middle_dip",
210 MiddleTip => "middle_tip",
211 RingMcp => "ring_mcp",
212 RingPip => "ring_pip",
213 RingDip => "ring_dip",
214 RingTip => "ring_tip",
215 PinkyMcp => "pinky_mcp",
216 PinkyPip => "pinky_pip",
217 PinkyDip => "pinky_dip",
218 PinkyTip => "pinky_tip",
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::constants::{
226 BODY_ISB_ANGLE_COUNT, BODY_JOINT_COUNT, BODY_LANDMARK_COUNT, HAND_JOINT_COUNT,
227 HAND_LANDMARK_COUNT,
228 };
229
230 #[test]
231 fn enum_lengths_match_protocol_counts() {
232 assert_eq!(Joint::ALL.len(), BODY_JOINT_COUNT);
234 assert_eq!(LandmarkName::ALL.len(), BODY_LANDMARK_COUNT);
235 assert_eq!(HandJoint::ALL.len(), HAND_JOINT_COUNT);
236 assert_eq!(HandLandmarkName::ALL.len(), HAND_LANDMARK_COUNT);
237 assert_eq!(IsbAngleName::ALL.len(), BODY_ISB_ANGLE_COUNT);
238 }
239
240 #[test]
241 fn index_round_trips_through_from_index() {
242 for (i, joint) in Joint::ALL.iter().enumerate() {
244 let recovered = Joint::from_index(i);
246
247 assert_eq!(recovered, Some(*joint));
249 assert_eq!(joint.index(), i);
250 }
251 }
252
253 #[test]
254 fn from_index_rejects_out_of_range() {
255 assert_eq!(Joint::from_index(BODY_JOINT_COUNT), None);
257 assert_eq!(HandLandmarkName::from_index(usize::MAX), None);
258 }
259
260 #[test]
261 fn hand_side_byte_round_trips() {
262 assert_eq!(
264 HandSide::from_byte(HandSide::Right.as_byte()),
265 Some(HandSide::Right)
266 );
267 assert_eq!(
268 HandSide::from_byte(HandSide::Left.as_byte()),
269 Some(HandSide::Left)
270 );
271 assert_eq!(HandSide::from_byte(2), None);
272 }
273
274 #[test]
275 fn labels_are_stable() {
276 assert_eq!(Joint::Hips.as_str(), "hips");
278 assert_eq!(Joint::LeftFoot.as_str(), "left_foot");
279 assert_eq!(LandmarkName::SacroiliacJoint.as_str(), "sacroiliac_joint");
280 assert_eq!(HandJoint::PinkyDip.as_str(), "pinky_dip");
281 assert_eq!(HandLandmarkName::ThumbTip.as_str(), "thumb_tip");
282 }
283}