diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index ce849d068952c..b5cc25cfed0da 100755 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -1244,7 +1244,7 @@ impl Plugin for AnimationPlugin { // `PostUpdate`. For now, we just disable ambiguity testing // for this system. animate_targets - .after(bevy_render::mesh::morph::inherit_weights) + .after(bevy_render::mesh::inherit_weights) .ambiguous_with_all(), trigger_untargeted_animation_events, expire_completed_transitions, diff --git a/crates/bevy_mesh/Cargo.toml b/crates/bevy_mesh/Cargo.toml new file mode 100644 index 0000000000000..1f3d542d67058 --- /dev/null +++ b/crates/bevy_mesh/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "bevy_mesh" +version = "0.15.0-dev" +edition = "2021" +description = "Provides mesh types for Bevy Engine" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[dependencies] +bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" } +bevy_image = { path = "../bevy_image", version = "0.15.0-dev" } +bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } +bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ + "bevy", +] } +bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } +bevy_transform = { path = "../bevy_transform", version = "0.15.0-dev" } +bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.15.0-dev" } +bevy_derive = { path = "../bevy_derive", version = "0.15.0-dev" } +bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } + +# misc +bitflags = { version = "2.3", features = ["serde"] } +bytemuck = { version = "1.5" } +wgpu = { version = "22", default-features = false } +serde = { version = "1", features = ["derive"] } +hexasphere = "15.0" +thiserror = "1.0" + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_render/src/mesh/mesh/conversions.rs b/crates/bevy_mesh/src/conversions.rs similarity index 98% rename from crates/bevy_render/src/mesh/mesh/conversions.rs rename to crates/bevy_mesh/src/conversions.rs index 3f3c8c2ae3243..f68c2d6f74321 100644 --- a/crates/bevy_render/src/mesh/mesh/conversions.rs +++ b/crates/bevy_mesh/src/conversions.rs @@ -4,15 +4,15 @@ //! # Examples //! //! ``` -//! use bevy_render::mesh::VertexAttributeValues; +//! use bevy_mesh::VertexAttributeValues; //! //! // creating std::vec::Vec //! let buffer = vec![[0_u32; 4]; 10]; //! -//! // converting std::vec::Vec to bevy_render::mesh::VertexAttributeValues +//! // converting std::vec::Vec to bevy_mesh::VertexAttributeValues //! let values = VertexAttributeValues::from(buffer.clone()); //! -//! // converting bevy_render::mesh::VertexAttributeValues to std::vec::Vec with two ways +//! // converting bevy_mesh::VertexAttributeValues to std::vec::Vec with two ways //! let result_into: Vec<[u32; 4]> = values.clone().try_into().unwrap(); //! let result_from: Vec<[u32; 4]> = Vec::try_from(values.clone()).unwrap(); //! @@ -24,7 +24,7 @@ //! assert!(error.is_err()); //! ``` -use crate::mesh::VertexAttributeValues; +use super::VertexAttributeValues; use bevy_math::{IVec2, IVec3, IVec4, UVec2, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec4}; use thiserror::Error; diff --git a/crates/bevy_mesh/src/index.rs b/crates/bevy_mesh/src/index.rs new file mode 100644 index 0000000000000..a6090a19d9beb --- /dev/null +++ b/crates/bevy_mesh/src/index.rs @@ -0,0 +1,137 @@ +use bevy_reflect::Reflect; +use core::iter::FusedIterator; +use thiserror::Error; +use wgpu::IndexFormat; + +/// 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 +/// branch taken. +pub(crate) enum FourIterators { + First(A), + Second(B), + Third(C), + Fourth(D), +} + +impl Iterator for FourIterators +where + A: Iterator, + B: Iterator, + C: Iterator, + D: Iterator, +{ + type Item = I; + + fn next(&mut self) -> Option { + match self { + FourIterators::First(iter) => iter.next(), + FourIterators::Second(iter) => iter.next(), + FourIterators::Third(iter) => iter.next(), + FourIterators::Fourth(iter) => iter.next(), + } + } +} + +/// An error that occurred while trying to invert the winding of a [`Mesh`](super::Mesh). +#[derive(Debug, Error)] +pub enum MeshWindingInvertError { + /// This error occurs when you try to invert the winding for a mesh with [`PrimitiveTopology::PointList`](super::PrimitiveTopology::PointList). + #[error("Mesh winding invertation does not work for primitive topology `PointList`")] + WrongTopology, + + /// This error occurs when you try to invert the winding for a mesh with + /// * [`PrimitiveTopology::TriangleList`](super::PrimitiveTopology::TriangleList), but the indices are not in chunks of 3. + /// * [`PrimitiveTopology::LineList`](super::PrimitiveTopology::LineList), but the indices are not in chunks of 2. + #[error("Indices weren't in chunks according to topology")] + AbruptIndicesEnd, +} + +/// An error that occurred while trying to extract a collection of triangles from a [`Mesh`](super::Mesh). +#[derive(Debug, Error)] +pub enum MeshTrianglesError { + #[error("Source mesh does not have primitive topology TriangleList or TriangleStrip")] + WrongTopology, + + #[error("Source mesh lacks position data")] + MissingPositions, + + #[error("Source mesh position data is not Float32x3")] + PositionsFormat, + + #[error("Source mesh lacks face index data")] + MissingIndices, + + #[error("Face index data references vertices that do not exist")] + BadIndices, +} + +/// An array of indices into the [`VertexAttributeValues`](super::VertexAttributeValues) for a mesh. +/// +/// It describes the order in which the vertex attributes should be joined into faces. +#[derive(Debug, Clone, Reflect)] +pub enum Indices { + U16(Vec), + U32(Vec), +} + +impl Indices { + /// Returns an iterator over the indices. + pub fn iter(&self) -> impl Iterator + '_ { + match self { + Indices::U16(vec) => IndicesIter::U16(vec.iter()), + Indices::U32(vec) => IndicesIter::U32(vec.iter()), + } + } + + /// Returns the number of indices. + pub fn len(&self) -> usize { + match self { + Indices::U16(vec) => vec.len(), + Indices::U32(vec) => vec.len(), + } + } + + /// Returns `true` if there are no indices. + pub fn is_empty(&self) -> bool { + match self { + Indices::U16(vec) => vec.is_empty(), + Indices::U32(vec) => vec.is_empty(), + } + } +} + +/// An Iterator for the [`Indices`]. +enum IndicesIter<'a> { + U16(core::slice::Iter<'a, u16>), + U32(core::slice::Iter<'a, u32>), +} + +impl Iterator for IndicesIter<'_> { + type Item = usize; + + fn next(&mut self) -> Option { + match self { + IndicesIter::U16(iter) => iter.next().map(|val| *val as usize), + IndicesIter::U32(iter) => iter.next().map(|val| *val as usize), + } + } + + fn size_hint(&self) -> (usize, Option) { + match self { + IndicesIter::U16(iter) => iter.size_hint(), + IndicesIter::U32(iter) => iter.size_hint(), + } + } +} + +impl<'a> ExactSizeIterator for IndicesIter<'a> {} +impl<'a> FusedIterator for IndicesIter<'a> {} + +impl From<&Indices> for IndexFormat { + fn from(indices: &Indices) -> Self { + match indices { + Indices::U16(_) => IndexFormat::Uint16, + Indices::U32(_) => IndexFormat::Uint32, + } + } +} diff --git a/crates/bevy_mesh/src/lib.rs b/crates/bevy_mesh/src/lib.rs new file mode 100644 index 0000000000000..110a46b76f899 --- /dev/null +++ b/crates/bevy_mesh/src/lib.rs @@ -0,0 +1,58 @@ +// FIXME(15321): solve CI failures, then replace with `#![expect()]`. +#![allow(missing_docs, reason = "Not all docs are written yet, see #3492.")] +#![allow(unsafe_code)] + +extern crate alloc; +extern crate core; + +mod conversions; +mod index; +mod mesh; +mod mikktspace; +pub mod morph; +pub mod primitives; +pub mod skinning; +mod vertex; +use bitflags::bitflags; +pub use index::*; +pub use mesh::*; +pub use mikktspace::*; +pub use primitives::*; +pub use vertex::*; + +bitflags! { + /// Our base mesh pipeline key bits start from the highest bit and go + /// downward. The PBR mesh pipeline key bits start from the lowest bit and + /// go upward. This allows the PBR bits in the downstream crate `bevy_pbr` + /// to coexist in the same field without any shifts. + #[derive(Clone, Debug)] + pub struct BaseMeshPipelineKey: u64 { + const MORPH_TARGETS = 1 << (u64::BITS - 1); + } +} + +impl BaseMeshPipelineKey { + pub const PRIMITIVE_TOPOLOGY_MASK_BITS: u64 = 0b111; + pub const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u64 = + (u64::BITS - 1 - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones()) as u64; + + pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { + let primitive_topology_bits = ((primitive_topology as u64) + & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) + << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; + Self::from_bits_retain(primitive_topology_bits) + } + + pub fn primitive_topology(&self) -> PrimitiveTopology { + let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS) + & Self::PRIMITIVE_TOPOLOGY_MASK_BITS; + match primitive_topology_bits { + x if x == PrimitiveTopology::PointList as u64 => PrimitiveTopology::PointList, + x if x == PrimitiveTopology::LineList as u64 => PrimitiveTopology::LineList, + x if x == PrimitiveTopology::LineStrip as u64 => PrimitiveTopology::LineStrip, + x if x == PrimitiveTopology::TriangleList as u64 => PrimitiveTopology::TriangleList, + x if x == PrimitiveTopology::TriangleStrip as u64 => PrimitiveTopology::TriangleStrip, + _ => PrimitiveTopology::default(), + } + } +} diff --git a/crates/bevy_render/src/mesh/mesh/mod.rs b/crates/bevy_mesh/src/mesh.rs similarity index 64% rename from crates/bevy_render/src/mesh/mesh/mod.rs rename to crates/bevy_mesh/src/mesh.rs index 6e6fd3f072f0c..4306698089fe6 100644 --- a/crates/bevy_render/src/mesh/mesh/mod.rs +++ b/crates/bevy_mesh/src/mesh.rs @@ -1,32 +1,20 @@ -mod conversions; -pub mod skinning; use bevy_transform::components::Transform; -use bitflags::bitflags; pub use wgpu::PrimitiveTopology; -use crate::{ - prelude::Image, - primitives::Aabb, - render_asset::{PrepareAssetError, RenderAsset, RenderAssetUsages, RenderAssets}, - render_resource::{TextureView, VertexBufferLayout}, - texture::GpuImage, +use super::{ + face_normal, generate_tangents_for_mesh, scale_normal, FourIterators, GenerateTangentsError, + Indices, MeshAttributeData, MeshTrianglesError, MeshVertexAttribute, MeshVertexAttributeId, + MeshVertexBufferLayout, MeshVertexBufferLayoutRef, MeshVertexBufferLayouts, + MeshWindingInvertError, VertexAttributeValues, VertexBufferLayout, VertexFormatSize, }; use alloc::collections::BTreeMap; -use bevy_asset::{Asset, Handle}; -use bevy_derive::EnumVariantMeta; -use bevy_ecs::system::{ - lifetimeless::{SRes, SResMut}, - SystemParamItem, -}; +use bevy_asset::{Asset, Handle, RenderAssetUsages}; +use bevy_image::Image; use bevy_math::{primitives::Triangle3d, *}; use bevy_reflect::Reflect; -use bevy_utils::tracing::{error, warn}; +use bevy_utils::tracing::warn; use bytemuck::cast_slice; -use core::{hash::Hash, iter::FusedIterator}; -use thiserror::Error; -use wgpu::{IndexFormat, VertexAttribute, VertexFormat, VertexStepMode}; - -use super::{MeshVertexBufferLayoutRef, MeshVertexBufferLayouts}; +use wgpu::{VertexAttribute, VertexFormat, VertexStepMode}; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; @@ -38,8 +26,8 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// or by converting a [primitive](bevy_math::primitives) using [`into`](Into). /// It is also possible to create one manually. They can be edited after creation. /// -/// Meshes can be rendered with a [`Mesh2d`](super::Mesh2d) and `MeshMaterial2d` -/// or [`Mesh3d`](super::Mesh3d) and `MeshMaterial3d` for 2D and 3D respectively. +/// Meshes can be rendered with a `Mesh2d` and `MeshMaterial2d` +/// or `Mesh3d` and `MeshMaterial3d` for 2D and 3D respectively. /// /// A [`Mesh`] in Bevy is equivalent to a "primitive" in the glTF format, for a /// glTF Mesh representation, see `GltfMesh`. @@ -50,9 +38,8 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// `StandardMaterial` or `ColorMaterial`: /// /// ``` -/// # use bevy_render::mesh::{Mesh, Indices}; -/// # use bevy_render::render_resource::PrimitiveTopology; -/// # use bevy_render::render_asset::RenderAssetUsages; +/// # use bevy_mesh::{Mesh, Indices, PrimitiveTopology}; +/// # use bevy_asset::RenderAssetUsages; /// 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()) @@ -90,7 +77,7 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// ## Other examples /// /// For further visualization, explanation, and examples, see the built-in Bevy examples, -/// and the [implementation of the built-in shapes](https://github.com/bevyengine/bevy/tree/main/crates/bevy_render/src/mesh/primitives). +/// and the [implementation of the built-in shapes](https://github.com/bevyengine/bevy/tree/main/crates/bevy_mesh/src/primitives). /// In particular, [generate_custom_mesh](https://github.com/bevyengine/bevy/blob/main/examples/3d/generate_custom_mesh.rs) /// teaches you to access and modify the attributes of a [`Mesh`] after creating it. /// @@ -101,8 +88,8 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// - It is possible and sometimes useful for multiple vertices to have the same /// [position attribute](Mesh::ATTRIBUTE_POSITION) value, /// it's a common technique in 3D modelling for complex UV mapping or other calculations. -/// - Bevy performs frustum culling based on the [`Aabb`] of meshes, which is calculated -/// and added automatically for new meshes only. If a mesh is modified, the entity's [`Aabb`] +/// - Bevy performs frustum culling based on the `Aabb` of meshes, which is calculated +/// and added automatically for new meshes only. If a mesh is modified, the entity's `Aabb` /// needs to be updated manually or deleted so that it is re-calculated. /// /// ## Use with `StandardMaterial` @@ -113,7 +100,7 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// - [`Normals`](Mesh::ATTRIBUTE_NORMAL): Bevy needs to know how light interacts with your mesh. /// [0.0, 0.0, 1.0] is very common for simple flat meshes on the XY plane, /// because simple meshes are smooth and they don't require complex light calculations. -/// - Vertex winding order: by default, `StandardMaterial.cull_mode` is [`Some(Face::Back)`](crate::render_resource::Face), +/// - Vertex winding order: by default, `StandardMaterial.cull_mode` is `Some(Face::Back)`, /// which means that Bevy would *only* render the "front" of each triangle, which /// is the side of the triangle from where the vertices appear in a *counter-clockwise* order. #[derive(Asset, Debug, Clone, Reflect)] @@ -158,7 +145,7 @@ impl Mesh { /// one color, for example a logo, and you want to "extend" those borders. /// /// For different mapping outside of `0..=1` range, - /// see [`ImageAddressMode`](crate::texture::ImageAddressMode). + /// see [`ImageAddressMode`](bevy_image::ImageAddressMode). /// /// The format of this attribute is [`VertexFormat::Float32x2`]. pub const ATTRIBUTE_UV_0: MeshVertexAttribute = @@ -225,7 +212,7 @@ impl Mesh { /// 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. + /// `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. @@ -253,7 +240,7 @@ impl Mesh { /// /// (Alternatively, you can use [`Mesh::insert_attribute`] to mutate an existing mesh in-place) /// - /// [`Aabb`] of entities with modified mesh are not updated automatically. + /// `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. @@ -401,9 +388,7 @@ impl Mesh { }) } - /// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in [`SpecializedMeshPipeline`]. - /// - /// [`SpecializedMeshPipeline`]: crate::render_resource::SpecializedMeshPipeline + /// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in `SpecializedMeshPipeline`. pub fn get_mesh_vertex_buffer_layout( &self, mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, @@ -802,7 +787,7 @@ impl Mesh { /// /// Note that attributes of `other` that don't exist on `self` will be ignored. /// - /// [`Aabb`] of entities with modified mesh are not updated automatically. + /// `Aabb` of entities with modified mesh are not updated automatically. /// /// # Panics /// @@ -881,7 +866,7 @@ impl Mesh { /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. /// - /// [`Aabb`] of entities with modified mesh are not updated automatically. + /// `Aabb` of entities with modified mesh are not updated automatically. pub fn transformed_by(mut self, transform: Transform) -> Self { self.transform_by(transform); self @@ -889,7 +874,7 @@ impl Mesh { /// 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. + /// `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; @@ -939,7 +924,7 @@ impl Mesh { /// Translates the vertex positions of the mesh by the given [`Vec3`]. /// - /// [`Aabb`] of entities with modified mesh are not updated automatically. + /// `Aabb` of entities with modified mesh are not updated automatically. pub fn translated_by(mut self, translation: Vec3) -> Self { self.translate_by(translation); self @@ -947,7 +932,7 @@ impl Mesh { /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. /// - /// [`Aabb`] of entities with modified mesh are not updated automatically. + /// `Aabb` of entities with modified mesh are not updated automatically. pub fn translate_by(&mut self, translation: Vec3) { if translation == Vec3::ZERO { return; @@ -965,7 +950,7 @@ impl Mesh { /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. /// - /// [`Aabb`] of entities with modified mesh are not updated automatically. + /// `Aabb` of entities with modified mesh are not updated automatically. pub fn rotated_by(mut self, rotation: Quat) -> Self { self.rotate_by(rotation); self @@ -973,7 +958,7 @@ impl Mesh { /// 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. + /// `Aabb` of entities with modified mesh are not updated automatically. pub fn rotate_by(&mut self, rotation: Quat) { if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = self.attribute_mut(Mesh::ATTRIBUTE_POSITION) @@ -1010,7 +995,7 @@ impl Mesh { /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. /// - /// [`Aabb`] of entities with modified mesh are not updated automatically. + /// `Aabb` of entities with modified mesh are not updated automatically. pub fn scaled_by(mut self, scale: Vec3) -> Self { self.scale_by(scale); self @@ -1018,7 +1003,7 @@ impl Mesh { /// 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. + /// `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; @@ -1061,35 +1046,25 @@ impl Mesh { } } - /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space - /// - /// Returns `None` if `self` doesn't have [`Mesh::ATTRIBUTE_POSITION`] of - /// type [`VertexAttributeValues::Float32x3`], or if `self` doesn't have any vertices. - pub fn compute_aabb(&self) -> Option { - let Some(VertexAttributeValues::Float32x3(values)) = - self.attribute(Mesh::ATTRIBUTE_POSITION) - else { - return None; - }; - - Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p))) - } - /// 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::mesh::morph::MorphTargetImage) for info. + /// 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 = Some(morph_targets); } + pub fn morph_targets(&self) -> Option<&Handle> { + 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::mesh::morph::MorphTargetImage) for info. + /// 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) /// @@ -1226,68 +1201,6 @@ impl Mesh { } } -/// A disjunction of four iterators. This is necessary to have a well-formed type for the output -/// of [`Mesh::triangles`], which produces iterators of four different types depending on the -/// branch taken. -enum FourIterators { - First(A), - Second(B), - Third(C), - Fourth(D), -} - -impl Iterator for FourIterators -where - A: Iterator, - B: Iterator, - C: Iterator, - D: Iterator, -{ - type Item = I; - - fn next(&mut self) -> Option { - match self { - FourIterators::First(iter) => iter.next(), - FourIterators::Second(iter) => iter.next(), - FourIterators::Third(iter) => iter.next(), - FourIterators::Fourth(iter) => iter.next(), - } - } -} - -/// An error that occurred while trying to invert the winding of a [`Mesh`]. -#[derive(Debug, Error)] -pub enum MeshWindingInvertError { - /// This error occurs when you try to invert the winding for a mesh with [`PrimitiveTopology::PointList`]. - #[error("Mesh winding invertation does not work for primitive topology `PointList`")] - WrongTopology, - - /// This error occurs when you try to invert the winding for a mesh with - /// * [`PrimitiveTopology::TriangleList`], but the indices are not in chunks of 3. - /// * [`PrimitiveTopology::LineList`], but the indices are not in chunks of 2. - #[error("Indices weren't in chunks according to topology")] - AbruptIndicesEnd, -} - -/// An error that occurred while trying to extract a collection of triangles from a [`Mesh`]. -#[derive(Debug, Error)] -pub enum MeshTrianglesError { - #[error("Source mesh does not have primitive topology TriangleList or TriangleStrip")] - WrongTopology, - - #[error("Source mesh lacks position data")] - MissingPositions, - - #[error("Source mesh position data is not Float32x3")] - PositionsFormat, - - #[error("Source mesh lacks face index data")] - MissingIndices, - - #[error("Face index data references vertices that do not exist")] - BadIndices, -} - impl core::ops::Mul for Transform { type Output = Mesh; @@ -1296,703 +1209,11 @@ impl core::ops::Mul for Transform { } } -#[derive(Debug, Clone, Copy)] -pub struct MeshVertexAttribute { - /// The friendly name of the vertex attribute - pub name: &'static str, - - /// The _unique_ id of the vertex attribute. This will also determine sort ordering - /// when generating vertex buffers. Built-in / standard attributes will use "close to zero" - /// indices. When in doubt, use a random / very large u64 to avoid conflicts. - pub id: MeshVertexAttributeId, - - /// The format of the vertex attribute. - pub format: VertexFormat, -} - -impl MeshVertexAttribute { - pub const fn new(name: &'static str, id: u64, format: VertexFormat) -> Self { - Self { - name, - id: MeshVertexAttributeId(id), - format, - } - } - - pub const fn at_shader_location(&self, shader_location: u32) -> VertexAttributeDescriptor { - VertexAttributeDescriptor::new(shader_location, self.id, self.name) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] -pub struct MeshVertexAttributeId(u64); - -impl From for MeshVertexAttributeId { - fn from(attribute: MeshVertexAttribute) -> Self { - attribute.id - } -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct MeshVertexBufferLayout { - attribute_ids: Vec, - layout: VertexBufferLayout, -} - -impl MeshVertexBufferLayout { - pub fn new(attribute_ids: Vec, layout: VertexBufferLayout) -> Self { - Self { - attribute_ids, - layout, - } - } - - #[inline] - pub fn contains(&self, attribute_id: impl Into) -> bool { - self.attribute_ids.contains(&attribute_id.into()) - } - - #[inline] - pub fn attribute_ids(&self) -> &[MeshVertexAttributeId] { - &self.attribute_ids - } - - #[inline] - pub fn layout(&self) -> &VertexBufferLayout { - &self.layout - } - - pub fn get_layout( - &self, - attribute_descriptors: &[VertexAttributeDescriptor], - ) -> Result { - let mut attributes = Vec::with_capacity(attribute_descriptors.len()); - for attribute_descriptor in attribute_descriptors { - if let Some(index) = self - .attribute_ids - .iter() - .position(|id| *id == attribute_descriptor.id) - { - let layout_attribute = &self.layout.attributes[index]; - attributes.push(VertexAttribute { - format: layout_attribute.format, - offset: layout_attribute.offset, - shader_location: attribute_descriptor.shader_location, - }); - } else { - return Err(MissingVertexAttributeError { - id: attribute_descriptor.id, - name: attribute_descriptor.name, - pipeline_type: None, - }); - } - } - - Ok(VertexBufferLayout { - array_stride: self.layout.array_stride, - step_mode: self.layout.step_mode, - attributes, - }) - } -} - -#[derive(Error, Debug)] -#[error("Mesh is missing requested attribute: {name} ({id:?}, pipeline type: {pipeline_type:?})")] -pub struct MissingVertexAttributeError { - pub(crate) pipeline_type: Option<&'static str>, - id: MeshVertexAttributeId, - name: &'static str, -} - -pub struct VertexAttributeDescriptor { - pub shader_location: u32, - pub id: MeshVertexAttributeId, - name: &'static str, -} - -impl VertexAttributeDescriptor { - pub const fn new(shader_location: u32, id: MeshVertexAttributeId, name: &'static str) -> Self { - Self { - shader_location, - id, - name, - } - } -} - -#[derive(Debug, Clone)] -struct MeshAttributeData { - attribute: MeshVertexAttribute, - values: VertexAttributeValues, -} - -fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] { - let (a, b, c) = (Vec3::from(a), Vec3::from(b), Vec3::from(c)); - (b - a).cross(c - a).normalize().into() -} - -pub trait VertexFormatSize { - fn get_size(self) -> u64; -} - -impl VertexFormatSize for VertexFormat { - #[allow(clippy::match_same_arms)] - fn get_size(self) -> u64 { - match self { - VertexFormat::Uint8x2 => 2, - VertexFormat::Uint8x4 => 4, - VertexFormat::Sint8x2 => 2, - VertexFormat::Sint8x4 => 4, - VertexFormat::Unorm8x2 => 2, - VertexFormat::Unorm8x4 => 4, - VertexFormat::Snorm8x2 => 2, - VertexFormat::Snorm8x4 => 4, - VertexFormat::Unorm10_10_10_2 => 4, - VertexFormat::Uint16x2 => 2 * 2, - VertexFormat::Uint16x4 => 2 * 4, - VertexFormat::Sint16x2 => 2 * 2, - VertexFormat::Sint16x4 => 2 * 4, - VertexFormat::Unorm16x2 => 2 * 2, - VertexFormat::Unorm16x4 => 2 * 4, - VertexFormat::Snorm16x2 => 2 * 2, - VertexFormat::Snorm16x4 => 2 * 4, - VertexFormat::Float16x2 => 2 * 2, - VertexFormat::Float16x4 => 2 * 4, - VertexFormat::Float32 => 4, - VertexFormat::Float32x2 => 4 * 2, - VertexFormat::Float32x3 => 4 * 3, - VertexFormat::Float32x4 => 4 * 4, - VertexFormat::Uint32 => 4, - VertexFormat::Uint32x2 => 4 * 2, - VertexFormat::Uint32x3 => 4 * 3, - VertexFormat::Uint32x4 => 4 * 4, - VertexFormat::Sint32 => 4, - VertexFormat::Sint32x2 => 4 * 2, - VertexFormat::Sint32x3 => 4 * 3, - VertexFormat::Sint32x4 => 4 * 4, - VertexFormat::Float64 => 8, - VertexFormat::Float64x2 => 8 * 2, - VertexFormat::Float64x3 => 8 * 3, - VertexFormat::Float64x4 => 8 * 4, - } - } -} - -/// Contains an array where each entry describes a property of a single vertex. -/// Matches the [`VertexFormats`](VertexFormat). -#[derive(Clone, Debug, EnumVariantMeta)] -pub enum VertexAttributeValues { - Float32(Vec), - Sint32(Vec), - Uint32(Vec), - Float32x2(Vec<[f32; 2]>), - Sint32x2(Vec<[i32; 2]>), - Uint32x2(Vec<[u32; 2]>), - Float32x3(Vec<[f32; 3]>), - Sint32x3(Vec<[i32; 3]>), - Uint32x3(Vec<[u32; 3]>), - Float32x4(Vec<[f32; 4]>), - Sint32x4(Vec<[i32; 4]>), - Uint32x4(Vec<[u32; 4]>), - Sint16x2(Vec<[i16; 2]>), - Snorm16x2(Vec<[i16; 2]>), - Uint16x2(Vec<[u16; 2]>), - Unorm16x2(Vec<[u16; 2]>), - Sint16x4(Vec<[i16; 4]>), - Snorm16x4(Vec<[i16; 4]>), - Uint16x4(Vec<[u16; 4]>), - Unorm16x4(Vec<[u16; 4]>), - Sint8x2(Vec<[i8; 2]>), - Snorm8x2(Vec<[i8; 2]>), - Uint8x2(Vec<[u8; 2]>), - Unorm8x2(Vec<[u8; 2]>), - Sint8x4(Vec<[i8; 4]>), - Snorm8x4(Vec<[i8; 4]>), - Uint8x4(Vec<[u8; 4]>), - Unorm8x4(Vec<[u8; 4]>), -} - -impl VertexAttributeValues { - /// Returns the number of vertices in this [`VertexAttributeValues`]. For a single - /// mesh, all of the [`VertexAttributeValues`] must have the same length. - #[allow(clippy::match_same_arms)] - pub fn len(&self) -> usize { - match self { - VertexAttributeValues::Float32(values) => values.len(), - VertexAttributeValues::Sint32(values) => values.len(), - VertexAttributeValues::Uint32(values) => values.len(), - VertexAttributeValues::Float32x2(values) => values.len(), - VertexAttributeValues::Sint32x2(values) => values.len(), - VertexAttributeValues::Uint32x2(values) => values.len(), - VertexAttributeValues::Float32x3(values) => values.len(), - VertexAttributeValues::Sint32x3(values) => values.len(), - VertexAttributeValues::Uint32x3(values) => values.len(), - VertexAttributeValues::Float32x4(values) => values.len(), - VertexAttributeValues::Sint32x4(values) => values.len(), - VertexAttributeValues::Uint32x4(values) => values.len(), - VertexAttributeValues::Sint16x2(values) => values.len(), - VertexAttributeValues::Snorm16x2(values) => values.len(), - VertexAttributeValues::Uint16x2(values) => values.len(), - VertexAttributeValues::Unorm16x2(values) => values.len(), - VertexAttributeValues::Sint16x4(values) => values.len(), - VertexAttributeValues::Snorm16x4(values) => values.len(), - VertexAttributeValues::Uint16x4(values) => values.len(), - VertexAttributeValues::Unorm16x4(values) => values.len(), - VertexAttributeValues::Sint8x2(values) => values.len(), - VertexAttributeValues::Snorm8x2(values) => values.len(), - VertexAttributeValues::Uint8x2(values) => values.len(), - VertexAttributeValues::Unorm8x2(values) => values.len(), - VertexAttributeValues::Sint8x4(values) => values.len(), - VertexAttributeValues::Snorm8x4(values) => values.len(), - VertexAttributeValues::Uint8x4(values) => values.len(), - VertexAttributeValues::Unorm8x4(values) => values.len(), - } - } - - /// Returns `true` if there are no vertices in this [`VertexAttributeValues`]. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns the values as float triples if possible. - pub fn as_float3(&self) -> Option<&[[f32; 3]]> { - match self { - VertexAttributeValues::Float32x3(values) => Some(values), - _ => None, - } - } - - // TODO: add vertex format as parameter here and perform type conversions - /// Flattens the [`VertexAttributeValues`] into a sequence of bytes. This is - /// useful for serialization and sending to the GPU. - #[allow(clippy::match_same_arms)] - pub fn get_bytes(&self) -> &[u8] { - match self { - VertexAttributeValues::Float32(values) => cast_slice(values), - VertexAttributeValues::Sint32(values) => cast_slice(values), - VertexAttributeValues::Uint32(values) => cast_slice(values), - VertexAttributeValues::Float32x2(values) => cast_slice(values), - VertexAttributeValues::Sint32x2(values) => cast_slice(values), - VertexAttributeValues::Uint32x2(values) => cast_slice(values), - VertexAttributeValues::Float32x3(values) => cast_slice(values), - VertexAttributeValues::Sint32x3(values) => cast_slice(values), - VertexAttributeValues::Uint32x3(values) => cast_slice(values), - VertexAttributeValues::Float32x4(values) => cast_slice(values), - VertexAttributeValues::Sint32x4(values) => cast_slice(values), - VertexAttributeValues::Uint32x4(values) => cast_slice(values), - VertexAttributeValues::Sint16x2(values) => cast_slice(values), - VertexAttributeValues::Snorm16x2(values) => cast_slice(values), - VertexAttributeValues::Uint16x2(values) => cast_slice(values), - VertexAttributeValues::Unorm16x2(values) => cast_slice(values), - VertexAttributeValues::Sint16x4(values) => cast_slice(values), - VertexAttributeValues::Snorm16x4(values) => cast_slice(values), - VertexAttributeValues::Uint16x4(values) => cast_slice(values), - VertexAttributeValues::Unorm16x4(values) => cast_slice(values), - VertexAttributeValues::Sint8x2(values) => cast_slice(values), - VertexAttributeValues::Snorm8x2(values) => cast_slice(values), - VertexAttributeValues::Uint8x2(values) => cast_slice(values), - VertexAttributeValues::Unorm8x2(values) => cast_slice(values), - VertexAttributeValues::Sint8x4(values) => cast_slice(values), - VertexAttributeValues::Snorm8x4(values) => cast_slice(values), - VertexAttributeValues::Uint8x4(values) => cast_slice(values), - VertexAttributeValues::Unorm8x4(values) => cast_slice(values), - } - } -} - -impl From<&VertexAttributeValues> for VertexFormat { - fn from(values: &VertexAttributeValues) -> Self { - match values { - VertexAttributeValues::Float32(_) => VertexFormat::Float32, - VertexAttributeValues::Sint32(_) => VertexFormat::Sint32, - VertexAttributeValues::Uint32(_) => VertexFormat::Uint32, - VertexAttributeValues::Float32x2(_) => VertexFormat::Float32x2, - VertexAttributeValues::Sint32x2(_) => VertexFormat::Sint32x2, - VertexAttributeValues::Uint32x2(_) => VertexFormat::Uint32x2, - VertexAttributeValues::Float32x3(_) => VertexFormat::Float32x3, - VertexAttributeValues::Sint32x3(_) => VertexFormat::Sint32x3, - VertexAttributeValues::Uint32x3(_) => VertexFormat::Uint32x3, - VertexAttributeValues::Float32x4(_) => VertexFormat::Float32x4, - VertexAttributeValues::Sint32x4(_) => VertexFormat::Sint32x4, - VertexAttributeValues::Uint32x4(_) => VertexFormat::Uint32x4, - VertexAttributeValues::Sint16x2(_) => VertexFormat::Sint16x2, - VertexAttributeValues::Snorm16x2(_) => VertexFormat::Snorm16x2, - VertexAttributeValues::Uint16x2(_) => VertexFormat::Uint16x2, - VertexAttributeValues::Unorm16x2(_) => VertexFormat::Unorm16x2, - VertexAttributeValues::Sint16x4(_) => VertexFormat::Sint16x4, - VertexAttributeValues::Snorm16x4(_) => VertexFormat::Snorm16x4, - VertexAttributeValues::Uint16x4(_) => VertexFormat::Uint16x4, - VertexAttributeValues::Unorm16x4(_) => VertexFormat::Unorm16x4, - VertexAttributeValues::Sint8x2(_) => VertexFormat::Sint8x2, - VertexAttributeValues::Snorm8x2(_) => VertexFormat::Snorm8x2, - VertexAttributeValues::Uint8x2(_) => VertexFormat::Uint8x2, - VertexAttributeValues::Unorm8x2(_) => VertexFormat::Unorm8x2, - VertexAttributeValues::Sint8x4(_) => VertexFormat::Sint8x4, - VertexAttributeValues::Snorm8x4(_) => VertexFormat::Snorm8x4, - VertexAttributeValues::Uint8x4(_) => VertexFormat::Uint8x4, - VertexAttributeValues::Unorm8x4(_) => VertexFormat::Unorm8x4, - } - } -} -/// An array of indices into the [`VertexAttributeValues`] for a mesh. -/// -/// It describes the order in which the vertex attributes should be joined into faces. -#[derive(Debug, Clone, Reflect)] -pub enum Indices { - U16(Vec), - U32(Vec), -} - -impl Indices { - /// Returns an iterator over the indices. - pub fn iter(&self) -> impl Iterator + '_ { - match self { - Indices::U16(vec) => IndicesIter::U16(vec.iter()), - Indices::U32(vec) => IndicesIter::U32(vec.iter()), - } - } - - /// Returns the number of indices. - pub fn len(&self) -> usize { - match self { - Indices::U16(vec) => vec.len(), - Indices::U32(vec) => vec.len(), - } - } - - /// Returns `true` if there are no indices. - pub fn is_empty(&self) -> bool { - match self { - Indices::U16(vec) => vec.is_empty(), - Indices::U32(vec) => vec.is_empty(), - } - } -} - -/// An Iterator for the [`Indices`]. -enum IndicesIter<'a> { - U16(core::slice::Iter<'a, u16>), - U32(core::slice::Iter<'a, u32>), -} - -impl Iterator for IndicesIter<'_> { - type Item = usize; - - fn next(&mut self) -> Option { - match self { - IndicesIter::U16(iter) => iter.next().map(|val| *val as usize), - IndicesIter::U32(iter) => iter.next().map(|val| *val as usize), - } - } - - fn size_hint(&self) -> (usize, Option) { - match self { - IndicesIter::U16(iter) => iter.size_hint(), - IndicesIter::U32(iter) => iter.size_hint(), - } - } -} - -impl<'a> ExactSizeIterator for IndicesIter<'a> {} -impl<'a> FusedIterator for IndicesIter<'a> {} - -impl From<&Indices> for IndexFormat { - fn from(indices: &Indices) -> Self { - match indices { - Indices::U16(_) => IndexFormat::Uint16, - Indices::U32(_) => IndexFormat::Uint32, - } - } -} - -bitflags! { - /// Our base mesh pipeline key bits start from the highest bit and go - /// downward. The PBR mesh pipeline key bits start from the lowest bit and - /// go upward. This allows the PBR bits in the downstream crate `bevy_pbr` - /// to coexist in the same field without any shifts. - #[derive(Clone, Debug)] - pub struct BaseMeshPipelineKey: u64 { - const MORPH_TARGETS = 1 << (u64::BITS - 1); - } -} - -impl BaseMeshPipelineKey { - pub const PRIMITIVE_TOPOLOGY_MASK_BITS: u64 = 0b111; - pub const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u64 = - (u64::BITS - 1 - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones()) as u64; - - pub fn from_primitive_topology(primitive_topology: PrimitiveTopology) -> Self { - let primitive_topology_bits = ((primitive_topology as u64) - & Self::PRIMITIVE_TOPOLOGY_MASK_BITS) - << Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS; - Self::from_bits_retain(primitive_topology_bits) - } - - pub fn primitive_topology(&self) -> PrimitiveTopology { - let primitive_topology_bits = (self.bits() >> Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS) - & Self::PRIMITIVE_TOPOLOGY_MASK_BITS; - match primitive_topology_bits { - x if x == PrimitiveTopology::PointList as u64 => PrimitiveTopology::PointList, - x if x == PrimitiveTopology::LineList as u64 => PrimitiveTopology::LineList, - x if x == PrimitiveTopology::LineStrip as u64 => PrimitiveTopology::LineStrip, - x if x == PrimitiveTopology::TriangleList as u64 => PrimitiveTopology::TriangleList, - x if x == PrimitiveTopology::TriangleStrip as u64 => PrimitiveTopology::TriangleStrip, - _ => PrimitiveTopology::default(), - } - } -} - -/// The render world representation of a [`Mesh`]. -#[derive(Debug, Clone)] -pub struct RenderMesh { - /// The number of vertices in the mesh. - pub vertex_count: u32, - - /// Morph targets for the mesh, if present. - pub morph_targets: Option, - - /// Information about the mesh data buffers, including whether the mesh uses - /// indices or not. - pub buffer_info: RenderMeshBufferInfo, - - /// Precomputed pipeline key bits for this mesh. - pub key_bits: BaseMeshPipelineKey, - - /// A reference to the vertex buffer layout. - /// - /// Combined with [`RenderMesh::buffer_info`], this specifies the complete - /// layout of the buffers associated with this mesh. - pub layout: MeshVertexBufferLayoutRef, -} - -impl RenderMesh { - /// Returns the primitive topology of this mesh (triangles, triangle strips, - /// etc.) - #[inline] - pub fn primitive_topology(&self) -> PrimitiveTopology { - self.key_bits.primitive_topology() - } -} - -/// The index/vertex buffer info of a [`RenderMesh`]. -#[derive(Debug, Clone)] -pub enum RenderMeshBufferInfo { - Indexed { - count: u32, - index_format: IndexFormat, - }, - NonIndexed, -} - -impl RenderAsset for RenderMesh { - type SourceAsset = Mesh; - type Param = ( - SRes>, - SResMut, - ); - - #[inline] - fn asset_usage(mesh: &Self::SourceAsset) -> RenderAssetUsages { - mesh.asset_usage - } - - fn byte_len(mesh: &Self::SourceAsset) -> Option { - let mut vertex_size = 0; - for attribute_data in mesh.attributes.values() { - let vertex_format = attribute_data.attribute.format; - vertex_size += vertex_format.get_size() as usize; - } - - let vertex_count = mesh.count_vertices(); - let index_bytes = mesh.get_index_buffer_bytes().map(<[_]>::len).unwrap_or(0); - Some(vertex_size * vertex_count + index_bytes) - } - - /// Converts the extracted mesh into a [`RenderMesh`]. - fn prepare_asset( - mesh: Self::SourceAsset, - (images, ref mut mesh_vertex_buffer_layouts): &mut SystemParamItem, - ) -> Result> { - let morph_targets = match mesh.morph_targets.as_ref() { - Some(mt) => { - let Some(target_image) = images.get(mt) else { - return Err(PrepareAssetError::RetryNextUpdate(mesh)); - }; - Some(target_image.texture_view.clone()) - } - None => None, - }; - - let buffer_info = match mesh.indices() { - Some(indices) => RenderMeshBufferInfo::Indexed { - count: indices.len() as u32, - index_format: indices.into(), - }, - None => RenderMeshBufferInfo::NonIndexed, - }; - - let mesh_vertex_buffer_layout = - mesh.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts); - - let mut key_bits = BaseMeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); - key_bits.set( - BaseMeshPipelineKey::MORPH_TARGETS, - mesh.morph_targets.is_some(), - ); - - Ok(RenderMesh { - vertex_count: mesh.count_vertices() as u32, - buffer_info, - key_bits, - layout: mesh_vertex_buffer_layout, - morph_targets, - }) - } -} - -struct MikktspaceGeometryHelper<'a> { - indices: Option<&'a Indices>, - positions: &'a Vec<[f32; 3]>, - normals: &'a Vec<[f32; 3]>, - uvs: &'a Vec<[f32; 2]>, - tangents: Vec<[f32; 4]>, -} - -impl MikktspaceGeometryHelper<'_> { - fn index(&self, face: usize, vert: usize) -> usize { - let index_index = face * 3 + vert; - - match self.indices { - Some(Indices::U16(indices)) => indices[index_index] as usize, - Some(Indices::U32(indices)) => indices[index_index] as usize, - None => index_index, - } - } -} - -impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> { - fn num_faces(&self) -> usize { - self.indices - .map(Indices::len) - .unwrap_or_else(|| self.positions.len()) - / 3 - } - - fn num_vertices_of_face(&self, _: usize) -> usize { - 3 - } - - fn position(&self, face: usize, vert: usize) -> [f32; 3] { - self.positions[self.index(face, vert)] - } - - fn normal(&self, face: usize, vert: usize) -> [f32; 3] { - self.normals[self.index(face, vert)] - } - - fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { - self.uvs[self.index(face, vert)] - } - - fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { - let idx = self.index(face, vert); - self.tangents[idx] = tangent; - } -} - -#[derive(Error, Debug)] -/// Failed to generate tangents for the mesh. -pub enum GenerateTangentsError { - #[error("cannot generate tangents for {0:?}")] - UnsupportedTopology(PrimitiveTopology), - #[error("missing indices")] - MissingIndices, - #[error("missing vertex attributes '{0}'")] - MissingVertexAttribute(&'static str), - #[error("the '{0}' vertex attribute should have {1:?} format")] - InvalidVertexAttributeFormat(&'static str, VertexFormat), - #[error("mesh not suitable for tangent generation")] - MikktspaceError, -} - -fn generate_tangents_for_mesh(mesh: &Mesh) -> Result, GenerateTangentsError> { - match mesh.primitive_topology() { - PrimitiveTopology::TriangleList => {} - other => return Err(GenerateTangentsError::UnsupportedTopology(other)), - }; - - let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or( - GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name), - )?; - let VertexAttributeValues::Float32x3(positions) = positions else { - return Err(GenerateTangentsError::InvalidVertexAttributeFormat( - Mesh::ATTRIBUTE_POSITION.name, - VertexFormat::Float32x3, - )); - }; - let normals = mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or( - GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name), - )?; - let VertexAttributeValues::Float32x3(normals) = normals else { - return Err(GenerateTangentsError::InvalidVertexAttributeFormat( - Mesh::ATTRIBUTE_NORMAL.name, - VertexFormat::Float32x3, - )); - }; - let uvs = mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or( - GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name), - )?; - let VertexAttributeValues::Float32x2(uvs) = uvs else { - return Err(GenerateTangentsError::InvalidVertexAttributeFormat( - Mesh::ATTRIBUTE_UV_0.name, - VertexFormat::Float32x2, - )); - }; - - let len = positions.len(); - let tangents = vec![[0., 0., 0., 0.]; len]; - let mut mikktspace_mesh = MikktspaceGeometryHelper { - indices: mesh.indices(), - positions, - normals, - uvs, - tangents, - }; - let success = bevy_mikktspace::generate_tangents(&mut mikktspace_mesh); - if !success { - return Err(GenerateTangentsError::MikktspaceError); - } - - // mikktspace seems to assume left-handedness so we can flip the sign to correct for this - for tangent in &mut mikktspace_mesh.tangents { - tangent[3] = -tangent[3]; - } - - Ok(mikktspace_mesh.tangents) -} - -/// Correctly scales and renormalizes an already normalized `normal` by the scale determined by its reciprocal `scale_recip` -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 super::Mesh; - use crate::{ - mesh::{Indices, MeshWindingInvertError, VertexAttributeValues}, - render_asset::RenderAssetUsages, - }; + use crate::mesh::{Indices, MeshWindingInvertError, VertexAttributeValues}; + use bevy_asset::RenderAssetUsages; use bevy_math::Vec3; use bevy_transform::components::Transform; use wgpu::PrimitiveTopology; diff --git a/crates/bevy_mesh/src/mikktspace.rs b/crates/bevy_mesh/src/mikktspace.rs new file mode 100644 index 0000000000000..ab44758fc3a4a --- /dev/null +++ b/crates/bevy_mesh/src/mikktspace.rs @@ -0,0 +1,142 @@ +use super::{Indices, Mesh, VertexAttributeValues}; +use bevy_math::Vec3; +use thiserror::Error; +use wgpu::{PrimitiveTopology, VertexFormat}; + +struct MikktspaceGeometryHelper<'a> { + indices: Option<&'a Indices>, + positions: &'a Vec<[f32; 3]>, + normals: &'a Vec<[f32; 3]>, + uvs: &'a Vec<[f32; 2]>, + tangents: Vec<[f32; 4]>, +} + +impl MikktspaceGeometryHelper<'_> { + fn index(&self, face: usize, vert: usize) -> usize { + let index_index = face * 3 + vert; + + match self.indices { + Some(Indices::U16(indices)) => indices[index_index] as usize, + Some(Indices::U32(indices)) => indices[index_index] as usize, + None => index_index, + } + } +} + +impl bevy_mikktspace::Geometry for MikktspaceGeometryHelper<'_> { + fn num_faces(&self) -> usize { + self.indices + .map(Indices::len) + .unwrap_or_else(|| self.positions.len()) + / 3 + } + + fn num_vertices_of_face(&self, _: usize) -> usize { + 3 + } + + fn position(&self, face: usize, vert: usize) -> [f32; 3] { + self.positions[self.index(face, vert)] + } + + fn normal(&self, face: usize, vert: usize) -> [f32; 3] { + self.normals[self.index(face, vert)] + } + + fn tex_coord(&self, face: usize, vert: usize) -> [f32; 2] { + self.uvs[self.index(face, vert)] + } + + fn set_tangent_encoded(&mut self, tangent: [f32; 4], face: usize, vert: usize) { + let idx = self.index(face, vert); + self.tangents[idx] = tangent; + } +} + +#[derive(Error, Debug)] +/// Failed to generate tangents for the mesh. +pub enum GenerateTangentsError { + #[error("cannot generate tangents for {0:?}")] + UnsupportedTopology(PrimitiveTopology), + #[error("missing indices")] + MissingIndices, + #[error("missing vertex attributes '{0}'")] + MissingVertexAttribute(&'static str), + #[error("the '{0}' vertex attribute should have {1:?} format")] + InvalidVertexAttributeFormat(&'static str, VertexFormat), + #[error("mesh not suitable for tangent generation")] + MikktspaceError, +} + +pub(crate) fn generate_tangents_for_mesh( + mesh: &Mesh, +) -> Result, GenerateTangentsError> { + match mesh.primitive_topology() { + PrimitiveTopology::TriangleList => {} + other => return Err(GenerateTangentsError::UnsupportedTopology(other)), + }; + + let positions = mesh.attribute(Mesh::ATTRIBUTE_POSITION).ok_or( + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_POSITION.name), + )?; + let VertexAttributeValues::Float32x3(positions) = positions else { + return Err(GenerateTangentsError::InvalidVertexAttributeFormat( + Mesh::ATTRIBUTE_POSITION.name, + VertexFormat::Float32x3, + )); + }; + let normals = mesh.attribute(Mesh::ATTRIBUTE_NORMAL).ok_or( + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_NORMAL.name), + )?; + let VertexAttributeValues::Float32x3(normals) = normals else { + return Err(GenerateTangentsError::InvalidVertexAttributeFormat( + Mesh::ATTRIBUTE_NORMAL.name, + VertexFormat::Float32x3, + )); + }; + let uvs = mesh.attribute(Mesh::ATTRIBUTE_UV_0).ok_or( + GenerateTangentsError::MissingVertexAttribute(Mesh::ATTRIBUTE_UV_0.name), + )?; + let VertexAttributeValues::Float32x2(uvs) = uvs else { + return Err(GenerateTangentsError::InvalidVertexAttributeFormat( + Mesh::ATTRIBUTE_UV_0.name, + VertexFormat::Float32x2, + )); + }; + + let len = positions.len(); + let tangents = vec![[0., 0., 0., 0.]; len]; + let mut mikktspace_mesh = MikktspaceGeometryHelper { + indices: mesh.indices(), + positions, + normals, + uvs, + tangents, + }; + let success = bevy_mikktspace::generate_tangents(&mut mikktspace_mesh); + if !success { + return Err(GenerateTangentsError::MikktspaceError); + } + + // mikktspace seems to assume left-handedness so we can flip the sign to correct for this + for tangent in &mut mikktspace_mesh.tangents { + tangent[3] = -tangent[3]; + } + + Ok(mikktspace_mesh.tangents) +} + +/// 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() + } +} diff --git a/crates/bevy_render/src/mesh/morph.rs b/crates/bevy_mesh/src/morph.rs similarity index 83% rename from crates/bevy_render/src/mesh/morph.rs rename to crates/bevy_mesh/src/morph.rs index 3096842e73b11..c1e0be8a5b9bf 100644 --- a/crates/bevy_render/src/mesh/morph.rs +++ b/crates/bevy_mesh/src/morph.rs @@ -1,20 +1,13 @@ -use crate::{ - mesh::Mesh, - render_asset::RenderAssetUsages, - render_resource::{Extent3d, TextureDimension, TextureFormat}, - texture::Image, -}; -use bevy_app::{Plugin, PostUpdate}; -use bevy_asset::Handle; +use super::Mesh; +use bevy_asset::{Handle, RenderAssetUsages}; use bevy_ecs::prelude::*; -use bevy_hierarchy::Children; +use bevy_image::Image; use bevy_math::Vec3; use bevy_reflect::prelude::*; use bytemuck::{Pod, Zeroable}; use core::iter; use thiserror::Error; - -use super::Mesh3d; +use wgpu::{Extent3d, TextureDimension, TextureFormat}; const MAX_TEXTURE_WIDTH: u32 = 2048; // NOTE: "component" refers to the element count of math objects, @@ -24,17 +17,6 @@ const MAX_COMPONENTS: u32 = MAX_TEXTURE_WIDTH * MAX_TEXTURE_WIDTH; /// Max target count available for [morph targets](MorphWeights). pub const MAX_MORPH_WEIGHTS: usize = 64; -/// [Inherit weights](inherit_weights) from glTF mesh parent entity to direct -/// bevy mesh child entities (ie: glTF primitive). -pub struct MorphPlugin; -impl Plugin for MorphPlugin { - fn build(&self, app: &mut bevy_app::App) { - app.register_type::() - .register_type::() - .add_systems(PostUpdate, inherit_weights); - } -} - #[derive(Error, Clone, Debug)] pub enum MorphBuildError { #[error( @@ -116,7 +98,7 @@ impl MorphTargetImage { } } -/// Controls the [morph targets] for all child [`Mesh3d`] entities. In most cases, [`MorphWeights`] should be considered +/// Controls the [morph targets] for all child `Mesh3d` entities. In most cases, [`MorphWeights`] should be considered /// the "source of truth" when writing morph targets for meshes. However you can choose to write child [`MeshMorphWeights`] /// if your situation requires more granularity. Just note that if you set [`MorphWeights`], it will overwrite child /// [`MeshMorphWeights`] values. @@ -124,9 +106,9 @@ impl MorphTargetImage { /// This exists because Bevy's [`Mesh`] corresponds to a _single_ surface / material, whereas morph targets /// as defined in the GLTF spec exist on "multi-primitive meshes" (where each primitive is its own surface with its own material). /// Therefore in Bevy [`MorphWeights`] an a parent entity are the "canonical weights" from a GLTF perspective, which then -/// synchronized to child [`Mesh3d`] / [`MeshMorphWeights`] (which correspond to "primitives" / "surfaces" from a GLTF perspective). +/// synchronized to child `Mesh3d` / [`MeshMorphWeights`] (which correspond to "primitives" / "surfaces" from a GLTF perspective). /// -/// Add this to the parent of one or more [`Entities`](`Entity`) with a [`Mesh3d`] with a [`MeshMorphWeights`]. +/// Add this to the parent of one or more [`Entities`](`Entity`) with a `Mesh3d` with a [`MeshMorphWeights`]. /// /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation #[derive(Reflect, Default, Debug, Clone, Component)] @@ -150,7 +132,7 @@ impl MorphWeights { first_mesh, }) } - /// The first child [`Mesh3d`] primitive controlled by these weights. + /// The first child `Mesh3d` primitive controlled by these weights. /// This can be used to look up metadata information such as [`Mesh::morph_target_names`]. pub fn first_mesh(&self) -> Option<&Handle> { self.first_mesh.as_ref() @@ -170,7 +152,7 @@ impl MorphWeights { /// /// See [`MorphWeights`] for more details on Bevy's morph target implementation. /// -/// Add this to an [`Entity`] with a [`Mesh3d`] with a [`MorphAttributes`] set +/// Add this to an [`Entity`] with a `Mesh3d` with a [`MorphAttributes`] set /// to control individual weights of each morph target. /// /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation @@ -193,22 +175,11 @@ impl MeshMorphWeights { pub fn weights_mut(&mut self) -> &mut [f32] { &mut self.weights } -} - -/// Bevy meshes are gltf primitives, [`MorphWeights`] on the bevy node entity -/// should be inherited by children meshes. -/// -/// Only direct children are updated, to fulfill the expectations of glTF spec. -pub fn inherit_weights( - morph_nodes: Query<(&Children, &MorphWeights), (Without, Changed)>, - mut morph_primitives: Query<&mut MeshMorphWeights, With>, -) { - for (children, parent_weights) in &morph_nodes { - let mut iter = morph_primitives.iter_many_mut(children); - while let Some(mut child_weight) = iter.fetch_next() { - child_weight.weights.clear(); - child_weight.weights.extend(&parent_weights.weights); - } + pub fn clear_weights(&mut self) { + self.weights.clear(); + } + pub fn extend_weights(&mut self, weights: &[f32]) { + self.weights.extend(weights); } } diff --git a/crates/bevy_render/src/mesh/primitives/dim2.rs b/crates/bevy_mesh/src/primitives/dim2.rs similarity index 99% rename from crates/bevy_render/src/mesh/primitives/dim2.rs rename to crates/bevy_mesh/src/primitives/dim2.rs index fc117a6f5dae5..70b1fd672231f 100644 --- a/crates/bevy_render/src/mesh/primitives/dim2.rs +++ b/crates/bevy_mesh/src/primitives/dim2.rs @@ -1,9 +1,7 @@ use core::f32::consts::FRAC_PI_2; -use crate::{ - mesh::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment}, - render_asset::RenderAssetUsages, -}; +use crate::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment}; +use bevy_asset::RenderAssetUsages; use super::{Extrudable, MeshBuilder, Meshable}; use bevy_math::{ @@ -1009,7 +1007,7 @@ mod tests { use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd}; use bevy_utils::HashSet; - use crate::mesh::{Mesh, MeshBuilder, Meshable, VertexAttributeValues}; + use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues}; fn count_distinct_positions(points: &[[f32; 3]]) -> usize { let mut map = HashSet::new(); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/capsule.rs b/crates/bevy_mesh/src/primitives/dim3/capsule.rs similarity index 99% rename from crates/bevy_render/src/mesh/primitives/dim3/capsule.rs rename to crates/bevy_mesh/src/primitives/dim3/capsule.rs index 33fe54d3d82b3..0fe4e4bef25e8 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/capsule.rs +++ b/crates/bevy_mesh/src/primitives/dim3/capsule.rs @@ -1,7 +1,5 @@ -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3}; use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/cone.rs b/crates/bevy_mesh/src/primitives/dim3/cone.rs similarity index 97% rename from crates/bevy_render/src/mesh/primitives/dim3/cone.rs rename to crates/bevy_mesh/src/primitives/dim3/cone.rs index 51e18e20c40d4..379bbf4b3391b 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/cone.rs +++ b/crates/bevy_mesh/src/primitives/dim3/cone.rs @@ -1,11 +1,8 @@ +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{ops, primitives::Cone, Vec3}; use wgpu::PrimitiveTopology; -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; - /// Anchoring options for [`ConeMeshBuilder`] #[derive(Debug, Copy, Clone, Default)] pub enum ConeAnchor { @@ -191,10 +188,9 @@ impl From for Mesh { #[cfg(test)] mod tests { + use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues}; use bevy_math::{primitives::Cone, Vec2}; - use crate::mesh::{primitives::MeshBuilder, Mesh, Meshable, VertexAttributeValues}; - /// Rounds floats to handle floating point error in tests. fn round_floats(points: &mut [[f32; N]]) { for point in points.iter_mut() { diff --git a/crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs b/crates/bevy_mesh/src/primitives/dim3/conical_frustum.rs similarity index 98% rename from crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs rename to crates/bevy_mesh/src/primitives/dim3/conical_frustum.rs index cf3d160ffb4b1..403c8bc107ca4 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/conical_frustum.rs +++ b/crates/bevy_mesh/src/primitives/dim3/conical_frustum.rs @@ -1,7 +1,5 @@ -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{ops, primitives::ConicalFrustum, Vec3}; use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/cuboid.rs b/crates/bevy_mesh/src/primitives/dim3/cuboid.rs similarity index 96% rename from crates/bevy_render/src/mesh/primitives/dim3/cuboid.rs rename to crates/bevy_mesh/src/primitives/dim3/cuboid.rs index 42bd61ec9326e..648c9bdb862f3 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/cuboid.rs +++ b/crates/bevy_mesh/src/primitives/dim3/cuboid.rs @@ -1,11 +1,8 @@ +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{primitives::Cuboid, Vec3}; use wgpu::PrimitiveTopology; -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; - /// A builder used for creating a [`Mesh`] with a [`Cuboid`] shape. pub struct CuboidMeshBuilder { half_size: Vec3, diff --git a/crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs b/crates/bevy_mesh/src/primitives/dim3/cylinder.rs similarity index 98% rename from crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs rename to crates/bevy_mesh/src/primitives/dim3/cylinder.rs index 67a81afc964d3..ed37f8a312450 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/cylinder.rs +++ b/crates/bevy_mesh/src/primitives/dim3/cylinder.rs @@ -1,11 +1,8 @@ +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{ops, primitives::Cylinder}; use wgpu::PrimitiveTopology; -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; - /// Anchoring options for [`CylinderMeshBuilder`] #[derive(Debug, Copy, Clone, Default)] pub enum CylinderAnchor { diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_mesh/src/primitives/dim3/mod.rs similarity index 100% rename from crates/bevy_render/src/mesh/primitives/dim3/mod.rs rename to crates/bevy_mesh/src/primitives/dim3/mod.rs diff --git a/crates/bevy_render/src/mesh/primitives/dim3/plane.rs b/crates/bevy_mesh/src/primitives/dim3/plane.rs similarity index 98% rename from crates/bevy_render/src/mesh/primitives/dim3/plane.rs rename to crates/bevy_mesh/src/primitives/dim3/plane.rs index 5a421069b67fc..9fe236ed1cb93 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/plane.rs +++ b/crates/bevy_mesh/src/primitives/dim3/plane.rs @@ -1,11 +1,8 @@ +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{primitives::Plane3d, Dir3, Quat, Vec2, Vec3}; use wgpu::PrimitiveTopology; -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; - /// A builder used for creating a [`Mesh`] with a [`Plane3d`] shape. #[derive(Clone, Copy, Debug, Default)] pub struct PlaneMeshBuilder { diff --git a/crates/bevy_render/src/mesh/primitives/dim3/sphere.rs b/crates/bevy_mesh/src/primitives/dim3/sphere.rs similarity index 98% rename from crates/bevy_render/src/mesh/primitives/dim3/sphere.rs rename to crates/bevy_mesh/src/primitives/dim3/sphere.rs index 5ab6257029534..ab507d693d192 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/sphere.rs +++ b/crates/bevy_mesh/src/primitives/dim3/sphere.rs @@ -1,10 +1,7 @@ -use core::f32::consts::PI; - -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{ops, primitives::Sphere}; +use core::f32::consts::PI; use hexasphere::shapes::IcoSphere; use thiserror::Error; use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/tetrahedron.rs b/crates/bevy_mesh/src/primitives/dim3/tetrahedron.rs similarity index 95% rename from crates/bevy_render/src/mesh/primitives/dim3/tetrahedron.rs rename to crates/bevy_mesh/src/primitives/dim3/tetrahedron.rs index 7dd3fd76d4176..fa4b0052f3d24 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/tetrahedron.rs +++ b/crates/bevy_mesh/src/primitives/dim3/tetrahedron.rs @@ -1,8 +1,6 @@ use super::triangle3d; -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::primitives::{Tetrahedron, Triangle3d}; use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/torus.rs b/crates/bevy_mesh/src/primitives/dim3/torus.rs similarity index 98% rename from crates/bevy_render/src/mesh/primitives/dim3/torus.rs rename to crates/bevy_mesh/src/primitives/dim3/torus.rs index e0d56a67613a5..cfcb098793a5e 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/torus.rs +++ b/crates/bevy_mesh/src/primitives/dim3/torus.rs @@ -1,12 +1,9 @@ +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{ops, primitives::Torus, Vec3}; use core::ops::RangeInclusive; use wgpu::PrimitiveTopology; -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; - /// A builder used for creating a [`Mesh`] with a [`Torus`] shape. #[derive(Clone, Debug)] pub struct TorusMeshBuilder { diff --git a/crates/bevy_render/src/mesh/primitives/dim3/triangle3d.rs b/crates/bevy_mesh/src/primitives/dim3/triangle3d.rs similarity index 97% rename from crates/bevy_render/src/mesh/primitives/dim3/triangle3d.rs rename to crates/bevy_mesh/src/primitives/dim3/triangle3d.rs index 9d4b05c117ff8..476939f80462a 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/triangle3d.rs +++ b/crates/bevy_mesh/src/primitives/dim3/triangle3d.rs @@ -1,11 +1,8 @@ +use crate::{Indices, Mesh, MeshBuilder, Meshable}; +use bevy_asset::RenderAssetUsages; use bevy_math::{primitives::Triangle3d, Vec3}; use wgpu::PrimitiveTopology; -use crate::{ - mesh::{Indices, Mesh, MeshBuilder, Meshable}, - render_asset::RenderAssetUsages, -}; - /// A builder used for creating a [`Mesh`] with a [`Triangle3d`] shape. pub struct Triangle3dMeshBuilder { triangle: Triangle3d, diff --git a/crates/bevy_render/src/mesh/primitives/extrusion.rs b/crates/bevy_mesh/src/primitives/extrusion.rs similarity index 99% rename from crates/bevy_render/src/mesh/primitives/extrusion.rs rename to crates/bevy_mesh/src/primitives/extrusion.rs index 123026524a83e..0093404b8ed77 100644 --- a/crates/bevy_render/src/mesh/primitives/extrusion.rs +++ b/crates/bevy_mesh/src/primitives/extrusion.rs @@ -3,9 +3,8 @@ use bevy_math::{ Vec2, Vec3, }; -use crate::mesh::{Indices, Mesh, VertexAttributeValues}; - use super::{MeshBuilder, Meshable}; +use crate::{Indices, Mesh, VertexAttributeValues}; /// A type representing a segment of the perimeter of an extrudable mesh. pub enum PerimeterSegment { diff --git a/crates/bevy_render/src/mesh/primitives/mod.rs b/crates/bevy_mesh/src/primitives/mod.rs similarity index 97% rename from crates/bevy_render/src/mesh/primitives/mod.rs rename to crates/bevy_mesh/src/primitives/mod.rs index bd6338c691bcf..02aa0f1caf41e 100644 --- a/crates/bevy_render/src/mesh/primitives/mod.rs +++ b/crates/bevy_mesh/src/primitives/mod.rs @@ -8,7 +8,7 @@ //! # use bevy_asset::Assets; //! # use bevy_ecs::prelude::ResMut; //! # use bevy_math::prelude::Circle; -//! # use bevy_render::prelude::*; +//! # use bevy_mesh::*; //! # //! # fn setup(mut meshes: ResMut>) { //! // Create circle mesh with default configuration diff --git a/crates/bevy_render/src/mesh/mesh/skinning.rs b/crates/bevy_mesh/src/skinning.rs similarity index 100% rename from crates/bevy_render/src/mesh/mesh/skinning.rs rename to crates/bevy_mesh/src/skinning.rs diff --git a/crates/bevy_mesh/src/vertex.rs b/crates/bevy_mesh/src/vertex.rs new file mode 100644 index 0000000000000..40420ed745fec --- /dev/null +++ b/crates/bevy_mesh/src/vertex.rs @@ -0,0 +1,445 @@ +use alloc::sync::Arc; +use bevy_derive::EnumVariantMeta; +use bevy_ecs::system::Resource; +use bevy_math::Vec3; +use bevy_utils::HashSet; +use bytemuck::cast_slice; +use core::hash::{Hash, Hasher}; +use thiserror::Error; +use wgpu::{BufferAddress, VertexAttribute, VertexFormat, VertexStepMode}; + +#[derive(Debug, Clone, Copy)] +pub struct MeshVertexAttribute { + /// The friendly name of the vertex attribute + pub name: &'static str, + + /// The _unique_ id of the vertex attribute. This will also determine sort ordering + /// when generating vertex buffers. Built-in / standard attributes will use "close to zero" + /// indices. When in doubt, use a random / very large u64 to avoid conflicts. + pub id: MeshVertexAttributeId, + + /// The format of the vertex attribute. + pub format: VertexFormat, +} + +impl MeshVertexAttribute { + pub const fn new(name: &'static str, id: u64, format: VertexFormat) -> Self { + Self { + name, + id: MeshVertexAttributeId(id), + format, + } + } + + pub const fn at_shader_location(&self, shader_location: u32) -> VertexAttributeDescriptor { + VertexAttributeDescriptor::new(shader_location, self.id, self.name) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct MeshVertexAttributeId(u64); + +impl From for MeshVertexAttributeId { + fn from(attribute: MeshVertexAttribute) -> Self { + attribute.id + } +} + +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct MeshVertexBufferLayout { + pub(crate) attribute_ids: Vec, + pub(crate) layout: VertexBufferLayout, +} + +impl MeshVertexBufferLayout { + pub fn new(attribute_ids: Vec, layout: VertexBufferLayout) -> Self { + Self { + attribute_ids, + layout, + } + } + + #[inline] + pub fn contains(&self, attribute_id: impl Into) -> bool { + self.attribute_ids.contains(&attribute_id.into()) + } + + #[inline] + pub fn attribute_ids(&self) -> &[MeshVertexAttributeId] { + &self.attribute_ids + } + + #[inline] + pub fn layout(&self) -> &VertexBufferLayout { + &self.layout + } + + pub fn get_layout( + &self, + attribute_descriptors: &[VertexAttributeDescriptor], + ) -> Result { + let mut attributes = Vec::with_capacity(attribute_descriptors.len()); + for attribute_descriptor in attribute_descriptors { + if let Some(index) = self + .attribute_ids + .iter() + .position(|id| *id == attribute_descriptor.id) + { + let layout_attribute = &self.layout.attributes[index]; + attributes.push(VertexAttribute { + format: layout_attribute.format, + offset: layout_attribute.offset, + shader_location: attribute_descriptor.shader_location, + }); + } else { + return Err(MissingVertexAttributeError { + id: attribute_descriptor.id, + name: attribute_descriptor.name, + pipeline_type: None, + }); + } + } + + Ok(VertexBufferLayout { + array_stride: self.layout.array_stride, + step_mode: self.layout.step_mode, + attributes, + }) + } +} + +#[derive(Error, Debug)] +#[error("Mesh is missing requested attribute: {name} ({id:?}, pipeline type: {pipeline_type:?})")] +pub struct MissingVertexAttributeError { + pub pipeline_type: Option<&'static str>, + id: MeshVertexAttributeId, + name: &'static str, +} + +pub struct VertexAttributeDescriptor { + pub shader_location: u32, + pub id: MeshVertexAttributeId, + name: &'static str, +} + +impl VertexAttributeDescriptor { + pub const fn new(shader_location: u32, id: MeshVertexAttributeId, name: &'static str) -> Self { + Self { + shader_location, + id, + name, + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct MeshAttributeData { + pub(crate) attribute: MeshVertexAttribute, + pub(crate) values: VertexAttributeValues, +} + +pub(crate) fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] { + let (a, b, c) = (Vec3::from(a), Vec3::from(b), Vec3::from(c)); + (b - a).cross(c - a).normalize().into() +} + +pub trait VertexFormatSize { + fn get_size(self) -> u64; +} + +impl VertexFormatSize for VertexFormat { + #[allow(clippy::match_same_arms)] + fn get_size(self) -> u64 { + match self { + VertexFormat::Uint8x2 => 2, + VertexFormat::Uint8x4 => 4, + VertexFormat::Sint8x2 => 2, + VertexFormat::Sint8x4 => 4, + VertexFormat::Unorm8x2 => 2, + VertexFormat::Unorm8x4 => 4, + VertexFormat::Snorm8x2 => 2, + VertexFormat::Snorm8x4 => 4, + VertexFormat::Unorm10_10_10_2 => 4, + VertexFormat::Uint16x2 => 2 * 2, + VertexFormat::Uint16x4 => 2 * 4, + VertexFormat::Sint16x2 => 2 * 2, + VertexFormat::Sint16x4 => 2 * 4, + VertexFormat::Unorm16x2 => 2 * 2, + VertexFormat::Unorm16x4 => 2 * 4, + VertexFormat::Snorm16x2 => 2 * 2, + VertexFormat::Snorm16x4 => 2 * 4, + VertexFormat::Float16x2 => 2 * 2, + VertexFormat::Float16x4 => 2 * 4, + VertexFormat::Float32 => 4, + VertexFormat::Float32x2 => 4 * 2, + VertexFormat::Float32x3 => 4 * 3, + VertexFormat::Float32x4 => 4 * 4, + VertexFormat::Uint32 => 4, + VertexFormat::Uint32x2 => 4 * 2, + VertexFormat::Uint32x3 => 4 * 3, + VertexFormat::Uint32x4 => 4 * 4, + VertexFormat::Sint32 => 4, + VertexFormat::Sint32x2 => 4 * 2, + VertexFormat::Sint32x3 => 4 * 3, + VertexFormat::Sint32x4 => 4 * 4, + VertexFormat::Float64 => 8, + VertexFormat::Float64x2 => 8 * 2, + VertexFormat::Float64x3 => 8 * 3, + VertexFormat::Float64x4 => 8 * 4, + } + } +} + +/// Contains an array where each entry describes a property of a single vertex. +/// Matches the [`VertexFormats`](VertexFormat). +#[derive(Clone, Debug, EnumVariantMeta)] +pub enum VertexAttributeValues { + Float32(Vec), + Sint32(Vec), + Uint32(Vec), + Float32x2(Vec<[f32; 2]>), + Sint32x2(Vec<[i32; 2]>), + Uint32x2(Vec<[u32; 2]>), + Float32x3(Vec<[f32; 3]>), + Sint32x3(Vec<[i32; 3]>), + Uint32x3(Vec<[u32; 3]>), + Float32x4(Vec<[f32; 4]>), + Sint32x4(Vec<[i32; 4]>), + Uint32x4(Vec<[u32; 4]>), + Sint16x2(Vec<[i16; 2]>), + Snorm16x2(Vec<[i16; 2]>), + Uint16x2(Vec<[u16; 2]>), + Unorm16x2(Vec<[u16; 2]>), + Sint16x4(Vec<[i16; 4]>), + Snorm16x4(Vec<[i16; 4]>), + Uint16x4(Vec<[u16; 4]>), + Unorm16x4(Vec<[u16; 4]>), + Sint8x2(Vec<[i8; 2]>), + Snorm8x2(Vec<[i8; 2]>), + Uint8x2(Vec<[u8; 2]>), + Unorm8x2(Vec<[u8; 2]>), + Sint8x4(Vec<[i8; 4]>), + Snorm8x4(Vec<[i8; 4]>), + Uint8x4(Vec<[u8; 4]>), + Unorm8x4(Vec<[u8; 4]>), +} + +impl VertexAttributeValues { + /// Returns the number of vertices in this [`VertexAttributeValues`]. For a single + /// mesh, all of the [`VertexAttributeValues`] must have the same length. + #[allow(clippy::match_same_arms)] + pub fn len(&self) -> usize { + match self { + VertexAttributeValues::Float32(values) => values.len(), + VertexAttributeValues::Sint32(values) => values.len(), + VertexAttributeValues::Uint32(values) => values.len(), + VertexAttributeValues::Float32x2(values) => values.len(), + VertexAttributeValues::Sint32x2(values) => values.len(), + VertexAttributeValues::Uint32x2(values) => values.len(), + VertexAttributeValues::Float32x3(values) => values.len(), + VertexAttributeValues::Sint32x3(values) => values.len(), + VertexAttributeValues::Uint32x3(values) => values.len(), + VertexAttributeValues::Float32x4(values) => values.len(), + VertexAttributeValues::Sint32x4(values) => values.len(), + VertexAttributeValues::Uint32x4(values) => values.len(), + VertexAttributeValues::Sint16x2(values) => values.len(), + VertexAttributeValues::Snorm16x2(values) => values.len(), + VertexAttributeValues::Uint16x2(values) => values.len(), + VertexAttributeValues::Unorm16x2(values) => values.len(), + VertexAttributeValues::Sint16x4(values) => values.len(), + VertexAttributeValues::Snorm16x4(values) => values.len(), + VertexAttributeValues::Uint16x4(values) => values.len(), + VertexAttributeValues::Unorm16x4(values) => values.len(), + VertexAttributeValues::Sint8x2(values) => values.len(), + VertexAttributeValues::Snorm8x2(values) => values.len(), + VertexAttributeValues::Uint8x2(values) => values.len(), + VertexAttributeValues::Unorm8x2(values) => values.len(), + VertexAttributeValues::Sint8x4(values) => values.len(), + VertexAttributeValues::Snorm8x4(values) => values.len(), + VertexAttributeValues::Uint8x4(values) => values.len(), + VertexAttributeValues::Unorm8x4(values) => values.len(), + } + } + + /// Returns `true` if there are no vertices in this [`VertexAttributeValues`]. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns the values as float triples if possible. + pub fn as_float3(&self) -> Option<&[[f32; 3]]> { + match self { + VertexAttributeValues::Float32x3(values) => Some(values), + _ => None, + } + } + + // TODO: add vertex format as parameter here and perform type conversions + /// Flattens the [`VertexAttributeValues`] into a sequence of bytes. This is + /// useful for serialization and sending to the GPU. + #[allow(clippy::match_same_arms)] + pub fn get_bytes(&self) -> &[u8] { + match self { + VertexAttributeValues::Float32(values) => cast_slice(values), + VertexAttributeValues::Sint32(values) => cast_slice(values), + VertexAttributeValues::Uint32(values) => cast_slice(values), + VertexAttributeValues::Float32x2(values) => cast_slice(values), + VertexAttributeValues::Sint32x2(values) => cast_slice(values), + VertexAttributeValues::Uint32x2(values) => cast_slice(values), + VertexAttributeValues::Float32x3(values) => cast_slice(values), + VertexAttributeValues::Sint32x3(values) => cast_slice(values), + VertexAttributeValues::Uint32x3(values) => cast_slice(values), + VertexAttributeValues::Float32x4(values) => cast_slice(values), + VertexAttributeValues::Sint32x4(values) => cast_slice(values), + VertexAttributeValues::Uint32x4(values) => cast_slice(values), + VertexAttributeValues::Sint16x2(values) => cast_slice(values), + VertexAttributeValues::Snorm16x2(values) => cast_slice(values), + VertexAttributeValues::Uint16x2(values) => cast_slice(values), + VertexAttributeValues::Unorm16x2(values) => cast_slice(values), + VertexAttributeValues::Sint16x4(values) => cast_slice(values), + VertexAttributeValues::Snorm16x4(values) => cast_slice(values), + VertexAttributeValues::Uint16x4(values) => cast_slice(values), + VertexAttributeValues::Unorm16x4(values) => cast_slice(values), + VertexAttributeValues::Sint8x2(values) => cast_slice(values), + VertexAttributeValues::Snorm8x2(values) => cast_slice(values), + VertexAttributeValues::Uint8x2(values) => cast_slice(values), + VertexAttributeValues::Unorm8x2(values) => cast_slice(values), + VertexAttributeValues::Sint8x4(values) => cast_slice(values), + VertexAttributeValues::Snorm8x4(values) => cast_slice(values), + VertexAttributeValues::Uint8x4(values) => cast_slice(values), + VertexAttributeValues::Unorm8x4(values) => cast_slice(values), + } + } +} + +impl From<&VertexAttributeValues> for VertexFormat { + fn from(values: &VertexAttributeValues) -> Self { + match values { + VertexAttributeValues::Float32(_) => VertexFormat::Float32, + VertexAttributeValues::Sint32(_) => VertexFormat::Sint32, + VertexAttributeValues::Uint32(_) => VertexFormat::Uint32, + VertexAttributeValues::Float32x2(_) => VertexFormat::Float32x2, + VertexAttributeValues::Sint32x2(_) => VertexFormat::Sint32x2, + VertexAttributeValues::Uint32x2(_) => VertexFormat::Uint32x2, + VertexAttributeValues::Float32x3(_) => VertexFormat::Float32x3, + VertexAttributeValues::Sint32x3(_) => VertexFormat::Sint32x3, + VertexAttributeValues::Uint32x3(_) => VertexFormat::Uint32x3, + VertexAttributeValues::Float32x4(_) => VertexFormat::Float32x4, + VertexAttributeValues::Sint32x4(_) => VertexFormat::Sint32x4, + VertexAttributeValues::Uint32x4(_) => VertexFormat::Uint32x4, + VertexAttributeValues::Sint16x2(_) => VertexFormat::Sint16x2, + VertexAttributeValues::Snorm16x2(_) => VertexFormat::Snorm16x2, + VertexAttributeValues::Uint16x2(_) => VertexFormat::Uint16x2, + VertexAttributeValues::Unorm16x2(_) => VertexFormat::Unorm16x2, + VertexAttributeValues::Sint16x4(_) => VertexFormat::Sint16x4, + VertexAttributeValues::Snorm16x4(_) => VertexFormat::Snorm16x4, + VertexAttributeValues::Uint16x4(_) => VertexFormat::Uint16x4, + VertexAttributeValues::Unorm16x4(_) => VertexFormat::Unorm16x4, + VertexAttributeValues::Sint8x2(_) => VertexFormat::Sint8x2, + VertexAttributeValues::Snorm8x2(_) => VertexFormat::Snorm8x2, + VertexAttributeValues::Uint8x2(_) => VertexFormat::Uint8x2, + VertexAttributeValues::Unorm8x2(_) => VertexFormat::Unorm8x2, + VertexAttributeValues::Sint8x4(_) => VertexFormat::Sint8x4, + VertexAttributeValues::Snorm8x4(_) => VertexFormat::Snorm8x4, + VertexAttributeValues::Uint8x4(_) => VertexFormat::Uint8x4, + VertexAttributeValues::Unorm8x4(_) => VertexFormat::Unorm8x4, + } + } +} + +/// Describes how the vertex buffer is interpreted. +#[derive(Default, Clone, Debug, Hash, Eq, PartialEq)] +pub struct VertexBufferLayout { + /// The stride, in bytes, between elements of this buffer. + pub array_stride: BufferAddress, + /// How often this vertex buffer is "stepped" forward. + pub step_mode: VertexStepMode, + /// The list of attributes which comprise a single vertex. + pub attributes: Vec, +} + +impl VertexBufferLayout { + /// Creates a new densely packed [`VertexBufferLayout`] from an iterator of vertex formats. + /// Iteration order determines the `shader_location` and `offset` of the [`VertexAttributes`](VertexAttribute). + /// The first iterated item will have a `shader_location` and `offset` of zero. + /// The `array_stride` is the sum of the size of the iterated [`VertexFormats`](VertexFormat) (in bytes). + pub fn from_vertex_formats>( + step_mode: VertexStepMode, + vertex_formats: T, + ) -> Self { + let mut offset = 0; + let mut attributes = Vec::new(); + for (shader_location, format) in vertex_formats.into_iter().enumerate() { + attributes.push(VertexAttribute { + format, + offset, + shader_location: shader_location as u32, + }); + offset += format.size(); + } + + VertexBufferLayout { + array_stride: offset, + step_mode, + attributes, + } + } + + /// Returns a [`VertexBufferLayout`] with the shader location of every attribute offset by + /// `location`. + pub fn offset_locations_by(mut self, location: u32) -> Self { + self.attributes.iter_mut().for_each(|attr| { + attr.shader_location += location; + }); + self + } +} + +/// Describes the layout of the mesh vertices in GPU memory. +/// +/// At most one copy of a mesh vertex buffer layout ever exists in GPU memory at +/// once. Therefore, comparing these for equality requires only a single pointer +/// comparison, and this type's [`PartialEq`] and [`Hash`] implementations take +/// advantage of this. To that end, this type doesn't implement +/// [`bevy_derive::Deref`] or [`bevy_derive::DerefMut`] in order to reduce the +/// possibility of accidental deep comparisons, which would be needlessly +/// expensive. +#[derive(Clone, Debug)] +pub struct MeshVertexBufferLayoutRef(pub Arc); + +/// Stores the single copy of each mesh vertex buffer layout. +#[derive(Clone, Default, Resource)] +pub struct MeshVertexBufferLayouts(HashSet>); + +impl MeshVertexBufferLayouts { + /// Inserts a new mesh vertex buffer layout in the store and returns a + /// reference to it, reusing the existing reference if this mesh vertex + /// buffer layout was already in the store. + pub fn insert(&mut self, layout: MeshVertexBufferLayout) -> MeshVertexBufferLayoutRef { + // Because the special `PartialEq` and `Hash` implementations that + // compare by pointer are on `MeshVertexBufferLayoutRef`, not on + // `Arc`, this compares the mesh vertex buffer + // structurally, not by pointer. + MeshVertexBufferLayoutRef( + self.0 + .get_or_insert_with(&layout, |layout| Arc::new(layout.clone())) + .clone(), + ) + } +} + +impl PartialEq for MeshVertexBufferLayoutRef { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.0, &other.0) + } +} + +impl Eq for MeshVertexBufferLayoutRef {} + +impl Hash for MeshVertexBufferLayoutRef { + fn hash(&self, state: &mut H) { + // Hash the address of the underlying data, so two layouts that share the same + // `MeshVertexBufferLayout` will have the same hash. + (Arc::as_ptr(&self.0) as usize).hash(state); + } +} diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index c73c3da310a1e..a81ba639f4609 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -19,10 +19,10 @@ webp = ["image/webp", "bevy_image/webp"] dds = ["ddsfile", "bevy_image/dds"] pnm = ["image/pnm", "bevy_image/pnm"] -ddsfile = ["dep:ddsfile", "bevy_image/ddsfile"] +ddsfile = ["bevy_image/ddsfile"] ktx2 = ["dep:ktx2", "bevy_image/ktx2"] -flate2 = ["dep:flate2", "bevy_image/flate2"] -ruzstd = ["dep:ruzstd", "bevy_image/ruzstd"] +flate2 = ["bevy_image/flate2"] +ruzstd = ["bevy_image/ruzstd"] basis-universal = ["dep:basis-universal", "bevy_image/basis-universal"] multi_threaded = ["bevy_tasks/multi_threaded"] @@ -59,7 +59,6 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.15.0-dev" } bevy_encase_derive = { path = "../bevy_encase_derive", version = "0.15.0-dev" } bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.15.0-dev" } bevy_math = { path = "../bevy_math", version = "0.15.0-dev" } -bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.15.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [ "bevy", ] } @@ -71,6 +70,7 @@ bevy_winit = { path = "../bevy_winit", version = "0.15.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.15.0-dev" } bevy_image = { path = "../bevy_image", version = "0.15.0-dev" } +bevy_mesh = { path = "../bevy_mesh", version = "0.15.0-dev" } # rendering image = { version = "0.25.2", default-features = false } @@ -90,17 +90,11 @@ wgpu = { version = "22", default-features = false, features = [ ] } naga = { version = "22", features = ["wgsl-in"] } serde = { version = "1", features = ["derive"] } -bitflags = { version = "2.3", features = ["serde"] } bytemuck = { version = "1.5", features = ["derive", "must_cast"] } downcast-rs = "1.2.0" thiserror = "1.0" futures-lite = "2.0.1" -hexasphere = "15.0" -ddsfile = { version = "0.5.2", optional = true } ktx2 = { version = "0.3.0", optional = true } -# For ktx2 supercompression -flate2 = { version = "1.0.22", optional = true } -ruzstd = { version = "0.7.0", optional = true } # For transcoding of UASTC/ETC1S universal formats, and for .basis file support basis-universal = { version = "0.3.0", optional = true } encase = { version = "0.10", features = ["glam"] } diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index f0ff52f32f2a3..7ab9642ce5dbe 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -84,7 +84,7 @@ use world_sync::{ use crate::gpu_readback::GpuReadbackPlugin; use crate::{ camera::CameraPlugin, - mesh::{morph::MorphPlugin, MeshPlugin, RenderMesh}, + mesh::{MeshPlugin, MorphPlugin, RenderMesh}, render_asset::prepare_assets, render_resource::{PipelineCache, Shader, ShaderLoader}, renderer::{render_system, RenderInstance, WgpuWrapper}, diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 3b6572ea100f9..40f9617c50d15 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,23 +1,33 @@ -#[allow(clippy::module_inception)] -mod mesh; - +use bevy_hierarchy::Children; +use bevy_math::Vec3; +pub use bevy_mesh::*; +use morph::{MeshMorphWeights, MorphWeights}; pub mod allocator; mod components; -pub mod morph; -pub mod primitives; - -use alloc::sync::Arc; +use crate::{ + primitives::Aabb, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + render_resource::TextureView, + texture::GpuImage, + RenderApp, +}; use allocator::MeshAllocatorPlugin; -use bevy_utils::HashSet; +use bevy_app::{App, Plugin, PostUpdate}; +use bevy_asset::{AssetApp, RenderAssetUsages}; +use bevy_ecs::{ + entity::Entity, + query::{Changed, With}, + system::Query, +}; +use bevy_ecs::{ + query::Without, + system::{ + lifetimeless::{SRes, SResMut}, + SystemParamItem, + }, +}; pub use components::{Mesh2d, Mesh3d}; -use core::hash::{Hash, Hasher}; -pub use mesh::*; -pub use primitives::*; - -use crate::{render_asset::RenderAssetPlugin, texture::GpuImage, RenderApp}; -use bevy_app::{App, Plugin}; -use bevy_asset::AssetApp; -use bevy_ecs::{entity::Entity, system::Resource}; +use wgpu::IndexFormat; /// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU. pub struct MeshPlugin; @@ -42,51 +52,158 @@ impl Plugin for MeshPlugin { } } -/// Describes the layout of the mesh vertices in GPU memory. +/// [Inherit weights](inherit_weights) from glTF mesh parent entity to direct +/// bevy mesh child entities (ie: glTF primitive). +pub struct MorphPlugin; +impl Plugin for MorphPlugin { + fn build(&self, app: &mut App) { + app.register_type::() + .register_type::() + .add_systems(PostUpdate, inherit_weights); + } +} + +/// Bevy meshes are gltf primitives, [`MorphWeights`] on the bevy node entity +/// should be inherited by children meshes. /// -/// At most one copy of a mesh vertex buffer layout ever exists in GPU memory at -/// once. Therefore, comparing these for equality requires only a single pointer -/// comparison, and this type's [`PartialEq`] and [`Hash`] implementations take -/// advantage of this. To that end, this type doesn't implement -/// [`bevy_derive::Deref`] or [`bevy_derive::DerefMut`] in order to reduce the -/// possibility of accidental deep comparisons, which would be needlessly -/// expensive. -#[derive(Clone, Debug)] -pub struct MeshVertexBufferLayoutRef(pub Arc); - -/// Stores the single copy of each mesh vertex buffer layout. -#[derive(Clone, Default, Resource)] -pub struct MeshVertexBufferLayouts(HashSet>); - -impl MeshVertexBufferLayouts { - /// Inserts a new mesh vertex buffer layout in the store and returns a - /// reference to it, reusing the existing reference if this mesh vertex - /// buffer layout was already in the store. - pub fn insert(&mut self, layout: MeshVertexBufferLayout) -> MeshVertexBufferLayoutRef { - // Because the special `PartialEq` and `Hash` implementations that - // compare by pointer are on `MeshVertexBufferLayoutRef`, not on - // `Arc`, this compares the mesh vertex buffer - // structurally, not by pointer. - MeshVertexBufferLayoutRef( - self.0 - .get_or_insert_with(&layout, |layout| Arc::new(layout.clone())) - .clone(), - ) +/// Only direct children are updated, to fulfill the expectations of glTF spec. +pub fn inherit_weights( + morph_nodes: Query<(&Children, &MorphWeights), (Without, Changed)>, + mut morph_primitives: Query<&mut MeshMorphWeights, With>, +) { + for (children, parent_weights) in &morph_nodes { + let mut iter = morph_primitives.iter_many_mut(children); + while let Some(mut child_weight) = iter.fetch_next() { + child_weight.clear_weights(); + child_weight.extend_weights(parent_weights.weights()); + } } } -impl PartialEq for MeshVertexBufferLayoutRef { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.0, &other.0) +pub trait MeshAabb { + /// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space + /// + /// Returns `None` if `self` doesn't have [`Mesh::ATTRIBUTE_POSITION`] of + /// type [`VertexAttributeValues::Float32x3`], or if `self` doesn't have any vertices. + fn compute_aabb(&self) -> Option; +} + +impl MeshAabb for Mesh { + fn compute_aabb(&self) -> Option { + let Some(VertexAttributeValues::Float32x3(values)) = + self.attribute(Mesh::ATTRIBUTE_POSITION) + else { + return None; + }; + + Aabb::enclosing(values.iter().map(|p| Vec3::from_slice(p))) } } -impl Eq for MeshVertexBufferLayoutRef {} +/// The render world representation of a [`Mesh`]. +#[derive(Debug, Clone)] +pub struct RenderMesh { + /// The number of vertices in the mesh. + pub vertex_count: u32, + + /// Morph targets for the mesh, if present. + pub morph_targets: Option, + + /// Information about the mesh data buffers, including whether the mesh uses + /// indices or not. + pub buffer_info: RenderMeshBufferInfo, + + /// Precomputed pipeline key bits for this mesh. + pub key_bits: BaseMeshPipelineKey, + + /// A reference to the vertex buffer layout. + /// + /// Combined with [`RenderMesh::buffer_info`], this specifies the complete + /// layout of the buffers associated with this mesh. + pub layout: MeshVertexBufferLayoutRef, +} + +impl RenderMesh { + /// Returns the primitive topology of this mesh (triangles, triangle strips, + /// etc.) + #[inline] + pub fn primitive_topology(&self) -> PrimitiveTopology { + self.key_bits.primitive_topology() + } +} + +/// The index/vertex buffer info of a [`RenderMesh`]. +#[derive(Debug, Clone)] +pub enum RenderMeshBufferInfo { + Indexed { + count: u32, + index_format: IndexFormat, + }, + NonIndexed, +} + +impl RenderAsset for RenderMesh { + type SourceAsset = Mesh; + type Param = ( + SRes>, + SResMut, + ); + + #[inline] + fn asset_usage(mesh: &Self::SourceAsset) -> RenderAssetUsages { + mesh.asset_usage + } + + fn byte_len(mesh: &Self::SourceAsset) -> Option { + let mut vertex_size = 0; + for attribute_data in mesh.attributes() { + let vertex_format = attribute_data.0.format; + vertex_size += vertex_format.get_size() as usize; + } + + let vertex_count = mesh.count_vertices(); + let index_bytes = mesh.get_index_buffer_bytes().map(<[_]>::len).unwrap_or(0); + Some(vertex_size * vertex_count + index_bytes) + } + + /// Converts the extracted mesh into a [`RenderMesh`]. + fn prepare_asset( + mesh: Self::SourceAsset, + (images, ref mut mesh_vertex_buffer_layouts): &mut SystemParamItem, + ) -> Result> { + let morph_targets = match mesh.morph_targets() { + Some(mt) => { + let Some(target_image) = images.get(mt) else { + return Err(PrepareAssetError::RetryNextUpdate(mesh)); + }; + Some(target_image.texture_view.clone()) + } + None => None, + }; + + let buffer_info = match mesh.indices() { + Some(indices) => RenderMeshBufferInfo::Indexed { + count: indices.len() as u32, + index_format: indices.into(), + }, + None => RenderMeshBufferInfo::NonIndexed, + }; + + let mesh_vertex_buffer_layout = + mesh.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts); + + let mut key_bits = BaseMeshPipelineKey::from_primitive_topology(mesh.primitive_topology()); + key_bits.set( + BaseMeshPipelineKey::MORPH_TARGETS, + mesh.morph_targets().is_some(), + ); -impl Hash for MeshVertexBufferLayoutRef { - fn hash(&self, state: &mut H) { - // Hash the address of the underlying data, so two layouts that share the same - // `MeshVertexBufferLayout` will have the same hash. - (Arc::as_ptr(&self.0) as usize).hash(state); + Ok(RenderMesh { + vertex_count: mesh.count_vertices() as u32, + buffer_info, + key_bits, + layout: mesh_vertex_buffer_layout, + morph_targets, + }) } } diff --git a/crates/bevy_render/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs index 42aed363395d1..02e60fa1914ce 100644 --- a/crates/bevy_render/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -58,6 +58,8 @@ pub use wgpu::{ VertexStepMode, COPY_BUFFER_ALIGNMENT, }; +pub use crate::mesh::VertexBufferLayout; + pub mod encase { pub use bevy_encase_derive::ShaderType; pub use encase::*; diff --git a/crates/bevy_render/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs index f05d819bfe637..6b70f93e86c00 100644 --- a/crates/bevy_render/src/render_resource/pipeline.rs +++ b/crates/bevy_render/src/render_resource/pipeline.rs @@ -1,4 +1,5 @@ use super::ShaderDefVal; +use crate::mesh::VertexBufferLayout; use crate::renderer::WgpuWrapper; use crate::{ define_atomic_id, @@ -9,8 +10,7 @@ use alloc::sync::Arc; use bevy_asset::Handle; use core::ops::Deref; use wgpu::{ - BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, - PushConstantRange, VertexAttribute, VertexFormat, VertexStepMode, + ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, PushConstantRange, }; define_atomic_id!(RenderPipelineId); @@ -122,54 +122,6 @@ pub struct VertexState { pub buffers: Vec, } -/// Describes how the vertex buffer is interpreted. -#[derive(Default, Clone, Debug, Hash, Eq, PartialEq)] -pub struct VertexBufferLayout { - /// The stride, in bytes, between elements of this buffer. - pub array_stride: BufferAddress, - /// How often this vertex buffer is "stepped" forward. - pub step_mode: VertexStepMode, - /// The list of attributes which comprise a single vertex. - pub attributes: Vec, -} - -impl VertexBufferLayout { - /// Creates a new densely packed [`VertexBufferLayout`] from an iterator of vertex formats. - /// Iteration order determines the `shader_location` and `offset` of the [`VertexAttributes`](VertexAttribute). - /// The first iterated item will have a `shader_location` and `offset` of zero. - /// The `array_stride` is the sum of the size of the iterated [`VertexFormats`](VertexFormat) (in bytes). - pub fn from_vertex_formats>( - step_mode: VertexStepMode, - vertex_formats: T, - ) -> Self { - let mut offset = 0; - let mut attributes = Vec::new(); - for (shader_location, format) in vertex_formats.into_iter().enumerate() { - attributes.push(VertexAttribute { - format, - offset, - shader_location: shader_location as u32, - }); - offset += format.size(); - } - - VertexBufferLayout { - array_stride: offset, - step_mode, - attributes, - } - } - - /// Returns a [`VertexBufferLayout`] with the shader location of every attribute offset by - /// `location`. - pub fn offset_locations_by(mut self, location: u32) -> Self { - self.attributes.iter_mut().for_each(|attr| { - attr.shader_location += location; - }); - self - } -} - /// Describes the fragment process in a render pipeline. #[derive(Clone, Debug, PartialEq, Eq)] pub struct FragmentState { diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index 0ff2f50cd15ff..55c13b3ab154c 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -1,8 +1,8 @@ use crate::{ - mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError}, + mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout}, render_resource::{ CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache, - RenderPipelineDescriptor, VertexBufferLayout, + RenderPipelineDescriptor, }, }; use bevy_ecs::system::Resource; diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 8f9a8748d69d0..011686a9a4148 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -19,7 +19,7 @@ use bevy_utils::{Parallel, TypeIdMap}; use crate::{ camera::{Camera, CameraProjection}, - mesh::{Mesh, Mesh3d}, + mesh::{Mesh, Mesh3d, MeshAabb}, primitives::{Aabb, Frustum, Sphere}, }; diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index fd6562997f546..d1067cf6a6915 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -53,7 +53,7 @@ use bevy_core_pipeline::core_2d::Transparent2d; use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_render::{ extract_component::{ExtractComponent, ExtractComponentPlugin}, - mesh::{Mesh, Mesh2d}, + mesh::{Mesh, Mesh2d, MeshAabb}, primitives::Aabb, render_phase::AddRenderCommand, render_resource::{Shader, SpecializedRenderPipelines},