Skip to content

Commit

Permalink
Add consuming builder methods for more ergonomic Mesh creation (bev…
Browse files Browse the repository at this point in the history
…yengine#10056)

# Objective

- This PR aims to make creating meshes a little bit more ergonomic,
specifically by removing the need for intermediate mutable variables.

## Solution

- We add methods that consume the `Mesh` and return a mesh with the
specified changes, so that meshes can be entirely constructed via
builder-style calls, without intermediate variables;
- Methods are flagged with `#[must_use]` to ensure proper use;
- Examples are updated to use the new methods where applicable. Some
examples are kept with the mutating methods so that users can still
easily discover them, and also where the new methods wouldn't really be
an improvement.

## Examples

Before:

```rust
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vs);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vns);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vts);
mesh.set_indices(Some(Indices::U32(tris)));
mesh
```

After:

```rust
Mesh::new(PrimitiveTopology::TriangleList)
    .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs)
    .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns)
    .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts)
    .with_indices(Some(Indices::U32(tris)))
```

Before:

```rust
let mut cube = Mesh::from(shape::Cube { size: 1.0 });

cube.generate_tangents().unwrap();

PbrBundle {
    mesh: meshes.add(cube),
    ..default()
}
```

After:

```rust
PbrBundle {
    mesh: meshes.add(
        Mesh::from(shape::Cube { size: 1.0 })
            .with_generated_tangents()
            .unwrap(),
    ),
    ..default()
}
```

---

## Changelog

- Added consuming builder methods for more ergonomic `Mesh` creation:
`with_inserted_attribute()`, `with_removed_attribute()`,
`with_indices()`, `with_duplicated_vertices()`,
`with_computed_flat_normals()`, `with_generated_tangents()`,
`with_morph_targets()`, `with_morph_target_names()`.
  • Loading branch information
coreh authored and Thomas Wilgenbus committed Oct 13, 2023
1 parent 11e99f2 commit 8e1d247
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 199 deletions.
4 changes: 3 additions & 1 deletion crates/bevy_pbr/src/pbr_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,13 @@ pub struct StandardMaterial {
/// - Vertex normals
///
/// Tangents do not have to be stored in your model,
/// they can be generated using the [`Mesh::generate_tangents`] method.
/// they can be generated using the [`Mesh::generate_tangents`] or
/// [`Mesh::with_generated_tangents`] methods.
/// If your material has a normal map, but still renders as a flat surface,
/// make sure your meshes have their tangents set.
///
/// [`Mesh::generate_tangents`]: bevy_render::mesh::Mesh::generate_tangents
/// [`Mesh::with_generated_tangents`]: bevy_render::mesh::Mesh::with_generated_tangents
#[texture(9)]
#[sampler(10)]
#[dependency]
Expand Down
178 changes: 142 additions & 36 deletions crates/bevy_render/src/mesh/mesh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,32 +49,32 @@ pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10;
/// # use bevy_render::mesh::{Mesh, Indices};
/// # use bevy_render::render_resource::PrimitiveTopology;
/// fn create_simple_parallelogram() -> Mesh {
/// // Create a new mesh, add 4 vertices, each with its own position attribute (coordinate in
/// // 3D space), for each of the corners of the parallelogram.
/// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
/// mesh.insert_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.
/// mesh.insert_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)
/// mesh.insert_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.
/// mesh.set_indices(Some(Indices::U32(vec![
/// // First triangle
/// 0, 3, 1,
/// // Second triangle
/// 1, 3, 2
/// ])));
/// mesh
/// // Create a new mesh using a triangle list topology, where each set of 3 vertices composes a triangle.
/// Mesh::new(PrimitiveTopology::TriangleList)
/// // 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_indices(Some(Indices::U32(vec![
/// // First triangle
/// 0, 3, 1,
/// // Second triangle
/// 1, 3, 2
/// ])))
/// }
/// ```
///
Expand Down Expand Up @@ -126,16 +126,18 @@ pub struct Mesh {
}

impl Mesh {
/// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`].
/// Where the vertex is located in space. Use in conjunction with [`Mesh::insert_attribute`]
/// or [`Mesh::with_inserted_attribute`].
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`].
/// Use in conjunction with [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`].
pub const ATTRIBUTE_NORMAL: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Normal", 1, VertexFormat::Float32x3);

/// Texture coordinates for the vertex. Use in conjunction with [`Mesh::insert_attribute`].
/// Texture coordinates for the vertex. Use in conjunction with [`Mesh::insert_attribute`]
/// or [`Mesh::with_inserted_attribute`].
///
/// Values are generally between 0. and 1., with `StandardMaterial` and `ColorMaterial`
/// `[0.,0.]` is the top left of the texture, and [1.,1.] the bottom-right.
Expand All @@ -147,27 +149,31 @@ impl Mesh {
MeshVertexAttribute::new("Vertex_Uv", 2, VertexFormat::Float32x2);

/// Alternate texture coordinates for the vertex. Use in conjunction with
/// [`Mesh::insert_attribute`].
/// [`Mesh::insert_attribute`] or [`Mesh::with_inserted_attribute`].
///
/// Typically, these are used for lightmaps, textures that provide
/// precomputed illumination.
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).
/// Usually generated with [`generate_tangents`](Mesh::generate_tangents) or
/// [`with_generated_tangents`](Mesh::with_generated_tangents).
pub const ATTRIBUTE_TANGENT: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_Tangent", 4, VertexFormat::Float32x4);

/// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`].
/// Per vertex coloring. Use in conjunction with [`Mesh::insert_attribute`]
/// or [`Mesh::with_inserted_attribute`].
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`].
/// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::insert_attribute`]
/// or [`Mesh::with_inserted_attribute`].
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`].
/// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::insert_attribute`]
/// or [`Mesh::with_inserted_attribute`].
pub const ATTRIBUTE_JOINT_INDEX: MeshVertexAttribute =
MeshVertexAttribute::new("Vertex_JointIndex", 7, VertexFormat::Uint16x4);

