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:
parent
7c4a0683c7
commit
4a23dc4216
@ -1244,7 +1244,7 @@ impl Plugin for AnimationPlugin {
|
|||||||
// `PostUpdate`. For now, we just disable ambiguity testing
|
// `PostUpdate`. For now, we just disable ambiguity testing
|
||||||
// for this system.
|
// for this system.
|
||||||
animate_targets
|
animate_targets
|
||||||
.after(bevy_render::mesh::morph::inherit_weights)
|
.after(bevy_render::mesh::inherit_weights)
|
||||||
.ambiguous_with_all(),
|
.ambiguous_with_all(),
|
||||||
trigger_untargeted_animation_events,
|
trigger_untargeted_animation_events,
|
||||||
expire_completed_transitions,
|
expire_completed_transitions,
|
||||||
|
|||||||
37
crates/bevy_mesh/Cargo.toml
Normal file
37
crates/bevy_mesh/Cargo.toml
Normal 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
|
||||||
@ -4,15 +4,15 @@
|
|||||||
//! # Examples
|
//! # Examples
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use bevy_render::mesh::VertexAttributeValues;
|
//! use bevy_mesh::VertexAttributeValues;
|
||||||
//!
|
//!
|
||||||
//! // creating std::vec::Vec
|
//! // creating std::vec::Vec
|
||||||
//! let buffer = vec![[0_u32; 4]; 10];
|
//! 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());
|
//! 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_into: Vec<[u32; 4]> = values.clone().try_into().unwrap();
|
||||||
//! let result_from: Vec<[u32; 4]> = Vec::try_from(values.clone()).unwrap();
|
//! let result_from: Vec<[u32; 4]> = Vec::try_from(values.clone()).unwrap();
|
||||||
//!
|
//!
|
||||||
@ -24,7 +24,7 @@
|
|||||||
//! assert!(error.is_err());
|
//! 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 bevy_math::{IVec2, IVec3, IVec4, UVec2, UVec3, UVec4, Vec2, Vec3, Vec3A, Vec4};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
137
crates/bevy_mesh/src/index.rs
Normal file
137
crates/bevy_mesh/src/index.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
crates/bevy_mesh/src/lib.rs
Normal file
58
crates/bevy_mesh/src/lib.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
142
crates/bevy_mesh/src/mikktspace.rs
Normal file
142
crates/bevy_mesh/src/mikktspace.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +1,13 @@
|
|||||||
use crate::{
|
use super::Mesh;
|
||||||
mesh::Mesh,
|
use bevy_asset::{Handle, RenderAssetUsages};
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
|
||||||
texture::Image,
|
|
||||||
};
|
|
||||||
use bevy_app::{Plugin, PostUpdate};
|
|
||||||
use bevy_asset::Handle;
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_hierarchy::Children;
|
use bevy_image::Image;
|
||||||
use bevy_math::Vec3;
|
use bevy_math::Vec3;
|
||||||
use bevy_reflect::prelude::*;
|
use bevy_reflect::prelude::*;
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use core::iter;
|
use core::iter;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use wgpu::{Extent3d, TextureDimension, TextureFormat};
|
||||||
use super::Mesh3d;
|
|
||||||
|
|
||||||
const MAX_TEXTURE_WIDTH: u32 = 2048;
|
const MAX_TEXTURE_WIDTH: u32 = 2048;
|
||||||
// NOTE: "component" refers to the element count of math objects,
|
// 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).
|
/// Max target count available for [morph targets](MorphWeights).
|
||||||
pub const MAX_MORPH_WEIGHTS: usize = 64;
|
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)]
|
#[derive(Error, Clone, Debug)]
|
||||||
pub enum MorphBuildError {
|
pub enum MorphBuildError {
|
||||||
#[error(
|
#[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`]
|
/// 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
|
/// if your situation requires more granularity. Just note that if you set [`MorphWeights`], it will overwrite child
|
||||||
/// [`MeshMorphWeights`] values.
|
/// [`MeshMorphWeights`] values.
|
||||||
@ -124,9 +106,9 @@ impl MorphTargetImage {
|
|||||||
/// This exists because Bevy's [`Mesh`] corresponds to a _single_ surface / material, whereas morph targets
|
/// 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).
|
/// 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
|
/// 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
|
/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
|
||||||
#[derive(Reflect, Default, Debug, Clone, Component)]
|
#[derive(Reflect, Default, Debug, Clone, Component)]
|
||||||
@ -150,7 +132,7 @@ impl MorphWeights {
|
|||||||
first_mesh,
|
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`].
|
/// This can be used to look up metadata information such as [`Mesh::morph_target_names`].
|
||||||
pub fn first_mesh(&self) -> Option<&Handle<Mesh>> {
|
pub fn first_mesh(&self) -> Option<&Handle<Mesh>> {
|
||||||
self.first_mesh.as_ref()
|
self.first_mesh.as_ref()
|
||||||
@ -170,7 +152,7 @@ impl MorphWeights {
|
|||||||
///
|
///
|
||||||
/// See [`MorphWeights`] for more details on Bevy's morph target implementation.
|
/// 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.
|
/// to control individual weights of each morph target.
|
||||||
///
|
///
|
||||||
/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
|
/// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation
|
||||||
@ -193,22 +175,11 @@ impl MeshMorphWeights {
|
|||||||
pub fn weights_mut(&mut self) -> &mut [f32] {
|
pub fn weights_mut(&mut self) -> &mut [f32] {
|
||||||
&mut self.weights
|
&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.
|
pub fn extend_weights(&mut self, weights: &[f32]) {
|
||||||
///
|
self.weights.extend(weights);
|
||||||
/// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1,9 +1,7 @@
|
|||||||
use core::f32::consts::FRAC_PI_2;
|
use core::f32::consts::FRAC_PI_2;
|
||||||
|
|
||||||
use crate::{
|
use crate::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment};
|
||||||
mesh::{primitives::dim3::triangle3d, Indices, Mesh, PerimeterSegment},
|
use bevy_asset::RenderAssetUsages;
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{Extrudable, MeshBuilder, Meshable};
|
use super::{Extrudable, MeshBuilder, Meshable};
|
||||||
use bevy_math::{
|
use bevy_math::{
|
||||||
@ -1009,7 +1007,7 @@ mod tests {
|
|||||||
use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};
|
use bevy_math::{prelude::Annulus, primitives::RegularPolygon, FloatOrd};
|
||||||
use bevy_utils::HashSet;
|
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 {
|
fn count_distinct_positions(points: &[[f32; 3]]) -> usize {
|
||||||
let mut map = HashSet::new();
|
let mut map = HashSet::new();
|
||||||
@ -1,7 +1,5 @@
|
|||||||
use crate::{
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
use bevy_asset::RenderAssetUsages;
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3};
|
use bevy_math::{ops, primitives::Capsule3d, Vec2, Vec3};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
@ -1,11 +1,8 @@
|
|||||||
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
|
use bevy_asset::RenderAssetUsages;
|
||||||
use bevy_math::{ops, primitives::Cone, Vec3};
|
use bevy_math::{ops, primitives::Cone, Vec3};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Anchoring options for [`ConeMeshBuilder`]
|
/// Anchoring options for [`ConeMeshBuilder`]
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
pub enum ConeAnchor {
|
pub enum ConeAnchor {
|
||||||
@ -191,10 +188,9 @@ impl From<Cone> for Mesh {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues};
|
||||||
use bevy_math::{primitives::Cone, Vec2};
|
use bevy_math::{primitives::Cone, Vec2};
|
||||||
|
|
||||||
use crate::mesh::{primitives::MeshBuilder, Mesh, Meshable, VertexAttributeValues};
|
|
||||||
|
|
||||||
/// Rounds floats to handle floating point error in tests.
|
/// Rounds floats to handle floating point error in tests.
|
||||||
fn round_floats<const N: usize>(points: &mut [[f32; N]]) {
|
fn round_floats<const N: usize>(points: &mut [[f32; N]]) {
|
||||||
for point in points.iter_mut() {
|
for point in points.iter_mut() {
|
||||||
@ -1,7 +1,5 @@
|
|||||||
use crate::{
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
use bevy_asset::RenderAssetUsages;
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
use bevy_math::{ops, primitives::ConicalFrustum, Vec3};
|
use bevy_math::{ops, primitives::ConicalFrustum, Vec3};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
@ -1,11 +1,8 @@
|
|||||||
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
|
use bevy_asset::RenderAssetUsages;
|
||||||
use bevy_math::{primitives::Cuboid, Vec3};
|
use bevy_math::{primitives::Cuboid, Vec3};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A builder used for creating a [`Mesh`] with a [`Cuboid`] shape.
|
/// A builder used for creating a [`Mesh`] with a [`Cuboid`] shape.
|
||||||
pub struct CuboidMeshBuilder {
|
pub struct CuboidMeshBuilder {
|
||||||
half_size: Vec3,
|
half_size: Vec3,
|
||||||
@ -1,11 +1,8 @@
|
|||||||
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
|
use bevy_asset::RenderAssetUsages;
|
||||||
use bevy_math::{ops, primitives::Cylinder};
|
use bevy_math::{ops, primitives::Cylinder};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Anchoring options for [`CylinderMeshBuilder`]
|
/// Anchoring options for [`CylinderMeshBuilder`]
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
pub enum CylinderAnchor {
|
pub enum CylinderAnchor {
|
||||||
@ -1,11 +1,8 @@
|
|||||||
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
|
use bevy_asset::RenderAssetUsages;
|
||||||
use bevy_math::{primitives::Plane3d, Dir3, Quat, Vec2, Vec3};
|
use bevy_math::{primitives::Plane3d, Dir3, Quat, Vec2, Vec3};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A builder used for creating a [`Mesh`] with a [`Plane3d`] shape.
|
/// A builder used for creating a [`Mesh`] with a [`Plane3d`] shape.
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct PlaneMeshBuilder {
|
pub struct PlaneMeshBuilder {
|
||||||
@ -1,10 +1,7 @@
|
|||||||
use core::f32::consts::PI;
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
|
use bevy_asset::RenderAssetUsages;
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
use bevy_math::{ops, primitives::Sphere};
|
use bevy_math::{ops, primitives::Sphere};
|
||||||
|
use core::f32::consts::PI;
|
||||||
use hexasphere::shapes::IcoSphere;
|
use hexasphere::shapes::IcoSphere;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
@ -1,8 +1,6 @@
|
|||||||
use super::triangle3d;
|
use super::triangle3d;
|
||||||
use crate::{
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
use bevy_asset::RenderAssetUsages;
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
use bevy_math::primitives::{Tetrahedron, Triangle3d};
|
use bevy_math::primitives::{Tetrahedron, Triangle3d};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
@ -1,12 +1,9 @@
|
|||||||
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
|
use bevy_asset::RenderAssetUsages;
|
||||||
use bevy_math::{ops, primitives::Torus, Vec3};
|
use bevy_math::{ops, primitives::Torus, Vec3};
|
||||||
use core::ops::RangeInclusive;
|
use core::ops::RangeInclusive;
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A builder used for creating a [`Mesh`] with a [`Torus`] shape.
|
/// A builder used for creating a [`Mesh`] with a [`Torus`] shape.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TorusMeshBuilder {
|
pub struct TorusMeshBuilder {
|
||||||
@ -1,11 +1,8 @@
|
|||||||
|
use crate::{Indices, Mesh, MeshBuilder, Meshable};
|
||||||
|
use bevy_asset::RenderAssetUsages;
|
||||||
use bevy_math::{primitives::Triangle3d, Vec3};
|
use bevy_math::{primitives::Triangle3d, Vec3};
|
||||||
use wgpu::PrimitiveTopology;
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh, MeshBuilder, Meshable},
|
|
||||||
render_asset::RenderAssetUsages,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A builder used for creating a [`Mesh`] with a [`Triangle3d`] shape.
|
/// A builder used for creating a [`Mesh`] with a [`Triangle3d`] shape.
|
||||||
pub struct Triangle3dMeshBuilder {
|
pub struct Triangle3dMeshBuilder {
|
||||||
triangle: Triangle3d,
|
triangle: Triangle3d,
|
||||||
@ -3,9 +3,8 @@ use bevy_math::{
|
|||||||
Vec2, Vec3,
|
Vec2, Vec3,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::mesh::{Indices, Mesh, VertexAttributeValues};
|
|
||||||
|
|
||||||
use super::{MeshBuilder, Meshable};
|
use super::{MeshBuilder, Meshable};
|
||||||
|
use crate::{Indices, Mesh, VertexAttributeValues};
|
||||||
|
|
||||||
/// A type representing a segment of the perimeter of an extrudable mesh.
|
/// A type representing a segment of the perimeter of an extrudable mesh.
|
||||||
pub enum PerimeterSegment {
|
pub enum PerimeterSegment {
|
||||||
@ -8,7 +8,7 @@
|
|||||||
//! # use bevy_asset::Assets;
|
//! # use bevy_asset::Assets;
|
||||||
//! # use bevy_ecs::prelude::ResMut;
|
//! # use bevy_ecs::prelude::ResMut;
|
||||||
//! # use bevy_math::prelude::Circle;
|
//! # use bevy_math::prelude::Circle;
|
||||||
//! # use bevy_render::prelude::*;
|
//! # use bevy_mesh::*;
|
||||||
//! #
|
//! #
|
||||||
//! # fn setup(mut meshes: ResMut<Assets<Mesh>>) {
|
//! # fn setup(mut meshes: ResMut<Assets<Mesh>>) {
|
||||||
//! // Create circle mesh with default configuration
|
//! // Create circle mesh with default configuration
|
||||||
445
crates/bevy_mesh/src/vertex.rs
Normal file
445
crates/bevy_mesh/src/vertex.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,10 +19,10 @@ webp = ["image/webp", "bevy_image/webp"]
|
|||||||
dds = ["ddsfile", "bevy_image/dds"]
|
dds = ["ddsfile", "bevy_image/dds"]
|
||||||
pnm = ["image/pnm", "bevy_image/pnm"]
|
pnm = ["image/pnm", "bevy_image/pnm"]
|
||||||
|
|
||||||
ddsfile = ["dep:ddsfile", "bevy_image/ddsfile"]
|
ddsfile = ["bevy_image/ddsfile"]
|
||||||
ktx2 = ["dep:ktx2", "bevy_image/ktx2"]
|
ktx2 = ["dep:ktx2", "bevy_image/ktx2"]
|
||||||
flate2 = ["dep:flate2", "bevy_image/flate2"]
|
flate2 = ["bevy_image/flate2"]
|
||||||
ruzstd = ["dep:ruzstd", "bevy_image/ruzstd"]
|
ruzstd = ["bevy_image/ruzstd"]
|
||||||
basis-universal = ["dep:basis-universal", "bevy_image/basis-universal"]
|
basis-universal = ["dep:basis-universal", "bevy_image/basis-universal"]
|
||||||
|
|
||||||
multi_threaded = ["bevy_tasks/multi_threaded"]
|
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_encase_derive = { path = "../bevy_encase_derive", version = "0.15.0-dev" }
|
||||||
bevy_hierarchy = { path = "../bevy_hierarchy", 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_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_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
|
||||||
"bevy",
|
"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_utils = { path = "../bevy_utils", version = "0.15.0-dev" }
|
||||||
bevy_tasks = { path = "../bevy_tasks", 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_image = { path = "../bevy_image", version = "0.15.0-dev" }
|
||||||
|
bevy_mesh = { path = "../bevy_mesh", version = "0.15.0-dev" }
|
||||||
|
|
||||||
# rendering
|
# rendering
|
||||||
image = { version = "0.25.2", default-features = false }
|
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"] }
|
naga = { version = "22", features = ["wgsl-in"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
bitflags = { version = "2.3", features = ["serde"] }
|
|
||||||
bytemuck = { version = "1.5", features = ["derive", "must_cast"] }
|
bytemuck = { version = "1.5", features = ["derive", "must_cast"] }
|
||||||
downcast-rs = "1.2.0"
|
downcast-rs = "1.2.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
futures-lite = "2.0.1"
|
futures-lite = "2.0.1"
|
||||||
hexasphere = "15.0"
|
|
||||||
ddsfile = { version = "0.5.2", optional = true }
|
|
||||||
ktx2 = { version = "0.3.0", 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
|
# For transcoding of UASTC/ETC1S universal formats, and for .basis file support
|
||||||
basis-universal = { version = "0.3.0", optional = true }
|
basis-universal = { version = "0.3.0", optional = true }
|
||||||
encase = { version = "0.10", features = ["glam"] }
|
encase = { version = "0.10", features = ["glam"] }
|
||||||
|
|||||||
@ -84,7 +84,7 @@ use world_sync::{
|
|||||||
use crate::gpu_readback::GpuReadbackPlugin;
|
use crate::gpu_readback::GpuReadbackPlugin;
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::CameraPlugin,
|
camera::CameraPlugin,
|
||||||
mesh::{morph::MorphPlugin, MeshPlugin, RenderMesh},
|
mesh::{MeshPlugin, MorphPlugin, RenderMesh},
|
||||||
render_asset::prepare_assets,
|
render_asset::prepare_assets,
|
||||||
render_resource::{PipelineCache, Shader, ShaderLoader},
|
render_resource::{PipelineCache, Shader, ShaderLoader},
|
||||||
renderer::{render_system, RenderInstance, WgpuWrapper},
|
renderer::{render_system, RenderInstance, WgpuWrapper},
|
||||||
|
|||||||
@ -1,23 +1,33 @@
|
|||||||
#[allow(clippy::module_inception)]
|
use bevy_hierarchy::Children;
|
||||||
mod mesh;
|
use bevy_math::Vec3;
|
||||||
|
pub use bevy_mesh::*;
|
||||||
|
use morph::{MeshMorphWeights, MorphWeights};
|
||||||
pub mod allocator;
|
pub mod allocator;
|
||||||
mod components;
|
mod components;
|
||||||
pub mod morph;
|
use crate::{
|
||||||
pub mod primitives;
|
primitives::Aabb,
|
||||||
|
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
|
||||||
use alloc::sync::Arc;
|
render_resource::TextureView,
|
||||||
|
texture::GpuImage,
|
||||||
|
RenderApp,
|
||||||
|
};
|
||||||
use allocator::MeshAllocatorPlugin;
|
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};
|
pub use components::{Mesh2d, Mesh3d};
|
||||||
use core::hash::{Hash, Hasher};
|
use wgpu::IndexFormat;
|
||||||
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};
|
|
||||||
|
|
||||||
/// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU.
|
/// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU.
|
||||||
pub struct MeshPlugin;
|
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
|
/// Only direct children are updated, to fulfill the expectations of glTF spec.
|
||||||
/// once. Therefore, comparing these for equality requires only a single pointer
|
pub fn inherit_weights(
|
||||||
/// comparison, and this type's [`PartialEq`] and [`Hash`] implementations take
|
morph_nodes: Query<(&Children, &MorphWeights), (Without<Mesh3d>, Changed<MorphWeights>)>,
|
||||||
/// advantage of this. To that end, this type doesn't implement
|
mut morph_primitives: Query<&mut MeshMorphWeights, With<Mesh3d>>,
|
||||||
/// [`bevy_derive::Deref`] or [`bevy_derive::DerefMut`] in order to reduce the
|
) {
|
||||||
/// possibility of accidental deep comparisons, which would be needlessly
|
for (children, parent_weights) in &morph_nodes {
|
||||||
/// expensive.
|
let mut iter = morph_primitives.iter_many_mut(children);
|
||||||
#[derive(Clone, Debug)]
|
while let Some(mut child_weight) = iter.fetch_next() {
|
||||||
pub struct MeshVertexBufferLayoutRef(pub Arc<MeshVertexBufferLayout>);
|
child_weight.clear_weights();
|
||||||
|
child_weight.extend_weights(parent_weights.weights());
|
||||||
/// 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 {
|
pub trait MeshAabb {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
/// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space
|
||||||
Arc::ptr_eq(&self.0, &other.0)
|
///
|
||||||
|
/// 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 {
|
/// Morph targets for the mesh, if present.
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
pub morph_targets: Option<TextureView>,
|
||||||
// Hash the address of the underlying data, so two layouts that share the same
|
|
||||||
// `MeshVertexBufferLayout` will have the same hash.
|
/// Information about the mesh data buffers, including whether the mesh uses
|
||||||
(Arc::as_ptr(&self.0) as usize).hash(state);
|
/// 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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,6 +58,8 @@ pub use wgpu::{
|
|||||||
VertexStepMode, COPY_BUFFER_ALIGNMENT,
|
VertexStepMode, COPY_BUFFER_ALIGNMENT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use crate::mesh::VertexBufferLayout;
|
||||||
|
|
||||||
pub mod encase {
|
pub mod encase {
|
||||||
pub use bevy_encase_derive::ShaderType;
|
pub use bevy_encase_derive::ShaderType;
|
||||||
pub use encase::*;
|
pub use encase::*;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use super::ShaderDefVal;
|
use super::ShaderDefVal;
|
||||||
|
use crate::mesh::VertexBufferLayout;
|
||||||
use crate::renderer::WgpuWrapper;
|
use crate::renderer::WgpuWrapper;
|
||||||
use crate::{
|
use crate::{
|
||||||
define_atomic_id,
|
define_atomic_id,
|
||||||
@ -9,8 +10,7 @@ use alloc::sync::Arc;
|
|||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
BufferAddress, ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState,
|
ColorTargetState, DepthStencilState, MultisampleState, PrimitiveState, PushConstantRange,
|
||||||
PushConstantRange, VertexAttribute, VertexFormat, VertexStepMode,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
define_atomic_id!(RenderPipelineId);
|
define_atomic_id!(RenderPipelineId);
|
||||||
@ -122,54 +122,6 @@ pub struct VertexState {
|
|||||||
pub buffers: Vec<VertexBufferLayout>,
|
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.
|
/// Describes the fragment process in a render pipeline.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct FragmentState {
|
pub struct FragmentState {
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError},
|
mesh::{MeshVertexBufferLayoutRef, MissingVertexAttributeError, VertexBufferLayout},
|
||||||
render_resource::{
|
render_resource::{
|
||||||
CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache,
|
CachedComputePipelineId, CachedRenderPipelineId, ComputePipelineDescriptor, PipelineCache,
|
||||||
RenderPipelineDescriptor, VertexBufferLayout,
|
RenderPipelineDescriptor,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use bevy_ecs::system::Resource;
|
use bevy_ecs::system::Resource;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ use bevy_utils::{Parallel, TypeIdMap};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
camera::{Camera, CameraProjection},
|
camera::{Camera, CameraProjection},
|
||||||
mesh::{Mesh, Mesh3d},
|
mesh::{Mesh, Mesh3d, MeshAabb},
|
||||||
primitives::{Aabb, Frustum, Sphere},
|
primitives::{Aabb, Frustum, Sphere},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -53,7 +53,7 @@ use bevy_core_pipeline::core_2d::Transparent2d;
|
|||||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||||
mesh::{Mesh, Mesh2d},
|
mesh::{Mesh, Mesh2d, MeshAabb},
|
||||||
primitives::Aabb,
|
primitives::Aabb,
|
||||||
render_phase::AddRenderCommand,
|
render_phase::AddRenderCommand,
|
||||||
render_resource::{Shader, SpecializedRenderPipelines},
|
render_resource::{Shader, SpecializedRenderPipelines},
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user