diff --git a/crates/bevy_gizmos/src/circles.rs b/crates/bevy_gizmos/src/circles.rs index aa4bc7d7c3..6fcd034938 100644 --- a/crates/bevy_gizmos/src/circles.rs +++ b/crates/bevy_gizmos/src/circles.rs @@ -178,6 +178,45 @@ where resolution: DEFAULT_CIRCLE_RESOLUTION, } } + + /// Draw a wireframe sphere in 3D made out of 3 circles around the axes. + /// + /// This should be called for each frame the sphere needs to be rendered. + /// + /// # Example + /// ``` + /// # use bevy_gizmos::prelude::*; + /// # use bevy_render::prelude::*; + /// # use bevy_math::prelude::*; + /// # use bevy_color::Color; + /// fn system(mut gizmos: Gizmos) { + /// gizmos.sphere(Vec3::ZERO, Quat::IDENTITY, 1., Color::BLACK); + /// + /// // Each circle has 32 line-segments by default. + /// // You may want to increase this for larger spheres. + /// gizmos + /// .sphere(Vec3::ZERO, Quat::IDENTITY, 5., Color::BLACK) + /// .resolution(64); + /// } + /// # bevy_ecs::system::assert_is_system(system); + /// ``` + #[inline] + pub fn sphere( + &mut self, + position: Vec3, + rotation: Quat, + radius: f32, + color: impl Into, + ) -> SphereBuilder<'_, 'w, 's, Config, Clear> { + SphereBuilder { + gizmos: self, + radius, + position, + rotation, + color: color.into(), + resolution: DEFAULT_CIRCLE_RESOLUTION, + } + } } /// A builder returned by [`Gizmos::ellipse`]. @@ -266,3 +305,66 @@ where self.gizmos.linestrip_2d(positions, self.color); } } + +/// Builder for configuring the drawing options of [`Sphere`]. +pub struct SphereBuilder<'a, 'w, 's, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, + + // Radius of the sphere + radius: f32, + + // Rotation of the sphere around the origin in 3D space + rotation: Quat, + // Center position of the sphere in 3D space + position: Vec3, + // Color of the sphere + color: Color, + + // Number of line-segments used to approximate the sphere geometry + resolution: usize, +} + +impl SphereBuilder<'_, '_, '_, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + /// Set the number of line-segments used to approximate the sphere geometry. + pub fn resolution(mut self, resolution: usize) -> Self { + self.resolution = resolution; + self + } +} + +impl Drop for SphereBuilder<'_, '_, '_, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + fn drop(&mut self) { + if !self.gizmos.enabled { + return; + } + + let SphereBuilder { + radius, + position: center, + rotation, + color, + resolution, + .. + } = self; + + // draws one great circle around each of the local axes + Vec3::AXES.into_iter().for_each(|axis| { + let normal = *rotation * axis; + self.gizmos + .circle(*center, Dir3::new_unchecked(normal), *radius, *color) + .resolution(*resolution); + }); + } +} diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index 9c0d1cb3b8..3b101a36f4 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -2,14 +2,13 @@ use std::{iter, marker::PhantomData, mem}; -use crate::circles::DEFAULT_CIRCLE_RESOLUTION; use bevy_color::{Color, LinearRgba}; use bevy_ecs::{ component::Tick, system::{Deferred, ReadOnlySystemParam, Res, Resource, SystemBuffer, SystemMeta, SystemParam}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; -use bevy_math::{Dir3, Quat, Rotation2d, Vec2, Vec3}; +use bevy_math::{Quat, Rotation2d, Vec2, Vec3}; use bevy_transform::TransformPoint; use bevy_utils::default; @@ -459,45 +458,6 @@ where strip_colors.push(LinearRgba::NAN); } - /// Draw a wireframe sphere in 3D made out of 3 circles around the axes. - /// - /// This should be called for each frame the sphere needs to be rendered. - /// - /// # Example - /// ``` - /// # use bevy_gizmos::prelude::*; - /// # use bevy_render::prelude::*; - /// # use bevy_math::prelude::*; - /// # use bevy_color::Color; - /// fn system(mut gizmos: Gizmos) { - /// gizmos.sphere(Vec3::ZERO, Quat::IDENTITY, 1., Color::BLACK); - /// - /// // Each circle has 32 line-segments by default. - /// // You may want to increase this for larger spheres. - /// gizmos - /// .sphere(Vec3::ZERO, Quat::IDENTITY, 5., Color::BLACK) - /// .resolution(64); - /// } - /// # bevy_ecs::system::assert_is_system(system); - /// ``` - #[inline] - pub fn sphere( - &mut self, - position: Vec3, - rotation: Quat, - radius: f32, - color: impl Into, - ) -> SphereBuilder<'_, 'w, 's, Config, Clear> { - SphereBuilder { - gizmos: self, - position, - rotation: rotation.normalize(), - radius, - color: color.into(), - resolution: DEFAULT_CIRCLE_RESOLUTION, - } - } - /// Draw a wireframe rectangle in 3D. /// /// This should be called for each frame the rectangle needs to be rendered. @@ -790,49 +750,6 @@ where } } -/// A builder returned by [`Gizmos::sphere`]. -pub struct SphereBuilder<'a, 'w, 's, Config, Clear = ()> -where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - position: Vec3, - rotation: Quat, - radius: f32, - color: Color, - resolution: usize, -} - -impl SphereBuilder<'_, '_, '_, Config, Clear> -where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - /// Set the number of line-segments per circle for this sphere. - pub fn resolution(mut self, resolution: usize) -> Self { - self.resolution = resolution; - self - } -} - -impl Drop for SphereBuilder<'_, '_, '_, Config, Clear> -where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - fn drop(&mut self) { - if !self.gizmos.enabled { - return; - } - for axis in Dir3::AXES { - self.gizmos - .circle(self.position, self.rotation * axis, self.radius, self.color) - .resolution(self.resolution); - } - } -} - fn rect_inner(size: Vec2) -> [Vec2; 4] { let half_size = size / 2.; let tl = Vec2::new(-half_size.x, half_size.y); diff --git a/crates/bevy_gizmos/src/primitives/dim2.rs b/crates/bevy_gizmos/src/primitives/dim2.rs index 1c0b699e60..9acca8356b 100644 --- a/crates/bevy_gizmos/src/primitives/dim2.rs +++ b/crates/bevy_gizmos/src/primitives/dim2.rs @@ -102,7 +102,7 @@ where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { - type Output<'a> = () where Self: 'a; + type Output<'a> = crate::circles::Ellipse2dBuilder<'a, 'w, 's, Config, Clear> where Self: 'a; fn primitive_2d( &mut self, @@ -111,11 +111,7 @@ where _angle: f32, color: impl Into, ) -> Self::Output<'_> { - if !self.enabled { - return; - } - - self.circle_2d(position, primitive.radius, color); + self.circle_2d(position, primitive.radius, color) } } @@ -205,46 +201,114 @@ where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { - type Output<'a> = () where Self: 'a; + type Output<'a> = crate::circles::Ellipse2dBuilder<'a, 'w, 's, Config, Clear> where Self: 'a; - fn primitive_2d( + fn primitive_2d<'a>( &mut self, primitive: &Ellipse, position: Vec2, angle: f32, color: impl Into, ) -> Self::Output<'_> { - if !self.enabled { - return; - } - - self.ellipse_2d(position, angle, primitive.half_size, color); + self.ellipse_2d(position, angle, primitive.half_size, color) } } // annulus 2d +/// Builder for configuring the drawing options of [`Annulus`]. +pub struct Annulus2dBuilder<'a, 'w, 's, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, + position: Vec2, + inner_radius: f32, + outer_radius: f32, + color: Color, + inner_resolution: usize, + outer_resolution: usize, +} + +impl Annulus2dBuilder<'_, '_, '_, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + /// Set the number of line-segments for each circle of the annulus. + pub fn resolution(mut self, resolution: usize) -> Self { + self.outer_resolution = resolution; + self.inner_resolution = resolution; + self + } + + /// Set the number of line-segments for the outer circle of the annulus. + pub fn outer_resolution(mut self, resolution: usize) -> Self { + self.outer_resolution = resolution; + self + } + + /// Set the number of line-segments for the inner circle of the annulus. + pub fn inner_resolution(mut self, resolution: usize) -> Self { + self.inner_resolution = resolution; + self + } +} + impl<'w, 's, Config, Clear> GizmoPrimitive2d for Gizmos<'w, 's, Config, Clear> where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { - type Output<'a> = () where Self: 'a; + type Output<'a> = Annulus2dBuilder<'a, 'w, 's, Config, Clear> where Self: 'a; fn primitive_2d( &mut self, primitive: &Annulus, position: Vec2, - angle: f32, + _angle: f32, color: impl Into, ) -> Self::Output<'_> { - if !self.enabled { + Annulus2dBuilder { + gizmos: self, + position, + inner_radius: primitive.inner_circle.radius, + outer_radius: primitive.outer_circle.radius, + color: color.into(), + inner_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION, + outer_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION, + } + } +} + +impl Drop for Annulus2dBuilder<'_, '_, '_, Config, Clear> +where + Config: GizmoConfigGroup, + Clear: 'static + Send + Sync, +{ + fn drop(&mut self) { + if !self.gizmos.enabled { return; } - let color = color.into(); - self.primitive_2d(&primitive.inner_circle, position, angle, color); - self.primitive_2d(&primitive.outer_circle, position, angle, color); + let Annulus2dBuilder { + gizmos, + position, + inner_radius, + outer_radius, + inner_resolution, + outer_resolution, + color, + .. + } = self; + + gizmos + .circle_2d(*position, *outer_radius, *color) + .resolution(*outer_resolution); + gizmos + .circle_2d(*position, *inner_radius, *color) + .resolution(*inner_resolution); } } @@ -266,8 +330,7 @@ where ) -> Self::Output<'_> { if !self.enabled { return; - } - + }; let [a, b, c, d] = [(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)].map(|(sign_x, sign_y)| { Vec2::new( diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index ec6ab6d651..31cb395526 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -1,7 +1,7 @@ //! A module for rendering each of the 3D [`bevy_math::primitives`] with [`Gizmos`]. use super::helpers::*; -use std::f32::consts::TAU; +use std::f32::consts::{FRAC_PI_2, PI, TAU}; use bevy_color::Color; use bevy_math::primitives::{ @@ -10,6 +10,7 @@ use bevy_math::primitives::{ }; use bevy_math::{Dir3, Quat, Vec3}; +use crate::circles::SphereBuilder; use crate::prelude::{GizmoConfigGroup, Gizmos}; const DEFAULT_RESOLUTION: usize = 5; @@ -55,41 +56,6 @@ where // sphere -/// Builder for configuring the drawing options of [`Sphere`]. -pub struct SphereBuilder<'a, 'w, 's, Config, Clear> -where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - - // Radius of the sphere - radius: f32, - - // Rotation of the sphere around the origin in 3D space - rotation: Quat, - // Center position of the sphere in 3D space - position: Vec3, - // Color of the sphere - color: Color, - - // Resolution of the gizmos used to approximate the sphere geometry - // The number of vertices used to approximate the sphere geometry. - resolution: usize, -} - -impl SphereBuilder<'_, '_, '_, Config, Clear> -where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - /// Set the number of lines used to approximate the sphere geometry. - pub fn resolution(mut self, resolution: usize) -> Self { - self.resolution = resolution; - self - } -} - impl<'w, 's, Config, Clear> GizmoPrimitive3d for Gizmos<'w, 's, Config, Clear> where Config: GizmoConfigGroup, @@ -104,59 +70,7 @@ where rotation: Quat, color: impl Into, ) -> Self::Output<'_> { - SphereBuilder { - gizmos: self, - radius: primitive.radius, - position, - rotation, - color: color.into(), - resolution: DEFAULT_RESOLUTION, - } - } -} - -impl Drop for SphereBuilder<'_, '_, '_, Config, Clear> -where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - fn drop(&mut self) { - if !self.gizmos.enabled { - return; - } - - let SphereBuilder { - radius, - position: center, - rotation, - color, - resolution, - .. - } = self; - - // draws the upper and lower semi spheres - [-1.0, 1.0].into_iter().for_each(|sign| { - let top = *center + (*rotation * Vec3::Y) * sign * *radius; - draw_semi_sphere( - self.gizmos, - *radius, - *resolution, - *rotation, - *center, - top, - *color, - ); - }); - - // draws one great circle of the sphere - draw_circle_3d( - self.gizmos, - *radius, - *resolution, - *rotation, - *center, - *color, - ); + self.sphere(position, rotation, primitive.radius, color) } } @@ -578,30 +492,27 @@ where resolution, } = self; - let normal = *rotation * Vec3::Y; + let normal = Dir3::new_unchecked(*rotation * Vec3::Y); + let up = normal.as_vec3() * *half_height; // draw upper and lower circle of the cylinder [-1.0, 1.0].into_iter().for_each(|sign| { - draw_circle_3d( - gizmos, - *radius, - *resolution, - *rotation, - *position + sign * *half_height * normal, - *color, - ); + gizmos + .circle(*position + sign * up, normal, *radius, *color) + .resolution(*resolution); }); // draw lines connecting the two cylinder circles - draw_cylinder_vertical_lines( - gizmos, - *radius, - *resolution, - *half_height, - *rotation, - *position, - *color, - ); + [Vec3::NEG_X, Vec3::NEG_Z, Vec3::X, Vec3::Z] + .into_iter() + .for_each(|axis| { + let axis = *rotation * axis; + gizmos.line( + *position + up + axis * *radius, + *position - up + axis * *radius, + *color, + ); + }); } } @@ -691,26 +602,57 @@ where resolution, } = self; - let normal = *rotation * Vec3::Y; + // Draw the circles at the top and bottom of the cylinder + let y_offset = *rotation * Vec3::Y; + gizmos + .circle( + *position + y_offset * *half_length, + Dir3::new_unchecked(y_offset), + *radius, + *color, + ) + .resolution(*resolution); + gizmos + .circle( + *position - y_offset * *half_length, + Dir3::new_unchecked(y_offset), + *radius, + *color, + ) + .resolution(*resolution); + let y_offset = y_offset * *half_length; - // draw two semi spheres for the capsule - [1.0, -1.0].into_iter().for_each(|sign| { - let center = *position + sign * *half_length * normal; - let top = center + sign * *radius * normal; - draw_semi_sphere(gizmos, *radius, *resolution, *rotation, center, top, *color); - draw_circle_3d(gizmos, *radius, *resolution, *rotation, center, *color); + // Draw the vertical lines and the cap semicircles + [Vec3::X, Vec3::Z].into_iter().for_each(|axis| { + let normal = *rotation * axis; + + gizmos.line( + *position + normal * *radius + y_offset, + *position + normal * *radius - y_offset, + *color, + ); + gizmos.line( + *position - normal * *radius + y_offset, + *position - normal * *radius - y_offset, + *color, + ); + + let rotation = *rotation + * Quat::from_euler(bevy_math::EulerRot::ZYX, 0., axis.z * FRAC_PI_2, FRAC_PI_2); + + gizmos + .arc_3d(PI, *radius, *position + y_offset, rotation, *color) + .resolution(*resolution / 2); + gizmos + .arc_3d( + PI, + *radius, + *position - y_offset, + rotation * Quat::from_rotation_y(PI), + *color, + ) + .resolution(*resolution / 2); }); - - // connect the two semi spheres with lines - draw_cylinder_vertical_lines( - gizmos, - *radius, - *resolution, - *half_length, - *rotation, - *position, - *color, - ); } } diff --git a/crates/bevy_gizmos/src/primitives/helpers.rs b/crates/bevy_gizmos/src/primitives/helpers.rs index 286ed1d8cf..3ca4a6ad18 100644 --- a/crates/bevy_gizmos/src/primitives/helpers.rs +++ b/crates/bevy_gizmos/src/primitives/helpers.rs @@ -46,33 +46,6 @@ pub(crate) fn circle_coordinates(radius: f32, resolution: usize) -> impl Iterato .take(resolution) } -/// Draws a semi-sphere. -/// -/// This function draws a semi-sphere at the specified `center` point with the given `rotation`, -/// `radius`, and `color`. The `resolution` parameter determines the level of detail, and the `top` -/// argument specifies the shape of the semi-sphere's tip. -pub(crate) fn draw_semi_sphere( - gizmos: &mut Gizmos<'_, '_, Config, Clear>, - radius: f32, - resolution: usize, - rotation: Quat, - center: Vec3, - top: Vec3, - color: Color, -) where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - circle_coordinates(radius, resolution) - .map(|p| Vec3::new(p.x, 0.0, p.y)) - .map(rotate_then_translate_3d(rotation, center)) - .for_each(|from| { - gizmos - .short_arc_3d_between(center, from, top, color) - .resolution(resolution / 2); - }); -} - /// Draws a circle in 3D space. /// /// # Note @@ -97,28 +70,3 @@ pub(crate) fn draw_circle_3d( .map(rotate_then_translate_3d(rotation, translation)); gizmos.linestrip(positions, color); } - -/// Draws the connecting lines of a cylinder between the top circle and the bottom circle. -pub(crate) fn draw_cylinder_vertical_lines( - gizmos: &mut Gizmos<'_, '_, Config, Clear>, - radius: f32, - resolution: usize, - half_height: f32, - rotation: Quat, - center: Vec3, - color: Color, -) where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - circle_coordinates(radius, resolution) - .map(move |point_2d| { - [1.0, -1.0] - .map(|sign| sign * half_height) - .map(|height| Vec3::new(point_2d.x, height, point_2d.y)) - }) - .map(|ps| ps.map(rotate_then_translate_3d(rotation, center))) - .for_each(|[start, end]| { - gizmos.line(start, end, color); - }); -} diff --git a/examples/math/render_primitives.rs b/examples/math/render_primitives.rs index 30ba73380e..e347cc33ff 100644 --- a/examples/math/render_primitives.rs +++ b/examples/math/render_primitives.rs @@ -452,8 +452,10 @@ fn draw_gizmos_2d(mut gizmos: Gizmos, state: Res>, time PrimitiveSelected::RectangleAndCuboid => { gizmos.primitive_2d(&RECTANGLE, POSITION, angle, color); } - PrimitiveSelected::CircleAndSphere => gizmos.primitive_2d(&CIRCLE, POSITION, angle, color), - PrimitiveSelected::Ellipse => gizmos.primitive_2d(&ELLIPSE, POSITION, angle, color), + PrimitiveSelected::CircleAndSphere => { + gizmos.primitive_2d(&CIRCLE, POSITION, angle, color); + } + PrimitiveSelected::Ellipse => drop(gizmos.primitive_2d(&ELLIPSE, POSITION, angle, color)), PrimitiveSelected::Triangle => gizmos.primitive_2d(&TRIANGLE_2D, POSITION, angle, color), PrimitiveSelected::Plane => gizmos.primitive_2d(&PLANE_2D, POSITION, angle, color), PrimitiveSelected::Line => drop(gizmos.primitive_2d(&LINE2D, POSITION, angle, color)), @@ -469,7 +471,7 @@ fn draw_gizmos_2d(mut gizmos: Gizmos, state: Res>, time PrimitiveSelected::Cylinder => {} PrimitiveSelected::Cone => {} PrimitiveSelected::ConicalFrustum => {} - PrimitiveSelected::Torus => gizmos.primitive_2d(&ANNULUS, POSITION, angle, color), + PrimitiveSelected::Torus => drop(gizmos.primitive_2d(&ANNULUS, POSITION, angle, color)), PrimitiveSelected::Tetrahedron => {} PrimitiveSelected::Arc => gizmos.primitive_2d(&ARC, POSITION, angle, color), PrimitiveSelected::CircularSector => {