#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct XformExportConfig {
pub include_rotation: bool,
pub include_scale: bool,
pub precision: usize,
}
#[derive(Debug, Clone)]
pub struct NodeXform {
pub name: String,
pub position: [f64; 3],
pub rotation: [f64; 4],
pub scale: [f64; 3],
}
#[derive(Debug, Clone)]
pub struct XformExportResult {
pub nodes: Vec<NodeXform>,
pub total_bytes: usize,
}
#[allow(dead_code)]
pub fn default_xform_export_config() -> XformExportConfig {
XformExportConfig {
include_rotation: true,
include_scale: true,
precision: 6,
}
}
#[allow(dead_code)]
pub fn new_xform_export() -> XformExportResult {
XformExportResult {
nodes: Vec::new(),
total_bytes: 0,
}
}
#[allow(dead_code)]
pub fn xform_add_node(result: &mut XformExportResult, node: NodeXform) {
result.nodes.push(node);
}
#[allow(dead_code)]
pub fn xform_export_to_json(result: &XformExportResult, cfg: &XformExportConfig) -> String {
let prec = cfg.precision;
let mut out = String::from("[\n");
for (i, n) in result.nodes.iter().enumerate() {
let comma = if i + 1 < result.nodes.len() { "," } else { "" };
let pos = format!(
"[{:.prec$},{:.prec$},{:.prec$}]",
n.position[0], n.position[1], n.position[2]
);
let mut fields = format!(" {{\"name\":\"{}\",\"position\":{}", n.name, pos);
if cfg.include_rotation {
let rot = format!(
"[{:.prec$},{:.prec$},{:.prec$},{:.prec$}]",
n.rotation[0], n.rotation[1], n.rotation[2], n.rotation[3]
);
fields.push_str(&format!(",\"rotation\":{}", rot));
}
if cfg.include_scale {
let scl = format!(
"[{:.prec$},{:.prec$},{:.prec$}]",
n.scale[0], n.scale[1], n.scale[2]
);
fields.push_str(&format!(",\"scale\":{}", scl));
}
fields.push('}');
out.push_str(&fields);
out.push_str(comma);
out.push('\n');
}
out.push(']');
out
}
#[allow(dead_code)]
pub fn xform_export_to_csv(result: &XformExportResult, cfg: &XformExportConfig) -> String {
let prec = cfg.precision;
let mut out = String::from("name,px,py,pz");
if cfg.include_rotation {
out.push_str(",rx,ry,rz,rw");
}
if cfg.include_scale {
out.push_str(",sx,sy,sz");
}
out.push('\n');
for n in &result.nodes {
let row = format!(
"{},{:.prec$},{:.prec$},{:.prec$}",
n.name, n.position[0], n.position[1], n.position[2]
);
out.push_str(&row);
if cfg.include_rotation {
out.push_str(&format!(
",{:.prec$},{:.prec$},{:.prec$},{:.prec$}",
n.rotation[0], n.rotation[1], n.rotation[2], n.rotation[3]
));
}
if cfg.include_scale {
out.push_str(&format!(
",{:.prec$},{:.prec$},{:.prec$}",
n.scale[0], n.scale[1], n.scale[2]
));
}
out.push('\n');
}
out
}
#[allow(dead_code)]
pub fn xform_node_count(result: &XformExportResult) -> usize {
result.nodes.len()
}
#[allow(dead_code)]
pub fn xform_node_by_name<'a>(result: &'a XformExportResult, name: &str) -> Option<&'a NodeXform> {
result.nodes.iter().find(|n| n.name == name)
}
#[allow(dead_code)]
pub fn xform_export_write_to_file(
result: &mut XformExportResult,
cfg: &XformExportConfig,
_path: &str,
) -> usize {
let json = xform_export_to_json(result, cfg);
result.total_bytes = json.len();
result.total_bytes
}
#[allow(dead_code)]
pub fn xform_export_clear(result: &mut XformExportResult) {
result.nodes.clear();
result.total_bytes = 0;
}
#[allow(dead_code)]
pub fn xform_total_bytes(result: &XformExportResult) -> usize {
result.total_bytes
}
fn sample_node(name: &str) -> NodeXform {
NodeXform {
name: name.to_string(),
position: [1.0, 2.0, 3.0],
rotation: [0.0, 0.0, 0.0, 1.0],
scale: [1.0, 1.0, 1.0],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_has_rotation_and_scale() {
let cfg = default_xform_export_config();
assert!(cfg.include_rotation);
assert!(cfg.include_scale);
assert_eq!(cfg.precision, 6);
}
#[test]
fn new_xform_export_is_empty() {
let r = new_xform_export();
assert_eq!(xform_node_count(&r), 0);
}
#[test]
fn add_node_increases_count() {
let mut r = new_xform_export();
xform_add_node(&mut r, sample_node("root"));
assert_eq!(xform_node_count(&r), 1);
}
#[test]
fn xform_node_by_name_found() {
let mut r = new_xform_export();
xform_add_node(&mut r, sample_node("spine"));
let found = xform_node_by_name(&r, "spine");
assert!(found.is_some());
assert_eq!(found.expect("should succeed").name, "spine");
}
#[test]
fn xform_node_by_name_not_found() {
let r = new_xform_export();
assert!(xform_node_by_name(&r, "missing").is_none());
}
#[test]
fn to_json_contains_name_and_position() {
let mut r = new_xform_export();
xform_add_node(&mut r, sample_node("hips"));
let cfg = default_xform_export_config();
let json = xform_export_to_json(&r, &cfg);
assert!(json.contains("\"hips\""));
assert!(json.contains("\"position\""));
assert!(json.contains("\"rotation\""));
assert!(json.contains("\"scale\""));
}
#[test]
fn to_csv_header_present() {
let r = new_xform_export();
let cfg = default_xform_export_config();
let csv = xform_export_to_csv(&r, &cfg);
assert!(csv.starts_with("name,px,py,pz"));
}
#[test]
fn write_to_file_updates_total_bytes() {
let mut r = new_xform_export();
xform_add_node(&mut r, sample_node("foot"));
let cfg = default_xform_export_config();
let bytes = xform_export_write_to_file(&mut r, &cfg, "/tmp/test_xform.json");
assert!(bytes > 0);
assert_eq!(xform_total_bytes(&r), bytes);
}
#[test]
fn clear_resets_state() {
let mut r = new_xform_export();
xform_add_node(&mut r, sample_node("hand"));
xform_export_clear(&mut r);
assert_eq!(xform_node_count(&r), 0);
assert_eq!(xform_total_bytes(&r), 0);
}
#[test]
fn multiple_nodes_in_json() {
let mut r = new_xform_export();
xform_add_node(&mut r, sample_node("head"));
xform_add_node(&mut r, sample_node("neck"));
xform_add_node(&mut r, sample_node("chest"));
let cfg = default_xform_export_config();
let json = xform_export_to_json(&r, &cfg);
assert!(json.contains("\"head\""));
assert!(json.contains("\"neck\""));
assert!(json.contains("\"chest\""));
assert_eq!(xform_node_count(&r), 3);
}
}