use crate::constants::{HAND_SIDE_LEFT, HAND_SIDE_RIGHT};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Quaternion {
pub w: f64,
pub x: f64,
pub y: f64,
pub z: f64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Landmark {
pub x: f64,
pub y: f64,
pub z: f64,
pub confidence: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum HandSide {
Right,
Left,
}
impl HandSide {
pub const fn from_byte(byte: u8) -> Option<Self> {
match byte {
HAND_SIDE_RIGHT => Some(Self::Right),
HAND_SIDE_LEFT => Some(Self::Left),
_ => None,
}
}
pub const fn as_byte(self) -> u8 {
match self {
Self::Right => HAND_SIDE_RIGHT,
Self::Left => HAND_SIDE_LEFT,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Right => "right",
Self::Left => "left",
}
}
}
macro_rules! name_enum {
(
$(#[$attr:meta])*
$name:ident { $( $variant:ident => $label:literal ),+ $(,)? }
) => {
$(#[$attr])*
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum $name {
$( $variant ),+
}
impl $name {
pub const ALL: &'static [$name] = &[ $( $name::$variant ),+ ];
pub const fn as_str(self) -> &'static str {
match self {
$( $name::$variant => $label ),+
}
}
pub const fn index(self) -> usize {
self as usize
}
pub const fn from_index(index: usize) -> Option<$name> {
let all: &[$name] = $name::ALL;
if index < all.len() {
Some(all[index])
} else {
None
}
}
}
};
}
name_enum! {
Joint {
Hips => "hips",
Spine => "spine",
Neck => "neck",
RightArm => "right_arm",
RightForearm => "right_forearm",
LeftArm => "left_arm",
LeftForearm => "left_forearm",
RightUpLeg => "right_upleg",
RightLeg => "right_leg",
RightFoot => "right_foot",
LeftUpLeg => "left_upleg",
LeftLeg => "left_leg",
LeftFoot => "left_foot",
}
}
name_enum! {
LandmarkName {
SacroiliacJoint => "sacroiliac_joint",
SuprasternalNotch => "suprasternal_notch",
Nose => "nose",
LeftEar => "left_ear",
RightEar => "right_ear",
LeftShoulder => "left_shoulder",
RightShoulder => "right_shoulder",
LeftElbow => "left_elbow",
RightElbow => "right_elbow",
LeftWrist => "left_wrist",
RightWrist => "right_wrist",
LeftHip => "left_hip",
RightHip => "right_hip",
LeftKnee => "left_knee",
RightKnee => "right_knee",
LeftAnkle => "left_ankle",
RightAnkle => "right_ankle",
LeftFootIndex => "left_foot_index",
RightFootIndex => "right_foot_index",
}
}
name_enum! {
HandJoint {
Wrist => "wrist",
ThumbMcp => "thumb_mcp",
ThumbPip => "thumb_pip",
ThumbDip => "thumb_dip",
IndexMcp => "index_mcp",
IndexPip => "index_pip",
IndexDip => "index_dip",
MiddleMcp => "middle_mcp",
MiddlePip => "middle_pip",
MiddleDip => "middle_dip",
RingMcp => "ring_mcp",
RingPip => "ring_pip",
RingDip => "ring_dip",
PinkyMcp => "pinky_mcp",
PinkyPip => "pinky_pip",
PinkyDip => "pinky_dip",
}
}
name_enum! {
HandLandmarkName {
Wrist => "wrist",
ThumbCmc => "thumb_cmc",
ThumbMcp => "thumb_mcp",
ThumbIp => "thumb_ip",
ThumbTip => "thumb_tip",
IndexMcp => "index_mcp",
IndexPip => "index_pip",
IndexDip => "index_dip",
IndexTip => "index_tip",
MiddleMcp => "middle_mcp",
MiddlePip => "middle_pip",
MiddleDip => "middle_dip",
MiddleTip => "middle_tip",
RingMcp => "ring_mcp",
RingPip => "ring_pip",
RingDip => "ring_dip",
RingTip => "ring_tip",
PinkyMcp => "pinky_mcp",
PinkyPip => "pinky_pip",
PinkyDip => "pinky_dip",
PinkyTip => "pinky_tip",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::{
BODY_JOINT_COUNT, BODY_LANDMARK_COUNT, HAND_JOINT_COUNT, HAND_LANDMARK_COUNT,
};
#[test]
fn enum_lengths_match_protocol_counts() {
assert_eq!(Joint::ALL.len(), BODY_JOINT_COUNT);
assert_eq!(LandmarkName::ALL.len(), BODY_LANDMARK_COUNT);
assert_eq!(HandJoint::ALL.len(), HAND_JOINT_COUNT);
assert_eq!(HandLandmarkName::ALL.len(), HAND_LANDMARK_COUNT);
}
#[test]
fn index_round_trips_through_from_index() {
for (i, joint) in Joint::ALL.iter().enumerate() {
let recovered = Joint::from_index(i);
assert_eq!(recovered, Some(*joint));
assert_eq!(joint.index(), i);
}
}
#[test]
fn from_index_rejects_out_of_range() {
assert_eq!(Joint::from_index(BODY_JOINT_COUNT), None);
assert_eq!(HandLandmarkName::from_index(usize::MAX), None);
}
#[test]
fn hand_side_byte_round_trips() {
assert_eq!(
HandSide::from_byte(HandSide::Right.as_byte()),
Some(HandSide::Right)
);
assert_eq!(
HandSide::from_byte(HandSide::Left.as_byte()),
Some(HandSide::Left)
);
assert_eq!(HandSide::from_byte(2), None);
}
#[test]
fn labels_are_stable() {
assert_eq!(Joint::Hips.as_str(), "hips");
assert_eq!(Joint::LeftFoot.as_str(), "left_foot");
assert_eq!(LandmarkName::SacroiliacJoint.as_str(), "sacroiliac_joint");
assert_eq!(HandJoint::PinkyDip.as_str(), "pinky_dip");
assert_eq!(HandLandmarkName::ThumbTip.as_str(), "thumb_tip");
}
}