draco-core 1.0.1

Pure Rust core encoder and decoder for Draco geometry compression
Documentation
//! Test to compare Rust encoder output with C++ encoder output.
//! This test decodes .drc files generated by C++ draco_encoder and compares them
//! with files encoded by our Rust implementation.

use draco_core::decoder_buffer::DecoderBuffer;
use draco_core::encoder_buffer::EncoderBuffer;
use draco_core::encoder_options::EncoderOptions;
use draco_core::geometry_attribute::GeometryAttributeType;
use draco_core::mesh::Mesh;
use draco_core::mesh_decoder::MeshDecoder;
use draco_core::mesh_encoder::MeshEncoder;
use std::fs;
use std::path::PathBuf;

fn get_testdata_path() -> PathBuf {
    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    path.pop(); // crates
    path.pop(); // root
    path.push("testdata");
    path
}

fn decode_drc(data: &[u8]) -> Result<Mesh, draco_core::DracoError> {
    let mut decoder = MeshDecoder::new();
    let mut mesh = Mesh::new();
    let mut buffer = DecoderBuffer::new(data);
    decoder.decode(&mut buffer, &mut mesh)?;
    Ok(mesh)
}

fn encode_mesh(
    mesh: &Mesh,
    speed: i32,
    quantization_bits: i32,
) -> Result<Vec<u8>, draco_core::DracoError> {
    let mut encoder = MeshEncoder::new();
    encoder.set_mesh(mesh.clone());

    let mut options = EncoderOptions::new();
    options.set_global_int("encoding_method", 1); // Edgebreaker
    options.set_global_int("encoding_speed", speed);
    options.set_global_int("decoding_speed", speed);

    // Set quantization for position attribute
    for i in 0..mesh.num_attributes() {
        let att = mesh.attribute(i);
        let bits = match att.attribute_type() {
            GeometryAttributeType::Position => quantization_bits,
            _ => 8,
        };
        options.set_attribute_int(i, "quantization_bits", bits);
    }

    let mut enc_buffer = EncoderBuffer::new();
    encoder.encode(&options, &mut enc_buffer)?;
    Ok(enc_buffer.data().to_vec())
}

/// Extract position values from a mesh as Vec<[f32; 3]>
fn extract_positions(mesh: &Mesh) -> Vec<[f32; 3]> {
    let pos_att_id = mesh.named_attribute_id(GeometryAttributeType::Position);
    if pos_att_id < 0 {
        return Vec::new();
    }

    let pos_attr = mesh.attribute(pos_att_id);
    let num_entries = pos_attr.size();
    let buffer = pos_attr.buffer();
    let byte_stride = pos_attr.byte_stride() as usize;

    let mut positions = Vec::with_capacity(num_entries);
    for i in 0..num_entries {
        let offset = i * byte_stride;
        let mut bytes = [0u8; 12];
        if offset + 12 <= buffer.data_size() {
            buffer.read(offset, &mut bytes);
            let x = f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
            let y = f32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
            let z = f32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
            positions.push([x, y, z]);
        }
    }
    positions
}

#[test]
fn compare_cpp_vs_rust_sphere_decode() {
    let testdata = get_testdata_path();

    println!("\n=== Comparing C++ vs Rust Decoder Results ===\n");

    for speed in 0..=10 {
        let cpp_path = testdata
            .join("reference_cpp")
            .join(format!("cpp_encoded_sphere_speed_{}.drc", speed));

        if !cpp_path.exists() {
            println!("Skipping speed {} - file not found: {:?}", speed, cpp_path);
            continue;
        }

        // Read and decode C++ file
        let cpp_data = fs::read(&cpp_path).expect("Failed to read C++ encoded file");

        println!("Speed {}: C++ file size = {} bytes", speed, cpp_data.len());

        // Try to decode with our Rust decoder
        match decode_drc(&cpp_data) {
            Ok(mesh) => {
                println!(
                    "  Decoded: {} vertices, {} faces",
                    mesh.num_points(),
                    mesh.num_faces()
                );

                // Print first 3 vertex positions for comparison
                let positions = extract_positions(&mesh);
                println!("  First 3 vertices:");
                for (i, p) in positions.iter().take(3).enumerate() {
                    println!("    v{}: [{:.6}, {:.6}, {:.6}]", i, p[0], p[1], p[2]);
                }
            }
            Err(e) => {
                println!("  DECODE ERROR: {:?}", e);
            }
        }
        println!();
    }
}

