From 3cca0610e54d373e19d2e3e5ddd4302b783ede0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 04:57:51 +0100 Subject: [PATCH 01/18] Added ramp and tests for ramp --- crates/bevy_math/src/primitives/dim3.rs | 57 +++++++++++++++ .../src/mesh/primitives/dim3/mod.rs | 1 + .../src/mesh/primitives/dim3/ramp.rs | 73 +++++++++++++++++++ examples/3d/3d_shapes.rs | 1 + 4 files changed, 132 insertions(+) create mode 100644 crates/bevy_render/src/mesh/primitives/dim3/ramp.rs diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 4eca0869727f5..782f5cad18a8d 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -765,6 +765,51 @@ impl Torus { } } +/// A ramp primitive shape. +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct Ramp { + /// Half of the width, height and depth of the ramp + pub half_size: Vec3, +} +impl Primitive3d for Ramp {} + +impl Default for Ramp { + fn default() -> Self { + Self { + half_size: Vec3::splat(0.5), + } + } +} + +impl Ramp { + /// Create a new Ramp from the dimensions of the base and the angle of the wedge at the apex. + /// The slope is the angle (incline) of the ramp in radians + pub fn new(half_width: f32, half_depth: f32, slope: f32) -> Ramp { + let half_height = half_depth * slope.tan(); + Self { + half_size: Vec3::new(half_width, half_height, half_depth), + } + } + + /// Get the slope of the wedge + /// The slope is the angle (incline) of the ramp in radians + pub fn slope(&self) -> f32 { + self.half_size.y.atan2(self.half_size.z) + } + + /// Get the surface area of the ramp + pub fn area(&self) -> f32 { + let half = self.half_size; + let half_volume = half.x * (half.y + half.z + (half.y * half.y + half.z * half.z).sqrt()) + half.y * half.z; + half_volume * 4.0 + } + + /// Get the volume of the ramp + pub fn volume(&self) -> f32 { + self.half_size.x * self.half_size.y * self.half_size.z * 4.0 + } +} + #[cfg(test)] mod tests { // Reference values were computed by hand and/or with external tools @@ -930,4 +975,16 @@ mod tests { assert_relative_eq!(torus.area(), 33.16187); assert_relative_eq!(torus.volume(), 4.97428, epsilon = 0.00001); } + + #[test] + fn ramp_math() { + let ramp = Ramp { + half_size: Vec3::splat(0.75) + }; + assert_eq!(ramp.slope(), std::f32::consts::FRAC_PI_4, "incorrect computed slope"); + assert_eq!(ramp.area(), 9.9319805, "incorrect ramp area"); + assert_eq!(ramp.volume(), 1.6875, "incorrect ramp volume"); + + assert_relative_eq!(Ramp::new(0.5, 0.5, FRAC_PI_3).half_size.y, 0.8660254); + } } diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index e2b337bb31934..437b44666f2fd 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -2,6 +2,7 @@ mod capsule; mod cuboid; mod cylinder; mod plane; +mod ramp; mod sphere; mod torus; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs b/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs new file mode 100644 index 0000000000000..29bc140b94d46 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs @@ -0,0 +1,73 @@ +use bevy_math::{primitives::Ramp, Vec3}; +use wgpu::PrimitiveTopology; + +use crate::{ + mesh::{Indices, Mesh, Meshable}, + render_asset::RenderAssetUsages, +}; + + +impl Meshable for Ramp { + type Output = Mesh; + + fn mesh(&self) -> Self::Output { + let min = -self.half_size; + let max = self.half_size; + + let slope_normal = Vec3::new(0.0, max.z, max.y).normalize_or_zero().to_array(); + + // Suppose Y-up right hand, and camera look from +Z to -Z + let vertices = &[ + // 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, 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, min.z], [-1.0, 0.0, 0.0], [0.0, 0.0]), + ([min.x, min.y, min.z], [-1.0, 0.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]), + // Slope + ([min.x, max.y, min.z], slope_normal, [0.0, 0.0]), + ([max.x, max.y, min.z], slope_normal, [0.0, 1.0]), + ([max.x, min.y, max.z], slope_normal, [1.0, 0.0]), + ([min.x, min.y, max.z], slope_normal, [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(); + + let indices = Indices::U32(vec![ + 0, 1, 2, 2, 3, 0, // back + 4, 5, 6, // right + 7, 8, 9, // left + 10, 11, 12, 12, 13, 10, // bottom + 14, 16, 15, 16, 14, 17, // Slope + ]); + + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_indices(indices) + } +} + +impl From for Mesh { + fn from(ramp: Ramp) -> Self { + ramp.mesh() + } +} \ No newline at end of file diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index fe6135cca62aa..168e4b68a07a8 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -43,6 +43,7 @@ fn setup( meshes.add(Cylinder::default()), meshes.add(Sphere::default().mesh().ico(5).unwrap()), meshes.add(Sphere::default().mesh().uv(32, 18)), + meshes.add(Ramp::default().mesh()), ]; let num_shapes = shapes.len(); From 1639d0d4d13e3720097a27bfc88c61493265c410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 07:34:00 +0100 Subject: [PATCH 02/18] Implement `Meshable` and `Default` for `Cone` --- crates/bevy_math/src/primitives/dim3.rs | 23 ++- .../src/mesh/primitives/dim3/cone.rs | 175 ++++++++++++++++++ .../src/mesh/primitives/dim3/mod.rs | 2 + .../src/mesh/primitives/dim3/ramp.rs | 3 +- examples/3d/3d_shapes.rs | 3 +- 5 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 crates/bevy_render/src/mesh/primitives/dim3/cone.rs diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 782f5cad18a8d..9363598a99f0f 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -588,6 +588,15 @@ pub struct Cone { } impl Primitive3d for Cone {} +impl Default for Cone { + fn default() -> Self { + Self { + radius: 0.5, + height: 1.0, + } + } +} + impl Cone { /// Get the base of the cone as a [`Circle`] #[inline(always)] @@ -777,13 +786,14 @@ impl Default for Ramp { fn default() -> Self { Self { half_size: Vec3::splat(0.5), - } + } } } impl Ramp { /// Create a new Ramp from the dimensions of the base and the angle of the wedge at the apex. /// The slope is the angle (incline) of the ramp in radians + /// For sensible results, the slope of the ramp should not exceed `PI / 2.0` or 90°. pub fn new(half_width: f32, half_depth: f32, slope: f32) -> Ramp { let half_height = half_depth * slope.tan(); Self { @@ -800,7 +810,8 @@ impl Ramp { /// Get the surface area of the ramp pub fn area(&self) -> f32 { let half = self.half_size; - let half_volume = half.x * (half.y + half.z + (half.y * half.y + half.z * half.z).sqrt()) + half.y * half.z; + let half_volume = half.x * (half.y + half.z + (half.y * half.y + half.z * half.z).sqrt()) + + half.y * half.z; half_volume * 4.0 } @@ -979,9 +990,13 @@ mod tests { #[test] fn ramp_math() { let ramp = Ramp { - half_size: Vec3::splat(0.75) + half_size: Vec3::splat(0.75), }; - assert_eq!(ramp.slope(), std::f32::consts::FRAC_PI_4, "incorrect computed slope"); + assert_eq!( + ramp.slope(), + std::f32::consts::FRAC_PI_4, + "incorrect computed slope" + ); assert_eq!(ramp.area(), 9.9319805, "incorrect ramp area"); assert_eq!(ramp.volume(), 1.6875, "incorrect ramp volume"); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/cone.rs b/crates/bevy_render/src/mesh/primitives/dim3/cone.rs new file mode 100644 index 0000000000000..912a7562b3774 --- /dev/null +++ b/crates/bevy_render/src/mesh/primitives/dim3/cone.rs @@ -0,0 +1,175 @@ +use bevy_math::{primitives::Cone, Vec2}; +use wgpu::PrimitiveTopology; + +use crate::{ + mesh::{Indices, Mesh, Meshable}, + render_asset::RenderAssetUsages, +}; + +/// A builder used for creating a [`Mesh`] with a [`Cone`] shape. +#[derive(Clone, Copy, Debug)] +pub struct ConeMeshBuilder { + /// The [`Cone`] shape. + pub cone: Cone, + /// The number of vertices used for the bottom of the cone. + /// + /// The default is `32`. + pub resolution: u32, + /// The number of segments along the height of the cone. + /// Must be greater than `0` for geometry to be generated. + /// + /// The default is `1`. + pub segments: u32, +} + +impl Default for ConeMeshBuilder { + fn default() -> Self { + Self { + cone: Cone::default(), + resolution: 32, + segments: 1, + } + } +} + +impl ConeMeshBuilder { + /// Creates a new [`ConeMeshBuilder`] from the given radius, a height, + /// and a resolution used for the bottom. + #[inline] + pub fn new(radius: f32, height: f32, resolution: u32) -> Self { + Self { + cone: Cone { radius, height }, + resolution, + ..Default::default() + } + } + + /// Sets the number of vertices used for the bottom of the cone. + #[inline] + pub const fn resolution(mut self, resolution: u32) -> Self { + self.resolution = resolution; + self + } + + /// Sets the number of segments along the height of the cone. + /// 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 = self.cone.height / segments as f32; + + // rings + let normal_y = Vec2::new(self.cone.radius, self.cone.height).normalize().x; + let normal_horizontal_mul = (1.0 - normal_y * normal_y).sqrt(); + for ring in 0..num_rings { + let y = ring as f32 * step_y; + let radius_at_y = (self.cone.height - y) / self.cone.height * self.cone.radius; + + for segment in 0..=resolution { + let theta = segment as f32 * step_theta; + let (sin, cos) = theta.sin_cos(); + + positions.push([radius_at_y * cos, y, radius_at_y * sin]); + normals.push([ + cos * normal_horizontal_mul, + normal_y, + sin * normal_horizontal_mul, + ]); + 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, + ]); + } + } + + // bottom cap + + let offset = positions.len() as u32; + let (y, normal_y, winding, radius) = (0.0, -1., (0, 1), 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, 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]); + } + + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_indices(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 Cone { + type Output = ConeMeshBuilder; + + fn mesh(&self) -> Self::Output { + ConeMeshBuilder { + cone: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(cone: Cone) -> Self { + cone.mesh().build() + } +} + +impl From for Mesh { + fn from(cone: ConeMeshBuilder) -> Self { + cone.build() + } +} diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index 437b44666f2fd..b4d25d1fa5d26 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -1,4 +1,5 @@ mod capsule; +mod cone; mod cuboid; mod cylinder; mod plane; @@ -7,6 +8,7 @@ mod sphere; mod torus; pub use capsule::*; +pub use cone::*; pub use cylinder::*; pub use plane::*; pub use sphere::*; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs b/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs index 29bc140b94d46..a3fb88713022a 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs @@ -6,7 +6,6 @@ use crate::{ render_asset::RenderAssetUsages, }; - impl Meshable for Ramp { type Output = Mesh; @@ -70,4 +69,4 @@ impl From for Mesh { fn from(ramp: Ramp) -> Self { ramp.mesh() } -} \ No newline at end of file +} diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 168e4b68a07a8..a78134deab066 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -43,7 +43,8 @@ fn setup( meshes.add(Cylinder::default()), meshes.add(Sphere::default().mesh().ico(5).unwrap()), meshes.add(Sphere::default().mesh().uv(32, 18)), - meshes.add(Ramp::default().mesh()), + meshes.add(Ramp::default()), + meshes.add(Cone::default()), ]; let num_shapes = shapes.len(); From 8a732f946f8300da86656876fb49c9bef7a6c567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:11:24 +0100 Subject: [PATCH 03/18] Implement `Bounded3d` for `Ramp` and added Ramp primitive gizmo --- crates/bevy_gizmos/src/primitives/dim3.rs | 50 ++++++++++++++++- .../src/bounding/bounded3d/primitive_impls.rs | 55 ++++++++++++++++++- 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index 3fd3bfdafba90..36ce37dd05f65 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -5,7 +5,7 @@ use std::f32::consts::TAU; use bevy_math::primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Primitive3d, Segment3d, Sphere, Torus, + Plane3d, Polyline3d, Primitive3d, Ramp, Segment3d, Sphere, Torus, }; use bevy_math::{Quat, Vec3}; use bevy_render::color::Color; @@ -917,3 +917,51 @@ impl Drop for Torus3dBuilder<'_, '_, '_, T> { }); } } + +// ramp + +impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { + type Output<'a> = () where Self: 'a; + + fn primitive_3d( + &mut self, + primitive: Ramp, + position: Vec3, + rotation: Quat, + color: Color, + ) -> Self::Output<'_> { + if !self.enabled { + return; + } + + let [half_extend_x, half_extend_y, half_extend_z] = primitive.half_size.to_array(); + + // transform the points from the reference unit cube-like ramp to the ramp coords + let [a, b, c, d, e, f] = [ + [-1.0, -1.0, -1.0], + [-1.0, -1.0, 1.0], + [1.0, -1.0, -1.0], + [1.0, -1.0, 1.0], + [1.0, 1.0, -1.0], + [-1.0, 1.0, -1.0], + ] + .map(|[sx, sy, sz]| Vec3::new(sx * half_extend_x, sy * half_extend_y, sz * half_extend_z)) + .map(rotate_then_translate_3d(rotation, position)); + + let lines = vec![ + (a, b), + (b, d), + (d, c), + (c, a), + (b, f), + (d, e), + (e, f), + (a, f), + (c, e), + ]; + + lines.into_iter().for_each(|(start, end)| { + self.line(start, end, color); + }); + } +} diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 6730254793877..cfa7be06e587e 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -3,10 +3,10 @@ use glam::{Mat3, Quat, Vec2, Vec3}; use crate::{ - bounding::{Bounded2d, BoundingCircle}, + bounding::{Bounded2d, BoundingCircle, BoundingVolume}, primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Segment3d, Sphere, Torus, Triangle2d, + Plane3d, Polyline3d, Ramp, Segment3d, Sphere, Torus, Triangle2d, }, }; @@ -304,6 +304,39 @@ impl Bounded3d for Torus { } } +impl Bounded3d for Ramp { + fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { + let line = Segment3d { + direction: Direction3d::new_unchecked(Vec3::X), + half_length: self.half_size.x, + }; + let front_aabb = line.aabb_3d( + translation + rotation * (Vec3::Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), + rotation, + ); + let top_aabb = line.aabb_3d( + translation + + rotation * (Vec3::NEG_Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), + rotation, + ); + let back_aabb = line.aabb_3d( + translation + rotation * (Vec3::NEG_Z * self.half_size.z + Vec3::Y * self.half_size.y), + rotation, + ); + + front_aabb.merge(&top_aabb).merge(&back_aabb) + } + + fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere { + BoundingSphere { + center: translation, + sphere: Sphere { + radius: self.half_size.length(), + }, + } + } +} + #[cfg(test)] mod tests { use glam::{Quat, Vec3}; @@ -312,7 +345,7 @@ mod tests { bounding::Bounded3d, primitives::{ Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, Plane3d, - Polyline3d, Segment3d, Sphere, Torus, + Polyline3d, Ramp, Segment3d, Sphere, Torus, }, }; @@ -546,4 +579,20 @@ mod tests { assert_eq!(bounding_sphere.center, translation); assert_eq!(bounding_sphere.radius(), 1.5); } + + #[test] + fn ramp() { + let ramp = Ramp { + half_size: Vec3::new(1.0, 0.8, 2.5), + }; + let translation = Vec3::new(3.5, -1.25, 2.0); + + let aabb = ramp.aabb_3d(translation, Quat::IDENTITY); + assert_eq!(aabb.min, Vec3::new(2.5, -2.05, -0.5)); + assert_eq!(aabb.max, Vec3::new(4.5, -0.45, 4.5)); + + let bounding_sphere = ramp.bounding_sphere(translation, Quat::IDENTITY); + assert_eq!(bounding_sphere.center, translation); + assert_eq!(bounding_sphere.radius(), 2.808914381); + } } From 3b9047f311f70c5199038968e735ad8c7df8b29a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:19:41 +0100 Subject: [PATCH 04/18] Implement reflect for Ramp --- crates/bevy_math/src/primitives/dim3.rs | 7 ++++++- crates/bevy_reflect/src/impls/math/primitives3d.rs | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 9363598a99f0f..877e8816ac55a 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -776,6 +776,7 @@ impl Torus { /// A ramp primitive shape. #[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct Ramp { /// Half of the width, height and depth of the ramp pub half_size: Vec3, @@ -793,7 +794,8 @@ impl Default for Ramp { impl Ramp { /// Create a new Ramp from the dimensions of the base and the angle of the wedge at the apex. /// The slope is the angle (incline) of the ramp in radians - /// For sensible results, the slope of the ramp should not exceed `PI / 2.0` or 90°. + /// For sensible results, the slope of the ramp should be in (0.0, PI / 2.0) + #[inline(always)] pub fn new(half_width: f32, half_depth: f32, slope: f32) -> Ramp { let half_height = half_depth * slope.tan(); Self { @@ -803,11 +805,13 @@ impl Ramp { /// Get the slope of the wedge /// The slope is the angle (incline) of the ramp in radians + #[inline(always)] pub fn slope(&self) -> f32 { self.half_size.y.atan2(self.half_size.z) } /// Get the surface area of the ramp + #[inline(always)] pub fn area(&self) -> f32 { let half = self.half_size; let half_volume = half.x * (half.y + half.z + (half.y * half.y + half.z * half.z).sqrt()) @@ -816,6 +820,7 @@ impl Ramp { } /// Get the volume of the ramp + #[inline(always)] pub fn volume(&self) -> f32 { self.half_size.x * self.half_size.y * self.half_size.z * 4.0 } diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index 3c986fc1a63fc..12d87e7a9e7cd 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -104,3 +104,11 @@ impl_reflect!( major_radius: f32, } ); + +impl_reflect!( + #[reflect(Debug, PartialEq, Serialize, Deserialize)] + #[type_path = "bevy_math::primitives"] + struct Ramp { + half_size: Vec3, + } +); \ No newline at end of file From 0be1b66eee2816f15efe26643ebb4fc4aba82834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:25:48 +0100 Subject: [PATCH 05/18] Update 3d_gizmos example to include ramp --- examples/3d/3d_gizmos.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/3d/3d_gizmos.rs b/examples/3d/3d_gizmos.rs index a3805c12d9296..089f140c92718 100644 --- a/examples/3d/3d_gizmos.rs +++ b/examples/3d/3d_gizmos.rs @@ -45,10 +45,11 @@ enum PrimitiveState { Cone, ConicalFrustum, Torus, + Ramp, } impl PrimitiveState { - const ALL: [Self; 11] = [ + const ALL: [Self; 12] = [ Self::Sphere, Self::Plane, Self::Line, @@ -59,6 +60,7 @@ impl PrimitiveState { Self::Cone, Self::ConicalFrustum, Self::Torus, + Self::Ramp, Self::Nothing, ]; fn next(self) -> Self { @@ -329,6 +331,17 @@ fn draw_primitives( .major_segments(segments) .minor_segments((segments / 4).max(1)); } + PrimitiveState::Ramp => { + gizmos + .primitive_3d( + Ramp { + half_size: Vec3::new(1.0, 0.5, 2.0), + }, + center, + rotation, + Color::default(), + ); + } } } From 13d9e67083bec2bc2f8b74cb8be722f323c74fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:56:09 +0100 Subject: [PATCH 06/18] Ran cargo fmt --- .../bevy_reflect/src/impls/math/primitives3d.rs | 2 +- examples/3d/3d_gizmos.rs | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index 12d87e7a9e7cd..d645d26f63a27 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -111,4 +111,4 @@ impl_reflect!( struct Ramp { half_size: Vec3, } -); \ No newline at end of file +); diff --git a/examples/3d/3d_gizmos.rs b/examples/3d/3d_gizmos.rs index 089f140c92718..67536ea32664f 100644 --- a/examples/3d/3d_gizmos.rs +++ b/examples/3d/3d_gizmos.rs @@ -332,15 +332,14 @@ fn draw_primitives( .minor_segments((segments / 4).max(1)); } PrimitiveState::Ramp => { - gizmos - .primitive_3d( - Ramp { - half_size: Vec3::new(1.0, 0.5, 2.0), - }, - center, - rotation, - Color::default(), - ); + gizmos.primitive_3d( + Ramp { + half_size: Vec3::new(1.0, 0.5, 2.0), + }, + center, + rotation, + Color::default(), + ); } } } From 85fb64c197874663edf261a6ab956ca3a0153721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:12:45 +0100 Subject: [PATCH 07/18] Truncated floats in tests --- crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs | 2 +- crates/bevy_math/src/primitives/dim3.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index cfa7be06e587e..a899648d28033 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -593,6 +593,6 @@ mod tests { let bounding_sphere = ramp.bounding_sphere(translation, Quat::IDENTITY); assert_eq!(bounding_sphere.center, translation); - assert_eq!(bounding_sphere.radius(), 2.808914381); + assert_eq!(bounding_sphere.radius(), 2.8089144); } } diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 877e8816ac55a..1ecadd969a407 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1002,7 +1002,7 @@ mod tests { std::f32::consts::FRAC_PI_4, "incorrect computed slope" ); - assert_eq!(ramp.area(), 9.9319805, "incorrect ramp area"); + assert_eq!(ramp.area(), 9.93198, "incorrect ramp area"); assert_eq!(ramp.volume(), 1.6875, "incorrect ramp volume"); assert_relative_eq!(Ramp::new(0.5, 0.5, FRAC_PI_3).half_size.y, 0.8660254); From 505cf0524b439c79005c9091d98f5cd9a4584d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 9 Feb 2024 20:05:16 +0100 Subject: [PATCH 08/18] Removed `Meshable` for `Cone`` --- crates/bevy_math/src/primitives/dim3.rs | 9 - .../src/mesh/primitives/dim3/cone.rs | 175 ------------------ .../src/mesh/primitives/dim3/mod.rs | 2 - examples/3d/3d_shapes.rs | 1 - 4 files changed, 187 deletions(-) delete mode 100644 crates/bevy_render/src/mesh/primitives/dim3/cone.rs diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 1ecadd969a407..e44aef22066cf 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -588,15 +588,6 @@ pub struct Cone { } impl Primitive3d for Cone {} -impl Default for Cone { - fn default() -> Self { - Self { - radius: 0.5, - height: 1.0, - } - } -} - impl Cone { /// Get the base of the cone as a [`Circle`] #[inline(always)] diff --git a/crates/bevy_render/src/mesh/primitives/dim3/cone.rs b/crates/bevy_render/src/mesh/primitives/dim3/cone.rs deleted file mode 100644 index 912a7562b3774..0000000000000 --- a/crates/bevy_render/src/mesh/primitives/dim3/cone.rs +++ /dev/null @@ -1,175 +0,0 @@ -use bevy_math::{primitives::Cone, Vec2}; -use wgpu::PrimitiveTopology; - -use crate::{ - mesh::{Indices, Mesh, Meshable}, - render_asset::RenderAssetUsages, -}; - -/// A builder used for creating a [`Mesh`] with a [`Cone`] shape. -#[derive(Clone, Copy, Debug)] -pub struct ConeMeshBuilder { - /// The [`Cone`] shape. - pub cone: Cone, - /// The number of vertices used for the bottom of the cone. - /// - /// The default is `32`. - pub resolution: u32, - /// The number of segments along the height of the cone. - /// Must be greater than `0` for geometry to be generated. - /// - /// The default is `1`. - pub segments: u32, -} - -impl Default for ConeMeshBuilder { - fn default() -> Self { - Self { - cone: Cone::default(), - resolution: 32, - segments: 1, - } - } -} - -impl ConeMeshBuilder { - /// Creates a new [`ConeMeshBuilder`] from the given radius, a height, - /// and a resolution used for the bottom. - #[inline] - pub fn new(radius: f32, height: f32, resolution: u32) -> Self { - Self { - cone: Cone { radius, height }, - resolution, - ..Default::default() - } - } - - /// Sets the number of vertices used for the bottom of the cone. - #[inline] - pub const fn resolution(mut self, resolution: u32) -> Self { - self.resolution = resolution; - self - } - - /// Sets the number of segments along the height of the cone. - /// 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 = self.cone.height / segments as f32; - - // rings - let normal_y = Vec2::new(self.cone.radius, self.cone.height).normalize().x; - let normal_horizontal_mul = (1.0 - normal_y * normal_y).sqrt(); - for ring in 0..num_rings { - let y = ring as f32 * step_y; - let radius_at_y = (self.cone.height - y) / self.cone.height * self.cone.radius; - - for segment in 0..=resolution { - let theta = segment as f32 * step_theta; - let (sin, cos) = theta.sin_cos(); - - positions.push([radius_at_y * cos, y, radius_at_y * sin]); - normals.push([ - cos * normal_horizontal_mul, - normal_y, - sin * normal_horizontal_mul, - ]); - 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, - ]); - } - } - - // bottom cap - - let offset = positions.len() as u32; - let (y, normal_y, winding, radius) = (0.0, -1., (0, 1), 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, 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]); - } - - Mesh::new( - PrimitiveTopology::TriangleList, - RenderAssetUsages::default(), - ) - .with_inserted_indices(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 Cone { - type Output = ConeMeshBuilder; - - fn mesh(&self) -> Self::Output { - ConeMeshBuilder { - cone: *self, - ..Default::default() - } - } -} - -impl From for Mesh { - fn from(cone: Cone) -> Self { - cone.mesh().build() - } -} - -impl From for Mesh { - fn from(cone: ConeMeshBuilder) -> Self { - cone.build() - } -} diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index b4d25d1fa5d26..437b44666f2fd 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -1,5 +1,4 @@ mod capsule; -mod cone; mod cuboid; mod cylinder; mod plane; @@ -8,7 +7,6 @@ mod sphere; mod torus; pub use capsule::*; -pub use cone::*; pub use cylinder::*; pub use plane::*; pub use sphere::*; diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index a78134deab066..b87ec3768f540 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -44,7 +44,6 @@ fn setup( meshes.add(Sphere::default().mesh().ico(5).unwrap()), meshes.add(Sphere::default().mesh().uv(32, 18)), meshes.add(Ramp::default()), - meshes.add(Cone::default()), ]; let num_shapes = shapes.len(); From c8a642ede1d7dffd101f9f9918ba4c322aa8f57e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:42:23 +0100 Subject: [PATCH 09/18] Generalize `Ramp` to `Prism` --- crates/bevy_gizmos/src/primitives/dim3.rs | 14 ++--- .../src/bounding/bounded3d/primitive_impls.rs | 48 ++++++++------- crates/bevy_math/src/primitives/dim3.rs | 61 ++++++------------- .../src/impls/math/primitives3d.rs | 3 +- .../src/mesh/primitives/dim3/mod.rs | 2 +- .../primitives/dim3/{ramp.rs => prism.rs} | 41 +++++++------ examples/3d/3d_gizmos.rs | 3 +- examples/3d/3d_shapes.rs | 5 +- 8 files changed, 87 insertions(+), 90 deletions(-) rename crates/bevy_render/src/mesh/primitives/dim3/{ramp.rs => prism.rs} (60%) diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index 36ce37dd05f65..58eb4520bdf61 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -5,7 +5,7 @@ use std::f32::consts::TAU; use bevy_math::primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Primitive3d, Ramp, Segment3d, Sphere, Torus, + Plane3d, Polyline3d, Primitive3d, Prism, Segment3d, Sphere, Torus, }; use bevy_math::{Quat, Vec3}; use bevy_render::color::Color; @@ -918,14 +918,14 @@ impl Drop for Torus3dBuilder<'_, '_, '_, T> { } } -// ramp +// prism -impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { +impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { type Output<'a> = () where Self: 'a; fn primitive_3d( &mut self, - primitive: Ramp, + primitive: Prism, position: Vec3, rotation: Quat, color: Color, @@ -936,14 +936,14 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { let [half_extend_x, half_extend_y, half_extend_z] = primitive.half_size.to_array(); - // transform the points from the reference unit cube-like ramp to the ramp coords + // transform the points from the reference unit cube-like prism to the actual prism coords let [a, b, c, d, e, f] = [ [-1.0, -1.0, -1.0], [-1.0, -1.0, 1.0], [1.0, -1.0, -1.0], [1.0, -1.0, 1.0], - [1.0, 1.0, -1.0], - [-1.0, 1.0, -1.0], + [1.0, 1.0, primitive.apex_displacement], + [-1.0, 1.0, primitive.apex_displacement], ] .map(|[sx, sy, sz]| Vec3::new(sx * half_extend_x, sy * half_extend_y, sz * half_extend_z)) .map(rotate_then_translate_3d(rotation, position)); diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index a899648d28033..3c055b5561e94 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -6,7 +6,7 @@ use crate::{ bounding::{Bounded2d, BoundingCircle, BoundingVolume}, primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Ramp, Segment3d, Sphere, Torus, Triangle2d, + Plane3d, Polyline3d, Prism, Segment3d, Sphere, Torus, Triangle2d, }, }; @@ -304,36 +304,41 @@ impl Bounded3d for Torus { } } -impl Bounded3d for Ramp { +impl Bounded3d for Prism { fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { let line = Segment3d { direction: Direction3d::new_unchecked(Vec3::X), half_length: self.half_size.x, }; - let front_aabb = line.aabb_3d( - translation + rotation * (Vec3::Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), + let apex_aabb = line.aabb_3d( + translation + + Vec3::new( + 0.0, + self.half_size.y, + self.half_size.z * self.apex_displacement, + ), rotation, ); - let top_aabb = line.aabb_3d( - translation - + rotation * (Vec3::NEG_Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), + let front_aabb = line.aabb_3d( + translation + (Vec3::NEG_Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), rotation, ); let back_aabb = line.aabb_3d( - translation + rotation * (Vec3::NEG_Z * self.half_size.z + Vec3::Y * self.half_size.y), + translation + rotation * (Vec3::Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), rotation, ); - front_aabb.merge(&top_aabb).merge(&back_aabb) + apex_aabb.merge(&front_aabb).merge(&back_aabb) } - fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere { - BoundingSphere { - center: translation, - sphere: Sphere { - radius: self.half_size.length(), - }, - } + fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere { + let furthest = self.half_size * Vec3::new(1.0, 1.0, self.apex_displacement.abs()); + let local_center = Vec3::new(0.0, 0.0, self.half_size.z * self.apex_displacement * 0.5); + + let radius = (furthest - local_center).length(); + let center = translation + rotation * local_center; + + BoundingSphere::new(center, radius) } } @@ -345,7 +350,7 @@ mod tests { bounding::Bounded3d, primitives::{ Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, Plane3d, - Polyline3d, Ramp, Segment3d, Sphere, Torus, + Polyline3d, Prism, Segment3d, Sphere, Torus, }, }; @@ -581,17 +586,18 @@ mod tests { } #[test] - fn ramp() { - let ramp = Ramp { + fn prism() { + let prism = Prism { half_size: Vec3::new(1.0, 0.8, 2.5), + apex_displacement: 1.0, }; let translation = Vec3::new(3.5, -1.25, 2.0); - let aabb = ramp.aabb_3d(translation, Quat::IDENTITY); + let aabb = prism.aabb_3d(translation, Quat::IDENTITY); assert_eq!(aabb.min, Vec3::new(2.5, -2.05, -0.5)); assert_eq!(aabb.max, Vec3::new(4.5, -0.45, 4.5)); - let bounding_sphere = ramp.bounding_sphere(translation, Quat::IDENTITY); + let bounding_sphere = prism.bounding_sphere(translation, Quat::IDENTITY); assert_eq!(bounding_sphere.center, translation); assert_eq!(bounding_sphere.radius(), 2.8089144); } diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index e44aef22066cf..e52618a72ba74 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -765,52 +765,37 @@ impl Torus { } } -/// A ramp primitive shape. +/// A triangular prism primitive, often representing a ramp. #[derive(Copy, Clone, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -pub struct Ramp { - /// Half of the width, height and depth of the ramp +pub struct Prism { + /// Half of the width, height and depth of the prism pub half_size: Vec3, + /// Displacement of the apex edge along the Z-axis. -1.0 positions the apex straight above the bottom-front edge. + pub apex_displacement: f32, } -impl Primitive3d for Ramp {} +impl Primitive3d for Prism {} -impl Default for Ramp { +impl Default for Prism { fn default() -> Self { Self { half_size: Vec3::splat(0.5), + apex_displacement: 0.0, } } } -impl Ramp { - /// Create a new Ramp from the dimensions of the base and the angle of the wedge at the apex. - /// The slope is the angle (incline) of the ramp in radians - /// For sensible results, the slope of the ramp should be in (0.0, PI / 2.0) - #[inline(always)] - pub fn new(half_width: f32, half_depth: f32, slope: f32) -> Ramp { - let half_height = half_depth * slope.tan(); - Self { - half_size: Vec3::new(half_width, half_height, half_depth), - } - } - - /// Get the slope of the wedge - /// The slope is the angle (incline) of the ramp in radians - #[inline(always)] - pub fn slope(&self) -> f32 { - self.half_size.y.atan2(self.half_size.z) - } - - /// Get the surface area of the ramp +impl Prism { + /// Get the surface area of the prism #[inline(always)] pub fn area(&self) -> f32 { - let half = self.half_size; - let half_volume = half.x * (half.y + half.z + (half.y * half.y + half.z * half.z).sqrt()) - + half.y * half.z; - half_volume * 4.0 + let [x, y, z] = self.half_size.to_array(); + let edge1 = (z * (self.apex_displacement + 1.0)).hypot(2.0 * y); + let edge2 = (z * (self.apex_displacement - 1.0)).hypot(2.0 * y); + 4.0 * z * y + 2.0 * x * (edge1 + edge2 + 2.0 * z) } - /// Get the volume of the ramp + /// Get the volume of the prism #[inline(always)] pub fn volume(&self) -> f32 { self.half_size.x * self.half_size.y * self.half_size.z * 4.0 @@ -984,18 +969,12 @@ mod tests { } #[test] - fn ramp_math() { - let ramp = Ramp { + fn prism_math() { + let prism = Prism { half_size: Vec3::splat(0.75), + apex_displacement: 1.0, }; - assert_eq!( - ramp.slope(), - std::f32::consts::FRAC_PI_4, - "incorrect computed slope" - ); - assert_eq!(ramp.area(), 9.93198, "incorrect ramp area"); - assert_eq!(ramp.volume(), 1.6875, "incorrect ramp volume"); - - assert_relative_eq!(Ramp::new(0.5, 0.5, FRAC_PI_3).half_size.y, 0.8660254); + assert_eq!(prism.volume(), 1.6875, "incorrect prism volume"); + assert_eq!(prism.area(), 9.93198, "incorrect prism area"); } } diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index d645d26f63a27..ecb7f4bcf030a 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -108,7 +108,8 @@ impl_reflect!( impl_reflect!( #[reflect(Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] - struct Ramp { + struct Prism { half_size: Vec3, + apex_displacement: f32, } ); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index 437b44666f2fd..7d15c43d02510 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -2,7 +2,7 @@ mod capsule; mod cuboid; mod cylinder; mod plane; -mod ramp; +mod prism; mod sphere; mod torus; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs b/crates/bevy_render/src/mesh/primitives/dim3/prism.rs similarity index 60% rename from crates/bevy_render/src/mesh/primitives/dim3/ramp.rs rename to crates/bevy_render/src/mesh/primitives/dim3/prism.rs index a3fb88713022a..f7629aa894ac2 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/prism.rs @@ -1,4 +1,4 @@ -use bevy_math::{primitives::Ramp, Vec3}; +use bevy_math::{primitives::Prism, Vec3}; use wgpu::PrimitiveTopology; use crate::{ @@ -6,40 +6,47 @@ use crate::{ render_asset::RenderAssetUsages, }; -impl Meshable for Ramp { +impl Meshable for Prism { type Output = Mesh; fn mesh(&self) -> Self::Output { let min = -self.half_size; let max = self.half_size; - let slope_normal = Vec3::new(0.0, max.z, max.y).normalize_or_zero().to_array(); + let front_normal = Vec3::new(0.0, max.z * (self.apex_displacement + 1.0), max.y * 2.0) + .normalize_or_zero() + .to_array(); + let back_normal = Vec3::new(0.0, max.z * (self.apex_displacement - 1.0), max.y * 2.0) + .normalize_or_zero() + .to_array(); + + let apex_z = self.apex_displacement * max.z; // Suppose Y-up right hand, and camera look from +Z to -Z let vertices = &[ // 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]), + ([min.x, max.y, apex_z], back_normal, [1.0, 0.0]), + ([max.x, max.y, apex_z], back_normal, [0.0, 0.0]), + ([max.x, min.y, min.z], back_normal, [0.0, 1.0]), + ([min.x, min.y, min.z], back_normal, [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, 1.0]), + ([max.x, max.y, apex_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, min.z], [-1.0, 0.0, 0.0], [0.0, 0.0]), + ([min.x, max.y, apex_z], [-1.0, 0.0, 0.0], [0.0, 0.0]), ([min.x, min.y, min.z], [-1.0, 0.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]), - // Slope - ([min.x, max.y, min.z], slope_normal, [0.0, 0.0]), - ([max.x, max.y, min.z], slope_normal, [0.0, 1.0]), - ([max.x, min.y, max.z], slope_normal, [1.0, 0.0]), - ([min.x, min.y, max.z], slope_normal, [1.0, 1.0]), + // Front + ([min.x, max.y, apex_z], front_normal, [0.0, 0.0]), + ([max.x, max.y, apex_z], front_normal, [0.0, 1.0]), + ([max.x, min.y, max.z], front_normal, [1.0, 0.0]), + ([min.x, min.y, max.z], front_normal, [1.0, 1.0]), ]; let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); @@ -65,8 +72,8 @@ impl Meshable for Ramp { } } -impl From for Mesh { - fn from(ramp: Ramp) -> Self { - ramp.mesh() +impl From for Mesh { + fn from(prism: Prism) -> Self { + prism.mesh() } } diff --git a/examples/3d/3d_gizmos.rs b/examples/3d/3d_gizmos.rs index 67536ea32664f..558bb31146f70 100644 --- a/examples/3d/3d_gizmos.rs +++ b/examples/3d/3d_gizmos.rs @@ -333,8 +333,9 @@ fn draw_primitives( } PrimitiveState::Ramp => { gizmos.primitive_3d( - Ramp { + Prism { half_size: Vec3::new(1.0, 0.5, 2.0), + apex_displacement: 0.5, }, center, rotation, diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index b87ec3768f540..892f8fd07e034 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -43,7 +43,10 @@ fn setup( meshes.add(Cylinder::default()), meshes.add(Sphere::default().mesh().ico(5).unwrap()), meshes.add(Sphere::default().mesh().uv(32, 18)), - meshes.add(Ramp::default()), + meshes.add(Prism { + apex_displacement: 4.0, + half_size: Vec3::ONE * 0.5, + }), ]; let num_shapes = shapes.len(); From 10cf433bf1eae090da8b3ffb9cd2b0accf9bc31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lynn=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:57:13 +0100 Subject: [PATCH 10/18] Fix prism bounding test --- crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 3c055b5561e94..52161a15c574a 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -598,7 +598,7 @@ mod tests { assert_eq!(aabb.max, Vec3::new(4.5, -0.45, 4.5)); let bounding_sphere = prism.bounding_sphere(translation, Quat::IDENTITY); - assert_eq!(bounding_sphere.center, translation); - assert_eq!(bounding_sphere.radius(), 2.8089144); + assert_eq!(bounding_sphere.center, Vec3::new(3.5, -1.25, 3.25)); + assert_eq!(bounding_sphere.radius(), 1.789553); } } From 748d65d9b84d0ae793bca9385ae5d75cdb7ae0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Thu, 11 Apr 2024 20:23:47 +0200 Subject: [PATCH 11/18] Fix merge errors --- crates/bevy_gizmos/src/primitives/dim3.rs | 4 ++-- .../src/bounding/bounded3d/primitive_impls.rs | 10 +++++----- crates/bevy_math/src/primitives/dim3.rs | 4 ++++ crates/bevy_reflect/src/impls/math/primitives3d.rs | 3 ++- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index f2b947094beb1..17b7db2859ec2 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -5,8 +5,8 @@ use std::f32::consts::TAU; use bevy_color::Color; use bevy_math::primitives::{ - BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Primitive3d, Prism, Segment3d, Sphere, Torus, + BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, + Polyline3d, Primitive3d, Prism, Segment3d, Sphere, Torus, }; use bevy_math::{Dir3, Quat, Vec3}; diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index f1462afd616fd..794278ebd087c 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -3,8 +3,8 @@ use crate::{ bounding::{Bounded2d, BoundingCircle, BoundingVolume}, primitives::{ - BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, - Plane3d, Polyline3d, Prism, Segment3d, Sphere, Torus, Triangle2d, + BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, + Polyline3d, Prism, Segment3d, Sphere, Torus, Triangle2d, }, Dir3, Mat3, Quat, Vec2, Vec3, }; @@ -306,7 +306,7 @@ impl Bounded3d for Torus { impl Bounded3d for Prism { fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { let line = Segment3d { - direction: Direction3d::new_unchecked(Vec3::X), + direction: Dir3::new_unchecked(Vec3::X), half_length: self.half_size.x, }; let apex_aabb = line.aabb_3d( @@ -348,8 +348,8 @@ mod tests { use crate::{ bounding::Bounded3d, primitives::{ - Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Direction3d, Line3d, Plane3d, - Polyline3d, Prism, Segment3d, Sphere, Torus, + Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, Prism, + Segment3d, Sphere, Torus, }, Dir3, }; diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index e0f027fec33e1..fb4f1befb97b2 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -648,6 +648,10 @@ impl Default for Prism { Self { half_size: Vec3::splat(0.5), apex_displacement: 0.0, + } + } +} + /// A 3D triangle primitive. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index f2b57b19a375c..99f9a6b375cdc 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -118,6 +118,7 @@ impl_reflect!( #[reflect(Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] struct Prism { - vertices: [Vec3; 4], + half_size: Vec3, + apex_displacement: f32, } ); From d8a10785245fc8c25ee9db851daa8c3dce78c29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Thu, 11 Apr 2024 20:41:03 +0200 Subject: [PATCH 12/18] Revert to ramp --- crates/bevy_gizmos/src/primitives/dim3.rs | 14 +-- .../src/bounding/bounded3d/primitive_impls.rs | 71 +++++---------- crates/bevy_math/src/primitives/dim3.rs | 89 +++++++++++++++++-- .../src/impls/math/primitives3d.rs | 3 +- .../src/mesh/primitives/dim3/mod.rs | 2 +- .../primitives/dim3/{prism.rs => ramp.rs} | 45 ++++------ 6 files changed, 132 insertions(+), 92 deletions(-) rename crates/bevy_render/src/mesh/primitives/dim3/{prism.rs => ramp.rs} (58%) diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index 17b7db2859ec2..dee6a6fd0db84 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -6,7 +6,7 @@ use std::f32::consts::TAU; use bevy_color::Color; use bevy_math::primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, - Polyline3d, Primitive3d, Prism, Segment3d, Sphere, Torus, + Polyline3d, Primitive3d, Ramp, Segment3d, Sphere, Torus, }; use bevy_math::{Dir3, Quat, Vec3}; @@ -942,14 +942,14 @@ impl Drop for Torus3dBuilder<'_, '_, '_, T> { } } -// prism +// ramp -impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { +impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { type Output<'a> = () where Self: 'a; fn primitive_3d( &mut self, - primitive: Prism, + primitive: Ramp, position: Vec3, rotation: Quat, color: Color, @@ -960,14 +960,14 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> let [half_extend_x, half_extend_y, half_extend_z] = primitive.half_size.to_array(); - // transform the points from the reference unit cube-like prism to the actual prism coords + // transform the points from the reference unit cube-like ramp to the actual ramp coords let [a, b, c, d, e, f] = [ [-1.0, -1.0, -1.0], [-1.0, -1.0, 1.0], [1.0, -1.0, -1.0], [1.0, -1.0, 1.0], - [1.0, 1.0, primitive.apex_displacement], - [-1.0, 1.0, primitive.apex_displacement], + [1.0, 1.0, 1.0], + [-1.0, 1.0, 1.0], ] .map(|[sx, sy, sz]| Vec3::new(sx * half_extend_x, sy * half_extend_y, sz * half_extend_z)) .map(rotate_then_translate_3d(rotation, position)); diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 794278ebd087c..5a055fc9ed336 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -1,10 +1,10 @@ //! Contains [`Bounded3d`] implementations for [geometric primitives](crate::primitives). use crate::{ - bounding::{Bounded2d, BoundingCircle, BoundingVolume}, + bounding::{Bounded2d, BoundingCircle}, primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, - Polyline3d, Prism, Segment3d, Sphere, Torus, Triangle2d, + Polyline3d, Ramp, Segment3d, Sphere, Torus, Triangle2d, }, Dir3, Mat3, Quat, Vec2, Vec3, }; @@ -303,41 +303,21 @@ impl Bounded3d for Torus { } } -impl Bounded3d for Prism { +impl Bounded3d for Ramp { fn aabb_3d(&self, translation: Vec3, rotation: Quat) -> Aabb3d { - let line = Segment3d { - direction: Dir3::new_unchecked(Vec3::X), - half_length: self.half_size.x, - }; - let apex_aabb = line.aabb_3d( - translation - + Vec3::new( - 0.0, - self.half_size.y, - self.half_size.z * self.apex_displacement, - ), - rotation, - ); - let front_aabb = line.aabb_3d( - translation + (Vec3::NEG_Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), - rotation, - ); - let back_aabb = line.aabb_3d( - translation + rotation * (Vec3::Z * self.half_size.z + Vec3::NEG_Y * self.half_size.y), - rotation, - ); - - apex_aabb.merge(&front_aabb).merge(&back_aabb) + let points = [ + self.half_size, + self.half_size * Vec3::new(1.0, -1.0, 1.0), + self.half_size * Vec3::new(1.0, -1.0, -1.0), + self.half_size * Vec3::new(-1.0, 1.0, 1.0), + self.half_size * Vec3::new(-1.0, -1.0, 1.0), + self.half_size * Vec3::NEG_ONE, + ]; + Aabb3d::from_point_cloud(translation, rotation, &points) } - fn bounding_sphere(&self, translation: Vec3, rotation: Quat) -> BoundingSphere { - let furthest = self.half_size * Vec3::new(1.0, 1.0, self.apex_displacement.abs()); - let local_center = Vec3::new(0.0, 0.0, self.half_size.z * self.apex_displacement * 0.5); - - let radius = (furthest - local_center).length(); - let center = translation + rotation * local_center; - - BoundingSphere::new(center, radius) + fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere { + BoundingSphere::new(translation, self.half_size.length()) } } @@ -348,7 +328,7 @@ mod tests { use crate::{ bounding::Bounded3d, primitives::{ - Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, Prism, + Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, Ramp, Segment3d, Sphere, Torus, }, Dir3, @@ -575,19 +555,16 @@ mod tests { } #[test] - fn prism() { - let prism = Prism { - half_size: Vec3::new(1.0, 0.8, 2.5), - apex_displacement: 1.0, - }; - let translation = Vec3::new(3.5, -1.25, 2.0); + fn ramp() { + let ramp = Ramp::new(1.0, 3.0, 4.0); + let translation = Vec3::new(-3.0, 1.75, 0.0); - let aabb = prism.aabb_3d(translation, Quat::IDENTITY); - assert_eq!(aabb.min, Vec3::new(2.5, -2.05, -0.5)); - assert_eq!(aabb.max, Vec3::new(4.5, -0.45, 4.5)); + let aabb = ramp.aabb_3d(translation, Quat::IDENTITY); + assert_eq!(aabb.min, Vec3::new(-3.5, 0.25, -2.0)); + assert_eq!(aabb.max, Vec3::new(-2.5, 3.25, 2.0)); - let bounding_sphere = prism.bounding_sphere(translation, Quat::IDENTITY); - assert_eq!(bounding_sphere.center, Vec3::new(3.5, -1.25, 3.25)); - assert_eq!(bounding_sphere.radius(), 1.789553); + let bounding_sphere = ramp.bounding_sphere(translation, Quat::IDENTITY); + assert_eq!(bounding_sphere.center, translation); + assert_eq!(bounding_sphere.radius(), 2.5495098); } } diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index fb4f1befb97b2..3b34faf8d3555 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -632,26 +632,75 @@ impl Torus { } } -/// A triangular prism primitive, often representing a ramp. -#[derive(Copy, Clone, Debug, PartialEq)] +/// A Ramp primitive. +/// The ramp will slant down along the Y axis towards the negative-Z axis. +#[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -pub struct Prism { - /// Half of the width, height and depth of the prism +pub struct Ramp { + /// Half of the width, height and depth of the ramp pub half_size: Vec3, - /// Displacement of the apex edge along the Z-axis. -1.0 positions the apex straight above the bottom-front edge. - pub apex_displacement: f32, } -impl Primitive3d for Prism {} +impl Primitive3d for Ramp {} -impl Default for Prism { +impl Default for Ramp { + /// Returns the default [`Ramp`] with a width, height, and depth of `1.0`. fn default() -> Self { Self { half_size: Vec3::splat(0.5), - apex_displacement: 0.0, } } } +impl Ramp { + /// Create a new `Ramp` from a full x, y, and z length + #[inline(always)] + pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Self { + Self::from_size(Vec3::new(x_length, y_length, z_length)) + } + + /// Create a new `Ramp` from a given full size + #[inline(always)] + pub fn from_size(size: Vec3) -> Self { + Self { + half_size: size / 2.0, + } + } + + /// Create a new `Ramp` from a given full base size and an `incline`. + /// The `incline` is the angle between the Z axis and the slanted face of the ramp. + /// The `incline` is in radians. + pub fn from_incline(x_length: f32, z_length: f32, incline: f32) -> Self { + let y_length = incline.tan() * z_length; + Self::from_size(Vec3::new(x_length, y_length, z_length)) + } + + /// Get the surface area of the ramp. + #[inline(always)] + pub fn area(&self) -> f32 { + (self.half_size.x + * (self.half_size.z + + self.half_size.y + + (self.half_size.z.powi(2) + self.half_size.y.powi(2)).sqrt()) + + self.half_size.z * self.half_size.y) + * 4. + } + + /// Get the volume of the ramp. + #[inline(always)] + pub fn volume(&self) -> f32 { + 4.0 * self.half_size.x * self.half_size.y * self.half_size.z + } + + /// Get the incline of the ramp. + /// + /// The `incline` is the angle between the Z axis and the slanted face of the ramp. + /// The `incline` is in radians. + #[inline(always)] + pub fn incline(&self) -> f32 { + self.half_size.y.atan2(self.half_size.z) + } +} + /// A 3D triangle primitive. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -1118,4 +1167,26 @@ mod tests { ); assert_relative_eq!(Tetrahedron::default().centroid(), Vec3::ZERO); } + + #[test] + fn ramp_math() { + let ramp = Ramp { + half_size: Vec3::new(1.9, 3.2, 7.45), + }; + + assert_eq!(ramp.area(), 237.922129, "incorrect area"); + assert_eq!(ramp.volume(), 181.18399, "incorrect volume"); + assert_eq!(ramp.incline(), 0.40570152, "incorrect incline"); + + assert_eq!(Ramp::default().area(), 4.4142136, "incorrect area"); + assert_eq!(Ramp::default().volume(), 0.5, "incorrect volume"); + assert_eq!(Ramp::default().incline(), 0.7853982, "incorrect incline"); + + let ramp = Ramp::from_incline(2.4, 3.8, 0.12); + assert_eq!( + ramp.half_size, + Vec3::new(1.2, 0.22910073, 1.9), + "incorrect from_incline" + ) + } } diff --git a/crates/bevy_reflect/src/impls/math/primitives3d.rs b/crates/bevy_reflect/src/impls/math/primitives3d.rs index 99f9a6b375cdc..0ee6568000e38 100644 --- a/crates/bevy_reflect/src/impls/math/primitives3d.rs +++ b/crates/bevy_reflect/src/impls/math/primitives3d.rs @@ -117,8 +117,7 @@ impl_reflect!( impl_reflect!( #[reflect(Debug, PartialEq, Serialize, Deserialize)] #[type_path = "bevy_math::primitives"] - struct Prism { + struct Ramp { half_size: Vec3, - apex_displacement: f32, } ); diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index 3911de8df95f7..af9e9f48f469a 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -2,7 +2,7 @@ mod capsule; mod cuboid; mod cylinder; mod plane; -mod prism; +mod ramp; mod sphere; mod torus; pub(crate) mod triangle3d; diff --git a/crates/bevy_render/src/mesh/primitives/dim3/prism.rs b/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs similarity index 58% rename from crates/bevy_render/src/mesh/primitives/dim3/prism.rs rename to crates/bevy_render/src/mesh/primitives/dim3/ramp.rs index f7629aa894ac2..19ecc43a8e596 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/prism.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/ramp.rs @@ -1,4 +1,4 @@ -use bevy_math::{primitives::Prism, Vec3}; +use bevy_math::{primitives::Ramp, Vec3}; use wgpu::PrimitiveTopology; use crate::{ @@ -6,36 +6,29 @@ use crate::{ render_asset::RenderAssetUsages, }; -impl Meshable for Prism { +impl Meshable for Ramp { type Output = Mesh; fn mesh(&self) -> Self::Output { let min = -self.half_size; let max = self.half_size; - let front_normal = Vec3::new(0.0, max.z * (self.apex_displacement + 1.0), max.y * 2.0) - .normalize_or_zero() - .to_array(); - let back_normal = Vec3::new(0.0, max.z * (self.apex_displacement - 1.0), max.y * 2.0) - .normalize_or_zero() - .to_array(); - - let apex_z = self.apex_displacement * max.z; + let top_normal = Vec3::new(0.0, min.z, max.y).normalize_or_zero().to_array(); // Suppose Y-up right hand, and camera look from +Z to -Z let vertices = &[ - // Back - ([min.x, max.y, apex_z], back_normal, [1.0, 0.0]), - ([max.x, max.y, apex_z], back_normal, [0.0, 0.0]), - ([max.x, min.y, min.z], back_normal, [0.0, 1.0]), - ([min.x, min.y, min.z], back_normal, [1.0, 1.0]), + // Slope + ([min.x, max.y, max.z], top_normal, [1.0, 0.0]), + ([max.x, max.y, max.z], top_normal, [0.0, 0.0]), + ([max.x, min.y, min.z], top_normal, [0.0, 1.0]), + ([min.x, min.y, min.z], top_normal, [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, apex_z], [1.0, 0.0, 0.0], [1.0, 1.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, apex_z], [-1.0, 0.0, 0.0], [0.0, 0.0]), + ([min.x, max.y, max.z], [-1.0, 0.0, 0.0], [0.0, 0.0]), ([min.x, min.y, min.z], [-1.0, 0.0, 0.0], [1.0, 1.0]), // Bottom ([max.x, min.y, max.z], [0.0, -1.0, 0.0], [0.0, 0.0]), @@ -43,10 +36,10 @@ impl Meshable for Prism { ([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]), // Front - ([min.x, max.y, apex_z], front_normal, [0.0, 0.0]), - ([max.x, max.y, apex_z], front_normal, [0.0, 1.0]), - ([max.x, min.y, max.z], front_normal, [1.0, 0.0]), - ([min.x, min.y, max.z], front_normal, [1.0, 1.0]), + ([min.x, max.y, max.z], [0.0, 0.0, 1.0], [0.0, 1.0]), + ([max.x, max.y, max.z], [0.0, 0.0, 1.0], [1.0, 1.0]), + ([max.x, min.y, max.z], [0.0, 0.0, 1.0], [1.0, 0.0]), + ([min.x, min.y, max.z], [0.0, 0.0, 1.0], [0.0, 0.0]), ]; let positions: Vec<_> = vertices.iter().map(|(p, _, _)| *p).collect(); @@ -54,11 +47,11 @@ impl Meshable for Prism { let uvs: Vec<_> = vertices.iter().map(|(_, _, uv)| *uv).collect(); let indices = Indices::U32(vec![ - 0, 1, 2, 2, 3, 0, // back + 0, 1, 2, 2, 3, 0, // slope 4, 5, 6, // right 7, 8, 9, // left 10, 11, 12, 12, 13, 10, // bottom - 14, 16, 15, 16, 14, 17, // Slope + 14, 16, 15, 16, 14, 17, // front ]); Mesh::new( @@ -72,8 +65,8 @@ impl Meshable for Prism { } } -impl From for Mesh { - fn from(prism: Prism) -> Self { - prism.mesh() +impl From for Mesh { + fn from(ramp: Ramp) -> Self { + ramp.mesh() } } From 8ae6ba589e34dc7fafa35692e4a291b80bcc2436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Thu, 11 Apr 2024 20:57:46 +0200 Subject: [PATCH 13/18] Fix example --- crates/bevy_render/src/mesh/primitives/dim3/mod.rs | 1 + examples/3d/3d_shapes.rs | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index af9e9f48f469a..76a9b87f8e950 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -10,5 +10,6 @@ pub(crate) mod triangle3d; pub use capsule::*; pub use cylinder::*; pub use plane::*; +pub use ramp::*; pub use sphere::*; pub use torus::*; diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 95e98b8012b98..cc91ab4f885e1 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -42,12 +42,9 @@ fn setup( meshes.add(Capsule3d::default()), meshes.add(Torus::default()), meshes.add(Cylinder::default()), + meshes.add(Ramp::default()), meshes.add(Sphere::default().mesh().ico(5).unwrap()), meshes.add(Sphere::default().mesh().uv(32, 18)), - meshes.add(Prism { - apex_displacement: 4.0, - half_size: Vec3::ONE * 0.5, - }), ]; let num_shapes = shapes.len(); From 5a91157bacfde6e3cc4a9936d1a04ea92cfe186a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:32:14 +0200 Subject: [PATCH 14/18] Fix merge errors --- crates/bevy_gizmos/src/primitives/dim3.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index 5607ecf8e9450..f42c4c1e9aeea 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -954,7 +954,7 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { primitive: Ramp, position: Vec3, rotation: Quat, - color: Color, + color: impl Into, ) -> Self::Output<'_> { if !self.enabled { return; @@ -986,6 +986,7 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { (c, e), ]; + let color = color.into(); lines.into_iter().for_each(|(start, end)| { self.line(start, end, color); }); From 3d43bdbbe163bb01f97422e5aeeb88b7f7579268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:47:22 +0200 Subject: [PATCH 15/18] fix CI errors --- crates/bevy_math/src/primitives/dim3.rs | 7 ++++--- crates/bevy_render/src/mesh/primitives/dim3/mod.rs | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 3b34faf8d3555..78ba6b4dbc871 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -1170,23 +1170,24 @@ mod tests { #[test] fn ramp_math() { + use std::f32::consts::FRAC_PI_4; let ramp = Ramp { half_size: Vec3::new(1.9, 3.2, 7.45), }; - assert_eq!(ramp.area(), 237.922129, "incorrect area"); + assert_eq!(ramp.area(), 237.92213, "incorrect area"); assert_eq!(ramp.volume(), 181.18399, "incorrect volume"); assert_eq!(ramp.incline(), 0.40570152, "incorrect incline"); assert_eq!(Ramp::default().area(), 4.4142136, "incorrect area"); assert_eq!(Ramp::default().volume(), 0.5, "incorrect volume"); - assert_eq!(Ramp::default().incline(), 0.7853982, "incorrect incline"); + assert_eq!(Ramp::default().incline(), FRAC_PI_4, "incorrect incline"); let ramp = Ramp::from_incline(2.4, 3.8, 0.12); assert_eq!( ramp.half_size, Vec3::new(1.2, 0.22910073, 1.9), "incorrect from_incline" - ) + ); } } diff --git a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs index 76a9b87f8e950..af9e9f48f469a 100644 --- a/crates/bevy_render/src/mesh/primitives/dim3/mod.rs +++ b/crates/bevy_render/src/mesh/primitives/dim3/mod.rs @@ -10,6 +10,5 @@ pub(crate) mod triangle3d; pub use capsule::*; pub use cylinder::*; pub use plane::*; -pub use ramp::*; pub use sphere::*; pub use torus::*; From 94fc2901f0c5e4f13b0b8f4bf9e5b77c822cdd30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 3 May 2024 11:58:20 +0200 Subject: [PATCH 16/18] Use `element_product` Co-Authored-By: NiseVoid --- crates/bevy_math/src/primitives/dim3.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index 1f65e027b1a17..6218c65224def 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -753,7 +753,7 @@ impl Ramp { /// Get the volume of the ramp. #[inline(always)] pub fn volume(&self) -> f32 { - 4.0 * self.half_size.x * self.half_size.y * self.half_size.z + 4.0 * self.half_size.element_product() } /// Get the incline of the ramp. From b100632fd046db0edcfa30ebc54c13dcdc1c3635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 3 May 2024 11:58:39 +0200 Subject: [PATCH 17/18] Use `Vec3A` for bounding --- .../bevy_math/src/bounding/bounded3d/primitive_impls.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs index 43e738d6d006f..2daba781c3fc2 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -309,7 +309,7 @@ impl Bounded3d for Ramp { self.half_size * Vec3::new(-1.0, -1.0, 1.0), self.half_size * Vec3::NEG_ONE, ]; - Aabb3d::from_point_cloud(translation, rotation, &points) + Aabb3d::from_point_cloud(translation, rotation, points.into_iter()) } fn bounding_sphere(&self, translation: Vec3, _rotation: Quat) -> BoundingSphere { @@ -631,11 +631,11 @@ mod tests { let translation = Vec3::new(-3.0, 1.75, 0.0); let aabb = ramp.aabb_3d(translation, Quat::IDENTITY); - assert_eq!(aabb.min, Vec3::new(-3.5, 0.25, -2.0)); - assert_eq!(aabb.max, Vec3::new(-2.5, 3.25, 2.0)); + assert_eq!(aabb.min, Vec3A::new(-3.5, 0.25, -2.0)); + assert_eq!(aabb.max, Vec3A::new(-2.5, 3.25, 2.0)); let bounding_sphere = ramp.bounding_sphere(translation, Quat::IDENTITY); - assert_eq!(bounding_sphere.center, translation); + assert_eq!(bounding_sphere.center, translation.into()); assert_eq!(bounding_sphere.radius(), 2.5495098); } } From b8b92afbd07539fda68fe104698e3e4ee04878d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20B=C3=BCttgenbach?= <62256001+solis-lumine-vorago@users.noreply.github.com> Date: Fri, 3 May 2024 12:05:31 +0200 Subject: [PATCH 18/18] Don't split halfsize in gizmos Co-Authored-By: NiseVoid Co-Authored-By: Joona Aalto --- crates/bevy_gizmos/src/primitives/dim3.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index f458222119015..f5d9d29e7fcf1 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -1095,18 +1095,16 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, 's, T> { return; } - let [half_extend_x, half_extend_y, half_extend_z] = primitive.half_size.to_array(); - // transform the points from the reference unit cube-like ramp to the actual ramp coords let [a, b, c, d, e, f] = [ - [-1.0, -1.0, -1.0], - [-1.0, -1.0, 1.0], - [1.0, -1.0, -1.0], - [1.0, -1.0, 1.0], - [1.0, 1.0, 1.0], - [-1.0, 1.0, 1.0], + Vec3::new(-1.0, -1.0, -1.0), + Vec3::new(-1.0, -1.0, 1.0), + Vec3::new(1.0, -1.0, -1.0), + Vec3::new(1.0, -1.0, 1.0), + Vec3::new(1.0, 1.0, 1.0), + Vec3::new(-1.0, 1.0, 1.0), ] - .map(|[sx, sy, sz]| Vec3::new(sx * half_extend_x, sy * half_extend_y, sz * half_extend_z)) + .map(|s| s * primitive.half_size) .map(rotate_then_translate_3d(rotation, position)); let lines = vec![