#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SkeletonJsonConfig {
pub include_bind_pose: bool,
pub precision: usize,
}
#[derive(Debug, Clone)]
pub struct JointJsonNode {
pub id: u32,
pub name: String,
pub parent_id: Option<u32>,
pub head: [f64; 3],
pub tail: [f64; 3],
}
#[derive(Debug, Clone)]
pub struct SkeletonJsonResult {
pub joints: Vec<JointJsonNode>,
}
#[allow(dead_code)]
pub fn default_skeleton_json_config() -> SkeletonJsonConfig {
SkeletonJsonConfig {
include_bind_pose: false,
precision: 6,
}
}
#[allow(dead_code)]
pub fn new_skeleton_json_export() -> SkeletonJsonResult {
SkeletonJsonResult { joints: Vec::new() }
}
#[allow(dead_code)]
pub fn skeleton_json_add_joint(result: &mut SkeletonJsonResult, joint: JointJsonNode) {
result.joints.push(joint);
}
#[allow(dead_code)]
pub fn skeleton_json_set_parent(
result: &mut SkeletonJsonResult,
child_id: u32,
parent_id: u32,
) -> bool {
if let Some(j) = result.joints.iter_mut().find(|j| j.id == child_id) {
j.parent_id = Some(parent_id);
true
} else {
false
}
}
#[allow(dead_code)]
pub fn skeleton_json_to_string(result: &SkeletonJsonResult, cfg: &SkeletonJsonConfig) -> String {
let prec = cfg.precision;
let mut out = String::from("{\"joints\":[\n");
for (i, j) in result.joints.iter().enumerate() {
let comma = if i + 1 < result.joints.len() { "," } else { "" };
let parent = match j.parent_id {
Some(p) => format!("{}", p),
None => "null".to_string(),
};
let head = format!(
"[{:.prec$},{:.prec$},{:.prec$}]",
j.head[0], j.head[1], j.head[2]
);
let tail = format!(
"[{:.prec$},{:.prec$},{:.prec$}]",
j.tail[0], j.tail[1], j.tail[2]
);
out.push_str(&format!(
" {{\"id\":{},\"name\":\"{}\",\"parent\":{},\"head\":{},\"tail\":{}}}{}",
j.id, j.name, parent, head, tail, comma
));
out.push('\n');
}
out.push_str("]}");
out
}
#[allow(dead_code)]
pub fn skeleton_json_joint_count(result: &SkeletonJsonResult) -> usize {
result.joints.len()
}
#[allow(dead_code)]
pub fn skeleton_json_write_to_file(
result: &SkeletonJsonResult,
cfg: &SkeletonJsonConfig,
_path: &str,
) -> usize {
skeleton_json_to_string(result, cfg).len()
}
#[allow(dead_code)]
pub fn skeleton_json_root_joints(result: &SkeletonJsonResult) -> Vec<u32> {
result
.joints
.iter()
.filter(|j| j.parent_id.is_none())
.map(|j| j.id)
.collect()
}
#[allow(dead_code)]
pub fn skeleton_json_depth(result: &SkeletonJsonResult) -> usize {
fn depth_of(result: &SkeletonJsonResult, id: u32) -> usize {
let children: Vec<u32> = result
.joints
.iter()
.filter(|j| j.parent_id == Some(id))
.map(|j| j.id)
.collect();
if children.is_empty() {
1
} else {
1 + children
.iter()
.map(|&c| depth_of(result, c))
.max()
.unwrap_or(0)
}
}
let roots = skeleton_json_root_joints(result);
roots
.iter()
.map(|&r| depth_of(result, r))
.max()
.unwrap_or(0)
}
#[allow(dead_code)]
pub fn skeleton_json_clear(result: &mut SkeletonJsonResult) {
result.joints.clear();
}
fn make_joint(id: u32, name: &str, parent: Option<u32>) -> JointJsonNode {
JointJsonNode {
id,
name: name.to_string(),
parent_id: parent,
head: [0.0, f64::from(id), 0.0],
tail: [0.0, f64::from(id) + 1.0, 0.0],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_values() {
let cfg = default_skeleton_json_config();
assert!(!cfg.include_bind_pose);
assert_eq!(cfg.precision, 6);
}
#[test]
fn new_export_is_empty() {
let r = new_skeleton_json_export();
assert_eq!(skeleton_json_joint_count(&r), 0);
}
#[test]
fn add_joint_increases_count() {
let mut r = new_skeleton_json_export();
skeleton_json_add_joint(&mut r, make_joint(0, "root", None));
assert_eq!(skeleton_json_joint_count(&r), 1);
}
#[test]
fn set_parent_updates_joint() {
let mut r = new_skeleton_json_export();
skeleton_json_add_joint(&mut r, make_joint(0, "hip", None));
skeleton_json_add_joint(&mut r, make_joint(1, "spine", None));
let ok = skeleton_json_set_parent(&mut r, 1, 0);
assert!(ok);
assert_eq!(r.joints[1].parent_id, Some(0));
}
#[test]
fn set_parent_missing_joint_returns_false() {
let mut r = new_skeleton_json_export();
let ok = skeleton_json_set_parent(&mut r, 99, 0);
assert!(!ok);
}
#[test]
fn root_joints_are_detected() {
let mut r = new_skeleton_json_export();
skeleton_json_add_joint(&mut r, make_joint(0, "hip", None));
skeleton_json_add_joint(&mut r, make_joint(1, "spine", Some(0)));
let roots = skeleton_json_root_joints(&r);
assert_eq!(roots, vec![0]);
}
#[test]
fn json_contains_joint_name() {
let mut r = new_skeleton_json_export();
skeleton_json_add_joint(&mut r, make_joint(0, "pelvis", None));
let cfg = default_skeleton_json_config();
let json = skeleton_json_to_string(&r, &cfg);
assert!(json.contains("\"pelvis\""));
}
#[test]
fn depth_single_chain() {
let mut r = new_skeleton_json_export();
skeleton_json_add_joint(&mut r, make_joint(0, "a", None));
skeleton_json_add_joint(&mut r, make_joint(1, "b", Some(0)));
skeleton_json_add_joint(&mut r, make_joint(2, "c", Some(1)));
assert_eq!(skeleton_json_depth(&r), 3);
}
#[test]
fn clear_removes_all_joints() {
let mut r = new_skeleton_json_export();
skeleton_json_add_joint(&mut r, make_joint(0, "x", None));
skeleton_json_clear(&mut r);
assert_eq!(skeleton_json_joint_count(&r), 0);
}
#[test]
fn write_to_file_returns_nonzero_bytes() {
let mut r = new_skeleton_json_export();
skeleton_json_add_joint(&mut r, make_joint(0, "root", None));
let cfg = default_skeleton_json_config();
let bytes = skeleton_json_write_to_file(&r, &cfg, "/tmp/skel.json");
assert!(bytes > 0);
}
}