Additional options to mesh primitives (#13605)

# Objective

Add new options to some primitives, like anchoring for Cones and
cylinders and custom angle ranges for Torus.
I think these kind of options are useful, but I would understand that
these addition feel overkill

## Solution

Add 

## Testing

- Did you test these changes? If so, how?

> I used the new options in the `3d_shapes` example with various
parameters and got the expected results

## Changelog

- Added `caps` bool option to toggle cylinder circle caps
- Added `angle_range` f32 range option non full torus shapes
- Added `anchor` enum option for cylinders, with either `Top`,
`Midpoint` or `Bottom`
- Added `anchor` enum option for cones, with either `Tip`, `Midpoint` or
`Base`
- **BREAKING** `TorusMeshBuilder` is no longer `Copy` due to
`RangeInclusive`, we can use a `(f32, f32)` if we want it to be `Copy`
This commit is contained in:
Félix Lescaudey de Maneville 2024-06-03 15:27:11 +02:00 committed by GitHub
parent 223a54629c
commit 190d032ae4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 27 deletions

View file

@ -6,6 +6,18 @@ use crate::{
render_asset::RenderAssetUsages,
};
/// Anchoring options for [`ConeMeshBuilder`]
#[derive(Debug, Copy, Clone, Default)]
pub enum ConeAnchor {
#[default]
/// Midpoint between the tip of the cone and the center of its base.
MidPoint,
/// The Tip of the triangle
Tip,
/// The center of the base circle
Base,
}
/// A builder used for creating a [`Mesh`] with a [`Cone`] shape.
#[derive(Clone, Copy, Debug)]
pub struct ConeMeshBuilder {
@ -15,6 +27,9 @@ pub struct ConeMeshBuilder {
///
/// The default is `32`.
pub resolution: u32,
/// The anchor point for the cone mesh, defaults to the midpoint between
/// the tip of the cone and the center of its base
pub anchor: ConeAnchor,
}
impl Default for ConeMeshBuilder {
@ -22,6 +37,7 @@ impl Default for ConeMeshBuilder {
Self {
cone: Cone::default(),
resolution: 32,
anchor: ConeAnchor::default(),
}
}
}
@ -34,6 +50,7 @@ impl ConeMeshBuilder {
Self {
cone: Cone { radius, height },
resolution,
anchor: ConeAnchor::MidPoint,
}
}
@ -43,6 +60,13 @@ impl ConeMeshBuilder {
self.resolution = resolution;
self
}
/// Sets a custom anchor point for the mesh
#[inline]
pub const fn anchor(mut self, anchor: ConeAnchor) -> Self {
self.anchor = anchor;
self
}
}
impl MeshBuilder for ConeMeshBuilder {
@ -130,6 +154,13 @@ impl MeshBuilder for ConeMeshBuilder {
indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]);
}
// Offset the vertex positions Y axis to match the anchor
match self.anchor {
ConeAnchor::Tip => positions.iter_mut().for_each(|p| p[1] -= half_height),
ConeAnchor::Base => positions.iter_mut().for_each(|p| p[1] += half_height),
ConeAnchor::MidPoint => (),
};
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),

View file

