 4a23dc4216
			
		
	
	
		4a23dc4216
		
			
		
	
	
	
	
		
			
			# 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>
		
			
				
	
	
		
			1372 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			1372 lines
		
	
	
		
			57 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 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 modelling for complex UV mapping or other calculations.
 | |
| /// - Bevy performs frustum culling based on the `Aabb` of meshes, which is calculated
 | |
| ///     and added automatically for new meshes only. If a mesh is modified, the entity's `Aabb`
 | |
| ///     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<MeshVertexAttributeId, MeshAttributeData>,
 | |
|     indices: Option<Indices>,
 | |
|     morph_targets: Option<Handle<Image>>,
 | |
|     morph_target_names: Option<Vec<String>>,
 | |
|     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<VertexAttributeValues>,
 | |
|     ) {
 | |
|         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<VertexAttributeValues>,
 | |
|     ) -> Self {
 | |
|         self.insert_attribute(attribute, values);
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     /// Removes the data for a vertex attribute
 | |
|     pub fn remove_attribute(
 | |
|         &mut self,
 | |
|         attribute: impl Into<MeshVertexAttributeId>,
 | |
|     ) -> Option<VertexAttributeValues> {
 | |
|         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<MeshVertexAttributeId>) -> Self {
 | |
|         self.remove_attribute(attribute);
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     #[inline]
 | |
|     pub fn contains_attribute(&self, id: impl Into<MeshVertexAttributeId>) -> 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<MeshVertexAttributeId>,
 | |
|     ) -> 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<MeshVertexAttributeId>,
 | |
|     ) -> 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<Item = (&MeshVertexAttribute, &VertexAttributeValues)> {
 | |
|         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<Item = (&MeshVertexAttribute, &mut VertexAttributeValues)> {
 | |
|         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<Indices> {
 | |
|         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<usize> = 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<u8> {
 | |
|         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<T: Copy>(values: &[T], indices: impl Iterator<Item = usize>) -> Vec<T> {
 | |
|             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<I>(
 | |
|             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, MeshWindingInvertError> {
 | |
|         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()];
 | |
|         let mut adjacency_counts = vec![0_usize; positions.len()];
 | |
| 
 | |
|         self.indices()
 | |
|             .unwrap()
 | |
|             .iter()
 | |
|             .collect::<Vec<usize>>()
 | |
|             .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;
 | |
|                     adjacency_counts[*pos] += 1;
 | |
|                 });
 | |
|             });
 | |
| 
 | |
|         // average (smooth) normals for shared vertices...
 | |
|         // TODO: support different methods of weighting the average
 | |
|         for i in 0..normals.len() {
 | |
|             let count = adjacency_counts[i];
 | |
|             if count > 0 {
 | |
|                 normals[i] = (normals[i] / (count as f32)).normalize();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         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<Mesh, GenerateTangentsError> {
 | |
|         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<Image>) {
 | |
|         self.morph_targets = Some(morph_targets);
 | |
|     }
 | |
| 
 | |
|     pub fn morph_targets(&self) -> Option<&Handle<Image>> {
 | |
|         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<Image>) -> 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<String>) {
 | |
|         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<String>) -> 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<impl Iterator<Item = Triangle3d> + '_, 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<T: TryInto<usize> + Copy>(
 | |
|             vertices: &[[f32; 3]],
 | |
|             indices: &[T],
 | |
|         ) -> Option<Triangle3d> {
 | |
|             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<Mesh> 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<usize>>(),
 | |
|             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<usize>>(),
 | |
|             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<usize>>(),
 | |
|             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<usize>>(),
 | |
|             vec![3, 2, 1, 0]
 | |
|         );
 | |
|     }
 | |
| }
 |