#![allow(clippy::needless_range_loop)]
use draco_core::decoder_buffer::DecoderBuffer;
use draco_core::draco_types::DataType;
use draco_core::encoder_buffer::EncoderBuffer;
use draco_core::encoder_options::EncoderOptions;
use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
use draco_core::geometry_indices::FaceIndex;
use draco_core::mesh::Mesh;
use draco_core::mesh_decoder::MeshDecoder;
use draco_core::mesh_encoder::MeshEncoder;
use draco_core::point_cloud::PointCloud;
use draco_core::point_cloud_decoder::PointCloudDecoder;
use draco_core::point_cloud_encoder::PointCloudEncoder;
use draco_core::version::{DEFAULT_MESH_VERSION, DEFAULT_POINT_CLOUD_VERSION};
#[cfg(feature = "legacy_bitstream_encode")]
use draco_core::DracoError;
const LEGACY_POINT_CLOUD_VERSION: (u8, u8) = (1, 3);
fn create_test_pc() -> PointCloud {
let mut pc = PointCloud::new();
let mut pos_att = PointAttribute::new();
let num_points = 3;
pos_att.init(
GeometryAttributeType::Position,
3,
DataType::Float32,
false,
num_points,
);
let positions: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
let buffer = pos_att.buffer_mut();
for i in 0..9 {
buffer.write(i * 4, &positions[i].to_le_bytes());
}
pc.add_attribute(pos_att);
pc
}
fn create_test_mesh() -> Mesh {
let mut mesh = Mesh::new();
let mut pos_att = PointAttribute::new();
let num_points = 3;
pos_att.init(
GeometryAttributeType::Position,
3,
DataType::Float32,
false,
num_points,
);
let positions: [f32; 9] = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
let buffer = pos_att.buffer_mut();
for i in 0..9 {
buffer.write(i * 4, &positions[i].to_le_bytes());
}
mesh.add_attribute(pos_att);
mesh.set_num_faces(1);
mesh.set_face(FaceIndex(0), [0u32.into(), 1u32.into(), 2u32.into()]);
mesh
}
#[test]
fn test_mesh_roundtrip_v1_3() {
let mesh = create_test_mesh();
let mut encoder = MeshEncoder::new();
encoder.set_mesh(mesh);
let mut options = EncoderOptions::new();
options.set_version(1, 3);
options.set_encoding_method(0);
let mut enc_buffer = EncoderBuffer::new();
assert!(encoder.encode(&options, &mut enc_buffer).is_ok());
let mut dec_buffer = DecoderBuffer::new(enc_buffer.data());
let mut decoded_mesh = Mesh::new();
let mut decoder = MeshDecoder::new();
assert!(decoder.decode(&mut dec_buffer, &mut decoded_mesh).is_ok());
assert_eq!(decoded_mesh.num_points(), 3);
assert_eq!(decoded_mesh.num_faces(), 1);
}
#[test]
fn test_mesh_roundtrip_v2_2() {
let mesh = create_test_mesh();
let mut encoder = MeshEncoder::new();
encoder.set_mesh(mesh);
let mut options = EncoderOptions::new();
options.set_version(DEFAULT_MESH_VERSION.0, DEFAULT_MESH_VERSION.1);
options.set_encoding_method(1); options.set_attribute_int(0, "quantization_bits", 14);
let mut enc_buffer = EncoderBuffer::new();
assert!(encoder.encode(&options, &mut enc_buffer).is_ok());
let mut dec_buffer = DecoderBuffer::new(enc_buffer.data());
let mut decoded_mesh = Mesh::new();
let mut decoder = MeshDecoder::new();
assert!(decoder.decode(&mut dec_buffer, &mut decoded_mesh).is_ok());
assert_eq!(decoded_mesh.num_points(), 3);
assert_eq!(decoded_mesh.num_faces(), 1);
}
fn sorted_positions(pc: &PointCloud) -> Vec<[i32; 3]> {
let att = pc.attribute(pc.named_attribute_id(GeometryAttributeType::Position));
let stride = att.byte_stride() as usize;
let mut out = Vec::with_capacity(att.size());
for i in 0..att.size() {
let mut position = [0i32; 3];
for (k, component) in position.iter_mut().enumerate() {
let mut bytes = [0u8; 4];
att.buffer().read(i * stride + k * 4, &mut bytes);
*component = (f32::from_le_bytes(bytes) * 1000.0).round() as i32;
}
out.push(position);
}
out.sort_unstable();
out
}
fn assert_point_cloud_round_trips(major: u8, minor: u8, method: i32) {
let pc = create_test_pc();
let expected = sorted_positions(&pc);
let mut encoder = PointCloudEncoder::new();
encoder.set_point_cloud(pc);
let mut options = EncoderOptions::new();
options.set_version(major, minor);
options.set_encoding_method(method);
options.set_attribute_int(0, "quantization_bits", 14);
let mut enc_buffer = EncoderBuffer::new();
encoder
.encode(&options, &mut enc_buffer)
.unwrap_or_else(|e| panic!("v{major}.{minor} method {method} encode: {e:?}"));
let mut dec_buffer = DecoderBuffer::new(enc_buffer.data());
let mut decoded_pc = PointCloud::new();
PointCloudDecoder::new()
.decode(&mut dec_buffer, &mut decoded_pc)
.unwrap_or_else(|e| panic!("v{major}.{minor} method {method} decode: {e:?}"));
assert_eq!(
decoded_pc.num_points(),
3,
"v{major}.{minor} method {method}: point count"
);
assert_eq!(
sorted_positions(&decoded_pc),
expected,
"v{major}.{minor} method {method}: positions"
);
}
#[test]
fn test_point_cloud_roundtrip_v1_3() {
assert_point_cloud_round_trips(
LEGACY_POINT_CLOUD_VERSION.0,
LEGACY_POINT_CLOUD_VERSION.1,
0,
);
}
#[test]
fn test_point_cloud_roundtrip_v2_3() {
assert_point_cloud_round_trips(
DEFAULT_POINT_CLOUD_VERSION.0,
DEFAULT_POINT_CLOUD_VERSION.1,
1, );
}
#[test]
fn a_sequential_point_cloud_round_trips_at_2_3() {
assert_point_cloud_round_trips(
DEFAULT_POINT_CLOUD_VERSION.0,
DEFAULT_POINT_CLOUD_VERSION.1,
0,
);
}
#[test]
#[cfg(feature = "legacy_bitstream_encode")]
fn a_sequential_mesh_at_1_3_survives_the_varint_index_boundary() {
use draco_core::geometry_indices::FaceIndex;
let mesh = grid_mesh(257);
assert!(
mesh.num_points() > 65536,
"the boundary is the point of this"
);
let mut options = EncoderOptions::new();
options.set_version(1, 3);
options.set_encoding_method(0);
let mut encoder = MeshEncoder::new();
encoder.set_mesh(mesh.clone());
let mut buffer = EncoderBuffer::new();
encoder.encode(&options, &mut buffer).expect("1.3 encode");
let mut decoded = Mesh::new();
MeshDecoder::new()
.decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)
.expect("1.3 decode");
assert_eq!(decoded.num_faces(), mesh.num_faces(), "face count");
for face_id in 0..mesh.num_faces() {
let index = FaceIndex(face_id as u32);
assert_eq!(
decoded.face(index),
mesh.face(index),
"face {face_id} differs, so the index width disagreed"
);
}
}
#[test]
#[cfg(feature = "legacy_bitstream_encode")]
fn every_edgebreaker_traversal_round_trips_at_every_claimed_version() {
use draco_core::mesh_edgebreaker_encoder::{
select_edgebreaker_traversal, EdgebreakerTraversal,
};
let large = grid_mesh(33);
let tiny = grid_mesh(10);
let encode = |mesh: &Mesh, speed: i32, major: u8, minor: u8| {
let mut options = EncoderOptions::new();
options.set_version(major, minor);
options.set_encoding_method(1); options.set_global_int("encoding_speed", speed);
options.set_global_int("decoding_speed", speed);
let mut encoder = MeshEncoder::new();
encoder.set_mesh(mesh.clone());
let mut buffer = EncoderBuffer::new();
encoder.encode(&options, &mut buffer).map(|()| buffer)
};
for (major, minor) in [(2u8, 2u8), (2, 1), (2, 0), (1, 2), (1, 1)] {
for (mesh, speed, expected) in [
(&large, 5, EdgebreakerTraversal::Standard),
(&large, 0, EdgebreakerTraversal::Valence),
(&tiny, 5, EdgebreakerTraversal::Standard),
(&tiny, 0, EdgebreakerTraversal::Standard),
] {
assert_eq!(
select_edgebreaker_traversal(speed as usize, mesh.num_faces(), false),
expected,
"{} faces at speed {speed}",
mesh.num_faces()
);
let label = format!("v{major}.{minor}, {expected:?}, {} faces", mesh.num_faces());
let buffer = encode(mesh, speed, major, minor)
.unwrap_or_else(|e| panic!("{label}: encode: {e:?}"));
let mut decoded = Mesh::new();
MeshDecoder::new()
.decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)
.unwrap_or_else(|e| panic!("{label}: decode: {e:?}"));
assert_eq!(decoded.num_faces(), mesh.num_faces(), "{label}: face count");
assert_eq!(
sorted_mesh_positions(&decoded),
sorted_mesh_positions(mesh),
"{label}: positions"
);
}
}
}
#[test]
#[cfg(feature = "legacy_bitstream_encode")]
fn a_mesh_with_topology_splits_round_trips_at_every_claimed_version() {
for (major, minor) in [(2u8, 2u8), (2, 1), (2, 0), (1, 2), (1, 1)] {
for (n, speed, predictive) in [
(10usize, 5i32, false),
(33, 5, false),
(33, 0, false),
(33, 0, true),
] {
if predictive && major >= 2 {
continue;
}
let mesh = annulus_mesh(n);
let mut options = EncoderOptions::new();
options.set_version(major, minor);
options.set_encoding_method(1); options.set_global_int("encoding_speed", speed);
options.set_global_int("decoding_speed", speed);
if predictive {
options.set_global_int("force_predictive_traversal", 1);
}
let traversal = if predictive { ", predictive" } else { "" };
let label = format!("v{major}.{minor}, {n}x{n} at speed {speed}{traversal}");
let decoded =
round_trip(&mesh, &options).unwrap_or_else(|error| panic!("{label}: {error:?}"));
assert_eq!(decoded.num_faces(), mesh.num_faces(), "{label}: face count");
assert_eq!(
sorted_mesh_positions(&decoded),
sorted_mesh_positions(&mesh),
"{label}: positions"
);
}
}
}
#[test]
#[cfg(feature = "legacy_bitstream_encode")]
fn every_attribute_coding_round_trips_at_every_claimed_version() {
let quantized_position = |major, minor, method, speed| {
let mut options = EncoderOptions::new();
options.set_version(major, minor);
options.set_encoding_method(method);
options.set_global_int("encoding_speed", speed);
options.set_global_int("decoding_speed", speed);
options.set_attribute_int(0, "quantization_bits", 12);
options
};
let mesh = grid_mesh(33);
let reference = sorted_mesh_positions(
&round_trip(&mesh, &quantized_position(2, 2, 1, 0)).expect("2.2 quantized"),
);
for (major, minor, method, speed) in [(2u8, 0u8, 1i32, 0i32), (1, 2, 1, 0), (1, 3, 0, 5)] {
let decoded = round_trip(&mesh, &quantized_position(major, minor, method, speed))
.unwrap_or_else(|e| panic!("v{major}.{minor} quantized: {e:?}"));
assert_eq!(
sorted_mesh_positions(&decoded),
reference,
"v{major}.{minor}: quantized positions"
);
}
let mut normal_options = EncoderOptions::new();
normal_options.set_version(1, 2);
normal_options.set_encoding_method(1);
normal_options.set_global_int("encoding_speed", 0);
normal_options.set_global_int("decoding_speed", 0);
normal_options.set_attribute_int(1, "quantization_bits", 10);
round_trip(&grid_mesh_with_normals(33), &normal_options).expect("v1.2 quantized normal");
for scheme in [3i32, 5, 6] {
let mesh = if scheme == 6 {
grid_mesh_with_normals(33)
} else {
grid_mesh_with_texcoords(33)
};
let scheme_options = |major, minor| {
let mut options = EncoderOptions::new();
options.set_version(major, minor);
options.set_encoding_method(1);
options.set_global_int("encoding_speed", 0);
options.set_global_int("decoding_speed", 0);
options.set_attribute_int(1, "prediction_scheme", scheme);
options.set_attribute_int(1, "quantization_bits", 10);
options
};
let reference = sorted_mesh_positions(
&round_trip(&mesh, &scheme_options(2, 2))
.unwrap_or_else(|e| panic!("scheme {scheme} at 2.2: {e:?}")),
);
for (major, minor) in [(2u8, 1u8), (2, 0), (1, 2)] {
let decoded = round_trip(&mesh, &scheme_options(major, minor))
.unwrap_or_else(|e| panic!("scheme {scheme} at {major}.{minor}: {e:?}"));
assert_eq!(
sorted_mesh_positions(&decoded),
reference,
"scheme {scheme} at {major}.{minor}: positions"
);
}
}
}
#[cfg(feature = "legacy_bitstream_encode")]
fn sorted_mesh_positions(mesh: &Mesh) -> Vec<[u32; 3]> {
let attribute = mesh.attribute(mesh.named_attribute_id(GeometryAttributeType::Position));
let stride = attribute.byte_stride() as usize;
let mut out = Vec::with_capacity(attribute.size());
for i in 0..attribute.size() {
let mut position = [0u32; 3];
for (k, component) in position.iter_mut().enumerate() {
let mut bytes = [0u8; 4];
attribute.buffer().read(i * stride + k * 4, &mut bytes);
*component = u32::from_le_bytes(bytes);
}
out.push(position);
}
out.sort_unstable();
out
}
#[cfg(feature = "legacy_bitstream_encode")]
fn sorted_mesh_texcoords(mesh: &Mesh) -> Vec<[u32; 2]> {
let attribute = mesh.attribute(mesh.named_attribute_id(GeometryAttributeType::TexCoord));
let stride = attribute.byte_stride() as usize;
let mut out = Vec::with_capacity(attribute.size());
for i in 0..attribute.size() {
let mut uv = [0u32; 2];
for (k, component) in uv.iter_mut().enumerate() {
let mut bytes = [0u8; 4];
attribute.buffer().read(i * stride + k * 4, &mut bytes);
*component = u32::from_le_bytes(bytes);
}
out.push(uv);
}
out.sort_unstable();
out
}
#[cfg(feature = "legacy_bitstream_encode")]
#[derive(Debug)]
enum RoundTripError {
Encode(#[allow(dead_code)] DracoError),
Decode(#[allow(dead_code)] DracoError),
}
#[cfg(feature = "legacy_bitstream_encode")]
fn round_trip(mesh: &Mesh, options: &EncoderOptions) -> Result<Mesh, RoundTripError> {
let mut encoder = MeshEncoder::new();
encoder.set_mesh(mesh.clone());
let mut buffer = EncoderBuffer::new();
encoder
.encode(options, &mut buffer)
.map_err(RoundTripError::Encode)?;
let mut decoded = Mesh::new();
MeshDecoder::new()
.decode(&mut DecoderBuffer::new(buffer.data()), &mut decoded)
.map_err(RoundTripError::Decode)?;
Ok(decoded)
}
#[cfg(feature = "legacy_bitstream_encode")]
fn grid_mesh_with_uv_seam(n: usize) -> Mesh {
use draco_core::geometry_indices::{AttributeValueIndex, PointIndex};
let seam_column = n / 2;
let num_points = n * n + n;
let point_of = |x: usize, y: usize, right_side: bool| -> u32 {
if right_side && x == seam_column {
(n * n + y) as u32
} else {
(y * n + x) as u32
}
};
let mut mesh = Mesh::new();
mesh.set_num_points(num_points);
let mut position = PointAttribute::new();
position.init(
GeometryAttributeType::Position,
3,
DataType::Float32,
false,
n * n,
);
for y in 0..n {
for x in 0..n {
let offset = (y * n + x) * 12;
position
.buffer_mut()
.write(offset, &(x as f32).to_le_bytes());
position
.buffer_mut()
.write(offset + 4, &(y as f32).to_le_bytes());
position
.buffer_mut()
.write(offset + 8, &(((x + y) % 5) as f32).to_le_bytes());
}
}
position.set_explicit_mapping(num_points);
for y in 0..n {
for x in 0..n {
position.set_point_map_entry(
PointIndex((y * n + x) as u32),
AttributeValueIndex((y * n + x) as u32),
);
}
position.set_point_map_entry(
PointIndex((n * n + y) as u32),
AttributeValueIndex((y * n + seam_column) as u32),
);
}
mesh.add_attribute(position);
let mut uv = PointAttribute::new();
uv.init(
GeometryAttributeType::TexCoord,
2,
DataType::Float32,
false,
num_points,
);
for y in 0..n {
for x in 0..n {
let offset = (y * n + x) * 8;
uv.buffer_mut()
.write(offset, &(x as f32 / n as f32).to_le_bytes());
uv.buffer_mut()
.write(offset + 4, &(y as f32 / n as f32).to_le_bytes());
}
let offset = (n * n + y) * 8;
uv.buffer_mut().write(offset, &0.0f32.to_le_bytes());
uv.buffer_mut()
.write(offset + 4, &(y as f32 / n as f32).to_le_bytes());
}
mesh.add_attribute(uv);
mesh.set_num_faces((n - 1) * (n - 1) * 2);
let mut face_id = 0;
for y in 0..n - 1 {
for x in 0..n - 1 {
let right_side = x >= seam_column;
let p0 = point_of(x, y, right_side);
let p1 = point_of(x + 1, y, right_side);
let p2 = point_of(x, y + 1, right_side);
let p3 = point_of(x + 1, y + 1, right_side);
mesh.set_face_from_indices(face_id, [p0, p1, p2]);
face_id += 1;
mesh.set_face_from_indices(face_id, [p1, p3, p2]);
face_id += 1;
}
}
mesh
}
#[cfg(feature = "legacy_bitstream_encode")]
fn grid_mesh_with_texcoords(n: usize) -> Mesh {
let mut mesh = grid_mesh(n);
let num_points = n * n;
let mut uv = PointAttribute::new();
uv.init(
GeometryAttributeType::TexCoord,
2,
DataType::Float32,
false,
num_points,
);
for y in 0..n {
for x in 0..n {
let offset = (y * n + x) * 8;
uv.buffer_mut()
.write(offset, &(x as f32 / n as f32).to_le_bytes());
uv.buffer_mut()
.write(offset + 4, &(y as f32 / n as f32).to_le_bytes());
}
}
mesh.add_attribute(uv);
mesh
}
#[cfg(feature = "legacy_bitstream_encode")]
fn grid_mesh_with_normals(n: usize) -> Mesh {
let mut mesh = grid_mesh(n);
let num_points = n * n;
let mut normal = PointAttribute::new();
normal.init(
GeometryAttributeType::Normal,
3,
DataType::Float32,
false,
num_points,
);
for i in 0..num_points {
for (k, component) in [0.0f32, 0.0, 1.0].iter().enumerate() {
normal
.buffer_mut()
.write(i * 12 + k * 4, &component.to_le_bytes());
}
}
mesh.add_attribute(normal);
mesh
}
#[cfg(feature = "legacy_bitstream_encode")]
fn annulus_mesh(n: usize) -> Mesh {
let holes = [(n / 4, n / 4), (n / 2, n / 2), (3 * n / 4 - 1, n / 4)];
let mut mesh = Mesh::new();
mesh.set_num_points(n * n);
mesh.add_attribute(grid_mesh(n).attribute(0).clone());
let mut faces = Vec::new();
for y in 0..n - 1 {
for x in 0..n - 1 {
if holes.contains(&(x, y)) {
continue;
}
let p0 = (y * n + x) as u32;
let p1 = (y * n + x + 1) as u32;
let p2 = ((y + 1) * n + x) as u32;
let p3 = ((y + 1) * n + x + 1) as u32;
faces.push([p0, p1, p2]);
faces.push([p1, p3, p2]);
}
}
mesh.set_num_faces(faces.len());
for (id, face) in faces.into_iter().enumerate() {
mesh.set_face_from_indices(id, face);
}
mesh
}
#[cfg(feature = "legacy_bitstream_encode")]
fn grid_mesh(n: usize) -> Mesh {
use draco_core::draco_types::DataType;
use draco_core::geometry_attribute::{GeometryAttributeType, PointAttribute};
let mut mesh = Mesh::new();
let num_points = n * n;
let mut position = PointAttribute::new();
position.init(
GeometryAttributeType::Position,
3,
DataType::Float32,
false,
num_points,
);
for y in 0..n {
for x in 0..n {
let offset = (y * n + x) * 12;
position
.buffer_mut()
.write(offset, &(x as f32).to_le_bytes());
position
.buffer_mut()
.write(offset + 4, &(y as f32).to_le_bytes());
position
.buffer_mut()
.write(offset + 8, &(((x + y) % 5) as f32).to_le_bytes());
}
}
mesh.set_num_points(num_points);
mesh.add_attribute(position);
mesh.set_num_faces((n - 1) * (n - 1) * 2);
let mut face_id = 0;
for y in 0..n - 1 {
for x in 0..n - 1 {
let p0 = (y * n + x) as u32;
let p1 = (y * n + x + 1) as u32;
let p2 = ((y + 1) * n + x) as u32;
let p3 = ((y + 1) * n + x + 1) as u32;
mesh.set_face_from_indices(face_id, [p0, p1, p2]);
face_id += 1;
mesh.set_face_from_indices(face_id, [p1, p3, p2]);
face_id += 1;
}
}
mesh
}
#[test]
#[cfg(feature = "legacy_bitstream_encode")]
fn a_seamed_predicted_texcoord_round_trips_at_every_claimed_version() {
let mesh = grid_mesh_with_uv_seam(9);
let options_for = |major: u8, minor: u8, speed: i32, uv_scheme: i32| {
let mut options = EncoderOptions::new();
options.set_version(major, minor);
options.set_encoding_method(1);
options.set_global_int("encoding_speed", speed);
options.set_global_int("decoding_speed", speed);
options.set_attribute_int(0, "quantization_bits", 12);
options.set_attribute_int(1, "quantization_bits", 12);
if uv_scheme >= 0 {
options.set_attribute_int(1, "prediction_scheme", uv_scheme);
}
options
};
for (speed, uv_scheme) in [(5i32, -1i32), (0, -1), (5, 5), (0, 5)] {
let reference = round_trip(&mesh, &options_for(2, 2, speed, uv_scheme))
.unwrap_or_else(|e| panic!("v2.2 speed {speed} scheme {uv_scheme}: {e:?}"));
let reference_positions = sorted_mesh_positions(&reference);
let reference_texcoords = sorted_mesh_texcoords(&reference);
for (major, minor) in [(2u8, 1u8), (2, 0), (1, 2), (1, 1)] {
let decoded = round_trip(&mesh, &options_for(major, minor, speed, uv_scheme))
.unwrap_or_else(|e| {
panic!("v{major}.{minor} speed {speed} scheme {uv_scheme}: {e:?}")
});
assert_eq!(
decoded.num_faces(),
reference.num_faces(),
"v{major}.{minor} speed {speed} scheme {uv_scheme}: face count"
);
assert_eq!(
sorted_mesh_positions(&decoded),
reference_positions,
"v{major}.{minor} speed {speed} scheme {uv_scheme}: positions"
);
assert_eq!(
decoded.num_attributes(),
2,
"v{major}.{minor}: the texture coordinate survives"
);
assert_eq!(
sorted_mesh_texcoords(&decoded),
reference_texcoords,
"v{major}.{minor} speed {speed} scheme {uv_scheme}: texture coordinates"
);
}
}
}