From 3f064d43b7cb9a8549942a6563cb58af34a0ba97 Mon Sep 17 00:00:00 2001 From: aviac Date: Wed, 1 Jul 2026 15:23:12 +0200 Subject: [PATCH 1/4] add ellipsoid primitive --- crates/bevy_math/src/primitives/dim3.rs | 82 +++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/crates/bevy_math/src/primitives/dim3.rs b/crates/bevy_math/src/primitives/dim3.rs index c070d77f422e9..767124033366c 100644 --- a/crates/bevy_math/src/primitives/dim3.rs +++ b/crates/bevy_math/src/primitives/dim3.rs @@ -88,6 +88,74 @@ impl Measured3d for Sphere { } } +/// An ellipsoid primitive representing all points whose distance from the origin is scaled +/// independently along each axis +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Debug, PartialEq, Default, Clone) +)] +#[cfg_attr( + all(feature = "serialize", feature = "bevy_reflect"), + reflect(Serialize, Deserialize) +)] +pub struct Ellipsoid { + /// The radii of the ellipsoid in each dimension + pub radii: Vec3, +} + +impl Primitive3d for Ellipsoid {} + +impl Default for Ellipsoid { + /// Returns the default [`Ellipsoid`] with a radius of `0.5` in each dimension. + fn default() -> Self { + Self { + radii: Vec3::splat(0.5), + } + } +} + +impl Ellipsoid { + /// Create a new [`Ellipsoid`] from a `radii`. + #[inline] + pub const fn new(radii: Vec3) -> Self { + Self { radii } + } + + /// Get the diameter of the ellipsoid in each dimension. + /// + /// This is a [`Vec3`] where the components refer to the diameter in each dimension. + #[inline] + pub fn diameter(&self) -> Vec3 { + 2.0 * self.radii + } +} + +impl Measured3d for Ellipsoid { + /// Get the surface area of the ellipsoid. + /// + /// This is only an approximation with a relative error of about 1.061%. + /// + /// See + #[inline] + fn area(&self) -> f32 { + let p = 1.6075; + let pow_p = |x| ops::powf(x, p); + let inner_sqrt = pow_p(self.radii.x) * pow_p(self.radii.y) + + pow_p(self.radii.y) * pow_p(self.radii.z) + + pow_p(self.radii.z) * pow_p(self.radii.x); + 4.0 * PI * ops::powf(inner_sqrt / 3.0, p.recip()) + } + + /// Get the volume of the ellipsoid. + #[inline] + fn volume(&self) -> f32 { + 4.0 * FRAC_PI_3 * self.radii.x * self.radii.y * self.radii.z + } +} + /// A bounded plane in 3D space. It forms a surface starting from the origin with a defined height and width. #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] @@ -1692,6 +1760,20 @@ mod tests { assert_eq!(sphere.volume(), 268.08257, "incorrect volume"); } + #[test] + fn ellipsoid_math() { + let ellipsoid = Ellipsoid { + radii: Vec3::new(1.0, 2.0, 3.0), + }; + assert_eq!( + ellipsoid.diameter(), + Vec3::new(2.0, 4.0, 6.0), + "incorrect diameter" + ); + assert_eq!(ellipsoid.area(), 48.971935, "incorrect area"); + assert_eq!(ellipsoid.volume(), 25.132742, "incorrect volume"); + } + #[test] fn plane_from_points() { let (plane, translation) = Plane3d::from_points(Vec3::X, Vec3::Z, Vec3::NEG_X); From 7444f7eaa7b222c30e3106b5f13bc91fc6ad6442 Mon Sep 17 00:00:00 2001 From: aviac Date: Wed, 1 Jul 2026 15:23:12 +0200 Subject: [PATCH 2/4] implement sampling and bounded trait for ellipsoid --- .../src/bounding/bounded3d/primitive_impls.rs | 36 ++++++++++++++++--- .../bevy_math/src/sampling/shape_sampling.rs | 22 ++++++++++++ 2 files changed, 54 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 7cf118e4d97e8..9478e7a428d6b 100644 --- a/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs +++ b/crates/bevy_math/src/bounding/bounded3d/primitive_impls.rs @@ -4,8 +4,8 @@ use crate::{ bounding::{Bounded2d, BoundingCircle, BoundingVolume}, ops, primitives::{ - Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Segment3d, - Sphere, Torus, Triangle2d, Triangle3d, + Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Ellipsoid, InfinitePlane3d, Line3d, + Segment3d, Sphere, Torus, Triangle2d, Triangle3d, }, Isometry2d, Isometry3d, Mat3, Vec2, Vec3, Vec3A, }; @@ -27,6 +27,18 @@ impl Bounded3d for Sphere { } } +impl Bounded3d for Ellipsoid { + fn aabb_3d(&self, isometry: impl Into) -> Aabb3d { + let isometry = isometry.into(); + Aabb3d::new(isometry.translation, self.radii) + } + + fn bounding_sphere(&self, isometry: impl Into) -> BoundingSphere { + let isometry = isometry.into(); + BoundingSphere::new(isometry.translation, self.radii.max_element()) + } +} + impl Bounded3d for InfinitePlane3d { fn aabb_3d(&self, isometry: impl Into) -> Aabb3d { let isometry = isometry.into(); @@ -372,8 +384,8 @@ mod tests { use crate::{ bounding::Bounded3d, primitives::{ - Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, InfinitePlane3d, Line3d, Polyline3d, - Segment3d, Sphere, Torus, Triangle3d, + Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Ellipsoid, InfinitePlane3d, Line3d, + Polyline3d, Segment3d, Sphere, Torus, Triangle3d, }, Dir3, }; @@ -392,6 +404,22 @@ mod tests { assert_eq!(bounding_sphere.radius(), 1.0); } + #[test] + fn ellipsoid() { + let ellipsoid = Ellipsoid { + radii: Vec3::new(1.0, 2.0, 3.0), + }; + let translation = Vec3::new(2.0, 1.0, 0.0); + + let aabb = ellipsoid.aabb_3d(translation); + assert_eq!(aabb.min, Vec3A::new(1.0, -1.0, -3.0)); + assert_eq!(aabb.max, Vec3A::new(3.0, 3.0, 3.0)); + + let bounding_sphere = ellipsoid.bounding_sphere(translation); + assert_eq!(bounding_sphere.center, translation.into()); + assert_eq!(bounding_sphere.radius(), 3.0); + } + #[test] fn plane() { let translation = Vec3::new(2.0, 1.0, 0.0); diff --git a/crates/bevy_math/src/sampling/shape_sampling.rs b/crates/bevy_math/src/sampling/shape_sampling.rs index a2e02675c9ce8..72d076fc653d9 100644 --- a/crates/bevy_math/src/sampling/shape_sampling.rs +++ b/crates/bevy_math/src/sampling/shape_sampling.rs @@ -222,6 +222,28 @@ impl ShapeSample for Sphere { } } +// NOTE: This implementation is *cheap* and *not perfectly accurate*. This was a deliberate choice +// since accurate sampling would require rejection based approaches. If this is re-evaluated to not +// matter in the future, the implementation may change to something else. +impl ShapeSample for Ellipsoid { + type Output = Vec3; + + fn sample_interior(&self, rng: &mut R) -> Vec3 { + let r_cubed = rng.random_range(0.0..=1.0); + let r = ops::cbrt(r_cubed); + + let p = r * sample_unit_sphere_boundary(rng); + + Vec3::new(p.x * self.radii.x, p.y * self.radii.y, p.z * self.radii.z) + } + + fn sample_boundary(&self, rng: &mut R) -> Vec3 { + let p = sample_unit_sphere_boundary(rng); + + Vec3::new(p.x * self.radii.x, p.y * self.radii.y, p.z * self.radii.z) + } +} + impl ShapeSample for Annulus { type Output = Vec2; From d10c77a0443c6f5731b7264e22ecf8e664d117e0 Mon Sep 17 00:00:00 2001 From: aviac Date: Wed, 1 Jul 2026 15:23:12 +0200 Subject: [PATCH 3/4] implement gizmos and meshing for ellipsoids --- crates/bevy_gizmos/src/circles.rs | 97 ++++++++++ crates/bevy_gizmos/src/primitives/dim3.rs | 30 +++- .../src/primitives/dim3/ellipsoid.rs | 170 ++++++++++++++++++ crates/bevy_mesh/src/primitives/dim3/mod.rs | 2 + 4 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 crates/bevy_mesh/src/primitives/dim3/ellipsoid.rs diff --git a/crates/bevy_gizmos/src/circles.rs b/crates/bevy_gizmos/src/circles.rs index 07ac90ea60e43..e1cb98cd0fe12 100644 --- a/crates/bevy_gizmos/src/circles.rs +++ b/crates/bevy_gizmos/src/circles.rs @@ -218,6 +218,46 @@ where resolution: DEFAULT_CIRCLE_RESOLUTION, } } + + /// Draw a wireframe ellipsoid in 3D made out of 3 ellipses around the axes with the given + /// `isometry` applied. + /// + /// If `isometry == Isometry3d::IDENTITY` then + /// + /// - the center is at `Vec3::ZERO` + /// - the 3 ellipses are in the XY, YZ and XZ planes. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::Color; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.ellipsoid(Isometry3d::IDENTITY, Vec3::new(1., 2., 3.), Color::BLACK); + /// + /// // Each circle has 32 line-segments by default. + /// // You may want to increase this for larger spheres. + /// gizmos + /// .ellipsoid(Isometry3d::IDENTITY, Vec3::new(1., 2., 3.), Color::BLACK) + /// .resolution(64); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + #[inline] + pub fn ellipsoid( + &mut self, + isometry: impl Into, + radii: Vec3, + color: impl Into, + ) -> EllipsoidBuilder<'_, Config, Clear> { + EllipsoidBuilder { + gizmos: self, + radii, + isometry: isometry.into(), + color: color.into(), + resolution: DEFAULT_CIRCLE_RESOLUTION, + } + } } /// A builder returned by [`GizmoBuffer::ellipse`]. @@ -353,3 +393,60 @@ where }); } } + +/// A builder returned by [`GizmoBuffer::ellipsoid`]. +pub struct EllipsoidBuilder<'a, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + gizmos: &'a mut GizmoBuffer, + + // Radii of the ellipsoid + radii: Vec3, + + isometry: Isometry3d, + // Color of the ellipsoid + color: Color, + + // Number of line-segments used to approximate the ellipsoid geometry + resolution: u32, +} + +impl EllipsoidBuilder<'_, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + /// Set the number of line-segments used to approximate the ellipsoid geometry. + pub fn resolution(mut self, resolution: u32) -> Self { + self.resolution = resolution; + self + } +} + +impl Drop for EllipsoidBuilder<'_, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + fn drop(&mut self) { + if !self.gizmos.enabled { + return; + } + + // draws one great circle around each of the local axes + [ + (Vec3::X, Vec2::new(self.radii.z, self.radii.y)), + (Vec3::Y, Vec2::new(self.radii.x, self.radii.z)), + (Vec3::Z, Vec2::new(self.radii.x, self.radii.y)), + ] + .into_iter() + .for_each(|(axis, half_size)| { + let axis_rotation = Isometry3d::from_rotation(Quat::from_rotation_arc(Vec3::Z, axis)); + self.gizmos + .ellipse(self.isometry * axis_rotation, half_size, self.color) + .resolution(self.resolution); + }); + } +} diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index b4dd923a7b009..c20ba8367816d 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -5,13 +5,17 @@ use super::helpers::*; use bevy_color::Color; use bevy_math::{ primitives::{ - Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, + Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Ellipsoid, Line3d, Plane3d, Polyline3d, Primitive3d, Segment3d, Sphere, Tetrahedron, Torus, Triangle3d, }, Dir3, Isometry3d, Quat, UVec2, Vec2, Vec3, }; -use crate::{circles::SphereBuilder, gizmos::GizmoBuffer, prelude::GizmoConfigGroup}; +use crate::{ + circles::{EllipsoidBuilder, SphereBuilder}, + gizmos::GizmoBuffer, + prelude::GizmoConfigGroup, +}; const DEFAULT_RESOLUTION: u32 = 5; // length used to simulate infinite lines @@ -80,6 +84,28 @@ where } } +// ellipsoid + +impl GizmoPrimitive3d for GizmoBuffer +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + type Output<'a> + = EllipsoidBuilder<'a, Config, Clear> + where + Self: 'a; + + fn primitive_3d( + &mut self, + primitive: &Ellipsoid, + isometry: impl Into, + color: impl Into, + ) -> Self::Output<'_> { + self.ellipsoid(isometry, primitive.radii, color) + } +} + // plane 3d /// Builder for configuring the drawing options of [`Plane3d`]. diff --git a/crates/bevy_mesh/src/primitives/dim3/ellipsoid.rs b/crates/bevy_mesh/src/primitives/dim3/ellipsoid.rs new file mode 100644 index 0000000000000..fb7f96d008e39 --- /dev/null +++ b/crates/bevy_mesh/src/primitives/dim3/ellipsoid.rs @@ -0,0 +1,170 @@ +use crate::{Indices, Mesh, MeshBuilder, Meshable, PrimitiveTopology}; +use bevy_asset::RenderAssetUsages; +use bevy_math::{ops, primitives::Ellipsoid, Vec3}; +use bevy_reflect::prelude::*; +use core::f32::consts::PI; + +/// A type of ellipsoid mesh. +#[derive(Clone, Copy, Debug, Reflect)] +#[reflect(Default, Debug, Clone)] +pub enum EllipsoidKind { + /// A UV ellipsoid, a spherical mesh that consists of quadrilaterals + /// apart from triangles at the top and bottom. + Uv { + /// The number of longitudinal sectors, aka the horizontal resolution. + #[doc(alias = "horizontal_resolution")] + sectors: u32, + /// The number of latitudinal stacks, aka the vertical resolution. + #[doc(alias = "vertical_resolution")] + stacks: u32, + }, +} + +impl Default for EllipsoidKind { + fn default() -> Self { + Self::Uv { + sectors: 10, + stacks: 10, + } + } +} + +/// A builder used for creating a [`Mesh`] with an [`Ellipsoid`] shape. +#[derive(Clone, Copy, Debug, Default, Reflect)] +#[reflect(Default, Debug, Clone)] +pub struct EllipsoidMeshBuilder { + /// The [`Ellipsoid`] shape. + pub ellipsoid: Ellipsoid, + /// The type of ellipsoid mesh that will be built. + pub kind: EllipsoidKind, +} + +impl EllipsoidMeshBuilder { + /// Creates a new [`EllipsoidMeshBuilder`] from a radius and [`EllipsoidKind`]. + #[inline] + pub const fn new(radius: Vec3, kind: EllipsoidKind) -> Self { + Self { + ellipsoid: Ellipsoid { radii: radius }, + kind, + } + } + + /// Sets the [`EllipsoidKind`] that will be used for building the mesh. + #[inline] + pub const fn kind(mut self, kind: EllipsoidKind) -> Self { + self.kind = kind; + self + } + + /// Creates a UV ellipsoid [`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. + #[expect( + clippy::explicit_counter_loop, + reason = "Clippy suggestion was much less clear." + )] + pub fn uv(&self, sectors: u32, stacks: u32) -> Mesh { + // Largely inspired from http://www.songho.ca/opengl/gl_ellipsoid.html + + let sectors_f32 = sectors as f32; + let stacks_f32 = stacks as f32; + let sector_step = 2. * PI / sectors_f32; + let stack_step = PI / stacks_f32; + + let n_vertices = (stacks * sectors) as usize; + let mut vertices: 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 mut indices: Vec = Vec::with_capacity(n_vertices * 2 * 3); + + let a = self.ellipsoid.radii.x; + let b = self.ellipsoid.radii.y; + let c = self.ellipsoid.radii.z; + + for i in 0..=stacks { + let stack_angle = PI / 2.0 - (i as f32) * stack_step; + + let cos_stack = ops::cos(stack_angle); + let sin_stack = ops::sin(stack_angle); + + for j in 0..=sectors { + let sector_angle = j as f32 * sector_step; + + let cos_sector = ops::cos(sector_angle); + let sin_sector = ops::sin(sector_angle); + + let x = a * cos_stack * cos_sector; + let y = b * cos_stack * sin_sector; + let z = c * sin_stack; + + vertices.push([x, y, z]); + + let n = Vec3::new(x / (a * a), y / (b * b), z / (c * c)).normalize(); + + normals.push(n.to_array()); + + 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); + indices.push(k2); + indices.push(k1 + 1); + } + if i != stacks - 1 { + indices.push(k1 + 1); + indices.push(k2); + indices.push(k2 + 1); + } + k1 += 1; + k2 += 1; + } + } + + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::default(), + ) + .with_inserted_indices(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 MeshBuilder for EllipsoidMeshBuilder { + /// Builds a [`Mesh`] according to the configuration in `self`. + fn build(&self) -> Mesh { + match self.kind { + EllipsoidKind::Uv { sectors, stacks } => self.uv(sectors, stacks), + } + } +} + +impl Meshable for Ellipsoid { + type Output = EllipsoidMeshBuilder; + + fn mesh(&self) -> Self::Output { + EllipsoidMeshBuilder { + ellipsoid: *self, + ..Default::default() + } + } +} + +impl From for Mesh { + fn from(ellipsoid: Ellipsoid) -> Self { + ellipsoid.mesh().build() + } +} diff --git a/crates/bevy_mesh/src/primitives/dim3/mod.rs b/crates/bevy_mesh/src/primitives/dim3/mod.rs index 21cd80b3a420f..4822ab6ed71e4 100644 --- a/crates/bevy_mesh/src/primitives/dim3/mod.rs +++ b/crates/bevy_mesh/src/primitives/dim3/mod.rs @@ -3,6 +3,7 @@ mod cone; mod conical_frustum; mod cuboid; mod cylinder; +mod ellipsoid; mod plane; mod polyline3d; mod segment3d; @@ -16,6 +17,7 @@ pub use cone::*; pub use conical_frustum::*; pub use cuboid::*; pub use cylinder::*; +pub use ellipsoid::*; pub use plane::*; pub use sphere::*; pub use tetrahedron::*; From 855ba64e406d965e38f6e288b6812352635742c0 Mon Sep 17 00:00:00 2001 From: aviac Date: Wed, 1 Jul 2026 15:23:12 +0200 Subject: [PATCH 4/4] add ellipsoids to primitives example --- examples/3d/3d_shapes.rs | 43 +++++++++++++++++------------- examples/math/render_primitives.rs | 11 +++++++- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 5edb6c777f3cc..68bf3c5e83a97 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -49,7 +49,7 @@ fn main() { #[derive(Component)] struct Shape; -const SHAPES_X_EXTENT: f32 = 14.0; +const SHAPES_X_EXTENT: f32 = 10.0; const EXTRUSION_X_EXTENT: f32 = 14.0; const Z_EXTENT: f32 = 8.0; const THICKNESS: f32 = 0.1; @@ -67,6 +67,7 @@ fn setup( let shapes = [ meshes.add(Cuboid::default()), + meshes.add(Ellipsoid::new(Vec3::new(1.0, 0.5, 0.25))), meshes.add(Tetrahedron::default()), meshes.add(Capsule3d::default()), meshes.add(Torus::default()), @@ -129,20 +130,23 @@ fn setup( meshes.add(Extrusion::new(Triangle2d::default().to_ring(THICKNESS), 1.)), ]; - let num_shapes = shapes.len(); + let num_shapes_per_row = shapes.len() / 2; for (i, shape) in shapes.into_iter().enumerate() { + let row = if i % 2 == 0 { Row::First } else { Row::Second }; + let z = row.z(); commands.spawn(( Mesh3d(shape), MeshMaterial3d(debug_material.clone()), Transform::from_xyz( - -SHAPES_X_EXTENT / 2. + i as f32 / (num_shapes - 1) as f32 * SHAPES_X_EXTENT, + -SHAPES_X_EXTENT / 2. + + (i / 2) as f32 / (num_shapes_per_row - 1) as f32 * SHAPES_X_EXTENT, 2.0, - Row::Front.z(), + z, ) .with_rotation(Quat::from_rotation_x(-PI / 4.)), Shape, - Row::Front, + row, )); } @@ -156,11 +160,11 @@ fn setup( -EXTRUSION_X_EXTENT / 2. + i as f32 / (num_extrusions - 1) as f32 * EXTRUSION_X_EXTENT, 2.0, - Row::Middle.z(), + Row::Third.z(), ) .with_rotation(Quat::from_rotation_x(-PI / 4.)), Shape, - Row::Middle, + Row::Third, )); } @@ -174,11 +178,11 @@ fn setup( -EXTRUSION_X_EXTENT / 2. + i as f32 / (num_ring_extrusions - 1) as f32 * EXTRUSION_X_EXTENT, 2.0, - Row::Rear.z(), + Row::Fourth.z(), ) .with_rotation(Quat::from_rotation_x(-PI / 4.)), Shape, - Row::Rear, + Row::Fourth, )); } @@ -269,25 +273,28 @@ fn toggle_wireframe( #[derive(Component, Clone, Copy)] enum Row { - Front, - Middle, - Rear, + First, + Second, + Third, + Fourth, } impl Row { fn z(self) -> f32 { match self { - Row::Front => Z_EXTENT / 2., - Row::Middle => 0., - Row::Rear => -Z_EXTENT / 2., + Row::First => Z_EXTENT / 2., + Row::Second => 0., + Row::Third => -Z_EXTENT / 2., + Row::Fourth => -Z_EXTENT, } } fn advance(self) -> Self { match self { - Row::Front => Row::Rear, - Row::Middle => Row::Front, - Row::Rear => Row::Middle, + Row::First => Row::Fourth, + Row::Second => Row::First, + Row::Third => Row::Second, + Row::Fourth => Row::Third, } } } diff --git a/examples/math/render_primitives.rs b/examples/math/render_primitives.rs index 4d7a3117bfa31..ea5fe67e5d8d5 100644 --- a/examples/math/render_primitives.rs +++ b/examples/math/render_primitives.rs @@ -69,6 +69,7 @@ enum PrimitiveSelected { RectangleAndCuboid, CircleAndSphere, Ellipse, + Ellipsoid, Triangle, Plane, Line, @@ -100,10 +101,11 @@ impl std::fmt::Display for PrimitiveSelected { } impl PrimitiveSelected { - const ALL: [Self; 20] = [ + const ALL: [Self; 21] = [ Self::RectangleAndCuboid, Self::CircleAndSphere, Self::Ellipse, + Self::Ellipsoid, Self::Triangle, Self::Plane, Self::Line, @@ -159,6 +161,9 @@ const CUBOID: Cuboid = Cuboid { const CIRCLE: Circle = Circle { radius: BIG_2D }; const SPHERE: Sphere = Sphere { radius: BIG_3D }; +const ELLIPSOID: Ellipsoid = Ellipsoid { + radii: Vec3::new(SMALL_3D * 1.0, SMALL_3D * 2.0, SMALL_3D * 3.0), +}; const ELLIPSE: Ellipse = Ellipse { half_size: Vec2::new(BIG_2D, SMALL_2D), @@ -445,6 +450,7 @@ fn draw_gizmos_2d(mut gizmos: Gizmos, state: Res>, time gizmos.primitive_2d(&CIRCLE, isometry, color); } PrimitiveSelected::Ellipse => drop(gizmos.primitive_2d(&ELLIPSE, isometry, color)), + PrimitiveSelected::Ellipsoid => {} PrimitiveSelected::Triangle => gizmos.primitive_2d(&TRIANGLE_2D, isometry, color), PrimitiveSelected::Plane => gizmos.primitive_2d(&PLANE_2D, isometry, color), PrimitiveSelected::Line => drop(gizmos.primitive_2d(&LINE_2D, isometry, color)), @@ -526,6 +532,7 @@ fn spawn_primitive_2d( Some(RECTANGLE.mesh().build()), Some(CIRCLE.mesh().build()), Some(ELLIPSE.mesh().build()), + None, // ellipsoid Some(TRIANGLE_2D.mesh().build()), None, // plane None, // line @@ -577,6 +584,7 @@ fn spawn_primitive_3d( Some(CUBOID.mesh().build()), Some(SPHERE.mesh().build()), None, // ellipse + Some(ELLIPSOID.mesh().build()), Some(TRIANGLE_3D.mesh().build()), Some(PLANE_3D.mesh().build()), None, // line @@ -700,6 +708,7 @@ fn draw_gizmos_3d(mut gizmos: Gizmos, state: Res>, time .resolution(resolution), ), PrimitiveSelected::Ellipse => {} + PrimitiveSelected::Ellipsoid => drop(gizmos.primitive_3d(&ELLIPSOID, isometry, color)), PrimitiveSelected::Triangle => gizmos.primitive_3d(&TRIANGLE_3D, isometry, color), PrimitiveSelected::Plane => drop(gizmos.primitive_3d(&PLANE_3D, isometry, color)), PrimitiveSelected::Line => gizmos.primitive_3d(&LINE_3D, isometry, color),