#![cfg(all(feature = "encoder", feature = "decoder"))]
use draco_core::{
DataType, DecoderBuffer, EncoderBuffer, EncoderOptions, GeometryAttributeType, PointAttribute,
PointCloud, PointCloudDecoder, PointCloudEncoder,
};
const NUM_POINTS: usize = 4096;
const POSITION_BITS: i32 = 16;
fn lcg(state: &mut u32) -> f32 {
*state = state.wrapping_mul(1664525).wrapping_add(1013904223);
(*state >> 8) as f32 / (1 << 24) as f32
}
fn positions() -> PointAttribute {
let mut attribute = PointAttribute::new();
attribute.init(
GeometryAttributeType::Position,
3,
DataType::Float32,
false,
NUM_POINTS,
);
let buffer = attribute.buffer_mut();
let mut state = 12345u32;
for point in 0..NUM_POINTS {
for component in 0..3 {
let value = lcg(&mut state) * 10.0;
buffer.write((point * 3 + component) * 4, &value.to_le_bytes());
}
}
attribute
}
fn tags() -> PointAttribute {
let mut attribute = PointAttribute::new();
attribute.init(
GeometryAttributeType::Generic,
1,
DataType::Uint32,
false,
NUM_POINTS,
);
let buffer = attribute.buffer_mut();
for point in 0..NUM_POINTS {
buffer.write(point * 4, &(point as u32).to_le_bytes());
}
attribute
}
fn cloud() -> PointCloud {
let mut cloud = PointCloud::new();
cloud.set_num_points(NUM_POINTS);
cloud.add_attribute(positions());
cloud.add_attribute(tags());
cloud
}
fn spatially_coherent() -> PointAttribute {
let positions = positions();
let mut attribute = PointAttribute::new();
attribute.init(
GeometryAttributeType::Generic,
3,
DataType::Float32,
false,
NUM_POINTS,
);
let stride = positions.byte_stride() as usize;
let read = |point: usize, component: usize| -> f32 {
let mut bytes = [0u8; 4];
positions
.buffer()
.read(point * stride + component * 4, &mut bytes);
f32::from_le_bytes(bytes)
};
let buffer = attribute.buffer_mut();
for point in 0..NUM_POINTS {
let (x, y, z) = (read(point, 0), read(point, 1), read(point, 2));
for (component, value) in [x + y, y - z, x * 0.5 + z].into_iter().enumerate() {
buffer.write((point * 3 + component) * 4, &value.to_le_bytes());
}
}
attribute
}
fn coherent_cloud() -> PointCloud {
let mut cloud = PointCloud::new();
cloud.set_num_points(NUM_POINTS);
cloud.add_attribute(positions());
cloud.add_attribute(spatially_coherent());
cloud
}
fn encode(cloud: &PointCloud, configure: impl Fn(&mut EncoderOptions)) -> Vec<u8> {
let mut options = EncoderOptions::new();
options.set_encoding_method(0); options.set_attribute_int(0, "quantization_bits", POSITION_BITS);
configure(&mut options);
let mut encoder = PointCloudEncoder::new();
encoder.set_point_cloud(cloud.clone());
let mut buffer = EncoderBuffer::new();
encoder.encode(&options, &mut buffer).expect("encode");
buffer.data().to_vec()
}
fn decode(bytes: &[u8]) -> PointCloud {
let mut decoded = PointCloud::new();
PointCloudDecoder::new()
.decode(&mut DecoderBuffer::new(bytes), &mut decoded)
.expect("decode");
decoded
}
fn position_of(cloud: &PointCloud, point: usize) -> [f32; 3] {
let attribute = cloud.attribute(0);
let stride = attribute.byte_stride() as usize;
let mut out = [0.0f32; 3];
for (component, slot) in out.iter_mut().enumerate() {
let mut bytes = [0u8; 4];
attribute
.buffer()
.read(point * stride + component * 4, &mut bytes);
*slot = f32::from_le_bytes(bytes);
}
out
}
fn tag_of(cloud: &PointCloud, point: usize) -> u32 {
let attribute = cloud.attribute(1);
let mut bytes = [0u8; 4];
attribute
.buffer()
.read(point * attribute.byte_stride() as usize, &mut bytes);
u32::from_le_bytes(bytes)
}
#[test]
fn the_order_is_the_callers_unless_it_is_asked_for() {
let mut options = EncoderOptions::new();
assert!(!options.spatial_point_order());
options.set_spatial_point_order(true);
assert!(options.spatial_point_order());
let cloud = cloud();
let untouched = encode(&cloud, |_| {});
let explicitly_off = encode(&cloud, |options| options.set_spatial_point_order(false));
assert_eq!(untouched, explicitly_off);
}
#[test]
fn a_spatial_order_is_smaller_when_the_values_follow_the_geometry() {
let cloud = coherent_cloud();
let as_given = encode(&cloud, |options| {
options.set_attribute_int(1, "quantization_bits", 12)
});
let sorted = encode(&cloud, |options| {
options.set_attribute_int(1, "quantization_bits", 12);
options.set_spatial_point_order(true);
});
println!(
"coherent: {} bytes as given, {} sorted, {:.1}% off",
as_given.len(),
sorted.len(),
(1.0 - sorted.len() as f64 / as_given.len() as f64) * 100.0
);
assert!(
sorted.len() < as_given.len(),
"sorting did not help data that follows the geometry: {} vs {}",
sorted.len(),
as_given.len()
);
}
#[test]
fn a_spatial_order_costs_more_when_an_attribute_tracks_the_input_order() {
let cloud = cloud();
let as_given = encode(&cloud, |_| {});
let sorted = encode(&cloud, |options| options.set_spatial_point_order(true));
println!(
"order-tracking: {} bytes as given, {} sorted, {:+.1}%",
as_given.len(),
sorted.len(),
(sorted.len() as f64 / as_given.len() as f64 - 1.0) * 100.0
);
assert!(
sorted.len() > as_given.len(),
"the known regression stopped happening, which is good news that this test cannot express: {} vs {}",
sorted.len(),
as_given.len()
);
}
#[test]
fn every_point_keeps_the_values_it_arrived_with() {
let source = cloud();
let sorted = decode(&encode(&source, |options| {
options.set_spatial_point_order(true)
}));
assert_eq!(sorted.num_points(), NUM_POINTS);
let mut seen = vec![false; NUM_POINTS];
for point in 0..NUM_POINTS {
let tag = tag_of(&sorted, point) as usize;
assert!(tag < NUM_POINTS, "tag {tag} is not one of ours");
assert!(!seen[tag], "tag {tag} arrived twice");
seen[tag] = true;
}
let step = 10.0 / ((1u32 << POSITION_BITS) - 1) as f32;
let tolerance = step * 2.0;
let mut worst = 0.0f32;
for point in 0..NUM_POINTS {
let tag = tag_of(&sorted, point) as usize;
let decoded = position_of(&sorted, point);
let original = position_of(&source, tag);
for axis in 0..3 {
worst = worst.max((decoded[axis] - original[axis]).abs());
}
}
println!("worst coordinate drift {worst:.6}, tolerance {tolerance:.6}");
assert!(
worst < tolerance,
"a point did not keep its position: drift {worst} against a {step} step"
);
}
#[test]
fn the_reordered_stream_holds_the_same_geometry() {
let source = cloud();
let plain = decode(&encode(&source, |_| {}));
let sorted = decode(&encode(&source, |options| {
options.set_spatial_point_order(true)
}));
let bag = |cloud: &PointCloud| {
let mut values: Vec<[u32; 3]> = (0..NUM_POINTS)
.map(|point| position_of(cloud, point).map(f32::to_bits))
.collect();
values.sort_unstable();
values
};
assert_eq!(
bag(&plain),
bag(&sorted),
"the reorder changed the geometry"
);
}
#[test]
fn a_cloud_with_no_positions_is_left_alone() {
let mut cloud = PointCloud::new();
cloud.set_num_points(NUM_POINTS);
cloud.add_attribute(tags());
let as_given = encode(&cloud, |_| {});
let asked = encode(&cloud, |options| options.set_spatial_point_order(true));
assert_eq!(
as_given, asked,
"a cloud with no position attribute was reordered anyway"
);
}
#[test]
fn the_kd_tree_coder_keeps_its_own_order() {
let cloud = cloud();
let with_kd_tree = |options: &mut EncoderOptions| {
options.set_encoding_method(1);
options.set_attribute_int(1, "quantization_bits", 16);
};
let as_given = encode(&cloud, |options| with_kd_tree(options));
let asked = encode(&cloud, |options| {
with_kd_tree(options);
options.set_spatial_point_order(true);
});
assert_eq!(as_given, asked, "the kd-tree stream changed");
}
fn clustered_positions() -> PointAttribute {
let mut attribute = PointAttribute::new();
attribute.init(
GeometryAttributeType::Position,
3,
DataType::Float32,
false,
NUM_POINTS,
);
let buffer = attribute.buffer_mut();
let mut state = 12345u32;
for point in 0..NUM_POINTS {
let coordinates = if point == NUM_POINTS - 1 {
[1000.0f32; 3]
} else {
[lcg(&mut state), lcg(&mut state), lcg(&mut state)]
};
for (component, value) in coordinates.iter().enumerate() {
buffer.write((point * 3 + component) * 4, &value.to_le_bytes());
}
}
attribute
}
fn mean_step(cloud: &PointCloud, points: usize) -> f64 {
let mut total = 0.0;
for point in 1..points {
let previous = position_of(cloud, point - 1);
let current = position_of(cloud, point);
total += previous
.iter()
.zip(current.iter())
.map(|(a, b)| f64::from(a - b).powi(2))
.sum::<f64>()
.sqrt();
}
total / (points - 1) as f64
}
#[test]
fn the_grid_resolves_a_cluster_the_quantization_can_tell_apart() {
let mut cloud = PointCloud::new();
cloud.set_num_points(NUM_POINTS);
cloud.add_attribute(clustered_positions());
cloud.add_attribute(tags());
let as_given = decode(&encode(&cloud, |options| {
options.set_attribute_int(0, "quantization_bits", 20);
}));
let sorted = decode(&encode(&cloud, |options| {
options.set_attribute_int(0, "quantization_bits", 20);
options.set_spatial_point_order(true);
}));
let given_step = mean_step(&as_given, NUM_POINTS - 1);
let sorted_step = mean_step(&sorted, NUM_POINTS - 1);
println!("mean step: {given_step:.6} as given, {sorted_step:.6} sorted");
assert!(
sorted_step * 5.0 < given_step,
"the spatial order left the cluster in its original order: mean step \
{sorted_step} against {given_step}"
);
}