diff --git a/benches/benches/bevy_render/compute_normals.rs b/benches/benches/bevy_render/compute_normals.rs index 74fcbfe7b11bd..105e0a58c71c6 100644 --- a/benches/benches/bevy_render/compute_normals.rs +++ b/benches/benches/bevy_render/compute_normals.rs @@ -4,8 +4,8 @@ use criterion::{criterion_group, Criterion}; use rand::random; use std::time::{Duration, Instant}; -use bevy_asset::RenderAssetUsages; -use bevy_mesh::{Indices, Mesh, PrimitiveTopology}; +use bevy_asset::{ExtractableAsset, RenderAssetUsages}; +use bevy_mesh::{Indices, Mesh, MeshExtractableData, PrimitiveTopology}; const GRID_SIZE: usize = 256; @@ -28,12 +28,12 @@ fn compute_normals(c: &mut Criterion) { .flat_map(|i| std::iter::repeat(i).zip(0..GRID_SIZE)) .map(|(i, j)| [i as f32, j as f32, random::()]) .collect::>(); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::MAIN_WORLD, + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_indices(indices.clone()), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_indices(indices.clone()) + .with_asset_usage(RenderAssetUsages::MAIN_WORLD) }; c.bench_function("smooth_normals", |b| { @@ -41,6 +41,7 @@ fn compute_normals(c: &mut Criterion) { let mut total = Duration::default(); for _ in 0..iters { let mut mesh = new_mesh(); + let mesh = mesh.extractable_data_mut().unwrap(); black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); let start = Instant::now(); mesh.compute_smooth_normals(); @@ -57,6 +58,7 @@ fn compute_normals(c: &mut Criterion) { let mut total = Duration::default(); for _ in 0..iters { let mut mesh = new_mesh(); + let mesh = mesh.extractable_data_mut().unwrap(); black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); let start = Instant::now(); mesh.compute_smooth_normals(); @@ -73,6 +75,7 @@ fn compute_normals(c: &mut Criterion) { let mut total = Duration::default(); for _ in 0..iters { let mut mesh = new_mesh(); + let mesh = mesh.extractable_data_mut().unwrap(); black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); let start = Instant::now(); mesh.compute_area_weighted_normals(); @@ -84,13 +87,14 @@ fn compute_normals(c: &mut Criterion) { }); }); - let new_mesh = || new_mesh().with_duplicated_vertices(); + let new_mesh = || new_mesh().with_extractable_data(|d| d.unwrap().with_duplicated_vertices()); c.bench_function("flat_normals", |b| { b.iter_custom(|iters| { let mut total = Duration::default(); for _ in 0..iters { let mut mesh = new_mesh(); + let mesh = mesh.extractable_data_mut().unwrap(); black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL)); let start = Instant::now(); mesh.compute_flat_normals(); diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 1c1a5a72dcc2e..8571196931bc9 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -168,7 +168,7 @@ pub mod prelude { #[doc(hidden)] pub use crate::{ Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetPlugin, AssetServer, Assets, - DirectAssetAccessExt, Handle, UntypedHandle, + DirectAssetAccessExt, ExtractableAsset, Handle, UntypedHandle, }; } diff --git a/crates/bevy_asset/src/render_asset.rs b/crates/bevy_asset/src/render_asset.rs index 90855e86e3389..b6b65a41b4b36 100644 --- a/crates/bevy_asset/src/render_asset.rs +++ b/crates/bevy_asset/src/render_asset.rs @@ -1,5 +1,8 @@ use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::Asset; bitflags::bitflags! { /// Defines where the asset will be used. @@ -50,3 +53,58 @@ impl Default for RenderAssetUsages { RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD } } + +/// Error returned when an asset due for extraction has already been extracted +#[derive(Debug, Error, Clone, Copy)] +pub enum AssetExtractionError { + #[error("The asset has already been extracted")] + AlreadyExtracted, + #[error("The asset type does not support extraction. To clone the asset to the renderworld, use `RenderAssetUsages::default()`")] + NoExtractionImplementation, +} + +/// Error returned when an asset due for extraction has already been extracted +#[derive(Debug, Error, Clone, Copy)] +pub enum ExtractableAssetAccessError { + #[error("The data has been extracted to the RenderWorld")] + ExtractedToRenderWorld, +} + +pub trait ExtractableAsset: Asset + Sized + Clone { + type Data; + + /// Take `self` and call `f` with previous gpu data, or error if it has been extracted, replace the data in place and returns the asset which can be extracted to the `RenderWorld`. + fn with_extractable_data( + mut self, + f: impl FnOnce(Result) -> Self::Data, + ) -> Self { + let prev_data = self.extract(); + let new_data = f(prev_data); + self.extractable_data_replace(new_data); + self + } + + /// Replace the data with a new value and return the old value, or `None` if it has been extracted. Then this asset can be re-extracted to the `RenderWorld`. + fn extractable_data_replace(&mut self, data: Self::Data) -> Option; + + /// Access the extractable data. Returns error if the data has been extracted. + fn extractable_data_ref(&self) -> Result<&Self::Data, ExtractableAssetAccessError>; + + /// Mutably access the extractable data. Returns error if the data has been extracted. + fn extractable_data_mut(&mut self) -> Result<&mut Self::Data, ExtractableAssetAccessError>; + + /// Extract the data and return it, or error if the data has been extracted. + fn extract(&mut self) -> Result; + + /// Make a copy of the asset to be moved to the `RenderWorld` / gpu. Heavy internal data (pixels, vertex attributes) + /// should be moved into the copy, leaving this asset with only metadata. + /// An error may be returned to indicate that the asset has already been extracted. + /// + /// This can be called in `RenderAsset::take_gpu_data`. + fn take_gpu_data(&mut self) -> Result { + let data = self.extract()?; + let mut new_asset = self.clone(); + new_asset.extractable_data_replace(data); + Ok(new_asset) + } +} diff --git a/crates/bevy_camera/src/primitives.rs b/crates/bevy_camera/src/primitives.rs index 9d2a5203c6669..213be91fdb153 100644 --- a/crates/bevy_camera/src/primitives.rs +++ b/crates/bevy_camera/src/primitives.rs @@ -1,5 +1,4 @@ -use core::borrow::Borrow; - +use bevy_asset::ExtractableAsset; use bevy_ecs::{component::Component, entity::EntityHashMap, reflect::ReflectComponent}; use bevy_math::{ bounding::{Aabb3d, BoundingVolume}, @@ -7,6 +6,7 @@ use bevy_math::{ }; use bevy_mesh::{Mesh, VertexAttributeValues}; use bevy_reflect::prelude::*; +use core::borrow::Borrow; pub trait MeshAabb { /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space @@ -23,8 +23,10 @@ impl MeshAabb for Mesh { return Some(aabb.into()); } - let Ok(VertexAttributeValues::Float32x3(values)) = - self.try_attribute(Mesh::ATTRIBUTE_POSITION) + let Some(VertexAttributeValues::Float32x3(values)) = self + .extractable_data_ref() + .ok() + .and_then(|d| d.attribute(Mesh::ATTRIBUTE_POSITION)) else { return None; }; diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 93681a5c199f4..1c044123a88cd 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -452,5 +452,6 @@ pub fn lut_placeholder() -> Image { texture_view_descriptor: None, asset_usage: RenderAssetUsages::RENDER_WORLD, copy_on_resize: false, + is_extracted_to_render_world: false, } } diff --git a/crates/bevy_gltf/src/loader/mod.rs b/crates/bevy_gltf/src/loader/mod.rs index 77339cfa95bef..9c4ffc2bd91cb 100644 --- a/crates/bevy_gltf/src/loader/mod.rs +++ b/crates/bevy_gltf/src/loader/mod.rs @@ -6,8 +6,8 @@ use async_lock::RwLock; #[cfg(feature = "bevy_animation")] use bevy_animation::{prelude::*, AnimatedBy, AnimationTargetId}; use bevy_asset::{ - io::Reader, AssetLoadError, AssetLoader, AssetPath, Handle, LoadContext, ParseAssetPathError, - ReadAssetBytesError, RenderAssetUsages, + io::Reader, AssetLoadError, AssetLoader, AssetPath, ExtractableAsset, Handle, LoadContext, + ParseAssetPathError, ReadAssetBytesError, RenderAssetUsages, }; use bevy_camera::{ primitives::Aabb, visibility::Visibility, Camera, Camera3d, OrthographicProjection, @@ -691,8 +691,8 @@ impl GltfLoader { }; let primitive_topology = primitive_topology(primitive.mode())?; - let mut mesh = Mesh::new(primitive_topology, settings.load_meshes); - + let mut mesh_asset = Mesh::new(primitive_topology, settings.load_meshes); + let mesh = mesh_asset.extractable_data_mut().unwrap(); // Read vertex attributes for (semantic, accessor) in primitive.attributes() { if [Semantic::Joints(0), Semantic::Weights(0)].contains(&semantic) { @@ -801,7 +801,8 @@ impl GltfLoader { }); } - let mesh_handle = load_context.add_labeled_asset(primitive_label.to_string(), mesh); + let mesh_handle = + load_context.add_labeled_asset(primitive_label.to_string(), mesh_asset); primitives.push(super::GltfPrimitive::new( &gltf_mesh, &primitive, diff --git a/crates/bevy_image/src/image.rs b/crates/bevy_image/src/image.rs index ad2db13e2b182..08c6008546d55 100644 --- a/crates/bevy_image/src/image.rs +++ b/crates/bevy_image/src/image.rs @@ -12,7 +12,9 @@ use bevy_reflect::TypePath; #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; -use bevy_asset::{uuid_handle, Asset, AssetApp, Assets, Handle, RenderAssetUsages}; +use bevy_asset::{ + uuid_handle, Asset, AssetApp, Assets, ExtractableAsset, Handle, RenderAssetUsages, +}; use bevy_color::{Color, ColorToComponents, Gray, LinearRgba, Srgba, Xyza}; use bevy_ecs::resource::Resource; use bevy_math::{AspectRatio, UVec2, UVec3, Vec2}; @@ -609,6 +611,50 @@ pub struct Image { pub asset_usage: RenderAssetUsages, /// Whether this image should be copied on the GPU when resized. pub copy_on_resize: bool, + /// Whether this image has been extracted to the render world. + pub is_extracted_to_render_world: bool, +} + +impl ExtractableAsset for Image { + type Data = Option>; + + fn extractable_data_replace(&mut self, data: Self::Data) -> Option { + let old_data = core::mem::replace(&mut self.data, data); + let old_data = if self.is_extracted_to_render_world { + None + } else { + Some(old_data) + }; + self.is_extracted_to_render_world = false; + old_data + } + + fn extractable_data_ref(&self) -> Result<&Self::Data, bevy_asset::ExtractableAssetAccessError> { + if self.is_extracted_to_render_world { + Err(bevy_asset::ExtractableAssetAccessError::ExtractedToRenderWorld) + } else { + Ok(&self.data) + } + } + + fn extractable_data_mut( + &mut self, + ) -> Result<&mut Self::Data, bevy_asset::ExtractableAssetAccessError> { + if self.is_extracted_to_render_world { + Err(bevy_asset::ExtractableAssetAccessError::ExtractedToRenderWorld) + } else { + Ok(&mut self.data) + } + } + + fn extract(&mut self) -> Result { + if self.is_extracted_to_render_world { + Err(bevy_asset::AssetExtractionError::AlreadyExtracted) + } else { + self.is_extracted_to_render_world = true; + Ok(self.data.take()) + } + } } /// Used in [`Image`], this determines what image sampler to use when rendering. The default setting, @@ -1049,6 +1095,7 @@ impl Image { texture_view_descriptor: None, asset_usage, copy_on_resize: false, + is_extracted_to_render_world: false, } } @@ -1180,6 +1227,7 @@ impl Image { }), asset_usage: RenderAssetUsages::default(), copy_on_resize: true, + is_extracted_to_render_world: false, } } diff --git a/crates/bevy_image/src/serialized_image.rs b/crates/bevy_image/src/serialized_image.rs index 026c857a1d837..452b3289f5bff 100644 --- a/crates/bevy_image/src/serialized_image.rs +++ b/crates/bevy_image/src/serialized_image.rs @@ -144,6 +144,7 @@ impl SerializedImage { .map(SerializedTextureViewDescriptor::into_texture_view_descriptor), asset_usage: RenderAssetUsages::RENDER_WORLD, copy_on_resize: false, + is_extracted_to_render_world: false, } } } diff --git a/crates/bevy_mesh/src/index.rs b/crates/bevy_mesh/src/index.rs index 0f64d21ee3c43..5699f09632ffe 100644 --- a/crates/bevy_mesh/src/index.rs +++ b/crates/bevy_mesh/src/index.rs @@ -6,10 +6,8 @@ use serde::{Deserialize, Serialize}; use thiserror::Error; use wgpu_types::IndexFormat; -use crate::MeshAccessError; - /// A disjunction of four iterators. This is necessary to have a well-formed type for the output -/// of [`Mesh::triangles`](super::Mesh::triangles), which produces iterators of four different types depending on the +/// of [`MeshExtractableData::triangles`](crate::MeshExtractableData::triangles), which produces iterators of four different types depending on the /// branch taken. pub(crate) enum FourIterators { First(A), @@ -58,8 +56,6 @@ pub enum MeshWindingInvertError { /// * [`PrimitiveTopology::LineList`](super::PrimitiveTopology::LineList), but the indices are not in chunks of 2. #[error("Indices weren't in chunks according to topology")] AbruptIndicesEnd, - #[error("Mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), } /// An error that occurred while trying to extract a collection of triangles from a [`Mesh`](super::Mesh). @@ -68,13 +64,14 @@ pub enum MeshTrianglesError { #[error("Source mesh does not have primitive topology TriangleList or TriangleStrip")] WrongTopology, + #[error("Source mesh position data does not exist")] + BadPositions, + #[error("Source mesh position data is not Float32x3")] PositionsFormat, #[error("Face index data references vertices that do not exist")] BadIndices, - #[error("mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), } /// An array of indices into the [`VertexAttributeValues`](super::VertexAttributeValues) for a mesh. diff --git a/crates/bevy_mesh/src/lib.rs b/crates/bevy_mesh/src/lib.rs index 5774761791098..650150b57b9f6 100644 --- a/crates/bevy_mesh/src/lib.rs +++ b/crates/bevy_mesh/src/lib.rs @@ -7,6 +7,7 @@ mod components; mod conversions; mod index; mod mesh; +mod mesh_extractable_data; #[cfg(feature = "bevy_mikktspace")] mod mikktspace; #[cfg(feature = "morph")] @@ -21,6 +22,7 @@ use bitflags::bitflags; pub use components::*; pub use index::*; pub use mesh::*; +pub use mesh_extractable_data::*; #[cfg(feature = "bevy_mikktspace")] pub use mikktspace::*; pub use primitives::*; diff --git a/crates/bevy_mesh/src/mesh.rs b/crates/bevy_mesh/src/mesh.rs index ea6482b1421c8..a70c4a7c15f2c 100644 --- a/crates/bevy_mesh/src/mesh.rs +++ b/crates/bevy_mesh/src/mesh.rs @@ -1,138 +1,23 @@ -use bevy_transform::components::Transform; pub use wgpu_types::PrimitiveTopology; -use super::{ - triangle_area_normal, triangle_normal, FourIterators, Indices, MeshAttributeData, - MeshTrianglesError, MeshVertexAttribute, MeshVertexAttributeId, MeshVertexBufferLayout, - MeshVertexBufferLayoutRef, MeshVertexBufferLayouts, MeshWindingInvertError, - VertexAttributeValues, VertexBufferLayout, -}; +use super::{MeshVertexAttribute, VertexAttributeValues}; +use crate::MeshExtractableData; #[cfg(feature = "serialize")] -use crate::SerializedMeshAttributeData; -use alloc::collections::BTreeMap; -#[cfg(feature = "morph")] -use bevy_asset::Handle; +use crate::{Indices, MeshVertexAttributeId, SerializedMeshAttributeData}; use bevy_asset::{Asset, RenderAssetUsages}; -#[cfg(feature = "morph")] -use bevy_image::Image; -use bevy_math::{bounding::Aabb3d, primitives::Triangle3d, *}; +use bevy_asset::{AssetExtractionError, ExtractableAsset, ExtractableAssetAccessError}; +use bevy_math::{bounding::Aabb3d, *}; #[cfg(feature = "serialize")] use bevy_platform::collections::HashMap; use bevy_reflect::Reflect; -use bytemuck::cast_slice; #[cfg(feature = "serialize")] use serde::{Deserialize, Serialize}; use thiserror::Error; -use tracing::warn; -use wgpu_types::{VertexAttribute, VertexFormat, VertexStepMode}; +use wgpu_types::VertexFormat; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; -/// Error from accessing mesh vertex attributes or indices -#[derive(Error, Debug, Clone)] -pub enum MeshAccessError { - #[error("The mesh vertex/index data has been extracted to the RenderWorld (via `Mesh::asset_usage`)")] - ExtractedToRenderWorld, - #[error("The requested mesh data wasn't found in this mesh")] - NotFound, -} - -const MESH_EXTRACTED_ERROR: &str = "Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh `asset_usage` must include `MAIN_WORLD`"; - -// storage for extractable data with access methods which return errors if the -// contents have already been extracted -#[derive(Debug, Clone, PartialEq, Reflect, Default)] -enum MeshExtractableData { - Data(T), - #[default] - NoData, - ExtractedToRenderWorld, -} - -impl MeshExtractableData { - // get a reference to internal data. returns error if data has been extracted, or if no - // data exists - fn as_ref(&self) -> Result<&T, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(data), - MeshExtractableData::NoData => Err(MeshAccessError::NotFound), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // get an optional reference to internal data. returns error if data has been extracted - fn as_ref_option(&self) -> Result, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(Some(data)), - MeshExtractableData::NoData => Ok(None), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // get a mutable reference to internal data. returns error if data has been extracted, - // or if no data exists - fn as_mut(&mut self) -> Result<&mut T, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(data), - MeshExtractableData::NoData => Err(MeshAccessError::NotFound), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // get an optional mutable reference to internal data. returns error if data has been extracted - fn as_mut_option(&mut self) -> Result, MeshAccessError> { - match self { - MeshExtractableData::Data(data) => Ok(Some(data)), - MeshExtractableData::NoData => Ok(None), - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - } - } - - // extract data and replace self with `ExtractedToRenderWorld`. returns error if - // data has been extracted - fn extract(&mut self) -> Result, MeshAccessError> { - match core::mem::replace(self, MeshExtractableData::ExtractedToRenderWorld) { - MeshExtractableData::ExtractedToRenderWorld => { - Err(MeshAccessError::ExtractedToRenderWorld) - } - not_extracted => Ok(not_extracted), - } - } - - // replace internal data. returns the existing data, or an error if data has been extracted - fn replace( - &mut self, - data: impl Into>, - ) -> Result, MeshAccessError> { - match core::mem::replace(self, data.into()) { - MeshExtractableData::ExtractedToRenderWorld => { - *self = MeshExtractableData::ExtractedToRenderWorld; - Err(MeshAccessError::ExtractedToRenderWorld) - } - MeshExtractableData::Data(t) => Ok(Some(t)), - MeshExtractableData::NoData => Ok(None), - } - } -} - -impl From> for MeshExtractableData { - fn from(value: Option) -> Self { - match value { - Some(data) => MeshExtractableData::Data(data), - None => MeshExtractableData::NoData, - } - } -} - /// A 3D object made out of vertices representing triangles, lines, or points, /// with "attribute" values for each vertex. /// @@ -152,35 +37,45 @@ impl From> for MeshExtractableData { /// `StandardMaterial` or `ColorMaterial`: /// /// ``` -/// # use bevy_mesh::{Mesh, Indices, PrimitiveTopology}; -/// # use bevy_asset::RenderAssetUsages; +/// # use bevy_mesh::{Mesh, Indices, PrimitiveTopology, MeshExtractableData}; /// fn create_simple_parallelogram() -> Mesh { /// // Create a new mesh using a triangle list topology, where each set of 3 vertices composes a triangle. -/// Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()) -/// // Add 4 vertices, each with its own position attribute (coordinate in -/// // 3D space), for each of the corners of the parallelogram. -/// .with_inserted_attribute( -/// Mesh::ATTRIBUTE_POSITION, -/// vec![[0.0, 0.0, 0.0], [1.0, 2.0, 0.0], [2.0, 2.0, 0.0], [1.0, 0.0, 0.0]] -/// ) -/// // Assign a UV coordinate to each vertex. -/// .with_inserted_attribute( -/// Mesh::ATTRIBUTE_UV_0, -/// vec![[0.0, 1.0], [0.5, 0.0], [1.0, 0.0], [0.5, 1.0]] -/// ) -/// // Assign normals (everything points outwards) -/// .with_inserted_attribute( -/// Mesh::ATTRIBUTE_NORMAL, -/// vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]] -/// ) -/// // After defining all the vertices and their attributes, build each triangle using the -/// // indices of the vertices that make it up in a counter-clockwise order. -/// .with_inserted_indices(Indices::U32(vec![ -/// // First triangle -/// 0, 3, 1, -/// // Second triangle -/// 1, 3, 2 -/// ])) +/// Mesh::from( +/// MeshExtractableData::new(PrimitiveTopology::TriangleList) +/// // Add 4 vertices, each with its own position attribute (coordinate in +/// // 3D space), for each of the corners of the parallelogram. +/// .with_inserted_attribute( +/// Mesh::ATTRIBUTE_POSITION, +/// vec![ +/// [0.0, 0.0, 0.0], +/// [1.0, 2.0, 0.0], +/// [2.0, 2.0, 0.0], +/// [1.0, 0.0, 0.0], +/// ], +/// ) +/// // Assign a UV coordinate to each vertex. +/// .with_inserted_attribute( +/// Mesh::ATTRIBUTE_UV_0, +/// vec![[0.0, 1.0], [0.5, 0.0], [1.0, 0.0], [0.5, 1.0]], +/// ) +/// // Assign normals (everything points outwards) +/// .with_inserted_attribute( +/// Mesh::ATTRIBUTE_NORMAL, +/// vec![ +/// [0.0, 0.0, 1.0], +/// [0.0, 0.0, 1.0], +/// [0.0, 0.0, 1.0], +/// [0.0, 0.0, 1.0], +/// ], +/// ) +/// // After defining all the vertices and their attributes, build each triangle using the +/// // indices of the vertices that make it up in a counter-clockwise order. +/// .with_inserted_indices(Indices::U32(vec![ +/// // First triangle +/// 0, 3, 1, // Second triangle +/// 1, 3, 2, +/// ])), +/// ) /// } /// ``` /// @@ -227,17 +122,7 @@ impl From> for MeshExtractableData { pub struct Mesh { #[reflect(ignore, clone)] primitive_topology: PrimitiveTopology, - /// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...) - /// for this mesh. Attribute ids to attribute values. - /// Uses a [`BTreeMap`] because, unlike `HashMap`, it has a defined iteration order, - /// which allows easy stable `VertexBuffers` (i.e. same buffer order) - #[reflect(ignore, clone)] - attributes: MeshExtractableData>, - indices: MeshExtractableData, - #[cfg(feature = "morph")] - morph_targets: MeshExtractableData>, - #[cfg(feature = "morph")] - morph_target_names: MeshExtractableData>, + extractable_data: Option, pub asset_usage: RenderAssetUsages, /// Whether or not to build a BLAS for use with `bevy_solari` raytracing. /// @@ -259,23 +144,80 @@ pub struct Mesh { pub final_aabb: Option, } +impl From for Mesh { + fn from(value: MeshExtractableData) -> Self { + Self { + primitive_topology: value.primitive_topology, + extractable_data: Some(value), + asset_usage: RenderAssetUsages::default(), + enable_raytracing: true, + final_aabb: None, + } + } +} + +impl ExtractableAsset for Mesh { + type Data = MeshExtractableData; + + fn extractable_data_replace(&mut self, data: Self::Data) -> Option { + self.extractable_data.replace(data) + } + + fn extractable_data_ref(&self) -> Result<&Self::Data, ExtractableAssetAccessError> { + self.extractable_data + .as_ref() + .ok_or(ExtractableAssetAccessError::ExtractedToRenderWorld) + } + + fn extractable_data_mut(&mut self) -> Result<&mut Self::Data, ExtractableAssetAccessError> { + self.extractable_data + .as_mut() + .ok_or(ExtractableAssetAccessError::ExtractedToRenderWorld) + } + + fn extract(&mut self) -> Result { + let data = self + .extractable_data + .take() + .ok_or(AssetExtractionError::AlreadyExtracted)?; + let positions = data.attribute(Mesh::ATTRIBUTE_POSITION); + + // store the aabb extents as they cannot be computed after extraction + if let Some(VertexAttributeValues::Float32x3(position_values), ..) = positions + && !position_values.is_empty() + { + let mut iter = position_values.iter().map(|p| Vec3::from_slice(p)); + let mut min = iter.next().unwrap(); + let mut max = min; + for v in iter { + min = Vec3::min(min, v); + max = Vec3::max(max, v); + } + self.final_aabb = Some(Aabb3d::from_min_max(min, max)); + } else { + self.final_aabb = None; + } + Ok(data) + } +} + impl Mesh { - /// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`] - /// or [`Mesh::with_inserted_attribute`]. + /// Where the vertex is located in space. Use in conjunction with [`MeshExtractableData::insert_attribute`] + /// or [`MeshExtractableData::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x3`]. pub const ATTRIBUTE_POSITION: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3); /// The direction the vertex normal is facing in. - /// Use in conjunction with [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`]. + /// Use in conjunction with [`MeshExtractableData::insert_attribute`] or [`MeshExtractableData::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x3`]. pub const ATTRIBUTE_NORMAL: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Normal", 1, VertexFormat::Float32x3); - /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::insert_attribute`] - /// or [`Mesh::with_inserted_attribute`]. + /// Texture coordinates for the vertex. Use in conjunction with [`MeshExtractableData::insert_attribute`] + /// or [`MeshExtractableData::with_inserted_attribute`]. /// /// Generally `[0.,0.]` is mapped to the top left of the texture, and `[1.,1.]` to the bottom-right. /// @@ -292,7 +234,7 @@ impl Mesh { MeshVertexAttribute::new("Vertex_Uv", 2, VertexFormat::Float32x2); /// Alternate texture coordinates for the vertex. Use in conjunction with - /// [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`]. + /// [`MeshExtractableData::insert_attribute`] or [`MeshExtractableData::with_inserted_attribute`]. /// /// Typically, these are used for lightmaps, textures that provide /// precomputed illumination. @@ -302,29 +244,29 @@ impl Mesh { MeshVertexAttribute::new("Vertex_Uv_1", 3, VertexFormat::Float32x2); /// The direction of the vertex tangent. Used for normal mapping. - /// Usually generated with [`generate_tangents`](Mesh::generate_tangents) or - /// [`with_generated_tangents`](Mesh::with_generated_tangents). + /// Usually generated with [`generate_tangents`](MeshExtractableData::generate_tangents) or + /// [`with_generated_tangents`](MeshExtractableData::with_generated_tangents). /// /// The format of this attribute is [`VertexFormat::Float32x4`]. pub const ATTRIBUTE_TANGENT: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Tangent", 4, VertexFormat::Float32x4); - /// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`] - /// or [`Mesh::with_inserted_attribute`]. + /// Per vertex coloring. Use in conjunction with [`MeshExtractableData::insert_attribute`] + /// or [`MeshExtractableData::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x4`]. pub const ATTRIBUTE_COLOR: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Color", 5, VertexFormat::Float32x4); - /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`] - /// or [`Mesh::with_inserted_attribute`]. + /// Per vertex joint transform matrix weight. Use in conjunction with [`MeshExtractableData::insert_attribute`] + /// or [`MeshExtractableData::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x4`]. pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_JointWeight", 6, VertexFormat::Float32x4); - /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`] - /// or [`Mesh::with_inserted_attribute`]. + /// Per vertex joint transform matrix index. Use in conjunction with [`MeshExtractableData::insert_attribute`] + /// or [`MeshExtractableData::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Uint16x4`]. pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute = @@ -340,12 +282,7 @@ impl Mesh { pub fn new(primitive_topology: PrimitiveTopology, asset_usage: RenderAssetUsages) -> Self { Mesh { primitive_topology, - attributes: MeshExtractableData::Data(Default::default()), - indices: MeshExtractableData::NoData, - #[cfg(feature = "morph")] - morph_targets: MeshExtractableData::NoData, - #[cfg(feature = "morph")] - morph_target_names: MeshExtractableData::NoData, + extractable_data: Some(MeshExtractableData::new(primitive_topology)), asset_usage, enable_raytracing: true, final_aabb: None, @@ -357,2144 +294,152 @@ impl Mesh { self.primitive_topology } - /// Sets the data for a vertex attribute (position, normal, etc.). The name will - /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the format of the values does not match the attribute's format. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_insert_attribute`] - #[inline] - pub fn insert_attribute( - &mut self, - attribute: MeshVertexAttribute, - values: impl Into, - ) { - self.try_insert_attribute(attribute, values) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Sets the data for a vertex attribute (position, normal, etc.). The name will - /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - /// - /// # Panics - /// Panics when the format of the values does not match the attribute's format. - #[inline] - pub fn try_insert_attribute( - &mut self, - attribute: MeshVertexAttribute, - values: impl Into, - ) -> Result<(), MeshAccessError> { - let values = values.into(); - let values_format = VertexFormat::from(&values); - if values_format != attribute.format { - panic!( - "Failed to insert attribute. Invalid attribute format for {}. Given format is {values_format:?} but expected {:?}", - attribute.name, attribute.format - ); - } - - self.attributes - .as_mut()? - .insert(attribute.id, MeshAttributeData { attribute, values }); - Ok(()) - } - - /// Consumes the mesh and returns a mesh with data set for a vertex attribute (position, normal, etc.). - /// The name will often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. - /// - /// (Alternatively, you can use [`Mesh::insert_attribute`] to mutate an existing mesh in-place) - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the format of the values does not match the attribute's format. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_inserted_attribute`] - #[must_use] - #[inline] - pub fn with_inserted_attribute( - mut self, - attribute: MeshVertexAttribute, - values: impl Into, - ) -> Self { - self.insert_attribute(attribute, values); - self - } - - /// Consumes the mesh and returns a mesh with data set for a vertex attribute (position, normal, etc.). - /// The name will often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. - /// - /// (Alternatively, you can use [`Mesh::insert_attribute`] to mutate an existing mesh in-place) - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_with_inserted_attribute( - mut self, - attribute: MeshVertexAttribute, - values: impl Into, - ) -> Result { - self.try_insert_attribute(attribute, values)?; - Ok(self) - } - - /// Removes the data for a vertex attribute - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_remove_attribute`] - pub fn remove_attribute( - &mut self, - attribute: impl Into, - ) -> Option { - self.attributes - .as_mut() - .expect(MESH_EXTRACTED_ERROR) - .remove(&attribute.into()) - .map(|data| data.values) - } - - /// Removes the data for a vertex attribute - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - pub fn try_remove_attribute( - &mut self, - attribute: impl Into, - ) -> Result { - Ok(self - .attributes - .as_mut()? - .remove(&attribute.into()) - .ok_or(MeshAccessError::NotFound)? - .values) - } - - /// Consumes the mesh and returns a mesh without the data for a vertex attribute - /// - /// (Alternatively, you can use [`Mesh::remove_attribute`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_removed_attribute`] - #[must_use] - pub fn with_removed_attribute(mut self, attribute: impl Into) -> Self { - self.remove_attribute(attribute); + pub fn with_asset_usage(mut self, asset_usage: RenderAssetUsages) -> Self { + self.asset_usage = asset_usage; self } +} - /// Consumes the mesh and returns a mesh without the data for a vertex attribute - /// - /// (Alternatively, you can use [`Mesh::remove_attribute`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - pub fn try_with_removed_attribute( - mut self, - attribute: impl Into, - ) -> Result { - self.try_remove_attribute(attribute)?; - Ok(self) - } - - /// Returns a bool indicating if the attribute is present in this mesh's vertex data. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_contains_attribute`] - #[inline] - pub fn contains_attribute(&self, id: impl Into) -> bool { - self.attributes - .as_ref() - .expect(MESH_EXTRACTED_ERROR) - .contains_key(&id.into()) - } - - /// Returns a bool indicating if the attribute is present in this mesh's vertex data. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_contains_attribute( - &self, - id: impl Into, - ) -> Result { - Ok(self.attributes.as_ref()?.contains_key(&id.into())) - } - - /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attribute`] or [`Mesh::try_attribute_option`] - #[inline] - pub fn attribute( - &self, - id: impl Into, - ) -> Option<&VertexAttributeValues> { - self.try_attribute_option(id).expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - #[inline] - pub fn try_attribute( - &self, - id: impl Into, - ) -> Result<&VertexAttributeValues, MeshAccessError> { - self.try_attribute_option(id)? - .ok_or(MeshAccessError::NotFound) - } - - /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_attribute_option( - &self, - id: impl Into, - ) -> Result, MeshAccessError> { - Ok(self - .attributes - .as_ref()? - .get(&id.into()) - .map(|data| &data.values)) - } - - /// Retrieves the full data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. - #[inline] - pub(crate) fn try_attribute_data( - &self, - id: impl Into, - ) -> Result, MeshAccessError> { - Ok(self.attributes.as_ref()?.get(&id.into())) - } - - /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attribute_mut`] - #[inline] - pub fn attribute_mut( - &mut self, - id: impl Into, - ) -> Option<&mut VertexAttributeValues> { - self.try_attribute_mut_option(id) - .expect(MESH_EXTRACTED_ERROR) - } +/// A version of [`Mesh`] suitable for serializing for short-term transfer. +/// +/// [`Mesh`] does not implement [`Serialize`] / [`Deserialize`] because it is made with the renderer in mind. +/// It is not a general-purpose mesh implementation, and its internals are subject to frequent change. +/// As such, storing a [`Mesh`] on disk is highly discouraged. +/// +/// But there are still some valid use cases for serializing a [`Mesh`], namely transferring meshes between processes. +/// To support this, you can create a [`SerializedMesh`] from a [`Mesh`] with [`SerializedMesh::from_mesh`], +/// and then deserialize it with [`SerializedMesh::deserialize`]. The caveats are: +/// - The mesh representation is not valid across different versions of Bevy. +/// - This conversion is lossy. Only the following information is preserved: +/// - Primitive topology +/// - Vertex attributes +/// - Indices +/// - Custom attributes that were not specified with [`MeshDeserializer::add_custom_vertex_attribute`] will be ignored while deserializing. +#[cfg(feature = "serialize")] +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SerializedMesh { + primitive_topology: PrimitiveTopology, + attributes: Vec<(MeshVertexAttributeId, SerializedMeshAttributeData)>, + indices: Option, +} - /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - #[inline] - pub fn try_attribute_mut( - &mut self, - id: impl Into, - ) -> Result<&mut VertexAttributeValues, MeshAccessError> { - self.try_attribute_mut_option(id)? - .ok_or(MeshAccessError::NotFound) +#[cfg(feature = "serialize")] +impl SerializedMesh { + /// Create a [`SerializedMesh`] from a [`Mesh`]. See the documentation for [`SerializedMesh`] for caveats. + pub fn from_mesh(mut mesh: Mesh) -> Self { + const MESH_EXTRACTED_ERROR: &str = "Mesh has been extracted to RenderWorld. To access vertex attributes, the mesh `asset_usage` must include `MAIN_WORLD`"; + let extractable_data = mesh.extractable_data.take().expect(MESH_EXTRACTED_ERROR); + Self { + primitive_topology: mesh.primitive_topology, + attributes: extractable_data + .attributes + .into_iter() + .map(|(id, data)| { + ( + id, + SerializedMeshAttributeData::from_mesh_attribute_data(data), + ) + }) + .collect(), + indices: extractable_data.indices, + } } - /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. + /// Create a [`Mesh`] from a [`SerializedMesh`]. See the documentation for [`SerializedMesh`] for caveats. /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_attribute_mut_option( - &mut self, - id: impl Into, - ) -> Result, MeshAccessError> { - Ok(self - .attributes - .as_mut()? - .get_mut(&id.into()) - .map(|data| &mut data.values)) + /// Use [`MeshDeserializer`] if you need to pass extra options to the deserialization process, such as specifying custom vertex attributes. + pub fn into_mesh(self) -> Mesh { + MeshDeserializer::default().deserialize(self) } +} - /// Returns an iterator that yields references to the data of each vertex attribute. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attributes`] - pub fn attributes( - &self, - ) -> impl Iterator { - self.try_attributes().expect(MESH_EXTRACTED_ERROR) - } +/// Use to specify extra options when deserializing a [`SerializedMesh`] into a [`Mesh`]. +#[cfg(feature = "serialize")] +pub struct MeshDeserializer { + custom_vertex_attributes: HashMap, MeshVertexAttribute>, +} - /// Returns an iterator that yields references to the data of each vertex attribute. - /// Returns an error if data has been extracted to `RenderWorld` - pub fn try_attributes( - &self, - ) -> Result, MeshAccessError> - { - Ok(self - .attributes - .as_ref()? - .values() - .map(|data| (&data.attribute, &data.values))) +#[cfg(feature = "serialize")] +impl Default for MeshDeserializer { + fn default() -> Self { + // Written like this so that the compiler can validate that we use all the built-in attributes. + // If you just added a new attribute and got a compile error, please add it to this list :) + const BUILTINS: [MeshVertexAttribute; Mesh::FIRST_AVAILABLE_CUSTOM_ATTRIBUTE as usize] = [ + Mesh::ATTRIBUTE_POSITION, + Mesh::ATTRIBUTE_NORMAL, + Mesh::ATTRIBUTE_UV_0, + Mesh::ATTRIBUTE_UV_1, + Mesh::ATTRIBUTE_TANGENT, + Mesh::ATTRIBUTE_COLOR, + Mesh::ATTRIBUTE_JOINT_WEIGHT, + Mesh::ATTRIBUTE_JOINT_INDEX, + ]; + Self { + custom_vertex_attributes: BUILTINS + .into_iter() + .map(|attribute| (attribute.name.into(), attribute)) + .collect(), + } } +} - /// Returns an iterator that yields mutable references to the data of each vertex attribute. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_attributes_mut`] - pub fn attributes_mut( - &mut self, - ) -> impl Iterator { - self.try_attributes_mut().expect(MESH_EXTRACTED_ERROR) +#[cfg(feature = "serialize")] +impl MeshDeserializer { + /// Create a new [`MeshDeserializer`]. + pub fn new() -> Self { + Self::default() } - /// Returns an iterator that yields mutable references to the data of each vertex attribute. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_attributes_mut( + /// Register a custom vertex attribute to the deserializer. Custom vertex attributes that were not added with this method will be ignored while deserializing. + pub fn add_custom_vertex_attribute( &mut self, - ) -> Result< - impl Iterator, - MeshAccessError, - > { - Ok(self - .attributes - .as_mut()? - .values_mut() - .map(|data| (&data.attribute, &mut data.values))) - } - - /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the - /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants - /// that use triangles. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_insert_indices`] - #[inline] - pub fn insert_indices(&mut self, indices: Indices) { - self.indices - .replace(Some(indices)) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the - /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants - /// that use triangles. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_insert_indices(&mut self, indices: Indices) -> Result<(), MeshAccessError> { - self.indices.replace(Some(indices))?; - Ok(()) - } - - /// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles - /// are constructed out of the vertex attributes and are therefore only useful for the - /// [`PrimitiveTopology`] variants that use triangles. - /// - /// (Alternatively, you can use [`Mesh::insert_indices`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_inserted_indices`] - #[must_use] - #[inline] - pub fn with_inserted_indices(mut self, indices: Indices) -> Self { - self.insert_indices(indices); + name: &str, + attribute: MeshVertexAttribute, + ) -> &mut Self { + self.custom_vertex_attributes.insert(name.into(), attribute); self } - /// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles - /// are constructed out of the vertex attributes and are therefore only useful for the - /// [`PrimitiveTopology`] variants that use triangles. - /// - /// (Alternatively, you can use [`Mesh::try_insert_indices`] to mutate an existing mesh in-place) + /// Deserialize a [`SerializedMesh`] into a [`Mesh`]. /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_with_inserted_indices(mut self, indices: Indices) -> Result { - self.try_insert_indices(indices)?; - Ok(self) + /// See the documentation for [`SerializedMesh`] for caveats. + pub fn deserialize(&self, serialized_mesh: SerializedMesh) -> Mesh { + let attributes = serialized_mesh + .attributes + .into_iter() + .filter_map(|(id, data)| { + let attribute = data.attribute.clone(); + let Some(data) = data.try_into_mesh_attribute_data(&self.custom_vertex_attributes) + else { + tracing::warn!( + "Deserialized mesh contains custom vertex attribute {attribute:?} that \ + was not specified with `MeshDeserializer::add_custom_vertex_attribute`. Ignoring." + ); + return None; + }; + Some((id, data)) + }) + .collect(); + let extractable_data = MeshExtractableData { + attributes, + indices: serialized_mesh.indices, + primitive_topology: serialized_mesh.primitive_topology, + #[cfg(feature = "morph")] + morph_target_names: None, + #[cfg(feature = "morph")] + morph_targets: None, + }; + Mesh { + extractable_data: Some(extractable_data), + ..Mesh::new( + serialized_mesh.primitive_topology, + RenderAssetUsages::default(), + ) + } } +} - /// Retrieves the vertex `indices` of the mesh, returns None if not found. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_indices`] - #[inline] - pub fn indices(&self) -> Option<&Indices> { - self.indices.as_ref_option().expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieves the vertex `indices` of the mesh. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the attribute does not exist. - #[inline] - pub fn try_indices(&self) -> Result<&Indices, MeshAccessError> { - self.indices.as_ref() - } - - /// Retrieves the vertex `indices` of the mesh, returns None if not found. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_indices_option(&self) -> Result, MeshAccessError> { - self.indices.as_ref_option() - } - - /// Retrieves the vertex `indices` of the mesh mutably. - #[inline] - pub fn indices_mut(&mut self) -> Option<&mut Indices> { - self.try_indices_mut_option().expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieves the vertex `indices` of the mesh mutably. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_indices_mut(&mut self) -> Result<&mut Indices, MeshAccessError> { - self.indices.as_mut() - } - - /// Retrieves the vertex `indices` of the mesh mutably. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_indices_mut_option(&mut self) -> Result, MeshAccessError> { - self.indices.as_mut_option() - } - - /// Removes the vertex `indices` from the mesh and returns them. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_remove_indices`] - #[inline] - pub fn remove_indices(&mut self) -> Option { - self.try_remove_indices().expect(MESH_EXTRACTED_ERROR) - } - - /// Removes the vertex `indices` from the mesh and returns them. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - #[inline] - pub fn try_remove_indices(&mut self) -> Result, MeshAccessError> { - self.indices.replace(None) - } - - /// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh. - /// - /// (Alternatively, you can use [`Mesh::remove_indices`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_removed_indices`] - #[must_use] - pub fn with_removed_indices(mut self) -> Self { - self.remove_indices(); - self - } - - /// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh. - /// - /// (Alternatively, you can use [`Mesh::try_remove_indices`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_with_removed_indices(mut self) -> Result { - self.try_remove_indices()?; - Ok(self) - } - - /// Returns the size of a vertex in bytes. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. - pub fn get_vertex_size(&self) -> u64 { - self.attributes - .as_ref() - .expect(MESH_EXTRACTED_ERROR) - .values() - .map(|data| data.attribute.format.size()) - .sum() - } - - /// Returns the size required for the vertex buffer in bytes. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. - pub fn get_vertex_buffer_size(&self) -> usize { - let vertex_size = self.get_vertex_size() as usize; - let vertex_count = self.count_vertices(); - vertex_count * vertex_size - } - - /// Computes and returns the index data of the mesh as bytes. - /// This is used to transform the index data into a GPU friendly format. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. - pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> { - let mesh_indices = self.indices.as_ref_option().expect(MESH_EXTRACTED_ERROR); - - mesh_indices.as_ref().map(|indices| match &indices { - Indices::U16(indices) => cast_slice(&indices[..]), - Indices::U32(indices) => cast_slice(&indices[..]), - }) - } - - /// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in `SpecializedMeshPipeline`. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. - pub fn get_mesh_vertex_buffer_layout( - &self, - mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, - ) -> MeshVertexBufferLayoutRef { - let mesh_attributes = self.attributes.as_ref().expect(MESH_EXTRACTED_ERROR); - - let mut attributes = Vec::with_capacity(mesh_attributes.len()); - let mut attribute_ids = Vec::with_capacity(mesh_attributes.len()); - let mut accumulated_offset = 0; - for (index, data) in mesh_attributes.values().enumerate() { - attribute_ids.push(data.attribute.id); - attributes.push(VertexAttribute { - offset: accumulated_offset, - format: data.attribute.format, - shader_location: index as u32, - }); - accumulated_offset += data.attribute.format.size(); - } - - let layout = MeshVertexBufferLayout { - layout: VertexBufferLayout { - array_stride: accumulated_offset, - step_mode: VertexStepMode::Vertex, - attributes, - }, - attribute_ids, - }; - mesh_vertex_buffer_layouts.insert(layout) - } - - /// Counts all vertices of the mesh. - /// - /// If the attributes have different vertex counts, the smallest is returned. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. - pub fn count_vertices(&self) -> usize { - let mut vertex_count: Option = None; - let mesh_attributes = self.attributes.as_ref().expect(MESH_EXTRACTED_ERROR); - - for (attribute_id, attribute_data) in mesh_attributes { - let attribute_len = attribute_data.values.len(); - if let Some(previous_vertex_count) = vertex_count { - if previous_vertex_count != attribute_len { - let name = mesh_attributes - .get(attribute_id) - .map(|data| data.attribute.name.to_string()) - .unwrap_or_else(|| format!("{attribute_id:?}")); - - warn!("{name} has a different vertex count ({attribute_len}) than other attributes ({previous_vertex_count}) in this mesh, \ - all attributes will be truncated to match the smallest."); - vertex_count = Some(core::cmp::min(previous_vertex_count, attribute_len)); - } - } else { - vertex_count = Some(attribute_len); - } - } - - vertex_count.unwrap_or(0) - } - - /// Computes and returns the vertex data of the mesh as bytes. - /// Therefore the attributes are located in the order of their [`MeshVertexAttribute::id`]. - /// This is used to transform the vertex data into a GPU friendly format. - /// - /// If the vertex attributes have different lengths, they are all truncated to - /// the length of the smallest. - /// - /// This is a convenience method which allocates a Vec. - /// Prefer pre-allocating and using [`Mesh::write_packed_vertex_buffer_data`] when possible. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. - pub fn create_packed_vertex_buffer_data(&self) -> Vec { - let mut attributes_interleaved_buffer = vec![0; self.get_vertex_buffer_size()]; - self.write_packed_vertex_buffer_data(&mut attributes_interleaved_buffer); - attributes_interleaved_buffer - } - - /// Computes and write the vertex data of the mesh into a mutable byte slice. - /// The attributes are located in the order of their [`MeshVertexAttribute::id`]. - /// This is used to transform the vertex data into a GPU friendly format. - /// - /// If the vertex attributes have different lengths, they are all truncated to - /// the length of the smallest. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. - pub fn write_packed_vertex_buffer_data(&self, slice: &mut [u8]) { - let mesh_attributes = self.attributes.as_ref().expect(MESH_EXTRACTED_ERROR); - - let vertex_size = self.get_vertex_size() as usize; - let vertex_count = self.count_vertices(); - // bundle into interleaved buffers - let mut attribute_offset = 0; - for attribute_data in mesh_attributes.values() { - let attribute_size = attribute_data.attribute.format.size() as usize; - let attributes_bytes = attribute_data.values.get_bytes(); - for (vertex_index, attribute_bytes) in attributes_bytes - .chunks_exact(attribute_size) - .take(vertex_count) - .enumerate() - { - let offset = vertex_index * vertex_size + attribute_offset; - slice[offset..offset + attribute_size].copy_from_slice(attribute_bytes); - } - - attribute_offset += attribute_size; - } - } - - /// Duplicates the vertex attributes so that no vertices are shared. - /// - /// This can dramatically increase the vertex count, so make sure this is what you want. - /// Does nothing if no [Indices] are set. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_duplicate_vertices`] - pub fn duplicate_vertices(&mut self) { - self.try_duplicate_vertices().expect(MESH_EXTRACTED_ERROR); - } - - /// Duplicates the vertex attributes so that no vertices are shared. - /// - /// This can dramatically increase the vertex count, so make sure this is what you want. - /// Does nothing if no [Indices] are set. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_duplicate_vertices(&mut self) -> Result<(), MeshAccessError> { - fn duplicate(values: &[T], indices: impl Iterator) -> Vec { - indices.map(|i| values[i]).collect() - } - - let Some(indices) = self.indices.replace(None)? else { - return Ok(()); - }; - - let mesh_attributes = self.attributes.as_mut()?; - - for attributes in mesh_attributes.values_mut() { - let indices = indices.iter(); - #[expect( - clippy::match_same_arms, - reason = "Although the `vec` binding on some match arms may have different types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever." - )] - match &mut attributes.values { - VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Float32x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Float32x3(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32x3(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32x3(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Float32x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint8x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm8x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint8x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm8x4(vec) => *vec = duplicate(vec, indices), - } - } - - Ok(()) - } - - /// Consumes the mesh and returns a mesh with no shared vertices. - /// - /// This can dramatically increase the vertex count, so make sure this is what you want. - /// Does nothing if no [`Indices`] are set. - /// - /// (Alternatively, you can use [`Mesh::duplicate_vertices`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_duplicated_vertices`] - #[must_use] - pub fn with_duplicated_vertices(mut self) -> Self { - self.duplicate_vertices(); - self - } - - /// Consumes the mesh and returns a mesh with no shared vertices. - /// - /// This can dramatically increase the vertex count, so make sure this is what you want. - /// Does nothing if no [`Indices`] are set. - /// - /// (Alternatively, you can use [`Mesh::try_duplicate_vertices`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_with_duplicated_vertices(mut self) -> Result { - self.try_duplicate_vertices()?; - Ok(self) - } - - /// Inverts the winding of the indices such that all counter-clockwise triangles are now - /// clockwise and vice versa. - /// For lines, their start and end indices are flipped. - /// - /// Does nothing if no [`Indices`] are set. - /// If this operation succeeded, an [`Ok`] result is returned. - pub fn invert_winding(&mut self) -> Result<(), MeshWindingInvertError> { - fn invert( - indices: &mut [I], - topology: PrimitiveTopology, - ) -> Result<(), MeshWindingInvertError> { - match topology { - PrimitiveTopology::TriangleList => { - // Early return if the index count doesn't match - if !indices.len().is_multiple_of(3) { - return Err(MeshWindingInvertError::AbruptIndicesEnd); - } - for chunk in indices.chunks_mut(3) { - // This currently can only be optimized away with unsafe, rework this when `feature(slice_as_chunks)` gets stable. - let [_, b, c] = chunk else { - return Err(MeshWindingInvertError::AbruptIndicesEnd); - }; - core::mem::swap(b, c); - } - Ok(()) - } - PrimitiveTopology::LineList => { - // Early return if the index count doesn't match - if !indices.len().is_multiple_of(2) { - return Err(MeshWindingInvertError::AbruptIndicesEnd); - } - indices.reverse(); - Ok(()) - } - PrimitiveTopology::TriangleStrip | PrimitiveTopology::LineStrip => { - indices.reverse(); - Ok(()) - } - _ => Err(MeshWindingInvertError::WrongTopology), - } - } - - let mesh_indices = self.indices.as_mut_option()?; - - match mesh_indices { - Some(Indices::U16(vec)) => invert(vec, self.primitive_topology), - Some(Indices::U32(vec)) => invert(vec, self.primitive_topology), - None => Ok(()), - } - } - - /// Consumes the mesh and returns a mesh with inverted winding of the indices such - /// that all counter-clockwise triangles are now clockwise and vice versa. - /// - /// Does nothing if no [`Indices`] are set. - pub fn with_inverted_winding(mut self) -> Result { - self.invert_winding().map(|_| self) - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. - /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat - /// normals. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].= - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_normals`] - pub fn compute_normals(&mut self) { - self.try_compute_normals().expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. - /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat - /// normals. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].= - pub fn try_compute_normals(&mut self) -> Result<(), MeshAccessError> { - assert!( - matches!(self.primitive_topology, PrimitiveTopology::TriangleList), - "`compute_normals` can only work on `TriangleList`s" - ); - if self.try_indices_option()?.is_none() { - self.try_compute_flat_normals() - } else { - self.try_compute_smooth_normals() - } - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. - /// - /// # Panics - /// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Consider calling [`Mesh::duplicate_vertices`] or exporting your mesh with normal - /// attributes. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_flat_normals`] - /// - /// FIXME: This should handle more cases since this is called as a part of gltf - /// mesh loading where we can't really blame users for loading meshes that might - /// not conform to the limitations here! - pub fn compute_flat_normals(&mut self) { - self.try_compute_flat_normals().expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. - /// - /// # Panics - /// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Consider calling [`Mesh::duplicate_vertices`] or exporting your mesh with normal - /// attributes. - /// - /// FIXME: This should handle more cases since this is called as a part of gltf - /// mesh loading where we can't really blame users for loading meshes that might - /// not conform to the limitations here! - pub fn try_compute_flat_normals(&mut self) -> Result<(), MeshAccessError> { - assert!( - self.try_indices_option()?.is_none(), - "`compute_flat_normals` can't work on indexed geometry. Consider calling either `Mesh::compute_smooth_normals` or `Mesh::duplicate_vertices` followed by `Mesh::compute_flat_normals`." - ); - assert!( - matches!(self.primitive_topology, PrimitiveTopology::TriangleList), - "`compute_flat_normals` can only work on `TriangleList`s" - ); - - let positions = self - .try_attribute(Mesh::ATTRIBUTE_POSITION)? - .as_float3() - .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); - - let normals: Vec<_> = positions - .chunks_exact(3) - .map(|p| triangle_normal(p[0], p[1], p[2])) - .flat_map(|normal| [normal; 3]) - .collect(); - - self.try_insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method weights normals by the angles of the corners of connected triangles, thus - /// eliminating triangle area and count as factors in the final normal. This does make it - /// somewhat slower than [`Mesh::compute_area_weighted_normals`] which does not need to - /// greedily normalize each triangle's normal or calculate corner angles. - /// - /// If you would rather have the computed normals be weighted by triangle area, see - /// [`Mesh::compute_area_weighted_normals`] instead. If you need to weight them in some other - /// way, see [`Mesh::compute_custom_smooth_normals`]. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_smooth_normals`] - pub fn compute_smooth_normals(&mut self) { - self.try_compute_smooth_normals() - .expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method weights normals by the angles of the corners of connected triangles, thus - /// eliminating triangle area and count as factors in the final normal. This does make it - /// somewhat slower than [`Mesh::compute_area_weighted_normals`] which does not need to - /// greedily normalize each triangle's normal or calculate corner angles. - /// - /// If you would rather have the computed normals be weighted by triangle area, see - /// [`Mesh::compute_area_weighted_normals`] instead. If you need to weight them in some other - /// way, see [`Mesh::compute_custom_smooth_normals`]. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_compute_smooth_normals(&mut self) -> Result<(), MeshAccessError> { - self.try_compute_custom_smooth_normals(|[a, b, c], positions, normals| { - let pa = Vec3::from(positions[a]); - let pb = Vec3::from(positions[b]); - let pc = Vec3::from(positions[c]); - - let ab = pb - pa; - let ba = pa - pb; - let bc = pc - pb; - let cb = pb - pc; - let ca = pa - pc; - let ac = pc - pa; - - const EPS: f32 = f32::EPSILON; - let weight_a = if ab.length_squared() * ac.length_squared() > EPS { - ab.angle_between(ac) - } else { - 0.0 - }; - let weight_b = if ba.length_squared() * bc.length_squared() > EPS { - ba.angle_between(bc) - } else { - 0.0 - }; - let weight_c = if ca.length_squared() * cb.length_squared() > EPS { - ca.angle_between(cb) - } else { - 0.0 - }; - - let normal = Vec3::from(triangle_normal(positions[a], positions[b], positions[c])); - - normals[a] += normal * weight_a; - normals[b] += normal * weight_b; - normals[c] += normal * weight_c; - }) - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method weights normals by the area of each triangle containing the vertex. Thus, - /// larger triangles will skew the normals of their vertices towards their own normal more - /// than smaller triangles will. - /// - /// This method is actually somewhat faster than [`Mesh::compute_smooth_normals`] because an - /// intermediate result of triangle normal calculation is already scaled by the triangle's area. - /// - /// If you would rather have the computed normals be influenced only by the angles of connected - /// edges, see [`Mesh::compute_smooth_normals`] instead. If you need to weight them in some - /// other way, see [`Mesh::compute_custom_smooth_normals`]. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_area_weighted_normals`] - pub fn compute_area_weighted_normals(&mut self) { - self.try_compute_area_weighted_normals() - .expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method weights normals by the area of each triangle containing the vertex. Thus, - /// larger triangles will skew the normals of their vertices towards their own normal more - /// than smaller triangles will. - /// - /// This method is actually somewhat faster than [`Mesh::compute_smooth_normals`] because an - /// intermediate result of triangle normal calculation is already scaled by the triangle's area. - /// - /// If you would rather have the computed normals be influenced only by the angles of connected - /// edges, see [`Mesh::compute_smooth_normals`] instead. If you need to weight them in some - /// other way, see [`Mesh::compute_custom_smooth_normals`]. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_compute_area_weighted_normals(&mut self) -> Result<(), MeshAccessError> { - self.try_compute_custom_smooth_normals(|[a, b, c], positions, normals| { - let normal = Vec3::from(triangle_area_normal( - positions[a], - positions[b], - positions[c], - )); - [a, b, c].into_iter().for_each(|pos| { - normals[pos] += normal; - }); - }) - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method allows you to customize how normals are weighted via the `per_triangle` parameter, - /// which must be a function or closure that accepts 3 parameters: - /// - The indices of the three vertices of the triangle as a `[usize; 3]`. - /// - A reference to the values of the [`Mesh::ATTRIBUTE_POSITION`] of the mesh (`&[[f32; 3]]`). - /// - A mutable reference to the sums of all normals so far. - /// - /// See also the standard methods included in Bevy for calculating smooth normals: - /// - [`Mesh::compute_smooth_normals`] - /// - [`Mesh::compute_area_weighted_normals`] - /// - /// An example that would weight each connected triangle's normal equally, thus skewing normals - /// towards the planes divided into the most triangles: - /// ``` - /// # use bevy_asset::RenderAssetUsages; - /// # use bevy_mesh::{Mesh, PrimitiveTopology, Meshable, MeshBuilder}; - /// # use bevy_math::{Vec3, primitives::Cuboid}; - /// # let mut mesh = Cuboid::default().mesh().build(); - /// mesh.compute_custom_smooth_normals(|[a, b, c], positions, normals| { - /// let normal = Vec3::from(bevy_mesh::triangle_normal(positions[a], positions[b], positions[c])); - /// for idx in [a, b, c] { - /// normals[idx] += normal; - /// } - /// }); - /// ``` - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_compute_custom_smooth_normals`] - // - // FIXME: This should handle more cases since this is called as a part of gltf - // mesh loading where we can't really blame users for loading meshes that might - // not conform to the limitations here! - // - // When fixed, also update "Panics" sections of - // - [Mesh::compute_smooth_normals] - // - [Mesh::with_computed_smooth_normals] - // - [Mesh::compute_area_weighted_normals] - // - [Mesh::with_computed_area_weighted_normals] - pub fn compute_custom_smooth_normals( - &mut self, - per_triangle: impl FnMut([usize; 3], &[[f32; 3]], &mut [Vec3]), - ) { - self.try_compute_custom_smooth_normals(per_triangle) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared - /// vertices. - /// - /// This method allows you to customize how normals are weighted via the `per_triangle` parameter, - /// which must be a function or closure that accepts 3 parameters: - /// - The indices of the three vertices of the triangle as a `[usize; 3]`. - /// - A reference to the values of the [`Mesh::ATTRIBUTE_POSITION`] of the mesh (`&[[f32; 3]]`). - /// - A mutable reference to the sums of all normals so far. - /// - /// See also the standard methods included in Bevy for calculating smooth normals: - /// - [`Mesh::compute_smooth_normals`] - /// - [`Mesh::compute_area_weighted_normals`] - /// - /// An example that would weight each connected triangle's normal equally, thus skewing normals - /// towards the planes divided into the most triangles: - /// ``` - /// # use bevy_asset::RenderAssetUsages; - /// # use bevy_mesh::{Mesh, PrimitiveTopology, Meshable, MeshBuilder}; - /// # use bevy_math::{Vec3, primitives::Cuboid}; - /// # let mut mesh = Cuboid::default().mesh().build(); - /// mesh.compute_custom_smooth_normals(|[a, b, c], positions, normals| { - /// let normal = Vec3::from(bevy_mesh::triangle_normal(positions[a], positions[b], positions[c])); - /// for idx in [a, b, c] { - /// normals[idx] += normal; - /// } - /// }); - /// ``` - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - // - // FIXME: This should handle more cases since this is called as a part of gltf - // mesh loading where we can't really blame users for loading meshes that might - // not conform to the limitations here! - // - // When fixed, also update "Panics" sections of - // - [Mesh::compute_smooth_normals] - // - [Mesh::with_computed_smooth_normals] - // - [Mesh::compute_area_weighted_normals] - // - [Mesh::with_computed_area_weighted_normals] - pub fn try_compute_custom_smooth_normals( - &mut self, - mut per_triangle: impl FnMut([usize; 3], &[[f32; 3]], &mut [Vec3]), - ) -> Result<(), MeshAccessError> { - assert!( - matches!(self.primitive_topology, PrimitiveTopology::TriangleList), - "smooth normals can only be computed on `TriangleList`s" - ); - assert!( - self.try_indices_option()?.is_some(), - "smooth normals can only be computed on indexed meshes" - ); - - let positions = self - .try_attribute(Mesh::ATTRIBUTE_POSITION)? - .as_float3() - .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); - - let mut normals = vec![Vec3::ZERO; positions.len()]; - - self.try_indices()? - .iter() - .collect::>() - .chunks_exact(3) - .for_each(|face| per_triangle([face[0], face[1], face[2]], positions, &mut normals)); - - for normal in &mut normals { - *normal = normal.try_normalize().unwrap_or(Vec3::ZERO); - } - - self.try_insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat - /// normals. - /// - /// (Alternatively, you can use [`Mesh::compute_normals`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_normals`] - #[must_use] - pub fn with_computed_normals(self) -> Self { - self.try_with_computed_normals() - .expect(MESH_EXTRACTED_ERROR) - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat - /// normals. - /// - /// (Alternatively, you can use [`Mesh::compute_normals`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - pub fn try_with_computed_normals(mut self) -> Result { - self.try_compute_normals()?; - Ok(self) - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_flat_normals`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh has indices defined - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_flat_normals`] - pub fn with_computed_flat_normals(mut self) -> Self { - self.compute_flat_normals(); - self - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_flat_normals`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh has indices defined - pub fn try_with_computed_flat_normals(mut self) -> Result { - self.try_compute_flat_normals()?; - Ok(self) - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_smooth_normals`] to mutate an existing mesh in-place) - /// - /// This method weights normals by the angles of triangle corners connected to each vertex. If - /// you would rather have the computed normals be weighted by triangle area, see - /// [`Mesh::with_computed_area_weighted_normals`] instead. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_smooth_normals`] - pub fn with_computed_smooth_normals(mut self) -> Self { - self.compute_smooth_normals(); - self - } - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_smooth_normals`] to mutate an existing mesh in-place) - /// - /// This method weights normals by the angles of triangle corners connected to each vertex. If - /// you would rather have the computed normals be weighted by triangle area, see - /// [`Mesh::with_computed_area_weighted_normals`] instead. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_with_computed_smooth_normals(mut self) -> Result { - self.try_compute_smooth_normals()?; - Ok(self) - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_area_weighted_normals`] to mutate an existing mesh in-place) - /// - /// This method weights normals by the area of each triangle containing the vertex. Thus, - /// larger triangles will skew the normals of their vertices towards their own normal more - /// than smaller triangles will. If you would rather have the computed normals be influenced - /// only by the angles of connected edges, see [`Mesh::with_computed_smooth_normals`] instead. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_computed_area_weighted_normals`] - pub fn with_computed_area_weighted_normals(mut self) -> Self { - self.compute_area_weighted_normals(); - self - } - - /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. - /// - /// (Alternatively, you can use [`Mesh::compute_area_weighted_normals`] to mutate an existing mesh in-place) - /// - /// This method weights normals by the area of each triangle containing the vertex. Thus, - /// larger triangles will skew the normals of their vertices towards their own normal more - /// than smaller triangles will. If you would rather have the computed normals be influenced - /// only by the angles of connected edges, see [`Mesh::with_computed_smooth_normals`] instead. - /// - /// # Panics - /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. - /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. - /// Panics if the mesh does not have indices defined. - pub fn try_with_computed_area_weighted_normals(mut self) -> Result { - self.try_compute_area_weighted_normals()?; - Ok(self) - } - - /// Generate tangents for the mesh using the `mikktspace` algorithm. - /// - /// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. - /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. - #[cfg(feature = "bevy_mikktspace")] - pub fn generate_tangents(&mut self) -> Result<(), super::GenerateTangentsError> { - let tangents = super::generate_tangents_for_mesh(self)?; - self.try_insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents)?; - Ok(()) - } - - /// Consumes the mesh and returns a mesh with tangents generated using the `mikktspace` algorithm. - /// - /// The resulting mesh will have the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. - /// - /// (Alternatively, you can use [`Mesh::generate_tangents`] to mutate an existing mesh in-place) - /// - /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. - #[cfg(feature = "bevy_mikktspace")] - pub fn with_generated_tangents(mut self) -> Result { - self.generate_tangents()?; - Ok(self) - } - - /// Merges the [`Mesh`] data of `other` with `self`. The attributes and indices of `other` will be appended to `self`. - /// - /// Note that attributes of `other` that don't exist on `self` will be ignored. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Errors - /// - /// If any of the following conditions are not met, this function errors: - /// * All of the vertex attributes that have the same attribute id, must also - /// have the same attribute type. - /// For example two attributes with the same id, but where one is a - /// [`VertexAttributeValues::Float32`] and the other is a - /// [`VertexAttributeValues::Float32x3`], would be invalid. - /// * Both meshes must have the same primitive topology. - pub fn merge(&mut self, other: &Mesh) -> Result<(), MeshMergeError> { - use VertexAttributeValues::*; - - // Check if the meshes `primitive_topology` field is the same, - // as if that is not the case, the resulting mesh could (and most likely would) - // be invalid. - if self.primitive_topology != other.primitive_topology { - return Err(MeshMergeError::IncompatiblePrimitiveTopology { - self_primitive_topology: self.primitive_topology, - other_primitive_topology: other.primitive_topology, - }); - } - - // The indices of `other` should start after the last vertex of `self`. - let index_offset = self.count_vertices(); - - // Extend attributes of `self` with attributes of `other`. - for (attribute, values) in self.try_attributes_mut()? { - if let Some(other_values) = other.try_attribute_option(attribute.id)? { - #[expect( - clippy::match_same_arms, - reason = "Although the bindings on some match arms may have different types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever." - )] - match (values, other_values) { - (Float32(vec1), Float32(vec2)) => vec1.extend(vec2), - (Sint32(vec1), Sint32(vec2)) => vec1.extend(vec2), - (Uint32(vec1), Uint32(vec2)) => vec1.extend(vec2), - (Float32x2(vec1), Float32x2(vec2)) => vec1.extend(vec2), - (Sint32x2(vec1), Sint32x2(vec2)) => vec1.extend(vec2), - (Uint32x2(vec1), Uint32x2(vec2)) => vec1.extend(vec2), - (Float32x3(vec1), Float32x3(vec2)) => vec1.extend(vec2), - (Sint32x3(vec1), Sint32x3(vec2)) => vec1.extend(vec2), - (Uint32x3(vec1), Uint32x3(vec2)) => vec1.extend(vec2), - (Sint32x4(vec1), Sint32x4(vec2)) => vec1.extend(vec2), - (Uint32x4(vec1), Uint32x4(vec2)) => vec1.extend(vec2), - (Float32x4(vec1), Float32x4(vec2)) => vec1.extend(vec2), - (Sint16x2(vec1), Sint16x2(vec2)) => vec1.extend(vec2), - (Snorm16x2(vec1), Snorm16x2(vec2)) => vec1.extend(vec2), - (Uint16x2(vec1), Uint16x2(vec2)) => vec1.extend(vec2), - (Unorm16x2(vec1), Unorm16x2(vec2)) => vec1.extend(vec2), - (Sint16x4(vec1), Sint16x4(vec2)) => vec1.extend(vec2), - (Snorm16x4(vec1), Snorm16x4(vec2)) => vec1.extend(vec2), - (Uint16x4(vec1), Uint16x4(vec2)) => vec1.extend(vec2), - (Unorm16x4(vec1), Unorm16x4(vec2)) => vec1.extend(vec2), - (Sint8x2(vec1), Sint8x2(vec2)) => vec1.extend(vec2), - (Snorm8x2(vec1), Snorm8x2(vec2)) => vec1.extend(vec2), - (Uint8x2(vec1), Uint8x2(vec2)) => vec1.extend(vec2), - (Unorm8x2(vec1), Unorm8x2(vec2)) => vec1.extend(vec2), - (Sint8x4(vec1), Sint8x4(vec2)) => vec1.extend(vec2), - (Snorm8x4(vec1), Snorm8x4(vec2)) => vec1.extend(vec2), - (Uint8x4(vec1), Uint8x4(vec2)) => vec1.extend(vec2), - (Unorm8x4(vec1), Unorm8x4(vec2)) => vec1.extend(vec2), - _ => { - return Err(MeshMergeError::IncompatibleVertexAttributes { - self_attribute: *attribute, - other_attribute: other - .try_attribute_data(attribute.id)? - .map(|data| data.attribute), - }) - } - } - } - } - - // Extend indices of `self` with indices of `other`. - if let (Some(indices), Some(other_indices)) = - (self.try_indices_mut_option()?, other.try_indices_option()?) - { - indices.extend(other_indices.iter().map(|i| (i + index_offset) as u32)); - } - Ok(()) - } - - /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_transformed_by`] - pub fn transformed_by(mut self, transform: Transform) -> Self { - self.transform_by(transform); - self - } - - /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_transformed_by(mut self, transform: Transform) -> Result { - self.try_transform_by(transform)?; - Ok(self) - } - - /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_transform_by`] - pub fn transform_by(&mut self, transform: Transform) { - self.try_transform_by(transform) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_transform_by(&mut self, transform: Transform) -> Result<(), MeshAccessError> { - // Needed when transforming normals and tangents - let scale_recip = 1. / transform.scale; - debug_assert!( - transform.scale.yzx() * transform.scale.zxy() != Vec3::ZERO, - "mesh transform scale cannot be zero on more than one axis" - ); - - if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? - { - // Apply scale, rotation, and translation to vertex positions - positions - .iter_mut() - .for_each(|pos| *pos = transform.transform_point(Vec3::from_slice(pos)).to_array()); - } - - // No need to transform normals or tangents if rotation is near identity and scale is uniform - if transform.rotation.is_near_identity() - && transform.scale.x == transform.scale.y - && transform.scale.y == transform.scale.z - { - return Ok(()); - } - - if let Some(VertexAttributeValues::Float32x3(normals)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_NORMAL)? - { - // Transform normals, taking into account non-uniform scaling and rotation - normals.iter_mut().for_each(|normal| { - *normal = (transform.rotation - * scale_normal(Vec3::from_array(*normal), scale_recip)) - .to_array(); - }); - } - - if let Some(VertexAttributeValues::Float32x4(tangents)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_TANGENT)? - { - // Transform tangents, taking into account non-uniform scaling and rotation - tangents.iter_mut().for_each(|tangent| { - let handedness = tangent[3]; - let scaled_tangent = Vec3::from_slice(tangent) * transform.scale; - *tangent = (transform.rotation * scaled_tangent.normalize_or_zero()) - .extend(handedness) - .to_array(); - }); - } - - Ok(()) - } - - /// Translates the vertex positions of the mesh by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_translated_by`] - pub fn translated_by(mut self, translation: Vec3) -> Self { - self.translate_by(translation); - self - } - - /// Translates the vertex positions of the mesh by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_translated_by(mut self, translation: Vec3) -> Result { - self.try_translate_by(translation)?; - Ok(self) - } - - /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_translate_by`] - pub fn translate_by(&mut self, translation: Vec3) { - self.try_translate_by(translation) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_translate_by(&mut self, translation: Vec3) -> Result<(), MeshAccessError> { - if translation == Vec3::ZERO { - return Ok(()); - } - - if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? - { - // Apply translation to vertex positions - positions - .iter_mut() - .for_each(|pos| *pos = (Vec3::from_slice(pos) + translation).to_array()); - } - - Ok(()) - } - - /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_rotated_by`] - pub fn rotated_by(mut self, rotation: Quat) -> Self { - self.try_rotate_by(rotation).expect(MESH_EXTRACTED_ERROR); - self - } - - /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_rotated_by(mut self, rotation: Quat) -> Result { - self.try_rotate_by(rotation)?; - Ok(self) - } - - /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_rotate_by`] - pub fn rotate_by(&mut self, rotation: Quat) { - self.try_rotate_by(rotation).expect(MESH_EXTRACTED_ERROR); - } - - /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_rotate_by(&mut self, rotation: Quat) -> Result<(), MeshAccessError> { - if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? - { - // Apply rotation to vertex positions - positions - .iter_mut() - .for_each(|pos| *pos = (rotation * Vec3::from_slice(pos)).to_array()); - } - - // No need to transform normals or tangents if rotation is near identity - if rotation.is_near_identity() { - return Ok(()); - } - - if let Some(VertexAttributeValues::Float32x3(normals)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_NORMAL)? - { - // Transform normals - normals.iter_mut().for_each(|normal| { - *normal = (rotation * Vec3::from_slice(normal).normalize_or_zero()).to_array(); - }); - } - - if let Some(VertexAttributeValues::Float32x4(tangents)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_TANGENT)? - { - // Transform tangents - tangents.iter_mut().for_each(|tangent| { - let handedness = tangent[3]; - *tangent = (rotation * Vec3::from_slice(tangent).normalize_or_zero()) - .extend(handedness) - .to_array(); - }); - } - - Ok(()) - } - - /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_scaled_by`] - pub fn scaled_by(mut self, scale: Vec3) -> Self { - self.scale_by(scale); - self - } - - /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_scaled_by(mut self, scale: Vec3) -> Result { - self.try_scale_by(scale)?; - Ok(self) - } - - /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_scale_by`] - pub fn scale_by(&mut self, scale: Vec3) { - self.try_scale_by(scale).expect(MESH_EXTRACTED_ERROR); - } - - /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`]. - /// - /// `Aabb` of entities with modified mesh are not updated automatically. - pub fn try_scale_by(&mut self, scale: Vec3) -> Result<(), MeshAccessError> { - // Needed when transforming normals and tangents - let scale_recip = 1. / scale; - debug_assert!( - scale.yzx() * scale.zxy() != Vec3::ZERO, - "mesh transform scale cannot be zero on more than one axis" - ); - - if let Some(VertexAttributeValues::Float32x3(positions)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_POSITION)? - { - // Apply scale to vertex positions - positions - .iter_mut() - .for_each(|pos| *pos = (scale * Vec3::from_slice(pos)).to_array()); - } - - // No need to transform normals or tangents if scale is uniform - if scale.x == scale.y && scale.y == scale.z { - return Ok(()); - } - - if let Some(VertexAttributeValues::Float32x3(normals)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_NORMAL)? - { - // Transform normals, taking into account non-uniform scaling - normals.iter_mut().for_each(|normal| { - *normal = scale_normal(Vec3::from_array(*normal), scale_recip).to_array(); - }); - } - - if let Some(VertexAttributeValues::Float32x4(tangents)) = - self.try_attribute_mut_option(Mesh::ATTRIBUTE_TANGENT)? - { - // Transform tangents, taking into account non-uniform scaling - tangents.iter_mut().for_each(|tangent| { - let handedness = tangent[3]; - let scaled_tangent = Vec3::from_slice(tangent) * scale; - *tangent = scaled_tangent - .normalize_or_zero() - .extend(handedness) - .to_array(); - }); - } - - Ok(()) - } - - /// Normalize joint weights so they sum to 1. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_normalize_joint_weights`] - pub fn normalize_joint_weights(&mut self) { - self.try_normalize_joint_weights() - .expect(MESH_EXTRACTED_ERROR); - } - - /// Normalize joint weights so they sum to 1. - pub fn try_normalize_joint_weights(&mut self) -> Result<(), MeshAccessError> { - if let Some(VertexAttributeValues::Float32x4(joints)) = - self.try_attribute_mut_option(Self::ATTRIBUTE_JOINT_WEIGHT)? - { - for weights in joints.iter_mut() { - // force negative weights to zero - weights.iter_mut().for_each(|w| *w = w.max(0.0)); - - let sum: f32 = weights.iter().sum(); - if sum == 0.0 { - // all-zero weights are invalid - weights[0] = 1.0; - } else { - let recip = sum.recip(); - for weight in weights.iter_mut() { - *weight *= recip; - } - } - } - } - - Ok(()) - } - - /// Get a list of this Mesh's [triangles] as an iterator if possible. - /// - /// Returns an error if any of the following conditions are met (see [`MeshTrianglesError`]): - /// * The Mesh's [primitive topology] is not `TriangleList` or `TriangleStrip`. - /// * The Mesh is missing position or index data. - /// * The Mesh's position data has the wrong format (not `Float32x3`). - /// - /// [primitive topology]: PrimitiveTopology - /// [triangles]: Triangle3d - pub fn triangles(&self) -> Result + '_, MeshTrianglesError> { - let position_data = self.try_attribute(Mesh::ATTRIBUTE_POSITION)?; - - let Some(vertices) = position_data.as_float3() else { - return Err(MeshTrianglesError::PositionsFormat); - }; - - let indices = self.try_indices()?; - - match self.primitive_topology { - PrimitiveTopology::TriangleList => { - // When indices reference out-of-bounds vertex data, the triangle is omitted. - // This implicitly truncates the indices to a multiple of 3. - let iterator = match indices { - Indices::U16(vec) => FourIterators::First( - vec.as_slice() - .chunks_exact(3) - .flat_map(move |indices| indices_to_triangle(vertices, indices)), - ), - Indices::U32(vec) => FourIterators::Second( - vec.as_slice() - .chunks_exact(3) - .flat_map(move |indices| indices_to_triangle(vertices, indices)), - ), - }; - - return Ok(iterator); - } - - PrimitiveTopology::TriangleStrip => { - // When indices reference out-of-bounds vertex data, the triangle is omitted. - // If there aren't enough indices to make a triangle, then an empty vector will be - // returned. - let iterator = match indices { - Indices::U16(vec) => { - FourIterators::Third(vec.as_slice().windows(3).enumerate().flat_map( - move |(i, indices)| { - if i % 2 == 0 { - indices_to_triangle(vertices, indices) - } else { - indices_to_triangle( - vertices, - &[indices[1], indices[0], indices[2]], - ) - } - }, - )) - } - Indices::U32(vec) => { - FourIterators::Fourth(vec.as_slice().windows(3).enumerate().flat_map( - move |(i, indices)| { - if i % 2 == 0 { - indices_to_triangle(vertices, indices) - } else { - indices_to_triangle( - vertices, - &[indices[1], indices[0], indices[2]], - ) - } - }, - )) - } - }; - - return Ok(iterator); - } - - _ => { - return Err(MeshTrianglesError::WrongTopology); - } - }; - - fn indices_to_triangle + Copy>( - vertices: &[[f32; 3]], - indices: &[T], - ) -> Option { - let vert0: Vec3 = Vec3::from(*vertices.get(indices[0].try_into().ok()?)?); - let vert1: Vec3 = Vec3::from(*vertices.get(indices[1].try_into().ok()?)?); - let vert2: Vec3 = Vec3::from(*vertices.get(indices[2].try_into().ok()?)?); - Some(Triangle3d { - vertices: [vert0, vert1, vert2], - }) - } - } - - /// Extracts the mesh vertex, index and morph target data for GPU upload. - /// This function is called internally in render world extraction, it is - /// unlikely to be useful outside of that context. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn take_gpu_data(&mut self) -> Result { - let attributes = self.attributes.extract()?; - let indices = self.indices.extract()?; - #[cfg(feature = "morph")] - let morph_targets = self.morph_targets.extract()?; - #[cfg(feature = "morph")] - let morph_target_names = self.morph_target_names.extract()?; - - // store the aabb extents as they cannot be computed after extraction - if let Some(MeshAttributeData { - values: VertexAttributeValues::Float32x3(position_values), - .. - }) = attributes - .as_ref_option()? - .and_then(|attrs| attrs.get(&Self::ATTRIBUTE_POSITION.id)) - && !position_values.is_empty() - { - let mut iter = position_values.iter().map(|p| Vec3::from_slice(p)); - let mut min = iter.next().unwrap(); - let mut max = min; - for v in iter { - min = Vec3::min(min, v); - max = Vec3::max(max, v); - } - self.final_aabb = Some(Aabb3d::from_min_max(min, max)); - } - - Ok(Self { - attributes, - indices, - #[cfg(feature = "morph")] - morph_targets, - #[cfg(feature = "morph")] - morph_target_names, - ..self.clone() - }) - } -} - -#[cfg(feature = "morph")] -impl Mesh { - /// Whether this mesh has morph targets. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_has_morph_targets`] - pub fn has_morph_targets(&self) -> bool { - self.try_has_morph_targets().expect(MESH_EXTRACTED_ERROR) - } - - /// Whether this mesh has morph targets. - pub fn try_has_morph_targets(&self) -> Result { - Ok(self.morph_targets.as_ref_option()?.is_some()) - } - - /// Set [morph targets] image for this mesh. This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. - /// - /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_set_morph_targets`] - pub fn set_morph_targets(&mut self, morph_targets: Handle) { - self.try_set_morph_targets(morph_targets) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Set [morph targets] image for this mesh. This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. - /// - /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation - pub fn try_set_morph_targets( - &mut self, - morph_targets: Handle, - ) -> Result<(), MeshAccessError> { - self.morph_targets.replace(Some(morph_targets))?; - Ok(()) - } - - /// Retrieve the morph targets for this mesh, or None if there are no morph targets. - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_morph_targets`] - pub fn morph_targets(&self) -> Option<&Handle> { - self.morph_targets - .as_ref_option() - .expect(MESH_EXTRACTED_ERROR) - } - - /// Retrieve the morph targets for this mesh, or None if there are no morph targets. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the morph targets do not exist. - pub fn try_morph_targets(&self) -> Result<&Handle, MeshAccessError> { - self.morph_targets.as_ref() - } - - /// Consumes the mesh and returns a mesh with the given [morph targets]. - /// - /// This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. - /// - /// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place) - /// - /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_with_morph_targets`] - #[must_use] - pub fn with_morph_targets(mut self, morph_targets: Handle) -> Self { - self.set_morph_targets(morph_targets); - self - } - - /// Consumes the mesh and returns a mesh with the given [morph targets]. - /// - /// This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. - /// - /// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place) - /// - /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_with_morph_targets( - mut self, - morph_targets: Handle, - ) -> Result { - self.try_set_morph_targets(morph_targets)?; - Ok(self) - } - - /// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_set_morph_target_names`] - pub fn set_morph_target_names(&mut self, names: Vec) { - self.try_set_morph_target_names(names) - .expect(MESH_EXTRACTED_ERROR); - } - - /// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_set_morph_target_names( - &mut self, - names: Vec, - ) -> Result<(), MeshAccessError> { - self.morph_target_names.replace(Some(names))?; - Ok(()) - } - - /// Consumes the mesh and returns a mesh with morph target names. - /// Names should correspond to the order of the morph targets in `set_morph_targets`. - /// - /// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place) - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_set_morph_target_names`] - #[must_use] - pub fn with_morph_target_names(self, names: Vec) -> Self { - self.try_with_morph_target_names(names) - .expect(MESH_EXTRACTED_ERROR) - } - - /// Consumes the mesh and returns a mesh with morph target names. - /// Names should correspond to the order of the morph targets in `set_morph_targets`. - /// - /// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place) - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`. - pub fn try_with_morph_target_names( - mut self, - names: Vec, - ) -> Result { - self.try_set_morph_target_names(names)?; - Ok(self) - } - - /// Gets a list of all morph target names, if they exist. - /// - /// # Panics - /// Panics when the mesh data has already been extracted to `RenderWorld`. To handle - /// this as an error use [`Mesh::try_morph_target_names`] - pub fn morph_target_names(&self) -> Option<&[String]> { - self.try_morph_target_names().expect(MESH_EXTRACTED_ERROR) - } - - /// Gets a list of all morph target names, if they exist. - /// - /// Returns an error if the mesh data has been extracted to `RenderWorld`or - /// if the morph targets do not exist. - pub fn try_morph_target_names(&self) -> Result, MeshAccessError> { - Ok(self - .morph_target_names - .as_ref_option()? - .map(core::ops::Deref::deref)) - } -} - -/// Correctly scales and renormalizes an already normalized `normal` by the scale determined by its reciprocal `scale_recip` -pub(crate) fn scale_normal(normal: Vec3, scale_recip: Vec3) -> Vec3 { - // This is basically just `normal * scale_recip` but with the added rule that `0. * anything == 0.` - // This is necessary because components of `scale_recip` may be infinities, which do not multiply to zero - let n = Vec3::select(normal.cmpeq(Vec3::ZERO), Vec3::ZERO, normal * scale_recip); - - // If n is finite, no component of `scale_recip` was infinite or the normal was perpendicular to the scale - // else the scale had at least one zero-component and the normal needs to point along the direction of that component - if n.is_finite() { - n.normalize_or_zero() - } else { - Vec3::select(n.abs().cmpeq(Vec3::INFINITY), n.signum(), Vec3::ZERO).normalize() - } -} - -impl core::ops::Mul for Transform { - type Output = Mesh; - - fn mul(self, rhs: Mesh) -> Self::Output { - rhs.transformed_by(self) - } -} - -/// A version of [`Mesh`] suitable for serializing for short-term transfer. -/// -/// [`Mesh`] does not implement [`Serialize`] / [`Deserialize`] because it is made with the renderer in mind. -/// It is not a general-purpose mesh implementation, and its internals are subject to frequent change. -/// As such, storing a [`Mesh`] on disk is highly discouraged. -/// -/// But there are still some valid use cases for serializing a [`Mesh`], namely transferring meshes between processes. -/// To support this, you can create a [`SerializedMesh`] from a [`Mesh`] with [`SerializedMesh::from_mesh`], -/// and then deserialize it with [`SerializedMesh::deserialize`]. The caveats are: -/// - The mesh representation is not valid across different versions of Bevy. -/// - This conversion is lossy. Only the following information is preserved: -/// - Primitive topology -/// - Vertex attributes -/// - Indices -/// - Custom attributes that were not specified with [`MeshDeserializer::add_custom_vertex_attribute`] will be ignored while deserializing. -#[cfg(feature = "serialize")] -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializedMesh { - primitive_topology: PrimitiveTopology, - attributes: Vec<(MeshVertexAttributeId, SerializedMeshAttributeData)>, - indices: Option, -} - -#[cfg(feature = "serialize")] -impl SerializedMesh { - /// Create a [`SerializedMesh`] from a [`Mesh`]. See the documentation for [`SerializedMesh`] for caveats. - pub fn from_mesh(mut mesh: Mesh) -> Self { - Self { - primitive_topology: mesh.primitive_topology, - attributes: mesh - .attributes - .replace(None) - .expect(MESH_EXTRACTED_ERROR) - .unwrap() - .into_iter() - .map(|(id, data)| { - ( - id, - SerializedMeshAttributeData::from_mesh_attribute_data(data), - ) - }) - .collect(), - indices: mesh.indices.replace(None).expect(MESH_EXTRACTED_ERROR), - } - } - - /// Create a [`Mesh`] from a [`SerializedMesh`]. See the documentation for [`SerializedMesh`] for caveats. - /// - /// Use [`MeshDeserializer`] if you need to pass extra options to the deserialization process, such as specifying custom vertex attributes. - pub fn into_mesh(self) -> Mesh { - MeshDeserializer::default().deserialize(self) - } -} - -/// Use to specify extra options when deserializing a [`SerializedMesh`] into a [`Mesh`]. -#[cfg(feature = "serialize")] -pub struct MeshDeserializer { - custom_vertex_attributes: HashMap, MeshVertexAttribute>, -} - -#[cfg(feature = "serialize")] -impl Default for MeshDeserializer { - fn default() -> Self { - // Written like this so that the compiler can validate that we use all the built-in attributes. - // If you just added a new attribute and got a compile error, please add it to this list :) - const BUILTINS: [MeshVertexAttribute; Mesh::FIRST_AVAILABLE_CUSTOM_ATTRIBUTE as usize] = [ - Mesh::ATTRIBUTE_POSITION, - Mesh::ATTRIBUTE_NORMAL, - Mesh::ATTRIBUTE_UV_0, - Mesh::ATTRIBUTE_UV_1, - Mesh::ATTRIBUTE_TANGENT, - Mesh::ATTRIBUTE_COLOR, - Mesh::ATTRIBUTE_JOINT_WEIGHT, - Mesh::ATTRIBUTE_JOINT_INDEX, - ]; - Self { - custom_vertex_attributes: BUILTINS - .into_iter() - .map(|attribute| (attribute.name.into(), attribute)) - .collect(), - } - } -} - -#[cfg(feature = "serialize")] -impl MeshDeserializer { - /// Create a new [`MeshDeserializer`]. - pub fn new() -> Self { - Self::default() - } - - /// Register a custom vertex attribute to the deserializer. Custom vertex attributes that were not added with this method will be ignored while deserializing. - pub fn add_custom_vertex_attribute( - &mut self, - name: &str, - attribute: MeshVertexAttribute, - ) -> &mut Self { - self.custom_vertex_attributes.insert(name.into(), attribute); - self - } - - /// Deserialize a [`SerializedMesh`] into a [`Mesh`]. - /// - /// See the documentation for [`SerializedMesh`] for caveats. - pub fn deserialize(&self, serialized_mesh: SerializedMesh) -> Mesh { - Mesh { - attributes: MeshExtractableData::Data( - serialized_mesh - .attributes - .into_iter() - .filter_map(|(id, data)| { - let attribute = data.attribute.clone(); - let Some(data) = - data.try_into_mesh_attribute_data(&self.custom_vertex_attributes) - else { - warn!( - "Deserialized mesh contains custom vertex attribute {attribute:?} that \ - was not specified with `MeshDeserializer::add_custom_vertex_attribute`. Ignoring." - ); - return None; - }; - Some((id, data)) - }) - .collect()), - indices: serialized_mesh.indices.into(), - ..Mesh::new(serialized_mesh.primitive_topology, RenderAssetUsages::default()) - } - } -} - -/// Error that can occur when calling [`Mesh::merge`]. +/// Error that can occur when calling [`MeshExtractableData::merge`]. #[derive(Error, Debug, Clone)] pub enum MeshMergeError { #[error("Incompatible vertex attribute types: {} and {}", self_attribute.name, other_attribute.map(|a| a.name).unwrap_or("None"))] @@ -2511,416 +456,4 @@ pub enum MeshMergeError { self_primitive_topology: PrimitiveTopology, other_primitive_topology: PrimitiveTopology, }, - #[error("Mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), -} - -#[cfg(test)] -mod tests { - use super::Mesh; - #[cfg(feature = "serialize")] - use super::SerializedMesh; - use crate::mesh::{Indices, MeshWindingInvertError, VertexAttributeValues}; - use crate::PrimitiveTopology; - use bevy_asset::RenderAssetUsages; - use bevy_math::bounding::Aabb3d; - use bevy_math::primitives::Triangle3d; - use bevy_math::Vec3; - use bevy_transform::components::Transform; - - #[test] - #[should_panic] - fn panic_invalid_format() { - let _mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]); - } - - #[test] - fn transform_mesh() { - let mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![[-1., -1., 2.], [1., -1., 2.], [0., 1., 2.]], - ) - .with_inserted_attribute( - Mesh::ATTRIBUTE_NORMAL, - vec![ - Vec3::new(-1., -1., 1.).normalize().to_array(), - Vec3::new(1., -1., 1.).normalize().to_array(), - [0., 0., 1.], - ], - ) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.], [1., 0.], [0.5, 1.]]); - - let mesh = mesh.transformed_by( - Transform::from_translation(Vec3::splat(-2.)).with_scale(Vec3::new(2., 0., -1.)), - ); - - if let Some(VertexAttributeValues::Float32x3(positions)) = - mesh.attribute(Mesh::ATTRIBUTE_POSITION) - { - // All positions are first scaled resulting in `vec![[-2, 0., -2.], [2., 0., -2.], [0., 0., -2.]]` - // and then shifted by `-2.` along each axis - assert_eq!( - positions, - &vec![[-4.0, -2.0, -4.0], [0.0, -2.0, -4.0], [-2.0, -2.0, -4.0]] - ); - } else { - panic!("Mesh does not have a position attribute"); - } - - if let Some(VertexAttributeValues::Float32x3(normals)) = - mesh.attribute(Mesh::ATTRIBUTE_NORMAL) - { - assert_eq!(normals, &vec![[0., -1., 0.], [0., -1., 0.], [0., 0., -1.]]); - } else { - panic!("Mesh does not have a normal attribute"); - } - - if let Some(VertexAttributeValues::Float32x2(uvs)) = mesh.attribute(Mesh::ATTRIBUTE_UV_0) { - assert_eq!(uvs, &vec![[0., 0.], [1., 0.], [0.5, 1.]]); - } else { - panic!("Mesh does not have a uv attribute"); - } - } - - #[test] - fn point_list_mesh_invert_winding() { - let mesh = Mesh::new(PrimitiveTopology::PointList, RenderAssetUsages::default()) - .with_inserted_indices(Indices::U32(vec![])); - assert!(matches!( - mesh.with_inverted_winding(), - Err(MeshWindingInvertError::WrongTopology) - )); - } - - #[test] - fn line_list_mesh_invert_winding() { - let mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) - .with_inserted_indices(Indices::U32(vec![0, 1, 1, 2, 2, 3])); - let mesh = mesh.with_inverted_winding().unwrap(); - assert_eq!( - mesh.indices().unwrap().iter().collect::>(), - vec![3, 2, 2, 1, 1, 0] - ); - } - - #[test] - fn line_list_mesh_invert_winding_fail() { - let mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) - .with_inserted_indices(Indices::U32(vec![0, 1, 1])); - assert!(matches!( - mesh.with_inverted_winding(), - Err(MeshWindingInvertError::AbruptIndicesEnd) - )); - } - - #[test] - fn line_strip_mesh_invert_winding() { - let mesh = Mesh::new(PrimitiveTopology::LineStrip, RenderAssetUsages::default()) - .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3])); - let mesh = mesh.with_inverted_winding().unwrap(); - assert_eq!( - mesh.indices().unwrap().iter().collect::>(), - vec![3, 2, 1, 0] - ); - } - - #[test] - fn triangle_list_mesh_invert_winding() { - let mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_indices(Indices::U32(vec![ - 0, 3, 1, // First triangle - 1, 3, 2, // Second triangle - ])); - let mesh = mesh.with_inverted_winding().unwrap(); - assert_eq!( - mesh.indices().unwrap().iter().collect::>(), - vec![ - 0, 1, 3, // First triangle - 1, 2, 3, // Second triangle - ] - ); - } - - #[test] - fn triangle_list_mesh_invert_winding_fail() { - let mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_indices(Indices::U32(vec![0, 3, 1, 2])); - assert!(matches!( - mesh.with_inverted_winding(), - Err(MeshWindingInvertError::AbruptIndicesEnd) - )); - } - - #[test] - fn triangle_strip_mesh_invert_winding() { - let mesh = Mesh::new( - PrimitiveTopology::TriangleStrip, - RenderAssetUsages::default(), - ) - .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3])); - let mesh = mesh.with_inverted_winding().unwrap(); - assert_eq!( - mesh.indices().unwrap().iter().collect::>(), - vec![3, 2, 1, 0] - ); - } - - #[test] - fn compute_area_weighted_normals() { - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ); - - // z y - // | / - // 3---2 - // | / \ - // 0-----1--x - - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], - ); - mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3])); - mesh.compute_area_weighted_normals(); - let normals = mesh - .attribute(Mesh::ATTRIBUTE_NORMAL) - .unwrap() - .as_float3() - .unwrap(); - assert_eq!(4, normals.len()); - // 0 - assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[0]); - // 1 - assert_eq!([0., 0., 1.], normals[1]); - // 2 - assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[2]); - // 3 - assert_eq!([1., 0., 0.], normals[3]); - } - - #[test] - fn compute_area_weighted_normals_proportionate() { - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ); - - // z y - // | / - // 3---2.. - // | / \ - // 0-------1---x - - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![[0., 0., 0.], [2., 0., 0.], [0., 1., 0.], [0., 0., 1.]], - ); - mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3])); - mesh.compute_area_weighted_normals(); - let normals = mesh - .attribute(Mesh::ATTRIBUTE_NORMAL) - .unwrap() - .as_float3() - .unwrap(); - assert_eq!(4, normals.len()); - // 0 - assert_eq!(Vec3::new(1., 0., 2.).normalize().to_array(), normals[0]); - // 1 - assert_eq!([0., 0., 1.], normals[1]); - // 2 - assert_eq!(Vec3::new(1., 0., 2.).normalize().to_array(), normals[2]); - // 3 - assert_eq!([1., 0., 0.], normals[3]); - } - - #[test] - fn compute_angle_weighted_normals() { - // CuboidMeshBuilder duplicates vertices (even though it is indexed) - - // 5---------4 - // /| /| - // 1-+-------0 | - // | 6-------|-7 - // |/ |/ - // 2---------3 - let verts = vec![ - [1.0, 1.0, 1.0], - [-1.0, 1.0, 1.0], - [-1.0, -1.0, 1.0], - [1.0, -1.0, 1.0], - [1.0, 1.0, -1.0], - [-1.0, 1.0, -1.0], - [-1.0, -1.0, -1.0], - [1.0, -1.0, -1.0], - ]; - - let indices = Indices::U16(vec![ - 0, 1, 2, 2, 3, 0, // front - 5, 4, 7, 7, 6, 5, // back - 1, 5, 6, 6, 2, 1, // left - 4, 0, 3, 3, 7, 4, // right - 4, 5, 1, 1, 0, 4, // top - 3, 2, 6, 6, 7, 3, // bottom - ]); - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ); - mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, verts); - mesh.insert_indices(indices); - mesh.compute_smooth_normals(); - - let normals = mesh - .attribute(Mesh::ATTRIBUTE_NORMAL) - .unwrap() - .as_float3() - .unwrap(); - - for new in normals.iter().copied().flatten() { - // std impl is unstable - const FRAC_1_SQRT_3: f32 = 0.57735026; - const MIN: f32 = FRAC_1_SQRT_3 - f32::EPSILON; - const MAX: f32 = FRAC_1_SQRT_3 + f32::EPSILON; - assert!(new.abs() >= MIN, "{new} < {MIN}"); - assert!(new.abs() <= MAX, "{new} > {MAX}"); - } - } - - #[test] - fn triangles_from_triangle_list() { - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ); - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![[0., 0., 0.], [1., 0., 0.], [1., 1., 0.], [0., 1., 0.]], - ); - mesh.insert_indices(Indices::U32(vec![0, 1, 2, 2, 3, 0])); - assert_eq!( - vec![ - Triangle3d { - vertices: [ - Vec3::new(0., 0., 0.), - Vec3::new(1., 0., 0.), - Vec3::new(1., 1., 0.), - ] - }, - Triangle3d { - vertices: [ - Vec3::new(1., 1., 0.), - Vec3::new(0., 1., 0.), - Vec3::new(0., 0., 0.), - ] - } - ], - mesh.triangles().unwrap().collect::>() - ); - } - - #[test] - fn triangles_from_triangle_strip() { - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleStrip, - RenderAssetUsages::default(), - ); - // Triangles: (0, 1, 2), (2, 1, 3), (2, 3, 4), (4, 3, 5) - // - // 4 - 5 - // | \ | - // 2 - 3 - // | \ | - // 0 - 1 - let positions: Vec = [ - [0., 0., 0.], - [1., 0., 0.], - [0., 1., 0.], - [1., 1., 0.], - [0., 2., 0.], - [1., 2., 0.], - ] - .into_iter() - .map(Vec3::from_array) - .collect(); - mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions.clone()); - mesh.insert_indices(Indices::U32(vec![0, 1, 2, 3, 4, 5])); - assert_eq!( - vec![ - Triangle3d { - vertices: [positions[0], positions[1], positions[2]] - }, - Triangle3d { - vertices: [positions[2], positions[1], positions[3]] - }, - Triangle3d { - vertices: [positions[2], positions[3], positions[4]] - }, - Triangle3d { - vertices: [positions[4], positions[3], positions[5]] - }, - ], - mesh.triangles().unwrap().collect::>() - ); - } - - #[test] - fn take_gpu_data_calculates_aabb() { - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ); - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![ - [-0.5, 0., 0.], - [-1., 0., 0.], - [-1., -1., 0.], - [-0.5, -1., 0.], - ], - ); - mesh.insert_indices(Indices::U32(vec![0, 1, 2, 2, 3, 0])); - mesh = mesh.take_gpu_data().unwrap(); - assert_eq!( - mesh.final_aabb, - Some(Aabb3d::from_min_max([-1., -1., 0.], [-0.5, 0., 0.])) - ); - } - - #[cfg(feature = "serialize")] - #[test] - fn serialize_deserialize_mesh() { - let mut mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ); - - mesh.insert_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![[0., 0., 0.], [2., 0., 0.], [0., 1., 0.], [0., 0., 1.]], - ); - mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3])); - - let serialized_mesh = SerializedMesh::from_mesh(mesh.clone()); - let serialized_string = serde_json::to_string(&serialized_mesh).unwrap(); - let serialized_mesh_from_string: SerializedMesh = - serde_json::from_str(&serialized_string).unwrap(); - let deserialized_mesh = serialized_mesh_from_string.into_mesh(); - assert_eq!(mesh, deserialized_mesh); - } } diff --git a/crates/bevy_mesh/src/mesh_extractable_data.rs b/crates/bevy_mesh/src/mesh_extractable_data.rs new file mode 100644 index 0000000000000..8ec37c4834c19 --- /dev/null +++ b/crates/bevy_mesh/src/mesh_extractable_data.rs @@ -0,0 +1,1642 @@ +use crate::{ + triangle_area_normal, triangle_normal, FourIterators, Indices, Mesh, MeshAttributeData, + MeshMergeError, MeshTrianglesError, MeshVertexAttribute, MeshVertexAttributeId, + MeshVertexBufferLayout, MeshVertexBufferLayoutRef, MeshVertexBufferLayouts, + MeshWindingInvertError, VertexAttributeValues, VertexBufferLayout, +}; +use alloc::collections::BTreeMap; +#[cfg(feature = "morph")] +use bevy_asset::Handle; +use bevy_asset::{Asset, ExtractableAssetAccessError}; +#[cfg(feature = "morph")] +use bevy_image::Image; +use bevy_math::{primitives::Triangle3d, Quat, Vec3, Vec3Swizzles as _}; +use bevy_reflect::Reflect; +use bevy_transform::components::Transform; +use bytemuck::cast_slice; +use tracing::warn; +use wgpu_types::{PrimitiveTopology, VertexAttribute, VertexFormat, VertexStepMode}; + +/// The data held by a Mesh that can be extracted to `RenderWorld` (if the asset usage is [`RenderAssetUsages::RENDER_WORLD`](bevy_asset::RenderAssetUsages) only), +/// use the methods in [`ExtractableAsset`](bevy_asset::ExtractableAsset) to handle errors and access or mutate it. +#[derive(Asset, Debug, Clone, Reflect, PartialEq)] +#[reflect(Clone)] +pub struct MeshExtractableData { + #[reflect(ignore, clone)] + pub(crate) primitive_topology: PrimitiveTopology, + /// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...) + /// for this mesh. Attribute ids to attribute values. + /// Uses a [`BTreeMap`] because, unlike `HashMap`, it has a defined iteration order, + /// which allows easy stable `VertexBuffers` (i.e. same buffer order) + #[reflect(ignore, clone)] + pub(crate) attributes: BTreeMap, + pub(crate) indices: Option, + #[cfg(feature = "morph")] + pub(crate) morph_targets: Option>, + #[cfg(feature = "morph")] + pub(crate) morph_target_names: Option>, +} + +impl MeshExtractableData { + /// Construct a new `MeshExtractableData`. You need to provide a [`PrimitiveTopology`] so that the + /// renderer knows how to treat the vertex data. Most of the time this will be + /// [`PrimitiveTopology::TriangleList`]. + pub fn new(primitive_topology: PrimitiveTopology) -> Self { + Self { + primitive_topology, + attributes: Default::default(), + indices: None, + #[cfg(feature = "morph")] + morph_targets: None, + #[cfg(feature = "morph")] + morph_target_names: None, + } + } + + /// Returns the topology of the `MeshExtractableData`. + pub fn primitive_topology(&self) -> PrimitiveTopology { + self.primitive_topology + } + + /// Sets the data for a vertex attribute (position, normal, etc.). The name will + /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + /// + /// Returns an error if the mesh data has been extracted to `RenderWorld`. + /// + /// # Panics + /// Panics when the format of the values does not match the attribute's format. + #[inline] + pub fn insert_attribute( + &mut self, + attribute: MeshVertexAttribute, + values: impl Into, + ) { + let values = values.into(); + let values_format = VertexFormat::from(&values); + if values_format != attribute.format { + panic!( + "Failed to insert attribute. Invalid attribute format for {}. Given format is {values_format:?} but expected {:?}", + attribute.name, attribute.format + ); + } + + self.attributes + .insert(attribute.id, MeshAttributeData { attribute, values }); + } + + /// Removes the data for a vertex attribute + /// Returns an error if the mesh data has been extracted to `RenderWorld`or + /// if the attribute does not exist. + pub fn remove_attribute( + &mut self, + attribute: impl Into, + ) -> Option { + self.attributes.remove(&attribute.into()).map(|v| v.values) + } + + /// Returns a bool indicating if the attribute is present in this mesh's vertex data. + /// + /// Returns an error if the mesh data has been extracted to `RenderWorld`. + #[inline] + pub fn contains_attribute(&self, id: impl Into) -> bool { + self.attributes.contains_key(&id.into()) + } + + /// Retrieves the data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. + /// + /// Returns an error if the mesh data has been extracted to `RenderWorld`. + #[inline] + pub fn attribute( + &self, + id: impl Into, + ) -> Option<&VertexAttributeValues> { + self.attributes.get(&id.into()).map(|data| &data.values) + } + + /// Retrieves the full data currently set to the vertex attribute with the specified [`MeshVertexAttributeId`]. + #[inline] + pub(crate) fn attribute_data( + &self, + id: impl Into, + ) -> Option<&MeshAttributeData> { + self.attributes.get(&id.into()) + } + + /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. + /// + /// Returns an error if the mesh data has been extracted to `RenderWorld`. + #[inline] + pub fn attribute_mut( + &mut self, + id: impl Into, + ) -> Option<&mut VertexAttributeValues> { + self.attributes + .get_mut(&id.into()) + .map(|data| &mut data.values) + } + + /// Returns an iterator that yields references to the data of each vertex attribute. + pub fn attributes( + &self, + ) -> impl Iterator { + self.attributes + .values() + .map(|data| (&data.attribute, &data.values)) + } + + /// Returns an iterator that yields mutable references to the data of each vertex attribute. + pub fn attributes_mut( + &mut self, + ) -> impl Iterator { + self.attributes + .values_mut() + .map(|data| (&data.attribute, &mut data.values)) + } + + /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the + /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants + /// that use triangles. + #[inline] + pub fn insert_indices(&mut self, indices: Indices) { + self.indices.replace(indices); + } + + /// Retrieves the vertex `indices` of the mesh, returns None if not found. + #[inline] + pub fn indices(&self) -> Option<&Indices> { + self.indices.as_ref() + } + + /// Retrieves the vertex `indices` of the mesh mutably. + #[inline] + pub fn indices_mut(&mut self) -> Option<&mut Indices> { + self.indices.as_mut() + } + + /// Removes the vertex `indices` from the mesh and returns them. + #[inline] + pub fn remove_indices(&mut self) -> Option { + self.indices.take() + } + + /// Returns the size of a vertex in bytes. + pub fn get_vertex_size(&self) -> u64 { + self.attributes + .values() + .map(|data| data.attribute.format.size()) + .sum() + } + + /// Returns the size required for the vertex buffer in bytes. + pub fn get_vertex_buffer_size(&self) -> usize { + let vertex_size = self.get_vertex_size() as usize; + let vertex_count = self.count_vertices(); + vertex_count * vertex_size + } + + /// Computes and returns the index data of the mesh as bytes. + /// This is used to transform the index data into a GPU friendly format. + pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> { + let mesh_indices = self.indices.as_ref(); + + mesh_indices.map(|indices| match &indices { + Indices::U16(indices) => cast_slice(&indices[..]), + Indices::U32(indices) => cast_slice(&indices[..]), + }) + } + + /// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in `SpecializedMeshPipeline`. + pub fn get_mesh_vertex_buffer_layout( + &self, + mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, + ) -> MeshVertexBufferLayoutRef { + let mesh_attributes = &self.attributes; + + let mut attributes = Vec::with_capacity(mesh_attributes.len()); + let mut attribute_ids = Vec::with_capacity(mesh_attributes.len()); + let mut accumulated_offset = 0; + for (index, data) in mesh_attributes.values().enumerate() { + attribute_ids.push(data.attribute.id); + attributes.push(VertexAttribute { + offset: accumulated_offset, + format: data.attribute.format, + shader_location: index as u32, + }); + accumulated_offset += data.attribute.format.size(); + } + + let layout = MeshVertexBufferLayout { + layout: VertexBufferLayout { + array_stride: accumulated_offset, + step_mode: VertexStepMode::Vertex, + attributes, + }, + attribute_ids, + }; + mesh_vertex_buffer_layouts.insert(layout) + } + + /// Counts all vertices of the mesh. + /// + /// If the attributes have different vertex counts, the smallest is returned. + pub fn count_vertices(&self) -> usize { + let mut vertex_count: Option = None; + let mesh_attributes = &self.attributes; + + for (attribute_id, attribute_data) in mesh_attributes { + let attribute_len = attribute_data.values.len(); + if let Some(previous_vertex_count) = vertex_count { + if previous_vertex_count != attribute_len { + let name = mesh_attributes + .get(attribute_id) + .map(|data| data.attribute.name.to_string()) + .unwrap_or_else(|| format!("{attribute_id:?}")); + + warn!("{name} has a different vertex count ({attribute_len}) than other attributes ({previous_vertex_count}) in this mesh, \ + all attributes will be truncated to match the smallest."); + vertex_count = Some(core::cmp::min(previous_vertex_count, attribute_len)); + } + } else { + vertex_count = Some(attribute_len); + } + } + + vertex_count.unwrap_or(0) + } + + /// Computes and returns the vertex data of the mesh as bytes. + /// Therefore the attributes are located in the order of their [`MeshVertexAttribute::id`]. + /// This is used to transform the vertex data into a GPU friendly format. + /// + /// If the vertex attributes have different lengths, they are all truncated to + /// the length of the smallest. + /// + /// This is a convenience method which allocates a Vec. + /// Prefer pre-allocating and using [`MeshExtractableData::write_packed_vertex_buffer_data`] when possible. + pub fn create_packed_vertex_buffer_data(&self) -> Vec { + let mut attributes_interleaved_buffer = vec![0; self.get_vertex_buffer_size()]; + self.write_packed_vertex_buffer_data(&mut attributes_interleaved_buffer); + attributes_interleaved_buffer + } + + /// Computes and write the vertex data of the mesh into a mutable byte slice. + /// The attributes are located in the order of their [`MeshVertexAttribute::id`]. + /// This is used to transform the vertex data into a GPU friendly format. + /// + /// If the vertex attributes have different lengths, they are all truncated to + /// the length of the smallest. + pub fn write_packed_vertex_buffer_data(&self, slice: &mut [u8]) { + let mesh_attributes = &self.attributes; + + let vertex_size = self.get_vertex_size() as usize; + let vertex_count = self.count_vertices(); + // bundle into interleaved buffers + let mut attribute_offset = 0; + for attribute_data in mesh_attributes.values() { + let attribute_size = attribute_data.attribute.format.size() as usize; + let attributes_bytes = attribute_data.values.get_bytes(); + for (vertex_index, attribute_bytes) in attributes_bytes + .chunks_exact(attribute_size) + .take(vertex_count) + .enumerate() + { + let offset = vertex_index * vertex_size + attribute_offset; + slice[offset..offset + attribute_size].copy_from_slice(attribute_bytes); + } + + attribute_offset += attribute_size; + } + } + + /// Duplicates the vertex attributes so that no vertices are shared. + /// + /// This can dramatically increase the vertex count, so make sure this is what you want. + /// Does nothing if no [Indices] are set. + pub fn duplicate_vertices(&mut self) { + fn duplicate(values: &[T], indices: impl Iterator) -> Vec { + indices.map(|i| values[i]).collect() + } + + let Some(indices) = self.indices.take() else { + return; + }; + + let mesh_attributes = &mut self.attributes; + + for attributes in mesh_attributes.values_mut() { + let indices = indices.iter(); + #[expect( + clippy::match_same_arms, + reason = "Although the `vec` binding on some match arms may have different types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever." + )] + match &mut attributes.values { + VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Float32x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint32x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint32x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Float32x3(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint32x3(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint32x3(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint32x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint32x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Float32x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint16x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Snorm16x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint16x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Unorm16x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint16x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Snorm16x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint16x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Unorm16x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint8x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Snorm8x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint8x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Unorm8x2(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Sint8x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Snorm8x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Uint8x4(vec) => *vec = duplicate(vec, indices), + VertexAttributeValues::Unorm8x4(vec) => *vec = duplicate(vec, indices), + } + } + } + + /// Inverts the winding of the indices such that all counter-clockwise triangles are now + /// clockwise and vice versa. + /// For lines, their start and end indices are flipped. + /// + /// Does nothing if no [`Indices`] are set. + /// If this operation succeeded, an [`Ok`] result is returned. + pub fn invert_winding(&mut self) -> Result<(), MeshWindingInvertError> { + fn invert( + indices: &mut [I], + topology: PrimitiveTopology, + ) -> Result<(), MeshWindingInvertError> { + match topology { + PrimitiveTopology::TriangleList => { + // Early return if the index count doesn't match + if !indices.len().is_multiple_of(3) { + return Err(MeshWindingInvertError::AbruptIndicesEnd); + } + for chunk in indices.chunks_mut(3) { + // This currently can only be optimized away with unsafe, rework this when `feature(slice_as_chunks)` gets stable. + let [_, b, c] = chunk else { + return Err(MeshWindingInvertError::AbruptIndicesEnd); + }; + core::mem::swap(b, c); + } + Ok(()) + } + PrimitiveTopology::LineList => { + // Early return if the index count doesn't match + if !indices.len().is_multiple_of(2) { + return Err(MeshWindingInvertError::AbruptIndicesEnd); + } + indices.reverse(); + Ok(()) + } + PrimitiveTopology::TriangleStrip | PrimitiveTopology::LineStrip => { + indices.reverse(); + Ok(()) + } + _ => Err(MeshWindingInvertError::WrongTopology), + } + } + + let topology = self.primitive_topology(); + let mesh_indices = self.indices_mut(); + + match mesh_indices { + Some(Indices::U16(vec)) => invert(vec, topology), + Some(Indices::U32(vec)) => invert(vec, topology), + None => Ok(()), + } + } + + /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. + /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat + /// normals. + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].= + pub fn compute_normals(&mut self) { + let topology = self.primitive_topology(); + assert!( + matches!(topology, PrimitiveTopology::TriangleList), + "`compute_normals` can only work on `TriangleList`s" + ); + if self.indices().is_none() { + self.compute_flat_normals(); + } else { + self.compute_smooth_normals(); + } + } + + /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. + /// + /// # Panics + /// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + /// Consider calling [`MeshExtractableData::duplicate_vertices`] or exporting your mesh with normal + /// attributes. + /// + /// FIXME: This should handle more cases since this is called as a part of gltf + /// mesh loading where we can't really blame users for loading meshes that might + /// not conform to the limitations here! + pub fn compute_flat_normals(&mut self) { + let topology = self.primitive_topology(); + assert!( + self.indices().is_none(), + "`compute_flat_normals` can't work on indexed geometry. Consider calling either `MeshExtractableData::compute_smooth_normals` or `Mesh::duplicate_vertices` followed by `MeshExtractableData::compute_flat_normals`." + ); + assert!( + matches!(topology, PrimitiveTopology::TriangleList), + "`compute_flat_normals` can only work on `TriangleList`s" + ); + + let positions = self + .attribute(Mesh::ATTRIBUTE_POSITION) + .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes don't exist") + .as_float3() + .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); + + let normals: Vec<_> = positions + .chunks_exact(3) + .map(|p| triangle_normal(p[0], p[1], p[2])) + .flat_map(|normal| [normal; 3]) + .collect(); + + self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + } + + /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared + /// vertices. + /// + /// This method weights normals by the angles of the corners of connected triangles, thus + /// eliminating triangle area and count as factors in the final normal. This does make it + /// somewhat slower than [`MeshExtractableData::compute_area_weighted_normals`] which does not need to + /// greedily normalize each triangle's normal or calculate corner angles. + /// + /// If you would rather have the computed normals be weighted by triangle area, see + /// [`MeshExtractableData::compute_area_weighted_normals`] instead. If you need to weight them in some other + /// way, see [`MeshExtractableData::compute_custom_smooth_normals`]. + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + /// Panics if the mesh does not have indices defined. + pub fn compute_smooth_normals(&mut self) { + self.compute_custom_smooth_normals(|[a, b, c], positions, normals| { + let pa = Vec3::from(positions[a]); + let pb = Vec3::from(positions[b]); + let pc = Vec3::from(positions[c]); + + let ab = pb - pa; + let ba = pa - pb; + let bc = pc - pb; + let cb = pb - pc; + let ca = pa - pc; + let ac = pc - pa; + + const EPS: f32 = f32::EPSILON; + let weight_a = if ab.length_squared() * ac.length_squared() > EPS { + ab.angle_between(ac) + } else { + 0.0 + }; + let weight_b = if ba.length_squared() * bc.length_squared() > EPS { + ba.angle_between(bc) + } else { + 0.0 + }; + let weight_c = if ca.length_squared() * cb.length_squared() > EPS { + ca.angle_between(cb) + } else { + 0.0 + }; + + let normal = Vec3::from(triangle_normal(positions[a], positions[b], positions[c])); + + normals[a] += normal * weight_a; + normals[b] += normal * weight_b; + normals[c] += normal * weight_c; + }); + } + + /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared + /// vertices. + /// + /// This method weights normals by the area of each triangle containing the vertex. Thus, + /// larger triangles will skew the normals of their vertices towards their own normal more + /// than smaller triangles will. + /// + /// This method is actually somewhat faster than [`MeshExtractableData::compute_smooth_normals`] because an + /// intermediate result of triangle normal calculation is already scaled by the triangle's area. + /// + /// If you would rather have the computed normals be influenced only by the angles of connected + /// edges, see [`MeshExtractableData::compute_smooth_normals`] instead. If you need to weight them in some + /// other way, see [`MeshExtractableData::compute_custom_smooth_normals`]. + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + /// Panics if the mesh does not have indices defined. + pub fn compute_area_weighted_normals(&mut self) { + self.compute_custom_smooth_normals(|[a, b, c], positions, normals| { + let normal = Vec3::from(triangle_area_normal( + positions[a], + positions[b], + positions[c], + )); + [a, b, c].into_iter().for_each(|pos| { + normals[pos] += normal; + }); + }); + } + + /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared + /// vertices. + /// + /// This method allows you to customize how normals are weighted via the `per_triangle` parameter, + /// which must be a function or closure that accepts 3 parameters: + /// - The indices of the three vertices of the triangle as a `[usize; 3]`. + /// - A reference to the values of the [`Mesh::ATTRIBUTE_POSITION`] of the mesh (`&[[f32; 3]]`). + /// - A mutable reference to the sums of all normals so far. + /// + /// See also the standard methods included in Bevy for calculating smooth normals: + /// - [`MeshExtractableData::compute_smooth_normals`] + /// - [`MeshExtractableData::compute_area_weighted_normals`] + /// + /// An example that would weight each connected triangle's normal equally, thus skewing normals + /// towards the planes divided into the most triangles: + /// ``` + /// # use bevy_asset::ExtractableAsset; + /// # use bevy_mesh::{Mesh, PrimitiveTopology, Meshable, MeshBuilder}; + /// # use bevy_math::{Vec3, primitives::Cuboid}; + /// # let mut mesh = Cuboid::default().mesh().build(); + /// # let mesh_data = mesh.extractable_data_mut().unwrap(); + /// mesh_data.compute_custom_smooth_normals(|[a, b, c], positions, normals| { + /// let normal = Vec3::from(bevy_mesh::triangle_normal(positions[a], positions[b], positions[c])); + /// for idx in [a, b, c] { + /// normals[idx] += normal; + /// } + /// }); + /// ``` + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + /// Panics if the mesh does not have indices defined. + // + // FIXME: This should handle more cases since this is called as a part of gltf + // mesh loading where we can't really blame users for loading meshes that might + // not conform to the limitations here! + // + // When fixed, also update "Panics" sections of + // - [MeshExtractableData::compute_smooth_normals] + // - [MeshExtractableData::with_computed_smooth_normals] + // - [MeshExtractableData::compute_area_weighted_normals] + // - [MeshExtractableData::with_computed_area_weighted_normals] + pub fn compute_custom_smooth_normals( + &mut self, + mut per_triangle: impl FnMut([usize; 3], &[[f32; 3]], &mut [Vec3]), + ) { + let topology = self.primitive_topology(); + assert!( + matches!(topology, PrimitiveTopology::TriangleList), + "smooth normals can only be computed on `TriangleList`s" + ); + let Some(indices) = self.indices() else { + panic!("smooth normals can only be computed on indexed meshes"); + }; + + let positions = self + .attribute(Mesh::ATTRIBUTE_POSITION) + .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes don't exist") + .as_float3() + .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); + + let mut normals = vec![Vec3::ZERO; positions.len()]; + + indices + .iter() + .collect::>() + .chunks_exact(3) + .for_each(|face| per_triangle([face[0], face[1], face[2]], positions, &mut normals)); + + for normal in &mut normals { + *normal = normal.try_normalize().unwrap_or(Vec3::ZERO); + } + + self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); + } + + /// Generate tangents for the mesh using the `mikktspace` algorithm. + /// + /// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. + /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. + #[cfg(feature = "bevy_mikktspace")] + pub fn generate_tangents(&mut self) -> Result<(), super::GenerateTangentsError> { + let topology = self.primitive_topology(); + let tangents = super::generate_tangents_for_mesh(self, topology)?; + self.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); + Ok(()) + } + + /// Merges the [`Mesh`] data of `other` with `self`. The attributes and indices of `other` will be appended to `self`. + /// + /// Note that attributes of `other` that don't exist on `self` will be ignored. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + /// + /// # Errors + /// + /// If any of the following conditions are not met, this function errors: + /// * All of the vertex attributes that have the same attribute id, must also + /// have the same attribute type. + /// For example two attributes with the same id, but where one is a + /// [`VertexAttributeValues::Float32`] and the other is a + /// [`VertexAttributeValues::Float32x3`], would be invalid. + pub fn merge(&mut self, other: &MeshExtractableData) -> Result<(), MeshMergeError> { + use VertexAttributeValues::*; + + // The indices of `other` should start after the last vertex of `self`. + let index_offset = self.count_vertices(); + + // Extend attributes of `self` with attributes of `other`. + for (attribute, values) in self.attributes_mut() { + if let Some(other_values) = other.attribute(attribute.id) { + #[expect( + clippy::match_same_arms, + reason = "Although the bindings on some match arms may have different types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever." + )] + match (values, other_values) { + (Float32(vec1), Float32(vec2)) => vec1.extend(vec2), + (Sint32(vec1), Sint32(vec2)) => vec1.extend(vec2), + (Uint32(vec1), Uint32(vec2)) => vec1.extend(vec2), + (Float32x2(vec1), Float32x2(vec2)) => vec1.extend(vec2), + (Sint32x2(vec1), Sint32x2(vec2)) => vec1.extend(vec2), + (Uint32x2(vec1), Uint32x2(vec2)) => vec1.extend(vec2), + (Float32x3(vec1), Float32x3(vec2)) => vec1.extend(vec2), + (Sint32x3(vec1), Sint32x3(vec2)) => vec1.extend(vec2), + (Uint32x3(vec1), Uint32x3(vec2)) => vec1.extend(vec2), + (Sint32x4(vec1), Sint32x4(vec2)) => vec1.extend(vec2), + (Uint32x4(vec1), Uint32x4(vec2)) => vec1.extend(vec2), + (Float32x4(vec1), Float32x4(vec2)) => vec1.extend(vec2), + (Sint16x2(vec1), Sint16x2(vec2)) => vec1.extend(vec2), + (Snorm16x2(vec1), Snorm16x2(vec2)) => vec1.extend(vec2), + (Uint16x2(vec1), Uint16x2(vec2)) => vec1.extend(vec2), + (Unorm16x2(vec1), Unorm16x2(vec2)) => vec1.extend(vec2), + (Sint16x4(vec1), Sint16x4(vec2)) => vec1.extend(vec2), + (Snorm16x4(vec1), Snorm16x4(vec2)) => vec1.extend(vec2), + (Uint16x4(vec1), Uint16x4(vec2)) => vec1.extend(vec2), + (Unorm16x4(vec1), Unorm16x4(vec2)) => vec1.extend(vec2), + (Sint8x2(vec1), Sint8x2(vec2)) => vec1.extend(vec2), + (Snorm8x2(vec1), Snorm8x2(vec2)) => vec1.extend(vec2), + (Uint8x2(vec1), Uint8x2(vec2)) => vec1.extend(vec2), + (Unorm8x2(vec1), Unorm8x2(vec2)) => vec1.extend(vec2), + (Sint8x4(vec1), Sint8x4(vec2)) => vec1.extend(vec2), + (Snorm8x4(vec1), Snorm8x4(vec2)) => vec1.extend(vec2), + (Uint8x4(vec1), Uint8x4(vec2)) => vec1.extend(vec2), + (Unorm8x4(vec1), Unorm8x4(vec2)) => vec1.extend(vec2), + _ => { + return Err(MeshMergeError::IncompatibleVertexAttributes { + self_attribute: *attribute, + other_attribute: other + .attribute_data(attribute.id) + .map(|data| data.attribute), + }) + } + } + } + } + + // Extend indices of `self` with indices of `other`. + if let (Some(indices), Some(other_indices)) = (self.indices_mut(), other.indices()) { + indices.extend(other_indices.iter().map(|i| (i + index_offset) as u32)); + } + Ok(()) + } + + /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn transform_by(&mut self, transform: Transform) { + // Needed when transforming normals and tangents + let scale_recip = 1. / transform.scale; + debug_assert!( + transform.scale.yzx() * transform.scale.zxy() != Vec3::ZERO, + "mesh transform scale cannot be zero on more than one axis" + ); + + if let Some(VertexAttributeValues::Float32x3(positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply scale, rotation, and translation to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = transform.transform_point(Vec3::from_slice(pos)).to_array()); + } + + // No need to transform normals or tangents if rotation is near identity and scale is uniform + if transform.rotation.is_near_identity() + && transform.scale.x == transform.scale.y + && transform.scale.y == transform.scale.z + { + return; + } + + if let Some(VertexAttributeValues::Float32x3(normals)) = + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) + { + // Transform normals, taking into account non-uniform scaling and rotation + normals.iter_mut().for_each(|normal| { + *normal = (transform.rotation + * scale_normal(Vec3::from_array(*normal), scale_recip)) + .to_array(); + }); + } + + if let Some(VertexAttributeValues::Float32x4(tangents)) = + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) + { + // Transform tangents, taking into account non-uniform scaling and rotation + tangents.iter_mut().for_each(|tangent| { + let handedness = tangent[3]; + let scaled_tangent = Vec3::from_slice(tangent) * transform.scale; + *tangent = (transform.rotation * scaled_tangent.normalize_or_zero()) + .extend(handedness) + .to_array(); + }); + } + } + + /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn translate_by(&mut self, translation: Vec3) { + if translation == Vec3::ZERO { + return; + } + + if let Some(VertexAttributeValues::Float32x3(positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply translation to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = (Vec3::from_slice(pos) + translation).to_array()); + } + } + + /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn rotate_by(&mut self, rotation: Quat) { + if let Some(VertexAttributeValues::Float32x3(positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply rotation to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = (rotation * Vec3::from_slice(pos)).to_array()); + } + + // No need to transform normals or tangents if rotation is near identity + if rotation.is_near_identity() { + return; + } + + if let Some(VertexAttributeValues::Float32x3(normals)) = + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) + { + // Transform normals + normals.iter_mut().for_each(|normal| { + *normal = (rotation * Vec3::from_slice(normal).normalize_or_zero()).to_array(); + }); + } + + if let Some(VertexAttributeValues::Float32x4(tangents)) = + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) + { + // Transform tangents + tangents.iter_mut().for_each(|tangent| { + let handedness = tangent[3]; + *tangent = (rotation * Vec3::from_slice(tangent).normalize_or_zero()) + .extend(handedness) + .to_array(); + }); + } + } + + /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn scale_by(&mut self, scale: Vec3) { + // Needed when transforming normals and tangents + let scale_recip = 1. / scale; + debug_assert!( + scale.yzx() * scale.zxy() != Vec3::ZERO, + "mesh transform scale cannot be zero on more than one axis" + ); + + if let Some(VertexAttributeValues::Float32x3(positions)) = + self.attribute_mut(Mesh::ATTRIBUTE_POSITION) + { + // Apply scale to vertex positions + positions + .iter_mut() + .for_each(|pos| *pos = (scale * Vec3::from_slice(pos)).to_array()); + } + + // No need to transform normals or tangents if scale is uniform + if scale.x == scale.y && scale.y == scale.z { + return; + } + + if let Some(VertexAttributeValues::Float32x3(normals)) = + self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) + { + // Transform normals, taking into account non-uniform scaling + normals.iter_mut().for_each(|normal| { + *normal = scale_normal(Vec3::from_array(*normal), scale_recip).to_array(); + }); + } + + if let Some(VertexAttributeValues::Float32x4(tangents)) = + self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) + { + // Transform tangents, taking into account non-uniform scaling + tangents.iter_mut().for_each(|tangent| { + let handedness = tangent[3]; + let scaled_tangent = Vec3::from_slice(tangent) * scale; + *tangent = scaled_tangent + .normalize_or_zero() + .extend(handedness) + .to_array(); + }); + } + } + + /// Normalize joint weights so they sum to 1. + pub fn normalize_joint_weights(&mut self) -> Result<(), ExtractableAssetAccessError> { + if let Some(VertexAttributeValues::Float32x4(joints)) = + self.attribute_mut(Mesh::ATTRIBUTE_JOINT_WEIGHT) + { + for weights in joints.iter_mut() { + // force negative weights to zero + weights.iter_mut().for_each(|w| *w = w.max(0.0)); + + let sum: f32 = weights.iter().sum(); + if sum == 0.0 { + // all-zero weights are invalid + weights[0] = 1.0; + } else { + let recip = sum.recip(); + for weight in weights.iter_mut() { + *weight *= recip; + } + } + } + } + + Ok(()) + } + + /// Get a list of this Mesh's [triangles] as an iterator if possible. + /// + /// Returns an error if any of the following conditions are met (see [`MeshTrianglesError`]): + /// * The Mesh's [primitive topology] is not `TriangleList` or `TriangleStrip`. + /// * The Mesh is missing position or index data. + /// * The Mesh's position data has the wrong format (not `Float32x3`). + /// + /// [primitive topology]: PrimitiveTopology + /// [triangles]: Triangle3d + pub fn triangles(&self) -> Result + '_, MeshTrianglesError> { + let Some(position_data) = self.attribute(Mesh::ATTRIBUTE_POSITION) else { + return Err(MeshTrianglesError::BadPositions); + }; + + let Some(vertices) = position_data.as_float3() else { + return Err(MeshTrianglesError::PositionsFormat); + }; + + let Some(indices) = self.indices() else { + return Err(MeshTrianglesError::BadIndices); + }; + + match self.primitive_topology() { + PrimitiveTopology::TriangleList => { + // When indices reference out-of-bounds vertex data, the triangle is omitted. + // This implicitly truncates the indices to a multiple of 3. + let iterator = match indices { + Indices::U16(vec) => FourIterators::First( + vec.as_slice() + .chunks_exact(3) + .flat_map(move |indices| indices_to_triangle(vertices, indices)), + ), + Indices::U32(vec) => FourIterators::Second( + vec.as_slice() + .chunks_exact(3) + .flat_map(move |indices| indices_to_triangle(vertices, indices)), + ), + }; + + return Ok(iterator); + } + + PrimitiveTopology::TriangleStrip => { + // When indices reference out-of-bounds vertex data, the triangle is omitted. + // If there aren't enough indices to make a triangle, then an empty vector will be + // returned. + let iterator = match indices { + Indices::U16(vec) => { + FourIterators::Third(vec.as_slice().windows(3).enumerate().flat_map( + move |(i, indices)| { + if i % 2 == 0 { + indices_to_triangle(vertices, indices) + } else { + indices_to_triangle( + vertices, + &[indices[1], indices[0], indices[2]], + ) + } + }, + )) + } + Indices::U32(vec) => { + FourIterators::Fourth(vec.as_slice().windows(3).enumerate().flat_map( + move |(i, indices)| { + if i % 2 == 0 { + indices_to_triangle(vertices, indices) + } else { + indices_to_triangle( + vertices, + &[indices[1], indices[0], indices[2]], + ) + } + }, + )) + } + }; + + return Ok(iterator); + } + + _ => { + return Err(MeshTrianglesError::WrongTopology); + } + }; + + fn indices_to_triangle + Copy>( + vertices: &[[f32; 3]], + indices: &[T], + ) -> Option { + let vert0: Vec3 = Vec3::from(*vertices.get(indices[0].try_into().ok()?)?); + let vert1: Vec3 = Vec3::from(*vertices.get(indices[1].try_into().ok()?)?); + let vert2: Vec3 = Vec3::from(*vertices.get(indices[2].try_into().ok()?)?); + Some(Triangle3d { + vertices: [vert0, vert1, vert2], + }) + } + } + + /// Consumes the mesh and returns a mesh with data set for a vertex attribute (position, normal, etc.). + /// The name will often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. + /// + /// (Alternatively, you can use [`MeshExtractableData::insert_attribute`] to mutate an existing mesh in-place) + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + #[must_use] + #[inline] + pub fn with_inserted_attribute( + mut self, + attribute: MeshVertexAttribute, + values: impl Into, + ) -> Self { + self.insert_attribute(attribute, values); + self + } + + /// Consumes the mesh and returns a mesh without the data for a vertex attribute + /// + /// (Alternatively, you can use [`MeshExtractableData::remove_attribute`] to mutate an existing mesh in-place) + #[must_use] + pub fn with_removed_attribute(mut self, attribute: impl Into) -> Self { + self.remove_attribute(attribute); + self + } + + /// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles + /// are constructed out of the vertex attributes and are therefore only useful for the + /// [`PrimitiveTopology`] variants that use triangles. + /// + /// (Alternatively, you can use [`MeshExtractableData::insert_indices`] to mutate an existing mesh in-place) + #[must_use] + #[inline] + pub fn with_inserted_indices(mut self, indices: Indices) -> Self { + self.insert_indices(indices); + self + } + + /// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh. + /// + /// (Alternatively, you can use [`MeshExtractableData::remove_indices`] to mutate an existing mesh in-place) + #[must_use] + pub fn with_removed_indices(mut self) -> Self { + self.remove_indices(); + self + } + + /// Consumes the mesh and returns a mesh with no shared vertices. + /// + /// This can dramatically increase the vertex count, so make sure this is what you want. + /// Does nothing if no [`Indices`] are set. + /// + /// (Alternatively, you can use [`MeshExtractableData::duplicate_vertices`] to mutate an existing mesh in-place) + #[must_use] + pub fn with_duplicated_vertices(mut self) -> Self { + self.duplicate_vertices(); + self + } + + /// Consumes the mesh and returns a mesh with inverted winding of the indices such + /// that all counter-clockwise triangles are now clockwise and vice versa. + /// + /// Does nothing if no [`Indices`] are set. + pub fn with_inverted_winding(mut self) -> Result { + self.invert_winding().map(|_| self) + } + + /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. + /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat + /// normals. + /// + /// (Alternatively, you can use [`MeshExtractableData::compute_normals`] to mutate an existing mesh in-place) + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + #[must_use] + pub fn with_computed_normals(mut self) -> Self { + self.compute_normals(); + self + } + + /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. + /// + /// (Alternatively, you can use [`MeshExtractableData::compute_flat_normals`] to mutate an existing mesh in-place) + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + /// Panics if the mesh has indices defined + pub fn with_computed_flat_normals(mut self) -> Self { + self.compute_flat_normals(); + self + } + + /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. + /// + /// (Alternatively, you can use [`MeshExtractableData::compute_smooth_normals`] to mutate an existing mesh in-place) + /// + /// This method weights normals by the angles of triangle corners connected to each vertex. If + /// you would rather have the computed normals be weighted by triangle area, see + /// [`MeshExtractableData::with_computed_area_weighted_normals`] instead. + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + /// Panics if the mesh does not have indices defined. + pub fn with_computed_smooth_normals(mut self) -> Self { + self.compute_smooth_normals(); + self + } + + /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. + /// + /// (Alternatively, you can use [`MeshExtractableData::compute_area_weighted_normals`] to mutate an existing mesh in-place) + /// + /// This method weights normals by the area of each triangle containing the vertex. Thus, + /// larger triangles will skew the normals of their vertices towards their own normal more + /// than smaller triangles will. If you would rather have the computed normals be influenced + /// only by the angles of connected edges, see [`MeshExtractableData::with_computed_smooth_normals`] instead. + /// + /// # Panics + /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. + /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. + /// Panics if the mesh does not have indices defined. + pub fn with_computed_area_weighted_normals(mut self) -> Self { + self.compute_area_weighted_normals(); + self + } + + /// Consumes the mesh and returns a mesh with tangents generated using the `mikktspace` algorithm. + /// + /// The resulting mesh will have the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. + /// + /// (Alternatively, you can use [`MeshExtractableData::generate_tangents`] to mutate an existing mesh in-place) + /// + /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. + #[cfg(feature = "bevy_mikktspace")] + pub fn with_generated_tangents(mut self) -> Result { + self.generate_tangents()?; + Ok(self) + } + + /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn transformed_by(mut self, transform: Transform) -> Self { + self.transform_by(transform); + self + } + + /// Translates the vertex positions of the mesh by the given [`Vec3`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn translated_by(mut self, translation: Vec3) -> Self { + self.translate_by(translation); + self + } + + /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn rotated_by(mut self, rotation: Quat) -> Self { + self.rotate_by(rotation); + self + } + + /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. + /// + /// `Aabb` of entities with modified mesh are not updated automatically. + pub fn scaled_by(mut self, scale: Vec3) -> Self { + self.scale_by(scale); + self + } +} + +#[cfg(feature = "morph")] +impl MeshExtractableData { + /// Whether this mesh has morph targets. + pub fn has_morph_targets(&self) -> bool { + self.morph_targets.is_some() + } + + /// Set [morph targets] image for this mesh. This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. + /// + /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation + pub fn set_morph_targets(&mut self, morph_targets: Handle) { + self.morph_targets.replace(morph_targets); + } + + /// Retrieve the morph targets for this mesh, or None if there are no morph targets. + /// + /// Returns an error if the mesh data has been extracted to `RenderWorld`or + /// if the morph targets do not exist. + pub fn morph_targets(&self) -> Option<&Handle> { + self.morph_targets.as_ref() + } + + /// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`. + /// + /// Returns an error if the mesh data has been extracted to `RenderWorld`. + pub fn set_morph_target_names(&mut self, names: Vec) { + self.morph_target_names.replace(names); + } + + /// Gets a list of all morph target names, if they exist. + /// + /// Returns an error if the mesh data has been extracted to `RenderWorld`or + /// if the morph targets do not exist. + pub fn morph_target_names(&self) -> Option<&[String]> { + self.morph_target_names.as_deref() + } + + /// Consumes the mesh and returns a mesh with the given [morph targets]. + /// + /// This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. + /// + /// (Alternatively, you can use [`MeshExtractableData::set_morph_targets`] to mutate an existing mesh in-place) + /// + /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation + #[must_use] + pub fn with_morph_targets(mut self, morph_targets: Handle) -> Self { + self.set_morph_targets(morph_targets); + self + } + + /// Consumes the mesh and returns a mesh with morph target names. + /// Names should correspond to the order of the morph targets in `set_morph_targets`. + /// + /// (Alternatively, you can use [`MeshExtractableData::set_morph_target_names`] to mutate an existing mesh in-place) + #[must_use] + pub fn with_morph_target_names(mut self, names: Vec) -> Self { + self.set_morph_target_names(names); + self + } +} + +impl core::ops::Mul for Transform { + type Output = MeshExtractableData; + + fn mul(self, rhs: MeshExtractableData) -> Self::Output { + rhs.transformed_by(self) + } +} + +/// Correctly scales and renormalizes an already normalized `normal` by the scale determined by its reciprocal `scale_recip` +pub(crate) fn scale_normal(normal: Vec3, scale_recip: Vec3) -> Vec3 { + // This is basically just `normal * scale_recip` but with the added rule that `0. * anything == 0.` + // This is necessary because components of `scale_recip` may be infinities, which do not multiply to zero + let n = Vec3::select(normal.cmpeq(Vec3::ZERO), Vec3::ZERO, normal * scale_recip); + + // If n is finite, no component of `scale_recip` was infinite or the normal was perpendicular to the scale + // else the scale had at least one zero-component and the normal needs to point along the direction of that component + if n.is_finite() { + n.normalize_or_zero() + } else { + Vec3::select(n.abs().cmpeq(Vec3::INFINITY), n.signum(), Vec3::ZERO).normalize() + } +} + +#[cfg(test)] +mod tests { + use crate::Mesh; + #[cfg(feature = "serialize")] + use crate::SerializedMesh; + use crate::{Indices, MeshWindingInvertError, VertexAttributeValues}; + use crate::{MeshExtractableData, PrimitiveTopology}; + use bevy_asset::ExtractableAsset; + use bevy_math::bounding::Aabb3d; + use bevy_math::primitives::Triangle3d; + use bevy_math::Vec3; + use bevy_transform::components::Transform; + + #[test] + #[should_panic] + fn panic_invalid_format() { + let _mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]); + } + + #[test] + fn transform_mesh() { + let mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![[-1., -1., 2.], [1., -1., 2.], [0., 1., 2.]], + ) + .with_inserted_attribute( + Mesh::ATTRIBUTE_NORMAL, + vec![ + Vec3::new(-1., -1., 1.).normalize().to_array(), + Vec3::new(1., -1., 1.).normalize().to_array(), + [0., 0., 1.], + ], + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.], [1., 0.], [0.5, 1.]]); + + let mesh = mesh.transformed_by( + Transform::from_translation(Vec3::splat(-2.)).with_scale(Vec3::new(2., 0., -1.)), + ); + + if let Some(VertexAttributeValues::Float32x3(positions)) = + mesh.attribute(Mesh::ATTRIBUTE_POSITION) + { + // All positions are first scaled resulting in `vec![[-2, 0., -2.], [2., 0., -2.], [0., 0., -2.]]` + // and then shifted by `-2.` along each axis + assert_eq!( + positions, + &vec![[-4.0, -2.0, -4.0], [0.0, -2.0, -4.0], [-2.0, -2.0, -4.0]] + ); + } else { + panic!("Mesh does not have a position attribute"); + } + + if let Some(VertexAttributeValues::Float32x3(normals)) = + mesh.attribute(Mesh::ATTRIBUTE_NORMAL) + { + assert_eq!(normals, &vec![[0., -1., 0.], [0., -1., 0.], [0., 0., -1.]]); + } else { + panic!("Mesh does not have a normal attribute"); + } + + if let Some(VertexAttributeValues::Float32x2(uvs)) = mesh.attribute(Mesh::ATTRIBUTE_UV_0) { + assert_eq!(uvs, &vec![[0., 0.], [1., 0.], [0.5, 1.]]); + } else { + panic!("Mesh does not have a uv attribute"); + } + } + + #[test] + fn point_list_mesh_invert_winding() { + let mesh = MeshExtractableData::new(PrimitiveTopology::PointList) + .with_inserted_indices(Indices::U32(vec![])); + assert!(matches!( + mesh.with_inverted_winding(), + Err(MeshWindingInvertError::WrongTopology) + )); + } + + #[test] + fn line_list_mesh_invert_winding() { + let mesh = MeshExtractableData::new(PrimitiveTopology::LineList) + .with_inserted_indices(Indices::U32(vec![0, 1, 1, 2, 2, 3])); + let mesh = mesh.with_inverted_winding().unwrap(); + assert_eq!( + mesh.indices().unwrap().iter().collect::>(), + vec![3, 2, 2, 1, 1, 0] + ); + } + + #[test] + fn line_list_mesh_invert_winding_fail() { + let mesh = MeshExtractableData::new(PrimitiveTopology::LineList) + .with_inserted_indices(Indices::U32(vec![0, 1, 1])); + assert!(matches!( + mesh.with_inverted_winding(), + Err(MeshWindingInvertError::AbruptIndicesEnd) + )); + } + + #[test] + fn line_strip_mesh_invert_winding() { + let mesh = MeshExtractableData::new(PrimitiveTopology::LineStrip) + .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3])); + let mesh = mesh.with_inverted_winding().unwrap(); + assert_eq!( + mesh.indices().unwrap().iter().collect::>(), + vec![3, 2, 1, 0] + ); + } + + #[test] + fn triangle_list_mesh_invert_winding() { + let mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList).with_inserted_indices( + Indices::U32(vec![ + 0, 3, 1, // First triangle + 1, 3, 2, // Second triangle + ]), + ); + let mesh = mesh.with_inverted_winding().unwrap(); + assert_eq!( + mesh.indices().unwrap().iter().collect::>(), + vec![ + 0, 1, 3, // First triangle + 1, 2, 3, // Second triangle + ] + ); + } + + #[test] + fn triangle_list_mesh_invert_winding_fail() { + let mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(vec![0, 3, 1, 2])); + assert!(matches!( + mesh.with_inverted_winding(), + Err(MeshWindingInvertError::AbruptIndicesEnd) + )); + } + + #[test] + fn triangle_strip_mesh_invert_winding() { + let mesh = MeshExtractableData::new(PrimitiveTopology::TriangleStrip) + .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3])); + let mesh = mesh.with_inverted_winding().unwrap(); + assert_eq!( + mesh.indices().unwrap().iter().collect::>(), + vec![3, 2, 1, 0] + ); + } + + #[test] + fn compute_area_weighted_normals() { + let mut mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList); + + // z y + // | / + // 3---2 + // | / \ + // 0-----1--x + + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], + ); + mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3])); + mesh.compute_area_weighted_normals(); + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); + assert_eq!(4, normals.len()); + // 0 + assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[0]); + // 1 + assert_eq!([0., 0., 1.], normals[1]); + // 2 + assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[2]); + // 3 + assert_eq!([1., 0., 0.], normals[3]); + } + + #[test] + fn compute_area_weighted_normals_proportionate() { + let mut mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList); + + // z y + // | / + // 3---2.. + // | / \ + // 0-------1---x + + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![[0., 0., 0.], [2., 0., 0.], [0., 1., 0.], [0., 0., 1.]], + ); + mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3])); + mesh.compute_area_weighted_normals(); + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); + assert_eq!(4, normals.len()); + // 0 + assert_eq!(Vec3::new(1., 0., 2.).normalize().to_array(), normals[0]); + // 1 + assert_eq!([0., 0., 1.], normals[1]); + // 2 + assert_eq!(Vec3::new(1., 0., 2.).normalize().to_array(), normals[2]); + // 3 + assert_eq!([1., 0., 0.], normals[3]); + } + + #[test] + fn compute_angle_weighted_normals() { + // CuboidMeshBuilder duplicates vertices (even though it is indexed) + + // 5---------4 + // /| /| + // 1-+-------0 | + // | 6-------|-7 + // |/ |/ + // 2---------3 + let verts = vec![ + [1.0, 1.0, 1.0], + [-1.0, 1.0, 1.0], + [-1.0, -1.0, 1.0], + [1.0, -1.0, 1.0], + [1.0, 1.0, -1.0], + [-1.0, 1.0, -1.0], + [-1.0, -1.0, -1.0], + [1.0, -1.0, -1.0], + ]; + + let indices = Indices::U16(vec![ + 0, 1, 2, 2, 3, 0, // front + 5, 4, 7, 7, 6, 5, // back + 1, 5, 6, 6, 2, 1, // left + 4, 0, 3, 3, 7, 4, // right + 4, 5, 1, 1, 0, 4, // top + 3, 2, 6, 6, 7, 3, // bottom + ]); + let mut mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, verts); + mesh.insert_indices(indices); + mesh.compute_smooth_normals(); + + let normals = mesh + .attribute(Mesh::ATTRIBUTE_NORMAL) + .unwrap() + .as_float3() + .unwrap(); + + for new in normals.iter().copied().flatten() { + // std impl is unstable + const FRAC_1_SQRT_3: f32 = 0.57735026; + const MIN: f32 = FRAC_1_SQRT_3 - f32::EPSILON; + const MAX: f32 = FRAC_1_SQRT_3 + f32::EPSILON; + assert!(new.abs() >= MIN, "{new} < {MIN}"); + assert!(new.abs() <= MAX, "{new} > {MAX}"); + } + } + + #[test] + fn triangles_from_triangle_list() { + let mut mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList); + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![[0., 0., 0.], [1., 0., 0.], [1., 1., 0.], [0., 1., 0.]], + ); + mesh.insert_indices(Indices::U32(vec![0, 1, 2, 2, 3, 0])); + assert_eq!( + vec![ + Triangle3d { + vertices: [ + Vec3::new(0., 0., 0.), + Vec3::new(1., 0., 0.), + Vec3::new(1., 1., 0.), + ] + }, + Triangle3d { + vertices: [ + Vec3::new(1., 1., 0.), + Vec3::new(0., 1., 0.), + Vec3::new(0., 0., 0.), + ] + } + ], + mesh.triangles().unwrap().collect::>() + ); + } + + #[test] + fn triangles_from_triangle_strip() { + let mut mesh = MeshExtractableData::new(PrimitiveTopology::TriangleStrip); + // Triangles: (0, 1, 2), (2, 1, 3), (2, 3, 4), (4, 3, 5) + // + // 4 - 5 + // | \ | + // 2 - 3 + // | \ | + // 0 - 1 + let positions: Vec = [ + [0., 0., 0.], + [1., 0., 0.], + [0., 1., 0.], + [1., 1., 0.], + [0., 2., 0.], + [1., 2., 0.], + ] + .into_iter() + .map(Vec3::from_array) + .collect(); + mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions.clone()); + mesh.insert_indices(Indices::U32(vec![0, 1, 2, 3, 4, 5])); + assert_eq!( + vec![ + Triangle3d { + vertices: [positions[0], positions[1], positions[2]] + }, + Triangle3d { + vertices: [positions[2], positions[1], positions[3]] + }, + Triangle3d { + vertices: [positions[2], positions[3], positions[4]] + }, + Triangle3d { + vertices: [positions[4], positions[3], positions[5]] + }, + ], + mesh.triangles().unwrap().collect::>() + ); + } + + #[test] + fn take_gpu_data_calculates_aabb() { + let mut mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList); + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![ + [-0.5, 0., 0.], + [-1., 0., 0.], + [-1., -1., 0.], + [-0.5, -1., 0.], + ], + ); + mesh.insert_indices(Indices::U32(vec![0, 1, 2, 2, 3, 0])); + let mesh = Mesh::from(mesh).take_gpu_data().unwrap(); + assert_eq!( + mesh.final_aabb, + Some(Aabb3d::from_min_max([-1., -1., 0.], [-0.5, 0., 0.])) + ); + } + + #[cfg(feature = "serialize")] + #[test] + fn serialize_deserialize_mesh() { + let mut mesh = MeshExtractableData::new(PrimitiveTopology::TriangleList); + + mesh.insert_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![[0., 0., 0.], [2., 0., 0.], [0., 1., 0.], [0., 0., 1.]], + ); + mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3])); + + let mesh = Mesh::from(mesh); + let serialized_mesh = SerializedMesh::from_mesh(mesh.clone()); + let serialized_string = serde_json::to_string(&serialized_mesh).unwrap(); + let serialized_mesh_from_string: SerializedMesh = + serde_json::from_str(&serialized_string).unwrap(); + let deserialized_mesh = serialized_mesh_from_string.into_mesh(); + assert_eq!(mesh, deserialized_mesh); + } +} diff --git a/crates/bevy_mesh/src/mikktspace.rs b/crates/bevy_mesh/src/mikktspace.rs index 36b24353c7c13..debc1beeb79d9 100644 --- a/crates/bevy_mesh/src/mikktspace.rs +++ b/crates/bevy_mesh/src/mikktspace.rs @@ -1,4 +1,4 @@ -use crate::MeshAccessError; +use crate::MeshExtractableData; use super::{Indices, Mesh, VertexAttributeValues}; use thiserror::Error; @@ -72,19 +72,18 @@ pub enum GenerateTangentsError { InvalidVertexAttributeFormat(&'static str, VertexFormat), #[error("mesh not suitable for tangent generation")] MikktspaceError(#[from] bevy_mikktspace::GenerateTangentSpaceError), - #[error("Mesh access error: {0}")] - MeshAccessError(#[from] MeshAccessError), } pub(crate) fn generate_tangents_for_mesh( - mesh: &Mesh, + mesh: &MeshExtractableData, + topology: PrimitiveTopology, ) -> Result, GenerateTangentsError> { - match mesh.primitive_topology() { + match topology { PrimitiveTopology::TriangleList => {} other => return Err(GenerateTangentsError::UnsupportedTopology(other)), }; - let positions = mesh.try_attribute_option(Mesh::ATTRIBUTE_POSITION)?.ok_or( + let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or( GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name), )?; let VertexAttributeValues::Float32x3(positions) = positions else { @@ -93,7 +92,7 @@ pub(crate) fn generate_tangents_for_mesh( VertexFormat::Float32x3, )); }; - let normals = mesh.try_attribute_option(Mesh::ATTRIBUTE_NORMAL)?.ok_or( + let normals = mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or( GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name), )?; let VertexAttributeValues::Float32x3(normals) = normals else { @@ -102,7 +101,7 @@ pub(crate) fn generate_tangents_for_mesh( VertexFormat::Float32x3, )); }; - let uvs = mesh.try_attribute_option(Mesh::ATTRIBUTE_UV_0)?.ok_or( + let uvs = mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or( GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name), )?; let VertexAttributeValues::Float32x2(uvs) = uvs else { @@ -115,7 +114,7 @@ pub(crate) fn generate_tangents_for_mesh( let len = positions.len(); let tangents = vec![[0., 0., 0., 0.]; len]; let mut mikktspace_mesh = MikktspaceGeometryHelper { - indices: mesh.try_indices_option()?, + indices: mesh.indices(), positions, normals, uvs, diff --git a/crates/bevy_mesh/src/morph.rs b/crates/bevy_mesh/src/morph.rs index 2cb39a31985cb..e7f4d20969469 100644 --- a/crates/bevy_mesh/src/morph.rs +++ b/crates/bevy_mesh/src/morph.rs @@ -133,7 +133,7 @@ impl MorphWeights { }) } /// The first child [`Mesh3d`](crate::Mesh3d) primitive controlled by these weights. - /// This can be used to look up metadata information such as [`Mesh::morph_target_names`]. + /// This can be used to look up metadata information such as [`crate::MeshExtractableData::morph_target_names`]. pub fn first_mesh(&self) -> Option<&Handle> { self.first_mesh.as_ref() } diff --git a/crates/bevy_mesh/src/primitives/dim2.rs b/crates/bevy_mesh/src/primitives/dim2.rs index 2a8bb4edf50cf..6aec5fdf562a9 100644 --- a/crates/bevy_mesh/src/primitives/dim2.rs +++ b/crates/bevy_mesh/src/primitives/dim2.rs @@ -1,8 +1,9 @@ use core::f32::consts::FRAC_PI_2; use core::mem; +use crate::MeshExtractableData; use crate::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment, VertexAttributeValues}; -use bevy_asset::RenderAssetUsages; +use bevy_asset::ExtractableAsset; use super::{Extrudable, MeshBuilder, Meshable}; use bevy_math::prelude::Polyline2d; @@ -210,14 +211,13 @@ impl MeshBuilder for CircularSectorMeshBuilder { indices.extend_from_slice(&[0, i, i + 1]); } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_inserted_indices(Indices::U32(indices)) } } @@ -357,14 +357,13 @@ impl MeshBuilder for CircularSegmentMeshBuilder { indices.extend_from_slice(&[0, i, i + 1]); } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_inserted_indices(Indices::U32(indices)) } } @@ -438,12 +437,11 @@ impl MeshBuilder for ConvexPolygonMeshBuilder { for i in 2..len as u32 { indices.extend_from_slice(&[0, i - 1, i]); } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_indices(Indices::U32(indices)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_indices(Indices::U32(indices)) } } @@ -602,14 +600,13 @@ impl MeshBuilder for EllipseMeshBuilder { indices.extend_from_slice(&[0, i, i + 1]); } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_inserted_indices(Indices::U32(indices)) } } @@ -659,9 +656,11 @@ impl MeshBuilder for Segment2dMeshBuilder { let positions = self.segment.vertices.map(|v| v.extend(0.0)).to_vec(); let indices = Indices::U32(vec![0, 1]); - Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_indices(indices) + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::LineList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_indices(indices), + ) } } @@ -702,9 +701,11 @@ impl MeshBuilder for Polyline2dMeshBuilder { .collect(), ); - Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::LineList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions), + ) } } @@ -811,14 +812,13 @@ impl MeshBuilder for AnnulusMeshBuilder { indices.extend_from_slice(&[next_outer, next_inner, inner_vertex]); } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_inserted_indices(Indices::U32(indices)) } } @@ -908,14 +908,13 @@ impl MeshBuilder for RhombusMeshBuilder { let uvs = vec![[1.0, 0.5], [0.5, 0.0], [0.0, 0.5], [0.5, 1.0]]; let indices = Indices::U32(vec![2, 0, 1, 2, 3, 0]); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } @@ -988,14 +987,13 @@ impl MeshBuilder for Triangle2dMeshBuilder { Indices::U32(vec![2, 1, 0]) }; - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } @@ -1065,14 +1063,13 @@ impl MeshBuilder for RectangleMeshBuilder { let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]]; let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } @@ -1207,14 +1204,13 @@ impl MeshBuilder for Capsule2dMeshBuilder { // Add indices for bottom right triangle of the part between the semicircles indices.extend_from_slice(&[resolution, vertex_count - 1, 0]); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_inserted_indices(Indices::U32(indices)) } } @@ -1290,7 +1286,7 @@ where } fn get_vertex_attributes(&self) -> Option { - fn get_positions(mesh: &mut Mesh) -> Option<&mut Vec<[f32; 3]>> { + fn get_positions(mesh: &mut MeshExtractableData) -> Option<&mut Vec<[f32; 3]>> { if let VertexAttributeValues::Float32x3(data) = mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)? { @@ -1300,7 +1296,7 @@ where } } - fn get_uvs(mesh: &mut Mesh) -> Option<&mut Vec<[f32; 2]>> { + fn get_uvs(mesh: &mut MeshExtractableData) -> Option<&mut Vec<[f32; 2]>> { if let VertexAttributeValues::Float32x2(data) = mesh.attribute_mut(Mesh::ATTRIBUTE_UV_0)? { @@ -1310,7 +1306,7 @@ where } } - fn get_normals(mesh: &mut Mesh) -> Option<&mut Vec<[f32; 3]>> { + fn get_normals(mesh: &mut MeshExtractableData) -> Option<&mut Vec<[f32; 3]>> { if let VertexAttributeValues::Float32x3(data) = mesh.attribute_mut(Mesh::ATTRIBUTE_NORMAL)? { @@ -1335,12 +1331,12 @@ where ); Some(RingMeshBuilderVertexAttributes { - outer_positions: mem::take(get_positions(&mut outer)?), - inner_positions: mem::take(get_positions(&mut inner)?), - outer_normals: mem::take(get_normals(&mut outer)?), - inner_normals: mem::take(get_normals(&mut inner)?), - outer_uvs: mem::take(get_uvs(&mut outer)?), - inner_uvs: mem::take(get_uvs(&mut inner)?), + outer_positions: mem::take(get_positions(outer.extractable_data_mut().unwrap())?), + inner_positions: mem::take(get_positions(inner.extractable_data_mut().unwrap())?), + outer_normals: mem::take(get_normals(outer.extractable_data_mut().unwrap())?), + inner_normals: mem::take(get_normals(inner.extractable_data_mut().unwrap())?), + outer_uvs: mem::take(get_uvs(outer.extractable_data_mut().unwrap())?), + inner_uvs: mem::take(get_uvs(inner.extractable_data_mut().unwrap())?), }) } } @@ -1424,14 +1420,13 @@ where let mut positions = outer_positions; positions.extend_from_slice(&inner_positions); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(Indices::U32(indices)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_inserted_indices(Indices::U32(indices)) } else { panic!("The inner and outer meshes should have the same number of vertices, and have required attributes"); } @@ -1526,6 +1521,7 @@ where #[cfg(test)] mod tests { + use bevy_asset::ExtractableAsset; use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd}; use bevy_platform::collections::HashSet; @@ -1542,6 +1538,7 @@ mod tests { #[test] fn test_annulus() { let mesh = Annulus::new(1.0, 1.2).mesh().resolution(16).build(); + let mesh = mesh.extractable_data_ref().unwrap(); assert_eq!( 32, @@ -1570,6 +1567,7 @@ mod tests { #[test] fn test_regular_polygon() { let mut mesh = Mesh::from(RegularPolygon::new(7.0, 4)); + let mesh = mesh.extractable_data_mut().unwrap(); let Some(VertexAttributeValues::Float32x3(mut positions)) = mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION) diff --git a/crates/bevy_mesh/src/primitives/dim3/capsule.rs b/crates/bevy_mesh/src/primitives/dim3/capsule.rs index f46ebce0d15a7..57abbaa12591d 100644 --- a/crates/bevy_mesh/src/primitives/dim3/capsule.rs +++ b/crates/bevy_mesh/src/primitives/dim3/capsule.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3}; use bevy_reflect::prelude::*; @@ -407,14 +406,13 @@ impl MeshBuilder for Capsule3dMeshBuilder { assert_eq!(vs.len(), vert_len); assert_eq!(tris.len(), fs_len as usize); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts) + .with_inserted_indices(Indices::U32(tris)), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts) - .with_inserted_indices(Indices::U32(tris)) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/cone.rs b/crates/bevy_mesh/src/primitives/dim3/cone.rs index d06a57f832ea5..c117071bc7d5b 100644 --- a/crates/bevy_mesh/src/primitives/dim3/cone.rs +++ b/crates/bevy_mesh/src/primitives/dim3/cone.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{ops, primitives::Cone, Vec3}; use bevy_reflect::prelude::*; @@ -160,14 +159,13 @@ impl MeshBuilder for ConeMeshBuilder { ConeAnchor::MidPoint => (), }; - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } @@ -191,6 +189,7 @@ impl From for Mesh { #[cfg(test)] mod tests { use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues}; + use bevy_asset::ExtractableAsset; use bevy_math::{primitives::Cone, Vec2}; /// Rounds floats to handle floating point error in tests. @@ -214,6 +213,7 @@ mod tests { .mesh() .resolution(4) .build(); + let mesh = mesh.extractable_data_mut().unwrap(); let Some(VertexAttributeValues::Float32x3(mut positions)) = mesh.remove_attribute(Mesh::ATTRIBUTE_POSITION) diff --git a/crates/bevy_mesh/src/primitives/dim3/conical_frustum.rs b/crates/bevy_mesh/src/primitives/dim3/conical_frustum.rs index 8c69378c01e64..20d123d444e5b 100644 --- a/crates/bevy_mesh/src/primitives/dim3/conical_frustum.rs +++ b/crates/bevy_mesh/src/primitives/dim3/conical_frustum.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{ops, primitives::ConicalFrustum, Vec3}; use bevy_reflect::prelude::*; @@ -155,14 +154,13 @@ impl MeshBuilder for ConicalFrustumMeshBuilder { build_cap(true, radius_top); build_cap(false, radius_bottom); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/cuboid.rs b/crates/bevy_mesh/src/primitives/dim3/cuboid.rs index 40a7cd45d4433..a3dbd7b4f3f9d 100644 --- a/crates/bevy_mesh/src/primitives/dim3/cuboid.rs +++ b/crates/bevy_mesh/src/primitives/dim3/cuboid.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{primitives::Cuboid, Vec3}; use bevy_reflect::prelude::*; @@ -71,14 +70,13 @@ impl MeshBuilder for CuboidMeshBuilder { 20, 21, 22, 22, 23, 20, // bottom ]); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(indices), ) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_inserted_indices(indices) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/cylinder.rs b/crates/bevy_mesh/src/primitives/dim3/cylinder.rs index 7b1b45974ea62..0626a6d9063ba 100644 --- a/crates/bevy_mesh/src/primitives/dim3/cylinder.rs +++ b/crates/bevy_mesh/src/primitives/dim3/cylinder.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{ops, primitives::Cylinder}; use bevy_reflect::prelude::*; @@ -193,14 +192,13 @@ impl MeshBuilder for CylinderMeshBuilder { CylinderAnchor::MidPoint => (), }; - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/plane.rs b/crates/bevy_mesh/src/primitives/dim3/plane.rs index fd892469be6af..de650df9f9fe2 100644 --- a/crates/bevy_mesh/src/primitives/dim3/plane.rs +++ b/crates/bevy_mesh/src/primitives/dim3/plane.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{primitives::Plane3d, Dir3, Quat, Vec2, Vec3}; use bevy_reflect::prelude::*; @@ -131,14 +130,13 @@ impl MeshBuilder for PlaneMeshBuilder { } } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/polyline3d.rs b/crates/bevy_mesh/src/primitives/dim3/polyline3d.rs index 4d13112579f09..c1a74ee339293 100644 --- a/crates/bevy_mesh/src/primitives/dim3/polyline3d.rs +++ b/crates/bevy_mesh/src/primitives/dim3/polyline3d.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::primitives::Polyline3d; use bevy_reflect::prelude::*; @@ -20,9 +19,11 @@ impl MeshBuilder for Polyline3dMeshBuilder { .collect(), ); - Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::LineList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions), + ) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/segment3d.rs b/crates/bevy_mesh/src/primitives/dim3/segment3d.rs index d032285283afb..9a1f7ac76bd95 100644 --- a/crates/bevy_mesh/src/primitives/dim3/segment3d.rs +++ b/crates/bevy_mesh/src/primitives/dim3/segment3d.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::primitives::Segment3d; use bevy_reflect::prelude::*; @@ -15,9 +14,11 @@ impl MeshBuilder for Segment3dMeshBuilder { let positions: Vec<_> = self.segment.vertices.into(); let indices = Indices::U32(vec![0, 1]); - Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::LineList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions), + ) } } @@ -39,12 +40,14 @@ impl From for Mesh { mod tests { use super::*; use crate::Meshable; + use bevy_asset::ExtractableAsset; use bevy_math::Vec3; #[test] fn segment3d_mesh_builder() { let segment = Segment3d::new(Vec3::ZERO, Vec3::X); let mesh = segment.mesh().build(); + let mesh = mesh.extractable_data_ref().unwrap(); assert_eq!(mesh.attribute(Mesh::ATTRIBUTE_POSITION).unwrap().len(), 2); assert_eq!(mesh.indices().unwrap().len(), 2); } diff --git a/crates/bevy_mesh/src/primitives/dim3/sphere.rs b/crates/bevy_mesh/src/primitives/dim3/sphere.rs index 6ae8eec5ed8bf..64c005d251ff3 100644 --- a/crates/bevy_mesh/src/primitives/dim3/sphere.rs +++ b/crates/bevy_mesh/src/primitives/dim3/sphere.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{ops, primitives::Sphere}; use bevy_reflect::prelude::*; use core::f32::consts::PI; @@ -151,14 +150,13 @@ impl SphereMeshBuilder { let indices = Indices::U32(indices); - Ok(Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)) + Ok(Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), + )) } /// Creates a UV sphere [`Mesh`] with the given number of @@ -220,14 +218,13 @@ impl SphereMeshBuilder { } } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/tetrahedron.rs b/crates/bevy_mesh/src/primitives/dim3/tetrahedron.rs index 529805d9a603f..1384fdcbd0859 100644 --- a/crates/bevy_mesh/src/primitives/dim3/tetrahedron.rs +++ b/crates/bevy_mesh/src/primitives/dim3/tetrahedron.rs @@ -1,6 +1,5 @@ use super::triangle3d; -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::primitives::{Tetrahedron, Triangle3d}; use bevy_reflect::prelude::*; @@ -40,14 +39,13 @@ impl MeshBuilder for TetrahedronMeshBuilder { // There are four faces and none of them share vertices. let indices = Indices::U32(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/torus.rs b/crates/bevy_mesh/src/primitives/dim3/torus.rs index 6f370c13418ca..3dce5e913b959 100644 --- a/crates/bevy_mesh/src/primitives/dim3/torus.rs +++ b/crates/bevy_mesh/src/primitives/dim3/torus.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{ops, primitives::Torus, Vec3}; use bevy_reflect::prelude::*; use core::ops::RangeInclusive; @@ -147,14 +146,13 @@ impl MeshBuilder for TorusMeshBuilder { } } - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_mesh/src/primitives/dim3/triangle3d.rs b/crates/bevy_mesh/src/primitives/dim3/triangle3d.rs index e35f272ab9bb1..3e0b88c412feb 100644 --- a/crates/bevy_mesh/src/primitives/dim3/triangle3d.rs +++ b/crates/bevy_mesh/src/primitives/dim3/triangle3d.rs @@ -1,5 +1,4 @@ -use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; -use bevy_asset::RenderAssetUsages; +use crate::{Indices, Mesh, MeshBuilder, MeshExtractableData, Meshable, PrimitiveTopology}; use bevy_math::{primitives::Triangle3d, Vec3}; use bevy_reflect::prelude::*; @@ -21,14 +20,13 @@ impl MeshBuilder for Triangle3dMeshBuilder { let indices = Indices::U32(vec![0, 1, 2]); - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(indices) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(indices) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/crates/bevy_mesh/src/primitives/extrusion.rs b/crates/bevy_mesh/src/primitives/extrusion.rs index 091735dc8349c..9db8545a0b4b9 100644 --- a/crates/bevy_mesh/src/primitives/extrusion.rs +++ b/crates/bevy_mesh/src/primitives/extrusion.rs @@ -1,10 +1,11 @@ +use bevy_asset::ExtractableAsset; use bevy_math::{ primitives::{Annulus, Capsule2d, Circle, Ellipse, Extrusion, Primitive2d}, Vec2, Vec3, }; use super::{MeshBuilder, Meshable}; -use crate::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues}; +use crate::{Indices, Mesh, MeshExtractableData, PrimitiveTopology, VertexAttributeValues}; /// A type representing a segment of the perimeter of an extrudable mesh. pub enum PerimeterSegment { @@ -179,10 +180,9 @@ where { fn build(&self) -> Mesh { // Create and move the base mesh to the front - let mut front_face = - self.base_builder - .build() - .translated_by(Vec3::new(0., 0., self.half_depth)); + let mut front_face_mesh = self.base_builder.build(); + let front_face = front_face_mesh.extractable_data_mut().unwrap(); + front_face.translate_by(Vec3::new(0., 0., self.half_depth)); // Move the uvs of the front face to be between (0., 0.) and (0.5, 0.5) if let Some(VertexAttributeValues::Float32x2(uvs)) = @@ -229,7 +229,7 @@ where // An extrusion of depth 0 does not need a mantel if self.half_depth == 0. { front_face.merge(&back_face).unwrap(); - return front_face; + return front_face_mesh; } let mantel = { @@ -413,16 +413,20 @@ where } } - Mesh::new(PrimitiveTopology::TriangleList, front_face.asset_usage) - .with_inserted_indices(Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), + ) }; front_face.merge(&back_face).unwrap(); - front_face.merge(&mantel).unwrap(); front_face + .merge(mantel.extractable_data_ref().unwrap()) + .unwrap(); + front_face_mesh } } diff --git a/crates/bevy_pbr/src/decal/forward.rs b/crates/bevy_pbr/src/decal/forward.rs index fa38e084dd7c7..023b85c0cc4d1 100644 --- a/crates/bevy_pbr/src/decal/forward.rs +++ b/crates/bevy_pbr/src/decal/forward.rs @@ -3,7 +3,7 @@ use crate::{ MaterialPlugin, StandardMaterial, }; use bevy_app::{App, Plugin}; -use bevy_asset::{Asset, Assets, Handle}; +use bevy_asset::{Asset, Assets, ExtractableAsset, Handle}; use bevy_ecs::{ component::Component, lifecycle::HookContext, resource::Resource, world::DeferredWorld, }; @@ -33,9 +33,12 @@ impl Plugin for ForwardDecalPlugin { Rectangle::from_size(Vec2::ONE) .mesh() .build() - .rotated_by(Quat::from_rotation_arc(Vec3::Z, Vec3::Y)) - .with_generated_tangents() - .unwrap(), + .with_extractable_data(|d| { + d.unwrap() + .rotated_by(Quat::from_rotation_arc(Vec3::Z, Vec3::Y)) + .with_generated_tangents() + .unwrap() + }), ); app.insert_resource(ForwardDecalMesh(mesh)); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 79163cb648134..03a6fdc0df063 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -391,5 +391,6 @@ pub fn stbn_placeholder() -> Image { texture_view_descriptor: None, asset_usage: RenderAssetUsages::RENDER_WORLD, copy_on_resize: false, + is_extracted_to_render_world: false, } } diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 23de97909404f..f498f976b5c02 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -2,12 +2,13 @@ use crate::meshlet::asset::{MeshletAabb, MeshletAabbErrorOffset, MeshletCullData use super::asset::{BvhNode, Meshlet, MeshletBoundingSphere, MeshletMesh}; use alloc::borrow::Cow; +use bevy_asset::ExtractableAsset; use bevy_math::{ bounding::{Aabb3d, BoundingSphere, BoundingVolume}, ops::log2, IVec3, Isometry3d, Vec2, Vec3, Vec3A, Vec3Swizzles, }; -use bevy_mesh::{Indices, Mesh}; +use bevy_mesh::{Indices, Mesh, MeshExtractableData}; use bevy_platform::collections::HashMap; use bevy_render::render_resource::PrimitiveTopology; use bevy_tasks::{AsyncComputeTaskPool, ParallelSlice}; @@ -71,6 +72,7 @@ impl MeshletMesh { let s = debug_span!("build meshlet mesh"); let _e = s.enter(); + let mesh = mesh.extractable_data_ref().unwrap(); // Validate mesh format let indices = validate_input_mesh(mesh)?; @@ -246,7 +248,9 @@ impl MeshletMesh { } } -fn validate_input_mesh(mesh: &Mesh) -> Result, MeshToMeshletMeshConversionError> { +fn validate_input_mesh( + mesh: &MeshExtractableData, +) -> Result, MeshToMeshletMeshConversionError> { if mesh.primitive_topology() != PrimitiveTopology::TriangleList { return Err(MeshToMeshletMeshConversionError::WrongMeshPrimitiveTopology); } diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 84bfc6ff53b39..53d82e345ee00 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -386,13 +386,13 @@ pub struct StandardMaterial { /// - Vertex normals /// /// Tangents do not have to be stored in your model, - /// they can be generated using the [`Mesh::generate_tangents`] or - /// [`Mesh::with_generated_tangents`] methods. + /// they can be generated using the [`MeshExtractableData::generate_tangents`] or + /// [`MeshExtractableData::with_generated_tangents`] methods. /// If your material has a normal map, but still renders as a flat surface, /// make sure your meshes have their tangents set. /// - /// [`Mesh::generate_tangents`]: bevy_mesh::Mesh::generate_tangents - /// [`Mesh::with_generated_tangents`]: bevy_mesh::Mesh::with_generated_tangents + /// [`MeshExtractableData::generate_tangents`]: bevy_mesh::MeshExtractableData::generate_tangents + /// [`MeshExtractableData::with_generated_tangents`]: bevy_mesh::MeshExtractableData::with_generated_tangents /// /// # Usage /// diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index e0243899f6b8d..515430768eb56 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1957,7 +1957,6 @@ pub fn build_dummy_white_gpu_image( sampler, size: image.texture_descriptor.size, mip_level_count: image.texture_descriptor.mip_level_count, - had_data: true, } } diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs index 24c6effab046c..dbf7a4c63e81c 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/intersections.rs @@ -1,5 +1,5 @@ use bevy_math::{bounding::Aabb3d, Affine3A, Dir3, Ray3d, Vec2, Vec3, Vec3A}; -use bevy_mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues}; +use bevy_mesh::{Indices, Mesh, MeshExtractableData, PrimitiveTopology, VertexAttributeValues}; use bevy_reflect::Reflect; use super::Backfaces; @@ -37,7 +37,7 @@ pub struct RayTriangleHit { /// Casts a ray on a mesh, and returns the intersection. pub(super) fn ray_intersection_over_mesh( - mesh: &Mesh, + mesh: &MeshExtractableData, transform: &Affine3A, ray: Ray3d, cull: Backfaces, diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs index 5fbcc370e1e98..db20fa88f1eea 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs @@ -17,7 +17,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use intersections::*; pub use intersections::{ray_aabb_intersection_3d, ray_mesh_intersection, RayMeshHit}; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Assets, ExtractableAsset, Handle}; use bevy_ecs::{prelude::*, system::lifetimeless::Read, system::SystemParam}; use bevy_math::FloatOrd; use bevy_transform::components::GlobalTransform; @@ -282,6 +282,10 @@ impl<'w, 's> MeshRayCast<'w, 's> { return; }; + let Ok(data) = mesh.extractable_data_ref() else { + return; + }; + // Backfaces of 2d meshes are never culled, unlike 3d meshes. let backfaces = match (has_backfaces, mesh2d.is_some()) { (false, false) => Backfaces::Cull, @@ -291,7 +295,7 @@ impl<'w, 's> MeshRayCast<'w, 's> { // Perform the actual ray cast. let _ray_cast_guard = ray_cast_guard.enter(); let transform = transform.affine(); - let intersection = ray_intersection_over_mesh(mesh, &transform, ray, backfaces); + let intersection = ray_intersection_over_mesh(data, &transform, ray, backfaces); if let Some(intersection) = intersection { let distance = FloatOrd(intersection.distance); diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index 654114e758f22..9a09e2c0ae7fa 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -1,7 +1,7 @@ //! Manages mesh vertex and index buffers. use alloc::vec::Vec; -use bevy_mesh::Indices; +use bevy_mesh::{Indices, MeshExtractableData}; use core::{ fmt::{self, Display, Formatter}, ops::Range, @@ -9,7 +9,7 @@ use core::{ use nonmax::NonMaxU32; use bevy_app::{App, Plugin}; -use bevy_asset::AssetId; +use bevy_asset::{AssetId, ExtractableAsset}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ resource::Resource, @@ -476,6 +476,7 @@ impl MeshAllocator { // Allocate. for (mesh_id, mesh) in &extracted_meshes.extracted { + let mesh = mesh.extractable_data_ref().unwrap(); let vertex_buffer_size = mesh.get_vertex_buffer_size() as u64; if vertex_buffer_size == 0 { continue; @@ -516,6 +517,7 @@ impl MeshAllocator { // Copy new mesh data in. for (mesh_id, mesh) in &extracted_meshes.extracted { + let mesh = mesh.extractable_data_ref().unwrap(); self.copy_mesh_vertex_data(mesh_id, mesh, render_device, render_queue); self.copy_mesh_index_data(mesh_id, mesh, render_device, render_queue); } @@ -526,7 +528,7 @@ impl MeshAllocator { fn copy_mesh_vertex_data( &mut self, mesh_id: &AssetId, - mesh: &Mesh, + mesh: &MeshExtractableData, render_device: &RenderDevice, render_queue: &RenderQueue, ) { @@ -551,7 +553,7 @@ impl MeshAllocator { fn copy_mesh_index_data( &mut self, mesh_id: &AssetId, - mesh: &Mesh, + mesh: &MeshExtractableData, render_device: &RenderDevice, render_queue: &RenderQueue, ) { @@ -1016,7 +1018,7 @@ impl ElementLayout { /// data. fn vertex( mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, - mesh: &Mesh, + mesh: &MeshExtractableData, ) -> ElementLayout { let mesh_vertex_buffer_layout = mesh.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts); @@ -1028,7 +1030,7 @@ impl ElementLayout { /// Creates the appropriate [`ElementLayout`] for the given mesh's index /// data. - fn index(mesh: &Mesh) -> Option { + fn index(mesh: &MeshExtractableData) -> Option { let size = match mesh.indices()? { Indices::U16(_) => 2, Indices::U32(_) => 4, diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index fb849cd51b3aa..94e1ca41dd840 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,14 +1,12 @@ pub mod allocator; use crate::{ - render_asset::{ - AssetExtractionError, PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets, - }, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, texture::GpuImage, RenderApp, }; use allocator::MeshAllocatorPlugin; use bevy_app::{App, Plugin}; -use bevy_asset::{AssetId, RenderAssetUsages}; +use bevy_asset::{AssetId, ExtractableAsset, RenderAssetUsages}; use bevy_ecs::{ prelude::*, system::{ @@ -135,14 +133,12 @@ impl RenderAsset for RenderMesh { fn take_gpu_data( source: &mut Self::SourceAsset, - _previous_gpu_asset: Option<&Self>, - ) -> Result { - source - .take_gpu_data() - .map_err(|_| AssetExtractionError::AlreadyExtracted) + ) -> Result { + source.take_gpu_data() } fn byte_len(mesh: &Self::SourceAsset) -> Option { + let mesh = mesh.extractable_data_ref().unwrap(); let mut vertex_size = 0; for attribute_data in mesh.attributes() { let vertex_format = attribute_data.0.format; @@ -156,16 +152,18 @@ impl RenderAsset for RenderMesh { /// Converts the extracted mesh into a [`RenderMesh`]. fn prepare_asset( - mesh: Self::SourceAsset, + mesh_source: Self::SourceAsset, _: AssetId, (_images, mesh_vertex_buffer_layouts): &mut SystemParamItem, _: Option<&Self>, ) -> Result> { + let mesh = mesh_source.extractable_data_ref().unwrap(); + #[cfg(feature = "morph")] let morph_targets = match mesh.morph_targets() { Some(mt) => { let Some(target_image) = _images.get(mt) else { - return Err(PrepareAssetError::RetryNextUpdate(mesh)); + return Err(PrepareAssetError::RetryNextUpdate(mesh_source)); }; Some(target_image.texture_view.clone()) } diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 8790f32fbfaa4..f0124a18d846e 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -3,7 +3,7 @@ use crate::{ RenderSystems, Res, }; use bevy_app::{App, Plugin, SubApp}; -use bevy_asset::{Asset, AssetEvent, AssetId, Assets, RenderAssetUsages}; +use bevy_asset::{Asset, AssetEvent, AssetExtractionError, AssetId, Assets, RenderAssetUsages}; use bevy_ecs::{ prelude::{Commands, IntoScheduleConfigs, MessageReader, ResMut, Resource}, schedule::{ScheduleConfigs, SystemSet}, @@ -28,15 +28,6 @@ pub enum PrepareAssetError { #[derive(SystemSet, Clone, PartialEq, Eq, Debug, Hash)] pub struct AssetExtractionSystems; -/// Error returned when an asset due for extraction has already been extracted -#[derive(Debug, Error)] -pub enum AssetExtractionError { - #[error("The asset has already been extracted")] - AlreadyExtracted, - #[error("The asset type does not support extraction. To clone the asset to the renderworld, use `RenderAssetUsages::default()`")] - NoExtractionImplementation, -} - /// Describes how an asset gets extracted and prepared for rendering. /// /// In the [`ExtractSchedule`] step the [`RenderAsset::SourceAsset`] is transferred @@ -94,12 +85,9 @@ pub trait RenderAsset: Send + Sync + 'static + Sized { /// Make a copy of the asset to be moved to the `RenderWorld` / gpu. Heavy internal data (pixels, vertex attributes) /// should be moved into the copy, leaving this asset with only metadata. - /// An error may be returned to indicate that the asset has already been extracted, and should not - /// have been modified on the CPU side (as it cannot be transferred to GPU again). - /// The previous GPU asset is also provided, which can be used to check if the modification is valid. + /// An error may be returned to indicate that the asset has already been extracted. fn take_gpu_data( _source: &mut Self::SourceAsset, - _previous_gpu_asset: Option<&Self>, ) -> Result { Err(AssetExtractionError::NoExtractionImplementation) } @@ -241,7 +229,6 @@ struct CachedExtractRenderAssetSystemState { state: SystemState<( MessageReader<'static, 'static, AssetEvent>, ResMut<'static, Assets>, - Option>>, )>, } @@ -261,7 +248,7 @@ pub(crate) fn extract_render_asset( ) { main_world.resource_scope( |world, mut cached_state: Mut>| { - let (mut events, mut assets, maybe_render_assets) = cached_state.state.get_mut(world); + let (mut events, mut assets) = cached_state.state.get_mut(world); let mut needs_extracting = >::default(); let mut removed = >::default(); @@ -303,8 +290,7 @@ pub(crate) fn extract_render_asset( if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) { if asset_usage == RenderAssetUsages::RENDER_WORLD { if let Some(asset) = assets.get_mut_untracked(id) { - let previous_asset = maybe_render_assets.as_ref().and_then(|render_assets| render_assets.get(id)); - match A::take_gpu_data(asset, previous_asset) { + match A::take_gpu_data(asset) { Ok(gpu_data_asset) => { extracted_assets.push((id, gpu_data_asset)); added.insert(id); diff --git a/crates/bevy_render/src/storage.rs b/crates/bevy_render/src/storage.rs index 0362add4ed959..b1aa26c198d8c 100644 --- a/crates/bevy_render/src/storage.rs +++ b/crates/bevy_render/src/storage.rs @@ -1,10 +1,10 @@ use crate::{ - render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset, RenderAssetPlugin}, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin}, render_resource::{Buffer, BufferUsages}, renderer::RenderDevice, }; use bevy_app::{App, Plugin}; -use bevy_asset::{Asset, AssetApp, AssetId, RenderAssetUsages}; +use bevy_asset::{Asset, AssetApp, AssetId, ExtractableAsset, RenderAssetUsages}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_utils::default; @@ -34,6 +34,50 @@ pub struct ShaderStorageBuffer { pub buffer_description: wgpu::BufferDescriptor<'static>, /// The asset usage of the storage buffer. pub asset_usage: RenderAssetUsages, + /// Whether this storage buffer has been extracted to the render world. + pub is_extracted_to_render_world: bool, +} + +impl ExtractableAsset for ShaderStorageBuffer { + type Data = Option>; + + fn extractable_data_replace(&mut self, data: Self::Data) -> Option { + let old_data = core::mem::replace(&mut self.data, data); + let old_data = if self.is_extracted_to_render_world { + None + } else { + Some(old_data) + }; + self.is_extracted_to_render_world = false; + old_data + } + + fn extractable_data_ref(&self) -> Result<&Self::Data, bevy_asset::ExtractableAssetAccessError> { + if self.is_extracted_to_render_world { + Err(bevy_asset::ExtractableAssetAccessError::ExtractedToRenderWorld) + } else { + Ok(&self.data) + } + } + + fn extractable_data_mut( + &mut self, + ) -> Result<&mut Self::Data, bevy_asset::ExtractableAssetAccessError> { + if self.is_extracted_to_render_world { + Err(bevy_asset::ExtractableAssetAccessError::ExtractedToRenderWorld) + } else { + Ok(&mut self.data) + } + } + + fn extract(&mut self) -> Result { + if self.is_extracted_to_render_world { + Err(bevy_asset::AssetExtractionError::AlreadyExtracted) + } else { + self.is_extracted_to_render_world = true; + Ok(self.data.take()) + } + } } impl Default for ShaderStorageBuffer { @@ -47,6 +91,7 @@ impl Default for ShaderStorageBuffer { mapped_at_creation: false, }, asset_usage: RenderAssetUsages::default(), + is_extracted_to_render_world: false, } } } @@ -101,7 +146,6 @@ where /// A storage buffer that is prepared as a [`RenderAsset`] and uploaded to the GPU. pub struct GpuShaderStorageBuffer { pub buffer: Buffer, - pub had_data: bool, } impl RenderAsset for GpuShaderStorageBuffer { @@ -114,18 +158,8 @@ impl RenderAsset for GpuShaderStorageBuffer { fn take_gpu_data( source: &mut Self::SourceAsset, - previous_gpu_asset: Option<&Self>, - ) -> Result { - let data = source.data.take(); - - let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data); - - valid_upload - .then(|| Self::SourceAsset { - data, - ..source.clone() - }) - .ok_or(AssetExtractionError::AlreadyExtracted) + ) -> Result { + source.take_gpu_data() } fn prepare_asset( @@ -141,17 +175,11 @@ impl RenderAsset for GpuShaderStorageBuffer { contents: &data, usage: source_asset.buffer_description.usage, }); - Ok(GpuShaderStorageBuffer { - buffer, - had_data: true, - }) + Ok(GpuShaderStorageBuffer { buffer }) } None => { let buffer = render_device.create_buffer(&source_asset.buffer_description); - Ok(GpuShaderStorageBuffer { - buffer, - had_data: false, - }) + Ok(GpuShaderStorageBuffer { buffer }) } } } diff --git a/crates/bevy_render/src/texture/fallback_image.rs b/crates/bevy_render/src/texture/fallback_image.rs index 738c84bcb9241..c6d4c271f958c 100644 --- a/crates/bevy_render/src/texture/fallback_image.rs +++ b/crates/bevy_render/src/texture/fallback_image.rs @@ -139,7 +139,6 @@ fn fallback_image_new( sampler, size: image.texture_descriptor.size, mip_level_count: image.texture_descriptor.mip_level_count, - had_data: true, } } diff --git a/crates/bevy_render/src/texture/gpu_image.rs b/crates/bevy_render/src/texture/gpu_image.rs index 43483fc993d1d..b75691dae15cd 100644 --- a/crates/bevy_render/src/texture/gpu_image.rs +++ b/crates/bevy_render/src/texture/gpu_image.rs @@ -1,9 +1,9 @@ use crate::{ - render_asset::{AssetExtractionError, PrepareAssetError, RenderAsset}, + render_asset::{PrepareAssetError, RenderAsset}, render_resource::{DefaultImageSampler, Sampler, Texture, TextureView}, renderer::{RenderDevice, RenderQueue}, }; -use bevy_asset::{AssetId, RenderAssetUsages}; +use bevy_asset::{AssetId, ExtractableAsset, RenderAssetUsages}; use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; use bevy_image::{Image, ImageSampler}; use bevy_math::{AspectRatio, UVec2}; @@ -21,7 +21,6 @@ pub struct GpuImage { pub sampler: Sampler, pub size: Extent3d, pub mip_level_count: u32, - pub had_data: bool, } impl RenderAsset for GpuImage { @@ -39,20 +38,8 @@ impl RenderAsset for GpuImage { fn take_gpu_data( source: &mut Self::SourceAsset, - previous_gpu_asset: Option<&Self>, - ) -> Result { - let data = source.data.take(); - - // check if this image originally had data and no longer does, that implies it - // has already been extracted - let valid_upload = data.is_some() || previous_gpu_asset.is_none_or(|prev| !prev.had_data); - - valid_upload - .then(|| Self::SourceAsset { - data, - ..source.clone() - }) - .ok_or(AssetExtractionError::AlreadyExtracted) + ) -> Result { + source.take_gpu_data() } #[inline] @@ -67,7 +54,6 @@ impl RenderAsset for GpuImage { (render_device, render_queue, default_sampler): &mut SystemParamItem, previous_asset: Option<&Self>, ) -> Result> { - let had_data = image.data.is_some(); let texture = if let Some(ref data) = image.data { render_device.create_texture_with_data( render_queue, @@ -131,7 +117,6 @@ impl RenderAsset for GpuImage { sampler, size: image.texture_descriptor.size, mip_level_count: image.texture_descriptor.mip_level_count, - had_data, }) } } diff --git a/crates/bevy_solari/src/scene/blas.rs b/crates/bevy_solari/src/scene/blas.rs index 58d6b7fd3abbb..447e95d2fcf16 100644 --- a/crates/bevy_solari/src/scene/blas.rs +++ b/crates/bevy_solari/src/scene/blas.rs @@ -1,5 +1,5 @@ use alloc::collections::VecDeque; -use bevy_asset::AssetId; +use bevy_asset::{AssetId, ExtractableAsset}; use bevy_ecs::{ resource::Resource, system::{Res, ResMut}, @@ -171,13 +171,17 @@ fn allocate_blas( } fn is_mesh_raytracing_compatible(mesh: &Mesh) -> bool { + let mesh_data = mesh.extractable_data_ref().unwrap(); let triangle_list = mesh.primitive_topology() == PrimitiveTopology::TriangleList; - let vertex_attributes = mesh.attributes().map(|(attribute, _)| attribute.id).eq([ - Mesh::ATTRIBUTE_POSITION.id, - Mesh::ATTRIBUTE_NORMAL.id, - Mesh::ATTRIBUTE_UV_0.id, - Mesh::ATTRIBUTE_TANGENT.id, - ]); - let indexed_32 = matches!(mesh.indices(), Some(Indices::U32(..))); + let vertex_attributes = mesh_data + .attributes() + .map(|(attribute, _)| attribute.id) + .eq([ + Mesh::ATTRIBUTE_POSITION.id, + Mesh::ATTRIBUTE_NORMAL.id, + Mesh::ATTRIBUTE_UV_0.id, + Mesh::ATTRIBUTE_TANGENT.id, + ]); + let indexed_32 = matches!(mesh_data.indices(), Some(Indices::U32(..))); mesh.enable_raytracing && triangle_list && vertex_attributes && indexed_32 } diff --git a/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs b/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs index 53039c3458de3..e9d85e69905f3 100644 --- a/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs +++ b/crates/bevy_sprite_render/src/tilemap_chunk/tilemap_chunk_material.rs @@ -110,5 +110,6 @@ pub fn make_chunk_tile_data_image(size: &UVec2, data: &[PackedTileData]) -> Imag texture_view_descriptor: None, asset_usage: RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD, copy_on_resize: false, + is_extracted_to_render_world: false, } } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 8eb5c051c3deb..b67dc7aa010d2 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -62,6 +62,7 @@ fn star( PrimitiveTopology::TriangleList, RenderAssetUsages::RENDER_WORLD, ); + let star_mut = star.extractable_data_mut().unwrap(); // Vertices need to have a position attribute. We will use the following // vertices (I hope you can spot the star in the schema). @@ -85,12 +86,12 @@ fn star( v_pos.push([r * ops::sin(a), r * ops::cos(a), 0.0]); } // Set the position attribute - star.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos); + star_mut.insert_attribute(Mesh::ATTRIBUTE_POSITION, v_pos); // And a RGB color attribute as well. A built-in `Mesh::ATTRIBUTE_COLOR` exists, but we // use a custom vertex attribute here for demonstration purposes. let mut v_color: Vec = vec![LinearRgba::BLACK.as_u32()]; v_color.extend_from_slice(&[LinearRgba::from(YELLOW).as_u32(); 10]); - star.insert_attribute( + star_mut.insert_attribute( MeshVertexAttribute::new("Vertex_Color", 1, VertexFormat::Uint32), v_color, ); @@ -108,7 +109,7 @@ fn star( for i in 2..=10 { indices.extend_from_slice(&[0, i, i - 1]); } - star.insert_indices(Indices::U32(indices)); + star_mut.insert_indices(Indices::U32(indices)); // We can now spawn the entities for the star and the camera commands.spawn(( diff --git a/examples/2d/mesh2d_vertex_color_texture.rs b/examples/2d/mesh2d_vertex_color_texture.rs index bd6e8199ffb6d..9e75c932a8734 100644 --- a/examples/2d/mesh2d_vertex_color_texture.rs +++ b/examples/2d/mesh2d_vertex_color_texture.rs @@ -28,7 +28,9 @@ fn setup( LinearRgba::WHITE.to_f32_array(), ]; // Insert the vertex colors as an attribute - mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors); + mesh.extractable_data_mut() + .unwrap() + .insert_attribute(Mesh::ATTRIBUTE_COLOR, vertex_colors); let mesh_handle = meshes.add(mesh); diff --git a/examples/3d/anisotropy.rs b/examples/3d/anisotropy.rs index 9804e3ea931e2..179988c55b0d1 100644 --- a/examples/3d/anisotropy.rs +++ b/examples/3d/anisotropy.rs @@ -115,8 +115,7 @@ fn setup(mut commands: Commands, asset_server: Res, app_status: Res Mesh3d( asset_server.add( Mesh::from(Sphere::new(0.1)) - .with_generated_tangents() - .unwrap(), + .with_extractable_data(|d| d.unwrap().with_generated_tangents().unwrap()), ), ), MeshMaterial3d(asset_server.add(StandardMaterial { diff --git a/examples/3d/clearcoat.rs b/examples/3d/clearcoat.rs index 613e95d4c62ea..d378b9839bc92 100644 --- a/examples/3d/clearcoat.rs +++ b/examples/3d/clearcoat.rs @@ -82,10 +82,11 @@ fn create_sphere_mesh(meshes: &mut Assets) -> Handle { // We're going to use normal maps, so make sure we've generated tangents, or // else the normal maps won't show up. - let mut sphere_mesh = Sphere::new(1.0).mesh().build(); - sphere_mesh - .generate_tangents() - .expect("Failed to generate tangents"); + let sphere_mesh = Sphere::new(1.0).mesh().build().with_extractable_data(|d| { + d.unwrap() + .with_generated_tangents() + .expect("Failed to generate tangents") + }); meshes.add(sphere_mesh) } diff --git a/examples/3d/clustered_decal_maps.rs b/examples/3d/clustered_decal_maps.rs index 0f88747fd1fd9..811a3b9cc0347 100644 --- a/examples/3d/clustered_decal_maps.rs +++ b/examples/3d/clustered_decal_maps.rs @@ -198,10 +198,13 @@ fn spawn_plane_mesh( } .mesh() .build() - .with_duplicated_vertices() - .with_computed_flat_normals() - .with_generated_tangents() - .unwrap(), + .with_extractable_data(|d| { + d.unwrap() + .with_duplicated_vertices() + .with_computed_flat_normals() + .with_generated_tangents() + .unwrap() + }), ); // Give the plane some texture. diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs index ff3656d7a6ecb..dd2749bf4ff0c 100644 --- a/examples/3d/deferred_rendering.rs +++ b/examples/3d/deferred_rendering.rs @@ -228,7 +228,10 @@ fn setup_parallax( // NOTE: for normal maps and depth maps to work, the mesh // needs tangents generated. - cube.generate_tangents().unwrap(); + cube.extractable_data_mut() + .unwrap() + .generate_tangents() + .unwrap(); let parallax_material = materials.add(StandardMaterial { perceptual_roughness: 0.4, diff --git a/examples/3d/generate_custom_mesh.rs b/examples/3d/generate_custom_mesh.rs index 7b4219effbd61..a7e40d75a0cd2 100644 --- a/examples/3d/generate_custom_mesh.rs +++ b/examples/3d/generate_custom_mesh.rs @@ -3,8 +3,7 @@ //! and how to change the UV mapping at run-time. use bevy::{ - asset::RenderAssetUsages, - mesh::{Indices, VertexAttributeValues}, + mesh::{Indices, MeshExtractableData, VertexAttributeValues}, prelude::*, render::render_resource::PrimitiveTopology, }; @@ -105,7 +104,7 @@ fn input_handler( #[rustfmt::skip] fn create_cube_mesh() -> Mesh { // Keep the mesh data accessible in future frames to be able to mutate it in toggle_texture. - Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD) + Mesh::from(MeshExtractableData::new(PrimitiveTopology::TriangleList) .with_inserted_attribute( Mesh::ATTRIBUTE_POSITION, // Each array is an [x, y, z] coordinate in local space. @@ -247,13 +246,17 @@ fn create_cube_mesh() -> Mesh { 12,13,15 , 13,14,15, // left (-x) 16,19,17 , 17,19,18, // back (+z) 20,21,23 , 21,22,23, // forward (-z) - ])) + ]))) } // Function that changes the UV mapping of the mesh, to apply the other texture. fn toggle_texture(mesh_to_change: &mut Mesh) { // Get a mutable reference to the values of the UV attribute, so we can iterate over it. - let uv_attribute = mesh_to_change.attribute_mut(Mesh::ATTRIBUTE_UV_0).unwrap(); + let uv_attribute = mesh_to_change + .extractable_data_mut() + .unwrap() + .attribute_mut(Mesh::ATTRIBUTE_UV_0) + .unwrap(); // The format of the UV coordinates should be Float32x2. let VertexAttributeValues::Float32x2(uv_attribute) = uv_attribute else { panic!("Unexpected vertex format, expected Float32x2."); diff --git a/examples/3d/lines.rs b/examples/3d/lines.rs index c29e9fb45c0a8..bc7ca1574d99a 100644 --- a/examples/3d/lines.rs +++ b/examples/3d/lines.rs @@ -2,7 +2,7 @@ use bevy::{ asset::RenderAssetUsages, - mesh::{MeshVertexBufferLayoutRef, PrimitiveTopology}, + mesh::{MeshExtractableData, MeshVertexBufferLayoutRef, PrimitiveTopology}, pbr::{MaterialPipeline, MaterialPipelineKey}, prelude::*, reflect::TypePath, @@ -96,14 +96,16 @@ impl From for Mesh { fn from(line: LineList) -> Self { let vertices: Vec<_> = line.lines.into_iter().flat_map(|(a, b)| [a, b]).collect(); - Mesh::new( - // This tells wgpu that the positions are list of lines - // where every pair is a start and end point - PrimitiveTopology::LineList, - RenderAssetUsages::RENDER_WORLD, + Mesh::from( + MeshExtractableData::new( + // This tells wgpu that the positions are list of lines + // where every pair is a start and end point + PrimitiveTopology::LineList, + ) + // Add the vertices positions as an attribute + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices), ) - // Add the vertices positions as an attribute - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + .with_asset_usage(RenderAssetUsages::RENDER_WORLD) } } @@ -115,13 +117,15 @@ struct LineStrip { impl From for Mesh { fn from(line: LineStrip) -> Self { - Mesh::new( - // This tells wgpu that the positions are a list of points - // where a line will be drawn between each consecutive point - PrimitiveTopology::LineStrip, - RenderAssetUsages::RENDER_WORLD, + Mesh::from( + MeshExtractableData::new( + // This tells wgpu that the positions are a list of points + // where a line will be drawn between each consecutive point + PrimitiveTopology::LineStrip, + ) + // Add the point positions as an attribute + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, line.points), ) - // Add the point positions as an attribute - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, line.points) + .with_asset_usage(RenderAssetUsages::RENDER_WORLD) } } diff --git a/examples/3d/motion_blur.rs b/examples/3d/motion_blur.rs index ef9349ee7b19d..e84bfc1e13299 100644 --- a/examples/3d/motion_blur.rs +++ b/examples/3d/motion_blur.rs @@ -85,7 +85,10 @@ fn setup_scene( let mut plane: Mesh = Plane3d::default().into(); let uv_size = 4000.0; let uvs = vec![[uv_size, 0.0], [0.0, 0.0], [0.0, uv_size], [uv_size; 2]]; - plane.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); + plane + .extractable_data_mut() + .unwrap() + .insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs); commands.spawn(( Mesh3d(meshes.add(plane)), MeshMaterial3d(materials.add(StandardMaterial { diff --git a/examples/3d/occlusion_culling.rs b/examples/3d/occlusion_culling.rs index d61356b9bca8c..6f1253f5619d2 100644 --- a/examples/3d/occlusion_culling.rs +++ b/examples/3d/occlusion_culling.rs @@ -315,7 +315,11 @@ fn spawn_small_cubes( .mesh() .ico(OUTER_SUBDIVISION_COUNT) .unwrap(); - let sphere_positions = sphere.attribute(Mesh::ATTRIBUTE_POSITION).unwrap(); + let sphere_positions = sphere + .extractable_data_ref() + .unwrap() + .attribute(Mesh::ATTRIBUTE_POSITION) + .unwrap(); // At each vertex, create a small cube. for sphere_position in sphere_positions.as_float3().unwrap() { diff --git a/examples/3d/parallax_mapping.rs b/examples/3d/parallax_mapping.rs index dad166adf61ef..a3cd22ad92904 100644 --- a/examples/3d/parallax_mapping.rs +++ b/examples/3d/parallax_mapping.rs @@ -267,8 +267,7 @@ fn setup( // NOTE: for normal maps and depth maps to work, the mesh // needs tangents generated. Mesh::from(Cuboid::default()) - .with_generated_tangents() - .unwrap(), + .with_extractable_data(|d| d.unwrap().with_generated_tangents().unwrap()), ), ), MeshMaterial3d(parallax_material.clone()), @@ -277,8 +276,7 @@ fn setup( let background_cube = meshes.add( Mesh::from(Cuboid::new(40.0, 40.0, 40.0)) - .with_generated_tangents() - .unwrap(), + .with_extractable_data(|d| d.unwrap().with_generated_tangents().unwrap()), ); let background_cube_bundle = |translation| { diff --git a/examples/3d/rotate_environment_map.rs b/examples/3d/rotate_environment_map.rs index 71a4fd751315c..bdc097829157f 100644 --- a/examples/3d/rotate_environment_map.rs +++ b/examples/3d/rotate_environment_map.rs @@ -50,10 +50,11 @@ fn create_sphere_mesh(meshes: &mut Assets) -> Handle { // We're going to use normal maps, so make sure we've generated tangents, or // else the normal maps won't show up. - let mut sphere_mesh = Sphere::new(1.0).mesh().build(); - sphere_mesh - .generate_tangents() - .expect("Failed to generate tangents"); + let sphere_mesh = Sphere::new(1.0).mesh().build().with_extractable_data(|d| { + d.unwrap() + .with_generated_tangents() + .expect("Failed to generate tangents") + }); meshes.add(sphere_mesh) } diff --git a/examples/3d/solari.rs b/examples/3d/solari.rs index 6449b40d2bb66..d05b0a3b14116 100644 --- a/examples/3d/solari.rs +++ b/examples/3d/solari.rs @@ -172,13 +172,17 @@ fn add_raytracing_meshes_on_scene_load( if let Ok((Mesh3d(mesh_handle), MeshMaterial3d(material_handle), material_name)) = mesh_query.get(descendant) { + // Ensure meshes are Solari compatible + let mesh = meshes.get_mut(mesh_handle).unwrap(); + let Ok(mesh) = mesh.extractable_data_mut() else { + continue; + }; + // Add raytracing mesh component commands .entity(descendant) .insert(RaytracingMesh3d(mesh_handle.clone())); - // Ensure meshes are Solari compatible - let mesh = meshes.get_mut(mesh_handle).unwrap(); if !mesh.contains_attribute(Mesh::ATTRIBUTE_UV_0) { let vertex_count = mesh.count_vertices(); mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0]; vertex_count]); diff --git a/examples/3d/vertex_colors.rs b/examples/3d/vertex_colors.rs index 3a5c31f94a7bf..6b33a2a0a3cf0 100644 --- a/examples/3d/vertex_colors.rs +++ b/examples/3d/vertex_colors.rs @@ -23,14 +23,19 @@ fn setup( // cube // Assign vertex colors based on vertex positions let mut colorful_cube = Mesh::from(Cuboid::default()); - if let Some(VertexAttributeValues::Float32x3(positions)) = - colorful_cube.attribute(Mesh::ATTRIBUTE_POSITION) + if let Some(VertexAttributeValues::Float32x3(positions)) = colorful_cube + .extractable_data_ref() + .unwrap() + .attribute(Mesh::ATTRIBUTE_POSITION) { let colors: Vec<[f32; 4]> = positions .iter() .map(|[r, g, b]| [(1. - *r) / 2., (1. - *g) / 2., (1. - *b) / 2., 1.]) .collect(); - colorful_cube.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors); + colorful_cube + .extractable_data_mut() + .unwrap() + .insert_attribute(Mesh::ATTRIBUTE_COLOR, colors); } commands.spawn(( Mesh3d(meshes.add(colorful_cube)), diff --git a/examples/animation/custom_skinned_mesh.rs b/examples/animation/custom_skinned_mesh.rs index e45d21bbc2282..81d1f7c6f8e4a 100644 --- a/examples/animation/custom_skinned_mesh.rs +++ b/examples/animation/custom_skinned_mesh.rs @@ -8,7 +8,7 @@ use bevy::{ math::ops, mesh::{ skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - Indices, PrimitiveTopology, VertexAttributeValues, + Indices, MeshExtractableData, PrimitiveTopology, VertexAttributeValues, }, prelude::*, }; @@ -54,88 +54,88 @@ fn setup( ]); // Create a mesh - let mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::RENDER_WORLD, + let mesh = Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + // Set mesh vertex positions + .with_inserted_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![ + [0.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [0.0, 0.5, 0.0], + [1.0, 0.5, 0.0], + [0.0, 1.0, 0.0], + [1.0, 1.0, 0.0], + [0.0, 1.5, 0.0], + [1.0, 1.5, 0.0], + [0.0, 2.0, 0.0], + [1.0, 2.0, 0.0], + ], + ) + // Add UV coordinates that map the left half of the texture since its a 1 x + // 2 rectangle. + .with_inserted_attribute( + Mesh::ATTRIBUTE_UV_0, + vec![ + [0.0, 0.00], + [0.5, 0.00], + [0.0, 0.25], + [0.5, 0.25], + [0.0, 0.50], + [0.5, 0.50], + [0.0, 0.75], + [0.5, 0.75], + [0.0, 1.00], + [0.5, 1.00], + ], + ) + // Set mesh vertex normals + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 10]) + // Set mesh vertex joint indices for mesh skinning. + // Each vertex gets 4 indices used to address the `JointTransforms` array in the vertex shader + // as well as `SkinnedMeshJoint` array in the `SkinnedMesh` component. + // This means that a maximum of 4 joints can affect a single vertex. + .with_inserted_attribute( + Mesh::ATTRIBUTE_JOINT_INDEX, + // Need to be explicit here as [u16; 4] could be either Uint16x4 or Unorm16x4. + VertexAttributeValues::Uint16x4(vec![ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + [0, 1, 0, 0], + ]), + ) + // Set mesh vertex joint weights for mesh skinning. + // Each vertex gets 4 joint weights corresponding to the 4 joint indices assigned to it. + // The sum of these weights should equal to 1. + .with_inserted_attribute( + Mesh::ATTRIBUTE_JOINT_WEIGHT, + vec![ + [1.00, 0.00, 0.0, 0.0], + [1.00, 0.00, 0.0, 0.0], + [0.75, 0.25, 0.0, 0.0], + [0.75, 0.25, 0.0, 0.0], + [0.50, 0.50, 0.0, 0.0], + [0.50, 0.50, 0.0, 0.0], + [0.25, 0.75, 0.0, 0.0], + [0.25, 0.75, 0.0, 0.0], + [0.00, 1.00, 0.0, 0.0], + [0.00, 1.00, 0.0, 0.0], + ], + ) + // Tell bevy to construct triangles from a list of vertex indices, + // where each 3 vertex indices form a triangle. + .with_inserted_indices(Indices::U16(vec![ + 0, 1, 3, 0, 3, 2, 2, 3, 5, 2, 5, 4, 4, 5, 7, 4, 7, 6, 6, 7, 9, 6, 9, 8, + ])), ) - // Set mesh vertex positions - .with_inserted_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![ - [0.0, 0.0, 0.0], - [1.0, 0.0, 0.0], - [0.0, 0.5, 0.0], - [1.0, 0.5, 0.0], - [0.0, 1.0, 0.0], - [1.0, 1.0, 0.0], - [0.0, 1.5, 0.0], - [1.0, 1.5, 0.0], - [0.0, 2.0, 0.0], - [1.0, 2.0, 0.0], - ], - ) - // Add UV coordinates that map the left half of the texture since its a 1 x - // 2 rectangle. - .with_inserted_attribute( - Mesh::ATTRIBUTE_UV_0, - vec![ - [0.0, 0.00], - [0.5, 0.00], - [0.0, 0.25], - [0.5, 0.25], - [0.0, 0.50], - [0.5, 0.50], - [0.0, 0.75], - [0.5, 0.75], - [0.0, 1.00], - [0.5, 1.00], - ], - ) - // Set mesh vertex normals - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 10]) - // Set mesh vertex joint indices for mesh skinning. - // Each vertex gets 4 indices used to address the `JointTransforms` array in the vertex shader - // as well as `SkinnedMeshJoint` array in the `SkinnedMesh` component. - // This means that a maximum of 4 joints can affect a single vertex. - .with_inserted_attribute( - Mesh::ATTRIBUTE_JOINT_INDEX, - // Need to be explicit here as [u16; 4] could be either Uint16x4 or Unorm16x4. - VertexAttributeValues::Uint16x4(vec![ - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - [0, 1, 0, 0], - ]), - ) - // Set mesh vertex joint weights for mesh skinning. - // Each vertex gets 4 joint weights corresponding to the 4 joint indices assigned to it. - // The sum of these weights should equal to 1. - .with_inserted_attribute( - Mesh::ATTRIBUTE_JOINT_WEIGHT, - vec![ - [1.00, 0.00, 0.0, 0.0], - [1.00, 0.00, 0.0, 0.0], - [0.75, 0.25, 0.0, 0.0], - [0.75, 0.25, 0.0, 0.0], - [0.50, 0.50, 0.0, 0.0], - [0.50, 0.50, 0.0, 0.0], - [0.25, 0.75, 0.0, 0.0], - [0.25, 0.75, 0.0, 0.0], - [0.00, 1.00, 0.0, 0.0], - [0.00, 1.00, 0.0, 0.0], - ], - ) - // Tell bevy to construct triangles from a list of vertex indices, - // where each 3 vertex indices form a triangle. - .with_inserted_indices(Indices::U16(vec![ - 0, 1, 3, 0, 3, 2, 2, 3, 5, 2, 5, 4, 4, 5, 7, 4, 7, 6, 6, 7, 9, 6, 9, 8, - ])); + .with_asset_usage(RenderAssetUsages::RENDER_WORLD); let mesh = meshes.add(mesh); diff --git a/examples/animation/morph_targets.rs b/examples/animation/morph_targets.rs index ffb468f26592c..21cdda7149831 100644 --- a/examples/animation/morph_targets.rs +++ b/examples/animation/morph_targets.rs @@ -86,7 +86,7 @@ fn name_morphs( if let AssetEvent::::Added { id } = event && let Some(path) = asset_server.get_path(*id) && let Some(mesh) = meshes.get(*id) - && let Some(names) = mesh.morph_target_names() + && let Some(names) = mesh.extractable_data_ref().unwrap().morph_target_names() { info!("Morph target names for {path:?}:"); diff --git a/examples/asset/alter_mesh.rs b/examples/asset/alter_mesh.rs index d62178e10cbc5..a85da07de5e2b 100644 --- a/examples/asset/alter_mesh.rs +++ b/examples/asset/alter_mesh.rs @@ -188,8 +188,10 @@ fn alter_mesh( // `ATTRIBUTE_POSITION` is a constant indicating that we want to know where the vertex is // located in space (as opposed to which way its normal is facing, vertex color, or other // details). - if let Some(VertexAttributeValues::Float32x3(positions)) = - mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) + if let Some(VertexAttributeValues::Float32x3(positions)) = mesh + .extractable_data_mut() + .unwrap() + .attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Check a Local value (which only this system can make use of) to determine if we're // currently scaled up or not. diff --git a/examples/ecs/error_handling.rs b/examples/ecs/error_handling.rs index d1c14a5ea8dad..0078159e4b16a 100644 --- a/examples/ecs/error_handling.rs +++ b/examples/ecs/error_handling.rs @@ -87,7 +87,10 @@ fn setup( // Create a new sphere mesh: let mut sphere_mesh = Sphere::new(1.0).mesh().ico(7)?; - sphere_mesh.generate_tangents()?; + sphere_mesh + .extractable_data_mut() + .unwrap() + .generate_tangents()?; // Spawn the mesh into the scene: let mut sphere = commands.spawn(( @@ -97,7 +100,7 @@ fn setup( )); // Generate random sample points: - let triangles = sphere_mesh.triangles()?; + let triangles = sphere_mesh.extractable_data_ref().unwrap().triangles()?; let distribution = UniformMeshSampler::try_new(triangles)?; // Setup sample points: diff --git a/examples/gltf/query_gltf_primitives.rs b/examples/gltf/query_gltf_primitives.rs index 892d087e2f24a..6b193aac79788 100644 --- a/examples/gltf/query_gltf_primitives.rs +++ b/examples/gltf/query_gltf_primitives.rs @@ -35,8 +35,10 @@ fn find_top_material_and_mesh( } if let Some(mesh) = meshes.get_mut(mesh_handle) - && let Some(VertexAttributeValues::Float32x3(positions)) = - mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION) + && let Some(VertexAttributeValues::Float32x3(positions)) = mesh + .extractable_data_mut() + .unwrap() + .attribute_mut(Mesh::ATTRIBUTE_POSITION) { for position in positions { *position = ( diff --git a/examples/math/custom_primitives.rs b/examples/math/custom_primitives.rs index ba2976d7a8472..731040b826bfd 100644 --- a/examples/math/custom_primitives.rs +++ b/examples/math/custom_primitives.rs @@ -7,7 +7,6 @@ use std::f32::consts::{PI, SQRT_2}; use bevy::pbr::wireframe::{WireframeConfig, WireframePlugin}; use bevy::{ - asset::RenderAssetUsages, camera::ScalingMode, color::palettes::css::{RED, WHITE}, input::common_conditions::{input_just_pressed, input_toggle_active}, @@ -17,7 +16,7 @@ use bevy::{ }, Isometry2d, }, - mesh::{Extrudable, ExtrusionBuilder, PerimeterSegment}, + mesh::{Extrudable, ExtrusionBuilder, MeshExtractableData, PerimeterSegment}, prelude::*, }; @@ -553,14 +552,13 @@ impl MeshBuilder for HeartMeshBuilder { } // Here, the actual `Mesh` is created. We set the indices, vertices, normals and UVs created above and specify the topology of the mesh. - Mesh::new( - bevy::mesh::PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), + Mesh::from( + MeshExtractableData::new(bevy::mesh::PrimitiveTopology::TriangleList) + .with_inserted_indices(bevy::mesh::Indices::U32(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs), ) - .with_inserted_indices(bevy::mesh::Indices::U32(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } diff --git a/examples/shader_advanced/custom_vertex_attribute.rs b/examples/shader_advanced/custom_vertex_attribute.rs index 1741b08e39fea..bad082d7a413f 100644 --- a/examples/shader_advanced/custom_vertex_attribute.rs +++ b/examples/shader_advanced/custom_vertex_attribute.rs @@ -32,13 +32,14 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - let mesh = Mesh::from(Cuboid::default()) + let mesh = Mesh::from(Cuboid::default()).with_extractable_data(|d| { // Sets the custom attribute - .with_inserted_attribute( + d.unwrap().with_inserted_attribute( ATTRIBUTE_BLEND_COLOR, // The cube mesh has 24 vertices (6 faces, 4 vertices per face), so we insert one BlendColor for each vec![[1.0, 0.0, 0.0, 1.0]; 24], - ); + ) + }); // cube commands.spawn(( diff --git a/examples/shader_advanced/specialized_mesh_pipeline.rs b/examples/shader_advanced/specialized_mesh_pipeline.rs index ee5abdb54d647..049def324d4b5 100644 --- a/examples/shader_advanced/specialized_mesh_pipeline.rs +++ b/examples/shader_advanced/specialized_mesh_pipeline.rs @@ -7,12 +7,11 @@ //! [`SpecializedMeshPipeline`] let's you customize the entire pipeline used when rendering a mesh. use bevy::{ - asset::RenderAssetUsages, camera::visibility::{self, VisibilityClass}, core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT}, ecs::change_detection::Tick, math::{vec3, vec4}, - mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology}, + mesh::{Indices, MeshExtractableData, MeshVertexBufferLayoutRef, PrimitiveTopology}, pbr::{ DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup, @@ -53,26 +52,25 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { // Build a custom triangle mesh with colors // We define a custom mesh because the examples only uses a limited // set of vertex attributes for simplicity - let mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_indices(Indices::U32(vec![0, 1, 2])) - .with_inserted_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![ - vec3(-0.5, -0.5, 0.0), - vec3(0.5, -0.5, 0.0), - vec3(0.0, 0.25, 0.0), - ], - ) - .with_inserted_attribute( - Mesh::ATTRIBUTE_COLOR, - vec![ - vec4(1.0, 0.0, 0.0, 1.0), - vec4(0.0, 1.0, 0.0, 1.0), - vec4(0.0, 0.0, 1.0, 1.0), - ], + let mesh = Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_indices(Indices::U32(vec![0, 1, 2])) + .with_inserted_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![ + vec3(-0.5, -0.5, 0.0), + vec3(0.5, -0.5, 0.0), + vec3(0.0, 0.25, 0.0), + ], + ) + .with_inserted_attribute( + Mesh::ATTRIBUTE_COLOR, + vec![ + vec4(1.0, 0.0, 0.0, 1.0), + vec4(0.0, 1.0, 0.0, 1.0), + vec4(0.0, 0.0, 1.0, 1.0), + ], + ), ); // spawn 3 triangles to show that batching works diff --git a/examples/tools/scene_viewer/morph_viewer_plugin.rs b/examples/tools/scene_viewer/morph_viewer_plugin.rs index 404c07d479e32..54082966393ad 100644 --- a/examples/tools/scene_viewer/morph_viewer_plugin.rs +++ b/examples/tools/scene_viewer/morph_viewer_plugin.rs @@ -9,7 +9,7 @@ //! Also illustrates how to read morph target names in [`detect_morphs`]. use crate::scene_viewer_plugin::SceneHandle; -use bevy::prelude::*; +use bevy::{mesh::MeshExtractableData, prelude::*}; use std::fmt; const FONT_SIZE: f32 = 13.0; @@ -260,7 +260,11 @@ fn detect_morphs( let target_names = weights .first_mesh() .and_then(|h| meshes.get(h)) - .and_then(|m| m.morph_target_names()); + .and_then(|m| { + m.extractable_data_ref() + .ok() + .and_then(MeshExtractableData::morph_target_names) + }); let targets = Target::new(name, weights.weights(), target_names, entity); detected.extend(targets); } diff --git a/tests/3d/test_invalid_skinned_mesh.rs b/tests/3d/test_invalid_skinned_mesh.rs index f371a06b7130e..3b07eb3d1bcd5 100644 --- a/tests/3d/test_invalid_skinned_mesh.rs +++ b/tests/3d/test_invalid_skinned_mesh.rs @@ -1,12 +1,11 @@ //! Test that the renderer can handle various invalid skinned meshes use bevy::{ - asset::RenderAssetUsages, camera::ScalingMode, math::ops, mesh::{ skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - Indices, PrimitiveTopology, VertexAttributeValues, + Indices, MeshExtractableData, PrimitiveTopology, VertexAttributeValues, }, post_process::motion_blur::MotionBlur, prelude::*, @@ -96,46 +95,46 @@ fn setup_meshes( mut inverse_bindposes_assets: ResMut>, ) { // Create a mesh with two rectangles. - let unskinned_mesh = Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_attribute( - Mesh::ATTRIBUTE_POSITION, - vec![ - [-0.3, -0.3, 0.0], - [0.3, -0.3, 0.0], - [-0.3, 0.3, 0.0], - [0.3, 0.3, 0.0], - [-0.4, 0.8, 0.0], - [0.4, 0.8, 0.0], - [-0.4, 1.8, 0.0], - [0.4, 1.8, 0.0], - ], - ) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 8]) - .with_inserted_indices(Indices::U16(vec![0, 1, 3, 0, 3, 2, 4, 5, 7, 4, 7, 6])); + let unskinned_mesh = Mesh::from( + MeshExtractableData::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute( + Mesh::ATTRIBUTE_POSITION, + vec![ + [-0.3, -0.3, 0.0], + [0.3, -0.3, 0.0], + [-0.3, 0.3, 0.0], + [0.3, 0.3, 0.0], + [-0.4, 0.8, 0.0], + [0.4, 0.8, 0.0], + [-0.4, 1.8, 0.0], + [0.4, 1.8, 0.0], + ], + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 8]) + .with_inserted_indices(Indices::U16(vec![0, 1, 3, 0, 3, 2, 4, 5, 7, 4, 7, 6])), + ); // Copy the mesh and add skinning attributes that bind each rectangle to a joint. - let skinned_mesh = unskinned_mesh - .clone() - .with_inserted_attribute( - Mesh::ATTRIBUTE_JOINT_INDEX, - VertexAttributeValues::Uint16x4(vec![ - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - [1, 0, 0, 0], - [1, 0, 0, 0], - [1, 0, 0, 0], - [1, 0, 0, 0], - ]), - ) - .with_inserted_attribute( - Mesh::ATTRIBUTE_JOINT_WEIGHT, - vec![[1.00, 0.00, 0.0, 0.0]; 8], - ); + let skinned_mesh = unskinned_mesh.clone().with_extractable_data(|d| { + d.unwrap() + .with_inserted_attribute( + Mesh::ATTRIBUTE_JOINT_INDEX, + VertexAttributeValues::Uint16x4(vec![ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 0, 0, 0], + [1, 0, 0, 0], + [1, 0, 0, 0], + [1, 0, 0, 0], + ]), + ) + .with_inserted_attribute( + Mesh::ATTRIBUTE_JOINT_WEIGHT, + vec![[1.00, 0.00, 0.0, 0.0]; 8], + ) + }); let unskinned_mesh_handle = mesh_assets.add(unskinned_mesh); let skinned_mesh_handle = mesh_assets.add(skinned_mesh);