#[test]
fn compare_cpp_vs_rust_roundtrip_sphere() {
    let testdata = get_testdata_path();

    println!("\n=== Comparing C++ Decode vs Rust Encode-Decode ===\n");

    // First decode a C++ speed 5 file (PARALLELOGRAM) to get reference mesh
    let cpp_path = testdata
        .join("reference_cpp")
        .join("cpp_encoded_sphere_speed_5.drc");

    if !cpp_path.exists() {
        println!("C++ reference file not found, skipping test");
        return;
    }

    let cpp_data = fs::read(&cpp_path).expect("Failed to read C++ file");
    let cpp_mesh = decode_drc(&cpp_data).expect("Failed to decode C++ mesh");

    println!(
        "C++ reference mesh: {} vertices, {} faces",
        cpp_mesh.num_points(),
        cpp_mesh.num_faces()
    );

    let cpp_positions = extract_positions(&cpp_mesh);

    // Now encode with Rust and decode again
    for speed in 0..=10 {
        match encode_mesh(&cpp_mesh, speed, 14) {
            Ok(rust_encoded) => {
                println!("Speed {}: Rust encoded {} bytes", speed, rust_encoded.len());

                match decode_drc(&rust_encoded) {
                    Ok(rust_decoded) => {
                        let rust_positions = extract_positions(&rust_decoded);

                        let mut max_diff = 0.0f32;
                        let mut diff_count = 0;

                        let min_count = cpp_positions.len().min(rust_positions.len());
                        for i in 0..min_count {
                            for j in 0..3 {
                                let diff = (cpp_positions[i][j] - rust_positions[i][j]).abs();
                                if diff > 0.001 {
                                    diff_count += 1;
                                    if diff_count <= 5 {
                                        println!(
                                            "  v{}.{}: C++={:.6} Rust={:.6} diff={:.6}",
                                            i, j, cpp_positions[i][j], rust_positions[i][j], diff
                                        );
                                    }
                                }
                                max_diff = max_diff.max(diff);
                            }
                        }

                        if diff_count > 5 {
                            println!("  ... and {} more differences", diff_count - 5);
                        }
                        println!("  Max difference: {:.6}", max_diff);
                    }
                    Err(e) => println!("  Rust decode error: {:?}", e),
                }
            }
            Err(e) => println!("Speed {}: Rust encode error: {:?}", speed, e),
        }
        println!();
    }
}

#[test]
fn binary_compare_cpp_vs_rust_encoded() {
    let testdata = get_testdata_path();

    println!("\n=== Binary Comparison: C++ vs Rust Encoder Output ===\n");

    // Decode a C++ file to get the mesh
    let cpp_path = testdata
        .join("reference_cpp")
        .join("cpp_encoded_sphere_speed_5.drc");

    if !cpp_path.exists() {
        println!("C++ reference file not found, skipping test");
        return;
    }

    let cpp_data = fs::read(&cpp_path).expect("Failed to read C++ file");
    let mesh = decode_drc(&cpp_data).expect("Failed to decode C++ mesh");

    println!(
        "Reference mesh: {} vertices, {} faces\n",
        mesh.num_points(),
        mesh.num_faces()
    );

    // For each speed, compare C++ and Rust encoded bytes
    for speed in [0, 1, 2, 5, 8, 10] {
        let cpp_path = testdata
            .join("reference_cpp")
            .join(format!("cpp_encoded_sphere_speed_{}.drc", speed));

        if !cpp_path.exists() {
            continue;
        }

        let cpp_encoded = fs::read(&cpp_path).expect("Failed to read C++ file");

        match encode_mesh(&mesh, speed, 14) {
            Ok(rust_encoded) => {
                println!("Speed {}:", speed);
                println!("  C++ size:  {} bytes", cpp_encoded.len());
                println!("  Rust size: {} bytes", rust_encoded.len());

                // Compare byte by byte up to min length
                let min_len = cpp_encoded.len().min(rust_encoded.len());
                let mut first_diff = None;
                let mut diff_count = 0;

                for i in 0..min_len {
                    if cpp_encoded[i] != rust_encoded[i] {
                        diff_count += 1;
                        if first_diff.is_none() {
                            first_diff = Some(i);
                        }
                    }
                }

                if let Some(pos) = first_diff {
                    println!("  First byte difference at position {}", pos);
                    println!("  Total different bytes: {} / {}", diff_count, min_len);

                    // Show bytes around first difference
                    let start = pos.saturating_sub(4);
                    let end = (pos + 8).min(min_len);
                    print!("  C++ bytes [{}-{}]: ", start, end);
                    for i in start..end {
                        print!("{:02x} ", cpp_encoded[i]);
                    }
                    println!();
                    print!("  Rust bytes [{}-{}]: ", start, end);
                    for i in start..end {
                        print!("{:02x} ", rust_encoded[i]);
                    }
                    println!();
                } else if cpp_encoded.len() == rust_encoded.len() {
                    println!("  IDENTICAL!");
                } else {
                    println!(
                        "  Same content for first {} bytes, but different lengths",
                        min_len
                    );
                }
            }
            Err(e) => println!("Speed {}: Rust encode error: {:?}", speed, e),
        }
        println!();
    }
}