use std::borrow::Cow;
use crate::buffer::helpers::slice_zeroes;
use crate::gltf::buffers::accessor::accessor_to_bytes;
use crate::gltf::buffers::{
MeshBufferGeometryMorphInfoWithOffset, MeshBufferMaterialMorphInfoWithOffset,
};
use crate::gltf::error::{AwsmGltfError, Result};
pub(super) fn convert_morph_targets(
primitive: &gltf::Primitive,
buffers: &[Vec<u8>],
vertex_count: usize,
geometry_morph_bytes: &mut Vec<u8>, _material_morph_bytes: &mut Vec<u8>, ) -> Result<(
Option<MeshBufferGeometryMorphInfoWithOffset>,
Option<MeshBufferMaterialMorphInfoWithOffset>,
)> {
let has_any_position_morph = primitive
.morph_targets()
.any(|morph_target| morph_target.positions().is_some());
let has_any_normal_morph = primitive
.morph_targets()
.any(|morph_target| morph_target.normals().is_some());
let has_any_tangent_morph = primitive
.morph_targets()
.any(|morph_target| morph_target.tangents().is_some());
if !has_any_position_morph && !has_any_normal_morph && !has_any_tangent_morph {
return Ok((None, None));
}
#[derive(Default)]
struct MorphTargetBufferData<'a> {
positions: Option<Cow<'a, [u8]>>, normals: Option<Cow<'a, [u8]>>, tangents: Option<Cow<'a, [u8]>>, }
let mut morph_targets_buffer_data = Vec::new();
for morph_target in primitive.morph_targets() {
let mut morph_target_buffer_data = MorphTargetBufferData::default();
if let Some(accessor) = morph_target.positions() {
morph_target_buffer_data.positions = Some(accessor_to_bytes(&accessor, buffers)?);
}
if let Some(accessor) = morph_target.normals() {
morph_target_buffer_data.normals = Some(accessor_to_bytes(&accessor, buffers)?);
}
if let Some(accessor) = morph_target.tangents() {
morph_target_buffer_data.tangents = Some(accessor_to_bytes(&accessor, buffers)?);
}
morph_targets_buffer_data.push(morph_target_buffer_data);
}
let targets_len = morph_targets_buffer_data.len();
let _original_vertex_count = primitive
.attributes()
.next()
.map(|(_, accessor)| accessor.count())
.unwrap_or(0);
let geometry_morph_info = if has_any_position_morph
|| has_any_normal_morph
|| has_any_tangent_morph
{
let geometry_values_offset = geometry_morph_bytes.len();
let size_per_target_per_vertex = 40;
let vertex_stride_size = size_per_target_per_vertex * targets_len;
for vertex_index in 0..vertex_count {
for morph_target_buffer_data in &morph_targets_buffer_data {
match &morph_target_buffer_data.positions {
Some(position_data) => {
let data_byte_offset = vertex_index * 12;
if data_byte_offset + 12 > position_data.len() {
return Err(AwsmGltfError::ConstructNormals(format!(
"Position morph data out of bounds for vertex {}",
vertex_index
)));
}
let position_bytes =
&position_data[data_byte_offset..data_byte_offset + 12];
geometry_morph_bytes.extend_from_slice(position_bytes);
}
None => {
geometry_morph_bytes.extend_from_slice(slice_zeroes(12));
}
}
match &morph_target_buffer_data.normals {
Some(normal_data) => {
let data_byte_offset = vertex_index * 12;
if data_byte_offset + 12 > normal_data.len() {
return Err(AwsmGltfError::ConstructNormals(format!(
"Normal morph data out of bounds for vertex {}",
vertex_index
)));
}
let normal_bytes = &normal_data[data_byte_offset..data_byte_offset + 12];
geometry_morph_bytes.extend_from_slice(normal_bytes);
}
None => {
geometry_morph_bytes.extend_from_slice(slice_zeroes(12));
}
}
match &morph_target_buffer_data.tangents {
Some(tangent_data) => {
let data_byte_offset = vertex_index * 12; if data_byte_offset + 12 > tangent_data.len() {
return Err(AwsmGltfError::ConstructNormals(format!(
"Tangent morph data out of bounds for vertex {}",
vertex_index
)));
}
let tangent_bytes = &tangent_data[data_byte_offset..data_byte_offset + 12];
geometry_morph_bytes.extend_from_slice(tangent_bytes);
geometry_morph_bytes.extend_from_slice(&[0u8; 4]);
}
None => {
geometry_morph_bytes.extend_from_slice(slice_zeroes(16));
}
}
}
}
let geometry_values_size = geometry_morph_bytes.len() - geometry_values_offset;
Some(MeshBufferGeometryMorphInfoWithOffset {
targets_len,
vertex_stride_size,
values_size: geometry_values_size,
values_offset: geometry_values_offset,
})
} else {
None
};
Ok((geometry_morph_info, None))
}