Path: blob/main/crates/bevy_gltf/src/vertex_attributes.rs
6595 views
use bevy_mesh::{Mesh, MeshVertexAttribute, VertexAttributeValues as Values, VertexFormat};1use bevy_platform::collections::HashMap;2use gltf::{3accessor::{DataType, Dimensions},4mesh::util::{ReadColors, ReadJoints, ReadTexCoords, ReadWeights},5};6use thiserror::Error;78use crate::convert_coordinates::ConvertCoordinates;910/// Represents whether integer data requires normalization11#[derive(Copy, Clone)]12struct Normalization(bool);1314impl Normalization {15fn apply_either<T, U>(16self,17value: T,18normalized_ctor: impl Fn(T) -> U,19unnormalized_ctor: impl Fn(T) -> U,20) -> U {21if self.0 {22normalized_ctor(value)23} else {24unnormalized_ctor(value)25}26}27}2829/// An error that occurs when accessing buffer data30#[derive(Error, Debug)]31pub(crate) enum AccessFailed {32#[error("Malformed vertex attribute data")]33MalformedData,34#[error("Unsupported vertex attribute format")]35UnsupportedFormat,36}3738/// Helper for reading buffer data39struct BufferAccessor<'a> {40accessor: gltf::Accessor<'a>,41buffer_data: &'a Vec<Vec<u8>>,42normalization: Normalization,43}4445impl<'a> BufferAccessor<'a> {46/// Creates an iterator over the elements in this accessor47fn iter<T: gltf::accessor::Item>(self) -> Result<gltf::accessor::Iter<'a, T>, AccessFailed> {48gltf::accessor::Iter::new(self.accessor, |buffer: gltf::Buffer| {49self.buffer_data.get(buffer.index()).map(Vec::as_slice)50})51.ok_or(AccessFailed::MalformedData)52}5354/// Applies the element iterator to a constructor or fails if normalization is required55fn with_no_norm<T: gltf::accessor::Item, U>(56self,57ctor: impl Fn(gltf::accessor::Iter<'a, T>) -> U,58) -> Result<U, AccessFailed> {59if self.normalization.0 {60return Err(AccessFailed::UnsupportedFormat);61}62self.iter().map(ctor)63}6465/// Applies the element iterator and the normalization flag to a constructor66fn with_norm<T: gltf::accessor::Item, U>(67self,68ctor: impl Fn(gltf::accessor::Iter<'a, T>, Normalization) -> U,69) -> Result<U, AccessFailed> {70let normalized = self.normalization;71self.iter().map(|v| ctor(v, normalized))72}73}7475/// An enum of the iterators user by different vertex attribute formats76enum VertexAttributeIter<'a> {77// For reading native WGPU formats78F32(gltf::accessor::Iter<'a, f32>),79U32(gltf::accessor::Iter<'a, u32>),80F32x2(gltf::accessor::Iter<'a, [f32; 2]>),81U32x2(gltf::accessor::Iter<'a, [u32; 2]>),82F32x3(gltf::accessor::Iter<'a, [f32; 3]>),83U32x3(gltf::accessor::Iter<'a, [u32; 3]>),84F32x4(gltf::accessor::Iter<'a, [f32; 4]>),85U32x4(gltf::accessor::Iter<'a, [u32; 4]>),86S16x2(gltf::accessor::Iter<'a, [i16; 2]>, Normalization),87U16x2(gltf::accessor::Iter<'a, [u16; 2]>, Normalization),88S16x4(gltf::accessor::Iter<'a, [i16; 4]>, Normalization),89U16x4(gltf::accessor::Iter<'a, [u16; 4]>, Normalization),90S8x2(gltf::accessor::Iter<'a, [i8; 2]>, Normalization),91U8x2(gltf::accessor::Iter<'a, [u8; 2]>, Normalization),92S8x4(gltf::accessor::Iter<'a, [i8; 4]>, Normalization),93U8x4(gltf::accessor::Iter<'a, [u8; 4]>, Normalization),94// Additional on-disk formats used for RGB colors95U16x3(gltf::accessor::Iter<'a, [u16; 3]>, Normalization),96U8x3(gltf::accessor::Iter<'a, [u8; 3]>, Normalization),97}9899impl<'a> VertexAttributeIter<'a> {100/// Creates an iterator over the elements in a vertex attribute accessor101fn from_accessor(102accessor: gltf::Accessor<'a>,103buffer_data: &'a Vec<Vec<u8>>,104) -> Result<VertexAttributeIter<'a>, AccessFailed> {105let normalization = Normalization(accessor.normalized());106let format = (accessor.data_type(), accessor.dimensions());107let acc = BufferAccessor {108accessor,109buffer_data,110normalization,111};112match format {113(DataType::F32, Dimensions::Scalar) => acc.with_no_norm(VertexAttributeIter::F32),114(DataType::U32, Dimensions::Scalar) => acc.with_no_norm(VertexAttributeIter::U32),115(DataType::F32, Dimensions::Vec2) => acc.with_no_norm(VertexAttributeIter::F32x2),116(DataType::U32, Dimensions::Vec2) => acc.with_no_norm(VertexAttributeIter::U32x2),117(DataType::F32, Dimensions::Vec3) => acc.with_no_norm(VertexAttributeIter::F32x3),118(DataType::U32, Dimensions::Vec3) => acc.with_no_norm(VertexAttributeIter::U32x3),119(DataType::F32, Dimensions::Vec4) => acc.with_no_norm(VertexAttributeIter::F32x4),120(DataType::U32, Dimensions::Vec4) => acc.with_no_norm(VertexAttributeIter::U32x4),121(DataType::I16, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::S16x2),122(DataType::U16, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::U16x2),123(DataType::I16, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::S16x4),124(DataType::U16, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::U16x4),125(DataType::I8, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::S8x2),126(DataType::U8, Dimensions::Vec2) => acc.with_norm(VertexAttributeIter::U8x2),127(DataType::I8, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::S8x4),128(DataType::U8, Dimensions::Vec4) => acc.with_norm(VertexAttributeIter::U8x4),129(DataType::U16, Dimensions::Vec3) => acc.with_norm(VertexAttributeIter::U16x3),130(DataType::U8, Dimensions::Vec3) => acc.with_norm(VertexAttributeIter::U8x3),131_ => Err(AccessFailed::UnsupportedFormat),132}133}134135/// Materializes values for any supported format of vertex attribute136fn into_any_values(self, convert_coordinates: bool) -> Result<Values, AccessFailed> {137match self {138VertexAttributeIter::F32(it) => Ok(Values::Float32(it.collect())),139VertexAttributeIter::U32(it) => Ok(Values::Uint32(it.collect())),140VertexAttributeIter::F32x2(it) => Ok(Values::Float32x2(it.collect())),141VertexAttributeIter::U32x2(it) => Ok(Values::Uint32x2(it.collect())),142VertexAttributeIter::F32x3(it) => Ok(if convert_coordinates {143// The following f32x3 values need to be converted to the correct coordinate system144// - Positions145// - Normals146//147// See <https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview>148Values::Float32x3(it.map(ConvertCoordinates::convert_coordinates).collect())149} else {150Values::Float32x3(it.collect())151}),152VertexAttributeIter::U32x3(it) => Ok(Values::Uint32x3(it.collect())),153VertexAttributeIter::F32x4(it) => Ok(if convert_coordinates {154// The following f32x4 values need to be converted to the correct coordinate system155// - Tangents156//157// See <https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview>158Values::Float32x4(it.map(ConvertCoordinates::convert_coordinates).collect())159} else {160Values::Float32x4(it.collect())161}),162VertexAttributeIter::U32x4(it) => Ok(Values::Uint32x4(it.collect())),163VertexAttributeIter::S16x2(it, n) => {164Ok(n.apply_either(it.collect(), Values::Snorm16x2, Values::Sint16x2))165}166VertexAttributeIter::U16x2(it, n) => {167Ok(n.apply_either(it.collect(), Values::Unorm16x2, Values::Uint16x2))168}169VertexAttributeIter::S16x4(it, n) => {170Ok(n.apply_either(it.collect(), Values::Snorm16x4, Values::Sint16x4))171}172VertexAttributeIter::U16x4(it, n) => {173Ok(n.apply_either(it.collect(), Values::Unorm16x4, Values::Uint16x4))174}175VertexAttributeIter::S8x2(it, n) => {176Ok(n.apply_either(it.collect(), Values::Snorm8x2, Values::Sint8x2))177}178VertexAttributeIter::U8x2(it, n) => {179Ok(n.apply_either(it.collect(), Values::Unorm8x2, Values::Uint8x2))180}181VertexAttributeIter::S8x4(it, n) => {182Ok(n.apply_either(it.collect(), Values::Snorm8x4, Values::Sint8x4))183}184VertexAttributeIter::U8x4(it, n) => {185Ok(n.apply_either(it.collect(), Values::Unorm8x4, Values::Uint8x4))186}187_ => Err(AccessFailed::UnsupportedFormat),188}189}190191/// Materializes RGBA values, converting compatible formats to Float32x4192fn into_rgba_values(self) -> Result<Values, AccessFailed> {193match self {194VertexAttributeIter::U8x3(it, Normalization(true)) => Ok(Values::Float32x4(195ReadColors::RgbU8(it).into_rgba_f32().collect(),196)),197VertexAttributeIter::U16x3(it, Normalization(true)) => Ok(Values::Float32x4(198ReadColors::RgbU16(it).into_rgba_f32().collect(),199)),200VertexAttributeIter::F32x3(it) => Ok(Values::Float32x4(201ReadColors::RgbF32(it).into_rgba_f32().collect(),202)),203VertexAttributeIter::U8x4(it, Normalization(true)) => Ok(Values::Float32x4(204ReadColors::RgbaU8(it).into_rgba_f32().collect(),205)),206VertexAttributeIter::U16x4(it, Normalization(true)) => Ok(Values::Float32x4(207ReadColors::RgbaU16(it).into_rgba_f32().collect(),208)),209s => s.into_any_values(false),210}211}212213/// Materializes joint index values, converting compatible formats to Uint16x4214fn into_joint_index_values(self) -> Result<Values, AccessFailed> {215match self {216VertexAttributeIter::U8x4(it, Normalization(false)) => {217Ok(Values::Uint16x4(ReadJoints::U8(it).into_u16().collect()))218}219s => s.into_any_values(false),220}221}222223/// Materializes joint weight values, converting compatible formats to Float32x4224fn into_joint_weight_values(self) -> Result<Values, AccessFailed> {225match self {226VertexAttributeIter::U8x4(it, Normalization(true)) => {227Ok(Values::Float32x4(ReadWeights::U8(it).into_f32().collect()))228}229VertexAttributeIter::U16x4(it, Normalization(true)) => {230Ok(Values::Float32x4(ReadWeights::U16(it).into_f32().collect()))231}232s => s.into_any_values(false),233}234}235236/// Materializes texture coordinate values, converting compatible formats to Float32x2237fn into_tex_coord_values(self) -> Result<Values, AccessFailed> {238match self {239VertexAttributeIter::U8x2(it, Normalization(true)) => Ok(Values::Float32x2(240ReadTexCoords::U8(it).into_f32().collect(),241)),242VertexAttributeIter::U16x2(it, Normalization(true)) => Ok(Values::Float32x2(243ReadTexCoords::U16(it).into_f32().collect(),244)),245s => s.into_any_values(false),246}247}248}249250enum ConversionMode {251Any,252Rgba,253JointIndex,254JointWeight,255TexCoord,256}257258#[derive(Error, Debug)]259pub(crate) enum ConvertAttributeError {260#[error("Vertex attribute {0} has format {1:?} but expected {3:?} for target attribute {2}")]261WrongFormat(String, VertexFormat, String, VertexFormat),262#[error("{0} in accessor {1}")]263AccessFailed(AccessFailed, usize),264#[error("Unknown vertex attribute {0}")]265UnknownName(String),266}267268pub(crate) fn convert_attribute(269semantic: gltf::Semantic,270accessor: gltf::Accessor,271buffer_data: &Vec<Vec<u8>>,272custom_vertex_attributes: &HashMap<Box<str>, MeshVertexAttribute>,273convert_coordinates: bool,274) -> Result<(MeshVertexAttribute, Values), ConvertAttributeError> {275if let Some((attribute, conversion, convert_coordinates)) = match &semantic {276gltf::Semantic::Positions => Some((277Mesh::ATTRIBUTE_POSITION,278ConversionMode::Any,279convert_coordinates,280)),281gltf::Semantic::Normals => Some((282Mesh::ATTRIBUTE_NORMAL,283ConversionMode::Any,284convert_coordinates,285)),286gltf::Semantic::Tangents => Some((287Mesh::ATTRIBUTE_TANGENT,288ConversionMode::Any,289convert_coordinates,290)),291gltf::Semantic::Colors(0) => Some((Mesh::ATTRIBUTE_COLOR, ConversionMode::Rgba, false)),292gltf::Semantic::TexCoords(0) => {293Some((Mesh::ATTRIBUTE_UV_0, ConversionMode::TexCoord, false))294}295gltf::Semantic::TexCoords(1) => {296Some((Mesh::ATTRIBUTE_UV_1, ConversionMode::TexCoord, false))297}298gltf::Semantic::Joints(0) => Some((299Mesh::ATTRIBUTE_JOINT_INDEX,300ConversionMode::JointIndex,301false,302)),303gltf::Semantic::Weights(0) => Some((304Mesh::ATTRIBUTE_JOINT_WEIGHT,305ConversionMode::JointWeight,306false,307)),308gltf::Semantic::Extras(name) => custom_vertex_attributes309.get(name.as_str())310.map(|attr| (*attr, ConversionMode::Any, false)),311_ => None,312} {313let raw_iter = VertexAttributeIter::from_accessor(accessor.clone(), buffer_data);314let converted_values = raw_iter.and_then(|iter| match conversion {315ConversionMode::Any => iter.into_any_values(convert_coordinates),316ConversionMode::Rgba => iter.into_rgba_values(),317ConversionMode::TexCoord => iter.into_tex_coord_values(),318ConversionMode::JointIndex => iter.into_joint_index_values(),319ConversionMode::JointWeight => iter.into_joint_weight_values(),320});321match converted_values {322Ok(values) => {323let loaded_format = VertexFormat::from(&values);324if attribute.format == loaded_format {325Ok((attribute, values))326} else {327Err(ConvertAttributeError::WrongFormat(328semantic.to_string(),329loaded_format,330attribute.name.to_string(),331attribute.format,332))333}334}335Err(err) => Err(ConvertAttributeError::AccessFailed(err, accessor.index())),336}337} else {338Err(ConvertAttributeError::UnknownName(semantic.to_string()))339}340}341342343