use crate::corner_table::CornerTable;
#[cfg(all(test, feature = "decoder"))]
use crate::draco_types::DataType;
use crate::geometry_attribute::GeometryAttributeType;
#[cfg(all(test, feature = "decoder"))]
use crate::geometry_attribute::PointAttribute;
use crate::geometry_indices::{CornerIndex, PointIndex, INVALID_CORNER_INDEX};
#[cfg(feature = "decoder")]
use crate::geometry_indices::{VertexIndex, INVALID_ATTRIBUTE_VALUE_INDEX};
use crate::mesh_prediction_scheme_data::MeshPredictionSchemeData;
use crate::normal_compression_utils::OctahedronToolBox;
use crate::portable_attribute::PredictionParent;
use crate::prediction_scheme::{
PredictionScheme, PredictionSchemeMethod, PredictionSchemeTransformType,
};
#[cfg(feature = "decoder")]
use crate::decoder_buffer::DecoderBuffer;
#[cfg(feature = "decoder")]
use crate::prediction_scheme::{PredictionSchemeDecoder, PredictionSchemeDecodingTransform};
#[cfg(feature = "decoder")]
use crate::prediction_scheme_normal_octahedron_canonicalized_decoding_transform::PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform;
#[cfg(feature = "decoder")]
use crate::rans_bit_decoder::RAnsBitDecoder;
#[cfg(feature = "encoder")]
use crate::encoder_buffer::EncoderBuffer;
#[cfg(feature = "encoder")]
use crate::prediction_scheme::{PredictionSchemeEncoder, PredictionSchemeEncodingTransform};
#[cfg(feature = "encoder")]
use crate::prediction_scheme_normal_octahedron_canonicalized_encoding_transform::PredictionSchemeNormalOctahedronCanonicalizedEncodingTransform;
#[cfg(feature = "encoder")]
use crate::rans_bit_encoder::RAnsBitEncoder;
use crate::status::{DracoError, Status};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NormalPredictionMode {
OneTriangle = 0,
TriangleArea = 1,
}
#[cfg(feature = "decoder")]
pub struct MeshPredictionSchemeGeometricNormalDecoder<'a> {
transform: PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform,
mesh_data: Option<MeshPredictionSchemeData<'a>>,
pos_parent: Option<PredictionParent<'a>>,
entry_to_point_id_map: Option<crate::prediction_scheme::EntryToPointIdMap<'a>>,
prediction_mode: NormalPredictionMode,
octahedron_tool_box: OctahedronToolBox,
flip_normal_bits: Vec<bool>,
flip_normal_bit_index: usize,
}
#[cfg(feature = "decoder")]
impl<'a> MeshPredictionSchemeGeometricNormalDecoder<'a> {
pub fn new(transform: PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform) -> Self {
Self {
transform,
mesh_data: None,
pos_parent: None,
entry_to_point_id_map: None,
prediction_mode: NormalPredictionMode::TriangleArea,
octahedron_tool_box: OctahedronToolBox::new(),
flip_normal_bits: Vec::new(),
flip_normal_bit_index: 0,
}
}
pub fn set_entry_to_point_id_map(
&mut self,
point_ids: crate::prediction_scheme::EntryToPointIdMap<'a>,
) {
self.entry_to_point_id_map = Some(point_ids);
}
pub fn init(&mut self, mesh_data: &MeshPredictionSchemeData<'a>) {
self.mesh_data = Some(mesh_data.clone());
}
fn is_initialized(&self) -> bool {
self.mesh_data
.as_ref()
.and_then(|m| m.corner_table())
.is_some()
&& self
.mesh_data
.as_ref()
.and_then(|m| m.data_to_corner_map())
.is_some()
&& self.pos_parent.is_some()
&& self.entry_to_point_id_map.is_some()
}
#[cfg(test)]
fn corner_positions(&self) -> Option<CornerPositions<'_>> {
let mesh_data = self.mesh_data.as_ref()?;
Some(CornerPositions::new(
mesh_data.corner_table()?,
mesh_data.vertex_to_data_map()?,
self.entry_to_point_id_map?,
self.pos_parent?,
))
}
}
#[cfg(feature = "decoder")]
fn position_for_vertex(
vertex_to_data_map: &[i32],
entry_to_point_id_map: crate::prediction_scheme::EntryToPointIdMap<'_>,
pos_parent: &PredictionParent<'_>,
v: crate::geometry_indices::VertexIndex,
) -> [i32; 3] {
let data_id = *vertex_to_data_map.get(v.0 as usize).unwrap_or(&-1);
if data_id < 0 {
return [0, 0, 0];
}
let Some(point_id) = entry_to_point_id_map.get(data_id as usize) else {
return [0, 0, 0];
};
let pos_val_id = pos_parent.mapped_index(PointIndex(point_id));
if pos_val_id == INVALID_ATTRIBUTE_VALUE_INDEX {
return [0, 0, 0];
}
let mut pos = [0i64; 3];
if !pos_parent.read_vector3_as_i64(pos_val_id.0 as usize, &mut pos) {
return [0, 0, 0];
}
let clamp_i32 = |x: i64| -> i32 {
if x > i32::MAX as i64 {
i32::MAX
} else if x < i32::MIN as i64 {
i32::MIN
} else {
x as i32
}
};
[clamp_i32(pos[0]), clamp_i32(pos[1]), clamp_i32(pos[2])]
}
#[cfg(feature = "decoder")]
struct CornerPositions<'b> {
corner_table: &'b CornerTable,
vertex_to_data_map: &'b [i32],
entry_to_point_id_map: crate::prediction_scheme::EntryToPointIdMap<'b>,
pos_parent: PredictionParent<'b>,
cache: Vec<[i32; 3]>,
}
#[cfg(feature = "decoder")]
impl<'b> CornerPositions<'b> {
fn new(
corner_table: &'b CornerTable,
vertex_to_data_map: &'b [i32],
entry_to_point_id_map: crate::prediction_scheme::EntryToPointIdMap<'b>,
pos_parent: PredictionParent<'b>,
) -> Self {
let cache = match pos_parent.int32_vector3_layout() {
Some((bytes, stride)) => vertex_to_data_map
.iter()
.map(|&data_id| {
let read = || {
let point_id = entry_to_point_id_map.get(usize::try_from(data_id).ok()?)?;
let entry = pos_parent.mapped_index(PointIndex(point_id));
if entry == INVALID_ATTRIBUTE_VALUE_INDEX {
return None;
}
let start = (entry.0 as usize).checked_mul(stride)?;
let raw = bytes.get(start..start.checked_add(12)?)?;
let component = |c: usize| {
i32::from_le_bytes([
raw[4 * c],
raw[4 * c + 1],
raw[4 * c + 2],
raw[4 * c + 3],
])
};
Some([component(0), component(1), component(2)])
};
read().unwrap_or([0, 0, 0])
})
.collect(),
None => (0..vertex_to_data_map.len())
.map(|v| {
position_for_vertex(
vertex_to_data_map,
entry_to_point_id_map,
&pos_parent,
VertexIndex(v as u32),
)
})
.collect(),
};
Self {
corner_table,
vertex_to_data_map,
entry_to_point_id_map,
pos_parent,
cache,
}
}
fn get(&mut self, corner_id: CornerIndex) -> [i32; 3] {
if corner_id == INVALID_CORNER_INDEX {
return [0, 0, 0];
}
self.get_by_vertex(self.corner_table.vertex(corner_id))
}
fn get_by_vertex(&mut self, v: VertexIndex) -> [i32; 3] {
match self.cache.get(v.0 as usize) {
Some(&pos) => pos,
None => position_for_vertex(
self.vertex_to_data_map,
self.entry_to_point_id_map,
&self.pos_parent,
v,
),
}
}
}
#[cfg(feature = "decoder")]
fn compute_predicted_value(
positions: &mut CornerPositions<'_>,
prediction_mode: NormalPredictionMode,
corner_id: CornerIndex,
prediction: &mut [i32; 3],
) {
if corner_id == INVALID_CORNER_INDEX {
prediction[0] = 0;
prediction[1] = 0;
prediction[2] = 0;
return;
}
let corner_table = positions.corner_table;
let pos_cent = positions.get(corner_id);
let mut normal = [0i64; 3];
let mut cit = VertexCornersIterator::new(corner_table, corner_id);
while !cit.end() {
let (v_next, v_prev) = if prediction_mode == NormalPredictionMode::OneTriangle {
(
corner_table.vertex_after(corner_id),
corner_table.vertex_before(corner_id),
)
} else {
(
corner_table.vertex_after(cit.corner()),
corner_table.vertex_before(cit.corner()),
)
};
let pos_prev = positions.get_by_vertex(v_prev);
let pos_next = positions.get_by_vertex(v_next);
let v_next = [
pos_next[0] as i64 - pos_cent[0] as i64,
pos_next[1] as i64 - pos_cent[1] as i64,
pos_next[2] as i64 - pos_cent[2] as i64,
];
let v_prev = [
pos_prev[0] as i64 - pos_cent[0] as i64,
pos_prev[1] as i64 - pos_cent[1] as i64,
pos_prev[2] as i64 - pos_cent[2] as i64,
];
let cross = cross_product(&v_next, &v_prev);
normal[0] = normal[0].wrapping_add(cross[0]);
normal[1] = normal[1].wrapping_add(cross[1]);
normal[2] = normal[2].wrapping_add(cross[2]);
cit.next(corner_table);
}
*prediction = scale_normal_to_prediction(normal, prediction_mode);
}
#[cfg(feature = "decoder")]
impl<'a> PredictionScheme<'a> for MeshPredictionSchemeGeometricNormalDecoder<'a> {
fn get_prediction_method(&self) -> PredictionSchemeMethod {
PredictionSchemeMethod::MeshPredictionGeometricNormal
}
fn is_initialized(&self) -> bool {
self.is_initialized()
}
fn get_num_parent_attributes(&self) -> i32 {
1
}
fn get_parent_attribute_type(&self, i: i32) -> GeometryAttributeType {
assert_eq!(i, 0);
GeometryAttributeType::Position
}
fn set_parent_attribute(&mut self, parent: PredictionParent<'a>) -> Status {
if parent.attribute_type() != GeometryAttributeType::Position {
return Err(DracoError::invalid_parameter(format!(
"Geometric normal prediction needs a position parent, got {:?}",
parent.attribute_type()
)));
}
if parent.num_components() != 3 {
return Err(DracoError::invalid_parameter(format!(
"Geometric normal prediction needs a 3-component parent, got {}",
parent.num_components()
)));
}
self.pos_parent = Some(parent);
Ok(())
}
fn get_transform_type(&self) -> PredictionSchemeTransformType {
PredictionSchemeTransformType::NormalOctahedronCanonicalized
}
}
#[cfg(feature = "decoder")]
impl<'a> PredictionSchemeDecoder<'a, i32> for MeshPredictionSchemeGeometricNormalDecoder<'a> {
fn compute_original_values(
&mut self,
data: &mut [i32],
_size: usize,
num_components: usize,
_entry_to_point_id_map: Option<crate::prediction_scheme::EntryToPointIdMap<'_>>,
) -> Status {
if !self.is_initialized() {
return Err(DracoError::general(
"Geometric normal prediction was never initialized".to_string(),
));
}
if num_components != 2 {
return Err(DracoError::invalid_parameter(format!(
"Geometric normal prediction needs 2 octahedral components, got {num_components}"
)));
}
self.transform.init(num_components)?;
let missing =
|what: &str| DracoError::general(format!("Geometric normal prediction has no {what}"));
let Some(mesh_data) = self.mesh_data.as_ref() else {
return Err(missing("mesh data"));
};
let Some(data_to_corner_map) = mesh_data.data_to_corner_map() else {
return Err(missing("data-to-corner map"));
};
let corner_map_size = data_to_corner_map.len();
if corner_map_size * num_components > data.len() {
return Err(DracoError::general(format!(
"Geometric normal prediction needs {} values, has {}",
corner_map_size * num_components,
data.len()
)));
}
let mut positions = match (
mesh_data.corner_table(),
mesh_data.vertex_to_data_map(),
self.pos_parent,
self.entry_to_point_id_map,
) {
(Some(corner_table), Some(vertex_to_data_map), Some(pos_parent), Some(entry_map)) => {
Some(CornerPositions::new(
corner_table,
vertex_to_data_map,
entry_map,
pos_parent,
))
}
_ => None,
};
let mut pred_normal_3d = [0i32; 3];
for i in 0..corner_map_size {
let corner_id = CornerIndex(data_to_corner_map[i]);
match positions.as_mut() {
Some(positions) => compute_predicted_value(
positions,
self.prediction_mode,
corner_id,
&mut pred_normal_3d,
),
None => pred_normal_3d = [0, 0, 0],
}
self.octahedron_tool_box
.canonicalize_integer_vector(&mut pred_normal_3d);
if self
.flip_normal_bits
.get(self.flip_normal_bit_index)
.copied()
.unwrap_or(false)
{
pred_normal_3d[0] = -pred_normal_3d[0];
pred_normal_3d[1] = -pred_normal_3d[1];
pred_normal_3d[2] = -pred_normal_3d[2];
}
self.flip_normal_bit_index += 1;
let (s, t) = self
.octahedron_tool_box
.integer_vector_to_quantized_octahedral_coords(&pred_normal_3d);
let prediction = [s, t];
let offset = i * num_components;
self.transform
.compute_original_value(&prediction, &mut data[offset..offset + num_components]);
}
Ok(())
}
fn decode_prediction_data(&mut self, buffer: &mut DecoderBuffer) -> Status {
let start_pos = buffer.position();
let bitstream_version: u16 = buffer.bitstream_version();
if bitstream_version < 0x0202 && !cfg!(feature = "legacy_bitstream_decode") {
return Err(DracoError::unsupported_feature(format!(
"Geometric normal prediction below 2.2 needs the legacy_bitstream_decode feature (stream is {}.{})",
bitstream_version >> 8,
bitstream_version & 0xff
)));
}
let try_decode_at_pos = |this: &mut Self, buf: &mut DecoderBuffer| -> Status {
this.transform.decode_transform_data(buf)?;
this.octahedron_tool_box
.set_quantization_bits(this.transform.quantization_bits());
if bitstream_version < 0x0202 {
let mode = buf.decode_u8().map_err(|_| {
DracoError::buffer(
"Stream ends before the pre-2.2 normal prediction mode".to_string(),
)
})?;
if mode > NormalPredictionMode::TriangleArea as u8 {
return Err(DracoError::unsupported_feature(format!(
"Normal prediction mode {mode}"
)));
}
this.prediction_mode = if mode == 0 {
NormalPredictionMode::OneTriangle
} else {
NormalPredictionMode::TriangleArea
};
}
let Some(num_values) = this
.mesh_data
.as_ref()
.and_then(|m| m.data_to_corner_map())
.map(|map| map.len())
else {
return Err(DracoError::general(
"Geometric normal prediction has no data-to-corner map".to_string(),
));
};
this.flip_normal_bits.clear();
this.flip_normal_bits.reserve(num_values);
let mut decoder = RAnsBitDecoder::new();
if !decoder.start_decoding(buf) {
return Err(DracoError::buffer(
"Normal flip-bit rANS stream is truncated".to_string(),
));
}
for _ in 0..num_values {
this.flip_normal_bits.push(decoder.decode_next_bit());
}
decoder.end_decoding();
this.flip_normal_bit_index = 0;
Ok(())
};
match try_decode_at_pos(self, buffer) {
Ok(()) => Ok(()),
Err(error) => {
let _ = buffer.set_position(start_pos);
Err(error)
}
}
}
}
struct VertexCornersIterator {
start_corner: CornerIndex,
corner: CornerIndex,
sweeping_left: bool,
is_end: bool,
}
impl VertexCornersIterator {
fn new(_corner_table: &CornerTable, corner_id: CornerIndex) -> Self {
Self {
start_corner: corner_id,
corner: corner_id,
sweeping_left: false,
is_end: corner_id == INVALID_CORNER_INDEX,
}
}
fn corner(&self) -> CornerIndex {
self.corner
}
fn end(&self) -> bool {
self.is_end || self.corner == INVALID_CORNER_INDEX
}
fn next(&mut self, corner_table: &CornerTable) {
if self.corner == INVALID_CORNER_INDEX {
return;
}
if !self.sweeping_left {
let right = corner_table.swing_right(self.corner);
if right == self.start_corner {
self.finish();
return;
}
if right != INVALID_CORNER_INDEX {
self.corner = right;
return;
}
self.sweeping_left = true;
self.set_or_finish(corner_table.swing_left(self.start_corner));
return;
}
self.set_or_finish(corner_table.swing_left(self.corner));
}
fn set_or_finish(&mut self, corner: CornerIndex) {
if corner == INVALID_CORNER_INDEX || corner == self.start_corner {
self.finish();
} else {
self.corner = corner;
}
}
fn finish(&mut self) {
self.corner = INVALID_CORNER_INDEX;
self.is_end = true;
}
}
#[cfg(feature = "encoder")]
pub struct MeshPredictionSchemeGeometricNormalEncoder<'a> {
transform: PredictionSchemeNormalOctahedronCanonicalizedEncodingTransform,
octahedron_tool_box: OctahedronToolBox,
mesh_data: Option<MeshPredictionSchemeData<'a>>,
pos_parent: Option<PredictionParent<'a>>,
prediction_mode: NormalPredictionMode,
flip_normal_bit_encoder: RAnsBitEncoder,
bitstream_version: u16,
}
#[cfg(feature = "encoder")]
impl<'a> MeshPredictionSchemeGeometricNormalEncoder<'a> {
pub fn new(transform: PredictionSchemeNormalOctahedronCanonicalizedEncodingTransform) -> Self {
Self {
transform,
octahedron_tool_box: OctahedronToolBox::new(),
mesh_data: None,
pos_parent: None,
prediction_mode: NormalPredictionMode::TriangleArea,
flip_normal_bit_encoder: RAnsBitEncoder::new(),
bitstream_version: 0,
}
}
pub fn set_bitstream_version(&mut self, major: u8, minor: u8) {
self.bitstream_version = crate::version::bitstream_version(major, minor);
self.prediction_mode = if self.bitstream_version == 0x0200 {
NormalPredictionMode::OneTriangle
} else {
NormalPredictionMode::TriangleArea
};
}
pub fn init(&mut self, mesh_data: &MeshPredictionSchemeData<'a>) {
self.mesh_data = Some(mesh_data.clone());
}
}
#[cfg(feature = "encoder")]
struct EncoderCornerPositions<'b> {
corner_table: &'b CornerTable,
cache: Vec<[i64; 3]>,
}
#[cfg(feature = "encoder")]
impl<'b> EncoderCornerPositions<'b> {
fn new(
corner_table: &'b CornerTable,
vertex_to_data_map: &'b [i32],
map: crate::prediction_scheme::EntryToPointIdMap<'b>,
pos_parent: PredictionParent<'b>,
) -> Self {
let cache = match pos_parent.int32_vector3_layout() {
Some((bytes, stride)) => vertex_to_data_map
.iter()
.map(|&data_id| {
let read = || {
let point_id = map.get(data_id as usize)?;
let entry = pos_parent.mapped_index(PointIndex(point_id));
let start = (entry.0 as usize).checked_mul(stride)?;
let raw = bytes.get(start..start.checked_add(12)?)?;
let component = |c: usize| {
i64::from(i32::from_le_bytes([
raw[4 * c],
raw[4 * c + 1],
raw[4 * c + 2],
raw[4 * c + 3],
]))
};
Some([component(0), component(1), component(2)])
};
read().unwrap_or([0, 0, 0])
})
.collect(),
None => vertex_to_data_map
.iter()
.map(|&data_id| Self::lookup(map, pos_parent, data_id))
.collect(),
};
Self {
corner_table,
cache,
}
}
fn get(&mut self, ci: CornerIndex) -> [i64; 3] {
let vertex = self.corner_table.vertex(ci).0 as usize;
self.cache.get(vertex).copied().unwrap_or([0, 0, 0])
}
fn lookup(
map: crate::prediction_scheme::EntryToPointIdMap<'_>,
pos_parent: PredictionParent<'_>,
data_id: i32,
) -> [i64; 3] {
let Some(point_id) = map.get(data_id as usize) else {
return [0, 0, 0];
};
let pos_val_id = pos_parent.mapped_index(PointIndex(point_id));
let mut pos = [0i64; 3];
if !pos_parent.read_vector3_as_i64(pos_val_id.0 as usize, &mut pos) {
return [0, 0, 0];
}
pos
}
}
#[cfg(feature = "encoder")]
fn compute_encoder_predicted_value(
positions: &mut EncoderCornerPositions<'_>,
prediction_mode: NormalPredictionMode,
corner_id: CornerIndex,
prediction: &mut [i32; 3],
) {
if corner_id == INVALID_CORNER_INDEX {
prediction[0] = 0;
prediction[1] = 0;
prediction[2] = 0;
return;
}
let corner_table = positions.corner_table;
let mut cit = VertexCornersIterator::new(corner_table, corner_id);
let pos_cent = positions.get(corner_id);
let mut normal = [0i64; 3];
while !cit.end() {
let base = if prediction_mode == NormalPredictionMode::OneTriangle {
corner_id
} else {
cit.corner()
};
let c_next = corner_table.next(base);
let c_prev = corner_table.previous(base);
let pos_next = positions.get(c_next);
let pos_prev = positions.get(c_prev);
let delta_next = [
pos_next[0].wrapping_sub(pos_cent[0]),
pos_next[1].wrapping_sub(pos_cent[1]),
pos_next[2].wrapping_sub(pos_cent[2]),
];
let delta_prev = [
pos_prev[0].wrapping_sub(pos_cent[0]),
pos_prev[1].wrapping_sub(pos_cent[1]),
pos_prev[2].wrapping_sub(pos_cent[2]),
];
let cross = cross_product(&delta_next, &delta_prev);
normal[0] = normal[0].wrapping_add(cross[0]);
normal[1] = normal[1].wrapping_add(cross[1]);
normal[2] = normal[2].wrapping_add(cross[2]);
cit.next(corner_table);
}
*prediction = scale_normal_to_prediction(normal, prediction_mode);
}
#[cfg(feature = "encoder")]
impl<'a> PredictionScheme<'a> for MeshPredictionSchemeGeometricNormalEncoder<'a> {
fn get_prediction_method(&self) -> PredictionSchemeMethod {
PredictionSchemeMethod::MeshPredictionGeometricNormal
}
fn is_initialized(&self) -> bool {
self.mesh_data.is_some() && self.pos_parent.is_some()
}
fn get_num_parent_attributes(&self) -> i32 {
1
}
fn get_parent_attribute_type(&self, i: i32) -> GeometryAttributeType {
if i == 0 {
GeometryAttributeType::Position
} else {
GeometryAttributeType::Invalid
}
}
fn set_parent_attribute(&mut self, parent: PredictionParent<'a>) -> Status {
if parent.attribute_type() != GeometryAttributeType::Position {
return Err(DracoError::invalid_parameter(format!(
"Geometric normal prediction needs a position parent, got {:?}",
parent.attribute_type()
)));
}
self.pos_parent = Some(parent);
Ok(())
}
fn get_transform_type(&self) -> PredictionSchemeTransformType {
self.transform.get_type()
}
}
#[cfg(feature = "encoder")]
impl<'a> PredictionSchemeEncoder<'a, i32, i32> for MeshPredictionSchemeGeometricNormalEncoder<'a> {
fn compute_correction_values(
&mut self,
in_data: &[i32],
out_corr: &mut [i32],
_size: usize,
num_components: usize,
entry_to_point_id_map: Option<crate::prediction_scheme::EntryToPointIdMap<'_>>,
) -> Status {
if !self.is_initialized() {
return Err(DracoError::general(
"Geometric normal prediction was never initialized".to_string(),
));
}
let Some(map) = entry_to_point_id_map else {
return Err(DracoError::invalid_parameter(
"Geometric normal prediction needs an entry-to-point map".to_string(),
));
};
if num_components != 2 {
return Err(DracoError::invalid_parameter(format!(
"Geometric normal prediction needs 2 octahedral components, got {num_components}"
)));
}
if !self
.octahedron_tool_box
.set_quantization_bits(self.transform.quantization_bits())
{
return Err(DracoError::invalid_parameter(format!(
"Octahedral quantization bits {} outside the supported range 2..=30",
self.transform.quantization_bits()
)));
}
self.flip_normal_bit_encoder.start_encoding();
let mesh_data = self.mesh_data.as_ref().unwrap();
let data_to_corner_map = mesh_data.data_to_corner_map().unwrap();
let mut pred_normal_3d = [0i32; 3];
let mut pos_pred_normal_oct = [0i32; 2];
let mut neg_pred_normal_oct = [0i32; 2];
let mut pos_correction = [0i32; 2];
let mut neg_correction = [0i32; 2];
let mut positions = EncoderCornerPositions::new(
mesh_data.corner_table().unwrap(),
mesh_data.vertex_to_data_map().unwrap(),
map,
self.pos_parent.unwrap(),
);
let corner_map_size = data_to_corner_map.len();
for i in 0..corner_map_size {
let corner_id = CornerIndex(data_to_corner_map[i]);
compute_encoder_predicted_value(
&mut positions,
self.prediction_mode,
corner_id,
&mut pred_normal_3d,
);
self.octahedron_tool_box
.canonicalize_integer_vector(&mut pred_normal_3d);
let (s_pos, t_pos) = self
.octahedron_tool_box
.integer_vector_to_quantized_octahedral_coords(&pred_normal_3d);
pos_pred_normal_oct[0] = s_pos;
pos_pred_normal_oct[1] = t_pos;
let neg_normal_3d = pred_normal_3d.map(i32::wrapping_neg);
let (s_neg, t_neg) = self
.octahedron_tool_box
.integer_vector_to_quantized_octahedral_coords(&neg_normal_3d);
neg_pred_normal_oct[0] = s_neg;
neg_pred_normal_oct[1] = t_neg;
let offset = i * num_components;
let in_val = &in_data[offset..offset + num_components];
self.transform
.compute_correction(in_val, &pos_pred_normal_oct, &mut pos_correction);
self.transform
.compute_correction(in_val, &neg_pred_normal_oct, &mut neg_correction);
pos_correction[0] = self.octahedron_tool_box.mod_max(pos_correction[0]);
pos_correction[1] = self.octahedron_tool_box.mod_max(pos_correction[1]);
neg_correction[0] = self.octahedron_tool_box.mod_max(neg_correction[0]);
neg_correction[1] = self.octahedron_tool_box.mod_max(neg_correction[1]);
let pos_abs_sum = pos_correction[0].abs() + pos_correction[1].abs();
let neg_abs_sum = neg_correction[0].abs() + neg_correction[1].abs();
if pos_abs_sum < neg_abs_sum {
self.flip_normal_bit_encoder.encode_bit(false);
out_corr[offset] = self.octahedron_tool_box.make_positive(pos_correction[0]);
out_corr[offset + 1] = self.octahedron_tool_box.make_positive(pos_correction[1]);
} else {
self.flip_normal_bit_encoder.encode_bit(true);
out_corr[offset] = self.octahedron_tool_box.make_positive(neg_correction[0]);
out_corr[offset + 1] = self.octahedron_tool_box.make_positive(neg_correction[1]);
}
}
Ok(())
}
fn encode_prediction_data(&mut self, buffer: &mut Vec<u8>) -> Status {
self.transform.encode_transform_data(buffer)?;
if self.bitstream_version != 0 && self.bitstream_version < 0x0202 {
buffer.push(self.prediction_mode as u8);
}
let mut temp_buffer = EncoderBuffer::new();
temp_buffer.set_version(
(self.bitstream_version >> 8) as u8,
(self.bitstream_version & 0xff) as u8,
);
self.flip_normal_bit_encoder.end_encoding(&mut temp_buffer);
buffer.extend_from_slice(temp_buffer.data());
Ok(())
}
}
#[cfg(any(feature = "encoder", feature = "decoder"))]
fn cross_product(a: &[i64; 3], b: &[i64; 3]) -> [i64; 3] {
[
a[1].wrapping_mul(b[2])
.wrapping_sub(a[2].wrapping_mul(b[1])),
a[2].wrapping_mul(b[0])
.wrapping_sub(a[0].wrapping_mul(b[2])),
a[0].wrapping_mul(b[1])
.wrapping_sub(a[1].wrapping_mul(b[0])),
]
}
#[cfg(any(feature = "encoder", feature = "decoder"))]
fn scale_normal_to_prediction(
mut normal: [i64; 3],
prediction_mode: NormalPredictionMode,
) -> [i32; 3] {
const UPPER_BOUND: i64 = 1 << 29;
let abs_sum = normal
.iter()
.try_fold(0i64, |sum, c| sum.checked_add(c.checked_abs()?))
.unwrap_or(i64::MAX);
let abs_sum = if prediction_mode == NormalPredictionMode::OneTriangle {
i64::from(abs_sum as i32)
} else {
abs_sum
};
if abs_sum > UPPER_BOUND {
let quotient = abs_sum / UPPER_BOUND;
for c in &mut normal {
*c /= quotient;
}
}
normal.map(|c| c as i32)
}
#[cfg(all(test, feature = "decoder"))]
mod tests {
use super::*;
use crate::corner_table::CornerTable;
use crate::geometry_attribute::GeometryAttributeType;
use crate::geometry_indices::{FaceIndex, PointIndex};
use crate::prediction_scheme::EntryToPointIdMap;
#[cfg(feature = "encoder")]
#[test]
fn the_prediction_mode_written_is_the_one_its_bitstream_knew() {
use crate::prediction_scheme::PredictionSchemeEncoder;
use crate::prediction_scheme_normal_octahedron_canonicalized_encoding_transform::PredictionSchemeNormalOctahedronCanonicalizedEncodingTransform as Transform;
for (major, minor, mode) in [
(1u8, 2u8, Some(1u8)),
(2, 0, Some(0)),
(2, 1, Some(1)),
(2, 2, None),
] {
let mut encoder = MeshPredictionSchemeGeometricNormalEncoder::new(Transform::new(255));
encoder.set_bitstream_version(major, minor);
let mut data = Vec::new();
encoder.encode_prediction_data(&mut data).unwrap();
match mode {
Some(mode) => assert_eq!(data[8], mode, "bitstream {major}.{minor}"),
None => assert_eq!(
encoder.prediction_mode,
NormalPredictionMode::TriangleArea,
"bitstream {major}.{minor} writes no mode and means triangle area"
),
}
}
}
#[test]
fn mesh_geometric_normal_position_lookup_returns_zero_when_entry_map_is_too_short() {
let mut corner_table = CornerTable::new(1);
corner_table.set_face_vertices(FaceIndex(0), PointIndex(0), PointIndex(1), PointIndex(2));
let data_to_corner_map = [0u32];
let vertex_to_data_map = [1, 0, 0];
let mut mesh_data = MeshPredictionSchemeData::new();
mesh_data.set(&corner_table, &data_to_corner_map, &vertex_to_data_map);
let mut position_attribute = PointAttribute::new();
position_attribute.init(
GeometryAttributeType::Position,
3,
DataType::Int32,
false,
1,
);
let mut decoder = MeshPredictionSchemeGeometricNormalDecoder::new(
PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform::new(),
);
decoder.init(&mesh_data);
assert!(decoder
.set_parent_attribute(
PredictionParent::portable(&position_attribute).expect("portable")
)
.is_ok());
let entry_to_point_id_map = [0u32];
decoder
.set_entry_to_point_id_map(EntryToPointIdMap::from_u32_slice(&entry_to_point_id_map));
let mut positions = decoder.corner_positions().expect("lookup resolves");
assert_eq!(positions.get(CornerIndex(0)), [0, 0, 0]);
}
#[test]
fn mesh_geometric_normal_position_lookup_returns_zero_for_truncated_buffer() {
let mut corner_table = CornerTable::new(1);
corner_table.set_face_vertices(FaceIndex(0), PointIndex(0), PointIndex(1), PointIndex(2));
let data_to_corner_map = [0u32];
let vertex_to_data_map = [0, 0, 0];
let mut mesh_data = MeshPredictionSchemeData::new();
mesh_data.set(&corner_table, &data_to_corner_map, &vertex_to_data_map);
let mut position_attribute = PointAttribute::new();
position_attribute.init(
GeometryAttributeType::Position,
3,
DataType::Int32,
false,
1,
);
position_attribute.buffer_mut().resize(8);
let mut decoder = MeshPredictionSchemeGeometricNormalDecoder::new(
PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform::new(),
);
decoder.init(&mesh_data);
assert!(decoder
.set_parent_attribute(
PredictionParent::portable(&position_attribute).expect("portable")
)
.is_ok());
let entry_to_point_id_map = [0u32];
decoder
.set_entry_to_point_id_map(EntryToPointIdMap::from_u32_slice(&entry_to_point_id_map));
let mut positions = decoder.corner_positions().expect("lookup resolves");
assert_eq!(positions.get(CornerIndex(0)), [0, 0, 0]);
}
#[test]
fn mesh_geometric_normal_position_cache_matches_the_uncached_lookup() {
let mut corner_table = CornerTable::new(2);
corner_table.set_face_vertices(FaceIndex(0), PointIndex(0), PointIndex(1), PointIndex(2));
corner_table.set_face_vertices(FaceIndex(1), PointIndex(2), PointIndex(1), PointIndex(3));
let data_to_corner_map = [0u32, 1, 2, 3];
let vertex_to_data_map = [0, 1, -1, 3];
let entry_to_point_id_map = [0u32, 1, 2, 7];
let mut mesh_data = MeshPredictionSchemeData::new();
mesh_data.set(&corner_table, &data_to_corner_map, &vertex_to_data_map);
let num_points = 4;
for data_type in [DataType::Int32, DataType::Uint32, DataType::Int16] {
let mut position_attribute = PointAttribute::new();
position_attribute.init(
GeometryAttributeType::Position,
3,
data_type,
false,
num_points,
);
for p in 0..num_points {
for c in 0..3 {
let value = (10 * p + c) as i32 - 17;
let at = p * 3 + c;
if data_type == DataType::Int16 {
position_attribute
.buffer_mut()
.update(&(value as i16).to_le_bytes(), Some(at * 2));
} else {
position_attribute
.buffer_mut()
.update(&value.to_le_bytes(), Some(at * 4));
}
}
}
let mut decoder = MeshPredictionSchemeGeometricNormalDecoder::new(
PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform::new(),
);
decoder.init(&mesh_data);
assert!(decoder
.set_parent_attribute(
PredictionParent::portable(&position_attribute).expect("portable")
)
.is_ok());
decoder.set_entry_to_point_id_map(EntryToPointIdMap::from_u32_slice(
&entry_to_point_id_map,
));
let mut positions = decoder.corner_positions().expect("lookup resolves");
let mut answers = std::collections::BTreeSet::new();
for corner in 0..6u32 {
let corner = CornerIndex(corner);
let expected = position_for_vertex(
&vertex_to_data_map,
EntryToPointIdMap::from_u32_slice(&entry_to_point_id_map),
&PredictionParent::portable(&position_attribute).expect("portable"),
corner_table.vertex(corner),
);
assert_eq!(positions.get(corner), expected, "{data_type:?} {corner:?}");
answers.insert(expected);
}
assert_eq!(answers.len(), 3, "{data_type:?} {answers:?}");
assert!(answers.contains(&[0, 0, 0]), "{data_type:?}");
}
let vertices: Vec<_> = (0..6u32)
.map(|c| corner_table.vertex(CornerIndex(c)).0)
.collect();
let unique: std::collections::BTreeSet<_> = vertices.iter().collect();
assert!(
unique.len() < vertices.len(),
"shared vertices: {vertices:?}"
);
}
#[test]
fn normal_scaling_saturates_and_truncates_as_upstream_does() {
use NormalPredictionMode::{OneTriangle, TriangleArea};
let cases: [([i64; 3], NormalPredictionMode, [i32; 3]); 6] = [
([i64::MIN, 0, 0], TriangleArea, [-536_870_912, 0, 0]),
([i64::MIN, 0, 0], OneTriangle, [0, 0, 0]),
(
[
3_074_410_431_869_452_288,
4_611_615_649_951_645_696,
4_611_615_649_951_645_696,
],
TriangleArea,
[178_954_239, 268_431_360, 268_431_360],
),
([3 << 30, 0, 0], TriangleArea, [1 << 29, 0, 0]),
([3 << 30, 0, 0], OneTriangle, [-(1 << 30), 0, 0]),
(
[-(3 << 28), 1 << 28, 0],
OneTriangle,
[-(3 << 27), 1 << 27, 0],
),
];
for (normal, mode, expected) in cases {
assert_eq!(
scale_normal_to_prediction(normal, mode),
expected,
"{normal:?} {mode:?}"
);
}
}
#[cfg(feature = "encoder")]
#[test]
fn encoder_position_table_matches_the_generic_lookup() {
let corner_table = corner_table_from(&[[0, 1, 2], [2, 1, 3]]);
let vertex_to_data_map = [0, 1, -1, 3];
let entry_to_point_id_map = [0u32, 1, 2, 7];
let map = EntryToPointIdMap::from_u32_slice(&entry_to_point_id_map);
let num_points = 4;
for data_type in [DataType::Int32, DataType::Uint32, DataType::Int16] {
let mut position_attribute = PointAttribute::new();
position_attribute.init(
GeometryAttributeType::Position,
3,
data_type,
false,
num_points,
);
for p in 0..num_points {
for c in 0..3 {
let value = (10 * p + c) as i32 - 17;
let at = p * 3 + c;
if data_type == DataType::Int16 {
position_attribute
.buffer_mut()
.update(&(value as i16).to_le_bytes(), Some(at * 2));
} else {
position_attribute
.buffer_mut()
.update(&value.to_le_bytes(), Some(at * 4));
}
}
}
let parent = PredictionParent::portable(&position_attribute).expect("portable");
let table =
EncoderCornerPositions::new(&corner_table, &vertex_to_data_map, map, parent);
let expected: Vec<_> = vertex_to_data_map
.iter()
.map(|&data_id| EncoderCornerPositions::lookup(map, parent, data_id))
.collect();
assert_eq!(table.cache, expected, "{data_type:?}");
let real = expected.iter().filter(|&&p| p != [0, 0, 0]).count();
assert_eq!(real, 2, "{data_type:?} {expected:?}");
}
}
#[cfg(feature = "encoder")]
#[test]
fn mesh_geometric_normal_decoder_predicts_what_the_encoder_did_past_i64() {
let ring = 6u32;
let faces: Vec<[u32; 3]> = (1..=ring).map(|i| [0, i, i % ring + 1]).collect();
let corner_table = corner_table_from(&faces);
let num_points = (ring + 1) as usize;
let identity: Vec<i32> = (0..num_points as i32).collect();
let data_to_corner_map: Vec<u32> = (0..num_points as u32).collect();
let mut mesh_data = MeshPredictionSchemeData::new();
mesh_data.set(&corner_table, &data_to_corner_map, &identity);
let mut position_attribute = PointAttribute::new();
position_attribute.init(
GeometryAttributeType::Position,
3,
DataType::Int32,
false,
num_points,
);
let (lo, hi) = (i32::MIN + 7, i32::MAX - 3);
let points: [[i32; 3]; 7] = [
[lo, hi, lo],
[hi, lo, hi],
[hi, hi, lo],
[lo, lo, hi],
[hi, lo, lo],
[lo, hi, hi],
[hi, hi, hi],
];
for (p, point) in points.iter().enumerate() {
for (c, value) in point.iter().enumerate() {
position_attribute
.buffer_mut()
.update(&value.to_le_bytes(), Some((p * 3 + c) * 4));
}
}
let parent = PredictionParent::portable(&position_attribute).expect("portable");
let entry_to_point_id_map: Vec<u32> = (0..num_points as u32).collect();
let map = EntryToPointIdMap::from_u32_slice(&entry_to_point_id_map);
let mut overflowed = false;
for mode in [
NormalPredictionMode::TriangleArea,
NormalPredictionMode::OneTriangle,
] {
let mut decoded = CornerPositions::new(&corner_table, &identity, map, parent);
let mut encoded = EncoderCornerPositions::new(&corner_table, &identity, map, parent);
for corner in 0..corner_table.num_corners() as u32 {
let corner = CornerIndex(corner);
let mut from_decoder = [0i32; 3];
let mut from_encoder = [0i32; 3];
compute_predicted_value(&mut decoded, mode, corner, &mut from_decoder);
compute_encoder_predicted_value(&mut encoded, mode, corner, &mut from_encoder);
assert_eq!(from_decoder, from_encoder, "{mode:?} {corner:?}");
let cent = encoded.get(corner);
let next = encoded.get(corner_table.next(corner));
let prev = encoded.get(corner_table.previous(corner));
let d = |a: [i64; 3]| [a[0] - cent[0], a[1] - cent[1], a[2] - cent[2]];
let (n, p) = (d(next), d(prev));
overflowed |= n[1].checked_mul(p[2]).is_none() || n[2].checked_mul(p[1]).is_none();
}
}
assert!(overflowed, "fixture must leave i64");
}
fn corner_table_from(faces: &[[u32; 3]]) -> CornerTable {
let faces: Vec<[crate::geometry_indices::VertexIndex; 3]> = faces
.iter()
.map(|f| f.map(crate::geometry_indices::VertexIndex))
.collect();
let mut corner_table = CornerTable::new(faces.len());
assert!(corner_table.init(&faces), "corner table builds");
corner_table
}
fn corners_of(corner_table: &CornerTable, v: u32) -> std::collections::BTreeSet<u32> {
(0..corner_table.num_corners() as u32)
.filter(|&c| corner_table.vertex(CornerIndex(c)).0 == v)
.collect()
}
fn walk_from(corner_table: &CornerTable, start: CornerIndex) -> Vec<u32> {
let mut visited = Vec::new();
let mut cit = VertexCornersIterator::new(corner_table, start);
while !cit.end() {
visited.push(cit.corner().0);
cit.next(corner_table);
assert!(visited.len() <= corner_table.num_corners(), "walk loops");
}
visited
}
#[test]
fn vertex_corners_iterator_covers_the_fan_once_from_every_start() {
let closed = corner_table_from(&[[0, 1, 2], [0, 2, 3], [0, 3, 1]]);
let open = corner_table_from(&[[0, 1, 2], [0, 2, 3], [0, 3, 4]]);
for (name, corner_table) in [("closed", &closed), ("open", &open)] {
let expected = corners_of(corner_table, 0);
assert!(expected.len() == 3, "{name} fan has three corners at v0");
for &start in &expected {
let visited = walk_from(corner_table, CornerIndex(start));
let unique: std::collections::BTreeSet<_> = visited.iter().copied().collect();
assert_eq!(
unique, expected,
"{name} fan from corner {start}: visited {visited:?}"
);
assert_eq!(
unique.len(),
visited.len(),
"{name} fan from corner {start} visits a corner twice: {visited:?}"
);
}
}
}
}