#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IkConstraintType {
Position,
Rotation,
LookAt,
TwoJoint,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct IkConstraintEntry {
pub bone_name: String,
pub target_name: String,
pub constraint_type: IkConstraintType,
pub weight: f32,
pub chain_length: u32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct IkConstraintExport {
pub constraints: Vec<IkConstraintEntry>,
}
#[allow(dead_code)]
pub fn new_ik_constraint_export() -> IkConstraintExport {
IkConstraintExport {
constraints: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_ik_constraint(export: &mut IkConstraintExport, entry: IkConstraintEntry) {
export.constraints.push(entry);
}
#[allow(dead_code)]
pub fn ik_constraint_count(export: &IkConstraintExport) -> usize {
export.constraints.len()
}
#[allow(dead_code)]
pub fn count_constraint_type(export: &IkConstraintExport, ctype: IkConstraintType) -> usize {
export
.constraints
.iter()
.filter(|c| c.constraint_type == ctype)
.count()
}
#[allow(dead_code)]
pub fn find_constraint_by_bone<'a>(
export: &'a IkConstraintExport,
bone: &str,
) -> Option<&'a IkConstraintEntry> {
export.constraints.iter().find(|c| c.bone_name == bone)
}
#[allow(dead_code)]
pub fn validate_ik_weights(export: &IkConstraintExport) -> bool {
export
.constraints
.iter()
.all(|c| (0.0..=1.0).contains(&c.weight))
}
#[allow(dead_code)]
pub fn clamp_ik_weights(export: &mut IkConstraintExport) {
for c in &mut export.constraints {
c.weight = c.weight.clamp(0.0, 1.0);
}
}
#[allow(dead_code)]
pub fn avg_chain_length_ik(export: &IkConstraintExport) -> f32 {
let n = export.constraints.len();
if n == 0 {
return 0.0;
}
export
.constraints
.iter()
.map(|c| c.chain_length as f32)
.sum::<f32>()
/ n as f32
}
#[allow(dead_code)]
pub fn ik_constraint_to_json(export: &IkConstraintExport) -> String {
format!("{{\"constraint_count\":{}}}", export.constraints.len())
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_constraint(bone: &str) -> IkConstraintEntry {
IkConstraintEntry {
bone_name: bone.to_string(),
target_name: "target".to_string(),
constraint_type: IkConstraintType::Position,
weight: 1.0,
chain_length: 3,
}
}
#[test]
fn test_add_and_count() {
let mut e = new_ik_constraint_export();
add_ik_constraint(&mut e, sample_constraint("arm"));
assert_eq!(ik_constraint_count(&e), 1);
}
#[test]
fn test_find_by_bone() {
let mut e = new_ik_constraint_export();
add_ik_constraint(&mut e, sample_constraint("arm"));
assert!(find_constraint_by_bone(&e, "arm").is_some());
}
#[test]
fn test_find_missing() {
let e = new_ik_constraint_export();
assert!(find_constraint_by_bone(&e, "leg").is_none());
}
#[test]
fn test_count_type() {
let mut e = new_ik_constraint_export();
add_ik_constraint(&mut e, sample_constraint("arm"));
assert_eq!(count_constraint_type(&e, IkConstraintType::Position), 1);
assert_eq!(count_constraint_type(&e, IkConstraintType::Rotation), 0);
}
#[test]
fn test_validate_valid() {
let mut e = new_ik_constraint_export();
add_ik_constraint(&mut e, sample_constraint("arm"));
assert!(validate_ik_weights(&e));
}
#[test]
fn test_validate_invalid() {
let mut e = new_ik_constraint_export();
e.constraints.push(IkConstraintEntry {
bone_name: "x".to_string(),
target_name: "t".to_string(),
constraint_type: IkConstraintType::Position,
weight: 1.5,
chain_length: 2,
});
assert!(!validate_ik_weights(&e));
}
#[test]
fn test_clamp_weights() {
let mut e = new_ik_constraint_export();
e.constraints.push(IkConstraintEntry {
bone_name: "x".to_string(),
target_name: "t".to_string(),
constraint_type: IkConstraintType::Position,
weight: 2.0,
chain_length: 2,
});
clamp_ik_weights(&mut e);
assert!(validate_ik_weights(&e));
}
#[test]
fn test_avg_chain_length() {
let mut e = new_ik_constraint_export();
add_ik_constraint(&mut e, sample_constraint("arm"));
assert!((avg_chain_length_ik(&e) - 3.0).abs() < 1e-5);
}
#[test]
fn test_ik_constraint_to_json() {
let e = new_ik_constraint_export();
let j = ik_constraint_to_json(&e);
assert!(j.contains("constraint_count"));
}
#[test]
fn test_empty_avg_chain_zero() {
let e = new_ik_constraint_export();
assert!(avg_chain_length_ik(&e).abs() < 1e-6);
}
}