@ -6,6 +6,18 @@ use crate::{
render_asset::RenderAssetUsages,
};
/// Anchoring options for [`CylinderMeshBuilder`]
#[derive(Debug, Copy, Clone, Default)]
pub enum CylinderAnchor {
#[default]
/// Midpoint between the top and bottom caps of the cylinder
MidPoint,
/// The center of the top circle cap
Top,
/// The center of the bottom circle cap
Bottom,
}
/// A builder used for creating a [`Mesh`] with a [`Cylinder`] shape.
#[derive(Clone, Copy, Debug)]
pub struct CylinderMeshBuilder {
@ -20,6 +32,12 @@ pub struct CylinderMeshBuilder {
///
/// The default is `1`.
pub segments: u32,
/// If set to `true`, the cylinder caps (flat circle faces) are built,
/// otherwise the mesh will be a shallow tube
pub caps: bool,
/// The anchor point for the cylinder mesh, defaults to the midpoint between
/// the top and bottom caps
pub anchor: CylinderAnchor,
}
impl Default for CylinderMeshBuilder {
@ -28,6 +46,8 @@ impl Default for CylinderMeshBuilder {
cylinder: Cylinder::default(),
resolution: 32,
segments: 1,
caps: true,
anchor: CylinderAnchor::default(),
}
}
}
@ -58,6 +78,20 @@ impl CylinderMeshBuilder {
self.segments = segments;
self
}
/// Ignore the cylinder caps, making the mesh a shallow tube instead
#[inline]
pub const fn without_caps(mut self) -> Self {
self.caps = false;
self
}
/// Sets a custom anchor point for the mesh
#[inline]
pub const fn anchor(mut self, anchor: CylinderAnchor) -> Self {
self.anchor = anchor;
self
}
}
impl MeshBuilder for CylinderMeshBuilder {
@ -118,36 +152,48 @@ impl MeshBuilder for CylinderMeshBuilder {
}
// caps
if self.caps {
let mut build_cap = |top: bool| {
let offset = positions.len() as u32;
let (y, normal_y, winding) = if top {
(self.cylinder.half_height, 1., (1, 0))
} else {
(-self.cylinder.half_height, -1., (0, 1))
};
let mut build_cap = |top: bool| {
let offset = positions.len() as u32;
let (y, normal_y, winding) = if top {
(self.cylinder.half_height, 1., (1, 0))
} else {
(-self.cylinder.half_height, -1., (0, 1))
for i in 0..self.resolution {
let theta = i as f32 * step_theta;
let (sin, cos) = theta.sin_cos();
positions.push([cos * self.cylinder.radius, y, sin * self.cylinder.radius]);
normals.push([0.0, normal_y, 0.0]);
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
}
for i in 1..(self.resolution - 1) {
indices.extend_from_slice(&[
offset,
offset + i + winding.0,
offset + i + winding.1,
]);
}
};
for i in 0..self.resolution {
let theta = i as f32 * step_theta;
let (sin, cos) = theta.sin_cos();
build_cap(true);
build_cap(false);
}
positions.push([cos * self.cylinder.radius, y, sin * self.cylinder.radius]);
normals.push([0.0, normal_y, 0.0]);
uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]);
}
for i in 1..(self.resolution - 1) {
indices.extend_from_slice(&[
offset,
offset + i + winding.0,
offset + i + winding.1,
]);
}
// Offset the vertex positions Y axis to match the anchor
match self.anchor {
CylinderAnchor::Top => positions
.iter_mut()
.for_each(|p| p[1] -= self.cylinder.half_height),
CylinderAnchor::Bottom => positions
.iter_mut()
.for_each(|p| p[1] += self.cylinder.half_height),
CylinderAnchor::MidPoint => (),
};
build_cap(true);
build_cap(false);
Mesh::new(
PrimitiveTopology::TriangleList,
RenderAssetUsages::default(),

View file

@ -1,4 +1,5 @@
use bevy_math::{primitives::Torus, Vec3};
use std::ops::RangeInclusive;
use wgpu::PrimitiveTopology;
use crate::{
@ -7,7 +8,7 @@ use crate::{
};
/// A builder used for creating a [`Mesh`] with a [`Torus`] shape.
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
pub struct TorusMeshBuilder {
/// The [`Torus`] shape.
pub torus: Torus,
@ -23,6 +24,8 @@ pub struct TorusMeshBuilder {
///
/// The default is `32`.
pub major_resolution: usize,
/// Optional angle range in radians, defaults to a full circle (0.0..=2 * PI)
pub angle_range: RangeInclusive<f32>,
}
impl Default for TorusMeshBuilder {
@ -31,6 +34,7 @@ impl Default for TorusMeshBuilder {
torus: Torus::default(),
minor_resolution: 24,
major_resolution: 32,
angle_range: (0.0..=2.0 * std::f32::consts::PI),
}
}
}
@ -65,6 +69,13 @@ impl TorusMeshBuilder {
self.major_resolution = resolution;
self
}
/// Sets a custom angle range in radians instead of a full circle
#[inline]
pub const fn angle_range(mut self, range: RangeInclusive<f32>) -> Self {
self.angle_range = range;
self
}
}
impl MeshBuilder for TorusMeshBuilder {
@ -76,11 +87,14 @@ impl MeshBuilder for TorusMeshBuilder {
let mut normals: Vec<[f32; 3]> = Vec::with_capacity(n_vertices);
let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(n_vertices);
let segment_stride = 2.0 * std::f32::consts::PI / self.major_resolution as f32;
let start_angle = self.angle_range.start();
let end_angle = self.angle_range.end();
let segment_stride = (end_angle - start_angle) / self.major_resolution as f32;
let side_stride = 2.0 * std::f32::consts::PI / self.minor_resolution as f32;
for segment in 0..=self.major_resolution {
let theta = segment_stride * segment as f32;
let theta = start_angle + segment_stride * segment as f32;
for side in 0..=self.minor_resolution {
let phi = side_stride * side as f32;