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},