From fb73e5d5faecdec43eb3dcbbaedc9288b6eaa19c Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sat, 2 Dec 2023 16:48:35 +0200 Subject: [PATCH 01/36] `From` for `Cuboid`, `Rectangle`, `RegularPolygon` and `Circle` --- crates/bevy_render/src/mesh/mod.rs | 1 + .../src/mesh/primitive_meshes/mod.rs | 150 ++++++++++++++++++ examples/2d/2d_shapes.rs | 16 +- 3 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/mod.rs diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 39c9b19cc9e5b..556b549e34d81 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,6 +1,7 @@ #[allow(clippy::module_inception)] mod mesh; pub mod morph; +pub mod primitive_meshes; /// Generation for some primitive shape meshes. pub mod shape; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs new file mode 100644 index 0000000000000..ceb5e70ef157f --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -0,0 +1,150 @@ +use super::{Indices, Mesh, PrimitiveTopology}; +use bevy_math::primitives::{Circle, Cuboid, Rectangle, RegularPolygon}; + +impl From for Mesh { + fn from(cuboid: Cuboid) -> Self { + let min = -cuboid.half_extents; + let max = cuboid.half_extents; + + // suppose Y-up right hand, and camera look from +z to -z + let vertices = &[ + // Front + ([min.x, min.y, max.z], [0.0, 0.0, 1.0], [0.0, 0.0]), + ([max.x, min.y, max.z], [0.0, 0.0, 1.0], [1.0, 0.0]), + ([max.x, max.y, max.z], [0.0, 0.0, 1.0], [1.0, 1.0]), + ([min.x, max.y, max.z], [0.0, 0.0, 1.0], [0.0, 1.0]), + // Back + ([min.x, max.y, min.z], [0.0, 0.0, -1.0], [1.0, 0.0]), + ([max.x, max.y, min.z], [0.0, 0.0, -1.0], [0.0, 0.0]), + ([max.x, min.y, min.z], [0.0, 0.0, -1.0], [0.0, 1.0]), + ([min.x, min.y, min.z], [0.0, 0.0, -1.0], [1.0, 1.0]), + // Right + ([max.x, min.y, min.z], [1.0, 0.0, 0.0], [0.0, 0.0]), + ([max.x, max.y, min.z], [1.0, 0.0, 0.0], [1.0, 0.0]), + ([max.x, max.y, max.z], [1.0, 0.0, 0.0], [1.0, 1.0]), + ([max.x, min.y, max.z], [1.0, 0.0, 0.0], [0.0, 1.0]), + // Left + ([min.x, min.y, max.z], [-1.0, 0.0, 0.0], [1.0, 0.0]), + ([min.x, max.y, max.z], [-1.0, 0.0, 0.0], [0.0, 0.0]), + ([min.x, max.y, min.z], [-1.0, 0.0, 0.0], [0.0, 1.0]), + ([min.x, min.y, min.z], [-1.0, 0.0, 0.0], [1.0, 1.0]), + // Top + ([max.x, max.y, min.z], [0.0, 1.0, 0.0], [1.0, 0.0]), + ([min.x, max.y, min.z], [0.0, 1.0, 0.0], [0.0, 0.0]), + ([min.x, max.y, max.z], [0.0, 1.0, 0.0], [0.0, 1.0]), + ([max.x, max.y, max.z], [0.0, 1.0, 0.0], [1.0, 1.0]), + // Bottom + ([max.x, min.y, max.z], [0.0, -1.0, 0.0], [0.0, 0.0]), + ([min.x, min.y, max.z], [0.0, -1.0, 0.0], [1.0, 0.0]), + ([min.x, min.y, min.z], [0.0, -1.0, 0.0], [1.0, 1.0]), + ([max.x, min.y, min.z], [0.0, -1.0, 0.0], [0.0, 1.0]), + ]; + + let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); + let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); + let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); + + let indices = Indices::U32(vec![ + 0, 1, 2, 2, 3, 0, // front + 4, 5, 6, 6, 7, 4, // back + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // top + 20, 21, 22, 22, 23, 20, // bottom + ]); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(indices)) + } +} + +pub trait MeshableRectangle { + fn mesh(&self, flip: bool) -> Mesh; +} + +impl MeshableRectangle for Rectangle { + fn mesh(&self, flip: bool) -> Mesh { + let (u_left, u_right) = if flip { (1.0, 0.0) } else { (0.0, 1.0) }; + let [hw, hh] = [self.half_width, self.half_height]; + let vertices = [ + ([-hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_left, 1.0]), + ([-hw, hh, 0.0], [0.0, 0.0, 1.0], [u_left, 0.0]), + ([hw, hh, 0.0], [0.0, 0.0, 1.0], [u_right, 0.0]), + ([hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_right, 1.0]), + ]; + + let indices = Indices::U32(vec![0, 2, 1, 0, 3, 2]); + + let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); + let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); + let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_indices(Some(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + } +} + +impl From for Mesh { + fn from(rectangle: Rectangle) -> Self { + rectangle.mesh(false) + } +} + +impl From for Mesh { + fn from(polygon: RegularPolygon) -> Self { + let sides = polygon.sides; + + debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); + + let mut positions = Vec::with_capacity(sides); + let mut normals = Vec::with_capacity(sides); + let mut uvs = Vec::with_capacity(sides); + + let step = std::f32::consts::TAU / sides as f32; + for i in 0..sides { + let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step; + let (sin, cos) = theta.sin_cos(); + + positions.push([ + cos * polygon.circumcircle.radius, + sin * polygon.circumcircle.radius, + 0.0, + ]); + normals.push([0.0, 0.0, 1.0]); + uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); + } + + let mut indices = Vec::with_capacity((sides - 2) * 3); + for i in 1..(sides as u32 - 1) { + indices.extend_from_slice(&[0, i + 1, i]); + } + + Mesh::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(Indices::U32(indices))) + } +} + +pub trait MeshableCircle { + fn mesh(&self, vertices: usize) -> Mesh; +} + +impl MeshableCircle for Circle { + fn mesh(&self, vertices: usize) -> Mesh { + Mesh::from(RegularPolygon::new(self.radius, vertices)) + } +} + +impl From for Mesh { + fn from(circle: Circle) -> Self { + circle.mesh(64) + } +} diff --git a/examples/2d/2d_shapes.rs b/examples/2d/2d_shapes.rs index 7fa168fe697c1..1191f92a7f6d8 100644 --- a/examples/2d/2d_shapes.rs +++ b/examples/2d/2d_shapes.rs @@ -16,15 +16,15 @@ fn setup( ) { commands.spawn(Camera2dBundle::default()); - // Circle + // Circle mesh commands.spawn(MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::new(50.).into()).into(), + mesh: meshes.add(primitives::Circle { radius: 50. }.into()).into(), material: materials.add(ColorMaterial::from(Color::PURPLE)), transform: Transform::from_translation(Vec3::new(-150., 0., 0.)), ..default() }); - // Rectangle + // Rectangle sprite commands.spawn(SpriteBundle { sprite: Sprite { color: Color::rgb(0.25, 0.25, 0.75), @@ -35,19 +35,21 @@ fn setup( ..default() }); - // Quad + // Rectangle mesh commands.spawn(MaterialMesh2dBundle { mesh: meshes - .add(shape::Quad::new(Vec2::new(50., 100.)).into()) + .add(primitives::Rectangle::new(50., 100.).into()) .into(), material: materials.add(ColorMaterial::from(Color::LIME_GREEN)), transform: Transform::from_translation(Vec3::new(50., 0., 0.)), ..default() }); - // Hexagon + // Hexagon mesh commands.spawn(MaterialMesh2dBundle { - mesh: meshes.add(shape::RegularPolygon::new(50., 6).into()).into(), + mesh: meshes + .add(primitives::RegularPolygon::new(50., 6).into()) + .into(), material: materials.add(ColorMaterial::from(Color::TURQUOISE)), transform: Transform::from_translation(Vec3::new(150., 0., 0.)), ..default() From 9eb26425d6eced94d4c752d3932f6599ac6cdbec Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 3 Dec 2023 18:33:38 +0200 Subject: [PATCH 02/36] Implement `From` for `Mesh` --- .../src/mesh/primitive_meshes/mod.rs | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index ceb5e70ef157f..05e175ecbd160 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,5 +1,8 @@ use super::{Indices, Mesh, PrimitiveTopology}; -use bevy_math::primitives::{Circle, Cuboid, Rectangle, RegularPolygon}; +use bevy_math::{ + primitives::{Circle, Cuboid, Rectangle, RegularPolygon, Triangle2d, WindingOrder}, + Vec2, +}; impl From for Mesh { fn from(cuboid: Cuboid) -> Self { @@ -148,3 +151,32 @@ impl From for Mesh { circle.mesh(64) } } + +impl From for Mesh { + fn from(triangle: Triangle2d) -> Self { + let [a, b, c] = triangle.vertices; + let max = a.min(b).min(c).abs().max(a.max(b).max(c)) * Vec2::new(1.0, -1.0); + let [norm_a, norm_b, norm_c] = [(a) / max, (b) / max, (c) / max]; + let vertices = [ + (a.extend(0.0), [0.0, 0.0, 1.0], norm_a / 2.0 + 0.5), + (b.extend(0.0), [0.0, 0.0, 1.0], norm_b / 2.0 + 0.5), + (c.extend(0.0), [0.0, 0.0, 1.0], norm_c / 2.0 + 0.5), + ]; + + let indices = if triangle.winding_order() == WindingOrder::CounterClockwise { + Indices::U32(vec![0, 1, 2]) + } else { + Indices::U32(vec![0, 2, 1]) + }; + + let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); + let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); + let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_indices(Some(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + } +} From 5fc1dd99f09a526e18b5236c90c42ac65584945c Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Tue, 5 Dec 2023 13:57:17 +0200 Subject: [PATCH 03/36] Split meshes into many modules, use builder pattern and add spheres --- crates/bevy_render/src/lib.rs | 6 +- .../src/mesh/primitive_meshes/circle.rs | 48 ++++ .../src/mesh/primitive_meshes/cuboid.rs | 72 ++++++ .../src/mesh/primitive_meshes/mod.rs | 190 ++------------- .../src/mesh/primitive_meshes/rectangle.rs | 64 +++++ .../mesh/primitive_meshes/regular_polygon.rs | 49 ++++ .../src/mesh/primitive_meshes/sphere.rs | 222 ++++++++++++++++++ .../src/mesh/primitive_meshes/triangle.rs | 43 ++++ 8 files changed, 517 insertions(+), 177 deletions(-) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/circle.rs create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/sphere.rs create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/triangle.rs diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 3de2e7467296f..190b6f6e09792 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -29,7 +29,11 @@ pub mod prelude { pub use crate::{ camera::{Camera, OrthographicProjection, PerspectiveProjection, Projection}, color::Color, - mesh::{morph::MorphWeights, shape, Mesh}, + mesh::{ + morph::MorphWeights, + primitive_meshes::{builders::*, Meshable}, + shape, Mesh, + }, render_resource::Shader, spatial_bundle::SpatialBundle, texture::{Image, ImagePlugin}, diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs new file mode 100644 index 0000000000000..e08fba5bbc4da --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs @@ -0,0 +1,48 @@ +use crate::mesh::Mesh; + +use super::Meshable; +use bevy_math::primitives::{Circle, RegularPolygon}; + +#[derive(Debug)] +pub struct CircleBuilder { + /// The circle shape. + pub circle: Circle, + /// The number of vertices used for the circle mesh. + pub vertices: usize, +} + +impl CircleBuilder { + pub fn build(&self) -> Mesh { + RegularPolygon::new(self.circle.radius, self.vertices).mesh() + } + + /// Sets the number of vertices used for the circle mesh. + #[doc(alias = "segments")] + pub fn vertices(mut self, vertices: usize) -> Self { + self.vertices = vertices; + self + } +} + +impl Meshable for Circle { + type Output = CircleBuilder; + + fn mesh(&self) -> Self::Output { + CircleBuilder { + circle: *self, + vertices: 64, + } + } +} + +impl From for Mesh { + fn from(circle: Circle) -> Self { + circle.mesh().build() + } +} + +impl From for Mesh { + fn from(circle: CircleBuilder) -> Self { + circle.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs new file mode 100644 index 0000000000000..df9df82969071 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs @@ -0,0 +1,72 @@ +use super::{Mesh, Meshable}; +use crate::mesh::Indices; +use bevy_math::primitives::Cuboid; +use wgpu::PrimitiveTopology; + +impl Meshable for Cuboid { + type Output = Mesh; + + fn mesh(&self) -> Mesh { + let min = -self.half_extents; + let max = self.half_extents; + + // suppose Y-up right hand, and camera look from +z to -z + let vertices = &[ + // Front + ([min.x, min.y, max.z], [0.0, 0.0, 1.0], [0.0, 0.0]), + ([max.x, min.y, max.z], [0.0, 0.0, 1.0], [1.0, 0.0]), + ([max.x, max.y, max.z], [0.0, 0.0, 1.0], [1.0, 1.0]), + ([min.x, max.y, max.z], [0.0, 0.0, 1.0], [0.0, 1.0]), + // Back + ([min.x, max.y, min.z], [0.0, 0.0, -1.0], [1.0, 0.0]), + ([max.x, max.y, min.z], [0.0, 0.0, -1.0], [0.0, 0.0]), + ([max.x, min.y, min.z], [0.0, 0.0, -1.0], [0.0, 1.0]), + ([min.x, min.y, min.z], [0.0, 0.0, -1.0], [1.0, 1.0]), + // Right + ([max.x, min.y, min.z], [1.0, 0.0, 0.0], [0.0, 0.0]), + ([max.x, max.y, min.z], [1.0, 0.0, 0.0], [1.0, 0.0]), + ([max.x, max.y, max.z], [1.0, 0.0, 0.0], [1.0, 1.0]), + ([max.x, min.y, max.z], [1.0, 0.0, 0.0], [0.0, 1.0]), + // Left + ([min.x, min.y, max.z], [-1.0, 0.0, 0.0], [1.0, 0.0]), + ([min.x, max.y, max.z], [-1.0, 0.0, 0.0], [0.0, 0.0]), + ([min.x, max.y, min.z], [-1.0, 0.0, 0.0], [0.0, 1.0]), + ([min.x, min.y, min.z], [-1.0, 0.0, 0.0], [1.0, 1.0]), + // Top + ([max.x, max.y, min.z], [0.0, 1.0, 0.0], [1.0, 0.0]), + ([min.x, max.y, min.z], [0.0, 1.0, 0.0], [0.0, 0.0]), + ([min.x, max.y, max.z], [0.0, 1.0, 0.0], [0.0, 1.0]), + ([max.x, max.y, max.z], [0.0, 1.0, 0.0], [1.0, 1.0]), + // Bottom + ([max.x, min.y, max.z], [0.0, -1.0, 0.0], [0.0, 0.0]), + ([min.x, min.y, max.z], [0.0, -1.0, 0.0], [1.0, 0.0]), + ([min.x, min.y, min.z], [0.0, -1.0, 0.0], [1.0, 1.0]), + ([max.x, min.y, min.z], [0.0, -1.0, 0.0], [0.0, 1.0]), + ]; + + let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); + let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); + let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); + + let indices = Indices::U32(vec![ + 0, 1, 2, 2, 3, 0, // front + 4, 5, 6, 6, 7, 4, // back + 8, 9, 10, 10, 11, 8, // right + 12, 13, 14, 14, 15, 12, // left + 16, 17, 18, 18, 19, 16, // top + 20, 21, 22, 22, 23, 20, // bottom + ]); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(indices)) + } +} + +impl From for Mesh { + fn from(cuboid: Cuboid) -> Self { + cuboid.mesh() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 05e175ecbd160..504fabb28d325 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,182 +1,20 @@ -use super::{Indices, Mesh, PrimitiveTopology}; -use bevy_math::{ - primitives::{Circle, Cuboid, Rectangle, RegularPolygon, Triangle2d, WindingOrder}, - Vec2, -}; +mod circle; +mod cuboid; +mod rectangle; +mod regular_polygon; +mod sphere; +mod triangle; -impl From for Mesh { - fn from(cuboid: Cuboid) -> Self { - let min = -cuboid.half_extents; - let max = cuboid.half_extents; - - // suppose Y-up right hand, and camera look from +z to -z - let vertices = &[ - // Front - ([min.x, min.y, max.z], [0.0, 0.0, 1.0], [0.0, 0.0]), - ([max.x, min.y, max.z], [0.0, 0.0, 1.0], [1.0, 0.0]), - ([max.x, max.y, max.z], [0.0, 0.0, 1.0], [1.0, 1.0]), - ([min.x, max.y, max.z], [0.0, 0.0, 1.0], [0.0, 1.0]), - // Back - ([min.x, max.y, min.z], [0.0, 0.0, -1.0], [1.0, 0.0]), - ([max.x, max.y, min.z], [0.0, 0.0, -1.0], [0.0, 0.0]), - ([max.x, min.y, min.z], [0.0, 0.0, -1.0], [0.0, 1.0]), - ([min.x, min.y, min.z], [0.0, 0.0, -1.0], [1.0, 1.0]), - // Right - ([max.x, min.y, min.z], [1.0, 0.0, 0.0], [0.0, 0.0]), - ([max.x, max.y, min.z], [1.0, 0.0, 0.0], [1.0, 0.0]), - ([max.x, max.y, max.z], [1.0, 0.0, 0.0], [1.0, 1.0]), - ([max.x, min.y, max.z], [1.0, 0.0, 0.0], [0.0, 1.0]), - // Left - ([min.x, min.y, max.z], [-1.0, 0.0, 0.0], [1.0, 0.0]), - ([min.x, max.y, max.z], [-1.0, 0.0, 0.0], [0.0, 0.0]), - ([min.x, max.y, min.z], [-1.0, 0.0, 0.0], [0.0, 1.0]), - ([min.x, min.y, min.z], [-1.0, 0.0, 0.0], [1.0, 1.0]), - // Top - ([max.x, max.y, min.z], [0.0, 1.0, 0.0], [1.0, 0.0]), - ([min.x, max.y, min.z], [0.0, 1.0, 0.0], [0.0, 0.0]), - ([min.x, max.y, max.z], [0.0, 1.0, 0.0], [0.0, 1.0]), - ([max.x, max.y, max.z], [0.0, 1.0, 0.0], [1.0, 1.0]), - // Bottom - ([max.x, min.y, max.z], [0.0, -1.0, 0.0], [0.0, 0.0]), - ([min.x, min.y, max.z], [0.0, -1.0, 0.0], [1.0, 0.0]), - ([min.x, min.y, min.z], [0.0, -1.0, 0.0], [1.0, 1.0]), - ([max.x, min.y, min.z], [0.0, -1.0, 0.0], [0.0, 1.0]), - ]; - - let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); - let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); - let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); - - let indices = Indices::U32(vec![ - 0, 1, 2, 2, 3, 0, // front - 4, 5, 6, 6, 7, 4, // back - 8, 9, 10, 10, 11, 8, // right - 12, 13, 14, 14, 15, 12, // left - 16, 17, 18, 18, 19, 16, // top - 20, 21, 22, 22, 23, 20, // bottom - ]); - - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(indices)) - } -} - -pub trait MeshableRectangle { - fn mesh(&self, flip: bool) -> Mesh; -} - -impl MeshableRectangle for Rectangle { - fn mesh(&self, flip: bool) -> Mesh { - let (u_left, u_right) = if flip { (1.0, 0.0) } else { (0.0, 1.0) }; - let [hw, hh] = [self.half_width, self.half_height]; - let vertices = [ - ([-hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_left, 1.0]), - ([-hw, hh, 0.0], [0.0, 0.0, 1.0], [u_left, 0.0]), - ([hw, hh, 0.0], [0.0, 0.0, 1.0], [u_right, 0.0]), - ([hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_right, 1.0]), - ]; - - let indices = Indices::U32(vec![0, 2, 1, 0, 3, 2]); - - let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); - let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); - let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); - - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - } -} - -impl From for Mesh { - fn from(rectangle: Rectangle) -> Self { - rectangle.mesh(false) - } -} - -impl From for Mesh { - fn from(polygon: RegularPolygon) -> Self { - let sides = polygon.sides; - - debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); - - let mut positions = Vec::with_capacity(sides); - let mut normals = Vec::with_capacity(sides); - let mut uvs = Vec::with_capacity(sides); - - let step = std::f32::consts::TAU / sides as f32; - for i in 0..sides { - let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step; - let (sin, cos) = theta.sin_cos(); - - positions.push([ - cos * polygon.circumcircle.radius, - sin * polygon.circumcircle.radius, - 0.0, - ]); - normals.push([0.0, 0.0, 1.0]); - uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); - } - - let mut indices = Vec::with_capacity((sides - 2) * 3); - for i in 1..(sides as u32 - 1) { - indices.extend_from_slice(&[0, i + 1, i]); - } - - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(Indices::U32(indices))) - } +pub(crate) mod builders { + pub use super::circle::CircleBuilder; + pub use super::rectangle::RectangleBuilder; + pub use super::sphere::SphereBuilder; } -pub trait MeshableCircle { - fn mesh(&self, vertices: usize) -> Mesh; -} - -impl MeshableCircle for Circle { - fn mesh(&self, vertices: usize) -> Mesh { - Mesh::from(RegularPolygon::new(self.radius, vertices)) - } -} - -impl From for Mesh { - fn from(circle: Circle) -> Self { - circle.mesh(64) - } -} - -impl From for Mesh { - fn from(triangle: Triangle2d) -> Self { - let [a, b, c] = triangle.vertices; - let max = a.min(b).min(c).abs().max(a.max(b).max(c)) * Vec2::new(1.0, -1.0); - let [norm_a, norm_b, norm_c] = [(a) / max, (b) / max, (c) / max]; - let vertices = [ - (a.extend(0.0), [0.0, 0.0, 1.0], norm_a / 2.0 + 0.5), - (b.extend(0.0), [0.0, 0.0, 1.0], norm_b / 2.0 + 0.5), - (c.extend(0.0), [0.0, 0.0, 1.0], norm_c / 2.0 + 0.5), - ]; - - let indices = if triangle.winding_order() == WindingOrder::CounterClockwise { - Indices::U32(vec![0, 1, 2]) - } else { - Indices::U32(vec![0, 2, 1]) - }; +use super::Mesh; - let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); - let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); - let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); +pub trait Meshable { + type Output; - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - } + fn mesh(&self) -> Self::Output; } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs new file mode 100644 index 0000000000000..1b78445f84a1f --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs @@ -0,0 +1,64 @@ +use super::{Mesh, Meshable}; +use crate::mesh::Indices; +use bevy_math::primitives::Rectangle; +use wgpu::PrimitiveTopology; + +#[derive(Debug)] +pub struct RectangleBuilder { + pub rectangle: Rectangle, + pub flipped: bool, +} + +impl RectangleBuilder { + fn build(&self) -> Mesh { + let (u_left, u_right) = if self.flipped { (1.0, 0.0) } else { (0.0, 1.0) }; + let [hw, hh] = [self.rectangle.half_width, self.rectangle.half_height]; + let vertices = [ + ([-hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_left, 1.0]), + ([-hw, hh, 0.0], [0.0, 0.0, 1.0], [u_left, 0.0]), + ([hw, hh, 0.0], [0.0, 0.0, 1.0], [u_right, 0.0]), + ([hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_right, 1.0]), + ]; + + let indices = Indices::U32(vec![0, 2, 1, 0, 3, 2]); + + let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); + let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); + let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_indices(Some(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + } + + /// Flips the UVs of the rectangle mesh. + pub fn flipped(mut self) -> Self { + self.flipped = !self.flipped; + self + } +} + +impl Meshable for Rectangle { + type Output = RectangleBuilder; + + fn mesh(&self) -> Self::Output { + RectangleBuilder { + rectangle: *self, + flipped: false, + } + } +} + +impl From for Mesh { + fn from(rectangle: Rectangle) -> Self { + rectangle.mesh().build() + } +} + +impl From for Mesh { + fn from(rectangle: RectangleBuilder) -> Self { + rectangle.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs new file mode 100644 index 0000000000000..e57c3bf261a35 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs @@ -0,0 +1,49 @@ +use super::{Mesh, Meshable}; +use crate::mesh::Indices; +use bevy_math::primitives::RegularPolygon; +use wgpu::PrimitiveTopology; + +impl Meshable for RegularPolygon { + type Output = Mesh; + + fn mesh(&self) -> Mesh { + let sides = self.sides; + + debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); + + let mut positions = Vec::with_capacity(sides); + let mut normals = Vec::with_capacity(sides); + let mut uvs = Vec::with_capacity(sides); + + let step = std::f32::consts::TAU / sides as f32; + for i in 0..sides { + let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step; + let (sin, cos) = theta.sin_cos(); + + positions.push([ + cos * self.circumcircle.radius, + sin * self.circumcircle.radius, + 0.0, + ]); + normals.push([0.0, 0.0, 1.0]); + uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); + } + + let mut indices = Vec::with_capacity((sides - 2) * 3); + for i in 1..(sides as u32 - 1) { + indices.extend_from_slice(&[0, i + 1, i]); + } + + Mesh::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(Indices::U32(indices))) + } +} + +impl From for Mesh { + fn from(polygon: RegularPolygon) -> Self { + polygon.mesh() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs new file mode 100644 index 0000000000000..1c5e8f83687f5 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs @@ -0,0 +1,222 @@ +use std::f32::consts::PI; + +use super::{Mesh, Meshable}; +use crate::mesh::Indices; +use bevy_math::primitives::Sphere; +use hexasphere::shapes::IcoSphere; +use thiserror::Error; +use wgpu::PrimitiveTopology; + +#[derive(Debug, Clone, Error)] +pub enum IcosphereError { + #[error("Cannot create an icosphere of {subdivisions} subdivisions due to there being too many vertices being generated: {number_of_resulting_points}. (Limited to 65535 vertices or 79 subdivisions)")] + TooManyVertices { + subdivisions: usize, + number_of_resulting_points: usize, + }, +} + +#[derive(Debug)] +pub enum SphereKind { + Ico { + /// The number of subdivisions applied. + subdivisions: usize, + }, + Uv { + /// Longitudinal sectors + sectors: usize, + /// Latitudinal stacks + stacks: usize, + }, +} + +#[derive(Debug)] +pub struct SphereBuilder { + pub sphere: Sphere, + pub kind: SphereKind, +} + +impl SphereBuilder { + /// Builds a sphere mesh according to the configuration in `self`. + /// + /// # Panics + /// + /// Panics if the sphere is a `SphereKind::Ico` with a subdivision count + /// that is greater than or equal to `80` because there will be too many vertices. + fn build(&self) -> Mesh { + match self.kind { + SphereKind::Ico { subdivisions } => self.ico(subdivisions).unwrap(), + SphereKind::Uv { sectors, stacks } => self.uv(sectors, stacks), + } + } + + /// Sets the [`SphereKind`] that will be used for building the mesh. + pub fn set_kind(mut self, kind: SphereKind) -> Self { + self.kind = kind; + self + } + + /// Create an icosphere mesh with the given number of subdivisions. + pub fn ico(&self, subdivisions: usize) -> Result { + if subdivisions >= 80 { + /* + Number of triangles: + N = 20 + + Number of edges: + E = 30 + + Number of vertices: + V = 12 + + Number of points within a triangle (triangular numbers): + inner(s) = (s^2 + s) / 2 + + Number of points on an edge: + edges(s) = s + + Add up all vertices on the surface: + vertices(s) = edges(s) * E + inner(s - 1) * N + V + + Expand and simplify. Notice that the triangular number formula has roots at -1, and 0, so translating it one to the right fixes it. + subdivisions(s) = 30s + 20((s^2 - 2s + 1 + s - 1) / 2) + 12 + subdivisions(s) = 30s + 10s^2 - 10s + 12 + subdivisions(s) = 10(s^2 + 2s) + 12 + + Factor an (s + 1) term to simplify in terms of calculation + subdivisions(s) = 10(s + 1)^2 + 12 - 10 + resulting_vertices(s) = 10(s + 1)^2 + 2 + */ + let temp = subdivisions + 1; + let number_of_resulting_points = temp * temp * 10 + 2; + return Err(IcosphereError::TooManyVertices { + subdivisions, + number_of_resulting_points, + }); + } + let generated = IcoSphere::new(subdivisions, |point| { + let inclination = point.y.acos(); + let azimuth = point.z.atan2(point.x); + + let norm_inclination = inclination / std::f32::consts::PI; + let norm_azimuth = 0.5 - (azimuth / std::f32::consts::TAU); + + [norm_azimuth, norm_inclination] + }); + + let raw_points = generated.raw_points(); + + let points = raw_points + .iter() + .map(|&p| (p * self.sphere.radius).into()) + .collect::>(); + + let normals = raw_points + .iter() + .copied() + .map(Into::into) + .collect::>(); + + let uvs = generated.raw_data().to_owned(); + + let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20); + + for i in 0..20 { + generated.get_indices(i, &mut indices); + } + + let indices = Indices::U32(indices); + + 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)) + } + + /// Creates a UV sphere mesh with the given number of + /// longitudinal sectors and latitudinal stacks. + pub fn uv(&self, sectors: usize, stacks: usize) -> Mesh { + // Largely inspired from http://www.songho.ca/opengl/gl_sphere.html + + let sectors_f32 = sectors as f32; + let stacks_f32 = stacks as f32; + let length_inv = 1. / self.sphere.radius; + let sector_step = 2. * PI / sectors_f32; + let stack_step = PI / stacks_f32; + + let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(stacks * sectors); + let mut normals: Vec<[f32; 3]> = Vec::with_capacity(stacks * sectors); + let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(stacks * sectors); + let mut indices: Vec = Vec::with_capacity(stacks * sectors * 2 * 3); + + for i in 0..stacks + 1 { + let stack_angle = PI / 2. - (i as f32) * stack_step; + let xy = self.sphere.radius * stack_angle.cos(); + let z = self.sphere.radius * stack_angle.sin(); + + for j in 0..sectors + 1 { + let sector_angle = (j as f32) * sector_step; + let x = xy * sector_angle.cos(); + let y = xy * sector_angle.sin(); + + vertices.push([x, y, z]); + normals.push([x * length_inv, y * length_inv, z * length_inv]); + uvs.push([(j as f32) / sectors_f32, (i as f32) / stacks_f32]); + } + } + + // indices + // k1--k1+1 + // | / | + // | / | + // k2--k2+1 + for i in 0..stacks { + let mut k1 = i * (sectors + 1); + let mut k2 = k1 + sectors + 1; + for _j in 0..sectors { + if i != 0 { + indices.push(k1 as u32); + indices.push(k2 as u32); + indices.push((k1 + 1) as u32); + } + if i != stacks - 1 { + indices.push((k1 + 1) as u32); + indices.push(k2 as u32); + indices.push((k2 + 1) as u32); + } + k1 += 1; + k2 += 1; + } + } + + Mesh::new(PrimitiveTopology::TriangleList) + .with_indices(Some(Indices::U32(indices))) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + } +} + +impl Meshable for Sphere { + type Output = SphereBuilder; + + fn mesh(&self) -> Self::Output { + SphereBuilder { + sphere: *self, + kind: SphereKind::Ico { subdivisions: 5 }, + } + } +} + +impl From for Mesh { + fn from(sphere: Sphere) -> Self { + sphere.mesh().build() + } +} + +impl From for Mesh { + fn from(sphere: SphereBuilder) -> Self { + sphere.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs new file mode 100644 index 0000000000000..2308d7cf71bf6 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs @@ -0,0 +1,43 @@ +use super::{Mesh, Meshable}; +use crate::mesh::Indices; +use bevy_math::{ + primitives::{Triangle2d, WindingOrder}, + Vec2, +}; +use wgpu::PrimitiveTopology; + +impl Meshable for Triangle2d { + type Output = Mesh; + + fn mesh(&self) -> Mesh { + let [a, b, c] = self.vertices; + let max = a.min(b).min(c).abs().max(a.max(b).max(c)) * Vec2::new(1.0, -1.0); + let [norm_a, norm_b, norm_c] = [(a) / max, (b) / max, (c) / max]; + let vertices = [ + (a.extend(0.0), [0.0, 0.0, 1.0], norm_a / 2.0 + 0.5), + (b.extend(0.0), [0.0, 0.0, 1.0], norm_b / 2.0 + 0.5), + (c.extend(0.0), [0.0, 0.0, 1.0], norm_c / 2.0 + 0.5), + ]; + + let indices = if self.winding_order() == WindingOrder::CounterClockwise { + Indices::U32(vec![0, 1, 2]) + } else { + Indices::U32(vec![0, 2, 1]) + }; + + let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); + let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); + let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_indices(Some(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + } +} +impl From for Mesh { + fn from(triangle: Triangle2d) -> Self { + triangle.mesh() + } +} From f47cea1f35110ab3567ca8a4c3ed2b5778698ce1 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Wed, 6 Dec 2023 13:12:52 +0200 Subject: [PATCH 04/36] Add torus mesh --- .../src/mesh/primitive_meshes/mod.rs | 2 + .../src/mesh/primitive_meshes/torus.rs | 118 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/torus.rs diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 504fabb28d325..a25ba1502036a 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -3,12 +3,14 @@ mod cuboid; mod rectangle; mod regular_polygon; mod sphere; +mod torus; mod triangle; pub(crate) mod builders { pub use super::circle::CircleBuilder; pub use super::rectangle::RectangleBuilder; pub use super::sphere::SphereBuilder; + pub use super::torus::TorusBuilder; } use super::Mesh; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs new file mode 100644 index 0000000000000..bc42f900d6925 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs @@ -0,0 +1,118 @@ +use super::Meshable; +use crate::mesh::{Indices, Mesh}; +use bevy_math::{primitives::Torus, Vec3}; +use wgpu::PrimitiveTopology; + +pub struct TorusBuilder { + pub torus: Torus, + pub subdivisions_segments: usize, + pub subdivisions_sides: usize, +} + +impl TorusBuilder { + pub fn build(&self) -> Mesh { + // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html + // (source code at https://github.com/SEilers/Apparat) + + let n_vertices = (self.subdivisions_segments + 1) * (self.subdivisions_sides + 1); + let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n_vertices); + 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.subdivisions_segments as f32; + let side_stride = 2.0 * std::f32::consts::PI / self.subdivisions_sides as f32; + + for segment in 0..=self.subdivisions_segments { + let theta = segment_stride * segment as f32; + + for side in 0..=self.subdivisions_sides { + let phi = side_stride * side as f32; + + let position = Vec3::new( + theta.cos() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), + self.torus.minor_radius * phi.sin(), + theta.sin() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), + ); + + let center = Vec3::new( + self.torus.major_radius * theta.cos(), + 0., + self.torus.major_radius * theta.sin(), + ); + let normal = (position - center).normalize(); + + positions.push(position.into()); + normals.push(normal.into()); + uvs.push([ + segment as f32 / self.subdivisions_segments as f32, + side as f32 / self.subdivisions_sides as f32, + ]); + } + } + + let n_faces = (self.subdivisions_segments) * (self.subdivisions_sides); + let n_triangles = n_faces * 2; + let n_indices = n_triangles * 3; + + let mut indices: Vec = Vec::with_capacity(n_indices); + + let n_vertices_per_row = self.subdivisions_sides + 1; + for segment in 0..self.subdivisions_segments { + for side in 0..self.subdivisions_sides { + let lt = side + segment * n_vertices_per_row; + let rt = (side + 1) + segment * n_vertices_per_row; + + let lb = side + (segment + 1) * n_vertices_per_row; + let rb = (side + 1) + (segment + 1) * n_vertices_per_row; + + indices.push(lt as u32); + indices.push(rt as u32); + indices.push(lb as u32); + + indices.push(rt as u32); + indices.push(rb as u32); + indices.push(lb as u32); + } + } + + 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) + } + + pub fn subdivisions_segments(mut self, subdivisions: usize) -> Self { + self.subdivisions_segments = subdivisions; + self + } + + pub fn subdivisions_sides(mut self, subdivisions: usize) -> Self { + self.subdivisions_sides = subdivisions; + self + } +} + +impl Meshable for Torus { + type Output = TorusBuilder; + + fn mesh(&self) -> Self::Output { + TorusBuilder { + torus: *self, + subdivisions_segments: 32, + subdivisions_sides: 24, + } + } +} + +impl From for Mesh { + fn from(sphere: Torus) -> Self { + sphere.mesh().build() + } +} + +impl From for Mesh { + fn from(sphere: TorusBuilder) -> Self { + sphere.build() + } +} From d51d769352fd017264136288b45eda228b888f2a Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Wed, 6 Dec 2023 19:51:34 +0200 Subject: [PATCH 05/36] Add WIP cone mesh --- .../src/mesh/primitive_meshes/cone.rs | 103 ++++++++++++++++++ .../src/mesh/primitive_meshes/mod.rs | 2 + 2 files changed, 105 insertions(+) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/cone.rs diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs new file mode 100644 index 0000000000000..a614905574223 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -0,0 +1,103 @@ +use super::Meshable; +use crate::mesh::{Indices, Mesh}; +use bevy_math::primitives::Cone; +use wgpu::PrimitiveTopology; + +pub struct ConeBuilder { + pub cone: Cone, + pub resolution: u32, +} + +impl ConeBuilder { + pub fn build(&self) -> Mesh { + let seg = 1; + let num_rings = seg + 1; + let num_vertices = self.resolution * 2 + num_rings * (self.resolution + 1); + let num_faces = self.resolution * (num_rings - 2); + let num_indices = (2 * num_faces + 2 * (self.resolution - 1) * 2) * 3; + + let mut positions = Vec::with_capacity(num_vertices as usize); + let mut normals = Vec::with_capacity(num_vertices as usize); + let mut uvs = Vec::with_capacity(num_vertices as usize); + let mut indices = Vec::with_capacity(num_indices as usize); + + let step_theta = std::f32::consts::TAU / self.resolution as f32; + + // caps + + positions.push([0.0, self.cone.height, 0.0]); + normals.push([0.0, 0.0, 0.0]); + uvs.push([0.0, 0.0]); + + let radius = self.cone.radius; + + for segment in 0..=self.resolution { + let theta = segment as f32 * step_theta; + let (sin, cos) = theta.sin_cos(); + + positions.push([radius * cos, -self.cone.height / 2.0, radius * sin]); + normals.push([cos, 0., sin]); + uvs.push([segment as f32 / self.resolution as f32, 0.0]); + } + + for j in 0..self.resolution { + indices.extend_from_slice(&[0, j + 1, j]); + } + + indices.extend(&[0, positions.len() as u32 - 1, positions.len() as u32 - 2]); + + // base + + let offset = positions.len() as u32; + let y = self.cone.height / -2.; + + let radius = self.cone.radius; + + for i in 0..self.resolution { + let theta = i as f32 * step_theta; + let (sin, cos) = theta.sin_cos(); + + positions.push([cos * radius, y, sin * radius]); + normals.push([0.0, -1.0, 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, offset + i + 1]); + } + + 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) + } + + pub fn segments(mut self, segments: usize) -> Self { + self.resolution = segments as u32; + self + } +} + +impl Meshable for Cone { + type Output = ConeBuilder; + + fn mesh(&self) -> Self::Output { + ConeBuilder { + cone: *self, + resolution: 32, + } + } +} + +impl From for Mesh { + fn from(sphere: Cone) -> Self { + sphere.mesh().build() + } +} + +impl From for Mesh { + fn from(sphere: ConeBuilder) -> Self { + sphere.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index a25ba1502036a..572367b31bce3 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,4 +1,5 @@ mod circle; +mod cone; mod cuboid; mod rectangle; mod regular_polygon; @@ -8,6 +9,7 @@ mod triangle; pub(crate) mod builders { pub use super::circle::CircleBuilder; + pub use super::cone::ConeBuilder; pub use super::rectangle::RectangleBuilder; pub use super::sphere::SphereBuilder; pub use super::torus::TorusBuilder; From 7e3969820a27bcc52faba289649743d48f905372 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Wed, 6 Dec 2023 19:52:01 +0200 Subject: [PATCH 06/36] Impl `Default` for some primitives --- crates/bevy_math/src/primitives/dim2.rs | 49 ++++++++++++++++++ crates/bevy_math/src/primitives/dim3.rs | 68 +++++++++++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index 35e5363dc6acd..a707c1498c8b5 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -34,6 +34,12 @@ pub struct Circle { } impl Primitive2d for Circle {} +impl Default for Circle { + fn default() -> Self { + Self { radius: 0.5 } + } +} + /// An ellipse primitive #[derive(Clone, Copy, Debug)] pub struct Ellipse { @@ -44,6 +50,15 @@ pub struct Ellipse { } impl Primitive2d for Ellipse {} +impl Default for Ellipse { + fn default() -> Self { + Self { + half_width: 0.5, + half_height: 0.5, + } + } +} + impl Ellipse { /// Create a new `Ellipse` from a "width" and a "height" pub fn new(width: f32, height: f32) -> Self { @@ -63,6 +78,14 @@ pub struct Plane2d { } impl Primitive2d for Plane2d {} +impl Default for Plane2d { + fn default() -> Self { + Self { + normal: Direction2d::from_normalized(Vec2::Y), + } + } +} + /// An infinite line along a direction in 2D space. /// /// For a finite line: [`Segment2d`] @@ -181,6 +204,14 @@ pub struct Triangle2d { } impl Primitive2d for Triangle2d {} +impl Default for Triangle2d { + fn default() -> Self { + Self { + vertices: [Vec2::Y * 0.5, Vec2::new(-0.5, -0.5), Vec2::new(0.5, -0.5)], + } + } +} + impl Triangle2d { /// Create a new `Triangle2d` from points `a`, `b`, and `c` pub fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { @@ -221,6 +252,15 @@ pub struct Rectangle { } impl Primitive2d for Rectangle {} +impl Default for Rectangle { + fn default() -> Self { + Self { + half_width: 0.5, + half_height: 0.5, + } + } +} + impl Rectangle { /// Create a rectangle from a full width and height pub fn new(width: f32, height: f32) -> Self { @@ -301,6 +341,15 @@ pub struct RegularPolygon { } impl Primitive2d for RegularPolygon {} +impl Default for RegularPolygon { + fn default() -> Self { + Self { + circumcircle: Circle { radius: 0.5 }, + sides: 6, + } + } +} + impl RegularPolygon { /// Create a new `RegularPolygon` /// from the radius of the circumcircle and number of sides diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 891f51558fd3b..9242bf41359ed 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -34,6 +34,12 @@ pub struct Sphere { } impl Primitive3d for Sphere {} +impl Default for Sphere { + fn default() -> Self { + Self { radius: 0.5 } + } +} + /// An unbounded plane in 3D space. It forms a separating surface through the origin, /// stretching infinitely far #[derive(Clone, Copy, Debug)] @@ -43,6 +49,14 @@ pub struct Plane3d { } impl Primitive3d for Plane3d {} +impl Default for Plane3d { + fn default() -> Self { + Self { + normal: Direction3d::from_normalized(Vec3::Y), + } + } +} + /// An infinite line along a direction in 3D space. /// /// For a finite line: [`Segment3d`] @@ -160,6 +174,14 @@ pub struct Cuboid { } impl Primitive3d for Cuboid {} +impl Default for Cuboid { + fn default() -> Self { + Self { + half_extents: Vec3::splat(0.5), + } + } +} + impl Cuboid { /// Create a cuboid from a full x, y, and z length pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self { @@ -184,6 +206,15 @@ pub struct Cylinder { } impl Primitive3d for Cylinder {} +impl Default for Cylinder { + fn default() -> Self { + Self { + radius: 0.5, + half_height: 0.5, + } + } +} + impl Cylinder { /// Create a cylinder from a radius and full height pub fn new(radius: f32, height: f32) -> Self { @@ -206,6 +237,15 @@ pub struct Capsule { impl super::Primitive2d for Capsule {} impl Primitive3d for Capsule {} +impl Default for Capsule { + fn default() -> Self { + Self { + radius: 0.5, + half_length: 0.5, + } + } +} + impl Capsule { /// Create a new `Capsule` from a radius and length pub fn new(radius: f32, length: f32) -> Self { @@ -226,6 +266,15 @@ pub struct Cone { } impl Primitive3d for Cone {} +impl Default for Cone { + fn default() -> Self { + Self { + radius: 0.5, + height: 1.0, + } + } +} + /// A conical frustum primitive. /// A conical frustum can be created /// by slicing off a section of a cone. @@ -240,6 +289,16 @@ pub struct ConicalFrustum { } impl Primitive3d for ConicalFrustum {} +impl Default for ConicalFrustum { + fn default() -> Self { + Self { + radius_top: 0.1, + radius_bottom: 0.5, + height: 1.0, + } + } +} + /// The type of torus determined by the minor and major radii #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TorusKind { @@ -274,6 +333,15 @@ pub struct Torus { } impl Primitive3d for Torus {} +impl Default for Torus { + fn default() -> Self { + Self { + minor_radius: 0.5, + major_radius: 1.0, + } + } +} + impl Torus { /// Create a new `Torus` from an inner and outer radius. /// From c9ed807d5f6579f483b9e8c1644586351d41f859 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Wed, 6 Dec 2023 19:54:36 +0200 Subject: [PATCH 07/36] Use primitives in `3d_shapes` example --- examples/3d/3d_shapes.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 999d11a74c878..556717520b68f 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -34,13 +34,13 @@ fn setup( }); let shapes = [ - meshes.add(shape::Cube::default().into()), - meshes.add(shape::Box::default().into()), - meshes.add(shape::Capsule::default().into()), - meshes.add(shape::Torus::default().into()), - meshes.add(shape::Cylinder::default().into()), - meshes.add(shape::Icosphere::default().try_into().unwrap()), - meshes.add(shape::UVSphere::default().into()), + meshes.add(primitives::Cuboid::default().into()), + //meshes.add(primitives::Capsule::default().into()), + meshes.add(primitives::Torus::default().into()), + //meshes.add(primitives::Cylinder::default().into()), + meshes.add(primitives::Cone::default().into()), + meshes.add(primitives::Sphere { radius: 0.5 }.mesh().ico(6).unwrap()), + meshes.add(primitives::Sphere { radius: 0.5 }.mesh().uv(6, 6)), ]; let num_shapes = shapes.len(); From b4ab57599cf84ce14f0f21215bea1a79ac971087 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 00:00:15 +0200 Subject: [PATCH 08/36] Fix cone UVs --- .../src/mesh/primitive_meshes/cone.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index a614905574223..e8cc6a9fc2d7a 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -10,11 +10,8 @@ pub struct ConeBuilder { impl ConeBuilder { pub fn build(&self) -> Mesh { - let seg = 1; - let num_rings = seg + 1; - let num_vertices = self.resolution * 2 + num_rings * (self.resolution + 1); - let num_faces = self.resolution * (num_rings - 2); - let num_indices = (2 * num_faces + 2 * (self.resolution - 1) * 2) * 3; + let num_vertices = self.resolution * 2 + 1; + let num_indices = self.resolution * 3; let mut positions = Vec::with_capacity(num_vertices as usize); let mut normals = Vec::with_capacity(num_vertices as usize); @@ -23,11 +20,15 @@ impl ConeBuilder { let step_theta = std::f32::consts::TAU / self.resolution as f32; - // caps + // tip - positions.push([0.0, self.cone.height, 0.0]); + positions.push([0.0, self.cone.height / 2.0, 0.0]); + // Invalid normal so that this doesn't affect shading. + // Requires normalization to be disabled in vertex shader! normals.push([0.0, 0.0, 0.0]); - uvs.push([0.0, 0.0]); + uvs.push([0.5, 0.5]); + + // lateral surface let radius = self.cone.radius; @@ -37,7 +38,7 @@ impl ConeBuilder { positions.push([radius * cos, -self.cone.height / 2.0, radius * sin]); normals.push([cos, 0., sin]); - uvs.push([segment as f32 / self.resolution as f32, 0.0]); + uvs.push([0.5 + cos * 0.5, 0.5 + sin * 0.5]); } for j in 0..self.resolution { From 68591a07e968b1819bd72b7b1d8dacdbd58f5254 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 00:02:27 +0200 Subject: [PATCH 09/36] Normalize vertex normals in fragment shader instead of vertex shader This is required for cone shading, but probably breaks mikktspace normal mapping. This should be done properly in another PR; don't merge this change! --- crates/bevy_pbr/src/render/mesh_functions.wgsl | 9 +++++---- crates/bevy_pbr/src/render/pbr_functions.wgsl | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh_functions.wgsl b/crates/bevy_pbr/src/render/mesh_functions.wgsl index 4757148e018a9..1a8ec0530f237 100644 --- a/crates/bevy_pbr/src/render/mesh_functions.wgsl +++ b/crates/bevy_pbr/src/render/mesh_functions.wgsl @@ -38,12 +38,13 @@ fn mesh_normal_local_to_world(vertex_normal: vec3, instance_index: u32) -> // Unreal Engine, Godot, and more all use the mikktspace method. Do not change this code // unless you really know what you are doing. // http://www.mikktspace.com/ - return normalize( - mat2x4_f32_to_mat3x3_unpack( + // + // For testing purposes, the normalization has been removed. + // Don't merge this! + return mat2x4_f32_to_mat3x3_unpack( mesh[instance_index].inverse_transpose_model_a, mesh[instance_index].inverse_transpose_model_b, - ) * vertex_normal - ); + ) * vertex_normal; } // Calculates the sign of the determinant of the 3x3 model matrix based on a diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 9b4668f3e75ec..83c8af99fec53 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -48,7 +48,7 @@ fn prepare_world_normal( double_sided: bool, is_front: bool, ) -> vec3 { - var output: vec3 = world_normal; + var output: vec3 = normalize(world_normal); #ifndef VERTEX_TANGENTS #ifndef STANDARDMATERIAL_NORMAL_MAP // NOTE: When NOT using normal-mapping, if looking at the back face of a double-sided From 5a1da1dac05947cbf7a42384e46ceafde817b56a Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 00:19:26 +0200 Subject: [PATCH 10/36] Add cylinder mesh --- .../src/mesh/primitive_meshes/cylinder.rs | 142 ++++++++++++++++++ .../src/mesh/primitive_meshes/mod.rs | 2 + .../src/mesh/primitive_meshes/torus.rs | 8 +- examples/3d/3d_shapes.rs | 2 +- 4 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs new file mode 100644 index 0000000000000..acc318cb6e7cb --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs @@ -0,0 +1,142 @@ +use super::Meshable; +use crate::mesh::{Indices, Mesh}; +use bevy_math::primitives::Cylinder; +use wgpu::PrimitiveTopology; + +pub struct CylinderBuilder { + pub cylinder: Cylinder, + pub resolution: u32, + pub segments: u32, +} + +impl CylinderBuilder { + pub fn resolution(mut self, resolution: u32) -> Self { + self.resolution = resolution; + self + } + + pub fn segments(mut self, segments: u32) -> Self { + self.segments = segments; + self + } + + pub fn build(&self) -> Mesh { + let resolution = self.resolution; + let segments = self.segments; + + debug_assert!(resolution > 2); + debug_assert!(segments > 0); + + let num_rings = segments + 1; + let num_vertices = resolution * 2 + num_rings * (resolution + 1); + let num_faces = resolution * (num_rings - 2); + let num_indices = (2 * num_faces + 2 * (resolution - 1) * 2) * 3; + + let mut positions = Vec::with_capacity(num_vertices as usize); + let mut normals = Vec::with_capacity(num_vertices as usize); + let mut uvs = Vec::with_capacity(num_vertices as usize); + let mut indices = Vec::with_capacity(num_indices as usize); + + let step_theta = std::f32::consts::TAU / resolution as f32; + let step_y = 2.0 * self.cylinder.half_height / segments as f32; + + // rings + + for ring in 0..num_rings { + let y = -self.cylinder.half_height + ring as f32 * step_y; + + for segment in 0..=resolution { + let theta = segment as f32 * step_theta; + let (sin, cos) = theta.sin_cos(); + + positions.push([self.cylinder.radius * cos, y, self.cylinder.radius * sin]); + normals.push([cos, 0., sin]); + uvs.push([ + segment as f32 / resolution as f32, + ring as f32 / segments as f32, + ]); + } + } + + // barrel skin + + for i in 0..segments { + let ring = i * (resolution + 1); + let next_ring = (i + 1) * (resolution + 1); + + for j in 0..resolution { + indices.extend_from_slice(&[ + ring + j, + next_ring + j, + ring + j + 1, + next_ring + j, + next_ring + j + 1, + ring + j + 1, + ]); + } + } + + // caps + + let mut build_cap = |top: bool| { + let offset = positions.len() as u32; + let (y, normal_y, winding) = if top { + (self.cylinder.half_height * 2.0 / 2., 1., (1, 0)) + } else { + (self.cylinder.half_height * 2.0 / -2., -1., (0, 1)) + }; + + for i in 0..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..(resolution - 1) { + indices.extend_from_slice(&[ + offset, + offset + i + winding.0, + offset + i + winding.1, + ]); + } + }; + + // top + + build_cap(true); + build_cap(false); + + 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) + } +} + +impl Meshable for Cylinder { + type Output = CylinderBuilder; + + fn mesh(&self) -> Self::Output { + CylinderBuilder { + cylinder: *self, + resolution: 16, + segments: 1, + } + } +} + +impl From for Mesh { + fn from(cylinder: Cylinder) -> Self { + cylinder.mesh().build() + } +} + +impl From for Mesh { + fn from(cylinder: CylinderBuilder) -> Self { + cylinder.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 572367b31bce3..9be63f19eb18a 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,6 +1,7 @@ mod circle; mod cone; mod cuboid; +mod cylinder; mod rectangle; mod regular_polygon; mod sphere; @@ -10,6 +11,7 @@ mod triangle; pub(crate) mod builders { pub use super::circle::CircleBuilder; pub use super::cone::ConeBuilder; + pub use super::cylinder::CylinderBuilder; pub use super::rectangle::RectangleBuilder; pub use super::sphere::SphereBuilder; pub use super::torus::TorusBuilder; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs index bc42f900d6925..fa3f7e254d789 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs @@ -106,13 +106,13 @@ impl Meshable for Torus { } impl From for Mesh { - fn from(sphere: Torus) -> Self { - sphere.mesh().build() + fn from(torus: Torus) -> Self { + torus.mesh().build() } } impl From for Mesh { - fn from(sphere: TorusBuilder) -> Self { - sphere.build() + fn from(torus: TorusBuilder) -> Self { + torus.build() } } diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 556717520b68f..b27853e4e6a00 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -37,7 +37,7 @@ fn setup( meshes.add(primitives::Cuboid::default().into()), //meshes.add(primitives::Capsule::default().into()), meshes.add(primitives::Torus::default().into()), - //meshes.add(primitives::Cylinder::default().into()), + meshes.add(primitives::Cylinder::default().into()), meshes.add(primitives::Cone::default().into()), meshes.add(primitives::Sphere { radius: 0.5 }.mesh().ico(6).unwrap()), meshes.add(primitives::Sphere { radius: 0.5 }.mesh().uv(6, 6)), From b979555114982b4e2d9efd71e686e0e464d33356 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 01:14:09 +0200 Subject: [PATCH 11/36] Add capsule mesh --- .../src/mesh/primitive_meshes/capsule.rs | 397 ++++++++++++++++++ .../src/mesh/primitive_meshes/mod.rs | 2 + examples/3d/3d_shapes.rs | 2 +- 3 files changed, 400 insertions(+), 1 deletion(-) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/capsule.rs diff --git a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs new file mode 100644 index 0000000000000..15b1657db72b6 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs @@ -0,0 +1,397 @@ +use super::Meshable; +use crate::mesh::{Indices, Mesh}; +use bevy_math::{primitives::Capsule, Vec2, Vec3}; +use wgpu::PrimitiveTopology; + +/// Manner in which UV coordinates are distributed vertically. +#[derive(Debug, Default, Clone, Copy)] +pub enum CapsuleUvProfile { + /// UV space is distributed by how much of the capsule consists of the hemispheres. + #[default] + Aspect, + /// Hemispheres get UV space according to the ratio of latitudes to rings. + Uniform, + /// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder + /// and lower third to the southern one. + Fixed, +} + +pub struct CapsuleBuilder { + pub capsule: Capsule, + pub rings: usize, + pub longitudes: usize, + pub latitudes: usize, + pub uv_profile: CapsuleUvProfile, +} + +impl CapsuleBuilder { + pub fn rings(mut self, rings: usize) -> Self { + self.rings = rings; + self + } + + pub fn longitudes(mut self, longitudes: usize) -> Self { + self.longitudes = longitudes; + self + } + + pub fn latitudes(mut self, latitudes: usize) -> Self { + self.latitudes = latitudes; + self + } + + pub fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self { + self.uv_profile = uv_profile; + self + } + + pub fn build(&self) -> Mesh { + // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db + let CapsuleBuilder { + capsule, + rings, + longitudes, + latitudes, + uv_profile, + } = *self; + let Capsule { + radius, + half_length, + } = capsule; + + let calc_middle = rings > 0; + let half_lats = latitudes / 2; + let half_latsn1 = half_lats - 1; + let half_latsn2 = half_lats - 2; + let ringsp1 = rings + 1; + let lonsp1 = longitudes + 1; + let summit = half_length + radius; + + // Vertex index offsets. + let vert_offset_north_hemi = longitudes; + let vert_offset_north_equator = vert_offset_north_hemi + lonsp1 * half_latsn1; + let vert_offset_cylinder = vert_offset_north_equator + lonsp1; + let vert_offset_south_equator = if calc_middle { + vert_offset_cylinder + lonsp1 * rings + } else { + vert_offset_cylinder + }; + let vert_offset_south_hemi = vert_offset_south_equator + lonsp1; + let vert_offset_south_polar = vert_offset_south_hemi + lonsp1 * half_latsn2; + let vert_offset_south_cap = vert_offset_south_polar + lonsp1; + + // Initialize arrays. + let vert_len = vert_offset_south_cap + longitudes; + + let mut vs: Vec = vec![Vec3::ZERO; vert_len]; + let mut vts: Vec = vec![Vec2::ZERO; vert_len]; + let mut vns: Vec = vec![Vec3::ZERO; vert_len]; + + let to_theta = 2.0 * std::f32::consts::PI / longitudes as f32; + let to_phi = std::f32::consts::PI / latitudes as f32; + let to_tex_horizontal = 1.0 / longitudes as f32; + let to_tex_vertical = 1.0 / half_lats as f32; + + let vt_aspect_ratio = match uv_profile { + CapsuleUvProfile::Aspect => radius / (2.0 * half_length + radius + radius), + CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32, + CapsuleUvProfile::Fixed => 1.0 / 3.0, + }; + let vt_aspect_north = 1.0 - vt_aspect_ratio; + let vt_aspect_south = vt_aspect_ratio; + + let mut theta_cartesian: Vec = vec![Vec2::ZERO; longitudes]; + let mut rho_theta_cartesian: Vec = vec![Vec2::ZERO; longitudes]; + let mut s_texture_cache: Vec = vec![0.0; lonsp1]; + + for j in 0..longitudes { + let jf = j as f32; + let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal); + let theta = jf * to_theta; + + let cos_theta = theta.cos(); + let sin_theta = theta.sin(); + + theta_cartesian[j] = Vec2::new(cos_theta, sin_theta); + rho_theta_cartesian[j] = Vec2::new(radius * cos_theta, radius * sin_theta); + + // North. + vs[j] = Vec3::new(0.0, summit, 0.0); + vts[j] = Vec2::new(s_texture_polar, 1.0); + vns[j] = Vec3::Y; + + // South. + let idx = vert_offset_south_cap + j; + vs[idx] = Vec3::new(0.0, -summit, 0.0); + vts[idx] = Vec2::new(s_texture_polar, 0.0); + vns[idx] = Vec3::new(0.0, -1.0, 0.0); + } + + // Equatorial vertices. + for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1) { + let s_texture = 1.0 - j as f32 * to_tex_horizontal; + *s_texture_cache_j = s_texture; + + // Wrap to first element upon reaching last. + let j_mod = j % longitudes; + let tc = theta_cartesian[j_mod]; + let rtc = rho_theta_cartesian[j_mod]; + + // North equator. + let idxn = vert_offset_north_equator + j; + vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y); + vts[idxn] = Vec2::new(s_texture, vt_aspect_north); + vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y); + + // South equator. + let idxs = vert_offset_south_equator + j; + vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y); + vts[idxs] = Vec2::new(s_texture, vt_aspect_south); + vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y); + } + + // Hemisphere vertices. + for i in 0..half_latsn1 { + let ip1f = i as f32 + 1.0; + let phi = ip1f * to_phi; + + // For coordinates. + let cos_phi_south = phi.cos(); + let sin_phi_south = phi.sin(); + + // Symmetrical hemispheres mean cosine and sine only needs + // to be calculated once. + let cos_phi_north = sin_phi_south; + let sin_phi_north = -cos_phi_south; + + let rho_cos_phi_north = radius * cos_phi_north; + let rho_sin_phi_north = radius * sin_phi_north; + let z_offset_north = half_length - rho_sin_phi_north; + + let rho_cos_phi_south = radius * cos_phi_south; + let rho_sin_phi_south = radius * sin_phi_south; + let z_offset_sout = -half_length - rho_sin_phi_south; + + // For texture coordinates. + let t_tex_fac = ip1f * to_tex_vertical; + let cmpl_tex_fac = 1.0 - t_tex_fac; + let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac; + let t_tex_south = cmpl_tex_fac * vt_aspect_south; + + let i_lonsp1 = i * lonsp1; + let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1; + let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1; + + for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) { + let j_mod = j % longitudes; + + let tc = theta_cartesian[j_mod]; + + // North hemisphere. + let idxn = vert_curr_lat_north + j; + vs[idxn] = Vec3::new( + rho_cos_phi_north * tc.x, + z_offset_north, + -rho_cos_phi_north * tc.y, + ); + vts[idxn] = Vec2::new(*s_texture, t_tex_north); + vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y); + + // South hemisphere. + let idxs = vert_curr_lat_south + j; + vs[idxs] = Vec3::new( + rho_cos_phi_south * tc.x, + z_offset_sout, + -rho_cos_phi_south * tc.y, + ); + vts[idxs] = Vec2::new(*s_texture, t_tex_south); + vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y); + } + } + + // Cylinder vertices. + if calc_middle { + // Exclude both origin and destination edges + // (North and South equators) from the interpolation. + let to_fac = 1.0 / ringsp1 as f32; + let mut idx_cyl_lat = vert_offset_cylinder; + + for h in 1..ringsp1 { + let fac = h as f32 * to_fac; + let cmpl_fac = 1.0 - fac; + let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south; + let z = -half_length * fac; + + for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) { + let j_mod = j % longitudes; + let tc = theta_cartesian[j_mod]; + let rtc = rho_theta_cartesian[j_mod]; + + vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y); + vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture); + vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y); + + idx_cyl_lat += 1; + } + } + } + + // Triangle indices. + + // Stride is 3 for polar triangles; + // stride is 6 for two triangles forming a quad. + let lons3 = longitudes * 3; + let lons6 = longitudes * 6; + let hemi_lons = half_latsn1 * lons6; + + let tri_offset_north_hemi = lons3; + let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons; + let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6; + let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons; + + let fs_len = tri_offset_south_cap + lons3; + let mut tris: Vec = vec![0; fs_len]; + + // Polar caps. + let mut i = 0; + let mut k = 0; + let mut m = tri_offset_south_cap; + while i < longitudes { + // North. + tris[k] = i as u32; + tris[k + 1] = (vert_offset_north_hemi + i) as u32; + tris[k + 2] = (vert_offset_north_hemi + i + 1) as u32; + + // South. + tris[m] = (vert_offset_south_cap + i) as u32; + tris[m + 1] = (vert_offset_south_polar + i + 1) as u32; + tris[m + 2] = (vert_offset_south_polar + i) as u32; + + i += 1; + k += 3; + m += 3; + } + + // Hemispheres. + + let mut i = 0; + let mut k = tri_offset_north_hemi; + let mut m = tri_offset_south_hemi; + + while i < half_latsn1 { + let i_lonsp1 = i * lonsp1; + + let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1; + let vert_next_lat_north = vert_curr_lat_north + lonsp1; + + let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1; + let vert_next_lat_south = vert_curr_lat_south + lonsp1; + + let mut j = 0; + while j < longitudes { + // North. + let north00 = vert_curr_lat_north + j; + let north01 = vert_next_lat_north + j; + let north11 = vert_next_lat_north + j + 1; + let north10 = vert_curr_lat_north + j + 1; + + tris[k] = north00 as u32; + tris[k + 1] = north11 as u32; + tris[k + 2] = north10 as u32; + + tris[k + 3] = north00 as u32; + tris[k + 4] = north01 as u32; + tris[k + 5] = north11 as u32; + + // South. + let south00 = vert_curr_lat_south + j; + let south01 = vert_next_lat_south + j; + let south11 = vert_next_lat_south + j + 1; + let south10 = vert_curr_lat_south + j + 1; + + tris[m] = south00 as u32; + tris[m + 1] = south11 as u32; + tris[m + 2] = south10 as u32; + + tris[m + 3] = south00 as u32; + tris[m + 4] = south01 as u32; + tris[m + 5] = south11 as u32; + + j += 1; + k += 6; + m += 6; + } + + i += 1; + } + + // Cylinder. + let mut i = 0; + let mut k = tri_offset_cylinder; + + while i < ringsp1 { + let vert_curr_lat = vert_offset_north_equator + i * lonsp1; + let vert_next_lat = vert_curr_lat + lonsp1; + + let mut j = 0; + while j < longitudes { + let cy00 = vert_curr_lat + j; + let cy01 = vert_next_lat + j; + let cy11 = vert_next_lat + j + 1; + let cy10 = vert_curr_lat + j + 1; + + tris[k] = cy00 as u32; + tris[k + 1] = cy11 as u32; + tris[k + 2] = cy10 as u32; + + tris[k + 3] = cy00 as u32; + tris[k + 4] = cy01 as u32; + tris[k + 5] = cy11 as u32; + + j += 1; + k += 6; + } + + i += 1; + } + + let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect(); + let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect(); + let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect(); + + assert_eq!(vs.len(), vert_len); + assert_eq!(tris.len(), fs_len); + + 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))) + } +} + +impl Meshable for Capsule { + type Output = CapsuleBuilder; + + fn mesh(&self) -> Self::Output { + CapsuleBuilder { + capsule: *self, + rings: 0, + latitudes: 16, + longitudes: 32, + uv_profile: CapsuleUvProfile::Aspect, + } + } +} + +impl From for Mesh { + fn from(capsule: Capsule) -> Self { + capsule.mesh().build() + } +} + +impl From for Mesh { + fn from(capsule: CapsuleBuilder) -> Self { + capsule.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 9be63f19eb18a..d9663b04b01fc 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,3 +1,4 @@ +mod capsule; mod circle; mod cone; mod cuboid; @@ -9,6 +10,7 @@ mod torus; mod triangle; pub(crate) mod builders { + pub use super::capsule::CapsuleBuilder; pub use super::circle::CircleBuilder; pub use super::cone::ConeBuilder; pub use super::cylinder::CylinderBuilder; diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index b27853e4e6a00..e6607849a94c0 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -35,7 +35,7 @@ fn setup( let shapes = [ meshes.add(primitives::Cuboid::default().into()), - //meshes.add(primitives::Capsule::default().into()), + meshes.add(primitives::Capsule::default().into()), meshes.add(primitives::Torus::default().into()), meshes.add(primitives::Cylinder::default().into()), meshes.add(primitives::Cone::default().into()), From 361c95f140a42fb78d39911f7f776448451c97d5 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 20:00:25 +0200 Subject: [PATCH 12/36] Add conical frustum mesh --- crates/bevy_math/src/primitives/dim3.rs | 4 +- .../mesh/primitive_meshes/conical_frustum.rs | 157 ++++++++++++++++++ .../src/mesh/primitive_meshes/mod.rs | 2 + examples/3d/3d_shapes.rs | 3 +- 4 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 9242bf41359ed..ebeb480e27830 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -292,9 +292,9 @@ impl Primitive3d for ConicalFrustum {} impl Default for ConicalFrustum { fn default() -> Self { Self { - radius_top: 0.1, + radius_top: 0.2, radius_bottom: 0.5, - height: 1.0, + height: 0.75, } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs new file mode 100644 index 0000000000000..6c138d6290d7d --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs @@ -0,0 +1,157 @@ +use super::Meshable; +use crate::mesh::{Indices, Mesh}; +use bevy_math::{primitives::ConicalFrustum, Vec3}; +use wgpu::PrimitiveTopology; + +pub struct ConicalFrustumBuilder { + pub frustum: ConicalFrustum, + pub resolution: u32, + pub segments: u32, +} + +impl ConicalFrustumBuilder { + pub fn resolution(mut self, resolution: u32) -> Self { + self.resolution = resolution; + self + } + + pub fn segments(mut self, segments: u32) -> Self { + self.segments = segments; + self + } + + pub fn build(&self) -> Mesh { + debug_assert!(self.resolution > 2); + debug_assert!(self.segments > 0); + + let ConicalFrustum { + radius_top, + radius_bottom, + height, + } = self.frustum; + + let num_rings = self.segments + 1; + let num_vertices = self.resolution * 2 + num_rings * (self.resolution + 1); + let num_faces = self.resolution * (num_rings - 2); + let num_indices = (2 * num_faces + 2 * (self.resolution - 1) * 2) * 3; + + let mut positions = Vec::with_capacity(num_vertices as usize); + let mut normals = Vec::with_capacity(num_vertices as usize); + let mut uvs = Vec::with_capacity(num_vertices as usize); + let mut indices = Vec::with_capacity(num_indices as usize); + + let step_theta = std::f32::consts::TAU / self.resolution as f32; + let step_y = height / self.segments as f32; + let step_radius = (radius_top - radius_bottom) / self.segments as f32; + + // rings + + for ring in 0..num_rings { + let y = -height / 2.0 + ring as f32 * step_y; + let radius = radius_bottom + ring as f32 * step_radius; + + for segment in 0..=self.resolution { + let theta = segment as f32 * step_theta; + let (sin, cos) = theta.sin_cos(); + + positions.push([radius * cos, y, radius * sin]); + normals.push( + Vec3::new(cos, (radius_bottom - radius_top) / height, sin) + .normalize() + .to_array(), + ); + uvs.push([ + segment as f32 / self.resolution as f32, + ring as f32 / self.segments as f32, + ]); + } + } + + // barrel skin + + for i in 0..self.segments { + let ring = i * (self.resolution + 1); + let next_ring = (i + 1) * (self.resolution + 1); + + for j in 0..self.resolution { + indices.extend_from_slice(&[ + ring + j, + next_ring + j, + ring + j + 1, + next_ring + j, + next_ring + j + 1, + ring + j + 1, + ]); + } + } + + // caps + + let mut build_cap = |top: bool| { + let offset = positions.len() as u32; + let (y, normal_y, winding) = if top { + (height / 2., 1., (1, 0)) + } else { + (height / -2., -1., (0, 1)) + }; + + let radius = if top { radius_top } else { radius_bottom }; + + if radius == 0.0 { + return; + } + + for i in 0..self.resolution { + let theta = i as f32 * step_theta; + let (sin, cos) = theta.sin_cos(); + + positions.push([cos * radius, y, sin * 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, + ]); + } + }; + + // top + + build_cap(true); + build_cap(false); + + 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) + } +} + +impl Meshable for ConicalFrustum { + type Output = ConicalFrustumBuilder; + + fn mesh(&self) -> Self::Output { + ConicalFrustumBuilder { + frustum: *self, + resolution: 32, + segments: 1, + } + } +} + +impl From for Mesh { + fn from(frustum: ConicalFrustum) -> Self { + frustum.mesh().build() + } +} + +impl From for Mesh { + fn from(frustum: ConicalFrustumBuilder) -> Self { + frustum.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index d9663b04b01fc..d65d3fea1ba45 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,6 +1,7 @@ mod capsule; mod circle; mod cone; +mod conical_frustum; mod cuboid; mod cylinder; mod rectangle; @@ -13,6 +14,7 @@ pub(crate) mod builders { pub use super::capsule::CapsuleBuilder; pub use super::circle::CircleBuilder; pub use super::cone::ConeBuilder; + pub use super::conical_frustum::ConicalFrustumBuilder; pub use super::cylinder::CylinderBuilder; pub use super::rectangle::RectangleBuilder; pub use super::sphere::SphereBuilder; diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index e6607849a94c0..69b0e7242c0a5 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -39,6 +39,7 @@ fn setup( meshes.add(primitives::Torus::default().into()), meshes.add(primitives::Cylinder::default().into()), meshes.add(primitives::Cone::default().into()), + meshes.add(primitives::ConicalFrustum::default().into()), meshes.add(primitives::Sphere { radius: 0.5 }.mesh().ico(6).unwrap()), meshes.add(primitives::Sphere { radius: 0.5 }.mesh().uv(6, 6)), ]; @@ -81,7 +82,7 @@ fn setup( }); commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(0.0, 6., 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y), + transform: Transform::from_xyz(2.0, 3., 6.0).looking_at(Vec3::new(2., 1., 0.), Vec3::Y), ..default() }); } From fd66b2f9e88afc993f1792b0023019dd4b077fc8 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 20:00:51 +0200 Subject: [PATCH 13/36] Clean up cone code --- .../src/mesh/primitive_meshes/cone.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index e8cc6a9fc2d7a..b0152ab3ca2d3 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -9,6 +9,11 @@ pub struct ConeBuilder { } impl ConeBuilder { + pub fn resolution(mut self, resolution: usize) -> Self { + self.resolution = resolution as u32; + self + } + pub fn build(&self) -> Mesh { let num_vertices = self.resolution * 2 + 1; let num_indices = self.resolution * 3; @@ -73,11 +78,6 @@ impl ConeBuilder { .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } - - pub fn segments(mut self, segments: usize) -> Self { - self.resolution = segments as u32; - self - } } impl Meshable for Cone { @@ -92,13 +92,13 @@ impl Meshable for Cone { } impl From for Mesh { - fn from(sphere: Cone) -> Self { - sphere.mesh().build() + fn from(cone: Cone) -> Self { + cone.mesh().build() } } impl From for Mesh { - fn from(sphere: ConeBuilder) -> Self { - sphere.build() + fn from(cone: ConeBuilder) -> Self { + cone.build() } } From c3213297887916dc561916965e48e89d5b788ef5 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 20:06:15 +0200 Subject: [PATCH 14/36] Change `Builder` names to `Mesh` --- .../src/mesh/primitive_meshes/capsule.rs | 14 +++++++------- .../src/mesh/primitive_meshes/circle.rs | 12 ++++++------ .../src/mesh/primitive_meshes/cone.rs | 12 ++++++------ .../src/mesh/primitive_meshes/conical_frustum.rs | 12 ++++++------ .../src/mesh/primitive_meshes/cylinder.rs | 12 ++++++------ .../bevy_render/src/mesh/primitive_meshes/mod.rs | 16 ++++++++-------- .../src/mesh/primitive_meshes/rectangle.rs | 12 ++++++------ .../src/mesh/primitive_meshes/sphere.rs | 12 ++++++------ .../src/mesh/primitive_meshes/torus.rs | 12 ++++++------ 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs index 15b1657db72b6..3251277e4b3fe 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs @@ -16,7 +16,7 @@ pub enum CapsuleUvProfile { Fixed, } -pub struct CapsuleBuilder { +pub struct CapsuleMesh { pub capsule: Capsule, pub rings: usize, pub longitudes: usize, @@ -24,7 +24,7 @@ pub struct CapsuleBuilder { pub uv_profile: CapsuleUvProfile, } -impl CapsuleBuilder { +impl CapsuleMesh { pub fn rings(mut self, rings: usize) -> Self { self.rings = rings; self @@ -47,7 +47,7 @@ impl CapsuleBuilder { pub fn build(&self) -> Mesh { // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db - let CapsuleBuilder { + let CapsuleMesh { capsule, rings, longitudes, @@ -371,10 +371,10 @@ impl CapsuleBuilder { } impl Meshable for Capsule { - type Output = CapsuleBuilder; + type Output = CapsuleMesh; fn mesh(&self) -> Self::Output { - CapsuleBuilder { + CapsuleMesh { capsule: *self, rings: 0, latitudes: 16, @@ -390,8 +390,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(capsule: CapsuleBuilder) -> Self { +impl From for Mesh { + fn from(capsule: CapsuleMesh) -> Self { capsule.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs index e08fba5bbc4da..ea764a92437ae 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs @@ -4,14 +4,14 @@ use super::Meshable; use bevy_math::primitives::{Circle, RegularPolygon}; #[derive(Debug)] -pub struct CircleBuilder { +pub struct CircleMesh { /// The circle shape. pub circle: Circle, /// The number of vertices used for the circle mesh. pub vertices: usize, } -impl CircleBuilder { +impl CircleMesh { pub fn build(&self) -> Mesh { RegularPolygon::new(self.circle.radius, self.vertices).mesh() } @@ -25,10 +25,10 @@ impl CircleBuilder { } impl Meshable for Circle { - type Output = CircleBuilder; + type Output = CircleMesh; fn mesh(&self) -> Self::Output { - CircleBuilder { + CircleMesh { circle: *self, vertices: 64, } @@ -41,8 +41,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(circle: CircleBuilder) -> Self { +impl From for Mesh { + fn from(circle: CircleMesh) -> Self { circle.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index b0152ab3ca2d3..7ae47588a243e 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -3,12 +3,12 @@ use crate::mesh::{Indices, Mesh}; use bevy_math::primitives::Cone; use wgpu::PrimitiveTopology; -pub struct ConeBuilder { +pub struct ConeMesh { pub cone: Cone, pub resolution: u32, } -impl ConeBuilder { +impl ConeMesh { pub fn resolution(mut self, resolution: usize) -> Self { self.resolution = resolution as u32; self @@ -81,10 +81,10 @@ impl ConeBuilder { } impl Meshable for Cone { - type Output = ConeBuilder; + type Output = ConeMesh; fn mesh(&self) -> Self::Output { - ConeBuilder { + ConeMesh { cone: *self, resolution: 32, } @@ -97,8 +97,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(cone: ConeBuilder) -> Self { +impl From for Mesh { + fn from(cone: ConeMesh) -> Self { cone.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs index 6c138d6290d7d..b814530e9e5c6 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs @@ -3,13 +3,13 @@ use crate::mesh::{Indices, Mesh}; use bevy_math::{primitives::ConicalFrustum, Vec3}; use wgpu::PrimitiveTopology; -pub struct ConicalFrustumBuilder { +pub struct ConicalFrustumMesh { pub frustum: ConicalFrustum, pub resolution: u32, pub segments: u32, } -impl ConicalFrustumBuilder { +impl ConicalFrustumMesh { pub fn resolution(mut self, resolution: u32) -> Self { self.resolution = resolution; self @@ -133,10 +133,10 @@ impl ConicalFrustumBuilder { } impl Meshable for ConicalFrustum { - type Output = ConicalFrustumBuilder; + type Output = ConicalFrustumMesh; fn mesh(&self) -> Self::Output { - ConicalFrustumBuilder { + ConicalFrustumMesh { frustum: *self, resolution: 32, segments: 1, @@ -150,8 +150,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(frustum: ConicalFrustumBuilder) -> Self { +impl From for Mesh { + fn from(frustum: ConicalFrustumMesh) -> Self { frustum.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs index acc318cb6e7cb..aecc5808beb20 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs @@ -3,13 +3,13 @@ use crate::mesh::{Indices, Mesh}; use bevy_math::primitives::Cylinder; use wgpu::PrimitiveTopology; -pub struct CylinderBuilder { +pub struct CylinderMesh { pub cylinder: Cylinder, pub resolution: u32, pub segments: u32, } -impl CylinderBuilder { +impl CylinderMesh { pub fn resolution(mut self, resolution: u32) -> Self { self.resolution = resolution; self @@ -118,10 +118,10 @@ impl CylinderBuilder { } impl Meshable for Cylinder { - type Output = CylinderBuilder; + type Output = CylinderMesh; fn mesh(&self) -> Self::Output { - CylinderBuilder { + CylinderMesh { cylinder: *self, resolution: 16, segments: 1, @@ -135,8 +135,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(cylinder: CylinderBuilder) -> Self { +impl From for Mesh { + fn from(cylinder: CylinderMesh) -> Self { cylinder.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index d65d3fea1ba45..8f0e2923e4098 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -11,14 +11,14 @@ mod torus; mod triangle; pub(crate) mod builders { - pub use super::capsule::CapsuleBuilder; - pub use super::circle::CircleBuilder; - pub use super::cone::ConeBuilder; - pub use super::conical_frustum::ConicalFrustumBuilder; - pub use super::cylinder::CylinderBuilder; - pub use super::rectangle::RectangleBuilder; - pub use super::sphere::SphereBuilder; - pub use super::torus::TorusBuilder; + pub use super::capsule::CapsuleMesh; + pub use super::circle::CircleMesh; + pub use super::cone::ConeMesh; + pub use super::conical_frustum::ConicalFrustumMesh; + pub use super::cylinder::CylinderMesh; + pub use super::rectangle::RectangleMesh; + pub use super::sphere::SphereMesh; + pub use super::torus::TorusMesh; } use super::Mesh; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs index 1b78445f84a1f..5aed1b481743a 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs @@ -4,12 +4,12 @@ use bevy_math::primitives::Rectangle; use wgpu::PrimitiveTopology; #[derive(Debug)] -pub struct RectangleBuilder { +pub struct RectangleMesh { pub rectangle: Rectangle, pub flipped: bool, } -impl RectangleBuilder { +impl RectangleMesh { fn build(&self) -> Mesh { let (u_left, u_right) = if self.flipped { (1.0, 0.0) } else { (0.0, 1.0) }; let [hw, hh] = [self.rectangle.half_width, self.rectangle.half_height]; @@ -41,10 +41,10 @@ impl RectangleBuilder { } impl Meshable for Rectangle { - type Output = RectangleBuilder; + type Output = RectangleMesh; fn mesh(&self) -> Self::Output { - RectangleBuilder { + RectangleMesh { rectangle: *self, flipped: false, } @@ -57,8 +57,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(rectangle: RectangleBuilder) -> Self { +impl From for Mesh { + fn from(rectangle: RectangleMesh) -> Self { rectangle.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs index 1c5e8f83687f5..d7fd8742d10c5 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs @@ -31,12 +31,12 @@ pub enum SphereKind { } #[derive(Debug)] -pub struct SphereBuilder { +pub struct SphereMesh { pub sphere: Sphere, pub kind: SphereKind, } -impl SphereBuilder { +impl SphereMesh { /// Builds a sphere mesh according to the configuration in `self`. /// /// # Panics @@ -199,10 +199,10 @@ impl SphereBuilder { } impl Meshable for Sphere { - type Output = SphereBuilder; + type Output = SphereMesh; fn mesh(&self) -> Self::Output { - SphereBuilder { + SphereMesh { sphere: *self, kind: SphereKind::Ico { subdivisions: 5 }, } @@ -215,8 +215,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(sphere: SphereBuilder) -> Self { +impl From for Mesh { + fn from(sphere: SphereMesh) -> Self { sphere.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs index fa3f7e254d789..85eb322588f37 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs @@ -3,13 +3,13 @@ use crate::mesh::{Indices, Mesh}; use bevy_math::{primitives::Torus, Vec3}; use wgpu::PrimitiveTopology; -pub struct TorusBuilder { +pub struct TorusMesh { pub torus: Torus, pub subdivisions_segments: usize, pub subdivisions_sides: usize, } -impl TorusBuilder { +impl TorusMesh { pub fn build(&self) -> Mesh { // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html // (source code at https://github.com/SEilers/Apparat) @@ -94,10 +94,10 @@ impl TorusBuilder { } impl Meshable for Torus { - type Output = TorusBuilder; + type Output = TorusMesh; fn mesh(&self) -> Self::Output { - TorusBuilder { + TorusMesh { torus: *self, subdivisions_segments: 32, subdivisions_sides: 24, @@ -111,8 +111,8 @@ impl From for Mesh { } } -impl From for Mesh { - fn from(torus: TorusBuilder) -> Self { +impl From for Mesh { + fn from(torus: TorusMesh) -> Self { torus.build() } } From 4e3d60ba086768527b05a8e0960ba403f04dd10a Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 23:02:08 +0200 Subject: [PATCH 15/36] Support regular polygons and circles facing different directions --- crates/bevy_render/src/lib.rs | 6 +- .../src/mesh/primitive_meshes/circle.rs | 68 ++++++++- .../src/mesh/primitive_meshes/mod.rs | 39 +++-- .../mesh/primitive_meshes/regular_polygon.rs | 140 +++++++++++++++--- 4 files changed, 211 insertions(+), 42 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 190b6f6e09792..72e020bfe23bd 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -29,11 +29,7 @@ pub mod prelude { pub use crate::{ camera::{Camera, OrthographicProjection, PerspectiveProjection, Projection}, color::Color, - mesh::{ - morph::MorphWeights, - primitive_meshes::{builders::*, Meshable}, - shape, Mesh, - }, + mesh::{morph::MorphWeights, primitive_meshes::*, shape, Mesh}, render_resource::Shader, spatial_bundle::SpatialBundle, texture::{Image, ImagePlugin}, diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs index ea764a92437ae..66d3c011a5f38 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs @@ -1,6 +1,6 @@ use crate::mesh::Mesh; -use super::Meshable; +use super::{Facing, Meshable}; use bevy_math::primitives::{Circle, RegularPolygon}; #[derive(Debug)] @@ -9,19 +9,78 @@ pub struct CircleMesh { pub circle: Circle, /// The number of vertices used for the circle mesh. pub vertices: usize, + pub facing: Facing, } impl CircleMesh { - pub fn build(&self) -> Mesh { - RegularPolygon::new(self.circle.radius, self.vertices).mesh() + /// Creates a new [`CircleMesh`] from a given radius and vertex count. + pub const fn new(radius: f32, vertices: usize) -> Self { + Self { + circle: Circle { radius }, + vertices, + facing: Facing::Z, + } } /// Sets the number of vertices used for the circle mesh. #[doc(alias = "segments")] - pub fn vertices(mut self, vertices: usize) -> Self { + pub const fn vertices(mut self, vertices: usize) -> Self { self.vertices = vertices; self } + + pub const fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } + + pub const fn facing_x(mut self) -> Self { + self.facing = Facing::X; + self + } + + pub const fn facing_y(mut self) -> Self { + self.facing = Facing::Y; + self + } + + pub const fn facing_z(mut self) -> Self { + self.facing = Facing::Z; + self + } + + pub const fn facing_neg_x(mut self) -> Self { + self.facing = Facing::NegX; + self + } + + pub const fn facing_neg_y(mut self) -> Self { + self.facing = Facing::NegY; + self + } + + pub const fn facing_neg_z(mut self) -> Self { + self.facing = Facing::NegZ; + self + } + + pub fn build(&self) -> Mesh { + RegularPolygon::new(self.circle.radius, self.vertices).into() + } + + pub(super) fn build_mesh_data( + &self, + translation: [f32; 3], + indices: &mut Vec, + positions: &mut Vec<[f32; 3]>, + normals: &mut Vec<[f32; 3]>, + uvs: &mut Vec<[f32; 2]>, + ) { + RegularPolygon::new(self.circle.radius, self.vertices) + .mesh() + .facing(self.facing) + .build_mesh_data(translation, indices, positions, normals, uvs); + } } impl Meshable for Circle { @@ -31,6 +90,7 @@ impl Meshable for Circle { CircleMesh { circle: *self, vertices: 64, + facing: Facing::Z, } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 8f0e2923e4098..82a22d5a8e976 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -10,16 +10,14 @@ mod sphere; mod torus; mod triangle; -pub(crate) mod builders { - pub use super::capsule::CapsuleMesh; - pub use super::circle::CircleMesh; - pub use super::cone::ConeMesh; - pub use super::conical_frustum::ConicalFrustumMesh; - pub use super::cylinder::CylinderMesh; - pub use super::rectangle::RectangleMesh; - pub use super::sphere::SphereMesh; - pub use super::torus::TorusMesh; -} +pub use capsule::CapsuleMesh; +pub use circle::CircleMesh; +pub use cone::ConeMesh; +pub use conical_frustum::ConicalFrustumMesh; +pub use cylinder::CylinderMesh; +pub use rectangle::RectangleMesh; +pub use sphere::SphereMesh; +pub use torus::TorusMesh; use super::Mesh; @@ -28,3 +26,24 @@ pub trait Meshable { fn mesh(&self) -> Self::Output; } + +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub enum Facing { + X = 1, + Y = 2, + #[default] + Z = 3, + NegX = -1, + NegY = -2, + NegZ = -3, +} + +impl Facing { + /// Returns `1` if the facing direction is positive `X`, `Y`, or `Z`, and `-1` otherwise. + pub const fn signum(&self) -> i8 { + match self { + Facing::X | Facing::Y | Facing::Z => 1, + _ => -1, + } + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs index e57c3bf261a35..ddbb31ef3c7eb 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs @@ -1,49 +1,143 @@ -use super::{Mesh, Meshable}; +use super::{Facing, Mesh, Meshable}; use crate::mesh::Indices; use bevy_math::primitives::RegularPolygon; use wgpu::PrimitiveTopology; -impl Meshable for RegularPolygon { - type Output = Mesh; +#[derive(Debug, Default)] +pub struct RegularPolygonMesh { + pub polygon: RegularPolygon, + pub facing: Facing, +} + +impl RegularPolygonMesh { + pub const fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } - fn mesh(&self) -> Mesh { - let sides = self.sides; + pub const fn facing_x(mut self) -> Self { + self.facing = Facing::X; + self + } - debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); + pub const fn facing_y(mut self) -> Self { + self.facing = Facing::Y; + self + } + + pub const fn facing_z(mut self) -> Self { + self.facing = Facing::Z; + self + } + + pub const fn facing_neg_x(mut self) -> Self { + self.facing = Facing::NegX; + self + } - let mut positions = Vec::with_capacity(sides); - let mut normals = Vec::with_capacity(sides); - let mut uvs = Vec::with_capacity(sides); + pub const fn facing_neg_y(mut self) -> Self { + self.facing = Facing::NegY; + self + } + + pub const fn facing_neg_z(mut self) -> Self { + self.facing = Facing::NegZ; + self + } + pub fn build(&self) -> Mesh { + let mut indices = Vec::with_capacity((self.polygon.sides - 2) * 3); + let mut positions = Vec::with_capacity(self.polygon.sides); + let mut normals = Vec::with_capacity(self.polygon.sides); + let mut uvs = Vec::with_capacity(self.polygon.sides); + + self.build_mesh_data( + [0.0; 3], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(Indices::U32(indices))) + } + + pub(super) fn build_mesh_data( + &self, + translation: [f32; 3], + indices: &mut Vec, + positions: &mut Vec<[f32; 3]>, + normals: &mut Vec<[f32; 3]>, + uvs: &mut Vec<[f32; 2]>, + ) { + let sides = self.polygon.sides; + + debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); + + let radius = self.polygon.circumcircle.radius; + let [trans_x, trans_y, trans_z] = translation; + let index_offset = positions.len() as u32; + let normal_sign = self.facing.signum() as f32; let step = std::f32::consts::TAU / sides as f32; + for i in 0..sides { let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step; let (sin, cos) = theta.sin_cos(); - positions.push([ - cos * self.circumcircle.radius, - sin * self.circumcircle.radius, - 0.0, - ]); - normals.push([0.0, 0.0, 1.0]); + let (position, normal) = match self.facing { + Facing::Z | Facing::NegZ => ( + [trans_x + cos * radius, trans_y + sin * radius, trans_z], + [0.0, 0.0, normal_sign], + ), + Facing::Y | Facing::NegY => ( + [trans_x + cos * radius, trans_y, trans_z + sin * radius], + [0.0, normal_sign, 0.0], + ), + Facing::X | Facing::NegX => ( + [trans_x, trans_y + cos * radius, trans_z + sin * radius], + [normal_sign, 0.0, 0.0], + ), + }; + + positions.push(position); + normals.push(normal); uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); } - let mut indices = Vec::with_capacity((sides - 2) * 3); + let winding = if normal_sign < 0.0 { (1, 0) } else { (0, 1) }; for i in 1..(sides as u32 - 1) { - indices.extend_from_slice(&[0, i + 1, i]); + indices.extend_from_slice(&[ + index_offset, + index_offset + i + winding.0, + index_offset + i + winding.1, + ]); } + } +} - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(Indices::U32(indices))) +impl Meshable for RegularPolygon { + type Output = RegularPolygonMesh; + + fn mesh(&self) -> Self::Output { + RegularPolygonMesh { + polygon: *self, + ..Default::default() + } } } impl From for Mesh { fn from(polygon: RegularPolygon) -> Self { - polygon.mesh() + polygon.mesh().build() + } +} + +impl From for Mesh { + fn from(polygon: RegularPolygonMesh) -> Self { + polygon.build() } } From 46b5f50c34f84cf1f2ada3ee2250f9da33334530 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 23:03:12 +0200 Subject: [PATCH 16/36] Fix camera position in 3d_shapes example --- examples/3d/3d_shapes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 69b0e7242c0a5..8002293d9d8a6 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -82,7 +82,7 @@ fn setup( }); commands.spawn(Camera3dBundle { - transform: Transform::from_xyz(2.0, 3., 6.0).looking_at(Vec3::new(2., 1., 0.), Vec3::Y), + transform: Transform::from_xyz(0.0, 5.0, 12.0).looking_at(Vec3::new(0., 1., 0.), Vec3::Y), ..default() }); } From e1d9d0671f7357487bf3871b4ccc3ad01a88a308 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Thu, 7 Dec 2023 23:04:21 +0200 Subject: [PATCH 17/36] Refactor cone, conical frustum and cylinder to use circle for base --- .../src/mesh/primitive_meshes/cone.rs | 27 +++------ .../mesh/primitive_meshes/conical_frustum.rs | 58 ++++++------------- .../src/mesh/primitive_meshes/cylinder.rs | 50 ++++++---------- 3 files changed, 45 insertions(+), 90 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index 7ae47588a243e..d133ae8e486be 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -1,4 +1,4 @@ -use super::Meshable; +use super::{CircleMesh, Meshable}; use crate::mesh::{Indices, Mesh}; use bevy_math::primitives::Cone; use wgpu::PrimitiveTopology; @@ -54,23 +54,14 @@ impl ConeMesh { // base - let offset = positions.len() as u32; - let y = self.cone.height / -2.; - - let radius = self.cone.radius; - - for i in 0..self.resolution { - let theta = i as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); - - positions.push([cos * radius, y, sin * radius]); - normals.push([0.0, -1.0, 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, offset + i + 1]); - } + let base = CircleMesh::new(self.cone.radius, self.resolution as usize).facing_neg_y(); + base.build_mesh_data( + [0.0, -self.cone.height / 2.0, 0.0], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); Mesh::new(PrimitiveTopology::TriangleList) .with_indices(Some(Indices::U32(indices))) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs index b814530e9e5c6..4a0724128fab7 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs @@ -1,5 +1,5 @@ use super::Meshable; -use crate::mesh::{Indices, Mesh}; +use crate::mesh::{primitive_meshes::CircleMesh, Indices, Mesh}; use bevy_math::{primitives::ConicalFrustum, Vec3}; use wgpu::PrimitiveTopology; @@ -85,44 +85,24 @@ impl ConicalFrustumMesh { } } - // caps - - let mut build_cap = |top: bool| { - let offset = positions.len() as u32; - let (y, normal_y, winding) = if top { - (height / 2., 1., (1, 0)) - } else { - (height / -2., -1., (0, 1)) - }; - - let radius = if top { radius_top } else { radius_bottom }; - - if radius == 0.0 { - return; - } - - for i in 0..self.resolution { - let theta = i as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); - - positions.push([cos * radius, y, sin * 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, - ]); - } - }; - - // top - - build_cap(true); - build_cap(false); + let top = CircleMesh::new(self.frustum.radius_top, self.resolution as usize).facing_y(); + top.build_mesh_data( + [0.0, self.frustum.height / 2.0, 0.0], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); + + let bottom = + CircleMesh::new(self.frustum.radius_bottom, self.resolution as usize).facing_neg_y(); + bottom.build_mesh_data( + [0.0, -self.frustum.height / 2.0, 0.0], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); Mesh::new(PrimitiveTopology::TriangleList) .with_indices(Some(Indices::U32(indices))) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs index aecc5808beb20..e5f36bbab2400 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs @@ -1,5 +1,5 @@ use super::Meshable; -use crate::mesh::{Indices, Mesh}; +use crate::mesh::{primitive_meshes::CircleMesh, Indices, Mesh}; use bevy_math::primitives::Cylinder; use wgpu::PrimitiveTopology; @@ -76,38 +76,22 @@ impl CylinderMesh { } } - // caps - - let mut build_cap = |top: bool| { - let offset = positions.len() as u32; - let (y, normal_y, winding) = if top { - (self.cylinder.half_height * 2.0 / 2., 1., (1, 0)) - } else { - (self.cylinder.half_height * 2.0 / -2., -1., (0, 1)) - }; - - for i in 0..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..(resolution - 1) { - indices.extend_from_slice(&[ - offset, - offset + i + winding.0, - offset + i + winding.1, - ]); - } - }; - - // top - - build_cap(true); - build_cap(false); + // Top and bottom + let base = CircleMesh::new(self.cylinder.radius, self.resolution as usize).facing_y(); + base.build_mesh_data( + [0.0, self.cylinder.half_height, 0.0], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); + base.facing_neg_y().build_mesh_data( + [0.0, -self.cylinder.half_height, 0.0], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); Mesh::new(PrimitiveTopology::TriangleList) .with_indices(Some(Indices::U32(indices))) From 4a1d4fc911ea38c5f870efc10985e94158775503 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Fri, 8 Dec 2023 19:35:22 +0200 Subject: [PATCH 18/36] Refactor and support 2D shape orientation and fix UVs --- crates/bevy_math/src/primitives/dim2.rs | 4 +- .../src/mesh/primitive_meshes/circle.rs | 7 +- .../src/mesh/primitive_meshes/mod.rs | 18 +++ .../src/mesh/primitive_meshes/rectangle.rs | 96 +++++++++++---- .../mesh/primitive_meshes/regular_polygon.rs | 37 ++---- .../src/mesh/primitive_meshes/triangle.rs | 112 +++++++++++++++--- 6 files changed, 205 insertions(+), 69 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index a707c1498c8b5..8146963acc7a1 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -197,7 +197,7 @@ impl BoxedPolyline2d { } /// A triangle in 2D space -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Triangle2d { /// The vertices of the triangle pub vertices: [Vec2; 3], @@ -214,7 +214,7 @@ impl Default for Triangle2d { impl Triangle2d { /// Create a new `Triangle2d` from points `a`, `b`, and `c` - pub fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { + pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { Self { vertices: [a, b, c], } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs index 66d3c011a5f38..d45657853327f 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs @@ -65,7 +65,10 @@ impl CircleMesh { } pub fn build(&self) -> Mesh { - RegularPolygon::new(self.circle.radius, self.vertices).into() + RegularPolygon::new(self.circle.radius, self.vertices) + .mesh() + .facing(self.facing) + .build() } pub(super) fn build_mesh_data( @@ -89,7 +92,7 @@ impl Meshable for Circle { fn mesh(&self) -> Self::Output { CircleMesh { circle: *self, - vertices: 64, + vertices: 32, facing: Facing::Z, } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 82a22d5a8e976..c53684da902aa 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -46,4 +46,22 @@ impl Facing { _ => -1, } } + + /// Returns the direction in as an array in the format `[x, y, z]`. + /// + /// # Example + /// + /// ```rust + /// assert_eq!(Facing::X.to_array(), [1.0, 0.0, 0.0]); + /// ``` + pub const fn to_array(&self) -> [f32; 3] { + match self { + Facing::X => [1.0, 0.0, 0.0], + Facing::Y => [0.0, 1.0, 0.0], + Facing::Z => [0.0, 0.0, 1.0], + Facing::NegX => [-1.0, 0.0, 0.0], + Facing::NegY => [0.0, -1.0, 0.0], + Facing::NegZ => [0.0, 0.0, -1.0], + } + } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs index 5aed1b481743a..f94733471d46b 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs @@ -1,30 +1,90 @@ -use super::{Mesh, Meshable}; +use super::{Facing, Mesh, Meshable}; use crate::mesh::Indices; use bevy_math::primitives::Rectangle; use wgpu::PrimitiveTopology; -#[derive(Debug)] +#[derive(Debug, Default, Clone)] pub struct RectangleMesh { pub rectangle: Rectangle, - pub flipped: bool, + pub facing: Facing, } impl RectangleMesh { + /// Creates a new [`RectangleMesh`] from a given radius and vertex count. + pub fn new(width: f32, height: f32) -> Self { + Self { + rectangle: Rectangle::new(width, height), + facing: Facing::Z, + } + } + + pub const fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } + + pub const fn facing_x(mut self) -> Self { + self.facing = Facing::X; + self + } + + pub const fn facing_y(mut self) -> Self { + self.facing = Facing::Y; + self + } + + pub const fn facing_z(mut self) -> Self { + self.facing = Facing::Z; + self + } + + pub const fn facing_neg_x(mut self) -> Self { + self.facing = Facing::NegX; + self + } + + pub const fn facing_neg_y(mut self) -> Self { + self.facing = Facing::NegY; + self + } + + pub const fn facing_neg_z(mut self) -> Self { + self.facing = Facing::NegZ; + self + } + fn build(&self) -> Mesh { - let (u_left, u_right) = if self.flipped { (1.0, 0.0) } else { (0.0, 1.0) }; let [hw, hh] = [self.rectangle.half_width, self.rectangle.half_height]; - let vertices = [ - ([-hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_left, 1.0]), - ([-hw, hh, 0.0], [0.0, 0.0, 1.0], [u_left, 0.0]), - ([hw, hh, 0.0], [0.0, 0.0, 1.0], [u_right, 0.0]), - ([hw, -hh, 0.0], [0.0, 0.0, 1.0], [u_right, 1.0]), - ]; + let positions = match self.facing { + Facing::Z | Facing::NegZ => vec![ + [hw, hh, 0.0], + [-hw, hh, 0.0], + [-hw, -hh, 0.0], + [hw, -hh, 0.0], + ], + Facing::Y | Facing::NegY => vec![ + [hw, 0.0, -hh], + [-hw, 0.0, -hh], + [-hw, 0.0, hh], + [hw, 0.0, hh], + ], + Facing::X | Facing::NegX => vec![ + [0.0, hh, -hw], + [0.0, hh, hw], + [0.0, -hh, hw], + [0.0, -hh, -hw], + ], + }; - let indices = Indices::U32(vec![0, 2, 1, 0, 3, 2]); + let normals = vec![self.facing.to_array(); 4]; + let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]]; - let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); - let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); - let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); + // Flip indices if facing -X, -Y, or -Z + let indices = if self.facing.signum() > 0 { + Indices::U32(vec![0, 1, 2, 0, 2, 3]) + } else { + Indices::U32(vec![0, 2, 1, 0, 3, 2]) + }; Mesh::new(PrimitiveTopology::TriangleList) .with_indices(Some(indices)) @@ -32,12 +92,6 @@ impl RectangleMesh { .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } - - /// Flips the UVs of the rectangle mesh. - pub fn flipped(mut self) -> Self { - self.flipped = !self.flipped; - self - } } impl Meshable for Rectangle { @@ -46,7 +100,7 @@ impl Meshable for Rectangle { fn mesh(&self) -> Self::Output { RectangleMesh { rectangle: *self, - flipped: false, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs index ddbb31ef3c7eb..8b6cef866e152 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs @@ -75,46 +75,33 @@ impl RegularPolygonMesh { uvs: &mut Vec<[f32; 2]>, ) { let sides = self.polygon.sides; - - debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); - let radius = self.polygon.circumcircle.radius; let [trans_x, trans_y, trans_z] = translation; + let index_offset = positions.len() as u32; + let facing_coords = self.facing.to_array(); let normal_sign = self.facing.signum() as f32; - let step = std::f32::consts::TAU / sides as f32; + let step = normal_sign * std::f32::consts::TAU / sides as f32; for i in 0..sides { - let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step; + let theta = std::f32::consts::FRAC_PI_2 + i as f32 * step; let (sin, cos) = theta.sin_cos(); + let x = cos * radius; + let y = sin * radius; - let (position, normal) = match self.facing { - Facing::Z | Facing::NegZ => ( - [trans_x + cos * radius, trans_y + sin * radius, trans_z], - [0.0, 0.0, normal_sign], - ), - Facing::Y | Facing::NegY => ( - [trans_x + cos * radius, trans_y, trans_z + sin * radius], - [0.0, normal_sign, 0.0], - ), - Facing::X | Facing::NegX => ( - [trans_x, trans_y + cos * radius, trans_z + sin * radius], - [normal_sign, 0.0, 0.0], - ), + let position = match self.facing { + Facing::X | Facing::NegX => [trans_x, trans_y + y, trans_z - x], + Facing::Y | Facing::NegY => [trans_x + x, trans_y, trans_z - y], + Facing::Z | Facing::NegZ => [trans_x + x, trans_y + y, trans_z], }; positions.push(position); - normals.push(normal); + normals.push(facing_coords); uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); } - let winding = if normal_sign < 0.0 { (1, 0) } else { (0, 1) }; for i in 1..(sides as u32 - 1) { - indices.extend_from_slice(&[ - index_offset, - index_offset + i + winding.0, - index_offset + i + winding.1, - ]); + indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]); } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs index 2308d7cf71bf6..5284257aa47d6 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs @@ -1,4 +1,4 @@ -use super::{Mesh, Meshable}; +use super::{Facing, Mesh, Meshable}; use crate::mesh::Indices; use bevy_math::{ primitives::{Triangle2d, WindingOrder}, @@ -6,38 +6,112 @@ use bevy_math::{ }; use wgpu::PrimitiveTopology; -impl Meshable for Triangle2d { - type Output = Mesh; - - fn mesh(&self) -> Mesh { - let [a, b, c] = self.vertices; - let max = a.min(b).min(c).abs().max(a.max(b).max(c)) * Vec2::new(1.0, -1.0); - let [norm_a, norm_b, norm_c] = [(a) / max, (b) / max, (c) / max]; - let vertices = [ - (a.extend(0.0), [0.0, 0.0, 1.0], norm_a / 2.0 + 0.5), - (b.extend(0.0), [0.0, 0.0, 1.0], norm_b / 2.0 + 0.5), - (c.extend(0.0), [0.0, 0.0, 1.0], norm_c / 2.0 + 0.5), +#[derive(Clone, Debug, Default)] +pub struct Triangle2dMesh { + pub triangle: Triangle2d, + pub facing: Facing, +} + +impl Triangle2dMesh { + /// Creates a new [`Triangle2dMesh`] from a given radius and vertex count. + pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { + Self { + triangle: Triangle2d::new(a, b, c), + facing: Facing::Z, + } + } + + pub const fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } + + pub const fn facing_x(mut self) -> Self { + self.facing = Facing::X; + self + } + + pub const fn facing_y(mut self) -> Self { + self.facing = Facing::Y; + self + } + + pub const fn facing_z(mut self) -> Self { + self.facing = Facing::Z; + self + } + + pub const fn facing_neg_x(mut self) -> Self { + self.facing = Facing::NegX; + self + } + + pub const fn facing_neg_y(mut self) -> Self { + self.facing = Facing::NegY; + self + } + + pub const fn facing_neg_z(mut self) -> Self { + self.facing = Facing::NegZ; + self + } + + pub fn build(&self) -> Mesh { + let [a, b, c] = self.triangle.vertices; + + let positions = match self.facing { + Facing::X | Facing::NegX => [[0.0, a.y, -a.x], [0.0, b.y, -b.x], [0.0, c.y, -c.x]], + Facing::Y | Facing::NegY => [[a.x, 0.0, -a.y], [b.x, 0.0, -b.y], [c.x, 0.0, -c.y]], + Facing::Z | Facing::NegZ => [[a.x, a.y, 0.0], [b.x, b.y, 0.0], [c.x, c.y, 0.0]], + }; + + let normals = vec![self.facing.to_array(); 3]; + + // The extents of the bounding box of the triangle, + // used to compute the UV coordinates of the points. + let extents = a.min(b).min(c).abs().max(a.max(b).max(c)) * Vec2::new(1.0, -1.0); + let uvs = vec![ + a / extents / 2.0 + 0.5, + b / extents / 2.0 + 0.5, + c / extents / 2.0 + 0.5, ]; - let indices = if self.winding_order() == WindingOrder::CounterClockwise { + let flipped = self.facing.signum() < 0; + let is_ccw = self.triangle.winding_order() == WindingOrder::CounterClockwise; + let is_cw = self.triangle.winding_order() == WindingOrder::Clockwise; + let indices = if (is_ccw && !flipped) || (is_cw && flipped) { Indices::U32(vec![0, 1, 2]) } else { Indices::U32(vec![0, 2, 1]) }; - let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); - let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); - let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); - Mesh::new(PrimitiveTopology::TriangleList) .with_indices(Some(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, Vec::from(positions)) .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } + +impl Meshable for Triangle2d { + type Output = Triangle2dMesh; + + fn mesh(&self) -> Triangle2dMesh { + Triangle2dMesh { + triangle: *self, + ..Default::default() + } + } +} + impl From for Mesh { fn from(triangle: Triangle2d) -> Self { - triangle.mesh() + triangle.mesh().build() + } +} + +impl From for Mesh { + fn from(triangle: Triangle2dMesh) -> Self { + triangle.build() } } From 0d6f225ad321f629493c5feda1926b25183ae7b2 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Fri, 8 Dec 2023 22:01:49 +0200 Subject: [PATCH 19/36] Make cone and torus default sizes more consistent --- crates/bevy_math/src/primitives/dim3.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index ebeb480e27830..94fc11382d7cf 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -292,9 +292,11 @@ impl Primitive3d for ConicalFrustum {} impl Default for ConicalFrustum { fn default() -> Self { Self { - radius_top: 0.2, + // This produces the same shape as the default Cone, + // but trancated to half the height. + radius_top: 0.25, radius_bottom: 0.5, - height: 0.75, + height: 0.5, } } } @@ -336,8 +338,8 @@ impl Primitive3d for Torus {} impl Default for Torus { fn default() -> Self { Self { - minor_radius: 0.5, - major_radius: 1.0, + minor_radius: 0.25, + major_radius: 0.75, } } } From 37f0f2463150426195bed83c1bd7f2da62523d40 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Fri, 8 Dec 2023 22:37:10 +0200 Subject: [PATCH 20/36] Add `CuboidMesh` --- .../src/mesh/primitive_meshes/cuboid.rs | 35 +++++++++++++++---- .../src/mesh/primitive_meshes/mod.rs | 1 + 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs index df9df82969071..c55e29c29ff5c 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs @@ -3,12 +3,21 @@ use crate::mesh::Indices; use bevy_math::primitives::Cuboid; use wgpu::PrimitiveTopology; -impl Meshable for Cuboid { - type Output = Mesh; +#[derive(Debug, Default, Clone)] +pub struct CuboidMesh { + pub cuboid: Cuboid, +} + +impl CuboidMesh { + pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self { + Self { + cuboid: Cuboid::new(x_length, y_length, z_length), + } + } - fn mesh(&self) -> Mesh { - let min = -self.half_extents; - let max = self.half_extents; + pub fn build(&self) -> Mesh { + let min = -self.cuboid.half_extents; + let max = self.cuboid.half_extents; // suppose Y-up right hand, and camera look from +z to -z let vertices = &[ @@ -65,8 +74,22 @@ impl Meshable for Cuboid { } } +impl Meshable for Cuboid { + type Output = CuboidMesh; + + fn mesh(&self) -> Self::Output { + CuboidMesh { cuboid: *self } + } +} + impl From for Mesh { fn from(cuboid: Cuboid) -> Self { - cuboid.mesh() + cuboid.mesh().build() + } +} + +impl From for Mesh { + fn from(cuboid: CuboidMesh) -> Self { + cuboid.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index c53684da902aa..3b2d05bdc3e80 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -14,6 +14,7 @@ pub use capsule::CapsuleMesh; pub use circle::CircleMesh; pub use cone::ConeMesh; pub use conical_frustum::ConicalFrustumMesh; +pub use cuboid::CuboidMesh; pub use cylinder::CylinderMesh; pub use rectangle::RectangleMesh; pub use sphere::SphereMesh; From 86c15e53a18715f031df5e1ebfa1f3ce90641604 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sat, 9 Dec 2023 00:01:24 +0200 Subject: [PATCH 21/36] Add more derives and add `MeshFacingExtension` --- .../src/mesh/primitive_meshes/capsule.rs | 20 +++++-- .../src/mesh/primitive_meshes/circle.rs | 59 +++++++------------ .../src/mesh/primitive_meshes/cone.rs | 14 ++++- .../mesh/primitive_meshes/conical_frustum.rs | 19 +++++- .../src/mesh/primitive_meshes/cuboid.rs | 2 +- .../src/mesh/primitive_meshes/cylinder.rs | 19 +++++- .../src/mesh/primitive_meshes/mod.rs | 28 +++++++++ .../src/mesh/primitive_meshes/rectangle.rs | 46 +++------------ .../mesh/primitive_meshes/regular_polygon.rs | 40 ++----------- .../src/mesh/primitive_meshes/sphere.rs | 14 +++-- .../src/mesh/primitive_meshes/torus.rs | 14 ++++- .../src/mesh/primitive_meshes/triangle.rs | 46 +++------------ 12 files changed, 154 insertions(+), 167 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs index 3251277e4b3fe..ae1064bc04575 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs @@ -4,7 +4,7 @@ use bevy_math::{primitives::Capsule, Vec2, Vec3}; use wgpu::PrimitiveTopology; /// Manner in which UV coordinates are distributed vertically. -#[derive(Debug, Default, Clone, Copy)] +#[derive(Clone, Copy, Debug, Default)] pub enum CapsuleUvProfile { /// UV space is distributed by how much of the capsule consists of the hemispheres. #[default] @@ -16,6 +16,7 @@ pub enum CapsuleUvProfile { Fixed, } +#[derive(Clone, Copy, Debug)] pub struct CapsuleMesh { pub capsule: Capsule, pub rings: usize, @@ -24,6 +25,18 @@ pub struct CapsuleMesh { pub uv_profile: CapsuleUvProfile, } +impl Default for CapsuleMesh { + fn default() -> Self { + Self { + capsule: Capsule::default(), + rings: 0, + longitudes: 32, + latitudes: 16, + uv_profile: CapsuleUvProfile::default(), + } + } +} + impl CapsuleMesh { pub fn rings(mut self, rings: usize) -> Self { self.rings = rings; @@ -376,10 +389,7 @@ impl Meshable for Capsule { fn mesh(&self) -> Self::Output { CapsuleMesh { capsule: *self, - rings: 0, - latitudes: 16, - longitudes: 32, - uv_profile: CapsuleUvProfile::Aspect, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs index d45657853327f..88db06314a2d8 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs @@ -1,9 +1,9 @@ use crate::mesh::Mesh; -use super::{Facing, Meshable}; +use super::{Facing, MeshFacingExtension, Meshable}; use bevy_math::primitives::{Circle, RegularPolygon}; -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct CircleMesh { /// The circle shape. pub circle: Circle, @@ -12,6 +12,23 @@ pub struct CircleMesh { pub facing: Facing, } +impl Default for CircleMesh { + fn default() -> Self { + Self { + circle: Circle::default(), + vertices: 32, + facing: Facing::Z, + } + } +} + +impl MeshFacingExtension for CircleMesh { + fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } +} + impl CircleMesh { /// Creates a new [`CircleMesh`] from a given radius and vertex count. pub const fn new(radius: f32, vertices: usize) -> Self { @@ -29,41 +46,6 @@ impl CircleMesh { self } - pub const fn facing(mut self, facing: Facing) -> Self { - self.facing = facing; - self - } - - pub const fn facing_x(mut self) -> Self { - self.facing = Facing::X; - self - } - - pub const fn facing_y(mut self) -> Self { - self.facing = Facing::Y; - self - } - - pub const fn facing_z(mut self) -> Self { - self.facing = Facing::Z; - self - } - - pub const fn facing_neg_x(mut self) -> Self { - self.facing = Facing::NegX; - self - } - - pub const fn facing_neg_y(mut self) -> Self { - self.facing = Facing::NegY; - self - } - - pub const fn facing_neg_z(mut self) -> Self { - self.facing = Facing::NegZ; - self - } - pub fn build(&self) -> Mesh { RegularPolygon::new(self.circle.radius, self.vertices) .mesh() @@ -92,8 +74,7 @@ impl Meshable for Circle { fn mesh(&self) -> Self::Output { CircleMesh { circle: *self, - vertices: 32, - facing: Facing::Z, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index d133ae8e486be..bb03184bf3881 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -1,13 +1,23 @@ -use super::{CircleMesh, Meshable}; +use super::{CircleMesh, MeshFacingExtension, Meshable}; use crate::mesh::{Indices, Mesh}; use bevy_math::primitives::Cone; use wgpu::PrimitiveTopology; +#[derive(Clone, Copy, Debug)] pub struct ConeMesh { pub cone: Cone, pub resolution: u32, } +impl Default for ConeMesh { + fn default() -> Self { + Self { + cone: Cone::default(), + resolution: 32, + } + } +} + impl ConeMesh { pub fn resolution(mut self, resolution: usize) -> Self { self.resolution = resolution as u32; @@ -77,7 +87,7 @@ impl Meshable for Cone { fn mesh(&self) -> Self::Output { ConeMesh { cone: *self, - resolution: 32, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs index 4a0724128fab7..70464b388fd2e 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs @@ -1,14 +1,28 @@ use super::Meshable; -use crate::mesh::{primitive_meshes::CircleMesh, Indices, Mesh}; +use crate::mesh::{ + primitive_meshes::{CircleMesh, MeshFacingExtension}, + Indices, Mesh, +}; use bevy_math::{primitives::ConicalFrustum, Vec3}; use wgpu::PrimitiveTopology; +#[derive(Clone, Copy, Debug)] pub struct ConicalFrustumMesh { pub frustum: ConicalFrustum, pub resolution: u32, pub segments: u32, } +impl Default for ConicalFrustumMesh { + fn default() -> Self { + Self { + frustum: ConicalFrustum::default(), + resolution: 32, + segments: 1, + } + } +} + impl ConicalFrustumMesh { pub fn resolution(mut self, resolution: u32) -> Self { self.resolution = resolution; @@ -118,8 +132,7 @@ impl Meshable for ConicalFrustum { fn mesh(&self) -> Self::Output { ConicalFrustumMesh { frustum: *self, - resolution: 32, - segments: 1, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs index c55e29c29ff5c..bed0e53bdc6a7 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs @@ -3,7 +3,7 @@ use crate::mesh::Indices; use bevy_math::primitives::Cuboid; use wgpu::PrimitiveTopology; -#[derive(Debug, Default, Clone)] +#[derive(Clone, Copy, Debug, Default)] pub struct CuboidMesh { pub cuboid: Cuboid, } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs index e5f36bbab2400..4f293085518d0 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs @@ -1,14 +1,28 @@ use super::Meshable; -use crate::mesh::{primitive_meshes::CircleMesh, Indices, Mesh}; +use crate::mesh::{ + primitive_meshes::{CircleMesh, MeshFacingExtension}, + Indices, Mesh, +}; use bevy_math::primitives::Cylinder; use wgpu::PrimitiveTopology; +#[derive(Clone, Copy, Debug)] pub struct CylinderMesh { pub cylinder: Cylinder, pub resolution: u32, pub segments: u32, } +impl Default for CylinderMesh { + fn default() -> Self { + Self { + cylinder: Cylinder::default(), + resolution: 32, + segments: 1, + } + } +} + impl CylinderMesh { pub fn resolution(mut self, resolution: u32) -> Self { self.resolution = resolution; @@ -107,8 +121,7 @@ impl Meshable for Cylinder { fn mesh(&self) -> Self::Output { CylinderMesh { cylinder: *self, - resolution: 16, - segments: 1, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 3b2d05bdc3e80..7f27e42bbbd22 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -66,3 +66,31 @@ impl Facing { } } } + +pub trait MeshFacingExtension: Sized { + fn facing(self, facing: Facing) -> Self; + + fn facing_x(self) -> Self { + self.facing(Facing::X) + } + + fn facing_y(self) -> Self { + self.facing(Facing::Y) + } + + fn facing_z(self) -> Self { + self.facing(Facing::Z) + } + + fn facing_neg_x(self) -> Self { + self.facing(Facing::NegX) + } + + fn facing_neg_y(self) -> Self { + self.facing(Facing::NegY) + } + + fn facing_neg_z(self) -> Self { + self.facing(Facing::NegZ) + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs index f94733471d46b..e7abd8f5317a0 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs @@ -1,14 +1,21 @@ -use super::{Facing, Mesh, Meshable}; +use super::{Facing, Mesh, MeshFacingExtension, Meshable}; use crate::mesh::Indices; use bevy_math::primitives::Rectangle; use wgpu::PrimitiveTopology; -#[derive(Debug, Default, Clone)] +#[derive(Clone, Copy, Debug, Default)] pub struct RectangleMesh { pub rectangle: Rectangle, pub facing: Facing, } +impl MeshFacingExtension for RectangleMesh { + fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } +} + impl RectangleMesh { /// Creates a new [`RectangleMesh`] from a given radius and vertex count. pub fn new(width: f32, height: f32) -> Self { @@ -18,41 +25,6 @@ impl RectangleMesh { } } - pub const fn facing(mut self, facing: Facing) -> Self { - self.facing = facing; - self - } - - pub const fn facing_x(mut self) -> Self { - self.facing = Facing::X; - self - } - - pub const fn facing_y(mut self) -> Self { - self.facing = Facing::Y; - self - } - - pub const fn facing_z(mut self) -> Self { - self.facing = Facing::Z; - self - } - - pub const fn facing_neg_x(mut self) -> Self { - self.facing = Facing::NegX; - self - } - - pub const fn facing_neg_y(mut self) -> Self { - self.facing = Facing::NegY; - self - } - - pub const fn facing_neg_z(mut self) -> Self { - self.facing = Facing::NegZ; - self - } - fn build(&self) -> Mesh { let [hw, hh] = [self.rectangle.half_width, self.rectangle.half_height]; let positions = match self.facing { diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs index 8b6cef866e152..c8d00190aad74 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs @@ -1,50 +1,22 @@ -use super::{Facing, Mesh, Meshable}; +use super::{Facing, Mesh, MeshFacingExtension, Meshable}; use crate::mesh::Indices; use bevy_math::primitives::RegularPolygon; use wgpu::PrimitiveTopology; -#[derive(Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct RegularPolygonMesh { pub polygon: RegularPolygon, pub facing: Facing, } -impl RegularPolygonMesh { - pub const fn facing(mut self, facing: Facing) -> Self { +impl MeshFacingExtension for RegularPolygonMesh { + fn facing(mut self, facing: Facing) -> Self { self.facing = facing; self } +} - pub const fn facing_x(mut self) -> Self { - self.facing = Facing::X; - self - } - - pub const fn facing_y(mut self) -> Self { - self.facing = Facing::Y; - self - } - - pub const fn facing_z(mut self) -> Self { - self.facing = Facing::Z; - self - } - - pub const fn facing_neg_x(mut self) -> Self { - self.facing = Facing::NegX; - self - } - - pub const fn facing_neg_y(mut self) -> Self { - self.facing = Facing::NegY; - self - } - - pub const fn facing_neg_z(mut self) -> Self { - self.facing = Facing::NegZ; - self - } - +impl RegularPolygonMesh { pub fn build(&self) -> Mesh { let mut indices = Vec::with_capacity((self.polygon.sides - 2) * 3); let mut positions = Vec::with_capacity(self.polygon.sides); diff --git a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs index d7fd8742d10c5..38bb6a543b6d7 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs @@ -7,7 +7,7 @@ use hexasphere::shapes::IcoSphere; use thiserror::Error; use wgpu::PrimitiveTopology; -#[derive(Debug, Clone, Error)] +#[derive(Clone, Copy, Debug, Error)] pub enum IcosphereError { #[error("Cannot create an icosphere of {subdivisions} subdivisions due to there being too many vertices being generated: {number_of_resulting_points}. (Limited to 65535 vertices or 79 subdivisions)")] TooManyVertices { @@ -16,7 +16,7 @@ pub enum IcosphereError { }, } -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub enum SphereKind { Ico { /// The number of subdivisions applied. @@ -30,7 +30,13 @@ pub enum SphereKind { }, } -#[derive(Debug)] +impl Default for SphereKind { + fn default() -> Self { + Self::Ico { subdivisions: 5 } + } +} + +#[derive(Clone, Copy, Debug, Default)] pub struct SphereMesh { pub sphere: Sphere, pub kind: SphereKind, @@ -204,7 +210,7 @@ impl Meshable for Sphere { fn mesh(&self) -> Self::Output { SphereMesh { sphere: *self, - kind: SphereKind::Ico { subdivisions: 5 }, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs index 85eb322588f37..c96b28ac7ba67 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs @@ -3,12 +3,23 @@ use crate::mesh::{Indices, Mesh}; use bevy_math::{primitives::Torus, Vec3}; use wgpu::PrimitiveTopology; +#[derive(Clone, Copy, Debug)] pub struct TorusMesh { pub torus: Torus, pub subdivisions_segments: usize, pub subdivisions_sides: usize, } +impl Default for TorusMesh { + fn default() -> Self { + Self { + torus: Torus::default(), + subdivisions_segments: 32, + subdivisions_sides: 24, + } + } +} + impl TorusMesh { pub fn build(&self) -> Mesh { // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html @@ -99,8 +110,7 @@ impl Meshable for Torus { fn mesh(&self) -> Self::Output { TorusMesh { torus: *self, - subdivisions_segments: 32, - subdivisions_sides: 24, + ..Default::default() } } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs index 5284257aa47d6..1540a5ede17d3 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs @@ -1,4 +1,4 @@ -use super::{Facing, Mesh, Meshable}; +use super::{Facing, Mesh, MeshFacingExtension, Meshable}; use crate::mesh::Indices; use bevy_math::{ primitives::{Triangle2d, WindingOrder}, @@ -6,12 +6,19 @@ use bevy_math::{ }; use wgpu::PrimitiveTopology; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Copy, Debug, Default)] pub struct Triangle2dMesh { pub triangle: Triangle2d, pub facing: Facing, } +impl MeshFacingExtension for Triangle2dMesh { + fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } +} + impl Triangle2dMesh { /// Creates a new [`Triangle2dMesh`] from a given radius and vertex count. pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { @@ -21,41 +28,6 @@ impl Triangle2dMesh { } } - pub const fn facing(mut self, facing: Facing) -> Self { - self.facing = facing; - self - } - - pub const fn facing_x(mut self) -> Self { - self.facing = Facing::X; - self - } - - pub const fn facing_y(mut self) -> Self { - self.facing = Facing::Y; - self - } - - pub const fn facing_z(mut self) -> Self { - self.facing = Facing::Z; - self - } - - pub const fn facing_neg_x(mut self) -> Self { - self.facing = Facing::NegX; - self - } - - pub const fn facing_neg_y(mut self) -> Self { - self.facing = Facing::NegY; - self - } - - pub const fn facing_neg_z(mut self) -> Self { - self.facing = Facing::NegZ; - self - } - pub fn build(&self) -> Mesh { let [a, b, c] = self.triangle.vertices; From 2f6708f9c6baf7711ce7a1b67e2823f8c108b614 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sat, 9 Dec 2023 00:32:04 +0200 Subject: [PATCH 22/36] Add `EllipseMesh` --- crates/bevy_math/src/primitives/dim2.rs | 2 +- .../src/mesh/primitive_meshes/ellipse.rs | 113 ++++++++++++++++++ .../src/mesh/primitive_meshes/mod.rs | 4 + examples/2d/2d_shapes.rs | 27 +++-- 4 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index c817cf82c078b..bf2fcd563bbaa 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -77,7 +77,7 @@ impl Primitive2d for Ellipse {} impl Default for Ellipse { fn default() -> Self { Self { - half_width: 0.5, + half_width: 1.0, half_height: 0.5, } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs new file mode 100644 index 0000000000000..e19c85959ac9d --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs @@ -0,0 +1,113 @@ +use crate::mesh::{Indices, Mesh}; + +use super::{Facing, MeshFacingExtension, Meshable}; +use bevy_math::primitives::Ellipse; +use wgpu::PrimitiveTopology; + +#[derive(Clone, Copy, Debug)] +pub struct EllipseMesh { + /// The ellipse shape. + pub ellipse: Ellipse, + /// The number of vertices used for the ellipse mesh. + pub vertices: usize, + pub facing: Facing, +} + +impl Default for EllipseMesh { + fn default() -> Self { + Self { + ellipse: Ellipse::default(), + vertices: 32, + facing: Facing::Z, + } + } +} + +impl MeshFacingExtension for EllipseMesh { + fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self + } +} + +impl EllipseMesh { + /// Creates a new [`EllipseMesh`] from a given half width and half height and a vertex count. + pub const fn new(half_width: f32, half_height: f32, vertices: usize) -> Self { + Self { + ellipse: Ellipse { + half_width, + half_height, + }, + vertices, + facing: Facing::Z, + } + } + + /// Sets the number of vertices used for the ellipse mesh. + #[doc(alias = "segments")] + pub const fn vertices(mut self, vertices: usize) -> Self { + self.vertices = vertices; + self + } + + pub fn build(&self) -> Mesh { + let mut indices = Vec::with_capacity((self.vertices - 2) * 3); + let mut positions = Vec::with_capacity(self.vertices); + let mut normals = Vec::with_capacity(self.vertices); + let mut uvs = Vec::with_capacity(self.vertices); + + let facing_coords = self.facing.to_array(); + let normal_sign = self.facing.signum() as f32; + let step = normal_sign * std::f32::consts::TAU / self.vertices as f32; + + for i in 0..self.vertices { + let theta = std::f32::consts::FRAC_PI_2 + i as f32 * step; + let (sin, cos) = theta.sin_cos(); + let x = cos * self.ellipse.half_width; + let y = sin * self.ellipse.half_height; + + let position = match self.facing { + Facing::X | Facing::NegX => [0.0, y, -x], + Facing::Y | Facing::NegY => [x, 0.0, -y], + Facing::Z | Facing::NegZ => [x, y, 0.0], + }; + + positions.push(position); + normals.push(facing_coords); + uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); + } + + for i in 1..(self.vertices as u32 - 1) { + indices.extend_from_slice(&[0, i, i + 1]); + } + + Mesh::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(Indices::U32(indices))) + } +} + +impl Meshable for Ellipse { + type Output = EllipseMesh; + + fn mesh(&self) -> Self::Output { + EllipseMesh { + ellipse: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(ellipse: Ellipse) -> Self { + ellipse.mesh().build() + } +} + +impl From for Mesh { + fn from(ellipse: EllipseMesh) -> Self { + ellipse.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 7f27e42bbbd22..eea4a13248827 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -4,6 +4,8 @@ mod cone; mod conical_frustum; mod cuboid; mod cylinder; +mod ellipse; +mod plane; mod rectangle; mod regular_polygon; mod sphere; @@ -16,6 +18,8 @@ pub use cone::ConeMesh; pub use conical_frustum::ConicalFrustumMesh; pub use cuboid::CuboidMesh; pub use cylinder::CylinderMesh; +pub use ellipse::EllipseMesh; +pub use plane::PlaneMesh; pub use rectangle::RectangleMesh; pub use sphere::SphereMesh; pub use torus::TorusMesh; diff --git a/examples/2d/2d_shapes.rs b/examples/2d/2d_shapes.rs index 1191f92a7f6d8..e9f81a133b152 100644 --- a/examples/2d/2d_shapes.rs +++ b/examples/2d/2d_shapes.rs @@ -18,40 +18,41 @@ fn setup( // Circle mesh commands.spawn(MaterialMesh2dBundle { - mesh: meshes.add(primitives::Circle { radius: 50. }.into()).into(), + mesh: meshes + .add(primitives::Circle { radius: 50.0 }.into()) + .into(), material: materials.add(ColorMaterial::from(Color::PURPLE)), - transform: Transform::from_translation(Vec3::new(-150., 0., 0.)), + transform: Transform::from_translation(Vec3::new(-150.0, 0.0, 0.0)), ..default() }); // Rectangle sprite - commands.spawn(SpriteBundle { - sprite: Sprite { - color: Color::rgb(0.25, 0.25, 0.75), - custom_size: Some(Vec2::new(50.0, 100.0)), - ..default() - }, - transform: Transform::from_translation(Vec3::new(-50., 0., 0.)), + commands.spawn(MaterialMesh2dBundle { + mesh: meshes + .add(primitives::Ellipse::new(50.0, 100.0).into()) + .into(), + material: materials.add(ColorMaterial::from(Color::rgb(0.25, 0.25, 0.75))), + transform: Transform::from_translation(Vec3::new(-50.0, 0.0, 0.0)), ..default() }); // Rectangle mesh commands.spawn(MaterialMesh2dBundle { mesh: meshes - .add(primitives::Rectangle::new(50., 100.).into()) + .add(primitives::Rectangle::new(50.0, 100.0).into()) .into(), material: materials.add(ColorMaterial::from(Color::LIME_GREEN)), - transform: Transform::from_translation(Vec3::new(50., 0., 0.)), + transform: Transform::from_translation(Vec3::new(50.0, 0.0, 0.0)), ..default() }); // Hexagon mesh commands.spawn(MaterialMesh2dBundle { mesh: meshes - .add(primitives::RegularPolygon::new(50., 6).into()) + .add(primitives::RegularPolygon::new(50.0, 6).into()) .into(), material: materials.add(ColorMaterial::from(Color::TURQUOISE)), - transform: Transform::from_translation(Vec3::new(150., 0., 0.)), + transform: Transform::from_translation(Vec3::new(150.0, 0.0, 0.0)), ..default() }); } From 041e38118046e8c1dc944221d821b51ea7875619 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sat, 9 Dec 2023 19:12:03 +0200 Subject: [PATCH 23/36] Reuse ellipse mesh code for regular polygons --- .../src/mesh/primitive_meshes/ellipse.rs | 47 +++++++++---- .../mesh/primitive_meshes/regular_polygon.rs | 66 +++++-------------- 2 files changed, 51 insertions(+), 62 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs index e19c85959ac9d..831e79c4625bf 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs @@ -56,20 +56,47 @@ impl EllipseMesh { let mut normals = Vec::with_capacity(self.vertices); let mut uvs = Vec::with_capacity(self.vertices); + self.build_mesh_data( + [0.0; 3], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_indices(Some(Indices::U32(indices))) + } + + pub(super) fn build_mesh_data( + &self, + translation: [f32; 3], + indices: &mut Vec, + positions: &mut Vec<[f32; 3]>, + normals: &mut Vec<[f32; 3]>, + uvs: &mut Vec<[f32; 2]>, + ) { + let sides = self.vertices; + let [trans_x, trans_y, trans_z] = translation; + + let index_offset = positions.len() as u32; let facing_coords = self.facing.to_array(); let normal_sign = self.facing.signum() as f32; - let step = normal_sign * std::f32::consts::TAU / self.vertices as f32; + let step = normal_sign * std::f32::consts::TAU / sides as f32; - for i in 0..self.vertices { + for i in 0..sides { let theta = std::f32::consts::FRAC_PI_2 + i as f32 * step; let (sin, cos) = theta.sin_cos(); let x = cos * self.ellipse.half_width; let y = sin * self.ellipse.half_height; let position = match self.facing { - Facing::X | Facing::NegX => [0.0, y, -x], - Facing::Y | Facing::NegY => [x, 0.0, -y], - Facing::Z | Facing::NegZ => [x, y, 0.0], + Facing::X | Facing::NegX => [trans_x, trans_y + y, trans_z - x], + Facing::Y | Facing::NegY => [trans_x + x, trans_y, trans_z - y], + Facing::Z | Facing::NegZ => [trans_x + x, trans_y + y, trans_z], }; positions.push(position); @@ -77,15 +104,9 @@ impl EllipseMesh { uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); } - for i in 1..(self.vertices as u32 - 1) { - indices.extend_from_slice(&[0, i, i + 1]); + for i in 1..(sides as u32 - 1) { + indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]); } - - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(Indices::U32(indices))) } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs index c8d00190aad74..f4b5a21f6a7dd 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs @@ -1,7 +1,5 @@ use super::{Facing, Mesh, MeshFacingExtension, Meshable}; -use crate::mesh::Indices; -use bevy_math::primitives::RegularPolygon; -use wgpu::PrimitiveTopology; +use bevy_math::primitives::{Ellipse, RegularPolygon}; #[derive(Clone, Copy, Debug, Default)] pub struct RegularPolygonMesh { @@ -18,24 +16,15 @@ impl MeshFacingExtension for RegularPolygonMesh { impl RegularPolygonMesh { pub fn build(&self) -> Mesh { - let mut indices = Vec::with_capacity((self.polygon.sides - 2) * 3); - let mut positions = Vec::with_capacity(self.polygon.sides); - let mut normals = Vec::with_capacity(self.polygon.sides); - let mut uvs = Vec::with_capacity(self.polygon.sides); - - self.build_mesh_data( - [0.0; 3], - &mut indices, - &mut positions, - &mut normals, - &mut uvs, - ); - - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(Indices::U32(indices))) + // The ellipse mesh is just a regular polygon with two radii + Ellipse { + half_width: self.polygon.circumcircle.radius, + half_height: self.polygon.circumcircle.radius, + } + .mesh() + .vertices(self.polygon.sides) + .facing(self.facing) + .build() } pub(super) fn build_mesh_data( @@ -46,35 +35,14 @@ impl RegularPolygonMesh { normals: &mut Vec<[f32; 3]>, uvs: &mut Vec<[f32; 2]>, ) { - let sides = self.polygon.sides; - let radius = self.polygon.circumcircle.radius; - let [trans_x, trans_y, trans_z] = translation; - - let index_offset = positions.len() as u32; - let facing_coords = self.facing.to_array(); - let normal_sign = self.facing.signum() as f32; - let step = normal_sign * std::f32::consts::TAU / sides as f32; - - for i in 0..sides { - let theta = std::f32::consts::FRAC_PI_2 + i as f32 * step; - let (sin, cos) = theta.sin_cos(); - let x = cos * radius; - let y = sin * radius; - - let position = match self.facing { - Facing::X | Facing::NegX => [trans_x, trans_y + y, trans_z - x], - Facing::Y | Facing::NegY => [trans_x + x, trans_y, trans_z - y], - Facing::Z | Facing::NegZ => [trans_x + x, trans_y + y, trans_z], - }; - - positions.push(position); - normals.push(facing_coords); - uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); - } - - for i in 1..(sides as u32 - 1) { - indices.extend_from_slice(&[index_offset, index_offset + i, index_offset + i + 1]); + Ellipse { + half_width: self.polygon.circumcircle.radius, + half_height: self.polygon.circumcircle.radius, } + .mesh() + .vertices(self.polygon.sides) + .facing(self.facing) + .build_mesh_data(translation, indices, positions, normals, uvs); } } From c54bf5d0497e6c9462812602fb80c5fd550c5955 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sat, 16 Dec 2023 22:47:05 +0200 Subject: [PATCH 24/36] Add plane mesh --- .../src/mesh/primitive_meshes/plane.rs | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 crates/bevy_render/src/mesh/primitive_meshes/plane.rs diff --git a/crates/bevy_render/src/mesh/primitive_meshes/plane.rs b/crates/bevy_render/src/mesh/primitive_meshes/plane.rs new file mode 100644 index 0000000000000..a40c16f6ba8db --- /dev/null +++ b/crates/bevy_render/src/mesh/primitive_meshes/plane.rs @@ -0,0 +1,81 @@ +use super::{Mesh, Meshable}; +use crate::mesh::Indices; +use bevy_math::{primitives::Plane3d, Quat, Vec2, Vec3}; +use wgpu::PrimitiveTopology; + +#[derive(Clone, Copy, Debug)] +pub struct PlaneMesh { + pub plane: Plane3d, + pub half_size: Vec2, +} + +impl Default for PlaneMesh { + fn default() -> Self { + Self { + plane: Plane3d::default(), + half_size: Vec2::ONE, + } + } +} + +impl PlaneMesh { + /// Creates a new [`PlaneMesh`] from a given normal and size. + /// + /// # Panics + /// + /// Panics if the given `normal` is zero (or very close to zero), or non-finite. + pub fn new(normal: Vec3, size: Vec2) -> Self { + Self { + plane: Plane3d::new(normal), + half_size: 2.0 * size, + } + } + + pub fn size(mut self, size: Vec2) -> Self { + self.half_size = size / 2.0; + self + } + + fn build(&self) -> Mesh { + let rotation = Quat::from_rotation_arc(Vec3::Y, *self.plane.normal); + let positions = vec![ + rotation * Vec3::new(self.half_size.x, 0.0, -self.half_size.y), + rotation * Vec3::new(-self.half_size.x, 0.0, -self.half_size.y), + rotation * Vec3::new(-self.half_size.x, 0.0, self.half_size.y), + rotation * Vec3::new(self.half_size.x, 0.0, self.half_size.y), + ]; + + let normals = vec![self.plane.normal.to_array(); 4]; + let uvs = vec![[1.0, 0.0], [0.0, 0.0], [0.0, 1.0], [1.0, 1.0]]; + let indices = Indices::U32(vec![0, 1, 2, 0, 2, 3]); + + Mesh::new(PrimitiveTopology::TriangleList) + .with_indices(Some(indices)) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + } +} + +impl Meshable for Plane3d { + type Output = PlaneMesh; + + fn mesh(&self) -> Self::Output { + PlaneMesh { + plane: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(plane: Plane3d) -> Self { + plane.mesh().build() + } +} + +impl From for Mesh { + fn from(plane: PlaneMesh) -> Self { + plane.build() + } +} From 60b00c210f1a39c7ead2bd959911b808623b4f00 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sat, 16 Dec 2023 22:57:38 +0200 Subject: [PATCH 25/36] Clean up cone mesh code --- .../src/mesh/primitive_meshes/cone.rs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index bb03184bf3881..b2edbd62fa8a2 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -25,6 +25,7 @@ impl ConeMesh { } pub fn build(&self) -> Mesh { + let Cone { radius, height } = self.cone; let num_vertices = self.resolution * 2 + 1; let num_indices = self.resolution * 3; @@ -33,25 +34,27 @@ impl ConeMesh { let mut uvs = Vec::with_capacity(num_vertices as usize); let mut indices = Vec::with_capacity(num_indices as usize); - let step_theta = std::f32::consts::TAU / self.resolution as f32; - - // tip - + // Tip positions.push([0.0, self.cone.height / 2.0, 0.0]); - // Invalid normal so that this doesn't affect shading. - // Requires normalization to be disabled in vertex shader! - normals.push([0.0, 0.0, 0.0]); - uvs.push([0.5, 0.5]); - // lateral surface + // The tip doesn't have a singular normal that works correctly. + // We use an invalid normal here so that it becomes NaN in the fragment shader + // and doesn't affect the overall shading. This might seem hacky, but it's one of + // the only ways to get perfectly smooth cones without creases or other shading artefacts. + // + // Note that this requires that normals are not normalized in the vertex shader, + // as that would make the entire triangle invalid and make the cone appear as black. + normals.push([0.0, 0.0, 0.0]); - let radius = self.cone.radius; + uvs.push([0.5, 0.5]); + // Lateral surface, i.e. the side of the cone + let step_theta = std::f32::consts::TAU / self.resolution as f32; for segment in 0..=self.resolution { let theta = segment as f32 * step_theta; let (sin, cos) = theta.sin_cos(); - positions.push([radius * cos, -self.cone.height / 2.0, radius * sin]); + positions.push([radius * cos, -height / 2.0, radius * sin]); normals.push([cos, 0., sin]); uvs.push([0.5 + cos * 0.5, 0.5 + sin * 0.5]); } @@ -62,11 +65,10 @@ impl ConeMesh { indices.extend(&[0, positions.len() as u32 - 1, positions.len() as u32 - 2]); - // base - - let base = CircleMesh::new(self.cone.radius, self.resolution as usize).facing_neg_y(); + // Base + let base = CircleMesh::new(radius, self.resolution as usize).facing_neg_y(); base.build_mesh_data( - [0.0, -self.cone.height / 2.0, 0.0], + [0.0, -height / 2.0, 0.0], &mut indices, &mut positions, &mut normals, From 7a1a81fc44e05e3bcd52525cb45877ceba98766c Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sat, 16 Dec 2023 23:50:24 +0200 Subject: [PATCH 26/36] Fix capsule rings --- crates/bevy_render/src/mesh/primitive_meshes/capsule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs index ae1064bc04575..c23a89582e4ce 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs @@ -233,7 +233,7 @@ impl CapsuleMesh { let fac = h as f32 * to_fac; let cmpl_fac = 1.0 - fac; let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south; - let z = -half_length * fac; + let z = half_length - 2.0 * half_length * fac; for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) { let j_mod = j % longitudes; From 9e4dca7e13a175314482d0e74db9916de20a76e1 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 00:10:39 +0200 Subject: [PATCH 27/36] Warn missing docs --- crates/bevy_render/src/mesh/primitive_meshes/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index eea4a13248827..e612aa6be972b 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,3 +1,5 @@ +#![warn(missing_docs)] + mod capsule; mod circle; mod cone; From 842b5465ffbb4a2218d973c225e42e0e74bbcd8d Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 02:40:52 +0200 Subject: [PATCH 28/36] Document and clean up mesh code --- crates/bevy_math/src/primitives/dim2.rs | 10 +-- .../src/mesh/primitive_meshes/capsule.rs | 36 ++++++++- .../src/mesh/primitive_meshes/circle.rs | 28 ++++--- .../src/mesh/primitive_meshes/cone.rs | 33 +++++--- .../mesh/primitive_meshes/conical_frustum.rs | 58 +++++++++----- .../src/mesh/primitive_meshes/cuboid.rs | 6 +- .../src/mesh/primitive_meshes/cylinder.rs | 25 +++++- .../src/mesh/primitive_meshes/ellipse.rs | 32 ++++---- .../src/mesh/primitive_meshes/mod.rs | 24 +++++- .../src/mesh/primitive_meshes/plane.rs | 7 +- .../src/mesh/primitive_meshes/rectangle.rs | 7 +- .../mesh/primitive_meshes/regular_polygon.rs | 22 +++++- .../src/mesh/primitive_meshes/sphere.rs | 49 ++++++++---- .../src/mesh/primitive_meshes/torus.rs | 76 +++++++++++++------ .../src/mesh/primitive_meshes/triangle.rs | 7 +- 15 files changed, 312 insertions(+), 108 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index bf2fcd563bbaa..7af4cd651b357 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -390,16 +390,16 @@ impl Default for RegularPolygon { impl RegularPolygon { /// Create a new `RegularPolygon` - /// from the radius of the circumcircle and number of sides + /// from the radius of the circumcircle and a number of sides /// /// # Panics /// - /// Panics if `circumcircle_radius` is non-positive - pub fn new(circumcircle_radius: f32, sides: usize) -> Self { - assert!(circumcircle_radius > 0.0); + /// Panics if `circumradius` is non-positive + pub fn new(circumradius: f32, sides: usize) -> Self { + assert!(circumradius > 0.0); Self { circumcircle: Circle { - radius: circumcircle_radius, + radius: circumradius, }, sides, } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs index c23a89582e4ce..4aafb4b860934 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs @@ -16,12 +16,22 @@ pub enum CapsuleUvProfile { Fixed, } +/// A builder used for creating a [`Mesh`] with a [`Capsule`] shape. #[derive(Clone, Copy, Debug)] pub struct CapsuleMesh { + /// The [`Capsule`] shape. pub capsule: Capsule, + /// The number of horizontal lines subdividing the cylindrical part of the capsule. + /// The default is `0`. pub rings: usize, + /// The number of vertical lines subdividing the hemispheres of the capsule. + /// The default is `32`. pub longitudes: usize, + /// The number of horizontal lines subdividing the hemispheres of the capsule. + /// The default is `16`. pub latitudes: usize, + /// The manner in which UV coordinates are distributed vertically. + /// The default is [`CapsuleUvProfile::Aspect`]. pub uv_profile: CapsuleUvProfile, } @@ -38,26 +48,44 @@ impl Default for CapsuleMesh { } impl CapsuleMesh { - pub fn rings(mut self, rings: usize) -> Self { + /// Creates a new [`CapsuleMesh`] from a given radius, height, longitudes and latitudes. + /// + /// Note that `height` is the distance between the centers of the hemispheres. + /// `radius` will be added to both ends to get the real height of the mesh. + pub fn new(radius: f32, height: f32, longitudes: usize, latitudes: usize) -> Self { + Self { + capsule: Capsule::new(radius, height), + longitudes, + latitudes, + ..Default::default() + } + } + + /// Sets the number of horizontal lines subdividing the cylindrical part of the capsule. + pub const fn rings(mut self, rings: usize) -> Self { self.rings = rings; self } - pub fn longitudes(mut self, longitudes: usize) -> Self { + /// Sets the number of vertical lines subdividing the hemispheres of the capsule. + pub const fn longitudes(mut self, longitudes: usize) -> Self { self.longitudes = longitudes; self } - pub fn latitudes(mut self, latitudes: usize) -> Self { + /// Sets the number of horizontal lines subdividing the hemispheres of the capsule. + pub const fn latitudes(mut self, latitudes: usize) -> Self { self.latitudes = latitudes; self } - pub fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self { + /// Sets the manner in which UV coordinates are distributed vertically. + pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self { self.uv_profile = uv_profile; self } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db let CapsuleMesh { diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs index 88db06314a2d8..ee85b417df114 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs @@ -3,12 +3,17 @@ use crate::mesh::Mesh; use super::{Facing, MeshFacingExtension, Meshable}; use bevy_math::primitives::{Circle, RegularPolygon}; +/// A builder used for creating a [`Mesh`] with a [`Circle`] shape. #[derive(Clone, Copy, Debug)] pub struct CircleMesh { - /// The circle shape. + /// The [`Circle`] shape. pub circle: Circle, /// The number of vertices used for the circle mesh. - pub vertices: usize, + /// The default is `32`. + #[doc(alias = "vertices")] + pub resolution: usize, + /// The XYZ direction that the mesh is facing. + /// The default is [`Facing::Z`]. pub facing: Facing, } @@ -16,7 +21,7 @@ impl Default for CircleMesh { fn default() -> Self { Self { circle: Circle::default(), - vertices: 32, + resolution: 32, facing: Facing::Z, } } @@ -31,23 +36,24 @@ impl MeshFacingExtension for CircleMesh { impl CircleMesh { /// Creates a new [`CircleMesh`] from a given radius and vertex count. - pub const fn new(radius: f32, vertices: usize) -> Self { + pub const fn new(radius: f32, resolution: usize) -> Self { Self { circle: Circle { radius }, - vertices, + resolution, facing: Facing::Z, } } - /// Sets the number of vertices used for the circle mesh. - #[doc(alias = "segments")] - pub const fn vertices(mut self, vertices: usize) -> Self { - self.vertices = vertices; + /// Sets the number of resolution used for the circle mesh. + #[doc(alias = "vertices")] + pub const fn resolution(mut self, resolution: usize) -> Self { + self.resolution = resolution; self } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { - RegularPolygon::new(self.circle.radius, self.vertices) + RegularPolygon::new(self.circle.radius, self.resolution) .mesh() .facing(self.facing) .build() @@ -61,7 +67,7 @@ impl CircleMesh { normals: &mut Vec<[f32; 3]>, uvs: &mut Vec<[f32; 2]>, ) { - RegularPolygon::new(self.circle.radius, self.vertices) + RegularPolygon::new(self.circle.radius, self.resolution) .mesh() .facing(self.facing) .build_mesh_data(translation, indices, positions, normals, uvs); diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index b2edbd62fa8a2..3d69fdb3e4278 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -3,10 +3,14 @@ use crate::mesh::{Indices, Mesh}; use bevy_math::primitives::Cone; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`Cone`] shape. #[derive(Clone, Copy, Debug)] pub struct ConeMesh { + /// The [`Cone`] shape. pub cone: Cone, - pub resolution: u32, + /// The number of vertices used for the base of the cone. + /// The default is `32`. + pub resolution: usize, } impl Default for ConeMesh { @@ -19,20 +23,31 @@ impl Default for ConeMesh { } impl ConeMesh { - pub fn resolution(mut self, resolution: usize) -> Self { - self.resolution = resolution as u32; + /// Creates a new [`ConeMesh`] from a given radius, height, + /// and number of vertices used for the base of the cone. + pub const fn new(radius: f32, height: f32, resolution: usize) -> Self { + Self { + cone: Cone { radius, height }, + resolution, + } + } + + /// Sets the number of vertices used for the base of the cone. + pub const fn resolution(mut self, resolution: usize) -> Self { + self.resolution = resolution; self } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { let Cone { radius, height } = self.cone; let num_vertices = self.resolution * 2 + 1; let num_indices = self.resolution * 3; - let mut positions = Vec::with_capacity(num_vertices as usize); - let mut normals = Vec::with_capacity(num_vertices as usize); - let mut uvs = Vec::with_capacity(num_vertices as usize); - let mut indices = Vec::with_capacity(num_indices as usize); + let mut positions = Vec::with_capacity(num_vertices); + let mut normals = Vec::with_capacity(num_vertices); + let mut uvs = Vec::with_capacity(num_vertices); + let mut indices = Vec::with_capacity(num_indices); // Tip positions.push([0.0, self.cone.height / 2.0, 0.0]); @@ -60,13 +75,13 @@ impl ConeMesh { } for j in 0..self.resolution { - indices.extend_from_slice(&[0, j + 1, j]); + indices.extend_from_slice(&[0, j as u32 + 1, j as u32]); } indices.extend(&[0, positions.len() as u32 - 1, positions.len() as u32 - 2]); // Base - let base = CircleMesh::new(radius, self.resolution as usize).facing_neg_y(); + let base = CircleMesh::new(radius, self.resolution).facing_neg_y(); base.build_mesh_data( [0.0, -height / 2.0, 0.0], &mut indices, diff --git a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs index 70464b388fd2e..2f41485460e93 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs @@ -6,11 +6,17 @@ use crate::mesh::{ use bevy_math::{primitives::ConicalFrustum, Vec3}; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`ConicalFrustum`] shape. #[derive(Clone, Copy, Debug)] pub struct ConicalFrustumMesh { + /// The [`ConicalFrustum`] shape. pub frustum: ConicalFrustum, - pub resolution: u32, - pub segments: u32, + /// The number of vertices used for the top and bottom of the conical frustum. + /// The default is `32`. + pub resolution: usize, + /// The number of horizontal lines subdividing the lateral surface of the conical frustum. + /// The default is `1`. + pub segments: usize, } impl Default for ConicalFrustumMesh { @@ -24,16 +30,33 @@ impl Default for ConicalFrustumMesh { } impl ConicalFrustumMesh { - pub fn resolution(mut self, resolution: u32) -> Self { + /// Creates a new [`ConicalFrustumMesh`] from the given top and bottom radii, a height, + /// and a resolution used for the top and bottom. + pub const fn new(radius_top: f32, radius_bottom: f32, height: f32, resolution: usize) -> Self { + Self { + frustum: ConicalFrustum { + radius_top, + radius_bottom, + height, + }, + resolution, + segments: 1, + } + } + + /// Sets the number of vertices used for the top and bottom of the conical frustum. + pub const fn resolution(mut self, resolution: usize) -> Self { self.resolution = resolution; self } - pub fn segments(mut self, segments: u32) -> Self { + /// Sets the number of horizontal lines subdividing the lateral surface of the conical frustum. + pub const fn segments(mut self, segments: usize) -> Self { self.segments = segments; self } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { debug_assert!(self.resolution > 2); debug_assert!(self.segments > 0); @@ -49,17 +72,16 @@ impl ConicalFrustumMesh { let num_faces = self.resolution * (num_rings - 2); let num_indices = (2 * num_faces + 2 * (self.resolution - 1) * 2) * 3; - let mut positions = Vec::with_capacity(num_vertices as usize); - let mut normals = Vec::with_capacity(num_vertices as usize); - let mut uvs = Vec::with_capacity(num_vertices as usize); - let mut indices = Vec::with_capacity(num_indices as usize); + let mut positions = Vec::with_capacity(num_vertices); + let mut normals = Vec::with_capacity(num_vertices); + let mut uvs = Vec::with_capacity(num_vertices); + let mut indices = Vec::with_capacity(num_indices); let step_theta = std::f32::consts::TAU / self.resolution as f32; let step_y = height / self.segments as f32; let step_radius = (radius_top - radius_bottom) / self.segments as f32; - // rings - + // Rings for ring in 0..num_rings { let y = -height / 2.0 + ring as f32 * step_y; let radius = radius_bottom + ring as f32 * step_radius; @@ -81,13 +103,12 @@ impl ConicalFrustumMesh { } } - // barrel skin - - for i in 0..self.segments { - let ring = i * (self.resolution + 1); - let next_ring = (i + 1) * (self.resolution + 1); + // Lateral surface + for i in 0..self.segments as u32 { + let ring = i * (self.resolution + 1) as u32; + let next_ring = (i + 1) * (self.resolution + 1) as u32; - for j in 0..self.resolution { + for j in 0..self.resolution as u32 { indices.extend_from_slice(&[ ring + j, next_ring + j, @@ -99,7 +120,7 @@ impl ConicalFrustumMesh { } } - let top = CircleMesh::new(self.frustum.radius_top, self.resolution as usize).facing_y(); + let top = CircleMesh::new(self.frustum.radius_top, self.resolution).facing_y(); top.build_mesh_data( [0.0, self.frustum.height / 2.0, 0.0], &mut indices, @@ -108,8 +129,7 @@ impl ConicalFrustumMesh { &mut uvs, ); - let bottom = - CircleMesh::new(self.frustum.radius_bottom, self.resolution as usize).facing_neg_y(); + let bottom = CircleMesh::new(self.frustum.radius_bottom, self.resolution).facing_neg_y(); bottom.build_mesh_data( [0.0, -self.frustum.height / 2.0, 0.0], &mut indices, diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs index bed0e53bdc6a7..f8e5c28126599 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs @@ -3,23 +3,27 @@ use crate::mesh::Indices; use bevy_math::primitives::Cuboid; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`Cuboid`] shape. #[derive(Clone, Copy, Debug, Default)] pub struct CuboidMesh { + /// The [`Cuboid`] shape. pub cuboid: Cuboid, } impl CuboidMesh { + /// Creates a new [`CuboidMesh`] from a full `X`, `Y`, and `Z` length. pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self { Self { cuboid: Cuboid::new(x_length, y_length, z_length), } } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { let min = -self.cuboid.half_extents; let max = self.cuboid.half_extents; - // suppose Y-up right hand, and camera look from +z to -z + // Suppose Y-up right hand, and camera look from +Z to -Z let vertices = &[ // Front ([min.x, min.y, max.z], [0.0, 0.0, 1.0], [0.0, 0.0]), diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs index 4f293085518d0..78c142a958744 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs @@ -6,10 +6,17 @@ use crate::mesh::{ use bevy_math::primitives::Cylinder; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`Cylinder`] shape. #[derive(Clone, Copy, Debug)] pub struct CylinderMesh { + /// The [`Cylinder`] shape. pub cylinder: Cylinder, + /// The number of vertices used for the top and bottom of the cylinder. + /// The default is `32`. pub resolution: u32, + /// The number of segments along the height of the cylinder. + /// Must be greater than `0` for geometry to be generated. + /// The default is `1`. pub segments: u32, } @@ -24,16 +31,30 @@ impl Default for CylinderMesh { } impl CylinderMesh { - pub fn resolution(mut self, resolution: u32) -> Self { + /// Creates a new [`CylinderMesh`] from the given radius, a height, + /// and a resolution used for the top and bottom. + pub fn new(radius: f32, height: f32, resolution: u32) -> Self { + Self { + cylinder: Cylinder::new(radius, height), + resolution, + ..Default::default() + } + } + + /// Sets the number of vertices used for the top and bottom of the cylinder. + pub const fn resolution(mut self, resolution: u32) -> Self { self.resolution = resolution; self } - pub fn segments(mut self, segments: u32) -> Self { + /// Sets the number of segments along the height of the cylinder. + /// Must be greater than `0` for geometry to be generated. + pub const fn segments(mut self, segments: u32) -> Self { self.segments = segments; self } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { let resolution = self.resolution; let segments = self.segments; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs index 831e79c4625bf..0044d10aef8dc 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs @@ -4,12 +4,17 @@ use super::{Facing, MeshFacingExtension, Meshable}; use bevy_math::primitives::Ellipse; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with an [`Ellipse`] shape. #[derive(Clone, Copy, Debug)] pub struct EllipseMesh { - /// The ellipse shape. + /// The [`Ellipse`] shape. pub ellipse: Ellipse, /// The number of vertices used for the ellipse mesh. - pub vertices: usize, + /// The default is `32`. + #[doc(alias = "vertices")] + pub resolution: usize, + /// The XYZ direction that the mesh is facing. + /// The default is [`Facing::Z`]. pub facing: Facing, } @@ -17,7 +22,7 @@ impl Default for EllipseMesh { fn default() -> Self { Self { ellipse: Ellipse::default(), - vertices: 32, + resolution: 32, facing: Facing::Z, } } @@ -32,29 +37,30 @@ impl MeshFacingExtension for EllipseMesh { impl EllipseMesh { /// Creates a new [`EllipseMesh`] from a given half width and half height and a vertex count. - pub const fn new(half_width: f32, half_height: f32, vertices: usize) -> Self { + pub const fn new(half_width: f32, half_height: f32, resolution: usize) -> Self { Self { ellipse: Ellipse { half_width, half_height, }, - vertices, + resolution, facing: Facing::Z, } } /// Sets the number of vertices used for the ellipse mesh. - #[doc(alias = "segments")] - pub const fn vertices(mut self, vertices: usize) -> Self { - self.vertices = vertices; + #[doc(alias = "vertices")] + pub const fn resolution(mut self, resolution: usize) -> Self { + self.resolution = resolution; self } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { - let mut indices = Vec::with_capacity((self.vertices - 2) * 3); - let mut positions = Vec::with_capacity(self.vertices); - let mut normals = Vec::with_capacity(self.vertices); - let mut uvs = Vec::with_capacity(self.vertices); + let mut indices = Vec::with_capacity((self.resolution - 2) * 3); + let mut positions = Vec::with_capacity(self.resolution); + let mut normals = Vec::with_capacity(self.resolution); + let mut uvs = Vec::with_capacity(self.resolution); self.build_mesh_data( [0.0; 3], @@ -79,7 +85,7 @@ impl EllipseMesh { normals: &mut Vec<[f32; 3]>, uvs: &mut Vec<[f32; 2]>, ) { - let sides = self.vertices; + let sides = self.resolution; let [trans_x, trans_y, trans_z] = translation; let index_offset = positions.len() as u32; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index e612aa6be972b..747354b744cc7 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -1,3 +1,5 @@ +//! Mesh generation for [primitive shapes](bevy_math::primitives). + #![warn(missing_docs)] mod capsule; @@ -28,20 +30,32 @@ pub use torus::TorusMesh; use super::Mesh; +/// A trait for shapes that can be turned into a [`Mesh`]. pub trait Meshable { + /// The output of [`Self::mesh`]. This can either be a [`Mesh`] + /// or a builder used for creating a [`Mesh`]. type Output; + /// Creates a [`Mesh`] for a shape. fn mesh(&self) -> Self::Output; } +/// The cartesian axis that a [`Mesh`] should be facing upon creation. +/// This is either positive or negative `X`, `Y`, or `Z`. #[derive(Clone, Copy, Debug, Default, PartialEq)] pub enum Facing { + /// Facing the `+X` direction. X = 1, + /// Facing the `+Y` direction. Y = 2, + /// Facing the `+Z` direction. #[default] Z = 3, + /// Facing the `-X` direction. NegX = -1, + /// Facing the `-Y` direction. NegY = -2, + /// Facing the `-Z` direction. NegZ = -3, } @@ -58,7 +72,7 @@ impl Facing { /// /// # Example /// - /// ```rust + /// ``` /// assert_eq!(Facing::X.to_array(), [1.0, 0.0, 0.0]); /// ``` pub const fn to_array(&self) -> [f32; 3] { @@ -73,29 +87,37 @@ impl Facing { } } +/// An extension trait for methods related to setting a specific [`Facing`] direction. pub trait MeshFacingExtension: Sized { + /// Set the [`Facing`] direction. fn facing(self, facing: Facing) -> Self; + /// Set the [`Facing`] direction to `+X`. fn facing_x(self) -> Self { self.facing(Facing::X) } + /// Set the [`Facing`] direction to `+Y`. fn facing_y(self) -> Self { self.facing(Facing::Y) } + /// Set the [`Facing`] direction to `+Z`. fn facing_z(self) -> Self { self.facing(Facing::Z) } + /// Set the [`Facing`] direction to `-X`. fn facing_neg_x(self) -> Self { self.facing(Facing::NegX) } + /// Set the [`Facing`] direction to `-Y`. fn facing_neg_y(self) -> Self { self.facing(Facing::NegY) } + /// Set the [`Facing`] direction to `-Z`. fn facing_neg_z(self) -> Self { self.facing(Facing::NegZ) } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/plane.rs b/crates/bevy_render/src/mesh/primitive_meshes/plane.rs index a40c16f6ba8db..17ab2edc0edfb 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/plane.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/plane.rs @@ -3,9 +3,12 @@ use crate::mesh::Indices; use bevy_math::{primitives::Plane3d, Quat, Vec2, Vec3}; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`Plane3d`] shape. #[derive(Clone, Copy, Debug)] pub struct PlaneMesh { + /// The [`Plane3d`] shape. pub plane: Plane3d, + /// Half the size of the plane mesh. pub half_size: Vec2, } @@ -31,12 +34,14 @@ impl PlaneMesh { } } + /// Sets the size of the plane mesh. pub fn size(mut self, size: Vec2) -> Self { self.half_size = size / 2.0; self } - fn build(&self) -> Mesh { + /// Builds a [`Mesh`] based on the configuration in `self`. + pub fn build(&self) -> Mesh { let rotation = Quat::from_rotation_arc(Vec3::Y, *self.plane.normal); let positions = vec![ rotation * Vec3::new(self.half_size.x, 0.0, -self.half_size.y), diff --git a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs index e7abd8f5317a0..f3a50506c9f56 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs @@ -3,9 +3,13 @@ use crate::mesh::Indices; use bevy_math::primitives::Rectangle; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`Rectangle`] shape. #[derive(Clone, Copy, Debug, Default)] pub struct RectangleMesh { + /// The [`Rectangle`] shape. pub rectangle: Rectangle, + /// The XYZ direction that the mesh is facing. + /// The default is [`Facing::Z`]. pub facing: Facing, } @@ -25,7 +29,8 @@ impl RectangleMesh { } } - fn build(&self) -> Mesh { + /// Builds a [`Mesh`] based on the configuration in `self`. + pub fn build(&self) -> Mesh { let [hw, hh] = [self.rectangle.half_width, self.rectangle.half_height]; let positions = match self.facing { Facing::Z | Facing::NegZ => vec![ diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs index f4b5a21f6a7dd..ca280bcf9f4bc 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs @@ -1,9 +1,13 @@ use super::{Facing, Mesh, MeshFacingExtension, Meshable}; use bevy_math::primitives::{Ellipse, RegularPolygon}; +/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape. #[derive(Clone, Copy, Debug, Default)] pub struct RegularPolygonMesh { + /// The [`RegularPolygon`] shape. pub polygon: RegularPolygon, + /// The XYZ direction that the mesh is facing. + /// The default is [`Facing::Z`]. pub facing: Facing, } @@ -15,6 +19,20 @@ impl MeshFacingExtension for RegularPolygonMesh { } impl RegularPolygonMesh { + /// Creates a new [`RegularPolygonMesh`] from the radius + /// of the circumcircle and a number of sides. + /// + /// # Panics + /// + /// Panics if `circumradius` is non-positive. + pub fn new(circumradius: f32, sides: usize) -> Self { + Self { + polygon: RegularPolygon::new(circumradius, sides), + ..Default::default() + } + } + + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { // The ellipse mesh is just a regular polygon with two radii Ellipse { @@ -22,7 +40,7 @@ impl RegularPolygonMesh { half_height: self.polygon.circumcircle.radius, } .mesh() - .vertices(self.polygon.sides) + .resolution(self.polygon.sides) .facing(self.facing) .build() } @@ -40,7 +58,7 @@ impl RegularPolygonMesh { half_height: self.polygon.circumcircle.radius, } .mesh() - .vertices(self.polygon.sides) + .resolution(self.polygon.sides) .facing(self.facing) .build_mesh_data(translation, indices, positions, normals, uvs); } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs index 38bb6a543b6d7..526a90675a4ab 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs @@ -7,6 +7,7 @@ use hexasphere::shapes::IcoSphere; use thiserror::Error; use wgpu::PrimitiveTopology; +/// An error when creating an icosphere [`Mesh`] from a [`SphereMesh`]. #[derive(Clone, Copy, Debug, Error)] pub enum IcosphereError { #[error("Cannot create an icosphere of {subdivisions} subdivisions due to there being too many vertices being generated: {number_of_resulting_points}. (Limited to 65535 vertices or 79 subdivisions)")] @@ -16,16 +17,23 @@ pub enum IcosphereError { }, } +/// A type of sphere mesh. #[derive(Clone, Copy, Debug)] pub enum SphereKind { + /// An icosphere, a spherical mesh that consists of equally sized triangles. Ico { /// The number of subdivisions applied. + /// The number of faces quadruples with each subdivision. subdivisions: usize, }, + /// A UV sphere, a spherical mesh that consists of quadrilaterals + /// apart from triangles at the top and bottom. Uv { - /// Longitudinal sectors + /// The number of longitudinal sectors, aka the horizontal resolution. + #[doc(alias = "horizontal_resolution")] sectors: usize, - /// Latitudinal stacks + /// The number of latitudinal stacks, aka the vertical resolution. + #[doc(alias = "vertical_resolution")] stacks: usize, }, } @@ -36,33 +44,48 @@ impl Default for SphereKind { } } +/// A builder used for creating a [`Mesh`] with an [`Sphere`] shape. #[derive(Clone, Copy, Debug, Default)] pub struct SphereMesh { + /// The [`Sphere`] shape. pub sphere: Sphere, + /// The type of sphere mesh that will be built. pub kind: SphereKind, } impl SphereMesh { - /// Builds a sphere mesh according to the configuration in `self`. + /// Creates a new [`SphereMesh`] from a radius and [`SphereKind`]. + pub const fn new(radius: f32, kind: SphereKind) -> Self { + Self { + sphere: Sphere { radius }, + kind, + } + } + + /// Sets the [`SphereKind`] that will be used for building the mesh. + pub const fn set_kind(mut self, kind: SphereKind) -> Self { + self.kind = kind; + self + } + + /// Builds a [`Mesh`] according to the configuration in `self`. /// /// # Panics /// /// Panics if the sphere is a `SphereKind::Ico` with a subdivision count /// that is greater than or equal to `80` because there will be too many vertices. - fn build(&self) -> Mesh { + pub fn build(&self) -> Mesh { match self.kind { SphereKind::Ico { subdivisions } => self.ico(subdivisions).unwrap(), SphereKind::Uv { sectors, stacks } => self.uv(sectors, stacks), } } - /// Sets the [`SphereKind`] that will be used for building the mesh. - pub fn set_kind(mut self, kind: SphereKind) -> Self { - self.kind = kind; - self - } - - /// Create an icosphere mesh with the given number of subdivisions. + /// Creates an icosphere mesh with the given number of subdivisions. + /// + /// The number of faces quadruples with each subdivision. + /// If there are `80` or more subdivisions, the vertex count will be too large, + /// and an [`IcosphereError`] is returned. pub fn ico(&self, subdivisions: usize) -> Result { if subdivisions >= 80 { /* @@ -140,8 +163,8 @@ impl SphereMesh { .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)) } - /// Creates a UV sphere mesh with the given number of - /// longitudinal sectors and latitudinal stacks. + /// Creates a UV sphere [`Mesh`] with the given number of + /// longitudinal sectors and latitudinal stacks, aka horizontal and vertical resolution. pub fn uv(&self, sectors: usize, stacks: usize) -> Mesh { // Largely inspired from http://www.songho.ca/opengl/gl_sphere.html diff --git a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs index c96b28ac7ba67..1dbdf618ba69c 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs @@ -3,40 +3,76 @@ use crate::mesh::{Indices, Mesh}; use bevy_math::{primitives::Torus, Vec3}; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`Torus`] shape. #[derive(Clone, Copy, Debug)] pub struct TorusMesh { + /// The [`Torus`] shape. pub torus: Torus, - pub subdivisions_segments: usize, - pub subdivisions_sides: usize, + /// The number of segments used for the main ring of the torus. + /// + /// A resolution of `4` would make the torus appear rectangular, + /// while a resolution of `32` resembles a circular ring. + pub major_resolution: usize, + /// The number of vertices used for each circular segment + /// in the ring or tube of the torus. + pub minor_resolution: usize, } impl Default for TorusMesh { fn default() -> Self { Self { torus: Torus::default(), - subdivisions_segments: 32, - subdivisions_sides: 24, + major_resolution: 32, + minor_resolution: 24, } } } impl TorusMesh { + /// Creates a new [`TorusMesh`] from an inner and outer radius. + /// + /// The inner radius is the radius of the hole, and the outer radius + /// is the radius of the entire object. + pub fn new(inner_radius: f32, outer_radius: f32) -> Self { + Self { + torus: Torus::new(inner_radius, outer_radius), + ..Default::default() + } + } + + /// Sets the number of segments used for the main ring of the torus. + /// + /// A resolution of `4` would make the torus appear rectangular, + /// while a resolution of `32` resembles a circular ring. + pub const fn major_resolution(mut self, resolution: usize) -> Self { + self.major_resolution = resolution; + self + } + + /// Sets the number of vertices used for each circular segment + /// in the ring or tube of the torus. + pub const fn minor_resolution(mut self, resolution: usize) -> Self { + self.minor_resolution = resolution; + self + } + + /// Builds a [`Mesh`] according to the configuration in `self`. pub fn build(&self) -> Mesh { // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html // (source code at https://github.com/SEilers/Apparat) - let n_vertices = (self.subdivisions_segments + 1) * (self.subdivisions_sides + 1); + let n_vertices = (self.major_resolution + 1) * (self.minor_resolution + 1); let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n_vertices); 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.subdivisions_segments as f32; - let side_stride = 2.0 * std::f32::consts::PI / self.subdivisions_sides as f32; + let segment_stride = 2.0 * std::f32::consts::PI / self.major_resolution as f32; + let side_stride = 2.0 * std::f32::consts::PI / self.minor_resolution as f32; - for segment in 0..=self.subdivisions_segments { + for segment in 0..=self.major_resolution { let theta = segment_stride * segment as f32; - for side in 0..=self.subdivisions_sides { + for side in 0..=self.minor_resolution { let phi = side_stride * side as f32; let position = Vec3::new( @@ -55,21 +91,21 @@ impl TorusMesh { positions.push(position.into()); normals.push(normal.into()); uvs.push([ - segment as f32 / self.subdivisions_segments as f32, - side as f32 / self.subdivisions_sides as f32, + segment as f32 / self.major_resolution as f32, + side as f32 / self.minor_resolution as f32, ]); } } - let n_faces = (self.subdivisions_segments) * (self.subdivisions_sides); + let n_faces = (self.major_resolution) * (self.minor_resolution); let n_triangles = n_faces * 2; let n_indices = n_triangles * 3; let mut indices: Vec = Vec::with_capacity(n_indices); - let n_vertices_per_row = self.subdivisions_sides + 1; - for segment in 0..self.subdivisions_segments { - for side in 0..self.subdivisions_sides { + let n_vertices_per_row = self.minor_resolution + 1; + for segment in 0..self.major_resolution { + for side in 0..self.minor_resolution { let lt = side + segment * n_vertices_per_row; let rt = (side + 1) + segment * n_vertices_per_row; @@ -92,16 +128,6 @@ impl TorusMesh { .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } - - pub fn subdivisions_segments(mut self, subdivisions: usize) -> Self { - self.subdivisions_segments = subdivisions; - self - } - - pub fn subdivisions_sides(mut self, subdivisions: usize) -> Self { - self.subdivisions_sides = subdivisions; - self - } } impl Meshable for Torus { diff --git a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs index 1540a5ede17d3..37d1d1436d53a 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs @@ -6,9 +6,13 @@ use bevy_math::{ }; use wgpu::PrimitiveTopology; +/// A builder used for creating a [`Mesh`] with a [`Triangle2d`] shape. #[derive(Clone, Copy, Debug, Default)] pub struct Triangle2dMesh { + /// The [`Triangle2d`] shape. pub triangle: Triangle2d, + /// The XYZ direction that the mesh is facing. + /// The default is [`Facing::Z`]. pub facing: Facing, } @@ -20,7 +24,7 @@ impl MeshFacingExtension for Triangle2dMesh { } impl Triangle2dMesh { - /// Creates a new [`Triangle2dMesh`] from a given radius and vertex count. + /// Creates a new [`Triangle2dMesh`] from points `a`, `b`, and `c`. pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { Self { triangle: Triangle2d::new(a, b, c), @@ -28,6 +32,7 @@ impl Triangle2dMesh { } } + /// Builds a [`Mesh`] based on the configuration in `self`. pub fn build(&self) -> Mesh { let [a, b, c] = self.triangle.vertices; From 45d204c8d8bfc51a45039ee6dece17f74125bcec Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 02:49:09 +0200 Subject: [PATCH 29/36] Inline methods --- crates/bevy_render/src/mesh/primitive_meshes/capsule.rs | 5 +++++ crates/bevy_render/src/mesh/primitive_meshes/circle.rs | 3 +++ crates/bevy_render/src/mesh/primitive_meshes/cone.rs | 2 ++ .../src/mesh/primitive_meshes/conical_frustum.rs | 3 +++ crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs | 1 + crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs | 3 +++ crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs | 3 +++ crates/bevy_render/src/mesh/primitive_meshes/mod.rs | 8 ++++++++ crates/bevy_render/src/mesh/primitive_meshes/plane.rs | 2 ++ crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs | 2 ++ .../src/mesh/primitive_meshes/regular_polygon.rs | 2 ++ crates/bevy_render/src/mesh/primitive_meshes/sphere.rs | 2 ++ crates/bevy_render/src/mesh/primitive_meshes/torus.rs | 3 +++ crates/bevy_render/src/mesh/primitive_meshes/triangle.rs | 2 ++ 14 files changed, 41 insertions(+) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs index 4aafb4b860934..f455d1133d973 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs @@ -52,6 +52,7 @@ impl CapsuleMesh { /// /// Note that `height` is the distance between the centers of the hemispheres. /// `radius` will be added to both ends to get the real height of the mesh. + #[inline] pub fn new(radius: f32, height: f32, longitudes: usize, latitudes: usize) -> Self { Self { capsule: Capsule::new(radius, height), @@ -62,24 +63,28 @@ impl CapsuleMesh { } /// Sets the number of horizontal lines subdividing the cylindrical part of the capsule. + #[inline] pub const fn rings(mut self, rings: usize) -> Self { self.rings = rings; self } /// Sets the number of vertical lines subdividing the hemispheres of the capsule. + #[inline] pub const fn longitudes(mut self, longitudes: usize) -> Self { self.longitudes = longitudes; self } /// Sets the number of horizontal lines subdividing the hemispheres of the capsule. + #[inline] pub const fn latitudes(mut self, latitudes: usize) -> Self { self.latitudes = latitudes; self } /// Sets the manner in which UV coordinates are distributed vertically. + #[inline] pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self { self.uv_profile = uv_profile; self diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs index ee85b417df114..5e88b24f2e1ef 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/circle.rs @@ -28,6 +28,7 @@ impl Default for CircleMesh { } impl MeshFacingExtension for CircleMesh { + #[inline] fn facing(mut self, facing: Facing) -> Self { self.facing = facing; self @@ -36,6 +37,7 @@ impl MeshFacingExtension for CircleMesh { impl CircleMesh { /// Creates a new [`CircleMesh`] from a given radius and vertex count. + #[inline] pub const fn new(radius: f32, resolution: usize) -> Self { Self { circle: Circle { radius }, @@ -45,6 +47,7 @@ impl CircleMesh { } /// Sets the number of resolution used for the circle mesh. + #[inline] #[doc(alias = "vertices")] pub const fn resolution(mut self, resolution: usize) -> Self { self.resolution = resolution; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs index 3d69fdb3e4278..11ac1f89d1412 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cone.rs @@ -25,6 +25,7 @@ impl Default for ConeMesh { impl ConeMesh { /// Creates a new [`ConeMesh`] from a given radius, height, /// and number of vertices used for the base of the cone. + #[inline] pub const fn new(radius: f32, height: f32, resolution: usize) -> Self { Self { cone: Cone { radius, height }, @@ -33,6 +34,7 @@ impl ConeMesh { } /// Sets the number of vertices used for the base of the cone. + #[inline] pub const fn resolution(mut self, resolution: usize) -> Self { self.resolution = resolution; self diff --git a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs index 2f41485460e93..832d96de58958 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs @@ -32,6 +32,7 @@ impl Default for ConicalFrustumMesh { impl ConicalFrustumMesh { /// Creates a new [`ConicalFrustumMesh`] from the given top and bottom radii, a height, /// and a resolution used for the top and bottom. + #[inline] pub const fn new(radius_top: f32, radius_bottom: f32, height: f32, resolution: usize) -> Self { Self { frustum: ConicalFrustum { @@ -45,12 +46,14 @@ impl ConicalFrustumMesh { } /// Sets the number of vertices used for the top and bottom of the conical frustum. + #[inline] pub const fn resolution(mut self, resolution: usize) -> Self { self.resolution = resolution; self } /// Sets the number of horizontal lines subdividing the lateral surface of the conical frustum. + #[inline] pub const fn segments(mut self, segments: usize) -> Self { self.segments = segments; self diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs index f8e5c28126599..8d0d687d01d67 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs @@ -12,6 +12,7 @@ pub struct CuboidMesh { impl CuboidMesh { /// Creates a new [`CuboidMesh`] from a full `X`, `Y`, and `Z` length. + #[inline] pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self { Self { cuboid: Cuboid::new(x_length, y_length, z_length), diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs index 78c142a958744..ebc0f035b3821 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs @@ -33,6 +33,7 @@ impl Default for CylinderMesh { impl CylinderMesh { /// Creates a new [`CylinderMesh`] from the given radius, a height, /// and a resolution used for the top and bottom. + #[inline] pub fn new(radius: f32, height: f32, resolution: u32) -> Self { Self { cylinder: Cylinder::new(radius, height), @@ -42,6 +43,7 @@ impl CylinderMesh { } /// Sets the number of vertices used for the top and bottom of the cylinder. + #[inline] pub const fn resolution(mut self, resolution: u32) -> Self { self.resolution = resolution; self @@ -49,6 +51,7 @@ impl CylinderMesh { /// Sets the number of segments along the height of the cylinder. /// Must be greater than `0` for geometry to be generated. + #[inline] pub const fn segments(mut self, segments: u32) -> Self { self.segments = segments; self diff --git a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs index 0044d10aef8dc..8d6de46c58072 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs @@ -29,6 +29,7 @@ impl Default for EllipseMesh { } impl MeshFacingExtension for EllipseMesh { + #[inline] fn facing(mut self, facing: Facing) -> Self { self.facing = facing; self @@ -37,6 +38,7 @@ impl MeshFacingExtension for EllipseMesh { impl EllipseMesh { /// Creates a new [`EllipseMesh`] from a given half width and half height and a vertex count. + #[inline] pub const fn new(half_width: f32, half_height: f32, resolution: usize) -> Self { Self { ellipse: Ellipse { @@ -49,6 +51,7 @@ impl EllipseMesh { } /// Sets the number of vertices used for the ellipse mesh. + #[inline] #[doc(alias = "vertices")] pub const fn resolution(mut self, resolution: usize) -> Self { self.resolution = resolution; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 747354b744cc7..7f1941fdef869 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -61,6 +61,7 @@ pub enum Facing { impl Facing { /// Returns `1` if the facing direction is positive `X`, `Y`, or `Z`, and `-1` otherwise. + #[inline] pub const fn signum(&self) -> i8 { match self { Facing::X | Facing::Y | Facing::Z => 1, @@ -75,6 +76,7 @@ impl Facing { /// ``` /// assert_eq!(Facing::X.to_array(), [1.0, 0.0, 0.0]); /// ``` + #[inline] pub const fn to_array(&self) -> [f32; 3] { match self { Facing::X => [1.0, 0.0, 0.0], @@ -93,31 +95,37 @@ pub trait MeshFacingExtension: Sized { fn facing(self, facing: Facing) -> Self; /// Set the [`Facing`] direction to `+X`. + #[inline] fn facing_x(self) -> Self { self.facing(Facing::X) } /// Set the [`Facing`] direction to `+Y`. + #[inline] fn facing_y(self) -> Self { self.facing(Facing::Y) } /// Set the [`Facing`] direction to `+Z`. + #[inline] fn facing_z(self) -> Self { self.facing(Facing::Z) } /// Set the [`Facing`] direction to `-X`. + #[inline] fn facing_neg_x(self) -> Self { self.facing(Facing::NegX) } /// Set the [`Facing`] direction to `-Y`. + #[inline] fn facing_neg_y(self) -> Self { self.facing(Facing::NegY) } /// Set the [`Facing`] direction to `-Z`. + #[inline] fn facing_neg_z(self) -> Self { self.facing(Facing::NegZ) } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/plane.rs b/crates/bevy_render/src/mesh/primitive_meshes/plane.rs index 17ab2edc0edfb..e0964771df771 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/plane.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/plane.rs @@ -27,6 +27,7 @@ impl PlaneMesh { /// # Panics /// /// Panics if the given `normal` is zero (or very close to zero), or non-finite. + #[inline] pub fn new(normal: Vec3, size: Vec2) -> Self { Self { plane: Plane3d::new(normal), @@ -35,6 +36,7 @@ impl PlaneMesh { } /// Sets the size of the plane mesh. + #[inline] pub fn size(mut self, size: Vec2) -> Self { self.half_size = size / 2.0; self diff --git a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs index f3a50506c9f56..874a0d85c6112 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs @@ -14,6 +14,7 @@ pub struct RectangleMesh { } impl MeshFacingExtension for RectangleMesh { + #[inline] fn facing(mut self, facing: Facing) -> Self { self.facing = facing; self @@ -22,6 +23,7 @@ impl MeshFacingExtension for RectangleMesh { impl RectangleMesh { /// Creates a new [`RectangleMesh`] from a given radius and vertex count. + #[inline] pub fn new(width: f32, height: f32) -> Self { Self { rectangle: Rectangle::new(width, height), diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs index ca280bcf9f4bc..8e3446a5aedf6 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs @@ -12,6 +12,7 @@ pub struct RegularPolygonMesh { } impl MeshFacingExtension for RegularPolygonMesh { + #[inline] fn facing(mut self, facing: Facing) -> Self { self.facing = facing; self @@ -25,6 +26,7 @@ impl RegularPolygonMesh { /// # Panics /// /// Panics if `circumradius` is non-positive. + #[inline] pub fn new(circumradius: f32, sides: usize) -> Self { Self { polygon: RegularPolygon::new(circumradius, sides), diff --git a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs index 526a90675a4ab..f75198cb616c6 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs @@ -55,6 +55,7 @@ pub struct SphereMesh { impl SphereMesh { /// Creates a new [`SphereMesh`] from a radius and [`SphereKind`]. + #[inline] pub const fn new(radius: f32, kind: SphereKind) -> Self { Self { sphere: Sphere { radius }, @@ -63,6 +64,7 @@ impl SphereMesh { } /// Sets the [`SphereKind`] that will be used for building the mesh. + #[inline] pub const fn set_kind(mut self, kind: SphereKind) -> Self { self.kind = kind; self diff --git a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs index 1dbdf618ba69c..561f78170e324 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs @@ -33,6 +33,7 @@ impl TorusMesh { /// /// The inner radius is the radius of the hole, and the outer radius /// is the radius of the entire object. + #[inline] pub fn new(inner_radius: f32, outer_radius: f32) -> Self { Self { torus: Torus::new(inner_radius, outer_radius), @@ -44,6 +45,7 @@ impl TorusMesh { /// /// A resolution of `4` would make the torus appear rectangular, /// while a resolution of `32` resembles a circular ring. + #[inline] pub const fn major_resolution(mut self, resolution: usize) -> Self { self.major_resolution = resolution; self @@ -51,6 +53,7 @@ impl TorusMesh { /// Sets the number of vertices used for each circular segment /// in the ring or tube of the torus. + #[inline] pub const fn minor_resolution(mut self, resolution: usize) -> Self { self.minor_resolution = resolution; self diff --git a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs index 37d1d1436d53a..3e498016c6561 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs @@ -17,6 +17,7 @@ pub struct Triangle2dMesh { } impl MeshFacingExtension for Triangle2dMesh { + #[inline] fn facing(mut self, facing: Facing) -> Self { self.facing = facing; self @@ -25,6 +26,7 @@ impl MeshFacingExtension for Triangle2dMesh { impl Triangle2dMesh { /// Creates a new [`Triangle2dMesh`] from points `a`, `b`, and `c`. + #[inline] pub const fn new(a: Vec2, b: Vec2, c: Vec2) -> Self { Self { triangle: Triangle2d::new(a, b, c), From 92dc3e96483a73556ea8cf04cc392902f416574a Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 11:52:36 +0200 Subject: [PATCH 30/36] Add re-exports --- crates/bevy_render/src/mesh/primitive_meshes/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs index 7f1941fdef869..9eb76c5b1eeab 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs @@ -25,8 +25,10 @@ pub use cylinder::CylinderMesh; pub use ellipse::EllipseMesh; pub use plane::PlaneMesh; pub use rectangle::RectangleMesh; +pub use regular_polygon::RegularPolygonMesh; pub use sphere::SphereMesh; pub use torus::TorusMesh; +pub use triangle::Triangle2dMesh; use super::Mesh; From 9bf725ea98b5025de538c351ccedf222e0b51e84 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 14:30:42 +0200 Subject: [PATCH 31/36] Revert shader change --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 3e6a380351f1d..66d815db7ec4a 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -48,7 +48,7 @@ fn prepare_world_normal( double_sided: bool, is_front: bool, ) -> vec3 { - var output: vec3 = normalize(world_normal); + var output: vec3 = world_normal; #ifndef VERTEX_TANGENTS #ifndef STANDARDMATERIAL_NORMAL_MAP // NOTE: When NOT using normal-mapping, if looking at the back face of a double-sided From 4a9371be7b5609c35441dc9ad5544b6d5fedbbf2 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 14:32:08 +0200 Subject: [PATCH 32/36] Fix comments --- examples/2d/2d_shapes.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/2d/2d_shapes.rs b/examples/2d/2d_shapes.rs index e9f81a133b152..3e6146ad6ad02 100644 --- a/examples/2d/2d_shapes.rs +++ b/examples/2d/2d_shapes.rs @@ -16,7 +16,7 @@ fn setup( ) { commands.spawn(Camera2dBundle::default()); - // Circle mesh + // Circle commands.spawn(MaterialMesh2dBundle { mesh: meshes .add(primitives::Circle { radius: 50.0 }.into()) @@ -26,7 +26,7 @@ fn setup( ..default() }); - // Rectangle sprite + // Ellipse commands.spawn(MaterialMesh2dBundle { mesh: meshes .add(primitives::Ellipse::new(50.0, 100.0).into()) @@ -36,7 +36,7 @@ fn setup( ..default() }); - // Rectangle mesh + // Rectangle commands.spawn(MaterialMesh2dBundle { mesh: meshes .add(primitives::Rectangle::new(50.0, 100.0).into()) @@ -46,7 +46,7 @@ fn setup( ..default() }); - // Hexagon mesh + // Hexagon commands.spawn(MaterialMesh2dBundle { mesh: meshes .add(primitives::RegularPolygon::new(50.0, 6).into()) From cd85d9bcf972ca9572e45712f83eb3f65f568741 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 18:40:35 +0200 Subject: [PATCH 33/36] Remove old shapes and update examples --- crates/bevy_render/src/lib.rs | 2 +- crates/bevy_render/src/mesh/mod.rs | 2 - .../src/mesh/primitive_meshes/capsule.rs | 440 ------------------ .../src/mesh/primitive_meshes/cylinder.rs | 163 ------- .../src/mesh/primitive_meshes/mod.rs | 134 ------ .../mesh/primitive_meshes/regular_polygon.rs | 90 ---- .../src/mesh/primitive_meshes/torus.rs | 157 ------- crates/bevy_render/src/mesh/shape/capsule.rs | 179 ++++--- .../{primitive_meshes => shape}/circle.rs | 0 .../mesh/{primitive_meshes => shape}/cone.rs | 0 .../conical_frustum.rs | 7 +- .../{primitive_meshes => shape}/cuboid.rs | 0 crates/bevy_render/src/mesh/shape/cylinder.rs | 176 ++++--- .../{primitive_meshes => shape}/ellipse.rs | 0 .../bevy_render/src/mesh/shape/icosphere.rs | 112 ----- crates/bevy_render/src/mesh/shape/mod.rs | 367 +++++---------- .../mesh/{primitive_meshes => shape}/plane.rs | 21 + .../{primitive_meshes => shape}/rectangle.rs | 0 .../src/mesh/shape/regular_polygon.rs | 147 +++--- .../{primitive_meshes => shape}/sphere.rs | 4 + crates/bevy_render/src/mesh/shape/torus.rs | 126 +++-- .../{primitive_meshes => shape}/triangle.rs | 0 crates/bevy_render/src/mesh/shape/uvsphere.rs | 89 ---- errors/B0004.md | 4 +- examples/2d/bloom_2d.rs | 6 +- examples/2d/mesh2d.rs | 8 +- examples/2d/mesh2d_vertex_color_texture.rs | 14 +- examples/3d/3d_gizmos.rs | 4 +- examples/3d/3d_scene.rs | 4 +- examples/3d/3d_shapes.rs | 7 +- examples/3d/3d_viewport_to_world.rs | 7 +- examples/3d/anti_aliasing.rs | 9 +- examples/3d/atmospheric_fog.rs | 2 +- examples/3d/blend_modes.rs | 15 +- examples/3d/bloom_3d.rs | 9 +- examples/3d/deferred_rendering.rs | 18 +- examples/3d/fog.rs | 32 +- examples/3d/lighting.rs | 36 +- examples/3d/orthographic.rs | 12 +- examples/3d/parallax_mapping.rs | 22 +- examples/3d/parenting.rs | 2 +- examples/3d/pbr.rs | 8 +- examples/3d/render_to_texture.rs | 6 +- examples/3d/shadow_biases.rs | 14 +- examples/3d/shadow_caster_receiver.rs | 23 +- examples/3d/spherical_area_lights.rs | 13 +- examples/3d/split_screen.rs | 7 +- examples/3d/spotlight.rs | 19 +- examples/3d/ssao.rs | 12 +- examples/3d/texture.rs | 4 +- examples/3d/tonemapping.rs | 29 +- examples/3d/transmission.rs | 29 +- examples/3d/transparency_3d.rs | 33 +- examples/3d/two_passes.rs | 9 +- examples/3d/vertex_colors.rs | 9 +- examples/3d/wireframe.rs | 10 +- examples/animation/animated_fox.rs | 7 +- examples/animation/animated_transform.rs | 5 +- examples/animation/cubic_curve.rs | 9 +- examples/async_tasks/async_compute.rs | 2 +- examples/audio/spatial_audio_2d.rs | 4 +- examples/audio/spatial_audio_3d.rs | 9 +- examples/ecs/iter_combinations.rs | 17 +- examples/games/breakout.rs | 2 +- examples/mobile/src/lib.rs | 17 +- examples/shader/animate_shader.rs | 2 +- examples/shader/array_texture.rs | 2 +- examples/shader/custom_vertex_attribute.rs | 2 +- examples/shader/extended_material.rs | 8 +- examples/shader/fallback_image.rs | 2 +- examples/shader/post_processing.rs | 2 +- examples/shader/shader_defs.rs | 4 +- examples/shader/shader_instancing.rs | 2 +- examples/shader/shader_material.rs | 2 +- examples/shader/shader_material_2d.rs | 4 +- examples/shader/shader_material_glsl.rs | 2 +- .../shader_material_screenspace_texture.rs | 9 +- examples/shader/shader_prepass.rs | 15 +- examples/shader/texture_binding_array.rs | 2 +- examples/stress_tests/bevymark.rs | 2 +- examples/stress_tests/many_cubes.rs | 2 +- examples/stress_tests/many_foxes.rs | 7 +- examples/stress_tests/many_lights.rs | 10 +- examples/tools/gamepad_viewer.rs | 19 +- examples/transforms/3d_rotation.rs | 2 +- examples/transforms/scale.rs | 2 +- examples/transforms/transform.rs | 10 +- examples/transforms/translation.rs | 2 +- examples/window/low_power.rs | 2 +- examples/window/screenshot.rs | 9 +- tests/window/minimising.rs | 9 +- tests/window/resizing.rs | 9 +- 92 files changed, 849 insertions(+), 2018 deletions(-) delete mode 100644 crates/bevy_render/src/mesh/primitive_meshes/capsule.rs delete mode 100644 crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs delete mode 100644 crates/bevy_render/src/mesh/primitive_meshes/mod.rs delete mode 100644 crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs delete mode 100644 crates/bevy_render/src/mesh/primitive_meshes/torus.rs rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/circle.rs (100%) rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/cone.rs (100%) rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/conical_frustum.rs (97%) rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/cuboid.rs (100%) rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/ellipse.rs (100%) delete mode 100644 crates/bevy_render/src/mesh/shape/icosphere.rs rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/plane.rs (80%) rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/rectangle.rs (100%) rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/sphere.rs (98%) rename crates/bevy_render/src/mesh/{primitive_meshes => shape}/triangle.rs (100%) delete mode 100644 crates/bevy_render/src/mesh/shape/uvsphere.rs diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 8dfef7c7c55e8..aecc4003e161c 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -29,7 +29,7 @@ pub mod prelude { pub use crate::{ camera::{Camera, OrthographicProjection, PerspectiveProjection, Projection}, color::Color, - mesh::{morph::MorphWeights, primitive_meshes::*, shape, Mesh}, + mesh::{morph::MorphWeights, shape, shape::*, Mesh}, render_resource::Shader, spatial_bundle::SpatialBundle, texture::{Image, ImagePlugin}, diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 556b549e34d81..cf2703389ce47 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -1,8 +1,6 @@ #[allow(clippy::module_inception)] mod mesh; pub mod morph; -pub mod primitive_meshes; -/// Generation for some primitive shape meshes. pub mod shape; pub use mesh::*; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs b/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs deleted file mode 100644 index f455d1133d973..0000000000000 --- a/crates/bevy_render/src/mesh/primitive_meshes/capsule.rs +++ /dev/null @@ -1,440 +0,0 @@ -use super::Meshable; -use crate::mesh::{Indices, Mesh}; -use bevy_math::{primitives::Capsule, Vec2, Vec3}; -use wgpu::PrimitiveTopology; - -/// Manner in which UV coordinates are distributed vertically. -#[derive(Clone, Copy, Debug, Default)] -pub enum CapsuleUvProfile { - /// UV space is distributed by how much of the capsule consists of the hemispheres. - #[default] - Aspect, - /// Hemispheres get UV space according to the ratio of latitudes to rings. - Uniform, - /// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder - /// and lower third to the southern one. - Fixed, -} - -/// A builder used for creating a [`Mesh`] with a [`Capsule`] shape. -#[derive(Clone, Copy, Debug)] -pub struct CapsuleMesh { - /// The [`Capsule`] shape. - pub capsule: Capsule, - /// The number of horizontal lines subdividing the cylindrical part of the capsule. - /// The default is `0`. - pub rings: usize, - /// The number of vertical lines subdividing the hemispheres of the capsule. - /// The default is `32`. - pub longitudes: usize, - /// The number of horizontal lines subdividing the hemispheres of the capsule. - /// The default is `16`. - pub latitudes: usize, - /// The manner in which UV coordinates are distributed vertically. - /// The default is [`CapsuleUvProfile::Aspect`]. - pub uv_profile: CapsuleUvProfile, -} - -impl Default for CapsuleMesh { - fn default() -> Self { - Self { - capsule: Capsule::default(), - rings: 0, - longitudes: 32, - latitudes: 16, - uv_profile: CapsuleUvProfile::default(), - } - } -} - -impl CapsuleMesh { - /// Creates a new [`CapsuleMesh`] from a given radius, height, longitudes and latitudes. - /// - /// Note that `height` is the distance between the centers of the hemispheres. - /// `radius` will be added to both ends to get the real height of the mesh. - #[inline] - pub fn new(radius: f32, height: f32, longitudes: usize, latitudes: usize) -> Self { - Self { - capsule: Capsule::new(radius, height), - longitudes, - latitudes, - ..Default::default() - } - } - - /// Sets the number of horizontal lines subdividing the cylindrical part of the capsule. - #[inline] - pub const fn rings(mut self, rings: usize) -> Self { - self.rings = rings; - self - } - - /// Sets the number of vertical lines subdividing the hemispheres of the capsule. - #[inline] - pub const fn longitudes(mut self, longitudes: usize) -> Self { - self.longitudes = longitudes; - self - } - - /// Sets the number of horizontal lines subdividing the hemispheres of the capsule. - #[inline] - pub const fn latitudes(mut self, latitudes: usize) -> Self { - self.latitudes = latitudes; - self - } - - /// Sets the manner in which UV coordinates are distributed vertically. - #[inline] - pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self { - self.uv_profile = uv_profile; - self - } - - /// Builds a [`Mesh`] based on the configuration in `self`. - pub fn build(&self) -> Mesh { - // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db - let CapsuleMesh { - capsule, - rings, - longitudes, - latitudes, - uv_profile, - } = *self; - let Capsule { - radius, - half_length, - } = capsule; - - let calc_middle = rings > 0; - let half_lats = latitudes / 2; - let half_latsn1 = half_lats - 1; - let half_latsn2 = half_lats - 2; - let ringsp1 = rings + 1; - let lonsp1 = longitudes + 1; - let summit = half_length + radius; - - // Vertex index offsets. - let vert_offset_north_hemi = longitudes; - let vert_offset_north_equator = vert_offset_north_hemi + lonsp1 * half_latsn1; - let vert_offset_cylinder = vert_offset_north_equator + lonsp1; - let vert_offset_south_equator = if calc_middle { - vert_offset_cylinder + lonsp1 * rings - } else { - vert_offset_cylinder - }; - let vert_offset_south_hemi = vert_offset_south_equator + lonsp1; - let vert_offset_south_polar = vert_offset_south_hemi + lonsp1 * half_latsn2; - let vert_offset_south_cap = vert_offset_south_polar + lonsp1; - - // Initialize arrays. - let vert_len = vert_offset_south_cap + longitudes; - - let mut vs: Vec = vec![Vec3::ZERO; vert_len]; - let mut vts: Vec = vec![Vec2::ZERO; vert_len]; - let mut vns: Vec = vec![Vec3::ZERO; vert_len]; - - let to_theta = 2.0 * std::f32::consts::PI / longitudes as f32; - let to_phi = std::f32::consts::PI / latitudes as f32; - let to_tex_horizontal = 1.0 / longitudes as f32; - let to_tex_vertical = 1.0 / half_lats as f32; - - let vt_aspect_ratio = match uv_profile { - CapsuleUvProfile::Aspect => radius / (2.0 * half_length + radius + radius), - CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32, - CapsuleUvProfile::Fixed => 1.0 / 3.0, - }; - let vt_aspect_north = 1.0 - vt_aspect_ratio; - let vt_aspect_south = vt_aspect_ratio; - - let mut theta_cartesian: Vec = vec![Vec2::ZERO; longitudes]; - let mut rho_theta_cartesian: Vec = vec![Vec2::ZERO; longitudes]; - let mut s_texture_cache: Vec = vec![0.0; lonsp1]; - - for j in 0..longitudes { - let jf = j as f32; - let s_texture_polar = 1.0 - ((jf + 0.5) * to_tex_horizontal); - let theta = jf * to_theta; - - let cos_theta = theta.cos(); - let sin_theta = theta.sin(); - - theta_cartesian[j] = Vec2::new(cos_theta, sin_theta); - rho_theta_cartesian[j] = Vec2::new(radius * cos_theta, radius * sin_theta); - - // North. - vs[j] = Vec3::new(0.0, summit, 0.0); - vts[j] = Vec2::new(s_texture_polar, 1.0); - vns[j] = Vec3::Y; - - // South. - let idx = vert_offset_south_cap + j; - vs[idx] = Vec3::new(0.0, -summit, 0.0); - vts[idx] = Vec2::new(s_texture_polar, 0.0); - vns[idx] = Vec3::new(0.0, -1.0, 0.0); - } - - // Equatorial vertices. - for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1) { - let s_texture = 1.0 - j as f32 * to_tex_horizontal; - *s_texture_cache_j = s_texture; - - // Wrap to first element upon reaching last. - let j_mod = j % longitudes; - let tc = theta_cartesian[j_mod]; - let rtc = rho_theta_cartesian[j_mod]; - - // North equator. - let idxn = vert_offset_north_equator + j; - vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y); - vts[idxn] = Vec2::new(s_texture, vt_aspect_north); - vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y); - - // South equator. - let idxs = vert_offset_south_equator + j; - vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y); - vts[idxs] = Vec2::new(s_texture, vt_aspect_south); - vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y); - } - - // Hemisphere vertices. - for i in 0..half_latsn1 { - let ip1f = i as f32 + 1.0; - let phi = ip1f * to_phi; - - // For coordinates. - let cos_phi_south = phi.cos(); - let sin_phi_south = phi.sin(); - - // Symmetrical hemispheres mean cosine and sine only needs - // to be calculated once. - let cos_phi_north = sin_phi_south; - let sin_phi_north = -cos_phi_south; - - let rho_cos_phi_north = radius * cos_phi_north; - let rho_sin_phi_north = radius * sin_phi_north; - let z_offset_north = half_length - rho_sin_phi_north; - - let rho_cos_phi_south = radius * cos_phi_south; - let rho_sin_phi_south = radius * sin_phi_south; - let z_offset_sout = -half_length - rho_sin_phi_south; - - // For texture coordinates. - let t_tex_fac = ip1f * to_tex_vertical; - let cmpl_tex_fac = 1.0 - t_tex_fac; - let t_tex_north = cmpl_tex_fac + vt_aspect_north * t_tex_fac; - let t_tex_south = cmpl_tex_fac * vt_aspect_south; - - let i_lonsp1 = i * lonsp1; - let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1; - let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1; - - for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) { - let j_mod = j % longitudes; - - let tc = theta_cartesian[j_mod]; - - // North hemisphere. - let idxn = vert_curr_lat_north + j; - vs[idxn] = Vec3::new( - rho_cos_phi_north * tc.x, - z_offset_north, - -rho_cos_phi_north * tc.y, - ); - vts[idxn] = Vec2::new(*s_texture, t_tex_north); - vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y); - - // South hemisphere. - let idxs = vert_curr_lat_south + j; - vs[idxs] = Vec3::new( - rho_cos_phi_south * tc.x, - z_offset_sout, - -rho_cos_phi_south * tc.y, - ); - vts[idxs] = Vec2::new(*s_texture, t_tex_south); - vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y); - } - } - - // Cylinder vertices. - if calc_middle { - // Exclude both origin and destination edges - // (North and South equators) from the interpolation. - let to_fac = 1.0 / ringsp1 as f32; - let mut idx_cyl_lat = vert_offset_cylinder; - - for h in 1..ringsp1 { - let fac = h as f32 * to_fac; - let cmpl_fac = 1.0 - fac; - let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south; - let z = half_length - 2.0 * half_length * fac; - - for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) { - let j_mod = j % longitudes; - let tc = theta_cartesian[j_mod]; - let rtc = rho_theta_cartesian[j_mod]; - - vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y); - vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture); - vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y); - - idx_cyl_lat += 1; - } - } - } - - // Triangle indices. - - // Stride is 3 for polar triangles; - // stride is 6 for two triangles forming a quad. - let lons3 = longitudes * 3; - let lons6 = longitudes * 6; - let hemi_lons = half_latsn1 * lons6; - - let tri_offset_north_hemi = lons3; - let tri_offset_cylinder = tri_offset_north_hemi + hemi_lons; - let tri_offset_south_hemi = tri_offset_cylinder + ringsp1 * lons6; - let tri_offset_south_cap = tri_offset_south_hemi + hemi_lons; - - let fs_len = tri_offset_south_cap + lons3; - let mut tris: Vec = vec![0; fs_len]; - - // Polar caps. - let mut i = 0; - let mut k = 0; - let mut m = tri_offset_south_cap; - while i < longitudes { - // North. - tris[k] = i as u32; - tris[k + 1] = (vert_offset_north_hemi + i) as u32; - tris[k + 2] = (vert_offset_north_hemi + i + 1) as u32; - - // South. - tris[m] = (vert_offset_south_cap + i) as u32; - tris[m + 1] = (vert_offset_south_polar + i + 1) as u32; - tris[m + 2] = (vert_offset_south_polar + i) as u32; - - i += 1; - k += 3; - m += 3; - } - - // Hemispheres. - - let mut i = 0; - let mut k = tri_offset_north_hemi; - let mut m = tri_offset_south_hemi; - - while i < half_latsn1 { - let i_lonsp1 = i * lonsp1; - - let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1; - let vert_next_lat_north = vert_curr_lat_north + lonsp1; - - let vert_curr_lat_south = vert_offset_south_equator + i_lonsp1; - let vert_next_lat_south = vert_curr_lat_south + lonsp1; - - let mut j = 0; - while j < longitudes { - // North. - let north00 = vert_curr_lat_north + j; - let north01 = vert_next_lat_north + j; - let north11 = vert_next_lat_north + j + 1; - let north10 = vert_curr_lat_north + j + 1; - - tris[k] = north00 as u32; - tris[k + 1] = north11 as u32; - tris[k + 2] = north10 as u32; - - tris[k + 3] = north00 as u32; - tris[k + 4] = north01 as u32; - tris[k + 5] = north11 as u32; - - // South. - let south00 = vert_curr_lat_south + j; - let south01 = vert_next_lat_south + j; - let south11 = vert_next_lat_south + j + 1; - let south10 = vert_curr_lat_south + j + 1; - - tris[m] = south00 as u32; - tris[m + 1] = south11 as u32; - tris[m + 2] = south10 as u32; - - tris[m + 3] = south00 as u32; - tris[m + 4] = south01 as u32; - tris[m + 5] = south11 as u32; - - j += 1; - k += 6; - m += 6; - } - - i += 1; - } - - // Cylinder. - let mut i = 0; - let mut k = tri_offset_cylinder; - - while i < ringsp1 { - let vert_curr_lat = vert_offset_north_equator + i * lonsp1; - let vert_next_lat = vert_curr_lat + lonsp1; - - let mut j = 0; - while j < longitudes { - let cy00 = vert_curr_lat + j; - let cy01 = vert_next_lat + j; - let cy11 = vert_next_lat + j + 1; - let cy10 = vert_curr_lat + j + 1; - - tris[k] = cy00 as u32; - tris[k + 1] = cy11 as u32; - tris[k + 2] = cy10 as u32; - - tris[k + 3] = cy00 as u32; - tris[k + 4] = cy01 as u32; - tris[k + 5] = cy11 as u32; - - j += 1; - k += 6; - } - - i += 1; - } - - let vs: Vec<[f32; 3]> = vs.into_iter().map(Into::into).collect(); - let vns: Vec<[f32; 3]> = vns.into_iter().map(Into::into).collect(); - let vts: Vec<[f32; 2]> = vts.into_iter().map(Into::into).collect(); - - assert_eq!(vs.len(), vert_len); - assert_eq!(tris.len(), fs_len); - - 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))) - } -} - -impl Meshable for Capsule { - type Output = CapsuleMesh; - - fn mesh(&self) -> Self::Output { - CapsuleMesh { - capsule: *self, - ..Default::default() - } - } -} - -impl From for Mesh { - fn from(capsule: Capsule) -> Self { - capsule.mesh().build() - } -} - -impl From for Mesh { - fn from(capsule: CapsuleMesh) -> Self { - capsule.build() - } -} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs b/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs deleted file mode 100644 index ebc0f035b3821..0000000000000 --- a/crates/bevy_render/src/mesh/primitive_meshes/cylinder.rs +++ /dev/null @@ -1,163 +0,0 @@ -use super::Meshable; -use crate::mesh::{ - primitive_meshes::{CircleMesh, MeshFacingExtension}, - Indices, Mesh, -}; -use bevy_math::primitives::Cylinder; -use wgpu::PrimitiveTopology; - -/// A builder used for creating a [`Mesh`] with a [`Cylinder`] shape. -#[derive(Clone, Copy, Debug)] -pub struct CylinderMesh { - /// The [`Cylinder`] shape. - pub cylinder: Cylinder, - /// The number of vertices used for the top and bottom of the cylinder. - /// The default is `32`. - pub resolution: u32, - /// The number of segments along the height of the cylinder. - /// Must be greater than `0` for geometry to be generated. - /// The default is `1`. - pub segments: u32, -} - -impl Default for CylinderMesh { - fn default() -> Self { - Self { - cylinder: Cylinder::default(), - resolution: 32, - segments: 1, - } - } -} - -impl CylinderMesh { - /// Creates a new [`CylinderMesh`] from the given radius, a height, - /// and a resolution used for the top and bottom. - #[inline] - pub fn new(radius: f32, height: f32, resolution: u32) -> Self { - Self { - cylinder: Cylinder::new(radius, height), - resolution, - ..Default::default() - } - } - - /// Sets the number of vertices used for the top and bottom of the cylinder. - #[inline] - pub const fn resolution(mut self, resolution: u32) -> Self { - self.resolution = resolution; - self - } - - /// Sets the number of segments along the height of the cylinder. - /// Must be greater than `0` for geometry to be generated. - #[inline] - pub const fn segments(mut self, segments: u32) -> Self { - self.segments = segments; - self - } - - /// Builds a [`Mesh`] based on the configuration in `self`. - pub fn build(&self) -> Mesh { - let resolution = self.resolution; - let segments = self.segments; - - debug_assert!(resolution > 2); - debug_assert!(segments > 0); - - let num_rings = segments + 1; - let num_vertices = resolution * 2 + num_rings * (resolution + 1); - let num_faces = resolution * (num_rings - 2); - let num_indices = (2 * num_faces + 2 * (resolution - 1) * 2) * 3; - - let mut positions = Vec::with_capacity(num_vertices as usize); - let mut normals = Vec::with_capacity(num_vertices as usize); - let mut uvs = Vec::with_capacity(num_vertices as usize); - let mut indices = Vec::with_capacity(num_indices as usize); - - let step_theta = std::f32::consts::TAU / resolution as f32; - let step_y = 2.0 * self.cylinder.half_height / segments as f32; - - // rings - - for ring in 0..num_rings { - let y = -self.cylinder.half_height + ring as f32 * step_y; - - for segment in 0..=resolution { - let theta = segment as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); - - positions.push([self.cylinder.radius * cos, y, self.cylinder.radius * sin]); - normals.push([cos, 0., sin]); - uvs.push([ - segment as f32 / resolution as f32, - ring as f32 / segments as f32, - ]); - } - } - - // barrel skin - - for i in 0..segments { - let ring = i * (resolution + 1); - let next_ring = (i + 1) * (resolution + 1); - - for j in 0..resolution { - indices.extend_from_slice(&[ - ring + j, - next_ring + j, - ring + j + 1, - next_ring + j, - next_ring + j + 1, - ring + j + 1, - ]); - } - } - - // Top and bottom - let base = CircleMesh::new(self.cylinder.radius, self.resolution as usize).facing_y(); - base.build_mesh_data( - [0.0, self.cylinder.half_height, 0.0], - &mut indices, - &mut positions, - &mut normals, - &mut uvs, - ); - base.facing_neg_y().build_mesh_data( - [0.0, -self.cylinder.half_height, 0.0], - &mut indices, - &mut positions, - &mut normals, - &mut uvs, - ); - - 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) - } -} - -impl Meshable for Cylinder { - type Output = CylinderMesh; - - fn mesh(&self) -> Self::Output { - CylinderMesh { - cylinder: *self, - ..Default::default() - } - } -} - -impl From for Mesh { - fn from(cylinder: Cylinder) -> Self { - cylinder.mesh().build() - } -} - -impl From for Mesh { - fn from(cylinder: CylinderMesh) -> Self { - cylinder.build() - } -} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs b/crates/bevy_render/src/mesh/primitive_meshes/mod.rs deleted file mode 100644 index 9eb76c5b1eeab..0000000000000 --- a/crates/bevy_render/src/mesh/primitive_meshes/mod.rs +++ /dev/null @@ -1,134 +0,0 @@ -//! Mesh generation for [primitive shapes](bevy_math::primitives). - -#![warn(missing_docs)] - -mod capsule; -mod circle; -mod cone; -mod conical_frustum; -mod cuboid; -mod cylinder; -mod ellipse; -mod plane; -mod rectangle; -mod regular_polygon; -mod sphere; -mod torus; -mod triangle; - -pub use capsule::CapsuleMesh; -pub use circle::CircleMesh; -pub use cone::ConeMesh; -pub use conical_frustum::ConicalFrustumMesh; -pub use cuboid::CuboidMesh; -pub use cylinder::CylinderMesh; -pub use ellipse::EllipseMesh; -pub use plane::PlaneMesh; -pub use rectangle::RectangleMesh; -pub use regular_polygon::RegularPolygonMesh; -pub use sphere::SphereMesh; -pub use torus::TorusMesh; -pub use triangle::Triangle2dMesh; - -use super::Mesh; - -/// A trait for shapes that can be turned into a [`Mesh`]. -pub trait Meshable { - /// The output of [`Self::mesh`]. This can either be a [`Mesh`] - /// or a builder used for creating a [`Mesh`]. - type Output; - - /// Creates a [`Mesh`] for a shape. - fn mesh(&self) -> Self::Output; -} - -/// The cartesian axis that a [`Mesh`] should be facing upon creation. -/// This is either positive or negative `X`, `Y`, or `Z`. -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub enum Facing { - /// Facing the `+X` direction. - X = 1, - /// Facing the `+Y` direction. - Y = 2, - /// Facing the `+Z` direction. - #[default] - Z = 3, - /// Facing the `-X` direction. - NegX = -1, - /// Facing the `-Y` direction. - NegY = -2, - /// Facing the `-Z` direction. - NegZ = -3, -} - -impl Facing { - /// Returns `1` if the facing direction is positive `X`, `Y`, or `Z`, and `-1` otherwise. - #[inline] - pub const fn signum(&self) -> i8 { - match self { - Facing::X | Facing::Y | Facing::Z => 1, - _ => -1, - } - } - - /// Returns the direction in as an array in the format `[x, y, z]`. - /// - /// # Example - /// - /// ``` - /// assert_eq!(Facing::X.to_array(), [1.0, 0.0, 0.0]); - /// ``` - #[inline] - pub const fn to_array(&self) -> [f32; 3] { - match self { - Facing::X => [1.0, 0.0, 0.0], - Facing::Y => [0.0, 1.0, 0.0], - Facing::Z => [0.0, 0.0, 1.0], - Facing::NegX => [-1.0, 0.0, 0.0], - Facing::NegY => [0.0, -1.0, 0.0], - Facing::NegZ => [0.0, 0.0, -1.0], - } - } -} - -/// An extension trait for methods related to setting a specific [`Facing`] direction. -pub trait MeshFacingExtension: Sized { - /// Set the [`Facing`] direction. - fn facing(self, facing: Facing) -> Self; - - /// Set the [`Facing`] direction to `+X`. - #[inline] - fn facing_x(self) -> Self { - self.facing(Facing::X) - } - - /// Set the [`Facing`] direction to `+Y`. - #[inline] - fn facing_y(self) -> Self { - self.facing(Facing::Y) - } - - /// Set the [`Facing`] direction to `+Z`. - #[inline] - fn facing_z(self) -> Self { - self.facing(Facing::Z) - } - - /// Set the [`Facing`] direction to `-X`. - #[inline] - fn facing_neg_x(self) -> Self { - self.facing(Facing::NegX) - } - - /// Set the [`Facing`] direction to `-Y`. - #[inline] - fn facing_neg_y(self) -> Self { - self.facing(Facing::NegY) - } - - /// Set the [`Facing`] direction to `-Z`. - #[inline] - fn facing_neg_z(self) -> Self { - self.facing(Facing::NegZ) - } -} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs b/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs deleted file mode 100644 index 8e3446a5aedf6..0000000000000 --- a/crates/bevy_render/src/mesh/primitive_meshes/regular_polygon.rs +++ /dev/null @@ -1,90 +0,0 @@ -use super::{Facing, Mesh, MeshFacingExtension, Meshable}; -use bevy_math::primitives::{Ellipse, RegularPolygon}; - -/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape. -#[derive(Clone, Copy, Debug, Default)] -pub struct RegularPolygonMesh { - /// The [`RegularPolygon`] shape. - pub polygon: RegularPolygon, - /// The XYZ direction that the mesh is facing. - /// The default is [`Facing::Z`]. - pub facing: Facing, -} - -impl MeshFacingExtension for RegularPolygonMesh { - #[inline] - fn facing(mut self, facing: Facing) -> Self { - self.facing = facing; - self - } -} - -impl RegularPolygonMesh { - /// Creates a new [`RegularPolygonMesh`] from the radius - /// of the circumcircle and a number of sides. - /// - /// # Panics - /// - /// Panics if `circumradius` is non-positive. - #[inline] - pub fn new(circumradius: f32, sides: usize) -> Self { - Self { - polygon: RegularPolygon::new(circumradius, sides), - ..Default::default() - } - } - - /// Builds a [`Mesh`] based on the configuration in `self`. - pub fn build(&self) -> Mesh { - // The ellipse mesh is just a regular polygon with two radii - Ellipse { - half_width: self.polygon.circumcircle.radius, - half_height: self.polygon.circumcircle.radius, - } - .mesh() - .resolution(self.polygon.sides) - .facing(self.facing) - .build() - } - - pub(super) fn build_mesh_data( - &self, - translation: [f32; 3], - indices: &mut Vec, - positions: &mut Vec<[f32; 3]>, - normals: &mut Vec<[f32; 3]>, - uvs: &mut Vec<[f32; 2]>, - ) { - Ellipse { - half_width: self.polygon.circumcircle.radius, - half_height: self.polygon.circumcircle.radius, - } - .mesh() - .resolution(self.polygon.sides) - .facing(self.facing) - .build_mesh_data(translation, indices, positions, normals, uvs); - } -} - -impl Meshable for RegularPolygon { - type Output = RegularPolygonMesh; - - fn mesh(&self) -> Self::Output { - RegularPolygonMesh { - polygon: *self, - ..Default::default() - } - } -} - -impl From for Mesh { - fn from(polygon: RegularPolygon) -> Self { - polygon.mesh().build() - } -} - -impl From for Mesh { - fn from(polygon: RegularPolygonMesh) -> Self { - polygon.build() - } -} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs b/crates/bevy_render/src/mesh/primitive_meshes/torus.rs deleted file mode 100644 index 561f78170e324..0000000000000 --- a/crates/bevy_render/src/mesh/primitive_meshes/torus.rs +++ /dev/null @@ -1,157 +0,0 @@ -use super::Meshable; -use crate::mesh::{Indices, Mesh}; -use bevy_math::{primitives::Torus, Vec3}; -use wgpu::PrimitiveTopology; - -/// A builder used for creating a [`Mesh`] with a [`Torus`] shape. -#[derive(Clone, Copy, Debug)] -pub struct TorusMesh { - /// The [`Torus`] shape. - pub torus: Torus, - /// The number of segments used for the main ring of the torus. - /// - /// A resolution of `4` would make the torus appear rectangular, - /// while a resolution of `32` resembles a circular ring. - pub major_resolution: usize, - /// The number of vertices used for each circular segment - /// in the ring or tube of the torus. - pub minor_resolution: usize, -} - -impl Default for TorusMesh { - fn default() -> Self { - Self { - torus: Torus::default(), - major_resolution: 32, - minor_resolution: 24, - } - } -} - -impl TorusMesh { - /// Creates a new [`TorusMesh`] from an inner and outer radius. - /// - /// The inner radius is the radius of the hole, and the outer radius - /// is the radius of the entire object. - #[inline] - pub fn new(inner_radius: f32, outer_radius: f32) -> Self { - Self { - torus: Torus::new(inner_radius, outer_radius), - ..Default::default() - } - } - - /// Sets the number of segments used for the main ring of the torus. - /// - /// A resolution of `4` would make the torus appear rectangular, - /// while a resolution of `32` resembles a circular ring. - #[inline] - pub const fn major_resolution(mut self, resolution: usize) -> Self { - self.major_resolution = resolution; - self - } - - /// Sets the number of vertices used for each circular segment - /// in the ring or tube of the torus. - #[inline] - pub const fn minor_resolution(mut self, resolution: usize) -> Self { - self.minor_resolution = resolution; - self - } - - /// Builds a [`Mesh`] according to the configuration in `self`. - pub fn build(&self) -> Mesh { - // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html - // (source code at https://github.com/SEilers/Apparat) - - let n_vertices = (self.major_resolution + 1) * (self.minor_resolution + 1); - let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n_vertices); - 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 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; - - for side in 0..=self.minor_resolution { - let phi = side_stride * side as f32; - - let position = Vec3::new( - theta.cos() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), - self.torus.minor_radius * phi.sin(), - theta.sin() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), - ); - - let center = Vec3::new( - self.torus.major_radius * theta.cos(), - 0., - self.torus.major_radius * theta.sin(), - ); - let normal = (position - center).normalize(); - - positions.push(position.into()); - normals.push(normal.into()); - uvs.push([ - segment as f32 / self.major_resolution as f32, - side as f32 / self.minor_resolution as f32, - ]); - } - } - - let n_faces = (self.major_resolution) * (self.minor_resolution); - let n_triangles = n_faces * 2; - let n_indices = n_triangles * 3; - - let mut indices: Vec = Vec::with_capacity(n_indices); - - let n_vertices_per_row = self.minor_resolution + 1; - for segment in 0..self.major_resolution { - for side in 0..self.minor_resolution { - let lt = side + segment * n_vertices_per_row; - let rt = (side + 1) + segment * n_vertices_per_row; - - let lb = side + (segment + 1) * n_vertices_per_row; - let rb = (side + 1) + (segment + 1) * n_vertices_per_row; - - indices.push(lt as u32); - indices.push(rt as u32); - indices.push(lb as u32); - - indices.push(rt as u32); - indices.push(rb as u32); - indices.push(lb as u32); - } - } - - 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) - } -} - -impl Meshable for Torus { - type Output = TorusMesh; - - fn mesh(&self) -> Self::Output { - TorusMesh { - torus: *self, - ..Default::default() - } - } -} - -impl From for Mesh { - fn from(torus: Torus) -> Self { - torus.mesh().build() - } -} - -impl From for Mesh { - fn from(torus: TorusMesh) -> Self { - torus.build() - } -} diff --git a/crates/bevy_render/src/mesh/shape/capsule.rs b/crates/bevy_render/src/mesh/shape/capsule.rs index d438ed28bc3ee..f455d1133d973 100644 --- a/crates/bevy_render/src/mesh/shape/capsule.rs +++ b/crates/bevy_render/src/mesh/shape/capsule.rs @@ -1,38 +1,10 @@ +use super::Meshable; use crate::mesh::{Indices, Mesh}; -use bevy_math::{Vec2, Vec3}; +use bevy_math::{primitives::Capsule, Vec2, Vec3}; use wgpu::PrimitiveTopology; -/// A cylinder with hemispheres at the top and bottom -#[derive(Debug, Copy, Clone)] -pub struct Capsule { - /// Radius on the `XZ` plane. - pub radius: f32, - /// Number of sections in cylinder between hemispheres. - pub rings: usize, - /// Height of the middle cylinder on the `Y` axis, excluding the hemispheres. - pub depth: f32, - /// Number of latitudes, distributed by inclination. Must be even. - pub latitudes: usize, - /// Number of longitudes, or meridians, distributed by azimuth. - pub longitudes: usize, - /// Manner in which UV coordinates are distributed vertically. - pub uv_profile: CapsuleUvProfile, -} -impl Default for Capsule { - fn default() -> Self { - Capsule { - radius: 0.5, - rings: 0, - depth: 1.0, - latitudes: 16, - longitudes: 32, - uv_profile: CapsuleUvProfile::Aspect, - } - } -} - -#[derive(Debug, Default, Clone, Copy)] /// Manner in which UV coordinates are distributed vertically. +#[derive(Clone, Copy, Debug, Default)] pub enum CapsuleUvProfile { /// UV space is distributed by how much of the capsule consists of the hemispheres. #[default] @@ -44,18 +16,93 @@ pub enum CapsuleUvProfile { Fixed, } -impl From for Mesh { - #[allow(clippy::needless_range_loop)] - fn from(capsule: Capsule) -> Self { - // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db +/// A builder used for creating a [`Mesh`] with a [`Capsule`] shape. +#[derive(Clone, Copy, Debug)] +pub struct CapsuleMesh { + /// The [`Capsule`] shape. + pub capsule: Capsule, + /// The number of horizontal lines subdividing the cylindrical part of the capsule. + /// The default is `0`. + pub rings: usize, + /// The number of vertical lines subdividing the hemispheres of the capsule. + /// The default is `32`. + pub longitudes: usize, + /// The number of horizontal lines subdividing the hemispheres of the capsule. + /// The default is `16`. + pub latitudes: usize, + /// The manner in which UV coordinates are distributed vertically. + /// The default is [`CapsuleUvProfile::Aspect`]. + pub uv_profile: CapsuleUvProfile, +} - let Capsule { - radius, - rings, - depth, +impl Default for CapsuleMesh { + fn default() -> Self { + Self { + capsule: Capsule::default(), + rings: 0, + longitudes: 32, + latitudes: 16, + uv_profile: CapsuleUvProfile::default(), + } + } +} + +impl CapsuleMesh { + /// Creates a new [`CapsuleMesh`] from a given radius, height, longitudes and latitudes. + /// + /// Note that `height` is the distance between the centers of the hemispheres. + /// `radius` will be added to both ends to get the real height of the mesh. + #[inline] + pub fn new(radius: f32, height: f32, longitudes: usize, latitudes: usize) -> Self { + Self { + capsule: Capsule::new(radius, height), + longitudes, latitudes, + ..Default::default() + } + } + + /// Sets the number of horizontal lines subdividing the cylindrical part of the capsule. + #[inline] + pub const fn rings(mut self, rings: usize) -> Self { + self.rings = rings; + self + } + + /// Sets the number of vertical lines subdividing the hemispheres of the capsule. + #[inline] + pub const fn longitudes(mut self, longitudes: usize) -> Self { + self.longitudes = longitudes; + self + } + + /// Sets the number of horizontal lines subdividing the hemispheres of the capsule. + #[inline] + pub const fn latitudes(mut self, latitudes: usize) -> Self { + self.latitudes = latitudes; + self + } + + /// Sets the manner in which UV coordinates are distributed vertically. + #[inline] + pub const fn uv_profile(mut self, uv_profile: CapsuleUvProfile) -> Self { + self.uv_profile = uv_profile; + self + } + + /// Builds a [`Mesh`] based on the configuration in `self`. + pub fn build(&self) -> Mesh { + // code adapted from https://behreajj.medium.com/making-a-capsule-mesh-via-script-in-five-3d-environments-c2214abf02db + let CapsuleMesh { + capsule, + rings, longitudes, + latitudes, uv_profile, + } = *self; + let Capsule { + radius, + half_length, } = capsule; let calc_middle = rings > 0; @@ -64,8 +111,7 @@ impl From for Mesh { let half_latsn2 = half_lats - 2; let ringsp1 = rings + 1; let lonsp1 = longitudes + 1; - let half_depth = depth * 0.5; - let summit = half_depth + radius; + let summit = half_length + radius; // Vertex index offsets. let vert_offset_north_hemi = longitudes; @@ -93,7 +139,7 @@ impl From for Mesh { let to_tex_vertical = 1.0 / half_lats as f32; let vt_aspect_ratio = match uv_profile { - CapsuleUvProfile::Aspect => radius / (depth + radius + radius), + CapsuleUvProfile::Aspect => radius / (2.0 * half_length + radius + radius), CapsuleUvProfile::Uniform => half_lats as f32 / (ringsp1 + latitudes) as f32, CapsuleUvProfile::Fixed => 1.0 / 3.0, }; @@ -128,9 +174,9 @@ impl From for Mesh { } // Equatorial vertices. - for j in 0..lonsp1 { + for (j, s_texture_cache_j) in s_texture_cache.iter_mut().enumerate().take(lonsp1) { let s_texture = 1.0 - j as f32 * to_tex_horizontal; - s_texture_cache[j] = s_texture; + *s_texture_cache_j = s_texture; // Wrap to first element upon reaching last. let j_mod = j % longitudes; @@ -139,13 +185,13 @@ impl From for Mesh { // North equator. let idxn = vert_offset_north_equator + j; - vs[idxn] = Vec3::new(rtc.x, half_depth, -rtc.y); + vs[idxn] = Vec3::new(rtc.x, half_length, -rtc.y); vts[idxn] = Vec2::new(s_texture, vt_aspect_north); vns[idxn] = Vec3::new(tc.x, 0.0, -tc.y); // South equator. let idxs = vert_offset_south_equator + j; - vs[idxs] = Vec3::new(rtc.x, -half_depth, -rtc.y); + vs[idxs] = Vec3::new(rtc.x, -half_length, -rtc.y); vts[idxs] = Vec2::new(s_texture, vt_aspect_south); vns[idxs] = Vec3::new(tc.x, 0.0, -tc.y); } @@ -166,11 +212,11 @@ impl From for Mesh { let rho_cos_phi_north = radius * cos_phi_north; let rho_sin_phi_north = radius * sin_phi_north; - let z_offset_north = half_depth - rho_sin_phi_north; + let z_offset_north = half_length - rho_sin_phi_north; let rho_cos_phi_south = radius * cos_phi_south; let rho_sin_phi_south = radius * sin_phi_south; - let z_offset_sout = -half_depth - rho_sin_phi_south; + let z_offset_sout = -half_length - rho_sin_phi_south; // For texture coordinates. let t_tex_fac = ip1f * to_tex_vertical; @@ -182,10 +228,9 @@ impl From for Mesh { let vert_curr_lat_north = vert_offset_north_hemi + i_lonsp1; let vert_curr_lat_south = vert_offset_south_hemi + i_lonsp1; - for j in 0..lonsp1 { + for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) { let j_mod = j % longitudes; - let s_texture = s_texture_cache[j]; let tc = theta_cartesian[j_mod]; // North hemisphere. @@ -195,7 +240,7 @@ impl From for Mesh { z_offset_north, -rho_cos_phi_north * tc.y, ); - vts[idxn] = Vec2::new(s_texture, t_tex_north); + vts[idxn] = Vec2::new(*s_texture, t_tex_north); vns[idxn] = Vec3::new(cos_phi_north * tc.x, -sin_phi_north, -cos_phi_north * tc.y); // South hemisphere. @@ -205,7 +250,7 @@ impl From for Mesh { z_offset_sout, -rho_cos_phi_south * tc.y, ); - vts[idxs] = Vec2::new(s_texture, t_tex_south); + vts[idxs] = Vec2::new(*s_texture, t_tex_south); vns[idxs] = Vec3::new(cos_phi_south * tc.x, -sin_phi_south, -cos_phi_south * tc.y); } } @@ -221,16 +266,15 @@ impl From for Mesh { let fac = h as f32 * to_fac; let cmpl_fac = 1.0 - fac; let t_texture = cmpl_fac * vt_aspect_north + fac * vt_aspect_south; - let z = half_depth - depth * fac; + let z = half_length - 2.0 * half_length * fac; - for j in 0..lonsp1 { + for (j, s_texture) in s_texture_cache.iter().enumerate().take(lonsp1) { let j_mod = j % longitudes; let tc = theta_cartesian[j_mod]; let rtc = rho_theta_cartesian[j_mod]; - let s_texture = s_texture_cache[j]; vs[idx_cyl_lat] = Vec3::new(rtc.x, z, -rtc.y); - vts[idx_cyl_lat] = Vec2::new(s_texture, t_texture); + vts[idx_cyl_lat] = Vec2::new(*s_texture, t_texture); vns[idx_cyl_lat] = Vec3::new(tc.x, 0.0, -tc.y); idx_cyl_lat += 1; @@ -371,3 +415,26 @@ impl From for Mesh { .with_indices(Some(Indices::U32(tris))) } } + +impl Meshable for Capsule { + type Output = CapsuleMesh; + + fn mesh(&self) -> Self::Output { + CapsuleMesh { + capsule: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(capsule: Capsule) -> Self { + capsule.mesh().build() + } +} + +impl From for Mesh { + fn from(capsule: CapsuleMesh) -> Self { + capsule.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/circle.rs b/crates/bevy_render/src/mesh/shape/circle.rs similarity index 100% rename from crates/bevy_render/src/mesh/primitive_meshes/circle.rs rename to crates/bevy_render/src/mesh/shape/circle.rs diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cone.rs b/crates/bevy_render/src/mesh/shape/cone.rs similarity index 100% rename from crates/bevy_render/src/mesh/primitive_meshes/cone.rs rename to crates/bevy_render/src/mesh/shape/cone.rs diff --git a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs b/crates/bevy_render/src/mesh/shape/conical_frustum.rs similarity index 97% rename from crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs rename to crates/bevy_render/src/mesh/shape/conical_frustum.rs index 832d96de58958..b15ae816432c2 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/conical_frustum.rs +++ b/crates/bevy_render/src/mesh/shape/conical_frustum.rs @@ -1,8 +1,5 @@ -use super::Meshable; -use crate::mesh::{ - primitive_meshes::{CircleMesh, MeshFacingExtension}, - Indices, Mesh, -}; +use super::{CircleMesh, MeshFacingExtension, Meshable}; +use crate::mesh::{Indices, Mesh}; use bevy_math::{primitives::ConicalFrustum, Vec3}; use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs b/crates/bevy_render/src/mesh/shape/cuboid.rs similarity index 100% rename from crates/bevy_render/src/mesh/primitive_meshes/cuboid.rs rename to crates/bevy_render/src/mesh/shape/cuboid.rs diff --git a/crates/bevy_render/src/mesh/shape/cylinder.rs b/crates/bevy_render/src/mesh/shape/cylinder.rs index a4d517ac73952..aabad9fe1f117 100644 --- a/crates/bevy_render/src/mesh/shape/cylinder.rs +++ b/crates/bevy_render/src/mesh/shape/cylinder.rs @@ -1,79 +1,108 @@ -use crate::mesh::{Indices, Mesh}; +use super::Meshable; +use crate::mesh::{ + shape::{CircleMesh, MeshFacingExtension}, + Indices, Mesh, +}; +use bevy_math::primitives::Cylinder; use wgpu::PrimitiveTopology; -/// A cylinder which stands on the XZ plane +/// A builder used for creating a [`Mesh`] with a [`Cylinder`] shape. #[derive(Clone, Copy, Debug)] -pub struct Cylinder { - /// Radius in the XZ plane. - pub radius: f32, - /// Height of the cylinder in the Y axis. - pub height: f32, - /// The number of vertices around each horizontal slice of the cylinder. If you are looking at the cylinder from - /// above, this is the number of points you will see on the circle. - /// A higher number will make it appear more circular. +pub struct CylinderMesh { + /// The [`Cylinder`] shape. + pub cylinder: Cylinder, + /// The number of vertices used for the top and bottom of the cylinder. + /// The default is `32`. pub resolution: u32, - /// The number of segments between the two ends. Setting this to 1 will have triangles spanning the full - /// height of the cylinder. Setting it to 2 will have two sets of triangles with a horizontal slice in the middle of - /// cylinder. Greater numbers increase triangles/slices in the same way. + /// The number of segments along the height of the cylinder. + /// Must be greater than `0` for geometry to be generated. + /// The default is `1`. pub segments: u32, } -impl Default for Cylinder { +impl Default for CylinderMesh { fn default() -> Self { Self { - radius: 0.5, - height: 1.0, - resolution: 16, + cylinder: Cylinder::default(), + resolution: 32, segments: 1, } } } -impl From for Mesh { - fn from(c: Cylinder) -> Self { - debug_assert!(c.radius > 0.0); - debug_assert!(c.height > 0.0); - debug_assert!(c.resolution > 2); - debug_assert!(c.segments > 0); +impl CylinderMesh { + /// Creates a new [`CylinderMesh`] from the given radius, a height, + /// and a resolution used for the top and bottom. + #[inline] + pub fn new(radius: f32, height: f32, resolution: u32) -> Self { + Self { + cylinder: Cylinder::new(radius, height), + resolution, + ..Default::default() + } + } + + /// Sets the number of vertices used for the top and bottom of the cylinder. + #[inline] + pub const fn resolution(mut self, resolution: u32) -> Self { + self.resolution = resolution; + self + } + + /// Sets the number of segments along the height of the cylinder. + /// Must be greater than `0` for geometry to be generated. + #[inline] + pub const fn segments(mut self, segments: u32) -> Self { + self.segments = segments; + self + } + + /// Builds a [`Mesh`] based on the configuration in `self`. + pub fn build(&self) -> Mesh { + let resolution = self.resolution; + let segments = self.segments; - let num_rings = c.segments + 1; - let num_vertices = c.resolution * 2 + num_rings * (c.resolution + 1); - let num_faces = c.resolution * (num_rings - 2); - let num_indices = (2 * num_faces + 2 * (c.resolution - 1) * 2) * 3; + debug_assert!(resolution > 2); + debug_assert!(segments > 0); + + let num_rings = segments + 1; + let num_vertices = resolution * 2 + num_rings * (resolution + 1); + let num_faces = resolution * (num_rings - 2); + let num_indices = (2 * num_faces + 2 * (resolution - 1) * 2) * 3; let mut positions = Vec::with_capacity(num_vertices as usize); let mut normals = Vec::with_capacity(num_vertices as usize); let mut uvs = Vec::with_capacity(num_vertices as usize); let mut indices = Vec::with_capacity(num_indices as usize); - let step_theta = std::f32::consts::TAU / c.resolution as f32; - let step_y = c.height / c.segments as f32; + let step_theta = std::f32::consts::TAU / resolution as f32; + let step_y = 2.0 * self.cylinder.half_height / segments as f32; // rings for ring in 0..num_rings { - let y = -c.height / 2.0 + ring as f32 * step_y; + let y = -self.cylinder.half_height + ring as f32 * step_y; - for segment in 0..=c.resolution { + for segment in 0..=resolution { let theta = segment as f32 * step_theta; let (sin, cos) = theta.sin_cos(); - positions.push([c.radius * cos, y, c.radius * sin]); + positions.push([self.cylinder.radius * cos, y, self.cylinder.radius * sin]); normals.push([cos, 0., sin]); uvs.push([ - segment as f32 / c.resolution as f32, - ring as f32 / c.segments as f32, + segment as f32 / resolution as f32, + ring as f32 / segments as f32, ]); } } // barrel skin - for i in 0..c.segments { - let ring = i * (c.resolution + 1); - let next_ring = (i + 1) * (c.resolution + 1); + for i in 0..segments { + let ring = i * (resolution + 1); + let next_ring = (i + 1) * (resolution + 1); - for j in 0..c.resolution { + for j in 0..resolution { indices.extend_from_slice(&[ ring + j, next_ring + j, @@ -85,38 +114,22 @@ impl From for Mesh { } } - // caps - - let mut build_cap = |top: bool| { - let offset = positions.len() as u32; - let (y, normal_y, winding) = if top { - (c.height / 2., 1., (1, 0)) - } else { - (c.height / -2., -1., (0, 1)) - }; - - for i in 0..c.resolution { - let theta = i as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); - - positions.push([cos * c.radius, y, sin * c.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..(c.resolution - 1) { - indices.extend_from_slice(&[ - offset, - offset + i + winding.0, - offset + i + winding.1, - ]); - } - }; - - // top - - build_cap(true); - build_cap(false); + // Top and bottom + let base = CircleMesh::new(self.cylinder.radius, self.resolution as usize).facing_y(); + base.build_mesh_data( + [0.0, self.cylinder.half_height, 0.0], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); + base.facing_neg_y().build_mesh_data( + [0.0, -self.cylinder.half_height, 0.0], + &mut indices, + &mut positions, + &mut normals, + &mut uvs, + ); Mesh::new(PrimitiveTopology::TriangleList) .with_indices(Some(Indices::U32(indices))) @@ -125,3 +138,26 @@ impl From for Mesh { .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } + +impl Meshable for Cylinder { + type Output = CylinderMesh; + + fn mesh(&self) -> Self::Output { + CylinderMesh { + cylinder: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(cylinder: Cylinder) -> Self { + cylinder.mesh().build() + } +} + +impl From for Mesh { + fn from(cylinder: CylinderMesh) -> Self { + cylinder.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs b/crates/bevy_render/src/mesh/shape/ellipse.rs similarity index 100% rename from crates/bevy_render/src/mesh/primitive_meshes/ellipse.rs rename to crates/bevy_render/src/mesh/shape/ellipse.rs diff --git a/crates/bevy_render/src/mesh/shape/icosphere.rs b/crates/bevy_render/src/mesh/shape/icosphere.rs deleted file mode 100644 index 457ea0f82661d..0000000000000 --- a/crates/bevy_render/src/mesh/shape/icosphere.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::mesh::{Indices, Mesh}; -use hexasphere::shapes::IcoSphere; -use thiserror::Error; -use wgpu::PrimitiveTopology; - -/// A sphere made from a subdivided Icosahedron. -#[derive(Debug, Clone, Copy)] -pub struct Icosphere { - /// The radius of the sphere. - pub radius: f32, - /// The number of subdivisions applied. - pub subdivisions: usize, -} - -impl Default for Icosphere { - fn default() -> Self { - Self { - radius: 1.0, - subdivisions: 5, - } - } -} - -#[derive(Debug, Clone, Error)] -pub enum FromIcosphereError { - #[error("Cannot create an icosphere of {subdivisions} subdivisions due to there being too many vertices being generated: {number_of_resulting_points}. (Limited to 65535 vertices or 79 subdivisions)")] - TooManyVertices { - subdivisions: usize, - number_of_resulting_points: usize, - }, -} - -impl TryFrom for Mesh { - type Error = FromIcosphereError; - - fn try_from(sphere: Icosphere) -> Result { - if sphere.subdivisions >= 80 { - /* - Number of triangles: - N = 20 - - Number of edges: - E = 30 - - Number of vertices: - V = 12 - - Number of points within a triangle (triangular numbers): - inner(s) = (s^2 + s) / 2 - - Number of points on an edge: - edges(s) = s - - Add up all vertices on the surface: - vertices(s) = edges(s) * E + inner(s - 1) * N + V - - Expand and simplify. Notice that the triangular number formula has roots at -1, and 0, so translating it one to the right fixes it. - subdivisions(s) = 30s + 20((s^2 - 2s + 1 + s - 1) / 2) + 12 - subdivisions(s) = 30s + 10s^2 - 10s + 12 - subdivisions(s) = 10(s^2 + 2s) + 12 - - Factor an (s + 1) term to simplify in terms of calculation - subdivisions(s) = 10(s + 1)^2 + 12 - 10 - resulting_vertices(s) = 10(s + 1)^2 + 2 - */ - let temp = sphere.subdivisions + 1; - let number_of_resulting_points = temp * temp * 10 + 2; - return Err(FromIcosphereError::TooManyVertices { - subdivisions: sphere.subdivisions, - number_of_resulting_points, - }); - } - let generated = IcoSphere::new(sphere.subdivisions, |point| { - let inclination = point.y.acos(); - let azimuth = point.z.atan2(point.x); - - let norm_inclination = inclination / std::f32::consts::PI; - let norm_azimuth = 0.5 - (azimuth / std::f32::consts::TAU); - - [norm_azimuth, norm_inclination] - }); - - let raw_points = generated.raw_points(); - - let points = raw_points - .iter() - .map(|&p| (p * sphere.radius).into()) - .collect::>(); - - let normals = raw_points - .iter() - .copied() - .map(Into::into) - .collect::>(); - - let uvs = generated.raw_data().to_owned(); - - let mut indices = Vec::with_capacity(generated.indices_per_main_triangle() * 20); - - for i in 0..20 { - generated.get_indices(i, &mut indices); - } - - let indices = Indices::U32(indices); - - 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)) - } -} diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index c9f6b9e1492bf..fff3ed7b06d75 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -1,277 +1,134 @@ -use super::{Indices, Mesh}; -use bevy_math::*; +//! Mesh generation for [primitive shapes](bevy_math::primitives). -#[derive(Debug, Copy, Clone)] -pub struct Cube { - pub size: f32, -} - -impl Cube { - pub fn new(size: f32) -> Cube { - Cube { size } - } -} - -impl Default for Cube { - fn default() -> Self { - Cube { size: 1.0 } - } -} - -impl From for Mesh { - fn from(cube: Cube) -> Self { - Box::new(cube.size, cube.size, cube.size).into() - } -} - -/// An axis-aligned box defined by its minimum and maximum point. -#[derive(Debug, Copy, Clone)] -pub struct Box { - pub min_x: f32, - pub max_x: f32, - - pub min_y: f32, - pub max_y: f32, +#![warn(missing_docs)] - pub min_z: f32, - pub max_z: f32, -} - -impl Box { - /// Creates a new box centered at the origin with the supplied side lengths. - pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Box { - Box { - max_x: x_length / 2.0, - min_x: -x_length / 2.0, - max_y: y_length / 2.0, - min_y: -y_length / 2.0, - max_z: z_length / 2.0, - min_z: -z_length / 2.0, +mod capsule; +mod circle; +mod cone; +mod conical_frustum; +mod cuboid; +mod cylinder; +mod ellipse; +mod plane; +mod rectangle; +mod regular_polygon; +mod sphere; +mod torus; +mod triangle; + +pub use capsule::CapsuleMesh; +pub use circle::CircleMesh; +pub use cone::ConeMesh; +pub use conical_frustum::ConicalFrustumMesh; +pub use cuboid::CuboidMesh; +pub use cylinder::CylinderMesh; +pub use ellipse::EllipseMesh; +pub use plane::PlaneMesh; +pub use rectangle::RectangleMesh; +pub use regular_polygon::RegularPolygonMesh; +pub use sphere::{SphereKind, SphereMesh}; +pub use torus::TorusMesh; +pub use triangle::Triangle2dMesh; + +use super::Mesh; + +/// A trait for shapes that can be turned into a [`Mesh`]. +pub trait Meshable { + /// The output of [`Self::mesh`]. This can either be a [`Mesh`] + /// or a builder used for creating a [`Mesh`]. + type Output; + + /// Creates a [`Mesh`] for a shape. + fn mesh(&self) -> Self::Output; +} + +/// The cartesian axis that a [`Mesh`] should be facing upon creation. +/// This is either positive or negative `X`, `Y`, or `Z`. +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub enum Facing { + /// Facing the `+X` direction. + X = 1, + /// Facing the `+Y` direction. + Y = 2, + /// Facing the `+Z` direction. + #[default] + Z = 3, + /// Facing the `-X` direction. + NegX = -1, + /// Facing the `-Y` direction. + NegY = -2, + /// Facing the `-Z` direction. + NegZ = -3, +} + +impl Facing { + /// Returns `1` if the facing direction is positive `X`, `Y`, or `Z`, and `-1` otherwise. + #[inline] + pub const fn signum(&self) -> i8 { + match self { + Facing::X | Facing::Y | Facing::Z => 1, + _ => -1, } } - /// Creates a new box given the coordinates of two opposing corners. - pub fn from_corners(a: Vec3, b: Vec3) -> Box { - let max = a.max(b); - let min = a.min(b); - Box { - max_x: max.x, - min_x: min.x, - max_y: max.y, - min_y: min.y, - max_z: max.z, - min_z: min.z, + /// Returns the direction in as an array in the format `[x, y, z]`. + /// + /// # Example + /// + /// ``` + /// assert_eq!(Facing::X.to_array(), [1.0, 0.0, 0.0]); + /// ``` + #[inline] + pub const fn to_array(&self) -> [f32; 3] { + match self { + Facing::X => [1.0, 0.0, 0.0], + Facing::Y => [0.0, 1.0, 0.0], + Facing::Z => [0.0, 0.0, 1.0], + Facing::NegX => [-1.0, 0.0, 0.0], + Facing::NegY => [0.0, -1.0, 0.0], + Facing::NegZ => [0.0, 0.0, -1.0], } } } -impl Default for Box { - fn default() -> Self { - Box::new(2.0, 1.0, 1.0) - } -} - -impl From for Mesh { - fn from(sp: Box) -> Self { - // suppose Y-up right hand, and camera look from +z to -z - let vertices = &[ - // Front - ([sp.min_x, sp.min_y, sp.max_z], [0., 0., 1.0], [0., 0.]), - ([sp.max_x, sp.min_y, sp.max_z], [0., 0., 1.0], [1.0, 0.]), - ([sp.max_x, sp.max_y, sp.max_z], [0., 0., 1.0], [1.0, 1.0]), - ([sp.min_x, sp.max_y, sp.max_z], [0., 0., 1.0], [0., 1.0]), - // Back - ([sp.min_x, sp.max_y, sp.min_z], [0., 0., -1.0], [1.0, 0.]), - ([sp.max_x, sp.max_y, sp.min_z], [0., 0., -1.0], [0., 0.]), - ([sp.max_x, sp.min_y, sp.min_z], [0., 0., -1.0], [0., 1.0]), - ([sp.min_x, sp.min_y, sp.min_z], [0., 0., -1.0], [1.0, 1.0]), - // Right - ([sp.max_x, sp.min_y, sp.min_z], [1.0, 0., 0.], [0., 0.]), - ([sp.max_x, sp.max_y, sp.min_z], [1.0, 0., 0.], [1.0, 0.]), - ([sp.max_x, sp.max_y, sp.max_z], [1.0, 0., 0.], [1.0, 1.0]), - ([sp.max_x, sp.min_y, sp.max_z], [1.0, 0., 0.], [0., 1.0]), - // Left - ([sp.min_x, sp.min_y, sp.max_z], [-1.0, 0., 0.], [1.0, 0.]), - ([sp.min_x, sp.max_y, sp.max_z], [-1.0, 0., 0.], [0., 0.]), - ([sp.min_x, sp.max_y, sp.min_z], [-1.0, 0., 0.], [0., 1.0]), - ([sp.min_x, sp.min_y, sp.min_z], [-1.0, 0., 0.], [1.0, 1.0]), - // Top - ([sp.max_x, sp.max_y, sp.min_z], [0., 1.0, 0.], [1.0, 0.]), - ([sp.min_x, sp.max_y, sp.min_z], [0., 1.0, 0.], [0., 0.]), - ([sp.min_x, sp.max_y, sp.max_z], [0., 1.0, 0.], [0., 1.0]), - ([sp.max_x, sp.max_y, sp.max_z], [0., 1.0, 0.], [1.0, 1.0]), - // Bottom - ([sp.max_x, sp.min_y, sp.max_z], [0., -1.0, 0.], [0., 0.]), - ([sp.min_x, sp.min_y, sp.max_z], [0., -1.0, 0.], [1.0, 0.]), - ([sp.min_x, sp.min_y, sp.min_z], [0., -1.0, 0.], [1.0, 1.0]), - ([sp.max_x, sp.min_y, sp.min_z], [0., -1.0, 0.], [0., 1.0]), - ]; - - let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); - let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); - let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); +/// An extension trait for methods related to setting a specific [`Facing`] direction. +pub trait MeshFacingExtension: Sized { + /// Set the [`Facing`] direction. + fn facing(self, facing: Facing) -> Self; - let indices = Indices::U32(vec![ - 0, 1, 2, 2, 3, 0, // front - 4, 5, 6, 6, 7, 4, // back - 8, 9, 10, 10, 11, 8, // right - 12, 13, 14, 14, 15, 12, // left - 16, 17, 18, 18, 19, 16, // top - 20, 21, 22, 22, 23, 20, // bottom - ]); - - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(indices)) + /// Set the [`Facing`] direction to `+X`. + #[inline] + fn facing_x(self) -> Self { + self.facing(Facing::X) } -} - -/// A rectangle on the `XY` plane centered at the origin. -#[derive(Debug, Copy, Clone)] -pub struct Quad { - /// Full width and height of the rectangle. - pub size: Vec2, - /// Horizontally-flip the texture coordinates of the resulting mesh. - pub flip: bool, -} -impl Default for Quad { - fn default() -> Self { - Quad::new(Vec2::ONE) + /// Set the [`Facing`] direction to `+Y`. + #[inline] + fn facing_y(self) -> Self { + self.facing(Facing::Y) } -} -impl Quad { - pub fn new(size: Vec2) -> Self { - Self { size, flip: false } + /// Set the [`Facing`] direction to `+Z`. + #[inline] + fn facing_z(self) -> Self { + self.facing(Facing::Z) } - pub fn flipped(size: Vec2) -> Self { - Self { size, flip: true } + /// Set the [`Facing`] direction to `-X`. + #[inline] + fn facing_neg_x(self) -> Self { + self.facing(Facing::NegX) } -} - -impl From for Mesh { - fn from(quad: Quad) -> Self { - let extent_x = quad.size.x / 2.0; - let extent_y = quad.size.y / 2.0; - let (u_left, u_right) = if quad.flip { (1.0, 0.0) } else { (0.0, 1.0) }; - let vertices = [ - ([-extent_x, -extent_y, 0.0], [0.0, 0.0, 1.0], [u_left, 1.0]), - ([-extent_x, extent_y, 0.0], [0.0, 0.0, 1.0], [u_left, 0.0]), - ([extent_x, extent_y, 0.0], [0.0, 0.0, 1.0], [u_right, 0.0]), - ([extent_x, -extent_y, 0.0], [0.0, 0.0, 1.0], [u_right, 1.0]), - ]; - - let indices = Indices::U32(vec![0, 2, 1, 0, 3, 2]); - - let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); - let normals: Vec<_> = vertices.iter().map(|(_, n, _)| *n).collect(); - let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); - - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(indices)) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - } -} - -/// A square on the `XZ` plane centered at the origin. -#[derive(Debug, Copy, Clone)] -pub struct Plane { - /// The total side length of the square. - pub size: f32, - /// The number of subdivisions in the mesh. - /// - /// 0 - is the original plane geometry, the 4 points in the XZ plane. - /// - /// 1 - is split by 1 line in the middle of the plane on both the X axis and the Z axis, resulting in a plane with 4 quads / 8 triangles. - /// - /// 2 - is a plane split by 2 lines on both the X and Z axes, subdividing the plane into 3 equal sections along each axis, resulting in a plane with 9 quads / 18 triangles. - /// - /// and so on... - pub subdivisions: u32, -} - -impl Default for Plane { - fn default() -> Self { - Plane { - size: 1.0, - subdivisions: 0, - } - } -} - -impl Plane { - /// Creates a new plane centered at the origin with the supplied side length and zero subdivisions. - pub fn from_size(size: f32) -> Self { - Self { - size, - subdivisions: 0, - } + /// Set the [`Facing`] direction to `-Y`. + #[inline] + fn facing_neg_y(self) -> Self { + self.facing(Facing::NegY) } -} - -impl From for Mesh { - fn from(plane: Plane) -> Self { - // here this is split in the z and x directions if one ever needs asymmetrical subdivision - // two Plane struct fields would need to be added instead of the single subdivisions field - let z_vertex_count = plane.subdivisions + 2; - let x_vertex_count = plane.subdivisions + 2; - let num_vertices = (z_vertex_count * x_vertex_count) as usize; - let num_indices = ((z_vertex_count - 1) * (x_vertex_count - 1) * 6) as usize; - let up = Vec3::Y.to_array(); - let mut positions: Vec<[f32; 3]> = Vec::with_capacity(num_vertices); - let mut normals: Vec<[f32; 3]> = Vec::with_capacity(num_vertices); - let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(num_vertices); - let mut indices: Vec = Vec::with_capacity(num_indices); - - for z in 0..z_vertex_count { - for x in 0..x_vertex_count { - let tx = x as f32 / (x_vertex_count - 1) as f32; - let tz = z as f32 / (z_vertex_count - 1) as f32; - positions.push([(-0.5 + tx) * plane.size, 0.0, (-0.5 + tz) * plane.size]); - normals.push(up); - uvs.push([tx, tz]); - } - } - - for y in 0..z_vertex_count - 1 { - for x in 0..x_vertex_count - 1 { - let quad = y * x_vertex_count + x; - indices.push(quad + x_vertex_count + 1); - indices.push(quad + 1); - indices.push(quad + x_vertex_count); - indices.push(quad); - indices.push(quad + x_vertex_count); - indices.push(quad + 1); - } - } - - 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) + /// Set the [`Facing`] direction to `-Z`. + #[inline] + fn facing_neg_z(self) -> Self { + self.facing(Facing::NegZ) } } - -mod capsule; -mod cylinder; -mod icosphere; -mod regular_polygon; -mod torus; -mod uvsphere; - -pub use capsule::{Capsule, CapsuleUvProfile}; -pub use cylinder::Cylinder; -pub use icosphere::Icosphere; -pub use regular_polygon::{Circle, RegularPolygon}; -pub use torus::Torus; -pub use uvsphere::UVSphere; -use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/primitive_meshes/plane.rs b/crates/bevy_render/src/mesh/shape/plane.rs similarity index 80% rename from crates/bevy_render/src/mesh/primitive_meshes/plane.rs rename to crates/bevy_render/src/mesh/shape/plane.rs index e0964771df771..3fadd47e164ea 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/plane.rs +++ b/crates/bevy_render/src/mesh/shape/plane.rs @@ -35,6 +35,27 @@ impl PlaneMesh { } } + /// Creates a new [`PlaneMesh`] from the given size, with the normal pointing upwards. + #[inline] + pub fn from_size(size: Vec2) -> Self { + Self { + half_size: 2.0 * size, + ..Default::default() + } + } + + /// Sets the normal of the plane, aka the direction the plane is facing. + /// + /// # Panics + /// + /// Panics if the given `normal` is zero (or very close to zero), or non-finite. + #[inline] + #[doc(alias = "facing")] + pub fn normal(mut self, normal: Vec3) -> Self { + self.plane = Plane3d::new(normal); + self + } + /// Sets the size of the plane mesh. #[inline] pub fn size(mut self, size: Vec2) -> Self { diff --git a/crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs b/crates/bevy_render/src/mesh/shape/rectangle.rs similarity index 100% rename from crates/bevy_render/src/mesh/primitive_meshes/rectangle.rs rename to crates/bevy_render/src/mesh/shape/rectangle.rs diff --git a/crates/bevy_render/src/mesh/shape/regular_polygon.rs b/crates/bevy_render/src/mesh/shape/regular_polygon.rs index 879c59fabd11e..8e3446a5aedf6 100644 --- a/crates/bevy_render/src/mesh/shape/regular_polygon.rs +++ b/crates/bevy_render/src/mesh/shape/regular_polygon.rs @@ -1,107 +1,90 @@ -use crate::mesh::{Indices, Mesh}; -use wgpu::PrimitiveTopology; +use super::{Facing, Mesh, MeshFacingExtension, Meshable}; +use bevy_math::primitives::{Ellipse, RegularPolygon}; -/// A regular polygon in the `XY` plane -#[derive(Debug, Copy, Clone)] -pub struct RegularPolygon { - /// Circumscribed radius in the `XY` plane. - /// - /// In other words, the vertices of this polygon will all touch a circle of this radius. - pub radius: f32, - /// Number of sides. - pub sides: usize, +/// A builder used for creating a [`Mesh`] with a [`RegularPolygon`] shape. +#[derive(Clone, Copy, Debug, Default)] +pub struct RegularPolygonMesh { + /// The [`RegularPolygon`] shape. + pub polygon: RegularPolygon, + /// The XYZ direction that the mesh is facing. + /// The default is [`Facing::Z`]. + pub facing: Facing, } -impl Default for RegularPolygon { - fn default() -> Self { - Self { - radius: 0.5, - sides: 6, - } +impl MeshFacingExtension for RegularPolygonMesh { + #[inline] + fn facing(mut self, facing: Facing) -> Self { + self.facing = facing; + self } } -impl RegularPolygon { - /// Creates a regular polygon in the `XY` plane - pub fn new(radius: f32, sides: usize) -> Self { - Self { radius, sides } - } -} - -impl From for Mesh { - fn from(polygon: RegularPolygon) -> Self { - let RegularPolygon { radius, sides } = polygon; - - debug_assert!(sides > 2, "RegularPolygon requires at least 3 sides."); - - let mut positions = Vec::with_capacity(sides); - let mut normals = Vec::with_capacity(sides); - let mut uvs = Vec::with_capacity(sides); - - let step = std::f32::consts::TAU / sides as f32; - for i in 0..sides { - let theta = std::f32::consts::FRAC_PI_2 - i as f32 * step; - let (sin, cos) = theta.sin_cos(); - - positions.push([cos * radius, sin * radius, 0.0]); - normals.push([0.0, 0.0, 1.0]); - uvs.push([0.5 * (cos + 1.0), 1.0 - 0.5 * (sin + 1.0)]); +impl RegularPolygonMesh { + /// Creates a new [`RegularPolygonMesh`] from the radius + /// of the circumcircle and a number of sides. + /// + /// # Panics + /// + /// Panics if `circumradius` is non-positive. + #[inline] + pub fn new(circumradius: f32, sides: usize) -> Self { + Self { + polygon: RegularPolygon::new(circumradius, sides), + ..Default::default() } + } - let mut indices = Vec::with_capacity((sides - 2) * 3); - for i in 1..(sides as u32 - 1) { - // Vertices are generated in CW order above, hence the reversed indices here - // to emit triangle vertices in CCW order. - indices.extend_from_slice(&[0, i + 1, i]); + /// Builds a [`Mesh`] based on the configuration in `self`. + pub fn build(&self) -> Mesh { + // The ellipse mesh is just a regular polygon with two radii + Ellipse { + half_width: self.polygon.circumcircle.radius, + half_height: self.polygon.circumcircle.radius, } - - Mesh::new(PrimitiveTopology::TriangleList) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - .with_indices(Some(Indices::U32(indices))) + .mesh() + .resolution(self.polygon.sides) + .facing(self.facing) + .build() } -} - -/// A circle in the `XY` plane -#[derive(Debug, Copy, Clone)] -pub struct Circle { - /// Inscribed radius in the `XY` plane. - pub radius: f32, - /// The number of vertices used. - pub vertices: usize, -} -impl Default for Circle { - fn default() -> Self { - Self { - radius: 0.5, - vertices: 64, + pub(super) fn build_mesh_data( + &self, + translation: [f32; 3], + indices: &mut Vec, + positions: &mut Vec<[f32; 3]>, + normals: &mut Vec<[f32; 3]>, + uvs: &mut Vec<[f32; 2]>, + ) { + Ellipse { + half_width: self.polygon.circumcircle.radius, + half_height: self.polygon.circumcircle.radius, } + .mesh() + .resolution(self.polygon.sides) + .facing(self.facing) + .build_mesh_data(translation, indices, positions, normals, uvs); } } -impl Circle { - /// Creates a circle in the `XY` plane - pub fn new(radius: f32) -> Self { - Self { - radius, +impl Meshable for RegularPolygon { + type Output = RegularPolygonMesh; + + fn mesh(&self) -> Self::Output { + RegularPolygonMesh { + polygon: *self, ..Default::default() } } } -impl From for RegularPolygon { - fn from(circle: Circle) -> Self { - Self { - radius: circle.radius, - sides: circle.vertices, - } +impl From for Mesh { + fn from(polygon: RegularPolygon) -> Self { + polygon.mesh().build() } } -impl From for Mesh { - fn from(circle: Circle) -> Self { - Mesh::from(RegularPolygon::from(circle)) +impl From for Mesh { + fn from(polygon: RegularPolygonMesh) -> Self { + polygon.build() } } diff --git a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs b/crates/bevy_render/src/mesh/shape/sphere.rs similarity index 98% rename from crates/bevy_render/src/mesh/primitive_meshes/sphere.rs rename to crates/bevy_render/src/mesh/shape/sphere.rs index f75198cb616c6..07455a30fa09c 100644 --- a/crates/bevy_render/src/mesh/primitive_meshes/sphere.rs +++ b/crates/bevy_render/src/mesh/shape/sphere.rs @@ -88,6 +88,8 @@ impl SphereMesh { /// The number of faces quadruples with each subdivision. /// If there are `80` or more subdivisions, the vertex count will be too large, /// and an [`IcosphereError`] is returned. + /// + /// A good default is `5` subdivisions. pub fn ico(&self, subdivisions: usize) -> Result { if subdivisions >= 80 { /* @@ -167,6 +169,8 @@ impl SphereMesh { /// Creates a UV sphere [`Mesh`] with the given number of /// longitudinal sectors and latitudinal stacks, aka horizontal and vertical resolution. + /// + /// A good default is `32` sectors and `18` stacks. pub fn uv(&self, sectors: usize, stacks: usize) -> Mesh { // Largely inspired from http://www.songho.ca/opengl/gl_sphere.html diff --git a/crates/bevy_render/src/mesh/shape/torus.rs b/crates/bevy_render/src/mesh/shape/torus.rs index 5254fcceebc01..561f78170e324 100644 --- a/crates/bevy_render/src/mesh/shape/torus.rs +++ b/crates/bevy_render/src/mesh/shape/torus.rs @@ -1,73 +1,114 @@ +use super::Meshable; use crate::mesh::{Indices, Mesh}; -use bevy_math::Vec3; +use bevy_math::{primitives::Torus, Vec3}; use wgpu::PrimitiveTopology; -/// A torus (donut) shape. -#[derive(Debug, Clone, Copy)] -pub struct Torus { - pub radius: f32, - pub ring_radius: f32, - pub subdivisions_segments: usize, - pub subdivisions_sides: usize, +/// A builder used for creating a [`Mesh`] with a [`Torus`] shape. +#[derive(Clone, Copy, Debug)] +pub struct TorusMesh { + /// The [`Torus`] shape. + pub torus: Torus, + /// The number of segments used for the main ring of the torus. + /// + /// A resolution of `4` would make the torus appear rectangular, + /// while a resolution of `32` resembles a circular ring. + pub major_resolution: usize, + /// The number of vertices used for each circular segment + /// in the ring or tube of the torus. + pub minor_resolution: usize, } -impl Default for Torus { +impl Default for TorusMesh { fn default() -> Self { - Torus { - radius: 1.0, - ring_radius: 0.5, - subdivisions_segments: 32, - subdivisions_sides: 24, + Self { + torus: Torus::default(), + major_resolution: 32, + minor_resolution: 24, } } } -impl From for Mesh { - fn from(torus: Torus) -> Self { +impl TorusMesh { + /// Creates a new [`TorusMesh`] from an inner and outer radius. + /// + /// The inner radius is the radius of the hole, and the outer radius + /// is the radius of the entire object. + #[inline] + pub fn new(inner_radius: f32, outer_radius: f32) -> Self { + Self { + torus: Torus::new(inner_radius, outer_radius), + ..Default::default() + } + } + + /// Sets the number of segments used for the main ring of the torus. + /// + /// A resolution of `4` would make the torus appear rectangular, + /// while a resolution of `32` resembles a circular ring. + #[inline] + pub const fn major_resolution(mut self, resolution: usize) -> Self { + self.major_resolution = resolution; + self + } + + /// Sets the number of vertices used for each circular segment + /// in the ring or tube of the torus. + #[inline] + pub const fn minor_resolution(mut self, resolution: usize) -> Self { + self.minor_resolution = resolution; + self + } + + /// Builds a [`Mesh`] according to the configuration in `self`. + pub fn build(&self) -> Mesh { // code adapted from http://apparat-engine.blogspot.com/2013/04/procedural-meshes-torus.html // (source code at https://github.com/SEilers/Apparat) - let n_vertices = (torus.subdivisions_segments + 1) * (torus.subdivisions_sides + 1); + let n_vertices = (self.major_resolution + 1) * (self.minor_resolution + 1); let mut positions: Vec<[f32; 3]> = Vec::with_capacity(n_vertices); 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 / torus.subdivisions_segments as f32; - let side_stride = 2.0 * std::f32::consts::PI / torus.subdivisions_sides as f32; + let segment_stride = 2.0 * std::f32::consts::PI / self.major_resolution as f32; + let side_stride = 2.0 * std::f32::consts::PI / self.minor_resolution as f32; - for segment in 0..=torus.subdivisions_segments { + for segment in 0..=self.major_resolution { let theta = segment_stride * segment as f32; - for side in 0..=torus.subdivisions_sides { + for side in 0..=self.minor_resolution { let phi = side_stride * side as f32; let position = Vec3::new( - theta.cos() * (torus.radius + torus.ring_radius * phi.cos()), - torus.ring_radius * phi.sin(), - theta.sin() * (torus.radius + torus.ring_radius * phi.cos()), + theta.cos() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), + self.torus.minor_radius * phi.sin(), + theta.sin() * (self.torus.major_radius + self.torus.minor_radius * phi.cos()), ); - let center = Vec3::new(torus.radius * theta.cos(), 0., torus.radius * theta.sin()); + let center = Vec3::new( + self.torus.major_radius * theta.cos(), + 0., + self.torus.major_radius * theta.sin(), + ); let normal = (position - center).normalize(); positions.push(position.into()); normals.push(normal.into()); uvs.push([ - segment as f32 / torus.subdivisions_segments as f32, - side as f32 / torus.subdivisions_sides as f32, + segment as f32 / self.major_resolution as f32, + side as f32 / self.minor_resolution as f32, ]); } } - let n_faces = (torus.subdivisions_segments) * (torus.subdivisions_sides); + let n_faces = (self.major_resolution) * (self.minor_resolution); let n_triangles = n_faces * 2; let n_indices = n_triangles * 3; let mut indices: Vec = Vec::with_capacity(n_indices); - let n_vertices_per_row = torus.subdivisions_sides + 1; - for segment in 0..torus.subdivisions_segments { - for side in 0..torus.subdivisions_sides { + let n_vertices_per_row = self.minor_resolution + 1; + for segment in 0..self.major_resolution { + for side in 0..self.minor_resolution { let lt = side + segment * n_vertices_per_row; let rt = (side + 1) + segment * n_vertices_per_row; @@ -91,3 +132,26 @@ impl From for Mesh { .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) } } + +impl Meshable for Torus { + type Output = TorusMesh; + + fn mesh(&self) -> Self::Output { + TorusMesh { + torus: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(torus: Torus) -> Self { + torus.mesh().build() + } +} + +impl From for Mesh { + fn from(torus: TorusMesh) -> Self { + torus.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitive_meshes/triangle.rs b/crates/bevy_render/src/mesh/shape/triangle.rs similarity index 100% rename from crates/bevy_render/src/mesh/primitive_meshes/triangle.rs rename to crates/bevy_render/src/mesh/shape/triangle.rs diff --git a/crates/bevy_render/src/mesh/shape/uvsphere.rs b/crates/bevy_render/src/mesh/shape/uvsphere.rs deleted file mode 100644 index b6b89ebc40157..0000000000000 --- a/crates/bevy_render/src/mesh/shape/uvsphere.rs +++ /dev/null @@ -1,89 +0,0 @@ -use wgpu::PrimitiveTopology; - -use crate::mesh::{Indices, Mesh}; -use std::f32::consts::PI; - -/// A sphere made of sectors and stacks. -#[allow(clippy::upper_case_acronyms)] -#[derive(Debug, Clone, Copy)] -pub struct UVSphere { - /// The radius of the sphere. - pub radius: f32, - /// Longitudinal sectors - pub sectors: usize, - /// Latitudinal stacks - pub stacks: usize, -} - -impl Default for UVSphere { - fn default() -> Self { - Self { - radius: 1.0, - sectors: 36, - stacks: 18, - } - } -} - -impl From for Mesh { - fn from(sphere: UVSphere) -> Self { - // Largely inspired from http://www.songho.ca/opengl/gl_sphere.html - - let sectors = sphere.sectors as f32; - let stacks = sphere.stacks as f32; - let length_inv = 1. / sphere.radius; - let sector_step = 2. * PI / sectors; - let stack_step = PI / stacks; - - let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(sphere.stacks * sphere.sectors); - let mut normals: Vec<[f32; 3]> = Vec::with_capacity(sphere.stacks * sphere.sectors); - let mut uvs: Vec<[f32; 2]> = Vec::with_capacity(sphere.stacks * sphere.sectors); - let mut indices: Vec = Vec::with_capacity(sphere.stacks * sphere.sectors * 2 * 3); - - for i in 0..sphere.stacks + 1 { - let stack_angle = PI / 2. - (i as f32) * stack_step; - let xy = sphere.radius * stack_angle.cos(); - let z = sphere.radius * stack_angle.sin(); - - for j in 0..sphere.sectors + 1 { - let sector_angle = (j as f32) * sector_step; - let x = xy * sector_angle.cos(); - let y = xy * sector_angle.sin(); - - vertices.push([x, y, z]); - normals.push([x * length_inv, y * length_inv, z * length_inv]); - uvs.push([(j as f32) / sectors, (i as f32) / stacks]); - } - } - - // indices - // k1--k1+1 - // | / | - // | / | - // k2--k2+1 - for i in 0..sphere.stacks { - let mut k1 = i * (sphere.sectors + 1); - let mut k2 = k1 + sphere.sectors + 1; - for _j in 0..sphere.sectors { - if i != 0 { - indices.push(k1 as u32); - indices.push(k2 as u32); - indices.push((k1 + 1) as u32); - } - if i != sphere.stacks - 1 { - indices.push((k1 + 1) as u32); - indices.push(k2 as u32); - indices.push((k2 + 1) as u32); - } - k1 += 1; - k2 += 1; - } - } - - Mesh::new(PrimitiveTopology::TriangleList) - .with_indices(Some(Indices::U32(indices))) - .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) - .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) - .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) - } -} diff --git a/errors/B0004.md b/errors/B0004.md index 55a2d3f8dab2d..cf2615cff7911 100644 --- a/errors/B0004.md +++ b/errors/B0004.md @@ -33,7 +33,7 @@ fn setup_cube( .with_children(|parent| { // cube parent.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() @@ -80,7 +80,7 @@ fn setup_cube( .with_children(|parent| { // cube parent.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() diff --git a/examples/2d/bloom_2d.rs b/examples/2d/bloom_2d.rs index 760804bcfbd60..296539b8c2a17 100644 --- a/examples/2d/bloom_2d.rs +++ b/examples/2d/bloom_2d.rs @@ -48,7 +48,9 @@ fn setup( // Circle mesh commands.spawn(MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::new(100.).into()).into(), + mesh: meshes + .add(primitives::Circle { radius: 100. }.into()) + .into(), // 4. Put something bright in a dark environment to see the effect material: materials.add(ColorMaterial::from(Color::rgb(7.5, 0.0, 7.5))), transform: Transform::from_translation(Vec3::new(-200., 0., 0.)), @@ -58,7 +60,7 @@ fn setup( // Hexagon mesh commands.spawn(MaterialMesh2dBundle { mesh: meshes - .add(shape::RegularPolygon::new(100., 6).into()) + .add(primitives::RegularPolygon::new(100., 6).into()) .into(), // 4. Put something bright in a dark environment to see the effect material: materials.add(ColorMaterial::from(Color::rgb(6.25, 9.4, 9.1))), diff --git a/examples/2d/mesh2d.rs b/examples/2d/mesh2d.rs index bc419e532180b..5ac3f682dc7b6 100644 --- a/examples/2d/mesh2d.rs +++ b/examples/2d/mesh2d.rs @@ -1,6 +1,6 @@ -//! Shows how to render a polygonal [`Mesh`], generated from a [`Quad`] primitive, in a 2D scene. +//! Shows how to render a polygonal [`Mesh`], generated from a [`Rectangle`] primitive, in a 2D scene. //! -//! [`Quad`]: shape::Quad +//! [`Rectangle`]: primitives::Rectangle use bevy::{prelude::*, sprite::MaterialMesh2dBundle}; @@ -18,7 +18,9 @@ fn setup( ) { commands.spawn(Camera2dBundle::default()); commands.spawn(MaterialMesh2dBundle { - mesh: meshes.add(Mesh::from(shape::Quad::default())).into(), + mesh: meshes + .add(Mesh::from(primitives::Rectangle::default())) + .into(), transform: Transform::default().with_scale(Vec3::splat(128.)), material: materials.add(ColorMaterial::from(Color::PURPLE)), ..default() diff --git a/examples/2d/mesh2d_vertex_color_texture.rs b/examples/2d/mesh2d_vertex_color_texture.rs index 1e439e28c9d16..8bd3dfae64cca 100644 --- a/examples/2d/mesh2d_vertex_color_texture.rs +++ b/examples/2d/mesh2d_vertex_color_texture.rs @@ -1,7 +1,7 @@ -//! Shows how to render a polygonal [`Mesh`], generated from a [`Quad`] primitive, in a 2D scene. +//! Shows how to render a polygonal [`Mesh`], generated from a [`Rectangle`] primitive, in a 2D scene. //! Adds a texture and colored vertices, giving per-vertex tinting. //! -//! [`Quad`]: shape::Quad +//! [`Rectangle`]: primitives::Rectangle use bevy::{ prelude::*, @@ -23,9 +23,9 @@ fn setup( ) { // Load the Bevy logo as a texture let texture_handle = asset_server.load("branding/banner.png"); - // Build a default quad mesh - let mut mesh = Mesh::from(shape::Quad::default()); - // Build vertex colors for the quad. One entry per vertex (the corners of the quad) + // Build a default rectangle mesh + let mut mesh = Mesh::from(primitives::Rectangle::default()); + // Build vertex colors for the rectangle. One entry per vertex (the corners of the rectangle) let vertex_colors: Vec<[f32; 4]> = vec![ Color::RED.as_rgba_f32(), Color::GREEN.as_rgba_f32(), @@ -40,7 +40,7 @@ fn setup( // Spawn camera commands.spawn(Camera2dBundle::default()); - // Spawn the quad with vertex colors + // Spawn the rectangle with vertex colors commands.spawn(MaterialMesh2dBundle { mesh: mesh_handle.clone(), transform: Transform::from_translation(Vec3::new(-96., 0., 0.)) @@ -49,7 +49,7 @@ fn setup( ..default() }); - // Spawning the quad with vertex colors and a texture results in tinting + // Spawning the rectangle with vertex colors and a texture results in tinting commands.spawn(MaterialMesh2dBundle { mesh: mesh_handle, transform: Transform::from_translation(Vec3::new(96., 0., 0.)) diff --git a/examples/3d/3d_gizmos.rs b/examples/3d/3d_gizmos.rs index ecee465e07b10..6e05998cabcd7 100644 --- a/examples/3d/3d_gizmos.rs +++ b/examples/3d/3d_gizmos.rs @@ -23,13 +23,13 @@ fn setup( }); // plane commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane::from_size(5.0))), + mesh: meshes.add(shape::PlaneMesh::default().size(Vec2::splat(5.0)).into()), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(shape::CuboidMesh::new(1.0, 1.0, 1.0).into()), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index ca142616fdb53..486d93991d9c0 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -17,14 +17,14 @@ fn setup( ) { // circular base commands.spawn(PbrBundle { - mesh: meshes.add(shape::Circle::new(4.0).into()), + mesh: meshes.add(primitives::Circle { radius: 4.0 }.into()), material: materials.add(Color::WHITE.into()), transform: Transform::from_rotation(Quat::from_rotation_x(-std::f32::consts::FRAC_PI_2)), ..default() }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(primitives::Cuboid::new(1.0, 1.0, 1.0).into()), material: materials.add(Color::rgb_u8(124, 144, 255).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 8002293d9d8a6..4388be54629a3 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -76,7 +76,12 @@ fn setup( // ground plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(50.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(50.0)) + .into(), + ), material: materials.add(Color::SILVER.into()), ..default() }); diff --git a/examples/3d/3d_viewport_to_world.rs b/examples/3d/3d_viewport_to_world.rs index 308e379a4e113..b6d941fa669d6 100644 --- a/examples/3d/3d_viewport_to_world.rs +++ b/examples/3d/3d_viewport_to_world.rs @@ -51,7 +51,12 @@ fn setup( // plane commands.spawn(( PbrBundle { - mesh: meshes.add(shape::Plane::from_size(20.).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(20.)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }, diff --git a/examples/3d/anti_aliasing.rs b/examples/3d/anti_aliasing.rs index 849bdbbc93a83..d4dda98033902 100644 --- a/examples/3d/anti_aliasing.rs +++ b/examples/3d/anti_aliasing.rs @@ -259,7 +259,12 @@ fn setup( ) { // Plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(50.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(50.0)) + .into(), + ), material: materials.add(Color::rgb(0.1, 0.2, 0.1).into()), ..default() }); @@ -272,7 +277,7 @@ fn setup( // Cubes for i in 0..5 { commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 0.25 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.25)))), material: cube_material.clone(), transform: Transform::from_xyz(i as f32 * 0.25 - 1.0, 0.125, -i as f32 * 0.5), ..default() diff --git a/examples/3d/atmospheric_fog.rs b/examples/3d/atmospheric_fog.rs index 60071e2dd9554..22cd01a4b963c 100644 --- a/examples/3d/atmospheric_fog.rs +++ b/examples/3d/atmospheric_fog.rs @@ -79,7 +79,7 @@ fn setup_terrain_scene( // Sky commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Box::default())), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(StandardMaterial { base_color: Color::hex("888888").unwrap(), unlit: true, diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index d378a713ec82e..42870fce5204a 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -37,13 +37,7 @@ fn setup( asset_server: Res, ) { let base_color = Color::rgba(0.9, 0.2, 0.3, 1.0); - let icosphere_mesh = meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 0.9, - subdivisions: 7, - }) - .unwrap(), - ); + let icosphere_mesh = meshes.add(primitives::Sphere { radius: 0.9 }.mesh().ico(7).unwrap()); // Opaque let opaque = commands @@ -149,7 +143,12 @@ fn setup( let black_material = materials.add(Color::BLACK.into()); let white_material = materials.add(Color::WHITE.into()); - let plane_mesh = meshes.add(shape::Plane::from_size(2.0).into()); + let plane_mesh = meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(2.0)) + .into(), + ); for x in -3..4 { for z in -3..4 { diff --git a/examples/3d/bloom_3d.rs b/examples/3d/bloom_3d.rs index b67f1d3909a93..8c72c4522edc8 100644 --- a/examples/3d/bloom_3d.rs +++ b/examples/3d/bloom_3d.rs @@ -55,14 +55,7 @@ fn setup_scene( ..default() }); - let mesh = meshes.add( - shape::Icosphere { - radius: 0.5, - subdivisions: 5, - } - .try_into() - .unwrap(), - ); + let mesh = meshes.add(primitives::Sphere { radius: 0.5 }.mesh().ico(5).unwrap()); for x in -5..5 { for z in -5..5 { diff --git a/examples/3d/deferred_rendering.rs b/examples/3d/deferred_rendering.rs index 6b39e3fd4f160..8cad535d9178e 100644 --- a/examples/3d/deferred_rendering.rs +++ b/examples/3d/deferred_rendering.rs @@ -103,16 +103,18 @@ fn setup( // Plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(50.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(50.0)) + .into(), + ), material: forward_mat_h.clone(), ..default() }); - let cube_h = meshes.add(Mesh::from(shape::Cube { size: 0.1 })); - let sphere_h = meshes.add(Mesh::from(shape::UVSphere { - radius: 0.125, - ..default() - })); + let cube_h = meshes.add(primitives::Cuboid::from_size(Vec3::splat(0.1)).into()); + let sphere_h = meshes.add(primitives::Sphere { radius: 0.125 }.mesh().uv(36, 18)); // Cubes commands.spawn(PbrBundle { @@ -196,7 +198,7 @@ fn setup( // sky commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Box::default())), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(StandardMaterial { base_color: Color::hex("888888").unwrap(), unlit: true, @@ -257,7 +259,7 @@ fn setup_parallax( let normal_handle = asset_server.load("textures/parallax_example/cube_normal.png"); normal.0 = Some(normal_handle); - let mut cube: Mesh = shape::Cube { size: 0.15 }.into(); + let mut cube: Mesh = primitives::Cuboid::from_size(Vec3::splat(0.15)).into(); // NOTE: for normal maps and depth maps to work, the mesh // needs tangents generated. diff --git a/examples/3d/fog.rs b/examples/3d/fog.rs index 253c2c8d567b5..36078ca6d6dab 100644 --- a/examples/3d/fog.rs +++ b/examples/3d/fog.rs @@ -58,16 +58,9 @@ fn setup_pyramid_scene( // pillars for (x, z) in &[(-1.5, -1.5), (1.5, -1.5), (1.5, 1.5), (-1.5, 1.5)] { commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Box { - min_x: -0.5, - max_x: 0.5, - min_z: -0.5, - max_z: 0.5, - min_y: 0.0, - max_y: 3.0, - })), + mesh: meshes.add(primitives::Cuboid::new(1.0, 3.0, 1.0).into()), material: stone.clone(), - transform: Transform::from_xyz(*x, 0.0, *z), + transform: Transform::from_xyz(*x, 1.5, *z), ..default() }); } @@ -75,7 +68,7 @@ fn setup_pyramid_scene( // orb commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::try_from(shape::Icosphere::default()).unwrap()), + mesh: meshes.add(primitives::Sphere::default().into()), material: materials.add(StandardMaterial { base_color: Color::hex("126212CC").unwrap(), reflectance: 1.0, @@ -94,17 +87,14 @@ fn setup_pyramid_scene( // steps for i in 0..50 { - let size = i as f32 / 2.0 + 3.0; - let y = -i as f32 / 2.0; + let size = i as f32 + 6.0; + let y = -i as f32 / 2.0 + 0.25; commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Box { - min_x: -size, - max_x: size, - min_z: -size, - max_z: size, - min_y: 0.0, - max_y: 0.5, - })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::new( + 2.0 * size, + 0.5, + 2.0 * size, + ))), material: stone.clone(), transform: Transform::from_xyz(0.0, y, 0.0), ..default() @@ -113,7 +103,7 @@ fn setup_pyramid_scene( // sky commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Box::default())), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(StandardMaterial { base_color: Color::hex("888888").unwrap(), unlit: true, diff --git a/examples/3d/lighting.rs b/examples/3d/lighting.rs index 614bf575ab5df..e19b419231b27 100644 --- a/examples/3d/lighting.rs +++ b/examples/3d/lighting.rs @@ -25,7 +25,12 @@ fn setup( ) { // ground plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(10.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(10.0)) + .into(), + ), material: materials.add(StandardMaterial { base_color: Color::WHITE, perceptual_roughness: 1.0, @@ -38,7 +43,7 @@ fn setup( let mut transform = Transform::from_xyz(2.5, 2.5, 0.0); transform.rotate_z(PI / 2.); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))), + mesh: meshes.add(Mesh::from(primitives::Cuboid::new(5.0, 0.15, 5.0))), transform, material: materials.add(StandardMaterial { base_color: Color::INDIGO, @@ -51,7 +56,7 @@ fn setup( let mut transform = Transform::from_xyz(0.0, 2.5, -2.5); transform.rotate_x(PI / 2.); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Box::new(5.0, 0.15, 5.0))), + mesh: meshes.add(Mesh::from(primitives::Cuboid::new(5.0, 0.15, 5.0))), transform, material: materials.add(StandardMaterial { base_color: Color::INDIGO, @@ -66,7 +71,7 @@ fn setup( transform.rotate_y(PI / 8.); commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Quad::new(Vec2::new(2.0, 0.5)))), + mesh: meshes.add(Mesh::from(primitives::Rectangle::new(2.0, 0.5))), transform, material: materials.add(StandardMaterial { base_color_texture: Some(asset_server.load("branding/bevy_logo_light.png")), @@ -83,7 +88,7 @@ fn setup( // cube commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::new(1.0, 1.0, 1.0))), material: materials.add(StandardMaterial { base_color: Color::PINK, ..default() @@ -96,10 +101,7 @@ fn setup( // sphere commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::UVSphere { - radius: 0.5, - ..default() - })), + mesh: meshes.add(primitives::Sphere { radius: 0.5 }.mesh().uv(32, 18)), material: materials.add(StandardMaterial { base_color: Color::LIME_GREEN, ..default() @@ -131,10 +133,7 @@ fn setup( }) .with_children(|builder| { builder.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::UVSphere { - radius: 0.1, - ..default() - })), + mesh: meshes.add(primitives::Sphere { radius: 0.1 }.mesh().uv(32, 18)), material: materials.add(StandardMaterial { base_color: Color::RED, emissive: Color::rgba_linear(7.13, 0.0, 0.0, 0.0), @@ -162,11 +161,7 @@ fn setup( .with_children(|builder| { builder.spawn(PbrBundle { transform: Transform::from_rotation(Quat::from_rotation_x(PI / 2.0)), - mesh: meshes.add(Mesh::from(shape::Capsule { - depth: 0.125, - radius: 0.1, - ..default() - })), + mesh: meshes.add(Mesh::from(primitives::Capsule::new(0.1, 0.125).mesh())), material: materials.add(StandardMaterial { base_color: Color::GREEN, emissive: Color::rgba_linear(0.0, 7.13, 0.0, 0.0), @@ -191,10 +186,7 @@ fn setup( }) .with_children(|builder| { builder.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::UVSphere { - radius: 0.1, - ..default() - })), + mesh: meshes.add(primitives::Sphere { radius: 0.1 }.mesh().uv(32, 18)), material: materials.add(StandardMaterial { base_color: Color::BLUE, emissive: Color::rgba_linear(0.0, 0.0, 7.13, 0.0), diff --git a/examples/3d/orthographic.rs b/examples/3d/orthographic.rs index c34e06a788513..b72dceda276eb 100644 --- a/examples/3d/orthographic.rs +++ b/examples/3d/orthographic.rs @@ -29,31 +29,33 @@ fn setup( // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), + mesh: meshes.add(Mesh::from( + primitives::Plane3d::default().mesh().size(Vec2::splat(5.0)), + )), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // cubes commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::ONE))), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(1.5, 0.5, 1.5), ..default() }); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::ONE))), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(1.5, 0.5, -1.5), ..default() }); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::ONE))), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(-1.5, 0.5, 1.5), ..default() }); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::ONE))), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(-1.5, 0.5, -1.5), ..default() diff --git a/examples/3d/parallax_mapping.rs b/examples/3d/parallax_mapping.rs index 37098494ee2eb..55580fabe10de 100644 --- a/examples/3d/parallax_mapping.rs +++ b/examples/3d/parallax_mapping.rs @@ -233,25 +233,17 @@ fn setup( }) .with_children(|commands| { // represent the light source as a sphere - let mesh = meshes.add( - shape::Icosphere { - radius: 0.05, - subdivisions: 3, - } - .try_into() - .unwrap(), - ); + let mesh = meshes.add(primitives::Sphere { radius: 0.05 }.mesh().ico(3).unwrap()); commands.spawn(PbrBundle { mesh, ..default() }); }); // Plane commands.spawn(PbrBundle { mesh: meshes.add( - shape::Plane { - size: 10.0, - subdivisions: 0, - } - .into(), + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(10.0)) + .into(), ), material: materials.add(StandardMaterial { // standard material derived from dark green, but @@ -284,7 +276,7 @@ fn setup( mesh: meshes.add( // NOTE: for normal maps and depth maps to work, the mesh // needs tangents generated. - Mesh::from(shape::Cube { size: 1.0 }) + Mesh::from(primitives::Cuboid::from_size(Vec3::splat(1.0))) .with_generated_tangents() .unwrap(), ), @@ -295,7 +287,7 @@ fn setup( )); let background_cube = meshes.add( - Mesh::from(shape::Cube { size: 40.0 }) + Mesh::from(primitives::Cuboid::from_size(Vec3::splat(40.0))) .with_generated_tangents() .unwrap(), ); diff --git a/examples/3d/parenting.rs b/examples/3d/parenting.rs index 827d8ffbec782..aff4b29726758 100644 --- a/examples/3d/parenting.rs +++ b/examples/3d/parenting.rs @@ -28,7 +28,7 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 2.0 })); + let cube_handle = meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(2.0)))); let cube_material_handle = materials.add(StandardMaterial { base_color: Color::rgb(0.8, 0.7, 0.6), ..default() diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index 348fc7d9d5750..88bafb0bb96d1 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -17,13 +17,7 @@ fn setup( mut materials: ResMut>, asset_server: Res, ) { - let sphere_mesh = meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 0.45, - ..default() - }) - .unwrap(), - ); + let sphere_mesh = meshes.add(primitives::Sphere { radius: 0.45 }.mesh().ico(5).unwrap()); // add entities to the world for y in -2..=2 { for x in -5..=5 { diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index a319e7d6e4e03..35494e1d3b2ef 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -63,7 +63,7 @@ fn setup( let image_handle = images.add(image); - let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 4.0 })); + let cube_handle = meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(4.0)))); let cube_material_handle = materials.add(StandardMaterial { base_color: Color::rgb(0.8, 0.7, 0.6), reflectance: 0.02, @@ -117,8 +117,8 @@ fn setup( first_pass_layer, )); - let cube_size = 4.0; - let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size))); + let cube_size = Vec3::splat(4.0); + let cube_handle = meshes.add(Mesh::from(primitives::Cuboid::from_size(cube_size))); // This material has the texture that has been rendered. let material_handle = materials.add(StandardMaterial { diff --git a/examples/3d/shadow_biases.rs b/examples/3d/shadow_biases.rs index 8900b75517f55..9ea1560cb7f85 100644 --- a/examples/3d/shadow_biases.rs +++ b/examples/3d/shadow_biases.rs @@ -41,10 +41,11 @@ fn setup( ..default() }); let sphere_handle = meshes.add( - Mesh::try_from(shape::Icosphere { + primitives::Sphere { radius: sphere_radius, - ..default() - }) + } + .mesh() + .ico(5) .unwrap(), ); @@ -112,7 +113,12 @@ fn setup( // ground plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(2.0 * spawn_plane_depth).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(2.0 * spawn_plane_depth)) + .into(), + ), material: white_handle, ..default() }); diff --git a/examples/3d/shadow_caster_receiver.rs b/examples/3d/shadow_caster_receiver.rs index b94144d8dfab4..a15f9d50aa63d 100644 --- a/examples/3d/shadow_caster_receiver.rs +++ b/examples/3d/shadow_caster_receiver.rs @@ -29,20 +29,13 @@ fn setup( ) { let spawn_plane_depth = 500.0f32; let spawn_height = 2.0; - let sphere_radius = 0.25; let white_handle = materials.add(StandardMaterial { base_color: Color::WHITE, perceptual_roughness: 1.0, ..default() }); - let sphere_handle = meshes.add( - Mesh::try_from(shape::Icosphere { - radius: sphere_radius, - ..default() - }) - .unwrap(), - ); + let sphere_handle = meshes.add(primitives::Sphere { radius: 0.25 }.mesh().ico(5).unwrap()); // sphere - initially a caster commands.spawn(PbrBundle { @@ -66,7 +59,12 @@ fn setup( // floating plane - initially not a shadow receiver and not a caster commands.spawn(( PbrBundle { - mesh: meshes.add(shape::Plane::from_size(20.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(20.0)) + .into(), + ), material: materials.add(Color::GREEN.into()), transform: Transform::from_xyz(0.0, 1.0, -10.0), ..default() @@ -77,7 +75,12 @@ fn setup( // lower ground plane - initially a shadow receiver commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(20.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(20.0)) + .into(), + ), material: white_handle, ..default() }); diff --git a/examples/3d/spherical_area_lights.rs b/examples/3d/spherical_area_lights.rs index 99dfc14d09eeb..9b631e15f4498 100644 --- a/examples/3d/spherical_area_lights.rs +++ b/examples/3d/spherical_area_lights.rs @@ -22,7 +22,12 @@ fn setup( // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(100.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(100.0)) + .into(), + ), material: materials.add(StandardMaterial { base_color: Color::rgb(0.2, 0.2, 0.2), perceptual_roughness: 0.08, @@ -36,11 +41,7 @@ fn setup( let radius_range = 0.0..0.8; let pos_len = position_range.end - position_range.start; let radius_len = radius_range.end - radius_range.start; - let mesh = meshes.add(Mesh::from(shape::UVSphere { - sectors: 128, - stacks: 64, - ..default() - })); + let mesh = meshes.add(primitives::Sphere::default().mesh().uv(128, 64)); for i in 0..COUNT { let percent = i as f32 / COUNT as f32; diff --git a/examples/3d/split_screen.rs b/examples/3d/split_screen.rs index 1a0f215d6464f..48b70367f6f98 100644 --- a/examples/3d/split_screen.rs +++ b/examples/3d/split_screen.rs @@ -24,7 +24,12 @@ fn setup( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(100.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(100.0)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); diff --git a/examples/3d/spotlight.rs b/examples/3d/spotlight.rs index db1ba40172817..b196495af48de 100644 --- a/examples/3d/spotlight.rs +++ b/examples/3d/spotlight.rs @@ -35,14 +35,19 @@ fn setup( ) { // ground plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(100.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(100.0)) + .into(), + ), material: materials.add(Color::WHITE.into()), ..default() }); // cubes let mut rng = StdRng::seed_from_u64(19878367467713); - let cube_mesh = meshes.add(Mesh::from(shape::Cube { size: 0.5 })); + let cube_mesh = meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.5)))); let blue = materials.add(Color::rgb_u8(124, 144, 255).into()); for _ in 0..40 { let x = rng.gen_range(-5.0..5.0); @@ -59,14 +64,8 @@ fn setup( )); } - let sphere_mesh = meshes.add(Mesh::from(shape::UVSphere { - radius: 0.05, - ..default() - })); - let sphere_mesh_direction = meshes.add(Mesh::from(shape::UVSphere { - radius: 0.1, - ..default() - })); + let sphere_mesh = meshes.add(primitives::Sphere { radius: 0.05 }.mesh().uv(32, 18)); + let sphere_mesh_direction = meshes.add(primitives::Sphere { radius: 0.1 }.mesh().uv(32, 18)); let red_emissive = materials.add(StandardMaterial { base_color: Color::RED, emissive: Color::rgba_linear(1.0, 0.0, 0.0, 0.0), diff --git a/examples/3d/ssao.rs b/examples/3d/ssao.rs index 61e1ef31953d7..f86a69659db95 100644 --- a/examples/3d/ssao.rs +++ b/examples/3d/ssao.rs @@ -48,30 +48,26 @@ fn setup( ..default() }); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::ONE))), material: material.clone(), transform: Transform::from_xyz(0.0, 0.0, 1.0), ..default() }); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::ONE))), material: material.clone(), transform: Transform::from_xyz(0.0, -1.0, 0.0), ..default() }); commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::ONE))), material, transform: Transform::from_xyz(1.0, 0.0, 0.0), ..default() }); commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::UVSphere { - radius: 0.4, - sectors: 72, - stacks: 36, - })), + mesh: meshes.add(primitives::Sphere { radius: 0.4 }.mesh().uv(72, 36)), material: materials.add(StandardMaterial { base_color: Color::rgb(0.4, 0.4, 0.4), perceptual_roughness: 1.0, diff --git a/examples/3d/texture.rs b/examples/3d/texture.rs index b0036abb4872f..2cf0c4f501837 100644 --- a/examples/3d/texture.rs +++ b/examples/3d/texture.rs @@ -24,10 +24,10 @@ fn setup( // create a new quad mesh. this is what we will apply the texture to let quad_width = 8.0; - let quad_handle = meshes.add(Mesh::from(shape::Quad::new(Vec2::new( + let quad_handle = meshes.add(Mesh::from(primitives::Rectangle::new( quad_width, quad_width * aspect, - )))); + ))); // this material renders the texture normally let material_handle = materials.add(StandardMaterial { diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index 62601e8cb2434..0912d04d67724 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -2,7 +2,6 @@ use bevy::{ core_pipeline::tonemapping::Tonemapping, - math::vec2, pbr::CascadeShadowConfigBuilder, prelude::*, reflect::TypePath, @@ -106,7 +105,12 @@ fn setup_basic_scene( // plane commands.spawn(( PbrBundle { - mesh: meshes.add(shape::Plane::from_size(50.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(50.0)) + .into(), + ), material: materials.add(Color::rgb(0.1, 0.2, 0.1).into()), ..default() }, @@ -119,7 +123,7 @@ fn setup_basic_scene( ..default() }); - let cube_mesh = meshes.add(Mesh::from(shape::Cube { size: 0.25 })); + let cube_mesh = meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.25)))); for i in 0..5 { commands.spawn(( PbrBundle { @@ -133,10 +137,7 @@ fn setup_basic_scene( } // spheres - let sphere_mesh = meshes.add(Mesh::from(shape::UVSphere { - radius: 0.125, - ..default() - })); + let sphere_mesh = meshes.add(primitives::Sphere { radius: 0.125 }.mesh().uv(32, 18)); for i in 0..6 { let j = i % 3; let s_val = if i < 3 { 0.0 } else { 0.2 }; @@ -225,10 +226,9 @@ fn setup_color_gradient_scene( commands.spawn(( MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Quad { - size: vec2(1.0, 1.0) * 0.7, - flip: false, - })), + mesh: meshes.add(Mesh::from(primitives::Rectangle::from_size(Vec2::splat( + 0.7, + )))), material: materials.add(ColorGradientMaterial {}), transform, visibility: Visibility::Hidden, @@ -250,10 +250,7 @@ fn setup_image_viewer_scene( // exr/hdr viewer (exr requires enabling bevy feature) commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Quad { - size: vec2(1.0, 1.0), - flip: false, - })), + mesh: meshes.add(Mesh::from(primitives::Rectangle::default())), material: materials.add(StandardMaterial { base_color_texture: None, unlit: true, @@ -336,7 +333,7 @@ fn update_image_viewer( if let Some(image_changed) = images.get(image_changed_id) { let size = image_changed.size_f32().normalize_or_zero() * 1.4; // Resize Mesh - let quad = Mesh::from(shape::Quad::new(size)); + let quad = Mesh::from(primitives::Rectangle::from_size(size)); meshes.insert(mesh_h, quad); } } diff --git a/examples/3d/transmission.rs b/examples/3d/transmission.rs index 591e4e7bc4311..83af347c16b2f 100644 --- a/examples/3d/transmission.rs +++ b/examples/3d/transmission.rs @@ -68,27 +68,18 @@ fn setup( mut materials: ResMut>, asset_server: Res, ) { - let icosphere_mesh = meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 0.9, - subdivisions: 7, - }) - .unwrap(), + let icosphere_mesh = meshes.add(primitives::Sphere { radius: 0.9 }.mesh().ico(7).unwrap()); + let cube_mesh = meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.7)))); + let plane_mesh = meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(2.0)) + .into(), ); - let cube_mesh = meshes.add(Mesh::from(shape::Cube { size: 0.7 })); - - let plane_mesh = meshes.add(shape::Plane::from_size(2.0).into()); - - let cylinder_mesh = meshes.add( - Mesh::try_from(shape::Cylinder { - radius: 0.5, - height: 2.0, - resolution: 50, - segments: 1, - }) - .unwrap(), - ); + let cylinder_mesh = meshes.add(Mesh::from( + primitives::Cylinder::new(0.5, 2.0).mesh().resolution(50), + )); // Cube #1 commands.spawn(( diff --git a/examples/3d/transparency_3d.rs b/examples/3d/transparency_3d.rs index f048f959423d2..8ff824cae3e9f 100644 --- a/examples/3d/transparency_3d.rs +++ b/examples/3d/transparency_3d.rs @@ -20,19 +20,18 @@ fn setup( ) { // opaque plane, uses `alpha_mode: Opaque` by default commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(6.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(6.0)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // transparent sphere, uses `alpha_mode: Mask(f32)` commands.spawn(PbrBundle { - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 0.5, - subdivisions: 3, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere { radius: 0.5 }.mesh().ico(3).unwrap()), material: materials.add(StandardMaterial { // Alpha channel of the color controls transparency. // We set it to 0.0 here, because it will be changed over time in the @@ -49,13 +48,7 @@ fn setup( }); // transparent unlit sphere, uses `alpha_mode: Mask(f32)` commands.spawn(PbrBundle { - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 0.5, - subdivisions: 3, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere { radius: 0.5 }.mesh().ico(3).unwrap()), material: materials.add(StandardMaterial { base_color: Color::rgba(0.2, 0.7, 0.1, 0.0), alpha_mode: AlphaMode::Mask(0.5), @@ -67,7 +60,7 @@ fn setup( }); // transparent cube, uses `alpha_mode: Blend` commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), // Notice how there is no need to set the `alpha_mode` explicitly here. // When converting a color to a material using `into()`, the alpha mode is // automatically set to `Blend` if the alpha channel is anything lower than 1.0. @@ -77,13 +70,7 @@ fn setup( }); // opaque sphere commands.spawn(PbrBundle { - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 0.5, - subdivisions: 3, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere::default().mesh().ico(3).unwrap()), material: materials.add(Color::rgb(0.7, 0.2, 0.1).into()), transform: Transform::from_xyz(0.0, 0.5, -1.5), ..default() diff --git a/examples/3d/two_passes.rs b/examples/3d/two_passes.rs index eded9674964da..83f00953820a9 100644 --- a/examples/3d/two_passes.rs +++ b/examples/3d/two_passes.rs @@ -17,13 +17,18 @@ fn setup( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(5.0)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() diff --git a/examples/3d/vertex_colors.rs b/examples/3d/vertex_colors.rs index 93d05882b76d5..9f85f3d5292ba 100644 --- a/examples/3d/vertex_colors.rs +++ b/examples/3d/vertex_colors.rs @@ -17,13 +17,18 @@ fn setup( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(5.0)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // cube // Assign vertex colors based on vertex positions - let mut colorful_cube = Mesh::from(shape::Cube { size: 1.0 }); + let mut colorful_cube = Mesh::from(primitives::Cuboid::default()); if let Some(VertexAttributeValues::Float32x3(positions)) = colorful_cube.attribute(Mesh::ATTRIBUTE_POSITION) { diff --git a/examples/3d/wireframe.rs b/examples/3d/wireframe.rs index 058ba18f6c108..0e696b2b8d3c2 100644 --- a/examples/3d/wireframe.rs +++ b/examples/3d/wireframe.rs @@ -54,7 +54,9 @@ fn setup( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane::from_size(5.0))), + mesh: meshes.add(Mesh::from( + primitives::Plane3d::default().mesh().size(Vec2::splat(5.0)), + )), material: materials.add(Color::BLUE.into()), ..default() }); @@ -62,7 +64,7 @@ fn setup( // Red cube: Never renders a wireframe commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::RED.into()), transform: Transform::from_xyz(-1.0, 0.5, -1.0), ..default() @@ -71,7 +73,7 @@ fn setup( )); // Orange cube: Follows global wireframe setting commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::ORANGE.into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() @@ -79,7 +81,7 @@ fn setup( // Green cube: Always renders a wireframe commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::GREEN.into()), transform: Transform::from_xyz(1.0, 0.5, 1.0), ..default() diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index cf695d81aa1f6..75b834866be2f 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -45,7 +45,12 @@ fn setup( // Plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(500000.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(00000.0)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); diff --git a/examples/animation/animated_transform.rs b/examples/animation/animated_transform.rs index 0a3aa023986c6..04d7e6a2f3162 100644 --- a/examples/animation/animated_transform.rs +++ b/examples/animation/animated_transform.rs @@ -118,7 +118,7 @@ fn setup( commands .spawn(( PbrBundle { - mesh: meshes.add(Mesh::try_from(shape::Icosphere::default()).unwrap()), + mesh: meshes.add(Mesh::try_from(primitives::Sphere::default()).unwrap()), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), ..default() }, @@ -138,7 +138,8 @@ fn setup( p.spawn(( PbrBundle { transform: Transform::from_xyz(1.5, 0.0, 0.0), - mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })), + mesh: meshes + .add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.5)))), material: materials.add(Color::rgb(0.3, 0.9, 0.3).into()), ..default() }, diff --git a/examples/animation/cubic_curve.rs b/examples/animation/cubic_curve.rs index 92a003edaa723..eb87b2c54e5bb 100644 --- a/examples/animation/cubic_curve.rs +++ b/examples/animation/cubic_curve.rs @@ -38,7 +38,7 @@ fn setup( // Spawning a cube to experiment on commands.spawn(( PbrBundle { - mesh: meshes.add(shape::Cube::default().into()), + mesh: meshes.add(primitives::Cuboid::default().into()), material: materials.add(Color::ORANGE.into()), transform: Transform::from_translation(points[0][0]), ..default() @@ -60,7 +60,12 @@ fn setup( // ground plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(50.).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(50.)) + .into(), + ), material: materials.add(Color::SILVER.into()), ..default() }); diff --git a/examples/async_tasks/async_compute.rs b/examples/async_tasks/async_compute.rs index ebf4eaa986b55..e0cd8fc103ad5 100644 --- a/examples/async_tasks/async_compute.rs +++ b/examples/async_tasks/async_compute.rs @@ -35,7 +35,7 @@ fn add_assets( mut meshes: ResMut>, mut materials: ResMut>, ) { - let box_mesh_handle = meshes.add(Mesh::from(shape::Cube { size: 0.25 })); + let box_mesh_handle = meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.25)))); commands.insert_resource(BoxMeshHandle(box_mesh_handle)); let box_material_handle = materials.add(Color::rgb(1.0, 0.2, 0.3).into()); diff --git a/examples/audio/spatial_audio_2d.rs b/examples/audio/spatial_audio_2d.rs index 8387c688d38ac..829052eabacc6 100644 --- a/examples/audio/spatial_audio_2d.rs +++ b/examples/audio/spatial_audio_2d.rs @@ -34,7 +34,9 @@ fn setup( // sound emitter commands.spawn(( MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::new(15.0).into()).into(), + mesh: meshes + .add(primitives::Circle { radius: 15.0 }.into()) + .into(), material: materials.add(ColorMaterial::from(Color::BLUE)), transform: Transform::from_translation(Vec3::new(0.0, 50.0, 0.0)), ..default() diff --git a/examples/audio/spatial_audio_3d.rs b/examples/audio/spatial_audio_3d.rs index 22a2537468685..5f7861f041fb1 100644 --- a/examples/audio/spatial_audio_3d.rs +++ b/examples/audio/spatial_audio_3d.rs @@ -22,10 +22,7 @@ fn setup( // sound emitter commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::UVSphere { - radius: 0.2, - ..default() - })), + mesh: meshes.add(primitives::Sphere { radius: 0.2 }.mesh().uv(32, 18)), material: materials.add(Color::BLUE.into()), transform: Transform::from_xyz(0.0, 0.0, 0.0), ..default() @@ -43,7 +40,7 @@ fn setup( .with_children(|parent| { // left ear indicator parent.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 0.2 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.2)))), material: materials.add(Color::RED.into()), transform: Transform::from_translation(listener.left_ear_offset), ..default() @@ -51,7 +48,7 @@ fn setup( // right ear indicator parent.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 0.2 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.2)))), material: materials.add(Color::GREEN.into()), transform: Transform::from_translation(listener.right_ear_offset), ..default() diff --git a/examples/ecs/iter_combinations.rs b/examples/ecs/iter_combinations.rs index 363dfc726c768..e755d29f0ac2a 100644 --- a/examples/ecs/iter_combinations.rs +++ b/examples/ecs/iter_combinations.rs @@ -43,14 +43,7 @@ fn generate_bodies( mut meshes: ResMut>, mut materials: ResMut>, ) { - let mesh = meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 1.0, - subdivisions: 3, - }) - .unwrap(), - ); - + let mesh = meshes.add(primitives::Sphere { radius: 1.0 }.mesh().ico(3).unwrap()); let color_range = 0.5..1.0; let vel_range = -0.5..0.5; @@ -106,13 +99,7 @@ fn generate_bodies( BodyBundle { pbr: PbrBundle { transform: Transform::from_scale(Vec3::splat(star_radius)), - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 1.0, - subdivisions: 5, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere { radius: 1.0 }.mesh().ico(5).unwrap()), material: materials.add(StandardMaterial { base_color: Color::ORANGE_RED, emissive: (Color::ORANGE_RED * 2.), diff --git a/examples/games/breakout.rs b/examples/games/breakout.rs index 9e5392df850c0..566ecceacf062 100644 --- a/examples/games/breakout.rs +++ b/examples/games/breakout.rs @@ -207,7 +207,7 @@ fn setup( // Ball commands.spawn(( MaterialMesh2dBundle { - mesh: meshes.add(shape::Circle::default().into()).into(), + mesh: meshes.add(primitives::Circle::default().into()).into(), material: materials.add(ColorMaterial::from(BALL_COLOR)), transform: Transform::from_translation(BALL_STARTING_POSITION).with_scale(BALL_SIZE), ..default() diff --git a/examples/mobile/src/lib.rs b/examples/mobile/src/lib.rs index d0ce7c2f9bcd2..942665f565428 100644 --- a/examples/mobile/src/lib.rs +++ b/examples/mobile/src/lib.rs @@ -62,26 +62,25 @@ fn setup_scene( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(5.0)) + .into(), + ), material: materials.add(Color::rgb(0.1, 0.2, 0.1).into()), ..default() }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.5, 0.4, 0.3).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() }); // sphere commands.spawn(PbrBundle { - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - subdivisions: 4, - radius: 0.5, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere { radius: 0.5 }.mesh().ico(4).unwrap()), material: materials.add(Color::rgb(0.1, 0.4, 0.8).into()), transform: Transform::from_xyz(1.5, 1.5, 1.5), ..default() diff --git a/examples/shader/animate_shader.rs b/examples/shader/animate_shader.rs index a828420387ed6..26455b02f13c0 100644 --- a/examples/shader/animate_shader.rs +++ b/examples/shader/animate_shader.rs @@ -21,7 +21,7 @@ fn setup( ) { // cube commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), transform: Transform::from_xyz(0.0, 0.5, 0.0), material: materials.add(CustomMaterial {}), ..default() diff --git a/examples/shader/array_texture.rs b/examples/shader/array_texture.rs index 478119cfa9c57..a0a98078edea9 100644 --- a/examples/shader/array_texture.rs +++ b/examples/shader/array_texture.rs @@ -77,7 +77,7 @@ fn create_array_texture( image.reinterpret_stacked_2d_as_array(array_layers); // Spawn some cubes using the array texture - let mesh_handle = meshes.add(Mesh::from(shape::Cube { size: 1.0 })); + let mesh_handle = meshes.add(Mesh::from(primitives::Cuboid::default())); let material_handle = materials.add(ArrayTextureMaterial { array_texture: loading_texture.handle.clone(), }); diff --git a/examples/shader/custom_vertex_attribute.rs b/examples/shader/custom_vertex_attribute.rs index 0411af15a7634..920ea70fdcc02 100644 --- a/examples/shader/custom_vertex_attribute.rs +++ b/examples/shader/custom_vertex_attribute.rs @@ -31,7 +31,7 @@ fn setup( mut meshes: ResMut>, mut materials: ResMut>, ) { - let mesh = Mesh::from(shape::Cube { size: 1.0 }) + let mesh = Mesh::from(primitives::Cuboid::default()) // Sets the custom attribute .with_inserted_attribute( ATTRIBUTE_BLEND_COLOR, diff --git a/examples/shader/extended_material.rs b/examples/shader/extended_material.rs index 7f055a9bef860..d973270250724 100644 --- a/examples/shader/extended_material.rs +++ b/examples/shader/extended_material.rs @@ -24,13 +24,7 @@ fn setup( ) { // sphere commands.spawn(MaterialMeshBundle { - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 1.0, - subdivisions: 5, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere { radius: 1.0 }.mesh().ico(5).unwrap()), transform: Transform::from_xyz(0.0, 0.5, 0.0), material: materials.add(ExtendedMaterial { base: StandardMaterial { diff --git a/examples/shader/fallback_image.rs b/examples/shader/fallback_image.rs index b799b56d27e02..75521b6de74cb 100644 --- a/examples/shader/fallback_image.rs +++ b/examples/shader/fallback_image.rs @@ -27,7 +27,7 @@ fn setup( mut materials: ResMut>, ) { commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(FallbackTestMaterial { image_1d: None, image_2d: None, diff --git a/examples/shader/post_processing.rs b/examples/shader/post_processing.rs index ad684675d806b..f4df0ba0bf145 100644 --- a/examples/shader/post_processing.rs +++ b/examples/shader/post_processing.rs @@ -322,7 +322,7 @@ fn setup( // cube commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index a638c3569a054..3c4ac6efc57d6 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -27,7 +27,7 @@ fn setup( ) { // blue cube commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), transform: Transform::from_xyz(-1.0, 0.5, 0.0), material: materials.add(CustomMaterial { color: Color::BLUE, @@ -38,7 +38,7 @@ fn setup( // red cube (with green color overridden by the IS_RED "shader def") commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), transform: Transform::from_xyz(1.0, 0.5, 0.0), material: materials.add(CustomMaterial { color: Color::GREEN, diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index a0b4c7db79c82..3c5d43904ffcd 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -35,7 +35,7 @@ fn main() { fn setup(mut commands: Commands, mut meshes: ResMut>) { commands.spawn(( - meshes.add(Mesh::from(shape::Cube { size: 0.5 })), + meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.5)))), SpatialBundle::INHERITED_IDENTITY, InstanceMaterialData( (1..=10) diff --git a/examples/shader/shader_material.rs b/examples/shader/shader_material.rs index eb20a77566fb1..73e9f9520c11e 100644 --- a/examples/shader/shader_material.rs +++ b/examples/shader/shader_material.rs @@ -22,7 +22,7 @@ fn setup( ) { // cube commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), transform: Transform::from_xyz(0.0, 0.5, 0.0), material: materials.add(CustomMaterial { color: Color::BLUE, diff --git a/examples/shader/shader_material_2d.rs b/examples/shader/shader_material_2d.rs index 351b5c5c8bf72..7a8d86b9c95d3 100644 --- a/examples/shader/shader_material_2d.rs +++ b/examples/shader/shader_material_2d.rs @@ -29,7 +29,9 @@ fn setup( // quad commands.spawn(MaterialMesh2dBundle { - mesh: meshes.add(Mesh::from(shape::Quad::default())).into(), + mesh: meshes + .add(Mesh::from(primitives::Rectangle::default())) + .into(), transform: Transform::default().with_scale(Vec3::splat(128.)), material: materials.add(CustomMaterial { color: Color::BLUE, diff --git a/examples/shader/shader_material_glsl.rs b/examples/shader/shader_material_glsl.rs index f24cfa5070334..f394ad1864053 100644 --- a/examples/shader/shader_material_glsl.rs +++ b/examples/shader/shader_material_glsl.rs @@ -28,7 +28,7 @@ fn setup( ) { // cube commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), transform: Transform::from_xyz(0.0, 0.5, 0.0), material: materials.add(CustomMaterial { color: Color::BLUE, diff --git a/examples/shader/shader_material_screenspace_texture.rs b/examples/shader/shader_material_screenspace_texture.rs index ddee9a68097e0..088db00781c43 100644 --- a/examples/shader/shader_material_screenspace_texture.rs +++ b/examples/shader/shader_material_screenspace_texture.rs @@ -25,7 +25,12 @@ fn setup( mut standard_materials: ResMut>, ) { commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(5.0)) + .into(), + ), material: standard_materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); @@ -35,7 +40,7 @@ fn setup( }); commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), transform: Transform::from_xyz(0.0, 0.5, 0.0), material: custom_materials.add(CustomMaterial { texture: asset_server.load( diff --git a/examples/shader/shader_prepass.rs b/examples/shader/shader_prepass.rs index de17d4341fcfd..1d455a22d1131 100644 --- a/examples/shader/shader_prepass.rs +++ b/examples/shader/shader_prepass.rs @@ -61,7 +61,12 @@ fn setup( // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(5.0)) + .into(), + ), material: std_materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); @@ -71,7 +76,7 @@ fn setup( // For a real application, this isn't ideal. commands.spawn(( MaterialMeshBundle { - mesh: meshes.add(shape::Quad::new(Vec2::new(20.0, 20.0)).into()), + mesh: meshes.add(primitives::Rectangle::new(20.0, 20.0).into()), material: depth_materials.add(PrepassOutputMaterial { settings: ShowPrepassSettings::default(), }), @@ -85,7 +90,7 @@ fn setup( // Opaque cube commands.spawn(( MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(CustomMaterial { color: Color::WHITE, color_texture: Some(asset_server.load("branding/icon.png")), @@ -99,7 +104,7 @@ fn setup( // Cube with alpha mask commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: std_materials.add(StandardMaterial { alpha_mode: AlphaMode::Mask(1.0), base_color_texture: Some(asset_server.load("branding/icon.png")), @@ -112,7 +117,7 @@ fn setup( // Cube with alpha blending. // Transparent materials are ignored by the prepass commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(CustomMaterial { color: Color::WHITE, color_texture: Some(asset_server.load("branding/icon.png")), diff --git a/examples/shader/texture_binding_array.rs b/examples/shader/texture_binding_array.rs index 91c23479c6322..5345961cd4d2c 100644 --- a/examples/shader/texture_binding_array.rs +++ b/examples/shader/texture_binding_array.rs @@ -74,7 +74,7 @@ fn setup( // a cube with multiple textures commands.spawn(MaterialMeshBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(BindlessMaterial { textures }), ..Default::default() }); diff --git a/examples/stress_tests/bevymark.rs b/examples/stress_tests/bevymark.rs index 1c727b28d5927..ae0433ba7936c 100644 --- a/examples/stress_tests/bevymark.rs +++ b/examples/stress_tests/bevymark.rs @@ -206,7 +206,7 @@ fn setup( textures, materials, quad: meshes - .add(Mesh::from(shape::Quad::new(Vec2::splat( + .add(Mesh::from(primitives::Rectangle::from_size(Vec2::splat( BIRD_TEXTURE_SIZE as f32, )))) .into(), diff --git a/examples/stress_tests/many_cubes.rs b/examples/stress_tests/many_cubes.rs index 6a1542a025fab..e62c0ab67b2b4 100644 --- a/examples/stress_tests/many_cubes.rs +++ b/examples/stress_tests/many_cubes.rs @@ -101,7 +101,7 @@ fn setup( let images = images.into_inner(); let material_assets = material_assets.into_inner(); - let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 })); + let mesh = meshes.add(Mesh::from(primitives::Cuboid::default())); let material_textures = init_textures(args, images); let materials = init_materials(args, &material_textures, material_assets); diff --git a/examples/stress_tests/many_foxes.rs b/examples/stress_tests/many_foxes.rs index 896b601e4cd5b..c83fbe1b326fe 100644 --- a/examples/stress_tests/many_foxes.rs +++ b/examples/stress_tests/many_foxes.rs @@ -185,7 +185,12 @@ fn setup( // Plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5000.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(5000.0)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index c36a04920c792..c55516bb54353 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -48,19 +48,13 @@ fn setup( const N_LIGHTS: usize = 100_000; commands.spawn(PbrBundle { - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - radius: RADIUS, - subdivisions: 9, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere { radius: RADIUS }.mesh().ico(9).unwrap()), material: materials.add(StandardMaterial::from(Color::WHITE)), transform: Transform::from_scale(Vec3::NEG_ONE), ..default() }); - let mesh = meshes.add(Mesh::from(shape::Cube { size: 1.0 })); + let mesh = meshes.add(Mesh::from(primitives::Cuboid::default())); let material = materials.add(StandardMaterial { base_color: Color::PINK, ..default() diff --git a/examples/tools/gamepad_viewer.rs b/examples/tools/gamepad_viewer.rs index 29ef2c5af2386..3434402139b08 100644 --- a/examples/tools/gamepad_viewer.rs +++ b/examples/tools/gamepad_viewer.rs @@ -72,12 +72,23 @@ impl FromWorld for ButtonMeshes { fn from_world(world: &mut World) -> Self { let mut meshes = world.resource_mut::>(); Self { - circle: meshes.add(shape::Circle::new(BUTTON_RADIUS).into()).into(), + circle: meshes + .add( + primitives::Circle { + radius: BUTTON_RADIUS, + } + .into(), + ) + .into(), triangle: meshes - .add(shape::RegularPolygon::new(BUTTON_RADIUS, 3).into()) + .add(primitives::RegularPolygon::new(BUTTON_RADIUS, 3).into()) + .into(), + start_pause: meshes + .add(primitives::Rectangle::from_size(START_SIZE).into()) + .into(), + trigger: meshes + .add(primitives::Rectangle::from_size(TRIGGER_SIZE).into()) .into(), - start_pause: meshes.add(shape::Quad::new(START_SIZE).into()).into(), - trigger: meshes.add(shape::Quad::new(TRIGGER_SIZE).into()).into(), } } } diff --git a/examples/transforms/3d_rotation.rs b/examples/transforms/3d_rotation.rs index ddedbbae859e9..74aa9191b1956 100644 --- a/examples/transforms/3d_rotation.rs +++ b/examples/transforms/3d_rotation.rs @@ -26,7 +26,7 @@ fn setup( // Spawn a cube to rotate. commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::WHITE.into()), transform: Transform::from_translation(Vec3::ZERO), ..default() diff --git a/examples/transforms/scale.rs b/examples/transforms/scale.rs index bbae8b71b9be2..2ea79fdd1871e 100644 --- a/examples/transforms/scale.rs +++ b/examples/transforms/scale.rs @@ -42,7 +42,7 @@ fn setup( // Spawn a cube to scale. commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::WHITE.into()), transform: Transform::from_rotation(Quat::from_rotation_y(PI / 4.0)), ..default() diff --git a/examples/transforms/transform.rs b/examples/transforms/transform.rs index 38bad31c4fa30..3455ef5d68b31 100644 --- a/examples/transforms/transform.rs +++ b/examples/transforms/transform.rs @@ -46,13 +46,7 @@ fn setup( // Add an object (sphere) for visualizing scaling. commands.spawn(( PbrBundle { - mesh: meshes.add( - Mesh::try_from(shape::Icosphere { - radius: 3.0, - subdivisions: 32, - }) - .unwrap(), - ), + mesh: meshes.add(primitives::Sphere { radius: 3.0 }.mesh().ico(32).unwrap()), material: materials.add(Color::YELLOW.into()), transform: Transform::from_translation(Vec3::ZERO), ..default() @@ -73,7 +67,7 @@ fn setup( Transform::from_translation(Vec3::Z * -10.0).with_rotation(Quat::from_rotation_y(PI / 2.)); commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::WHITE.into()), transform: cube_spawn, ..default() diff --git a/examples/transforms/translation.rs b/examples/transforms/translation.rs index e8aaef4edfdd3..f5c9e5ceb2126 100644 --- a/examples/transforms/translation.rs +++ b/examples/transforms/translation.rs @@ -40,7 +40,7 @@ fn setup( let entity_spawn = Vec3::ZERO; commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::WHITE.into()), transform: Transform::from_translation(entity_spawn), ..default() diff --git a/examples/window/low_power.rs b/examples/window/low_power.rs index 8a1af9af3b2a4..68ccf8ff54436 100644 --- a/examples/window/low_power.rs +++ b/examples/window/low_power.rs @@ -151,7 +151,7 @@ pub(crate) mod test_setup { ) { commands.spawn(( PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 0.5 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::from_size(Vec3::splat(0.5)))), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), ..default() }, diff --git a/examples/window/screenshot.rs b/examples/window/screenshot.rs index 8ebfdaba5efec..c42a281928a02 100644 --- a/examples/window/screenshot.rs +++ b/examples/window/screenshot.rs @@ -35,13 +35,18 @@ fn setup( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(shape::Plane::from_size(5.0).into()), + mesh: meshes.add( + primitives::Plane3d::default() + .mesh() + .size(Vec2::splat(5.0)) + .into(), + ), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() diff --git a/tests/window/minimising.rs b/tests/window/minimising.rs index 40314a2153a9f..41784e11d17b9 100644 --- a/tests/window/minimising.rs +++ b/tests/window/minimising.rs @@ -35,16 +35,15 @@ fn setup_3d( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane { - size: 5.0, - subdivisions: 0, - })), + mesh: meshes.add(Mesh::from( + primitives::Plane3d::default().mesh().size(Vec2::splat(5.0)), + )), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() diff --git a/tests/window/resizing.rs b/tests/window/resizing.rs index 0d9135989c164..6af9b0533d9c0 100644 --- a/tests/window/resizing.rs +++ b/tests/window/resizing.rs @@ -118,16 +118,15 @@ fn setup_3d( ) { // plane commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Plane { - size: 5.0, - subdivisions: 0, - })), + mesh: meshes.add(Mesh::from( + primitives::Plane3d::default().mesh().size(Vec2::splat(5.0)), + )), material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()), ..default() }); // cube commands.spawn(PbrBundle { - mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), + mesh: meshes.add(Mesh::from(primitives::Cuboid::default())), material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), transform: Transform::from_xyz(0.0, 0.5, 0.0), ..default() From 6ebcc9bc3b75f48a2ba103aa795661defe1b3b3b Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 18:52:16 +0200 Subject: [PATCH 34/36] Fix import in doc example --- crates/bevy_render/src/mesh/shape/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index fff3ed7b06d75..e867b570358a4 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -76,6 +76,7 @@ impl Facing { /// # Example /// /// ``` + /// # use bevy_render::prelude::Facing; /// assert_eq!(Facing::X.to_array(), [1.0, 0.0, 0.0]); /// ``` #[inline] From ff949a98d2a020e8d18f414b4f5c42de2c0476a7 Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 19:43:26 +0200 Subject: [PATCH 35/36] Re-export more --- crates/bevy_render/src/mesh/shape/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index e867b570358a4..87589cae04128 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -16,7 +16,7 @@ mod sphere; mod torus; mod triangle; -pub use capsule::CapsuleMesh; +pub use capsule::{CapsuleMesh, CapsuleUvProfile}; pub use circle::CircleMesh; pub use cone::ConeMesh; pub use conical_frustum::ConicalFrustumMesh; @@ -26,7 +26,7 @@ pub use ellipse::EllipseMesh; pub use plane::PlaneMesh; pub use rectangle::RectangleMesh; pub use regular_polygon::RegularPolygonMesh; -pub use sphere::{SphereKind, SphereMesh}; +pub use sphere::{IcosphereError, SphereKind, SphereMesh}; pub use torus::TorusMesh; pub use triangle::Triangle2dMesh; From a0c42ce6f8011d66c99fc723ffc587ede9f650ef Mon Sep 17 00:00:00 2001 From: Joona Aalto Date: Sun, 17 Dec 2023 20:07:43 +0200 Subject: [PATCH 36/36] Add missing docs --- crates/bevy_render/src/mesh/shape/sphere.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_render/src/mesh/shape/sphere.rs b/crates/bevy_render/src/mesh/shape/sphere.rs index 07455a30fa09c..336edcad509eb 100644 --- a/crates/bevy_render/src/mesh/shape/sphere.rs +++ b/crates/bevy_render/src/mesh/shape/sphere.rs @@ -10,9 +10,12 @@ use wgpu::PrimitiveTopology; /// An error when creating an icosphere [`Mesh`] from a [`SphereMesh`]. #[derive(Clone, Copy, Debug, Error)] pub enum IcosphereError { + /// The icosphere has too many vertices. #[error("Cannot create an icosphere of {subdivisions} subdivisions due to there being too many vertices being generated: {number_of_resulting_points}. (Limited to 65535 vertices or 79 subdivisions)")] TooManyVertices { + /// The number of subdivisions used. 79 is the largest allowed value for a mesh to be generated. subdivisions: usize, + /// The number of vertices generated. 65535 is the largest allowed value for a mesh to be generated. number_of_resulting_points: usize, }, }