use bevy_transform::components::Transform; pub use wgpu::PrimitiveTopology; use super::{ face_normal, generate_tangents_for_mesh, scale_normal, FourIterators, GenerateTangentsError, Indices, MeshAttributeData, MeshTrianglesError, MeshVertexAttribute, MeshVertexAttributeId, MeshVertexBufferLayout, MeshVertexBufferLayoutRef, MeshVertexBufferLayouts, MeshWindingInvertError, VertexAttributeValues, VertexBufferLayout, VertexFormatSize, }; use alloc::collections::BTreeMap; use bevy_asset::{Asset, Handle, RenderAssetUsages}; use bevy_image::Image; use bevy_math::{primitives::Triangle3d, *}; use bevy_reflect::Reflect; use bevy_utils::tracing::warn; use bytemuck::cast_slice; use wgpu::{VertexAttribute, VertexFormat, VertexStepMode}; pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; /// A 3D object made out of vertices representing triangles, lines, or points, /// with "attribute" values for each vertex. /// /// Meshes can be automatically generated by a bevy `AssetLoader` (generally by loading a `Gltf` file), /// or by converting a [primitive](bevy_math::primitives) using [`into`](Into). /// It is also possible to create one manually. They can be edited after creation. /// /// Meshes can be rendered with a `Mesh2d` and `MeshMaterial2d` /// or `Mesh3d` and `MeshMaterial3d` for 2D and 3D respectively. /// /// A [`Mesh`] in Bevy is equivalent to a "primitive" in the glTF format, for a /// glTF Mesh representation, see `GltfMesh`. /// /// ## Manual creation /// /// The following function will construct a flat mesh, to be rendered with a /// `StandardMaterial` or `ColorMaterial`: /// /// ``` /// # use bevy_mesh::{Mesh, Indices, PrimitiveTopology}; /// # use bevy_asset::RenderAssetUsages; /// fn create_simple_parallelogram() -> Mesh { /// // Create a new mesh using a triangle list topology, where each set of 3 vertices composes a triangle. /// Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default()) /// // Add 4 vertices, each with its own position attribute (coordinate in /// // 3D space), for each of the corners of the parallelogram. /// .with_inserted_attribute( /// Mesh::ATTRIBUTE_POSITION, /// vec![[0.0, 0.0, 0.0], [1.0, 2.0, 0.0], [2.0, 2.0, 0.0], [1.0, 0.0, 0.0]] /// ) /// // Assign a UV coordinate to each vertex. /// .with_inserted_attribute( /// Mesh::ATTRIBUTE_UV_0, /// vec![[0.0, 1.0], [0.5, 0.0], [1.0, 0.0], [0.5, 1.0]] /// ) /// // Assign normals (everything points outwards) /// .with_inserted_attribute( /// Mesh::ATTRIBUTE_NORMAL, /// vec![[0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0], [0.0, 0.0, 1.0]] /// ) /// // After defining all the vertices and their attributes, build each triangle using the /// // indices of the vertices that make it up in a counter-clockwise order. /// .with_inserted_indices(Indices::U32(vec![ /// // First triangle /// 0, 3, 1, /// // Second triangle /// 1, 3, 2 /// ])) /// } /// ``` /// /// You can see how it looks like [here](https://github.com/bevyengine/bevy/blob/main/assets/docs/Mesh.png), /// used in a `Mesh3d` with a square bevy logo texture, with added axis, points, /// lines and text for clarity. /// /// ## Other examples /// /// For further visualization, explanation, and examples, see the built-in Bevy examples, /// and the [implementation of the built-in shapes](https://github.com/bevyengine/bevy/tree/main/crates/bevy_mesh/src/primitives). /// In particular, [generate_custom_mesh](https://github.com/bevyengine/bevy/blob/main/examples/3d/generate_custom_mesh.rs) /// teaches you to access and modify the attributes of a [`Mesh`] after creating it. /// /// ## Common points of confusion /// /// - UV maps in Bevy start at the top-left, see [`ATTRIBUTE_UV_0`](Mesh::ATTRIBUTE_UV_0), /// other APIs can have other conventions, `OpenGL` starts at bottom-left. /// - It is possible and sometimes useful for multiple vertices to have the same /// [position attribute](Mesh::ATTRIBUTE_POSITION) value, /// it's a common technique in 3D modeling for complex UV mapping or other calculations. /// - Bevy performs frustum culling based on the `Aabb` of meshes, which is calculated /// and added automatically for new meshes only. If a mesh is modified, the entity's `Aabb` /// needs to be updated manually or deleted so that it is re-calculated. /// /// ## Use with `StandardMaterial` /// /// To render correctly with `StandardMaterial`, a mesh needs to have properly defined: /// - [`UVs`](Mesh::ATTRIBUTE_UV_0): Bevy needs to know how to map a texture onto the mesh /// (also true for `ColorMaterial`). /// - [`Normals`](Mesh::ATTRIBUTE_NORMAL): Bevy needs to know how light interacts with your mesh. /// [0.0, 0.0, 1.0] is very common for simple flat meshes on the XY plane, /// because simple meshes are smooth and they don't require complex light calculations. /// - Vertex winding order: by default, `StandardMaterial.cull_mode` is `Some(Face::Back)`, /// which means that Bevy would *only* render the "front" of each triangle, which /// is the side of the triangle from where the vertices appear in a *counter-clockwise* order. #[derive(Asset, Debug, Clone, Reflect)] pub struct Mesh { #[reflect(ignore)] primitive_topology: PrimitiveTopology, /// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...) /// for this mesh. Attribute ids to attribute values. /// Uses a [`BTreeMap`] because, unlike `HashMap`, it has a defined iteration order, /// which allows easy stable `VertexBuffers` (i.e. same buffer order) #[reflect(ignore)] attributes: BTreeMap, indices: Option, morph_targets: Option>, morph_target_names: Option>, pub asset_usage: RenderAssetUsages, } impl Mesh { /// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`] /// or [`Mesh::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x3`]. pub const ATTRIBUTE_POSITION: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3); /// The direction the vertex normal is facing in. /// Use in conjunction with [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x3`]. pub const ATTRIBUTE_NORMAL: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Normal", 1, VertexFormat::Float32x3); /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::insert_attribute`] /// or [`Mesh::with_inserted_attribute`]. /// /// Generally `[0.,0.]` is mapped to the top left of the texture, and `[1.,1.]` to the bottom-right. /// /// By default values outside will be clamped per pixel not for the vertex, /// "stretching" the borders of the texture. /// This behavior can be useful in some cases, usually when the borders have only /// one color, for example a logo, and you want to "extend" those borders. /// /// For different mapping outside of `0..=1` range, /// see [`ImageAddressMode`](bevy_image::ImageAddressMode). /// /// The format of this attribute is [`VertexFormat::Float32x2`]. pub const ATTRIBUTE_UV_0: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Uv", 2, VertexFormat::Float32x2); /// Alternate texture coordinates for the vertex. Use in conjunction with /// [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`]. /// /// Typically, these are used for lightmaps, textures that provide /// precomputed illumination. /// /// The format of this attribute is [`VertexFormat::Float32x2`]. pub const ATTRIBUTE_UV_1: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Uv_1", 3, VertexFormat::Float32x2); /// The direction of the vertex tangent. Used for normal mapping. /// Usually generated with [`generate_tangents`](Mesh::generate_tangents) or /// [`with_generated_tangents`](Mesh::with_generated_tangents). /// /// The format of this attribute is [`VertexFormat::Float32x4`]. pub const ATTRIBUTE_TANGENT: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Tangent", 4, VertexFormat::Float32x4); /// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`] /// or [`Mesh::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x4`]. pub const ATTRIBUTE_COLOR: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_Color", 5, VertexFormat::Float32x4); /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`] /// or [`Mesh::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Float32x4`]. pub const ATTRIBUTE_JOINT_WEIGHT: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_JointWeight", 6, VertexFormat::Float32x4); /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`] /// or [`Mesh::with_inserted_attribute`]. /// /// The format of this attribute is [`VertexFormat::Uint16x4`]. pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute = MeshVertexAttribute::new("Vertex_JointIndex", 7, VertexFormat::Uint16x4); /// Construct a new mesh. You need to provide a [`PrimitiveTopology`] so that the /// renderer knows how to treat the vertex data. Most of the time this will be /// [`PrimitiveTopology::TriangleList`]. pub fn new(primitive_topology: PrimitiveTopology, asset_usage: RenderAssetUsages) -> Self { Mesh { primitive_topology, attributes: Default::default(), indices: None, morph_targets: None, morph_target_names: None, asset_usage, } } /// Returns the topology of the mesh. pub fn primitive_topology(&self) -> PrimitiveTopology { self.primitive_topology } /// Sets the data for a vertex attribute (position, normal, etc.). The name will /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. /// /// # Panics /// Panics when the format of the values does not match the attribute's format. #[inline] pub fn insert_attribute( &mut self, attribute: MeshVertexAttribute, values: impl Into, ) { let values = values.into(); let values_format = VertexFormat::from(&values); if values_format != attribute.format { panic!( "Failed to insert attribute. Invalid attribute format for {}. Given format is {values_format:?} but expected {:?}", attribute.name, attribute.format ); } self.attributes .insert(attribute.id, MeshAttributeData { attribute, values }); } /// Consumes the mesh and returns a mesh with data set for a vertex attribute (position, normal, etc.). /// The name will often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]. /// /// (Alternatively, you can use [`Mesh::insert_attribute`] to mutate an existing mesh in-place) /// /// `Aabb` of entities with modified mesh are not updated automatically. /// /// # Panics /// Panics when the format of the values does not match the attribute's format. #[must_use] #[inline] pub fn with_inserted_attribute( mut self, attribute: MeshVertexAttribute, values: impl Into, ) -> Self { self.insert_attribute(attribute, values); self } /// Removes the data for a vertex attribute pub fn remove_attribute( &mut self, attribute: impl Into, ) -> Option { self.attributes .remove(&attribute.into()) .map(|data| data.values) } /// Consumes the mesh and returns a mesh without the data for a vertex attribute /// /// (Alternatively, you can use [`Mesh::remove_attribute`] to mutate an existing mesh in-place) #[must_use] pub fn with_removed_attribute(mut self, attribute: impl Into) -> Self { self.remove_attribute(attribute); self } #[inline] pub fn contains_attribute(&self, id: impl Into) -> bool { self.attributes.contains_key(&id.into()) } /// Retrieves the data currently set to the vertex attribute with the specified `name`. #[inline] pub fn attribute( &self, id: impl Into, ) -> Option<&VertexAttributeValues> { self.attributes.get(&id.into()).map(|data| &data.values) } /// Retrieves the data currently set to the vertex attribute with the specified `name` mutably. #[inline] pub fn attribute_mut( &mut self, id: impl Into, ) -> Option<&mut VertexAttributeValues> { self.attributes .get_mut(&id.into()) .map(|data| &mut data.values) } /// Returns an iterator that yields references to the data of each vertex attribute. pub fn attributes( &self, ) -> impl Iterator { self.attributes .values() .map(|data| (&data.attribute, &data.values)) } /// Returns an iterator that yields mutable references to the data of each vertex attribute. pub fn attributes_mut( &mut self, ) -> impl Iterator { self.attributes .values_mut() .map(|data| (&data.attribute, &mut data.values)) } /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants /// that use triangles. #[inline] pub fn insert_indices(&mut self, indices: Indices) { self.indices = Some(indices); } /// Consumes the mesh and returns a mesh with the given vertex indices. They describe how triangles /// are constructed out of the vertex attributes and are therefore only useful for the /// [`PrimitiveTopology`] variants that use triangles. /// /// (Alternatively, you can use [`Mesh::insert_indices`] to mutate an existing mesh in-place) #[must_use] #[inline] pub fn with_inserted_indices(mut self, indices: Indices) -> Self { self.insert_indices(indices); self } /// Retrieves the vertex `indices` of the mesh. #[inline] pub fn indices(&self) -> Option<&Indices> { self.indices.as_ref() } /// Retrieves the vertex `indices` of the mesh mutably. #[inline] pub fn indices_mut(&mut self) -> Option<&mut Indices> { self.indices.as_mut() } /// Removes the vertex `indices` from the mesh and returns them. #[inline] pub fn remove_indices(&mut self) -> Option { core::mem::take(&mut self.indices) } /// Consumes the mesh and returns a mesh without the vertex `indices` of the mesh. /// /// (Alternatively, you can use [`Mesh::remove_indices`] to mutate an existing mesh in-place) #[must_use] pub fn with_removed_indices(mut self) -> Self { self.remove_indices(); self } /// Returns the size of a vertex in bytes. pub fn get_vertex_size(&self) -> u64 { self.attributes .values() .map(|data| data.attribute.format.get_size()) .sum() } /// Returns the size required for the vertex buffer in bytes. pub fn get_vertex_buffer_size(&self) -> usize { let vertex_size = self.get_vertex_size() as usize; let vertex_count = self.count_vertices(); vertex_count * vertex_size } /// Computes and returns the index data of the mesh as bytes. /// This is used to transform the index data into a GPU friendly format. pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> { self.indices.as_ref().map(|indices| match &indices { Indices::U16(indices) => cast_slice(&indices[..]), Indices::U32(indices) => cast_slice(&indices[..]), }) } /// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in `SpecializedMeshPipeline`. pub fn get_mesh_vertex_buffer_layout( &self, mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts, ) -> MeshVertexBufferLayoutRef { let mut attributes = Vec::with_capacity(self.attributes.len()); let mut attribute_ids = Vec::with_capacity(self.attributes.len()); let mut accumulated_offset = 0; for (index, data) in self.attributes.values().enumerate() { attribute_ids.push(data.attribute.id); attributes.push(VertexAttribute { offset: accumulated_offset, format: data.attribute.format, shader_location: index as u32, }); accumulated_offset += data.attribute.format.get_size(); } let layout = MeshVertexBufferLayout { layout: VertexBufferLayout { array_stride: accumulated_offset, step_mode: VertexStepMode::Vertex, attributes, }, attribute_ids, }; mesh_vertex_buffer_layouts.insert(layout) } /// Counts all vertices of the mesh. /// /// If the attributes have different vertex counts, the smallest is returned. pub fn count_vertices(&self) -> usize { let mut vertex_count: Option = None; for (attribute_id, attribute_data) in &self.attributes { let attribute_len = attribute_data.values.len(); if let Some(previous_vertex_count) = vertex_count { if previous_vertex_count != attribute_len { let name = self .attributes .get(attribute_id) .map(|data| data.attribute.name.to_string()) .unwrap_or_else(|| format!("{attribute_id:?}")); warn!("{name} has a different vertex count ({attribute_len}) than other attributes ({previous_vertex_count}) in this mesh, \ all attributes will be truncated to match the smallest."); vertex_count = Some(core::cmp::min(previous_vertex_count, attribute_len)); } } else { vertex_count = Some(attribute_len); } } vertex_count.unwrap_or(0) } /// Computes and returns the vertex data of the mesh as bytes. /// Therefore the attributes are located in the order of their [`MeshVertexAttribute::id`]. /// This is used to transform the vertex data into a GPU friendly format. /// /// If the vertex attributes have different lengths, they are all truncated to /// the length of the smallest. /// /// This is a convenience method which allocates a Vec. /// Prefer pre-allocating and using [`Mesh::write_packed_vertex_buffer_data`] when possible. pub fn create_packed_vertex_buffer_data(&self) -> Vec { let mut attributes_interleaved_buffer = vec![0; self.get_vertex_buffer_size()]; self.write_packed_vertex_buffer_data(&mut attributes_interleaved_buffer); attributes_interleaved_buffer } /// Computes and write the vertex data of the mesh into a mutable byte slice. /// The attributes are located in the order of their [`MeshVertexAttribute::id`]. /// This is used to transform the vertex data into a GPU friendly format. /// /// If the vertex attributes have different lengths, they are all truncated to /// the length of the smallest. pub fn write_packed_vertex_buffer_data(&self, slice: &mut [u8]) { let vertex_size = self.get_vertex_size() as usize; let vertex_count = self.count_vertices(); // bundle into interleaved buffers let mut attribute_offset = 0; for attribute_data in self.attributes.values() { let attribute_size = attribute_data.attribute.format.get_size() as usize; let attributes_bytes = attribute_data.values.get_bytes(); for (vertex_index, attribute_bytes) in attributes_bytes .chunks_exact(attribute_size) .take(vertex_count) .enumerate() { let offset = vertex_index * vertex_size + attribute_offset; slice[offset..offset + attribute_size].copy_from_slice(attribute_bytes); } attribute_offset += attribute_size; } } /// Duplicates the vertex attributes so that no vertices are shared. /// /// This can dramatically increase the vertex count, so make sure this is what you want. /// Does nothing if no [Indices] are set. #[allow(clippy::match_same_arms)] pub fn duplicate_vertices(&mut self) { fn duplicate(values: &[T], indices: impl Iterator) -> Vec { indices.map(|i| values[i]).collect() } let Some(indices) = self.indices.take() else { return; }; for attributes in self.attributes.values_mut() { let indices = indices.iter(); match &mut attributes.values { VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Float32x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint32x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint32x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Float32x3(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint32x3(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint32x3(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint32x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint32x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Float32x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint16x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Snorm16x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint16x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Unorm16x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint16x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Snorm16x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint16x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Unorm16x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint8x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Snorm8x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint8x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Unorm8x2(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Sint8x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Snorm8x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Uint8x4(vec) => *vec = duplicate(vec, indices), VertexAttributeValues::Unorm8x4(vec) => *vec = duplicate(vec, indices), } } } /// Consumes the mesh and returns a mesh with no shared vertices. /// /// This can dramatically increase the vertex count, so make sure this is what you want. /// Does nothing if no [`Indices`] are set. /// /// (Alternatively, you can use [`Mesh::duplicate_vertices`] to mutate an existing mesh in-place) #[must_use] pub fn with_duplicated_vertices(mut self) -> Self { self.duplicate_vertices(); self } /// Inverts the winding of the indices such that all counter-clockwise triangles are now /// clockwise and vice versa. /// For lines, their start and end indices are flipped. /// /// Does nothing if no [`Indices`] are set. /// If this operation succeeded, an [`Ok`] result is returned. pub fn invert_winding(&mut self) -> Result<(), MeshWindingInvertError> { fn invert( indices: &mut [I], topology: PrimitiveTopology, ) -> Result<(), MeshWindingInvertError> { match topology { PrimitiveTopology::TriangleList => { // Early return if the index count doesn't match if indices.len() % 3 != 0 { return Err(MeshWindingInvertError::AbruptIndicesEnd); } for chunk in indices.chunks_mut(3) { // This currently can only be optimized away with unsafe, rework this when `feature(slice_as_chunks)` gets stable. let [_, b, c] = chunk else { return Err(MeshWindingInvertError::AbruptIndicesEnd); }; core::mem::swap(b, c); } Ok(()) } PrimitiveTopology::LineList => { // Early return if the index count doesn't match if indices.len() % 2 != 0 { return Err(MeshWindingInvertError::AbruptIndicesEnd); } indices.reverse(); Ok(()) } PrimitiveTopology::TriangleStrip | PrimitiveTopology::LineStrip => { indices.reverse(); Ok(()) } _ => Err(MeshWindingInvertError::WrongTopology), } } match &mut self.indices { Some(Indices::U16(vec)) => invert(vec, self.primitive_topology), Some(Indices::U32(vec)) => invert(vec, self.primitive_topology), None => Ok(()), } } /// Consumes the mesh and returns a mesh with inverted winding of the indices such /// that all counter-clockwise triangles are now clockwise and vice versa. /// /// Does nothing if no [`Indices`] are set. pub fn with_inverted_winding(mut self) -> Result { self.invert_winding().map(|_| self) } /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat /// normals. /// /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// /// FIXME: This should handle more cases since this is called as a part of gltf /// mesh loading where we can't really blame users for loading meshes that might /// not conform to the limitations here! pub fn compute_normals(&mut self) { assert!( matches!(self.primitive_topology, PrimitiveTopology::TriangleList), "`compute_normals` can only work on `TriangleList`s" ); if self.indices().is_none() { self.compute_flat_normals(); } else { self.compute_smooth_normals(); } } /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. /// /// # Panics /// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Consider calling [`Mesh::duplicate_vertices`] or exporting your mesh with normal /// attributes. /// /// FIXME: This should handle more cases since this is called as a part of gltf /// mesh loading where we can't really blame users for loading meshes that might /// not conform to the limitations here! pub fn compute_flat_normals(&mut self) { assert!( self.indices().is_none(), "`compute_flat_normals` can't work on indexed geometry. Consider calling either `Mesh::compute_smooth_normals` or `Mesh::duplicate_vertices` followed by `Mesh::compute_flat_normals`." ); assert!( matches!(self.primitive_topology, PrimitiveTopology::TriangleList), "`compute_flat_normals` can only work on `TriangleList`s" ); let positions = self .attribute(Mesh::ATTRIBUTE_POSITION) .unwrap() .as_float3() .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); let normals: Vec<_> = positions .chunks_exact(3) .map(|p| face_normal(p[0], p[1], p[2])) .flat_map(|normal| [normal; 3]) .collect(); self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); } /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of an indexed mesh, smoothing normals for shared /// vertices. /// /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh does not have indices defined. /// /// FIXME: This should handle more cases since this is called as a part of gltf /// mesh loading where we can't really blame users for loading meshes that might /// not conform to the limitations here! pub fn compute_smooth_normals(&mut self) { assert!( matches!(self.primitive_topology, PrimitiveTopology::TriangleList), "`compute_smooth_normals` can only work on `TriangleList`s" ); assert!( self.indices().is_some(), "`compute_smooth_normals` can only work on indexed meshes" ); let positions = self .attribute(Mesh::ATTRIBUTE_POSITION) .unwrap() .as_float3() .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); let mut normals = vec![Vec3::ZERO; positions.len()]; self.indices() .unwrap() .iter() .collect::>() .chunks_exact(3) .for_each(|face| { let [a, b, c] = [face[0], face[1], face[2]]; let normal = Vec3::from(face_normal(positions[a], positions[b], positions[c])); [a, b, c].iter().for_each(|pos| { normals[*pos] += normal; }); }); // average (smooth) normals for shared vertices... // TODO: support different methods of weighting the average for normal in &mut normals { *normal = normal.try_normalize().unwrap_or(Vec3::ZERO); } self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); } /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. /// If the mesh is indexed, this defaults to smooth normals. Otherwise, it defaults to flat /// normals. /// /// (Alternatively, you can use [`Mesh::compute_normals`] to mutate an existing mesh in-place) /// /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. #[must_use] pub fn with_computed_normals(mut self) -> Self { self.compute_normals(); self } /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. /// /// (Alternatively, you can use [`Mesh::compute_flat_normals`] to mutate an existing mesh in-place) /// /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh has indices defined #[must_use] pub fn with_computed_flat_normals(mut self) -> Self { self.compute_flat_normals(); self } /// Consumes the mesh and returns a mesh with calculated [`Mesh::ATTRIBUTE_NORMAL`]. /// /// (Alternatively, you can use [`Mesh::compute_smooth_normals`] to mutate an existing mesh in-place) /// /// # Panics /// Panics if [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`. /// Panics if the mesh has any other topology than [`PrimitiveTopology::TriangleList`]. /// Panics if the mesh does not have indices defined. #[must_use] pub fn with_computed_smooth_normals(mut self) -> Self { self.compute_smooth_normals(); self } /// Generate tangents for the mesh using the `mikktspace` algorithm. /// /// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. pub fn generate_tangents(&mut self) -> Result<(), GenerateTangentsError> { let tangents = generate_tangents_for_mesh(self)?; self.insert_attribute(Mesh::ATTRIBUTE_TANGENT, tangents); Ok(()) } /// Consumes the mesh and returns a mesh with tangents generated using the `mikktspace` algorithm. /// /// The resulting mesh will have the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful. /// /// (Alternatively, you can use [`Mesh::generate_tangents`] to mutate an existing mesh in-place) /// /// Requires a [`PrimitiveTopology::TriangleList`] topology and the [`Mesh::ATTRIBUTE_POSITION`], [`Mesh::ATTRIBUTE_NORMAL`] and [`Mesh::ATTRIBUTE_UV_0`] attributes set. pub fn with_generated_tangents(mut self) -> Result { self.generate_tangents()?; Ok(self) } /// Merges the [`Mesh`] data of `other` with `self`. The attributes and indices of `other` will be appended to `self`. /// /// Note that attributes of `other` that don't exist on `self` will be ignored. /// /// `Aabb` of entities with modified mesh are not updated automatically. /// /// # Panics /// /// Panics if the vertex attribute values of `other` are incompatible with `self`. /// For example, [`VertexAttributeValues::Float32`] is incompatible with [`VertexAttributeValues::Float32x3`]. #[allow(clippy::match_same_arms)] pub fn merge(&mut self, other: &Mesh) { use VertexAttributeValues::*; // The indices of `other` should start after the last vertex of `self`. let index_offset = self .attribute(Mesh::ATTRIBUTE_POSITION) .get_or_insert(&Float32x3(Vec::default())) .len(); // Extend attributes of `self` with attributes of `other`. for (attribute, values) in self.attributes_mut() { let enum_variant_name = values.enum_variant_name(); if let Some(other_values) = other.attribute(attribute.id) { match (values, other_values) { (Float32(vec1), Float32(vec2)) => vec1.extend(vec2), (Sint32(vec1), Sint32(vec2)) => vec1.extend(vec2), (Uint32(vec1), Uint32(vec2)) => vec1.extend(vec2), (Float32x2(vec1), Float32x2(vec2)) => vec1.extend(vec2), (Sint32x2(vec1), Sint32x2(vec2)) => vec1.extend(vec2), (Uint32x2(vec1), Uint32x2(vec2)) => vec1.extend(vec2), (Float32x3(vec1), Float32x3(vec2)) => vec1.extend(vec2), (Sint32x3(vec1), Sint32x3(vec2)) => vec1.extend(vec2), (Uint32x3(vec1), Uint32x3(vec2)) => vec1.extend(vec2), (Sint32x4(vec1), Sint32x4(vec2)) => vec1.extend(vec2), (Uint32x4(vec1), Uint32x4(vec2)) => vec1.extend(vec2), (Float32x4(vec1), Float32x4(vec2)) => vec1.extend(vec2), (Sint16x2(vec1), Sint16x2(vec2)) => vec1.extend(vec2), (Snorm16x2(vec1), Snorm16x2(vec2)) => vec1.extend(vec2), (Uint16x2(vec1), Uint16x2(vec2)) => vec1.extend(vec2), (Unorm16x2(vec1), Unorm16x2(vec2)) => vec1.extend(vec2), (Sint16x4(vec1), Sint16x4(vec2)) => vec1.extend(vec2), (Snorm16x4(vec1), Snorm16x4(vec2)) => vec1.extend(vec2), (Uint16x4(vec1), Uint16x4(vec2)) => vec1.extend(vec2), (Unorm16x4(vec1), Unorm16x4(vec2)) => vec1.extend(vec2), (Sint8x2(vec1), Sint8x2(vec2)) => vec1.extend(vec2), (Snorm8x2(vec1), Snorm8x2(vec2)) => vec1.extend(vec2), (Uint8x2(vec1), Uint8x2(vec2)) => vec1.extend(vec2), (Unorm8x2(vec1), Unorm8x2(vec2)) => vec1.extend(vec2), (Sint8x4(vec1), Sint8x4(vec2)) => vec1.extend(vec2), (Snorm8x4(vec1), Snorm8x4(vec2)) => vec1.extend(vec2), (Uint8x4(vec1), Uint8x4(vec2)) => vec1.extend(vec2), (Unorm8x4(vec1), Unorm8x4(vec2)) => vec1.extend(vec2), _ => panic!( "Incompatible vertex attribute types {} and {}", enum_variant_name, other_values.enum_variant_name() ), } } } // Extend indices of `self` with indices of `other`. if let (Some(indices), Some(other_indices)) = (self.indices_mut(), other.indices()) { match (indices, other_indices) { (Indices::U16(i1), Indices::U16(i2)) => { i1.extend(i2.iter().map(|i| *i + index_offset as u16)); } (Indices::U32(i1), Indices::U32(i2)) => { i1.extend(i2.iter().map(|i| *i + index_offset as u32)); } (Indices::U16(i1), Indices::U32(i2)) => { i1.extend(i2.iter().map(|i| *i as u16 + index_offset as u16)); } (Indices::U32(i1), Indices::U16(i2)) => { i1.extend(i2.iter().map(|i| *i as u32 + index_offset as u32)); } } } } /// Transforms the vertex positions, normals, and tangents of the mesh by the given [`Transform`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn transformed_by(mut self, transform: Transform) -> Self { self.transform_by(transform); self } /// Transforms the vertex positions, normals, and tangents of the mesh in place by the given [`Transform`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn transform_by(&mut self, transform: Transform) { // Needed when transforming normals and tangents let scale_recip = 1. / transform.scale; debug_assert!( transform.scale.yzx() * transform.scale.zxy() != Vec3::ZERO, "mesh transform scale cannot be zero on more than one axis" ); if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply scale, rotation, and translation to vertex positions positions .iter_mut() .for_each(|pos| *pos = transform.transform_point(Vec3::from_slice(pos)).to_array()); } // No need to transform normals or tangents if rotation is near identity and scale is uniform if transform.rotation.is_near_identity() && transform.scale.x == transform.scale.y && transform.scale.y == transform.scale.z { return; } if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { // Transform normals, taking into account non-uniform scaling and rotation normals.iter_mut().for_each(|normal| { *normal = (transform.rotation * scale_normal(Vec3::from_array(*normal), scale_recip)) .to_array(); }); } if let Some(VertexAttributeValues::Float32x3(ref mut tangents)) = self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) { // Transform tangents, taking into account non-uniform scaling and rotation tangents.iter_mut().for_each(|tangent| { let scaled_tangent = Vec3::from_slice(tangent) * transform.scale; *tangent = (transform.rotation * scaled_tangent.normalize_or_zero()).to_array(); }); } } /// Translates the vertex positions of the mesh by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn translated_by(mut self, translation: Vec3) -> Self { self.translate_by(translation); self } /// Translates the vertex positions of the mesh in place by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn translate_by(&mut self, translation: Vec3) { if translation == Vec3::ZERO { return; } if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply translation to vertex positions positions .iter_mut() .for_each(|pos| *pos = (Vec3::from_slice(pos) + translation).to_array()); } } /// Rotates the vertex positions, normals, and tangents of the mesh by the given [`Quat`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn rotated_by(mut self, rotation: Quat) -> Self { self.rotate_by(rotation); self } /// Rotates the vertex positions, normals, and tangents of the mesh in place by the given [`Quat`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn rotate_by(&mut self, rotation: Quat) { if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply rotation to vertex positions positions .iter_mut() .for_each(|pos| *pos = (rotation * Vec3::from_slice(pos)).to_array()); } // No need to transform normals or tangents if rotation is near identity if rotation.is_near_identity() { return; } if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { // Transform normals normals.iter_mut().for_each(|normal| { *normal = (rotation * Vec3::from_slice(normal).normalize_or_zero()).to_array(); }); } if let Some(VertexAttributeValues::Float32x3(ref mut tangents)) = self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) { // Transform tangents tangents.iter_mut().for_each(|tangent| { *tangent = (rotation * Vec3::from_slice(tangent).normalize_or_zero()).to_array(); }); } } /// Scales the vertex positions, normals, and tangents of the mesh by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn scaled_by(mut self, scale: Vec3) -> Self { self.scale_by(scale); self } /// Scales the vertex positions, normals, and tangents of the mesh in place by the given [`Vec3`]. /// /// `Aabb` of entities with modified mesh are not updated automatically. pub fn scale_by(&mut self, scale: Vec3) { // Needed when transforming normals and tangents let scale_recip = 1. / scale; debug_assert!( scale.yzx() * scale.zxy() != Vec3::ZERO, "mesh transform scale cannot be zero on more than one axis" ); if let Some(VertexAttributeValues::Float32x3(ref mut positions)) = self.attribute_mut(Mesh::ATTRIBUTE_POSITION) { // Apply scale to vertex positions positions .iter_mut() .for_each(|pos| *pos = (scale * Vec3::from_slice(pos)).to_array()); } // No need to transform normals or tangents if scale is uniform if scale.x == scale.y && scale.y == scale.z { return; } if let Some(VertexAttributeValues::Float32x3(ref mut normals)) = self.attribute_mut(Mesh::ATTRIBUTE_NORMAL) { // Transform normals, taking into account non-uniform scaling normals.iter_mut().for_each(|normal| { *normal = scale_normal(Vec3::from_array(*normal), scale_recip).to_array(); }); } if let Some(VertexAttributeValues::Float32x3(ref mut tangents)) = self.attribute_mut(Mesh::ATTRIBUTE_TANGENT) { // Transform tangents, taking into account non-uniform scaling tangents.iter_mut().for_each(|tangent| { let scaled_tangent = Vec3::from_slice(tangent) * scale; *tangent = scaled_tangent.normalize_or_zero().to_array(); }); } } /// Whether this mesh has morph targets. pub fn has_morph_targets(&self) -> bool { self.morph_targets.is_some() } /// Set [morph targets] image for this mesh. This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. /// /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation pub fn set_morph_targets(&mut self, morph_targets: Handle) { self.morph_targets = Some(morph_targets); } pub fn morph_targets(&self) -> Option<&Handle> { self.morph_targets.as_ref() } /// Consumes the mesh and returns a mesh with the given [morph targets]. /// /// This requires a "morph target image". See [`MorphTargetImage`](crate::morph::MorphTargetImage) for info. /// /// (Alternatively, you can use [`Mesh::set_morph_targets`] to mutate an existing mesh in-place) /// /// [morph targets]: https://en.wikipedia.org/wiki/Morph_target_animation #[must_use] pub fn with_morph_targets(mut self, morph_targets: Handle) -> Self { self.set_morph_targets(morph_targets); self } /// Sets the names of each morph target. This should correspond to the order of the morph targets in `set_morph_targets`. pub fn set_morph_target_names(&mut self, names: Vec) { self.morph_target_names = Some(names); } /// Consumes the mesh and returns a mesh with morph target names. /// Names should correspond to the order of the morph targets in `set_morph_targets`. /// /// (Alternatively, you can use [`Mesh::set_morph_target_names`] to mutate an existing mesh in-place) #[must_use] pub fn with_morph_target_names(mut self, names: Vec) -> Self { self.set_morph_target_names(names); self } /// Gets a list of all morph target names, if they exist. pub fn morph_target_names(&self) -> Option<&[String]> { self.morph_target_names.as_deref() } /// Normalize joint weights so they sum to 1. pub fn normalize_joint_weights(&mut self) { if let Some(joints) = self.attribute_mut(Self::ATTRIBUTE_JOINT_WEIGHT) { let VertexAttributeValues::Float32x4(ref mut joints) = joints else { panic!("unexpected joint weight format"); }; for weights in joints.iter_mut() { // force negative weights to zero weights.iter_mut().for_each(|w| *w = w.max(0.0)); let sum: f32 = weights.iter().sum(); if sum == 0.0 { // all-zero weights are invalid weights[0] = 1.0; } else { let recip = sum.recip(); for weight in weights.iter_mut() { *weight *= recip; } } } } } /// Get a list of this Mesh's [triangles] as an iterator if possible. /// /// Returns an error if any of the following conditions are met (see [`MeshTrianglesError`]): /// * The Mesh's [primitive topology] is not `TriangleList` or `TriangleStrip`. /// * The Mesh is missing position or index data. /// * The Mesh's position data has the wrong format (not `Float32x3`). /// /// [primitive topology]: PrimitiveTopology /// [triangles]: Triangle3d pub fn triangles(&self) -> Result + '_, MeshTrianglesError> { let Some(position_data) = self.attribute(Mesh::ATTRIBUTE_POSITION) else { return Err(MeshTrianglesError::MissingPositions); }; let Some(vertices) = position_data.as_float3() else { return Err(MeshTrianglesError::PositionsFormat); }; let Some(indices) = self.indices() else { return Err(MeshTrianglesError::MissingIndices); }; match self.primitive_topology { PrimitiveTopology::TriangleList => { // When indices reference out-of-bounds vertex data, the triangle is omitted. // This implicitly truncates the indices to a multiple of 3. let iterator = match indices { Indices::U16(vec) => FourIterators::First( vec.as_slice() .chunks_exact(3) .flat_map(move |indices| indices_to_triangle(vertices, indices)), ), Indices::U32(vec) => FourIterators::Second( vec.as_slice() .chunks_exact(3) .flat_map(move |indices| indices_to_triangle(vertices, indices)), ), }; return Ok(iterator); } PrimitiveTopology::TriangleStrip => { // When indices reference out-of-bounds vertex data, the triangle is omitted. // If there aren't enough indices to make a triangle, then an empty vector will be // returned. let iterator = match indices { Indices::U16(vec) => FourIterators::Third( vec.as_slice() .windows(3) .flat_map(move |indices| indices_to_triangle(vertices, indices)), ), Indices::U32(vec) => FourIterators::Fourth( vec.as_slice() .windows(3) .flat_map(move |indices| indices_to_triangle(vertices, indices)), ), }; return Ok(iterator); } _ => { return Err(MeshTrianglesError::WrongTopology); } }; fn indices_to_triangle + Copy>( vertices: &[[f32; 3]], indices: &[T], ) -> Option { let vert0: Vec3 = Vec3::from(*vertices.get(indices[0].try_into().ok()?)?); let vert1: Vec3 = Vec3::from(*vertices.get(indices[1].try_into().ok()?)?); let vert2: Vec3 = Vec3::from(*vertices.get(indices[2].try_into().ok()?)?); Some(Triangle3d { vertices: [vert0, vert1, vert2], }) } } } impl core::ops::Mul for Transform { type Output = Mesh; fn mul(self, rhs: Mesh) -> Self::Output { rhs.transformed_by(self) } } #[cfg(test)] mod tests { use super::Mesh; use crate::mesh::{Indices, MeshWindingInvertError, VertexAttributeValues}; use bevy_asset::RenderAssetUsages; use bevy_math::Vec3; use bevy_transform::components::Transform; use wgpu::PrimitiveTopology; #[test] #[should_panic] fn panic_invalid_format() { let _mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::default(), ) .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]); } #[test] fn transform_mesh() { let mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::default(), ) .with_inserted_attribute( Mesh::ATTRIBUTE_POSITION, vec![[-1., -1., 2.], [1., -1., 2.], [0., 1., 2.]], ) .with_inserted_attribute( Mesh::ATTRIBUTE_NORMAL, vec![ Vec3::new(-1., -1., 1.).normalize().to_array(), Vec3::new(1., -1., 1.).normalize().to_array(), [0., 0., 1.], ], ) .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0., 0.], [1., 0.], [0.5, 1.]]); let mesh = mesh.transformed_by( Transform::from_translation(Vec3::splat(-2.)).with_scale(Vec3::new(2., 0., -1.)), ); if let Some(VertexAttributeValues::Float32x3(positions)) = mesh.attribute(Mesh::ATTRIBUTE_POSITION) { // All positions are first scaled resulting in `vec![[-2, 0., -2.], [2., 0., -2.], [0., 0., -2.]]` // and then shifted by `-2.` along each axis assert_eq!( positions, &vec![[-4.0, -2.0, -4.0], [0.0, -2.0, -4.0], [-2.0, -2.0, -4.0]] ); } else { panic!("Mesh does not have a position attribute"); } if let Some(VertexAttributeValues::Float32x3(normals)) = mesh.attribute(Mesh::ATTRIBUTE_NORMAL) { assert_eq!(normals, &vec![[0., -1., 0.], [0., -1., 0.], [0., 0., -1.]]); } else { panic!("Mesh does not have a normal attribute"); } if let Some(VertexAttributeValues::Float32x2(uvs)) = mesh.attribute(Mesh::ATTRIBUTE_UV_0) { assert_eq!(uvs, &vec![[0., 0.], [1., 0.], [0.5, 1.]]); } else { panic!("Mesh does not have a uv attribute"); } } #[test] fn point_list_mesh_invert_winding() { let mesh = Mesh::new(PrimitiveTopology::PointList, RenderAssetUsages::default()) .with_inserted_indices(Indices::U32(vec![])); assert!(matches!( mesh.with_inverted_winding(), Err(MeshWindingInvertError::WrongTopology) )); } #[test] fn line_list_mesh_invert_winding() { let mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) .with_inserted_indices(Indices::U32(vec![0, 1, 1, 2, 2, 3])); let mesh = mesh.with_inverted_winding().unwrap(); assert_eq!( mesh.indices().unwrap().iter().collect::>(), vec![3, 2, 2, 1, 1, 0] ); } #[test] fn line_list_mesh_invert_winding_fail() { let mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default()) .with_inserted_indices(Indices::U32(vec![0, 1, 1])); assert!(matches!( mesh.with_inverted_winding(), Err(MeshWindingInvertError::AbruptIndicesEnd) )); } #[test] fn line_strip_mesh_invert_winding() { let mesh = Mesh::new(PrimitiveTopology::LineStrip, RenderAssetUsages::default()) .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3])); let mesh = mesh.with_inverted_winding().unwrap(); assert_eq!( mesh.indices().unwrap().iter().collect::>(), vec![3, 2, 1, 0] ); } #[test] fn triangle_list_mesh_invert_winding() { let mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::default(), ) .with_inserted_indices(Indices::U32(vec![ 0, 3, 1, // First triangle 1, 3, 2, // Second triangle ])); let mesh = mesh.with_inverted_winding().unwrap(); assert_eq!( mesh.indices().unwrap().iter().collect::>(), vec![ 0, 1, 3, // First triangle 1, 2, 3, // Second triangle ] ); } #[test] fn triangle_list_mesh_invert_winding_fail() { let mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::default(), ) .with_inserted_indices(Indices::U32(vec![0, 3, 1, 2])); assert!(matches!( mesh.with_inverted_winding(), Err(MeshWindingInvertError::AbruptIndicesEnd) )); } #[test] fn triangle_strip_mesh_invert_winding() { let mesh = Mesh::new( PrimitiveTopology::TriangleStrip, RenderAssetUsages::default(), ) .with_inserted_indices(Indices::U32(vec![0, 1, 2, 3])); let mesh = mesh.with_inverted_winding().unwrap(); assert_eq!( mesh.indices().unwrap().iter().collect::>(), vec![3, 2, 1, 0] ); } #[test] fn compute_smooth_normals() { let mut mesh = Mesh::new( PrimitiveTopology::TriangleList, RenderAssetUsages::default(), ); // z y // | / // 3---2 // | / \ // 0-----1--x mesh.insert_attribute( Mesh::ATTRIBUTE_POSITION, vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]], ); mesh.insert_indices(Indices::U16(vec![0, 1, 2, 0, 2, 3])); mesh.compute_smooth_normals(); let normals = mesh .attribute(Mesh::ATTRIBUTE_NORMAL) .unwrap() .as_float3() .unwrap(); assert_eq!(4, normals.len()); // 0 assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[0]); // 1 assert_eq!([0., 0., 1.], normals[1]); // 2 assert_eq!(Vec3::new(1., 0., 1.).normalize().to_array(), normals[2]); // 3 assert_eq!([1., 0., 0.], normals[3]); } }