#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BoneVisConfig {
pub bone_color: [f32; 3],
pub joint_radius: f32,
pub show_joints: bool,
pub enabled: bool,
pub line_width: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct BoneStick {
pub child_pos: [f32; 3],
pub parent_pos: [f32; 3],
pub child_idx: usize,
pub parent_idx: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct JointSphere {
pub position: [f32; 3],
pub radius: f32,
pub joint_idx: usize,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct BoneVisDrawCall {
pub sticks: Vec<BoneStick>,
pub spheres: Vec<JointSphere>,
pub config: BoneVisConfig,
}
#[allow(dead_code)]
pub fn default_bone_vis_config() -> BoneVisConfig {
BoneVisConfig {
bone_color: [1.0, 0.6, 0.1],
joint_radius: 0.05,
show_joints: true,
enabled: true,
line_width: 2.0,
}
}
#[allow(dead_code)]
pub fn build_bone_sticks(positions: &[[f32; 3]], parents: &[i32]) -> Vec<BoneStick> {
let len = positions.len().min(parents.len());
let mut sticks = Vec::new();
for i in 0..len {
let p = parents[i];
if p < 0 {
continue;
}
let parent_idx = p as usize;
if parent_idx >= positions.len() {
continue;
}
sticks.push(BoneStick {
child_pos: positions[i],
parent_pos: positions[parent_idx],
child_idx: i,
parent_idx,
});
}
sticks
}
#[allow(dead_code)]
pub fn build_joint_spheres(positions: &[[f32; 3]], cfg: &BoneVisConfig) -> Vec<JointSphere> {
positions
.iter()
.enumerate()
.map(|(i, &pos)| JointSphere {
position: pos,
radius: cfg.joint_radius,
joint_idx: i,
})
.collect()
}
#[allow(dead_code)]
pub fn bone_vis_draw_call(
positions: &[[f32; 3]],
parents: &[i32],
cfg: &BoneVisConfig,
) -> BoneVisDrawCall {
let sticks = build_bone_sticks(positions, parents);
let spheres = if cfg.show_joints {
build_joint_spheres(positions, cfg)
} else {
Vec::new()
};
BoneVisDrawCall {
sticks,
spheres,
config: cfg.clone(),
}
}
#[allow(dead_code)]
pub fn bone_count(call: &BoneVisDrawCall) -> usize {
call.sticks.len()
}
#[allow(dead_code)]
pub fn set_bone_color(cfg: &mut BoneVisConfig, r: f32, g: f32, b: f32) {
cfg.bone_color = [r, g, b];
}
#[allow(dead_code)]
pub fn set_joint_radius(cfg: &mut BoneVisConfig, radius: f32) {
cfg.joint_radius = radius;
}
#[allow(dead_code)]
pub fn bone_vis_toggle_joints(cfg: &mut BoneVisConfig) {
cfg.show_joints = !cfg.show_joints;
}
#[allow(dead_code)]
pub fn bone_vis_is_enabled(cfg: &BoneVisConfig) -> bool {
cfg.enabled
}
#[allow(dead_code)]
pub fn selected_bone_draw_call(call: &BoneVisDrawCall, bone_idx: usize) -> Option<BoneStick> {
call.sticks.get(bone_idx).cloned()
}
#[cfg(test)]
mod tests {
use super::*;
fn chain_positions() -> Vec<[f32; 3]> {
vec![[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 2.0, 0.0]]
}
fn chain_parents() -> Vec<i32> {
vec![-1, 0, 1]
}
#[test]
fn default_config_enabled() {
let cfg = default_bone_vis_config();
assert!(bone_vis_is_enabled(&cfg));
assert!(cfg.show_joints);
}
#[test]
fn build_sticks_single_chain() {
let pos = chain_positions();
let par = chain_parents();
let sticks = build_bone_sticks(&pos, &par);
assert_eq!(sticks.len(), 2);
}
#[test]
fn build_sticks_root_only() {
let pos = vec![[0.0_f32, 0.0, 0.0]];
let par = vec![-1_i32];
let sticks = build_bone_sticks(&pos, &par);
assert_eq!(sticks.len(), 0);
}
#[test]
fn build_joint_spheres_count() {
let pos = chain_positions();
let cfg = default_bone_vis_config();
let spheres = build_joint_spheres(&pos, &cfg);
assert_eq!(spheres.len(), 3);
}
#[test]
fn joint_sphere_radius_matches_config() {
let pos = vec![[1.0_f32, 2.0, 3.0]];
let mut cfg = default_bone_vis_config();
set_joint_radius(&mut cfg, 0.12);
let spheres = build_joint_spheres(&pos, &cfg);
assert!((spheres[0].radius - 0.12).abs() < 1e-6);
}
#[test]
fn bone_vis_draw_call_bone_count() {
let pos = chain_positions();
let par = chain_parents();
let cfg = default_bone_vis_config();
let call = bone_vis_draw_call(&pos, &par, &cfg);
assert_eq!(bone_count(&call), 2);
}
#[test]
fn selected_bone_in_bounds() {
let pos = chain_positions();
let par = chain_parents();
let cfg = default_bone_vis_config();
let call = bone_vis_draw_call(&pos, &par, &cfg);
assert!(selected_bone_draw_call(&call, 0).is_some());
}
#[test]
fn selected_bone_out_of_bounds() {
let pos = chain_positions();
let par = chain_parents();
let cfg = default_bone_vis_config();
let call = bone_vis_draw_call(&pos, &par, &cfg);
assert!(selected_bone_draw_call(&call, 99).is_none());
}
#[test]
fn toggle_joints_hides_spheres() {
let pos = chain_positions();
let par = chain_parents();
let mut cfg = default_bone_vis_config();
bone_vis_toggle_joints(&mut cfg);
let call = bone_vis_draw_call(&pos, &par, &cfg);
assert_eq!(call.spheres.len(), 0);
}
#[test]
fn set_bone_color_applies() {
let mut cfg = default_bone_vis_config();
set_bone_color(&mut cfg, 0.1, 0.2, 0.3);
assert!((cfg.bone_color[0] - 0.1).abs() < 1e-6);
assert!((cfg.bone_color[1] - 0.2).abs() < 1e-6);
assert!((cfg.bone_color[2] - 0.3).abs() < 1e-6);
}
}