mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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:
parent
223a54629c
commit
190d032ae4
3 changed files with 118 additions and 27 deletions
|
@ -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(),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue