Split out bevy_mesh from bevy_render (#15666)

# Objective

- bevy_render is gargantuan

## Solution

- Split out bevy_mesh

## Testing

- Ran some examples, everything looks fine

## Migration Guide

`bevy_render::mesh::morph::inherit_weights` is now
`bevy_render::mesh::inherit_weights`

if you were using `Mesh::compute_aabb`, you will need to `use
bevy_render::mesh::MeshAabb;` now

---------

Co-authored-by: Joona Aalto <jondolf.dev@gmail.com>
This commit is contained in:
vero 2024-10-06 10:18:11 -04:00 committed by GitHub
parent 7c4a0683c7
commit 4a23dc4216
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1087 additions and 1042 deletions

View file

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

View file

@ -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

View file

@ -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;

View file

@ -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<A, B, C, D> {
First(A),
Second(B),
Third(C),
Fourth(D),
}
impl<A, B, C, D, I> Iterator for FourIterators<A, B, C, D>
where
A: Iterator<Item = I>,
B: Iterator<Item = I>,
C: Iterator<Item = I>,
D: Iterator<Item = I>,
{
type Item = I;
fn next(&mut self) -> Option<Self::Item> {
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<u16>),
U32(Vec<u32>),
}
impl Indices {
/// Returns an iterator over the indices.
pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
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<Self::Item> {
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<usize>) {
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,
}
}
}

View file

@ -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(),
}
}
}

View file

@ -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<Vec<[f32; 4]>, 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()
}
}

View file

@ -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::<MorphWeights>()
.register_type::<MeshMorphWeights>()
.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<Mesh>> {
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
}
pub fn clear_weights(&mut self) {
self.weights.clear();
}
/// 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<Mesh3d>, Changed<MorphWeights>)>,
mut morph_primitives: Query<&mut MeshMorphWeights, With<Mesh3d>>,
) {
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 extend_weights(&mut self, weights: &[f32]) {
self.weights.extend(weights);
}
}

View file

@ -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();

View file

@ -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;

View file

@ -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<Cone> 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<const N: usize>(points: &mut [[f32; N]]) {
for point in points.iter_mut() {

View file

@ -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;

View file

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

View file

@ -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 {

View file

@ -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 {

View file

@ -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;

View file

@ -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;

View file

@ -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 {

View file

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

View file

@ -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 {

View file

@ -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<Assets<Mesh>>) {
//! // Create circle mesh with default configuration

View file

@ -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<MeshVertexAttribute> for MeshVertexAttributeId {
fn from(attribute: MeshVertexAttribute) -> Self {
attribute.id
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct MeshVertexBufferLayout {
pub(crate) attribute_ids: Vec<MeshVertexAttributeId>,
pub(crate) layout: VertexBufferLayout,
}
impl MeshVertexBufferLayout {
pub fn new(attribute_ids: Vec<MeshVertexAttributeId>, layout: VertexBufferLayout) -> Self {
Self {
attribute_ids,
layout,
}
}
#[inline]
pub fn contains(&self, attribute_id: impl Into<MeshVertexAttributeId>) -> 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<VertexBufferLayout, MissingVertexAttributeError> {
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<f32>),
Sint32(Vec<i32>),
Uint32(Vec<u32>),
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<VertexAttribute>,
}
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<T: IntoIterator<Item = VertexFormat>>(
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<MeshVertexBufferLayout>);
/// Stores the single copy of each mesh vertex buffer layout.
#[derive(Clone, Default, Resource)]
pub struct MeshVertexBufferLayouts(HashSet<Arc<MeshVertexBufferLayout>>);
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<MeshVertexBufferLayout>`, 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<H: Hasher>(&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);
}
}

View file

@ -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"] }

View file

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

View file

@ -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::<MorphWeights>()
.register_type::<MeshMorphWeights>()
.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<MeshVertexBufferLayout>);
/// Stores the single copy of each mesh vertex buffer layout.
#[derive(Clone, Default, Resource)]
pub struct MeshVertexBufferLayouts(HashSet<Arc<MeshVertexBufferLayout>>);
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<MeshVertexBufferLayout>`, 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<Mesh3d>, Changed<MorphWeights>)>,
mut morph_primitives: Query<&mut MeshMorphWeights, With<Mesh3d>>,
) {
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<Aabb>;
}
impl MeshAabb for Mesh {
fn compute_aabb(&self) -> Option<Aabb> {
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,
impl Hash for MeshVertexBufferLayoutRef {
fn hash<H: Hasher>(&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);
/// Morph targets for the mesh, if present.
pub morph_targets: Option<TextureView>,
/// 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<RenderAssets<GpuImage>>,
SResMut<MeshVertexBufferLayouts>,
);
#[inline]
fn asset_usage(mesh: &Self::SourceAsset) -> RenderAssetUsages {
mesh.asset_usage
}
fn byte_len(mesh: &Self::SourceAsset) -> Option<usize> {
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<Self::Param>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
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(),
);
Ok(RenderMesh {
vertex_count: mesh.count_vertices() as u32,
buffer_info,
key_bits,
layout: mesh_vertex_buffer_layout,
morph_targets,
})
}
}

View file

@ -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::*;

View file

@ -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<VertexBufferLayout>,
}
/// 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<VertexAttribute>,
}
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<T: IntoIterator<Item = VertexFormat>>(
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 {

View file

@ -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;

View file

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

View file

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