#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct NormalsExportConfig {
pub precision: usize,
pub face_normals: bool,
pub normalise: bool,
}
#[derive(Debug, Clone)]
pub struct NormalFrame {
pub index: u32,
pub normals: Vec<f32>,
}
#[derive(Debug, Clone)]
pub struct NormalsExportResult {
pub frames: Vec<NormalFrame>,
pub total_bytes: usize,
}
pub fn default_normals_export_config() -> NormalsExportConfig {
NormalsExportConfig {
precision: 6,
face_normals: false,
normalise: true,
}
}
pub fn new_normals_export() -> NormalsExportResult {
NormalsExportResult {
frames: Vec::new(),
total_bytes: 0,
}
}
pub fn normals_add_frame(result: &mut NormalsExportResult, frame: NormalFrame) {
result.frames.push(frame);
}
pub fn normals_frame_count(result: &NormalsExportResult) -> usize {
result.frames.len()
}
pub fn normals_per_frame(result: &NormalsExportResult) -> usize {
result
.frames
.first()
.map(|f| f.normals.len() / 3)
.unwrap_or(0)
}
pub fn normals_export_to_json(
result: &NormalsExportResult,
cfg: &NormalsExportConfig,
) -> 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\":{},\"normals\":[", frame.index));
let chunks: Vec<String> = frame
.normals
.chunks(3)
.map(|n| {
let (nx, ny, nz) = normalise_if(n[0], n[1], n[2], cfg.normalise);
format!("[{:.prec$},{:.prec$},{:.prec$}]", nx, ny, nz)
})
.collect();
out.push_str(&chunks.join(","));
out.push_str(&format!("]}}{}\n", comma));
}
out.push_str("]}");
out
}
pub fn normals_export_to_binary(
result: &NormalsExportResult,
cfg: &NormalsExportConfig,
) -> Vec<u8> {
let mut buf: Vec<u8> = Vec::new();
for frame in &result.frames {
for chunk in frame.normals.chunks(3) {
let (nx, ny, nz) = normalise_if(chunk[0], chunk[1], chunk[2], cfg.normalise);
buf.extend_from_slice(&nx.to_le_bytes());
buf.extend_from_slice(&ny.to_le_bytes());
buf.extend_from_slice(&nz.to_le_bytes());
}
}
buf
}
pub fn normals_total_bytes(result: &NormalsExportResult) -> usize {
result.total_bytes
}
pub fn normals_write_to_file(
result: &mut NormalsExportResult,
cfg: &NormalsExportConfig,
_path: &str,
) -> usize {
let json = normals_export_to_json(result, cfg);
result.total_bytes = json.len();
result.total_bytes
}
pub fn normals_export_clear(result: &mut NormalsExportResult) {
result.frames.clear();
result.total_bytes = 0;
}
fn normalise_if(x: f32, y: f32, z: f32, do_it: bool) -> (f32, f32, f32) {
if !do_it {
return (x, y, z);
}
let len = (x * x + y * y + z * z).sqrt();
if len < 1e-10 {
(0.0, 1.0, 0.0)
} else {
(x / len, y / len, z / len)
}
}
fn make_frame(index: u32) -> NormalFrame {
NormalFrame {
index,
normals: vec![0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0],
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_values() {
let cfg = default_normals_export_config();
assert_eq!(cfg.precision, 6);
assert!(!cfg.face_normals);
assert!(cfg.normalise);
}
#[test]
fn new_export_is_empty() {
let r = new_normals_export();
assert_eq!(normals_frame_count(&r), 0);
assert_eq!(normals_per_frame(&r), 0);
}
#[test]
fn add_frame_increases_count() {
let mut r = new_normals_export();
normals_add_frame(&mut r, make_frame(0));
assert_eq!(normals_frame_count(&r), 1);
}
#[test]
fn normals_per_frame_computed_correctly() {
let mut r = new_normals_export();
normals_add_frame(&mut r, make_frame(0));
assert_eq!(normals_per_frame(&r), 3);
}
#[test]
fn json_contains_frames_key() {
let mut r = new_normals_export();
normals_add_frame(&mut r, make_frame(0));
let cfg = default_normals_export_config();
let json = normals_export_to_json(&r, &cfg);
assert!(json.contains("\"frames\""));
assert!(json.contains("\"normals\""));
}
#[test]
fn binary_length_matches_frame_count() {
let mut r = new_normals_export();
normals_add_frame(&mut r, make_frame(0));
let cfg = default_normals_export_config();
let bin = normals_export_to_binary(&r, &cfg);
assert_eq!(bin.len(), 36);
}
#[test]
fn write_to_file_updates_total_bytes() {
let mut r = new_normals_export();
normals_add_frame(&mut r, make_frame(0));
let cfg = default_normals_export_config();
let n = normals_write_to_file(&mut r, &cfg, "/tmp/normals.json");
assert!(n > 0);
assert_eq!(normals_total_bytes(&r), n);
}
#[test]
fn clear_resets_everything() {
let mut r = new_normals_export();
normals_add_frame(&mut r, make_frame(0));
let cfg = default_normals_export_config();
normals_write_to_file(&mut r, &cfg, "/tmp/normals.json");
normals_export_clear(&mut r);
assert_eq!(normals_frame_count(&r), 0);
assert_eq!(normals_total_bytes(&r), 0);
}
#[test]
fn normalise_if_unit_vector_unchanged() {
let (x, y, z) = normalise_if(0.0, 1.0, 0.0, true);
assert!((x - 0.0).abs() < 1e-6);
assert!((y - 1.0).abs() < 1e-6);
assert!((z - 0.0).abs() < 1e-6);
}
#[test]
fn normalise_if_scales_vector() {
let (x, y, z) = normalise_if(2.0, 0.0, 0.0, true);
assert!((x - 1.0).abs() < 1e-6);
assert!((y - 0.0).abs() < 1e-6);
assert!((z - 0.0).abs() < 1e-6);
}
#[test]
fn multiple_frames_binary_size() {
let mut r = new_normals_export();
normals_add_frame(&mut r, make_frame(0));
normals_add_frame(&mut r, make_frame(1));
let cfg = default_normals_export_config();
let bin = normals_export_to_binary(&r, &cfg);
assert_eq!(bin.len(), 72);
}
}