#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ClothSimExportConfig {
pub precision: usize,
pub include_velocity: bool,
}
#[derive(Debug, Clone)]
pub struct ClothFrame {
pub index: u32,
pub positions: Vec<f32>,
pub velocities: Vec<f32>,
}
#[derive(Debug, Clone)]
pub struct ClothSimExportResult {
pub frames: Vec<ClothFrame>,
pub total_bytes: usize,
}
pub fn default_cloth_sim_export_config() -> ClothSimExportConfig {
ClothSimExportConfig {
precision: 6,
include_velocity: false,
}
}
pub fn new_cloth_sim_export() -> ClothSimExportResult {
ClothSimExportResult {
frames: Vec::new(),
total_bytes: 0,
}
}
pub fn cloth_sim_add_frame(result: &mut ClothSimExportResult, frame: ClothFrame) {
result.frames.push(frame);
}
pub fn cloth_sim_frame_count(result: &ClothSimExportResult) -> usize {
result.frames.len()
}
pub fn cloth_sim_vertex_count(result: &ClothSimExportResult) -> usize {
result
.frames
.first()
.map(|f| f.positions.len() / 3)
.unwrap_or(0)
}
pub fn cloth_sim_export_to_json(
result: &ClothSimExportResult,
cfg: &ClothSimExportConfig,
) -> String {
let prec = cfg.precision;
let mut out = String::from("{\"frames\":[\n");
for (fi, frame) in result.frames.iter().enumerate() {
let comma = if fi + 1 < result.frames.len() { "," } else { "" };
out.push_str(&format!(" {{\"index\":{},\"positions\":[", frame.index));
let pos_strs: Vec<String> = frame
.positions
.chunks(3)
.map(|p| format!("[{:.prec$},{:.prec$},{:.prec$}]", p[0], p[1], p[2]))
.collect();
out.push_str(&pos_strs.join(","));
out.push(']');
if cfg.include_velocity && !frame.velocities.is_empty() {
out.push_str(",\"velocities\":[");
let vel_strs: Vec<String> = frame
.velocities
.chunks(3)
.map(|v| format!("[{:.prec$},{:.prec$},{:.prec$}]", v[0], v[1], v[2]))
.collect();
out.push_str(&vel_strs.join(","));
out.push(']');
}
out.push('}');
out.push_str(comma);
out.push('\n');
}
out.push_str("]}");
out
}
pub fn cloth_sim_export_to_binary(
result: &ClothSimExportResult,
cfg: &ClothSimExportConfig,
) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
for frame in &result.frames {
for &v in &frame.positions {
buf.extend_from_slice(&v.to_le_bytes());
}
if cfg.include_velocity {
for &v in &frame.velocities {
buf.extend_from_slice(&v.to_le_bytes());
}
}
}
buf
}
pub fn cloth_sim_total_bytes(result: &ClothSimExportResult) -> usize {
result.total_bytes
}
pub fn cloth_sim_write_to_file(
result: &mut ClothSimExportResult,
cfg: &ClothSimExportConfig,
_path: &str,
) -> usize {
let json = cloth_sim_export_to_json(result, cfg);
result.total_bytes = json.len();
result.total_bytes
}
pub fn cloth_sim_clear(result: &mut ClothSimExportResult) {
result.frames.clear();
result.total_bytes = 0;
}
fn make_frame(index: u32, verts: u32) -> ClothFrame {
let mut positions = Vec::new();
for i in 0..verts {
let f = i as f32;
positions.push(f);
positions.push(f * 0.5);
positions.push(0.0);
}
ClothFrame {
index,
positions,
velocities: Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_values() {
let cfg = default_cloth_sim_export_config();
assert_eq!(cfg.precision, 6);
assert!(!cfg.include_velocity);
}
#[test]
fn new_export_is_empty() {
let r = new_cloth_sim_export();
assert_eq!(cloth_sim_frame_count(&r), 0);
assert_eq!(cloth_sim_vertex_count(&r), 0);
}
#[test]
fn add_frame_increments_count() {
let mut r = new_cloth_sim_export();
cloth_sim_add_frame(&mut r, make_frame(0, 4));
assert_eq!(cloth_sim_frame_count(&r), 1);
}
#[test]
fn vertex_count_from_first_frame() {
let mut r = new_cloth_sim_export();
cloth_sim_add_frame(&mut r, make_frame(0, 6));
assert_eq!(cloth_sim_vertex_count(&r), 6);
}
#[test]
fn json_contains_frames_and_positions() {
let mut r = new_cloth_sim_export();
cloth_sim_add_frame(&mut r, make_frame(0, 3));
let cfg = default_cloth_sim_export_config();
let json = cloth_sim_export_to_json(&r, &cfg);
assert!(json.contains("\"frames\""));
assert!(json.contains("\"positions\""));
}
#[test]
fn binary_size_matches_vertex_data() {
let mut r = new_cloth_sim_export();
cloth_sim_add_frame(&mut r, make_frame(0, 4));
let cfg = default_cloth_sim_export_config();
let bin = cloth_sim_export_to_binary(&r, &cfg);
assert_eq!(bin.len(), 48);
}
#[test]
fn write_to_file_sets_total_bytes() {
let mut r = new_cloth_sim_export();
cloth_sim_add_frame(&mut r, make_frame(0, 4));
let cfg = default_cloth_sim_export_config();
let n = cloth_sim_write_to_file(&mut r, &cfg, "/tmp/cloth.json");
assert!(n > 0);
assert_eq!(cloth_sim_total_bytes(&r), n);
}
#[test]
fn clear_resets_everything() {
let mut r = new_cloth_sim_export();
cloth_sim_add_frame(&mut r, make_frame(0, 4));
let cfg = default_cloth_sim_export_config();
cloth_sim_write_to_file(&mut r, &cfg, "/tmp/cloth.json");
cloth_sim_clear(&mut r);
assert_eq!(cloth_sim_frame_count(&r), 0);
assert_eq!(cloth_sim_total_bytes(&r), 0);
}
#[test]
fn two_frames_in_binary() {
let mut r = new_cloth_sim_export();
cloth_sim_add_frame(&mut r, make_frame(0, 2));
cloth_sim_add_frame(&mut r, make_frame(1, 2));
let cfg = default_cloth_sim_export_config();
let bin = cloth_sim_export_to_binary(&r, &cfg);
assert_eq!(bin.len(), 48);
}
#[test]
fn velocity_appears_in_json_when_enabled() {
let mut r = new_cloth_sim_export();
let mut frame = make_frame(0, 2);
frame.velocities = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6];
cloth_sim_add_frame(&mut r, frame);
let mut cfg = default_cloth_sim_export_config();
cfg.include_velocity = true;
let json = cloth_sim_export_to_json(&r, &cfg);
assert!(json.contains("\"velocities\""));
}
}