#[cfg(feature = "encoder")]
use crate::compression_config::EncodedGeometryType;
#[cfg(feature = "encoder")]
use crate::encoder_options::EncoderOptions;
#[cfg(feature = "encoder")]
use crate::geometry_attribute::GeometryAttributeType;
#[cfg(feature = "encoder")]
use crate::point_cloud_encoder::GeometryEncoder;
#[cfg(feature = "encoder")]
use crate::prediction_scheme::PredictionSchemeMethod;
#[cfg(feature = "encoder")]
fn introduced_at(method: PredictionSchemeMethod) -> (u8, u8) {
match method {
PredictionSchemeMethod::MeshPredictionConstrainedMultiParallelogram => (1, 2),
PredictionSchemeMethod::MeshPredictionTexCoordsPortable
| PredictionSchemeMethod::MeshPredictionGeometricNormal => (2, 0),
PredictionSchemeMethod::None
| PredictionSchemeMethod::Undefined
| PredictionSchemeMethod::Difference
| PredictionSchemeMethod::MeshPredictionParallelogram
| PredictionSchemeMethod::MeshPredictionMultiParallelogram
| PredictionSchemeMethod::MeshPredictionTexCoordsDeprecated => (1, 1),
}
}
#[cfg(feature = "encoder")]
pub(crate) fn downgrade_without_position_parent(
method: PredictionSchemeMethod,
encoder: &dyn GeometryEncoder,
options: &crate::encoder_options::EncoderOptions,
) -> PredictionSchemeMethod {
let predicts_from_position = matches!(
method,
PredictionSchemeMethod::MeshPredictionGeometricNormal
| PredictionSchemeMethod::MeshPredictionTexCoordsPortable
| PredictionSchemeMethod::MeshPredictionTexCoordsDeprecated
);
if !predicts_from_position {
return method;
}
if encoder.point_cloud().and_then(position_parent).is_none() {
return PredictionSchemeMethod::Difference;
}
let (major, minor) = options.get_version();
if crate::version::binds_portable_parent_only(major, minor)
&& portable_position_parent(encoder).is_none()
{
return PredictionSchemeMethod::Difference;
}
if single_connectivity(options) {
return PredictionSchemeMethod::Difference;
}
method
}
#[cfg(feature = "encoder")]
fn portable_position_parent(
encoder: &dyn crate::point_cloud_encoder::GeometryEncoder,
) -> Option<crate::portable_attribute::PredictionParent<'_>> {
let pc = encoder.point_cloud()?;
let att_id = pc.named_attribute_id(GeometryAttributeType::Position);
if att_id < 0 {
return None;
}
let att = encoder.get_portable_attribute(att_id)?;
let parent = crate::portable_attribute::PredictionParent::portable(att).ok()?;
(parent.attribute_type() == GeometryAttributeType::Position && parent.num_components() == 3)
.then_some(parent)
}
#[cfg(feature = "encoder")]
fn single_connectivity(options: &crate::encoder_options::EncoderOptions) -> bool {
match options.get_global_int("split_mesh_on_seams", -1) {
-1 => options.get_speed() >= 6,
explicit => explicit != 0,
}
}
#[cfg(feature = "encoder")]
fn position_parent(
pc: &crate::point_cloud::PointCloud,
) -> Option<&crate::geometry_attribute::PointAttribute> {
pc.named_attribute(GeometryAttributeType::Position)
.filter(|att| att.num_components() == 3)
}
#[cfg(feature = "encoder")]
fn era_substitute(method: PredictionSchemeMethod) -> PredictionSchemeMethod {
match method {
PredictionSchemeMethod::MeshPredictionGeometricNormal => PredictionSchemeMethod::Difference,
PredictionSchemeMethod::MeshPredictionTexCoordsPortable
| PredictionSchemeMethod::MeshPredictionConstrainedMultiParallelogram => {
PredictionSchemeMethod::MeshPredictionParallelogram
}
other => other,
}
}
#[cfg(feature = "encoder")]
fn downgrade_to_bitstream_era(
method: PredictionSchemeMethod,
options: &EncoderOptions,
) -> PredictionSchemeMethod {
let (major, minor) = options.get_version();
if major == 0 {
return method;
}
if crate::version::version_less_than(major, minor, introduced_at(method)) {
return era_substitute(method);
}
method
}
#[cfg(feature = "encoder")]
pub fn select_prediction_method(
att_id: i32,
options: &EncoderOptions,
encoder: &dyn GeometryEncoder,
) -> PredictionSchemeMethod {
downgrade_to_bitstream_era(
select_prediction_method_for_newest(att_id, options, encoder),
options,
)
}
#[cfg(feature = "encoder")]
fn select_prediction_method_for_newest(
att_id: i32,
options: &EncoderOptions,
encoder: &dyn GeometryEncoder,
) -> PredictionSchemeMethod {
let speed = options.get_speed();
if speed >= 10 {
return PredictionSchemeMethod::Difference;
}
if encoder.get_geometry_type() == EncodedGeometryType::TriangularMesh {
let att_quant = options.get_attribute_int(att_id, "quantization_bits", -1);
let pc = encoder.point_cloud().unwrap(); let att = pc.attribute(att_id);
if att_quant != -1
&& att.attribute_type() == GeometryAttributeType::TexCoord
&& att.num_components() == 2
{
let pos_att = position_parent(pc);
let mut is_pos_att_valid = false;
if let Some(pos_att) = pos_att {
if pos_att.data_type().is_integral() {
is_pos_att_valid = true;
} else {
let pos_att_id = pc.named_attribute_id(GeometryAttributeType::Position);
let pos_quant = options.get_attribute_int(pos_att_id, "quantization_bits", -1);
if pos_quant > 0 && pos_quant <= 21 && 2 * pos_quant + att_quant < 64 {
is_pos_att_valid = true;
}
}
}
if is_pos_att_valid && speed < 4 {
return PredictionSchemeMethod::MeshPredictionTexCoordsPortable;
}
}
if att.attribute_type() == GeometryAttributeType::Normal {
if speed < 4 {
let pos_att_id = pc.named_attribute_id(GeometryAttributeType::Position);
if let Some(pos_att) = position_parent(pc) {
if pos_att.data_type().is_integral()
|| options.get_attribute_int(pos_att_id, "quantization_bits", -1) > 0
{
return PredictionSchemeMethod::MeshPredictionGeometricNormal;
}
}
}
return PredictionSchemeMethod::Difference;
}
if speed >= 8 {
return PredictionSchemeMethod::Difference;
}
if speed >= 2 || pc.num_points() < 40 {
return PredictionSchemeMethod::MeshPredictionParallelogram;
}
return PredictionSchemeMethod::MeshPredictionConstrainedMultiParallelogram;
}
PredictionSchemeMethod::Difference
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compression_config::EncodedGeometryType;
use crate::corner_table::CornerTable;
use crate::draco_types::DataType;
use crate::geometry_attribute::{GeometryAttributeType, PointAttribute};
use crate::mesh::Mesh;
use crate::point_cloud::PointCloud;
struct MockGeometryEncoder {
point_cloud: PointCloud,
options: EncoderOptions,
geometry_type: EncodedGeometryType,
encoding_method: Option<i32>,
}
impl GeometryEncoder for MockGeometryEncoder {
fn point_cloud(&self) -> Option<&PointCloud> {
Some(&self.point_cloud)
}
fn mesh(&self) -> Option<&Mesh> {
None
}
fn corner_table(&self) -> Option<&CornerTable> {
None
}
fn options(&self) -> &EncoderOptions {
&self.options
}
fn get_geometry_type(&self) -> EncodedGeometryType {
self.geometry_type
}
fn get_encoding_method(&self) -> Option<i32> {
self.encoding_method
}
}
fn make_attribute(
attribute_type: GeometryAttributeType,
data_type: DataType,
) -> PointAttribute {
let mut attribute = PointAttribute::new();
attribute.init(attribute_type, 3, data_type, false, 1);
attribute
}
#[test]
fn sequential_mesh_still_selects_mesh_prediction_schemes() {
let mut point_cloud = PointCloud::new();
point_cloud.set_num_points(64);
point_cloud.add_attribute(make_attribute(
GeometryAttributeType::Position,
DataType::Float32,
));
let generic_att_id = point_cloud.add_attribute(make_attribute(
GeometryAttributeType::Generic,
DataType::Float32,
));
let mut options = EncoderOptions::new();
options.set_global_int("encoding_speed", 5);
let encoder = MockGeometryEncoder {
point_cloud,
options: options.clone(),
geometry_type: EncodedGeometryType::TriangularMesh,
encoding_method: Some(0),
};
assert_eq!(
select_prediction_method(generic_att_id, &options, &encoder),
PredictionSchemeMethod::MeshPredictionParallelogram
);
}
#[test]
fn normal_prediction_matches_cpp_when_positions_are_quantized() {
let mut point_cloud = PointCloud::new();
point_cloud.set_num_points(64);
let pos_att_id = point_cloud.add_attribute(make_attribute(
GeometryAttributeType::Position,
DataType::Float32,
));
let normal_att_id = point_cloud.add_attribute(make_attribute(
GeometryAttributeType::Normal,
DataType::Float32,
));
let mut options = EncoderOptions::new();
options.set_global_int("encoding_speed", 1);
options.set_attribute_int(pos_att_id, "quantization_bits", 14);
let encoder = MockGeometryEncoder {
point_cloud,
options: options.clone(),
geometry_type: EncodedGeometryType::TriangularMesh,
encoding_method: Some(0),
};
assert_eq!(
select_prediction_method(normal_att_id, &options, &encoder),
PredictionSchemeMethod::MeshPredictionGeometricNormal
);
}
#[test]
fn a_normal_is_not_predicted_from_position_below_2_0() {
for version in [(1, 1), (1, 2), (1, 3)] {
let mut point_cloud = PointCloud::new();
point_cloud.set_num_points(64);
let pos_att_id = point_cloud.add_attribute(make_attribute(
GeometryAttributeType::Position,
DataType::Float32,
));
let normal_att_id = point_cloud.add_attribute(make_attribute(
GeometryAttributeType::Normal,
DataType::Float32,
));
let mut options = EncoderOptions::new();
options.set_global_int("encoding_speed", 1);
options.set_attribute_int(pos_att_id, "quantization_bits", 14);
options.set_version(version.0, version.1);
let encoder = MockGeometryEncoder {
point_cloud,
options: options.clone(),
geometry_type: EncodedGeometryType::TriangularMesh,
encoding_method: Some(0),
};
assert_eq!(
select_prediction_method(normal_att_id, &options, &encoder),
PredictionSchemeMethod::Difference,
"bitstream {}.{} predates MESH_PREDICTION_GEOMETRIC_NORMAL",
version.0,
version.1
);
}
}
#[test]
fn no_substitute_outlives_the_version_it_is_written_into() {
const EVERY_METHOD: [PredictionSchemeMethod; 9] = [
PredictionSchemeMethod::None,
PredictionSchemeMethod::Undefined,
PredictionSchemeMethod::Difference,
PredictionSchemeMethod::MeshPredictionParallelogram,
PredictionSchemeMethod::MeshPredictionMultiParallelogram,
PredictionSchemeMethod::MeshPredictionTexCoordsDeprecated,
PredictionSchemeMethod::MeshPredictionConstrainedMultiParallelogram,
PredictionSchemeMethod::MeshPredictionTexCoordsPortable,
PredictionSchemeMethod::MeshPredictionGeometricNormal,
];
for target in [
crate::version::EncodeTarget::MeshEdgebreaker,
crate::version::EncodeTarget::MeshSequential,
crate::version::EncodeTarget::PointCloudSequential,
crate::version::EncodeTarget::PointCloudKdTree,
] {
for &(major, minor) in target.claimed_versions() {
let mut options = EncoderOptions::new();
options.set_version(major, minor);
for method in EVERY_METHOD {
let picked = downgrade_to_bitstream_era(method, &options);
let (at_major, at_minor) = introduced_at(picked);
assert!(
!crate::version::version_less_than(major, minor, (at_major, at_minor)),
"{target:?} at {major}.{minor} turns {method:?} into {picked:?}, \
which arrived only at {at_major}.{at_minor}"
);
}
}
}
}
}