Expand Down Expand Up @@ -224,6 +230,24 @@ impl Mesh {
.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)
///
/// # 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,
Expand All @@ -234,6 +258,15 @@ impl Mesh {
.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())
Expand Down Expand Up @@ -283,6 +316,18 @@ impl Mesh {
self.indices = 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::set_indices`] to mutate an existing mesh in-place)
#[must_use]
#[inline]
pub fn with_indices(mut self, indices: Option<Indices>) -> Self {
self.set_indices(indices);
self
}

/// Retrieves the vertex `indices` of the mesh.
#[inline]
pub fn indices(&self) -> Option<&Indices> {
Expand Down Expand Up @@ -436,6 +481,18 @@ impl Mesh {
}
}

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

/// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh.
///
/// # Panics
Expand Down Expand Up @@ -465,6 +522,20 @@ impl Mesh {
self.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
}

/// 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 [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3` or
/// if the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
/// Consider calling [`Mesh::with_duplicated_vertices`] or export your mesh with normal attributes.
#[must_use]
pub fn with_computed_flat_normals(mut self) -> Self {
self.compute_flat_normals();
self
}

/// Generate tangents for the mesh using the `mikktspace` algorithm.
///
/// Sets the [`Mesh::ATTRIBUTE_TANGENT`] attribute if successful.
Expand All @@ -475,6 +546,18 @@ impl Mesh {
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)
}

/// Compute the Axis-Aligned Bounding Box of the mesh vertices in model space
pub fn compute_aabb(&self) -> Option<Aabb> {
let Some(VertexAttributeValues::Float32x3(values)) =
Expand All @@ -498,11 +581,34 @@ impl Mesh {
self.morph_targets = Some(morph_targets);
}

/// Consumes the mesh and returns a mesh with the given [morph targets].
///
/// This requires a "morph target image". See [`MorphTargetImage`](crate::mesh::morph::MorphTargetImage) for info.
///
/// (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()
Expand Down Expand Up @@ -1123,7 +1229,7 @@ mod tests {
#[test]
#[should_panic]
fn panic_invalid_format() {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]);
let _mesh = Mesh::new(PrimitiveTopology::TriangleList)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vec![[0.0, 0.0, 0.0]]);
}
}
11 changes: 5 additions & 6 deletions crates/bevy_render/src/mesh/shape/capsule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,11 +364,10 @@ impl From<Capsule> for Mesh {
assert_eq!(vs.len(), vert_len);
assert_eq!(tris.len(), fs_len);

let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vs);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vns);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, vts);
mesh.set_indices(Some(Indices::U32(tris)));
mesh
Mesh::new(PrimitiveTopology::TriangleList)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vs)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, vns)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, vts)
.with_indices(Some(Indices::U32(tris)))
}
}
11 changes: 5 additions & 6 deletions crates/bevy_render/src/mesh/shape/cylinder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,10 @@ impl From<Cylinder> for Mesh {
build_cap(true);
build_cap(false);

let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(Indices::U32(indices)));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh
Mesh::new(PrimitiveTopology::TriangleList)
.with_indices(Some(Indices::U32(indices)))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
}
}
11 changes: 5 additions & 6 deletions crates/bevy_render/src/mesh/shape/icosphere.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,10 @@ impl TryFrom<Icosphere> for Mesh {

let indices = Indices::U32(indices);

let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.set_indices(Some(indices));
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, points);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
Ok(mesh)
Ok(Mesh::new(PrimitiveTopology::TriangleList)
.with_indices(Some(indices))
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, points)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs))
}
}
Loading

0 comments on commit 8e1d247

Please sign in to comment.