From 8895113784e58d4f9c220732aba8571eab8b5db3 Mon Sep 17 00:00:00 2001 From: Robert Walter <26892280+RobWalt@users.noreply.github.com> Date: Wed, 28 Aug 2024 01:37:19 +0000 Subject: [PATCH] Use `Isometry` in `bevy_gizmos` wherever we can (#14676) # Objective - Solves the last bullet in and closes #14319 - Make better use of the `Isometry` types - Prevent issues like #14655 - Probably simplify and clean up a lot of code through the use of Gizmos as well (i.e. the 3D gizmos for cylinders circles & lines don't connect well, probably due to wrong rotations) ## Solution - go through the `bevy_gizmos` crate and give all methods a slight workover ## Testing - For all the changed examples I run `git switch main && cargo rr --example && git switch && cargo rr --example ` and compare the visual results - Check if all doc tests are still compiling - Check the docs in general and update them !!! --- ## Migration Guide The gizmos methods function signature changes as follows: - 2D - if it took `position` & `rotation_angle` before -> `Isometry2d::new(position, Rot2::radians(rotation_angle))` - if it just took `position` before -> `Isometry2d::from_translation(position)` - 3D - if it took `position` & `rotation` before -> `Isometry3d::new(position, rotation)` - if it just took `position` before -> `Isometry3d::from_translation(position)` --- crates/bevy_gizmos/src/arcs.rs | 32 +- crates/bevy_gizmos/src/circles.rs | 123 +++-- crates/bevy_gizmos/src/cross.rs | 62 ++- crates/bevy_gizmos/src/gizmos.rs | 35 +- crates/bevy_gizmos/src/grid.rs | 69 ++- crates/bevy_gizmos/src/light.rs | 20 +- crates/bevy_gizmos/src/primitives/dim2.rs | 186 +++---- crates/bevy_gizmos/src/primitives/dim3.rs | 514 +++++++------------ crates/bevy_gizmos/src/primitives/helpers.rs | 55 +- crates/bevy_gizmos/src/rounded_box.rs | 76 ++- examples/2d/2d_viewport_to_world.rs | 4 +- examples/2d/bounding_2d.rs | 44 +- examples/2d/mesh2d_arcs.rs | 12 +- examples/3d/3d_viewport_to_world.rs | 9 +- examples/ecs/observers.rs | 3 +- examples/gizmos/2d_gizmos.rs | 20 +- examples/gizmos/3d_gizmos.rs | 49 +- examples/math/cubic_splines.rs | 20 +- examples/math/custom_primitives.rs | 18 +- examples/math/render_primitives.rs | 61 +-- 20 files changed, 627 insertions(+), 785 deletions(-) diff --git a/crates/bevy_gizmos/src/arcs.rs b/crates/bevy_gizmos/src/arcs.rs index 1aa6b5c78a..819a87d5ca 100644 --- a/crates/bevy_gizmos/src/arcs.rs +++ b/crates/bevy_gizmos/src/arcs.rs @@ -6,7 +6,7 @@ use crate::circles::DEFAULT_CIRCLE_RESOLUTION; use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_color::Color; -use bevy_math::{Isometry2d, Quat, Vec2, Vec3}; +use bevy_math::{Isometry2d, Isometry3d, Quat, Vec2, Vec3}; use std::f32::consts::{FRAC_PI_2, TAU}; // === 2D === @@ -140,9 +140,9 @@ where /// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This /// value should be in the range (-2 * PI..=2 * PI) /// - `radius`: distance between the arc and its center point - /// - `position`: position of the arcs center point - /// - `rotation`: defines orientation of the arc, by default we assume the arc is contained in a - /// plane parallel to the XZ plane and the default starting point is (`position + Vec3::X`) + /// - `isometry` defines the translation and rotation of the arc. + /// - the translation specifies the center of the arc + /// - the rotation is counter-clockwise starting from `Vec3::Y` /// - `color`: color of the arc /// /// # Builder methods @@ -163,8 +163,7 @@ where /// .arc_3d( /// 270.0_f32.to_radians(), /// 0.25, - /// Vec3::ONE, - /// rotation, + /// Isometry3d::new(Vec3::ONE, rotation), /// ORANGE /// ) /// .resolution(100); @@ -176,15 +175,13 @@ where &mut self, angle: f32, radius: f32, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Arc3dBuilder<'_, 'w, 's, Config, Clear> { Arc3dBuilder { gizmos: self, start_vertex: Vec3::X, - center: position, - rotation, + isometry, angle, radius, color: color.into(), @@ -317,8 +314,7 @@ where Arc3dBuilder { gizmos: self, start_vertex, - center, - rotation, + isometry: Isometry3d::new(center, rotation), angle, radius, color: color.into(), @@ -344,8 +340,7 @@ where // // DO NOT expose this field to users as it is easy to mess this up start_vertex: Vec3, - center: Vec3, - rotation: Quat, + isometry: Isometry3d, angle: f32, radius: f32, color: Color, @@ -380,8 +375,7 @@ where let positions = arc_3d_inner( self.start_vertex, - self.center, - self.rotation, + self.isometry, self.angle, self.radius, resolution, @@ -392,8 +386,7 @@ where fn arc_3d_inner( start_vertex: Vec3, - center: Vec3, - rotation: Quat, + isometry: Isometry3d, angle: f32, radius: f32, resolution: u32, @@ -406,7 +399,8 @@ fn arc_3d_inner( .map(move |frac| frac as f32 / resolution as f32) .map(move |percentage| angle * percentage) .map(move |frac_angle| Quat::from_axis_angle(Vec3::Y, frac_angle) * start_vertex) - .map(move |p| rotation * (p * radius) + center) + .map(move |vec3| vec3 * radius) + .map(move |vec3| isometry * vec3) } // helper function for getting a default value for the resolution parameter diff --git a/crates/bevy_gizmos/src/circles.rs b/crates/bevy_gizmos/src/circles.rs index 1f1d2bac21..d819311e4d 100644 --- a/crates/bevy_gizmos/src/circles.rs +++ b/crates/bevy_gizmos/src/circles.rs @@ -5,8 +5,8 @@ use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_color::Color; -use bevy_math::Mat2; -use bevy_math::{Dir3, Quat, Vec2, Vec3}; +use bevy_math::{Isometry2d, Isometry3d}; +use bevy_math::{Quat, Vec2, Vec3}; use std::f32::consts::TAU; pub(crate) const DEFAULT_CIRCLE_RESOLUTION: u32 = 32; @@ -24,7 +24,12 @@ where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { - /// Draw an ellipse in 3D at `position` with the flat side facing `normal`. + /// Draw an ellipse in 3D with the given `isometry` applied. + /// + /// If `isometry == Isometry3d::IDENTITY` then + /// + /// - the center is at `Vec3::ZERO` + /// - the `half_sizes` are aligned with the `Vec3::X` and `Vec3::Y` axes. /// /// This should be called for each frame the ellipse needs to be rendered. /// @@ -34,12 +39,12 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::{RED, GREEN}; /// fn system(mut gizmos: Gizmos) { - /// gizmos.ellipse(Vec3::ZERO, Quat::IDENTITY, Vec2::new(1., 2.), GREEN); + /// gizmos.ellipse(Isometry3d::IDENTITY, Vec2::new(1., 2.), GREEN); /// /// // Ellipses have 32 line-segments by default. /// // You may want to increase this for larger ellipses. /// gizmos - /// .ellipse(Vec3::ZERO, Quat::IDENTITY, Vec2::new(5., 1.), RED) + /// .ellipse(Isometry3d::IDENTITY, Vec2::new(5., 1.), RED) /// .resolution(64); /// } /// # bevy_ecs::system::assert_is_system(system); @@ -47,22 +52,25 @@ where #[inline] pub fn ellipse( &mut self, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, half_size: Vec2, color: impl Into, ) -> EllipseBuilder<'_, 'w, 's, Config, Clear> { EllipseBuilder { gizmos: self, - position, - rotation, + isometry, half_size, color: color.into(), resolution: DEFAULT_CIRCLE_RESOLUTION, } } - /// Draw an ellipse in 2D. + /// Draw an ellipse in 2D with the given `isometry` applied. + /// + /// If `isometry == Isometry2d::IDENTITY` then + /// + /// - the center is at `Vec2::ZERO` + /// - the `half_sizes` are aligned with the `Vec2::X` and `Vec2::Y` axes. /// /// This should be called for each frame the ellipse needs to be rendered. /// @@ -72,12 +80,12 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::{RED, GREEN}; /// fn system(mut gizmos: Gizmos) { - /// gizmos.ellipse_2d(Vec2::ZERO, 180.0_f32.to_radians(), Vec2::new(2., 1.), GREEN); + /// gizmos.ellipse_2d(Isometry2d::from_rotation(Rot2::degrees(180.0)), Vec2::new(2., 1.), GREEN); /// /// // Ellipses have 32 line-segments by default. /// // You may want to increase this for larger ellipses. /// gizmos - /// .ellipse_2d(Vec2::ZERO, 180.0_f32.to_radians(), Vec2::new(5., 1.), RED) + /// .ellipse_2d(Isometry2d::from_rotation(Rot2::degrees(180.0)), Vec2::new(5., 1.), RED) /// .resolution(64); /// } /// # bevy_ecs::system::assert_is_system(system); @@ -85,24 +93,25 @@ where #[inline] pub fn ellipse_2d( &mut self, - position: Vec2, - angle: f32, + isometry: Isometry2d, half_size: Vec2, color: impl Into, ) -> Ellipse2dBuilder<'_, 'w, 's, Config, Clear> { Ellipse2dBuilder { gizmos: self, - position, - rotation: Mat2::from_angle(angle), + isometry, half_size, color: color.into(), resolution: DEFAULT_CIRCLE_RESOLUTION, } } - /// Draw a circle in 3D at `position` with the flat side facing `normal`. + /// Draw a circle in 3D with the given `isometry` applied. /// - /// This should be called for each frame the circle needs to be rendered. + /// If `isometry == Isometry3d::IDENTITY` then + /// + /// - the center is at `Vec3::ZERO` + /// - the radius is aligned with the `Vec3::X` and `Vec3::Y` axes. /// /// # Example /// ``` @@ -110,12 +119,12 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::{RED, GREEN}; /// fn system(mut gizmos: Gizmos) { - /// gizmos.circle(Vec3::ZERO, Dir3::Z, 1., GREEN); + /// gizmos.circle(Isometry3d::IDENTITY, 1., GREEN); /// /// // Circles have 32 line-segments by default. /// // You may want to increase this for larger circles. /// gizmos - /// .circle(Vec3::ZERO, Dir3::Z, 5., RED) + /// .circle(Isometry3d::IDENTITY, 5., RED) /// .resolution(64); /// } /// # bevy_ecs::system::assert_is_system(system); @@ -123,22 +132,25 @@ where #[inline] pub fn circle( &mut self, - position: Vec3, - normal: Dir3, + isometry: Isometry3d, radius: f32, color: impl Into, ) -> EllipseBuilder<'_, 'w, 's, Config, Clear> { EllipseBuilder { gizmos: self, - position, - rotation: Quat::from_rotation_arc(Vec3::Z, *normal), + isometry, half_size: Vec2::splat(radius), color: color.into(), resolution: DEFAULT_CIRCLE_RESOLUTION, } } - /// Draw a circle in 2D. + /// Draw a circle in 2D with the given `isometry` applied. + /// + /// If `isometry == Isometry2d::IDENTITY` then + /// + /// - the center is at `Vec2::ZERO` + /// - the radius is aligned with the `Vec2::X` and `Vec2::Y` axes. /// /// This should be called for each frame the circle needs to be rendered. /// @@ -148,12 +160,12 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::{RED, GREEN}; /// fn system(mut gizmos: Gizmos) { - /// gizmos.circle_2d(Vec2::ZERO, 1., GREEN); + /// gizmos.circle_2d(Isometry2d::IDENTITY, 1., GREEN); /// /// // Circles have 32 line-segments by default. /// // You may want to increase this for larger circles. /// gizmos - /// .circle_2d(Vec2::ZERO, 5., RED) + /// .circle_2d(Isometry2d::IDENTITY, 5., RED) /// .resolution(64); /// } /// # bevy_ecs::system::assert_is_system(system); @@ -161,21 +173,26 @@ where #[inline] pub fn circle_2d( &mut self, - position: Vec2, + isometry: Isometry2d, radius: f32, color: impl Into, ) -> Ellipse2dBuilder<'_, 'w, 's, Config, Clear> { Ellipse2dBuilder { gizmos: self, - position, - rotation: Mat2::IDENTITY, + isometry, half_size: Vec2::splat(radius), color: color.into(), resolution: DEFAULT_CIRCLE_RESOLUTION, } } - /// Draw a wireframe sphere in 3D made out of 3 circles around the axes. + /// Draw a wireframe sphere in 3D made out of 3 circles around the axes with the given + /// `isometry` applied. + /// + /// If `isometry == Isometry3d::IDENTITY` then + /// + /// - the center is at `Vec3::ZERO` + /// - the 3 circles are in the XY, YZ and XZ planes. /// /// This should be called for each frame the sphere needs to be rendered. /// @@ -185,12 +202,12 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::Color; /// fn system(mut gizmos: Gizmos) { - /// gizmos.sphere(Vec3::ZERO, Quat::IDENTITY, 1., Color::BLACK); + /// gizmos.sphere(Isometry3d::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) + /// .sphere(Isometry3d::IDENTITY, 5., Color::BLACK) /// .resolution(64); /// } /// # bevy_ecs::system::assert_is_system(system); @@ -198,16 +215,14 @@ where #[inline] pub fn sphere( &mut self, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, radius: f32, color: impl Into, ) -> SphereBuilder<'_, 'w, 's, Config, Clear> { SphereBuilder { gizmos: self, radius, - position, - rotation: rotation.normalize(), + isometry, color: color.into(), resolution: DEFAULT_CIRCLE_RESOLUTION, } @@ -221,8 +236,7 @@ where Clear: 'static + Send + Sync, { gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, half_size: Vec2, color: Color, resolution: u32, @@ -251,8 +265,7 @@ where } let positions = ellipse_inner(self.half_size, self.resolution) - .map(|vec2| self.rotation * vec2.extend(0.)) - .map(|vec3| vec3 + self.position); + .map(|vec2| self.isometry * vec2.extend(0.)); self.gizmos.linestrip(positions, self.color); } } @@ -264,8 +277,7 @@ where Clear: 'static + Send + Sync, { gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - position: Vec2, - rotation: Mat2, + isometry: Isometry2d, half_size: Vec2, color: Color, resolution: u32, @@ -294,9 +306,8 @@ where return; }; - let positions = ellipse_inner(self.half_size, self.resolution) - .map(|vec2| self.rotation * vec2) - .map(|vec2| vec2 + self.position); + let positions = + ellipse_inner(self.half_size, self.resolution).map(|vec2| self.isometry * vec2); self.gizmos.linestrip_2d(positions, self.color); } } @@ -312,10 +323,7 @@ where // 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, + isometry: Isometry3d, // Color of the sphere color: Color, @@ -345,21 +353,12 @@ where 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; + let axis_rotation = Isometry3d::from_rotation(Quat::from_rotation_arc(Vec3::Z, axis)); self.gizmos - .circle(*center, Dir3::new_unchecked(normal), *radius, *color) - .resolution(*resolution); + .circle(self.isometry * axis_rotation, self.radius, self.color) + .resolution(self.resolution); }); } } diff --git a/crates/bevy_gizmos/src/cross.rs b/crates/bevy_gizmos/src/cross.rs index 282f6c3dfd..710aa71e9a 100644 --- a/crates/bevy_gizmos/src/cross.rs +++ b/crates/bevy_gizmos/src/cross.rs @@ -5,13 +5,18 @@ use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_color::Color; -use bevy_math::{Mat2, Mat3, Quat, Vec2, Vec3}; +use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3}; impl Gizmos<'_, '_, Config> where Config: GizmoConfigGroup, { - /// Draw a cross in 3D at `position`. + /// Draw a cross in 3D with the given `isometry` applied. + /// + /// If `isometry == Isometry3d::IDENTITY` then + /// + /// - the center is at `Vec3::ZERO` + /// - the `half_size`s are aligned with the `Vec3::X`, `Vec3::Y` and `Vec3::Z` axes. /// /// This should be called for each frame the cross needs to be rendered. /// @@ -21,29 +26,26 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::WHITE; /// fn system(mut gizmos: Gizmos) { - /// gizmos.cross(Vec3::ZERO, Quat::IDENTITY, 0.5, WHITE); + /// gizmos.cross(Isometry3d::IDENTITY, 0.5, WHITE); /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` - pub fn cross( - &mut self, - position: Vec3, - rotation: Quat, - half_size: f32, - color: impl Into, - ) { - let axes = half_size * Mat3::from_quat(rotation); - let local_x = axes.col(0); - let local_y = axes.col(1); - let local_z = axes.col(2); - + pub fn cross(&mut self, isometry: Isometry3d, half_size: f32, color: impl Into) { let color: Color = color.into(); - self.line(position + local_x, position - local_x, color); - self.line(position + local_y, position - local_y, color); - self.line(position + local_z, position - local_z, color); + [Vec3::X, Vec3::Y, Vec3::Z] + .map(|axis| axis * half_size) + .into_iter() + .for_each(|axis| { + self.line(isometry * axis, isometry * (-axis), color); + }); } - /// Draw a cross in 2D (on the xy plane) at `position`. + /// Draw a cross in 2D with the given `isometry` applied. + /// + /// If `isometry == Isometry2d::IDENTITY` then + /// + /// - the center is at `Vec3::ZERO` + /// - the `half_size`s are aligned with the `Vec3::X` and `Vec3::Y` axes. /// /// This should be called for each frame the cross needs to be rendered. /// @@ -53,23 +55,17 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::WHITE; /// fn system(mut gizmos: Gizmos) { - /// gizmos.cross_2d(Vec2::ZERO, 0.0, 0.5, WHITE); + /// gizmos.cross_2d(Isometry2d::IDENTITY, 0.5, WHITE); /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` - pub fn cross_2d( - &mut self, - position: Vec2, - angle: f32, - half_size: f32, - color: impl Into, - ) { - let axes = half_size * Mat2::from_angle(angle); - let local_x = axes.col(0); - let local_y = axes.col(1); - + pub fn cross_2d(&mut self, isometry: Isometry2d, half_size: f32, color: impl Into) { let color: Color = color.into(); - self.line_2d(position + local_x, position - local_x, color); - self.line_2d(position + local_y, position - local_y, color); + [Vec2::X, Vec2::Y] + .map(|axis| axis * half_size) + .into_iter() + .for_each(|axis| { + self.line_2d(isometry * axis, isometry * (-axis), color); + }); } } diff --git a/crates/bevy_gizmos/src/gizmos.rs b/crates/bevy_gizmos/src/gizmos.rs index bd32ba8f1f..50998cc6d8 100644 --- a/crates/bevy_gizmos/src/gizmos.rs +++ b/crates/bevy_gizmos/src/gizmos.rs @@ -8,7 +8,7 @@ use bevy_ecs::{ system::{Deferred, ReadOnlySystemParam, Res, Resource, SystemBuffer, SystemMeta, SystemParam}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; -use bevy_math::{Quat, Rot2, Vec2, Vec3}; +use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3}; use bevy_transform::TransformPoint; use bevy_utils::default; @@ -452,7 +452,12 @@ where strip_colors.push(LinearRgba::NAN); } - /// Draw a wireframe rectangle in 3D. + /// Draw a wireframe rectangle in 3D with the given `isometry` applied. + /// + /// If `isometry == Isometry3d::IDENTITY` then + /// + /// - the center is at `Vec3::ZERO` + /// - the sizes are aligned with the `Vec3::X` and `Vec3::Y` axes. /// /// This should be called for each frame the rectangle needs to be rendered. /// @@ -462,16 +467,16 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::GREEN; /// fn system(mut gizmos: Gizmos) { - /// gizmos.rect(Vec3::ZERO, Quat::IDENTITY, Vec2::ONE, GREEN); + /// gizmos.rect(Isometry3d::IDENTITY, Vec2::ONE, GREEN); /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` #[inline] - pub fn rect(&mut self, position: Vec3, rotation: Quat, size: Vec2, color: impl Into) { + pub fn rect(&mut self, isometry: Isometry3d, size: Vec2, color: impl Into) { if !self.enabled { return; } - let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2.extend(0.)); + let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * vec2.extend(0.)); self.linestrip([tl, tr, br, bl, tl], color); } @@ -674,7 +679,12 @@ where self.line_gradient_2d(start, start + vector, start_color, end_color); } - /// Draw a wireframe rectangle in 2D. + /// Draw a wireframe rectangle in 2D with the given `isometry` applied. + /// + /// If `isometry == Isometry2d::IDENTITY` then + /// + /// - the center is at `Vec2::ZERO` + /// - the sizes are aligned with the `Vec2::X` and `Vec2::Y` axes. /// /// This should be called for each frame the rectangle needs to be rendered. /// @@ -684,23 +694,16 @@ where /// # use bevy_math::prelude::*; /// # use bevy_color::palettes::basic::GREEN; /// fn system(mut gizmos: Gizmos) { - /// gizmos.rect_2d(Vec2::ZERO, 0., Vec2::ONE, GREEN); + /// gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::ONE, GREEN); /// } /// # bevy_ecs::system::assert_is_system(system); /// ``` #[inline] - pub fn rect_2d( - &mut self, - position: Vec2, - rotation: impl Into, - size: Vec2, - color: impl Into, - ) { + pub fn rect_2d(&mut self, isometry: Isometry2d, size: Vec2, color: impl Into) { if !self.enabled { return; } - let rotation: Rot2 = rotation.into(); - let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2); + let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * vec2); self.linestrip_2d([tl, tr, br, bl, tl], color); } diff --git a/crates/bevy_gizmos/src/grid.rs b/crates/bevy_gizmos/src/grid.rs index b65137205b..05b04c0376 100644 --- a/crates/bevy_gizmos/src/grid.rs +++ b/crates/bevy_gizmos/src/grid.rs @@ -5,7 +5,8 @@ use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_color::Color; -use bevy_math::{Quat, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles}; +use bevy_math::Vec3Swizzles; +use bevy_math::{Isometry2d, Isometry3d, Quat, UVec2, UVec3, Vec2, Vec3}; /// A builder returned by [`Gizmos::grid_3d`] pub struct GridBuilder3d<'a, 'w, 's, Config, Clear> @@ -14,8 +15,7 @@ where Clear: 'static + Send + Sync, { gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, spacing: Vec3, cell_count: UVec3, skew: Vec3, @@ -29,8 +29,7 @@ where Clear: 'static + Send + Sync, { gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, spacing: Vec2, cell_count: UVec2, skew: Vec2, @@ -147,8 +146,7 @@ where fn drop(&mut self) { draw_grid( self.gizmos, - self.position, - self.rotation, + self.isometry, self.spacing, self.cell_count, self.skew, @@ -166,8 +164,7 @@ where fn drop(&mut self) { draw_grid( self.gizmos, - self.position, - self.rotation, + self.isometry, self.spacing.extend(0.), self.cell_count.extend(0), self.skew.extend(0.), @@ -187,8 +184,11 @@ where /// /// # Arguments /// - /// - `position`: The center point of the grid. - /// - `rotation`: defines the orientation of the grid, by default we assume the grid is contained in a plane parallel to the XY plane. + /// - `isometry` defines the translation and rotation of the grid. + /// - the translation specifies the center of the grid + /// - defines the orientation of the grid, by default + /// we assume the grid is contained in a plane parallel + /// to the XY plane /// - `cell_count`: defines the amount of cells in the x and y axes /// - `spacing`: defines the distance between cells along the x and y axes /// - `color`: color of the grid @@ -205,8 +205,7 @@ where /// # use bevy_color::palettes::basic::GREEN; /// fn system(mut gizmos: Gizmos) { /// gizmos.grid( - /// Vec3::ZERO, - /// Quat::IDENTITY, + /// Isometry3d::IDENTITY, /// UVec2::new(10, 10), /// Vec2::splat(2.), /// GREEN @@ -218,16 +217,14 @@ where /// ``` pub fn grid( &mut self, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, cell_count: UVec2, spacing: Vec2, color: impl Into, ) -> GridBuilder2d<'_, 'w, 's, Config, Clear> { GridBuilder2d { gizmos: self, - position, - rotation, + isometry, spacing, cell_count, skew: Vec2::ZERO, @@ -242,8 +239,10 @@ where /// /// # Arguments /// - /// - `position`: The center point of the grid. - /// - `rotation`: defines the orientation of the grid, by default we assume the grid is contained in a plane parallel to the XY plane. + /// - `isometry` defines the translation and rotation of the grid. + /// - the translation specifies the center of the grid + /// - defines the orientation of the grid, by default + /// we assume the grid is aligned with all axes /// - `cell_count`: defines the amount of cells in the x, y and z axes /// - `spacing`: defines the distance between cells along the x, y and z axes /// - `color`: color of the grid @@ -260,8 +259,7 @@ where /// # use bevy_color::palettes::basic::GREEN; /// fn system(mut gizmos: Gizmos) { /// gizmos.grid_3d( - /// Vec3::ZERO, - /// Quat::IDENTITY, + /// Isometry3d::IDENTITY, /// UVec3::new(10, 2, 10), /// Vec3::splat(2.), /// GREEN @@ -273,16 +271,14 @@ where /// ``` pub fn grid_3d( &mut self, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, cell_count: UVec3, spacing: Vec3, color: impl Into, ) -> GridBuilder3d<'_, 'w, 's, Config, Clear> { GridBuilder3d { gizmos: self, - position, - rotation, + isometry, spacing, cell_count, skew: Vec3::ZERO, @@ -297,8 +293,10 @@ where /// /// # Arguments /// - /// - `position`: The center point of the grid. - /// - `rotation`: defines the orientation of the grid. + /// - `isometry` defines the translation and rotation of the grid. + /// - the translation specifies the center of the grid + /// - defines the orientation of the grid, by default + /// we assume the grid is aligned with all axes /// - `cell_count`: defines the amount of cells in the x and y axes /// - `spacing`: defines the distance between cells along the x and y axes /// - `color`: color of the grid @@ -315,8 +313,7 @@ where /// # use bevy_color::palettes::basic::GREEN; /// fn system(mut gizmos: Gizmos) { /// gizmos.grid_2d( - /// Vec2::ZERO, - /// 0.0, + /// Isometry2d::IDENTITY, /// UVec2::new(10, 10), /// Vec2::splat(1.), /// GREEN @@ -328,16 +325,17 @@ where /// ``` pub fn grid_2d( &mut self, - position: Vec2, - rotation: f32, + isometry: Isometry2d, cell_count: UVec2, spacing: Vec2, color: impl Into, ) -> GridBuilder2d<'_, 'w, 's, Config, Clear> { GridBuilder2d { gizmos: self, - position: position.extend(0.), - rotation: Quat::from_rotation_z(rotation), + isometry: Isometry3d::new( + isometry.translation.extend(0.0), + Quat::from_rotation_z(isometry.rotation.as_radians()), + ), spacing, cell_count, skew: Vec2::ZERO, @@ -350,8 +348,7 @@ where #[allow(clippy::too_many_arguments)] fn draw_grid( gizmos: &mut Gizmos<'_, '_, Config, Clear>, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, spacing: Vec3, cell_count: UVec3, skew: Vec3, @@ -428,7 +425,7 @@ fn draw_grid( x_lines .chain(y_lines) .chain(z_lines) - .map(|ps| ps.map(|p| position + rotation * p)) + .map(|vec3s| vec3s.map(|vec3| isometry * vec3)) .for_each(|[start, end]| { gizmos.line(start, end, color); }); diff --git a/crates/bevy_gizmos/src/light.rs b/crates/bevy_gizmos/src/light.rs index 343f0bf33e..6155b9e6a0 100644 --- a/crates/bevy_gizmos/src/light.rs +++ b/crates/bevy_gizmos/src/light.rs @@ -19,7 +19,7 @@ use bevy_ecs::{ }; use bevy_math::{ primitives::{Cone, Sphere}, - Quat, Vec3, + Isometry3d, Quat, Vec3, }; use bevy_pbr::{DirectionalLight, PointLight, SpotLight}; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; @@ -44,13 +44,16 @@ fn point_light_gizmo( &Sphere { radius: point_light.radius, }, - position, - Quat::IDENTITY, + Isometry3d::from_translation(position), color, ) .resolution(16); gizmos - .sphere(position, Quat::IDENTITY, point_light.range, color) + .sphere( + Isometry3d::from_translation(position), + point_light.range, + color, + ) .resolution(32); } @@ -68,8 +71,7 @@ fn spot_light_gizmo( &Sphere { radius: spot_light.radius, }, - translation, - Quat::IDENTITY, + Isometry3d::from_translation(translation), color, ) .resolution(16); @@ -84,8 +86,7 @@ fn spot_light_gizmo( radius: spot_light.range * angle.sin(), height, }, - position, - rotation * Quat::from_rotation_x(PI / 2.0), + Isometry3d::new(position, rotation * Quat::from_rotation_x(PI / 2.0)), color, ) .height_resolution(4) @@ -105,8 +106,7 @@ fn spot_light_gizmo( .arc_3d( 2.0 * spot_light.outer_angle, spot_light.range, - translation, - rotation * arc_rotation, + Isometry3d::new(translation, rotation * arc_rotation), color, ) .resolution(16); diff --git a/crates/bevy_gizmos/src/primitives/dim2.rs b/crates/bevy_gizmos/src/primitives/dim2.rs index f57904bb80..7d0d3850a1 100644 --- a/crates/bevy_gizmos/src/primitives/dim2.rs +++ b/crates/bevy_gizmos/src/primitives/dim2.rs @@ -10,7 +10,7 @@ use bevy_math::primitives::{ CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Primitive2d, Rectangle, RegularPolygon, Rhombus, Segment2d, Triangle2d, }; -use bevy_math::{Dir2, Isometry2d, Mat2, Rot2, Vec2}; +use bevy_math::{Dir2, Isometry2d, Rot2, Vec2}; use crate::prelude::{GizmoConfigGroup, Gizmos}; @@ -31,8 +31,7 @@ pub trait GizmoPrimitive2d { fn primitive_2d( &mut self, primitive: &P, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_>; } @@ -49,19 +48,15 @@ where fn primitive_2d( &mut self, primitive: &Dir2, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { return; } - - let direction = Mat2::from_angle(angle) * **primitive; - - let start = position; - let end = position + MIN_LINE_LEN * direction; - self.arrow_2d(start, end, color); + let start = Vec2::ZERO; + let end = *primitive * MIN_LINE_LEN; + self.arrow_2d(isometry * start, isometry * end, color); } } @@ -77,16 +72,17 @@ where fn primitive_2d( &mut self, primitive: &Arc2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { return; } + let start_iso = isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.half_angle)); + self.arc_2d( - Isometry2d::new(position, Rot2::radians(angle - primitive.half_angle)), + start_iso, primitive.half_angle * 2.0, primitive.radius, color, @@ -106,11 +102,10 @@ where fn primitive_2d( &mut self, primitive: &Circle, - position: Vec2, - _angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { - self.circle_2d(position, primitive.radius, color) + self.circle_2d(isometry, primitive.radius, color) } } @@ -126,8 +121,7 @@ where fn primitive_2d( &mut self, primitive: &CircularSector, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -136,20 +130,21 @@ where let color = color.into(); + let start_iso = + isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle)); + let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle)); + // we need to draw the arc part of the sector, and the two lines connecting the arc and the center self.arc_2d( - Isometry2d::new(position, Rot2::radians(angle - primitive.arc.half_angle)), + start_iso, primitive.arc.half_angle * 2.0, primitive.arc.radius, color, ); - let start = position - + primitive.arc.radius * Mat2::from_angle(angle - primitive.arc.half_angle) * Vec2::Y; - let end = position - + primitive.arc.radius * Mat2::from_angle(angle + primitive.arc.half_angle) * Vec2::Y; - self.line_2d(position, start, color); - self.line_2d(position, end, color); + let end_position = primitive.arc.radius * Vec2::Y; + self.line_2d(isometry * Vec2::ZERO, start_iso * end_position, color); + self.line_2d(isometry * Vec2::ZERO, end_iso * end_position, color); } } @@ -165,8 +160,7 @@ where fn primitive_2d( &mut self, primitive: &CircularSegment, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -175,19 +169,20 @@ where let color = color.into(); + let start_iso = + isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.arc.half_angle)); + let end_iso = isometry * Isometry2d::from_rotation(Rot2::radians(primitive.arc.half_angle)); + // we need to draw the arc part of the segment, and the line connecting the two ends self.arc_2d( - Isometry2d::new(position, Rot2::radians(angle - primitive.arc.half_angle)), + start_iso, primitive.arc.half_angle * 2.0, primitive.arc.radius, color, ); - let start = position - + primitive.arc.radius * Mat2::from_angle(angle - primitive.arc.half_angle) * Vec2::Y; - let end = position - + primitive.arc.radius * Mat2::from_angle(angle + primitive.arc.half_angle) * Vec2::Y; - self.line_2d(end, start, color); + let position = primitive.arc.radius * Vec2::Y; + self.line_2d(start_iso * position, end_iso * position, color); } } @@ -203,11 +198,10 @@ where fn primitive_2d<'a>( &mut self, primitive: &Ellipse, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { - self.ellipse_2d(position, angle, primitive.half_size, color) + self.ellipse_2d(isometry, primitive.half_size, color) } } @@ -220,7 +214,7 @@ where Clear: 'static + Send + Sync, { gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, - position: Vec2, + isometry: Isometry2d, inner_radius: f32, outer_radius: f32, color: Color, @@ -263,13 +257,12 @@ where fn primitive_2d( &mut self, primitive: &Annulus, - position: Vec2, - _angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { Annulus2dBuilder { gizmos: self, - position, + isometry, inner_radius: primitive.inner_circle.radius, outer_radius: primitive.outer_circle.radius, color: color.into(), @@ -291,7 +284,7 @@ where let Annulus2dBuilder { gizmos, - position, + isometry, inner_radius, outer_radius, inner_resolution, @@ -301,10 +294,10 @@ where } = self; gizmos - .circle_2d(*position, *outer_radius, *color) + .circle_2d(*isometry, *outer_radius, *color) .resolution(*outer_resolution); gizmos - .circle_2d(*position, *inner_radius, *color) + .circle_2d(*isometry, *inner_radius, *color) .resolution(*inner_resolution); } } @@ -321,8 +314,7 @@ where fn primitive_2d( &mut self, primitive: &Rhombus, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -335,7 +327,7 @@ where primitive.half_diagonals.y * sign_y, ) }); - let positions = [a, b, c, d, a].map(rotate_then_translate_2d(angle, position)); + let positions = [a, b, c, d, a].map(|vec2| isometry * vec2); self.linestrip_2d(positions, color); } } @@ -352,8 +344,7 @@ where fn primitive_2d( &mut self, primitive: &Capsule2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { let polymorphic_color: Color = color.into(); @@ -377,14 +368,14 @@ where let scaling = Vec2::X * primitive.radius + Vec2::Y * primitive.half_length; reference_point * scaling }) - .map(rotate_then_translate_2d(angle, position)); + .map(|vec2| isometry * vec2); // draw left and right side of capsule "rectangle" self.line_2d(bottom_left, top_left, polymorphic_color); self.line_2d(bottom_right, top_right, polymorphic_color); - let start_angle_top = angle - FRAC_PI_2; - let start_angle_bottom = angle + FRAC_PI_2; + let start_angle_top = isometry.rotation.as_radians() - FRAC_PI_2; + let start_angle_bottom = isometry.rotation.as_radians() + FRAC_PI_2; // draw arcs self.arc_2d( @@ -414,9 +405,8 @@ where direction: Dir2, // Direction of the line - position: Vec2, // position of the center of the line - rotation: Mat2, // rotation of the line - color: Color, // color of the line + isometry: Isometry2d, + color: Color, // color of the line draw_arrow: bool, // decides whether to indicate the direction of the line with an arrow } @@ -443,15 +433,13 @@ where fn primitive_2d( &mut self, primitive: &Line2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { Line2dBuilder { gizmos: self, direction: primitive.direction, - position, - rotation: Mat2::from_angle(angle), + isometry, color: color.into(), draw_arrow: false, } @@ -468,22 +456,20 @@ where return; } - let direction = self.rotation * *self.direction; - let [start, end] = [1.0, -1.0] .map(|sign| sign * INFINITE_LEN) // offset the line from the origin infinitely into the given direction - .map(|length| direction * length) - // translate the line to the given position - .map(|offset| self.position + offset); + .map(|length| self.direction * length) + // transform the line with the given isometry + .map(|offset| self.isometry * offset); self.gizmos.line_2d(start, end, self.color); // optionally draw an arrow head at the center of the line if self.draw_arrow { self.gizmos.arrow_2d( - self.position - direction * MIN_LINE_LEN, - self.position, + self.isometry * (-self.direction * MIN_LINE_LEN), + self.isometry * Vec2::ZERO, self.color, ); } @@ -502,8 +488,7 @@ where fn primitive_2d( &mut self, primitive: &Plane2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { let polymorphic_color: Color = color.into(); @@ -511,8 +496,6 @@ where if !self.enabled { return; } - let rotation = Mat2::from_angle(angle); - // draw normal of the plane (orthogonal to the plane itself) let normal = primitive.normal; let normal_segment = Segment2d { @@ -522,22 +505,21 @@ where self.primitive_2d( &normal_segment, // offset the normal so it starts on the plane line - position + HALF_MIN_LINE_LEN * rotation * *normal, - angle, + Isometry2d::new(isometry * (HALF_MIN_LINE_LEN * normal), isometry.rotation), polymorphic_color, ) .draw_arrow(true); // draw the plane line let direction = Dir2::new_unchecked(-normal.perp()); - self.primitive_2d(&Line2d { direction }, position, angle, polymorphic_color) + self.primitive_2d(&Line2d { direction }, isometry, polymorphic_color) .draw_arrow(false); // draw an arrow such that the normal is always left side of the plane with respect to the // planes direction. This is to follow the "counter-clockwise" convention self.arrow_2d( - position, - position + MIN_LINE_LEN * (rotation * *direction), + isometry * Vec2::ZERO, + isometry * (MIN_LINE_LEN * direction), polymorphic_color, ); } @@ -556,9 +538,8 @@ where direction: Dir2, // Direction of the line segment half_length: f32, // Half-length of the line segment - position: Vec2, // position of the center of the line segment - rotation: Mat2, // rotation of the line segment - color: Color, // color of the line segment + isometry: Isometry2d, // isometric transformation of the line segment + color: Color, // color of the line segment draw_arrow: bool, // decides whether to draw just a line or an arrow } @@ -585,8 +566,7 @@ where fn primitive_2d( &mut self, primitive: &Segment2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { Segment2dBuilder { @@ -594,8 +574,7 @@ where direction: primitive.direction, half_length: primitive.half_length, - position, - rotation: Mat2::from_angle(angle), + isometry, color: color.into(), draw_arrow: Default::default(), @@ -613,9 +592,9 @@ where return; } - let direction = self.rotation * *self.direction; - let start = self.position - direction * self.half_length; - let end = self.position + direction * self.half_length; + let direction = self.direction * self.half_length; + let start = self.isometry * (-direction); + let end = self.isometry * direction; if self.draw_arrow { self.gizmos.arrow_2d(start, end, self.color); @@ -638,8 +617,7 @@ where fn primitive_2d( &mut self, primitive: &Polyline2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -651,7 +629,7 @@ where .vertices .iter() .copied() - .map(rotate_then_translate_2d(angle, position)), + .map(|vec2| isometry * vec2), color, ); } @@ -669,8 +647,7 @@ where fn primitive_2d( &mut self, primitive: &BoxedPolyline2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -682,7 +659,7 @@ where .vertices .iter() .copied() - .map(rotate_then_translate_2d(angle, position)), + .map(|vec2| isometry * vec2), color, ); } @@ -700,15 +677,14 @@ where fn primitive_2d( &mut self, primitive: &Triangle2d, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { return; } let [a, b, c] = primitive.vertices; - let positions = [a, b, c, a].map(rotate_then_translate_2d(angle, position)); + let positions = [a, b, c, a].map(|vec2| isometry * vec2); self.linestrip_2d(positions, color); } } @@ -725,8 +701,7 @@ where fn primitive_2d( &mut self, primitive: &Rectangle, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -740,7 +715,7 @@ where primitive.half_size.y * sign_y, ) }); - let positions = [a, b, c, d, a].map(rotate_then_translate_2d(angle, position)); + let positions = [a, b, c, d, a].map(|vec2| isometry * vec2); self.linestrip_2d(positions, color); } } @@ -758,8 +733,7 @@ where fn primitive_2d( &mut self, primitive: &Polygon, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -781,7 +755,7 @@ where .iter() .copied() .chain(closing_point) - .map(rotate_then_translate_2d(angle, position)), + .map(|vec2| isometry * vec2), color, ); } @@ -799,8 +773,7 @@ where fn primitive_2d( &mut self, primitive: &BoxedPolygon, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -820,7 +793,7 @@ where .iter() .copied() .chain(closing_point) - .map(rotate_then_translate_2d(angle, position)), + .map(|vec2| isometry * vec2), color, ); } @@ -838,8 +811,7 @@ where fn primitive_2d( &mut self, primitive: &RegularPolygon, - position: Vec2, - angle: f32, + isometry: Isometry2d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -847,8 +819,8 @@ where } let points = (0..=primitive.sides) - .map(|p| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, p)) - .map(rotate_then_translate_2d(angle, position)); + .map(|n| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, n)) + .map(|vec2| isometry * vec2); self.linestrip_2d(points, color); } } diff --git a/crates/bevy_gizmos/src/primitives/dim3.rs b/crates/bevy_gizmos/src/primitives/dim3.rs index 0839da864a..e2da1a1158 100644 --- a/crates/bevy_gizmos/src/primitives/dim3.rs +++ b/crates/bevy_gizmos/src/primitives/dim3.rs @@ -1,14 +1,14 @@ //! A module for rendering each of the 3D [`bevy_math::primitives`] with [`Gizmos`]. use super::helpers::*; -use std::f32::consts::{FRAC_PI_2, PI, TAU}; +use std::f32::consts::TAU; use bevy_color::Color; use bevy_math::primitives::{ BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d, Primitive3d, Segment3d, Sphere, Tetrahedron, Torus, Triangle3d, }; -use bevy_math::{Dir3, Quat, Vec3}; +use bevy_math::{Dir3, Isometry3d, Quat, Vec3}; use crate::circles::SphereBuilder; use crate::prelude::{GizmoConfigGroup, Gizmos}; @@ -28,8 +28,7 @@ pub trait GizmoPrimitive3d { fn primitive_3d( &mut self, primitive: &P, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_>; } @@ -46,11 +45,12 @@ where fn primitive_3d( &mut self, primitive: &Dir3, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { - self.arrow(position, position + (rotation * **primitive), color); + let start = Vec3::ZERO; + let end = primitive.as_vec3(); + self.arrow(isometry * start, isometry * end, color); } } @@ -66,11 +66,10 @@ where fn primitive_3d( &mut self, primitive: &Sphere, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { - self.sphere(position, rotation, primitive.radius, color) + self.sphere(isometry, primitive.radius, color) } } @@ -87,10 +86,7 @@ where // direction of the normal orthogonal to the plane normal: Dir3, - // Rotation of the plane around the origin in 3D space - rotation: Quat, - // Center position of the plane in 3D space - position: Vec3, + isometry: Isometry3d, // Color of the plane color: Color, @@ -136,15 +132,13 @@ where fn primitive_3d( &mut self, primitive: &Plane3d, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { Plane3dBuilder { gizmos: self, normal: primitive.normal, - rotation, - position, + isometry, color: color.into(), axis_count: 4, segment_count: 3, @@ -164,37 +158,33 @@ where } // draws the normal - let normal = self.rotation * *self.normal; self.gizmos - .primitive_3d(&self.normal, self.position, self.rotation, self.color); - let normals_normal = self.rotation * self.normal.any_orthonormal_vector(); + .primitive_3d(&self.normal, self.isometry, self.color); // draws the axes // get rotation for each direction + let normals_normal = self.normal.any_orthonormal_vector(); (0..self.axis_count) .map(|i| i as f32 * (1.0 / self.axis_count as f32) * TAU) - .map(|angle| Quat::from_axis_angle(normal, angle)) - .for_each(|quat| { - let axis_direction = quat * normals_normal; - let direction = Dir3::new_unchecked(axis_direction); - + .map(|angle| Quat::from_axis_angle(self.normal.as_vec3(), angle)) + .flat_map(|quat| { + let segment_length = self.segment_length; + let isometry = self.isometry; // for each axis draw dotted line (0..) .filter(|i| i % 2 != 0) - .map(|percent| (percent as f32 + 0.5) * self.segment_length * axis_direction) - .map(|position| position + self.position) .take(self.segment_count as usize) - .for_each(|position| { - self.gizmos.primitive_3d( - &Segment3d { - direction, - half_length: self.segment_length * 0.5, - }, - position, - Quat::IDENTITY, - self.color, - ); - }); + .map(|i| [i, i + 1]) + .map(move |percents| { + percents + .map(|percent| percent as f32 + 0.5) + .map(|percent| percent * segment_length * normals_normal) + .map(|vec3| quat * vec3) + .map(|vec3| isometry * vec3) + }) + }) + .for_each(|[start, end]| { + self.gizmos.line(start, end, self.color); }); } } @@ -211,8 +201,7 @@ where fn primitive_3d( &mut self, primitive: &Line3d, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -220,13 +209,13 @@ where } let color = color.into(); - let direction = rotation * *primitive.direction; - self.arrow(position, position + direction, color); + let direction = primitive.direction.as_vec3(); + self.arrow(isometry * Vec3::ZERO, isometry * direction, color); let [start, end] = [1.0, -1.0] .map(|sign| sign * INFINITE_LEN) - .map(|length| direction * length) - .map(|offset| position + offset); + .map(|length| primitive.direction * length) + .map(|offset| isometry * offset); self.line(start, end, color); } } @@ -243,18 +232,15 @@ where fn primitive_3d( &mut self, primitive: &Segment3d, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { return; } - let direction = rotation * *primitive.direction; - let start = position - direction * primitive.half_length; - let end = position + direction * primitive.half_length; - self.line(start, end, color); + let direction = primitive.direction.as_vec3(); + self.line(isometry * direction, isometry * (-direction), color); } } @@ -271,20 +257,14 @@ where fn primitive_3d( &mut self, primitive: &Polyline3d, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { return; } - self.linestrip( - primitive - .vertices - .map(rotate_then_translate_3d(rotation, position)), - color, - ); + self.linestrip(primitive.vertices.map(|vec3| isometry * vec3), color); } } @@ -300,8 +280,7 @@ where fn primitive_3d( &mut self, primitive: &BoxedPolyline3d, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -313,7 +292,7 @@ where .vertices .iter() .copied() - .map(rotate_then_translate_3d(rotation, position)), + .map(|vec3| isometry * vec3), color, ); } @@ -331,8 +310,7 @@ where fn primitive_3d( &mut self, primitive: &Triangle3d, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { @@ -340,10 +318,7 @@ where } let [a, b, c] = primitive.vertices; - self.linestrip( - [a, b, c, a].map(rotate_then_translate_3d(rotation, position)), - color, - ); + self.linestrip([a, b, c, a].map(|vec3| isometry * vec3), color); } } @@ -359,16 +334,13 @@ where fn primitive_3d( &mut self, primitive: &Cuboid, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> 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 to the cuboid coords let vertices @ [a, b, c, d, e, f, g, h] = [ [1.0, 1.0, 1.0], @@ -380,8 +352,9 @@ where [-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)); + .map(Vec3::from) + .map(|vec3| vec3 * primitive.half_size) + .map(|vec3| isometry * vec3); // lines for the upper rectangle of the cuboid let upper = [a, b, c, d] @@ -421,12 +394,7 @@ where // Half height of the cylinder half_height: f32, - // Center position of the cylinder - position: Vec3, - // Rotation of the cylinder - // - // default orientation is: the cylinder is aligned with `Vec3::Y` axis - rotation: Quat, + isometry: Isometry3d, // Color of the cylinder color: Color, @@ -456,16 +424,14 @@ where fn primitive_3d( &mut self, primitive: &Cylinder, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { Cylinder3dBuilder { gizmos: self, radius: primitive.radius, half_height: primitive.half_height, - position, - rotation, + isometry, color: color.into(), resolution: DEFAULT_RESOLUTION, } @@ -482,37 +448,17 @@ where return; } - let Cylinder3dBuilder { - gizmos, - radius, - half_height, - position, - rotation, - color, - resolution, - } = self; - - 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| { - gizmos - .circle(*position + sign * up, normal, *radius, *color) - .resolution(*resolution); - }); - - // draw lines connecting the two cylinder circles - [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, - ); - }); + self.gizmos + .primitive_3d( + &ConicalFrustum { + radius_top: self.radius, + radius_bottom: self.radius, + height: self.half_height * 2.0, + }, + self.isometry, + self.color, + ) + .resolution(self.resolution); } } @@ -531,12 +477,7 @@ where // Half length of the capsule half_length: f32, - // Center position of the capsule - position: Vec3, - // Rotation of the capsule - // - // default orientation is: the capsule is aligned with `Vec3::Y` axis - rotation: Quat, + isometry: Isometry3d, // Color of the capsule color: Color, @@ -566,16 +507,14 @@ where fn primitive_3d( &mut self, primitive: &Capsule3d, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { Capsule3dBuilder { gizmos: self, radius: primitive.radius, half_length: primitive.half_length, - position, - rotation, + isometry, color: color.into(), resolution: DEFAULT_RESOLUTION, } @@ -592,66 +531,39 @@ where return; } - let Capsule3dBuilder { - gizmos, - radius, - half_length, - position, - rotation, - color, - resolution, - } = self; + let [upper_apex, lower_apex] = [-1.0, 1.0] + .map(|sign| Vec3::Y * sign * (self.half_length + self.radius)) + .map(|vec3| self.isometry * vec3); + let [upper_center, lower_center] = [-1.0, 1.0] + .map(|sign| Vec3::Y * sign * self.half_length) + .map(|vec3| self.isometry * vec3); + let [upper_points, lower_points] = [-1.0, 1.0] + .map(|sign| Vec3::Y * sign * self.half_length) + .map(|vec3| { + circle_coordinates_closed(self.radius, self.resolution) + .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + vec3) + .map(|vec3| self.isometry * vec3) + .collect::>() + }); - // 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; + upper_points.iter().skip(1).copied().for_each(|start| { + self.gizmos + .short_arc_3d_between(upper_center, start, upper_apex, self.color); + }); + lower_points.iter().skip(1).copied().for_each(|start| { + self.gizmos + .short_arc_3d_between(lower_center, start, lower_apex, self.color); + }); - // Draw the vertical lines and the cap semicircles - [Vec3::X, Vec3::Z].into_iter().for_each(|axis| { - let normal = *rotation * axis; + let upper_lines = upper_points.windows(2).map(|win| (win[0], win[1])); + let lower_lines = lower_points.windows(2).map(|win| (win[0], win[1])); + upper_lines.chain(lower_lines).for_each(|(start, end)| { + self.gizmos.line(start, end, self.color); + }); - 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); + let connection_lines = upper_points.into_iter().zip(lower_points).skip(1); + connection_lines.for_each(|(start, end)| { + self.gizmos.line(start, end, self.color); }); } } @@ -671,12 +583,7 @@ where // Height of the cone height: f32, - // Center of the cone, half-way between the tip and the base - position: Vec3, - // Rotation of the cone - // - // default orientation is: cone base normal is aligned with the `Vec3::Y` axis - rotation: Quat, + isometry: Isometry3d, // Color of the cone color: Color, @@ -728,16 +635,14 @@ where fn primitive_3d( &mut self, primitive: &Cone, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { Cone3dBuilder { gizmos: self, radius: primitive.radius, height: primitive.height, - position, - rotation, + isometry, color: color.into(), base_resolution: DEFAULT_RESOLUTION, height_resolution: DEFAULT_RESOLUTION, @@ -755,37 +660,29 @@ where return; } - let Cone3dBuilder { - gizmos, - radius, - height, - position, - rotation, - color, - base_resolution, - height_resolution, - } = self; + let half_height = self.height * 0.5; + let apex = self.isometry * (Vec3::Y * half_height); + let circle_center = half_height * Vec3::NEG_Y; + let circle_coords = circle_coordinates_closed(self.radius, self.height_resolution) + .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + circle_center) + .map(|vec3| self.isometry * vec3) + .collect::>(); - let half_height = *height * 0.5; + // connections to apex + circle_coords + .iter() + .skip(1) + .map(|vec3| (*vec3, apex)) + .for_each(|(start, end)| { + self.gizmos.line(start, end, self.color); + }); - // draw the base circle of the cone - draw_circle_3d( - gizmos, - *radius, - *base_resolution, - *rotation, - *position - *rotation * Vec3::Y * half_height, - *color, - ); - - // connect the base circle with the tip of the cone - let end = Vec3::Y * half_height; - circle_coordinates(*radius, *height_resolution) - .map(|p| Vec3::new(p.x, -half_height, p.y)) - .map(move |p| [p, end]) - .map(|ps| ps.map(rotate_then_translate_3d(*rotation, *position))) - .for_each(|[start, end]| { - gizmos.line(start, end, *color); + // base circle + circle_coords + .windows(2) + .map(|win| (win[0], win[1])) + .for_each(|(start, end)| { + self.gizmos.line(start, end, self.color); }); } } @@ -807,12 +704,7 @@ where // Height of the conical frustum height: f32, - // Center of conical frustum, half-way between the top and the bottom - position: Vec3, - // Rotation of the conical frustum - // - // default orientation is: conical frustum base shape normals are aligned with `Vec3::Y` axis - rotation: Quat, + isometry: Isometry3d, // Color of the conical frustum color: Color, @@ -842,8 +734,7 @@ where fn primitive_3d( &mut self, primitive: &ConicalFrustum, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { ConicalFrustum3dBuilder { @@ -851,8 +742,7 @@ where radius_top: primitive.radius_top, radius_bottom: primitive.radius_bottom, height: primitive.height, - position, - rotation, + isometry, color: color.into(), resolution: DEFAULT_RESOLUTION, } @@ -869,46 +759,26 @@ where return; } - let ConicalFrustum3dBuilder { - gizmos, - radius_top, - radius_bottom, - height, - position, - rotation, - color, - resolution, - } = self; - - let half_height = *height * 0.5; - let normal = *rotation * Vec3::Y; - - // draw the two circles of the conical frustum - [(*radius_top, half_height), (*radius_bottom, -half_height)] - .into_iter() - .for_each(|(radius, height)| { - draw_circle_3d( - gizmos, - radius, - *resolution, - *rotation, - *position + height * normal, - *color, - ); + let half_height = self.height * 0.5; + let [upper_points, lower_points] = [(-1.0, self.radius_bottom), (1.0, self.radius_top)] + .map(|(sign, radius)| { + let translation = Vec3::Y * sign * half_height; + circle_coordinates_closed(radius, self.resolution) + .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation) + .map(|vec3| self.isometry * vec3) + .collect::>() }); - // connect the two circles of the conical frustum - circle_coordinates(*radius_top, *resolution) - .map(move |p| Vec3::new(p.x, half_height, p.y)) - .zip( - circle_coordinates(*radius_bottom, *resolution) - .map(|p| Vec3::new(p.x, -half_height, p.y)), - ) - .map(|(start, end)| [start, end]) - .map(|ps| ps.map(rotate_then_translate_3d(*rotation, *position))) - .for_each(|[start, end]| { - gizmos.line(start, end, *color); - }); + let upper_lines = upper_points.windows(2).map(|win| (win[0], win[1])); + let lower_lines = lower_points.windows(2).map(|win| (win[0], win[1])); + upper_lines.chain(lower_lines).for_each(|(start, end)| { + self.gizmos.line(start, end, self.color); + }); + + let connection_lines = upper_points.into_iter().zip(lower_points).skip(1); + connection_lines.for_each(|(start, end)| { + self.gizmos.line(start, end, self.color); + }); } } @@ -927,12 +797,7 @@ where // Radius of the major circle (ring) major_radius: f32, - // Center of the torus - position: Vec3, - // Rotation of the conical frustum - // - // default orientation is: major circle normal is aligned with `Vec3::Y` axis - rotation: Quat, + isometry: Isometry3d, // Color of the torus color: Color, @@ -970,16 +835,14 @@ where fn primitive_3d( &mut self, primitive: &Torus, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { Torus3dBuilder { gizmos: self, minor_radius: primitive.minor_radius, major_radius: primitive.major_radius, - position, - rotation, + isometry, color: color.into(), minor_resolution: DEFAULT_RESOLUTION, major_resolution: DEFAULT_RESOLUTION, @@ -997,62 +860,42 @@ where return; } - let Torus3dBuilder { - gizmos, - minor_radius, - major_radius, - position, - rotation, - color, - minor_resolution, - major_resolution, - } = self; - - let normal = *rotation * Vec3::Y; - // draw 4 circles with major_radius - [ - (*major_radius - *minor_radius, 0.0), - (*major_radius + *minor_radius, 0.0), - (*major_radius, *minor_radius), - (*major_radius, -*minor_radius), + let [inner, outer, top, bottom] = [ + (self.major_radius - self.minor_radius, 0.0), + (self.major_radius + self.minor_radius, 0.0), + (self.major_radius, self.minor_radius), + (self.major_radius, -self.minor_radius), ] - .into_iter() - .for_each(|(radius, height)| { - draw_circle_3d( - gizmos, - radius, - *major_resolution, - *rotation, - *position + height * normal, - *color, - ); + .map(|(radius, height)| { + let translation = height * Vec3::Y; + circle_coordinates_closed(radius, self.major_resolution) + .map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation) + .map(|vec3| self.isometry * vec3) + .collect::>() }); - // along the major circle draw orthogonal minor circles - let affine = rotate_then_translate_3d(*rotation, *position); - circle_coordinates(*major_radius, *major_resolution) - .map(|p| Vec3::new(p.x, 0.0, p.y)) - .flat_map(|major_circle_point| { - let minor_center = affine(major_circle_point); + [&inner, &outer, &top, &bottom] + .iter() + .flat_map(|points| points.windows(2).map(|win| (win[0], win[1]))) + .for_each(|(start, end)| { + self.gizmos.line(start, end, self.color); + }); - // direction facing from the center of the torus towards the minor circles center - let dir_to_translation = (minor_center - *position).normalize(); - - // the minor circle is draw with 4 arcs this is done to make the minor circle - // connect properly with each of the major circles - let circle_points = [dir_to_translation, normal, -dir_to_translation, -normal] - .map(|offset| minor_center + offset.normalize() * *minor_radius); - circle_points - .into_iter() - .zip(circle_points.into_iter().cycle().skip(1)) - .map(move |(from, to)| (minor_center, from, to)) - .collect::>() + inner + .into_iter() + .zip(top) + .zip(outer) + .zip(bottom) + .flat_map(|(((inner, top), outer), bottom)| { + let center = (inner + top + outer + bottom) * 0.25; + [(inner, top), (top, outer), (outer, bottom), (bottom, inner)] + .map(|(start, end)| (start, end, center)) }) - .for_each(|(center, from, to)| { - gizmos - .short_arc_3d_between(center, from, to, *color) - .resolution(*minor_resolution); + .for_each(|(from, to, center)| { + self.gizmos + .short_arc_3d_between(center, from, to, self.color) + .resolution(self.minor_resolution); }); } } @@ -1065,23 +908,20 @@ impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d for Gizmos<'w, ' fn primitive_3d( &mut self, primitive: &Tetrahedron, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: impl Into, ) -> Self::Output<'_> { if !self.enabled { return; } - let [a, b, c, d] = primitive - .vertices - .map(rotate_then_translate_3d(rotation, position)); + let [a, b, c, d] = primitive.vertices.map(|vec3| isometry * vec3); let lines = [(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)]; let color = color.into(); - for (a, b) in lines.into_iter() { - self.line(a, b, color); - } + lines.into_iter().for_each(|(start, end)| { + self.line(start, end, color); + }); } } diff --git a/crates/bevy_gizmos/src/primitives/helpers.rs b/crates/bevy_gizmos/src/primitives/helpers.rs index b76fb34f6b..864c4bd0dc 100644 --- a/crates/bevy_gizmos/src/primitives/helpers.rs +++ b/crates/bevy_gizmos/src/primitives/helpers.rs @@ -1,28 +1,6 @@ use std::f32::consts::TAU; -use bevy_color::Color; -use bevy_math::{Mat2, Quat, Vec2, Vec3}; - -use crate::prelude::{GizmoConfigGroup, Gizmos}; - -/// Performs an isometric transformation on 2D vectors. -/// -/// This function takes angle and a position vector, and returns a closure that applies -/// the isometric transformation to any given 2D vector. The transformation involves rotating -/// the vector by the specified angle and then translating it by the given position. -pub(crate) fn rotate_then_translate_2d(angle: f32, position: Vec2) -> impl Fn(Vec2) -> Vec2 { - move |v| Mat2::from_angle(angle) * v + position -} - -/// Performs an isometric transformation on 3D vectors. -/// -/// This function takes a quaternion representing rotation and a 3D vector representing -/// translation, and returns a closure that applies the isometric transformation to any -/// given 3D vector. The transformation involves rotating the vector by the specified -/// quaternion and then translating it by the given translation vector. -pub(crate) fn rotate_then_translate_3d(rotation: Quat, translation: Vec3) -> impl Fn(Vec3) -> Vec3 { - move |v| rotation * v + translation -} +use bevy_math::Vec2; /// Calculates the `nth` coordinate of a circle. /// @@ -37,6 +15,8 @@ pub(crate) fn single_circle_coordinate(radius: f32, resolution: u32, nth_point: /// Generates an iterator over the coordinates of a circle. /// +/// The coordinates form a open circle, meaning the first and last points aren't the same. +/// /// This function creates an iterator that yields the positions of points approximating a /// circle with the given radius, divided into linear segments. The iterator produces `resolution` /// number of points. @@ -46,27 +26,18 @@ pub(crate) fn circle_coordinates(radius: f32, resolution: u32) -> impl Iterator< .take(resolution as usize) } -/// Draws a circle in 3D space. +/// Generates an iterator over the coordinates of a circle. /// -/// # Note +/// The coordinates form a closed circle, meaning the first and last points are the same. /// -/// This function is necessary to use instead of `gizmos.circle` for certain primitives to ensure that points align correctly. For example, the major circles of a torus are drawn with this method, and using `gizmos.circle` would result in the minor circles not being positioned precisely on the major circles' segment points. -pub(crate) fn draw_circle_3d( - gizmos: &mut Gizmos<'_, '_, Config, Clear>, +/// This function creates an iterator that yields the positions of points approximating a +/// circle with the given radius, divided into linear segments. The iterator produces `resolution` +/// number of points. +pub(crate) fn circle_coordinates_closed( radius: f32, resolution: u32, - rotation: Quat, - translation: Vec3, - color: Color, -) where - Config: GizmoConfigGroup, - Clear: 'static + Send + Sync, -{ - let positions = (0..=resolution) - .map(|frac| frac as f32 / resolution as f32) - .map(|percentage| percentage * TAU) - .map(|angle| Vec2::from(angle.sin_cos()) * radius) - .map(|p| Vec3::new(p.x, 0.0, p.y)) - .map(rotate_then_translate_3d(rotation, translation)); - gizmos.linestrip(positions, color); +) -> impl Iterator { + circle_coordinates(radius, resolution).chain(std::iter::once(single_circle_coordinate( + radius, resolution, resolution, + ))) } diff --git a/crates/bevy_gizmos/src/rounded_box.rs b/crates/bevy_gizmos/src/rounded_box.rs index 232bcee6df..ac459c9178 100644 --- a/crates/bevy_gizmos/src/rounded_box.rs +++ b/crates/bevy_gizmos/src/rounded_box.rs @@ -7,7 +7,7 @@ use std::f32::consts::FRAC_PI_2; use crate::prelude::{GizmoConfigGroup, Gizmos}; use bevy_color::Color; -use bevy_math::{Quat, Vec2, Vec3}; +use bevy_math::{Isometry2d, Isometry3d, Quat, Vec2, Vec3}; use bevy_transform::components::Transform; /// A builder returned by [`Gizmos::rounded_rect`] and [`Gizmos::rounded_rect_2d`] @@ -23,8 +23,7 @@ pub struct RoundedCuboidBuilder<'a, 'w, 's, T: GizmoConfigGroup> { config: RoundedBoxConfig, } struct RoundedBoxConfig { - position: Vec3, - rotation: Quat, + isometry: Isometry3d, color: Color, corner_radius: f32, arc_resolution: u32, @@ -82,15 +81,14 @@ impl Drop for RoundedRectBuilder<'_, '_, '_, T> { // Handle cases where the rectangle collapses into simpler shapes if outer_half_size.x * outer_half_size.y == 0. { self.gizmos.line( - config.position + config.rotation * -outer_half_size.extend(0.), - config.position + config.rotation * outer_half_size.extend(0.), + config.isometry * -outer_half_size.extend(0.), + config.isometry * outer_half_size.extend(0.), config.color, ); return; } if corner_radius == 0. { - self.gizmos - .rect(config.position, config.rotation, self.size, config.color); + self.gizmos.rect(config.isometry, self.size, config.color); return; } @@ -112,7 +110,7 @@ impl Drop for RoundedRectBuilder<'_, '_, '_, T> { Vec3::new(-inner_half_size.x, inner_half_size.y, 0.), Vec3::new(-inner_half_size.x, outer_half_size.y, 0.), ] - .map(|v| config.position + config.rotation * v); + .map(|vec3| config.isometry * vec3); for chunk in vertices.chunks_exact(3) { self.gizmos @@ -159,8 +157,8 @@ impl Drop for RoundedCuboidBuilder<'_, '_, '_, T> { // Handle cases where the rounded cuboid collapses into simpler shapes if edge_radius == 0.0 { - let transform = Transform::from_translation(config.position) - .with_rotation(config.rotation) + let transform = Transform::from_translation(config.isometry.translation.into()) + .with_rotation(config.isometry.rotation) .with_scale(self.size); self.gizmos.cuboid(transform, config.color); return; @@ -181,12 +179,10 @@ impl Drop for RoundedCuboidBuilder<'_, '_, '_, T> { ]; for (position, size, rotation) in rects { - let world_rotation = config.rotation * rotation; - let local_position = config.rotation * (position * inner_half_size); + let local_position = position * inner_half_size; self.gizmos .rounded_rect( - config.position + local_position, - world_rotation, + config.isometry * Isometry3d::new(local_position, rotation), size, config.color, ) @@ -195,8 +191,7 @@ impl Drop for RoundedCuboidBuilder<'_, '_, '_, T> { self.gizmos .rounded_rect( - config.position - local_position, - world_rotation, + config.isometry * Isometry3d::new(-local_position, rotation), size, config.color, ) @@ -213,8 +208,11 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// /// # Arguments /// - /// - `position`: The center point of the rectangle. - /// - `rotation`: defines orientation of the rectangle, by default we assume the rectangle is contained in a plane parallel to the XY plane. + /// - `isometry` defines the translation and rotation of the rectangle. + /// - the translation specifies the center of the rectangle + /// - defines orientation of the rectangle, by default we + /// assume the rectangle is contained in a plane parallel + /// to the XY plane. /// - `size`: defines the size of the rectangle. This refers to the 'outer size', similar to a bounding box. /// - `color`: color of the rectangle /// @@ -231,8 +229,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// # use bevy_color::palettes::css::GREEN; /// fn system(mut gizmos: Gizmos) { /// gizmos.rounded_rect( - /// Vec3::ZERO, - /// Quat::IDENTITY, + /// Isometry3d::IDENTITY, /// Vec2::ONE, /// GREEN /// ) @@ -243,8 +240,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// ``` pub fn rounded_rect( &mut self, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, size: Vec2, color: impl Into, ) -> RoundedRectBuilder<'_, 'w, 's, T> { @@ -252,8 +248,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { RoundedRectBuilder { gizmos: self, config: RoundedBoxConfig { - position, - rotation, + isometry, color: color.into(), corner_radius, arc_resolution: DEFAULT_ARC_RESOLUTION, @@ -268,8 +263,10 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// /// # Arguments /// - /// - `position`: The center point of the rectangle. - /// - `rotation`: defines orientation of the rectangle. + /// - `isometry` defines the translation and rotation of the rectangle. + /// - the translation specifies the center of the rectangle + /// - defines orientation of the rectangle, by default we + /// assume the rectangle aligned with all axes. /// - `size`: defines the size of the rectangle. This refers to the 'outer size', similar to a bounding box. /// - `color`: color of the rectangle /// @@ -286,8 +283,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// # use bevy_color::palettes::css::GREEN; /// fn system(mut gizmos: Gizmos) { /// gizmos.rounded_rect_2d( - /// Vec2::ZERO, - /// 0., + /// Isometry2d::IDENTITY, /// Vec2::ONE, /// GREEN /// ) @@ -298,8 +294,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// ``` pub fn rounded_rect_2d( &mut self, - position: Vec2, - rotation: f32, + isometry: Isometry2d, size: Vec2, color: impl Into, ) -> RoundedRectBuilder<'_, 'w, 's, T> { @@ -307,8 +302,10 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { RoundedRectBuilder { gizmos: self, config: RoundedBoxConfig { - position: position.extend(0.), - rotation: Quat::from_rotation_z(rotation), + isometry: Isometry3d::new( + isometry.translation.extend(0.0), + Quat::from_rotation_z(isometry.rotation.as_radians()), + ), color: color.into(), corner_radius, arc_resolution: DEFAULT_ARC_RESOLUTION, @@ -323,8 +320,10 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// /// # Arguments /// - /// - `position`: The center point of the cuboid. - /// - `rotation`: defines orientation of the cuboid. + /// - `isometry` defines the translation and rotation of the cuboid. + /// - the translation specifies the center of the cuboid + /// - defines orientation of the cuboid, by default we + /// assume the cuboid aligned with all axes. /// - `size`: defines the size of the cuboid. This refers to the 'outer size', similar to a bounding box. /// - `color`: color of the cuboid /// @@ -341,8 +340,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// # use bevy_color::palettes::css::GREEN; /// fn system(mut gizmos: Gizmos) { /// gizmos.rounded_cuboid( - /// Vec3::ZERO, - /// Quat::IDENTITY, + /// Isometry3d::IDENTITY, /// Vec3::ONE, /// GREEN /// ) @@ -353,8 +351,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { /// ``` pub fn rounded_cuboid( &mut self, - position: Vec3, - rotation: Quat, + isometry: Isometry3d, size: Vec3, color: impl Into, ) -> RoundedCuboidBuilder<'_, 'w, 's, T> { @@ -362,8 +359,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> { RoundedCuboidBuilder { gizmos: self, config: RoundedBoxConfig { - position, - rotation, + isometry, color: color.into(), corner_radius, arc_resolution: DEFAULT_ARC_RESOLUTION, diff --git a/examples/2d/2d_viewport_to_world.rs b/examples/2d/2d_viewport_to_world.rs index 45f759a5e4..c065d1c119 100644 --- a/examples/2d/2d_viewport_to_world.rs +++ b/examples/2d/2d_viewport_to_world.rs @@ -1,6 +1,6 @@ //! This example demonstrates how to use the `Camera::viewport_to_world_2d` method. -use bevy::{color::palettes::basic::WHITE, prelude::*}; +use bevy::{color::palettes::basic::WHITE, math::Isometry2d, prelude::*}; fn main() { App::new() @@ -30,7 +30,7 @@ fn draw_cursor( return; }; - gizmos.circle_2d(point, 10., WHITE); + gizmos.circle_2d(Isometry2d::from_translation(point), 10., WHITE); } fn setup(mut commands: Commands) { diff --git a/examples/2d/bounding_2d.rs b/examples/2d/bounding_2d.rs index 2025b00741..625a11aec6 100644 --- a/examples/2d/bounding_2d.rs +++ b/examples/2d/bounding_2d.rs @@ -105,24 +105,25 @@ fn render_shapes(mut gizmos: Gizmos, query: Query<(&Shape, &Transform)>) { for (shape, transform) in query.iter() { let translation = transform.translation.xy(); let rotation = transform.rotation.to_euler(EulerRot::YXZ).2; + let isometry = Isometry2d::new(translation, Rot2::radians(rotation)); match shape { Shape::Rectangle(r) => { - gizmos.primitive_2d(r, translation, rotation, color); + gizmos.primitive_2d(r, isometry, color); } Shape::Circle(c) => { - gizmos.primitive_2d(c, translation, rotation, color); + gizmos.primitive_2d(c, isometry, color); } Shape::Triangle(t) => { - gizmos.primitive_2d(t, translation, rotation, color); + gizmos.primitive_2d(t, isometry, color); } Shape::Line(l) => { - gizmos.primitive_2d(l, translation, rotation, color); + gizmos.primitive_2d(l, isometry, color); } Shape::Capsule(c) => { - gizmos.primitive_2d(c, translation, rotation, color); + gizmos.primitive_2d(c, isometry, color); } Shape::Polygon(p) => { - gizmos.primitive_2d(p, translation, rotation, color); + gizmos.primitive_2d(p, isometry, color); } } } @@ -185,10 +186,14 @@ fn render_volumes(mut gizmos: Gizmos, query: Query<(&CurrentVolume, &Intersects) let color = if **intersects { AQUA } else { ORANGE_RED }; match volume { CurrentVolume::Aabb(a) => { - gizmos.rect_2d(a.center(), 0., a.half_size() * 2., color); + gizmos.rect_2d( + Isometry2d::from_translation(a.center()), + a.half_size() * 2., + color, + ); } CurrentVolume::Circle(c) => { - gizmos.circle_2d(c.center(), c.radius(), color); + gizmos.circle_2d(Isometry2d::from_translation(c.center()), c.radius(), color); } } } @@ -283,7 +288,7 @@ fn setup(mut commands: Commands) { fn draw_filled_circle(gizmos: &mut Gizmos, position: Vec2, color: Srgba) { for r in [1., 2., 3.] { - gizmos.circle_2d(position, r, color); + gizmos.circle_2d(Isometry2d::from_translation(position), r, color); } } @@ -353,8 +358,9 @@ fn aabb_cast_system( **intersects = toi.is_some(); if let Some(toi) = toi { gizmos.rect_2d( - aabb_cast.ray.ray.origin + *aabb_cast.ray.ray.direction * toi, - 0., + Isometry2d::from_translation( + aabb_cast.ray.ray.origin + *aabb_cast.ray.ray.direction * toi, + ), aabb_cast.aabb.half_size() * 2., LIME, ); @@ -382,7 +388,9 @@ fn bounding_circle_cast_system( **intersects = toi.is_some(); if let Some(toi) = toi { gizmos.circle_2d( - circle_cast.ray.ray.origin + *circle_cast.ray.ray.direction * toi, + Isometry2d::from_translation( + circle_cast.ray.ray.origin + *circle_cast.ray.ray.direction * toi, + ), circle_cast.circle.radius(), LIME, ); @@ -403,7 +411,11 @@ fn aabb_intersection_system( ) { let center = get_intersection_position(&time); let aabb = Aabb2d::new(center, Vec2::splat(50.)); - gizmos.rect_2d(center, 0., aabb.half_size() * 2., YELLOW); + gizmos.rect_2d( + Isometry2d::from_translation(center), + aabb.half_size() * 2., + YELLOW, + ); for (volume, mut intersects) in volumes.iter_mut() { let hit = match volume { @@ -422,7 +434,11 @@ fn circle_intersection_system( ) { let center = get_intersection_position(&time); let circle = BoundingCircle::new(center, 50.); - gizmos.circle_2d(center, circle.radius(), YELLOW); + gizmos.circle_2d( + Isometry2d::from_translation(center), + circle.radius(), + YELLOW, + ); for (volume, mut intersects) in volumes.iter_mut() { let hit = match volume { diff --git a/examples/2d/mesh2d_arcs.rs b/examples/2d/mesh2d_arcs.rs index 4b22417deb..a600c986b6 100644 --- a/examples/2d/mesh2d_arcs.rs +++ b/examples/2d/mesh2d_arcs.rs @@ -120,9 +120,17 @@ fn draw_bounds( let isometry = Isometry2d::new(translation, Rot2::radians(rotation)); let aabb = shape.0.aabb_2d(isometry); - gizmos.rect_2d(aabb.center(), 0.0, aabb.half_size() * 2.0, RED); + gizmos.rect_2d( + Isometry2d::from_translation(aabb.center()), + aabb.half_size() * 2.0, + RED, + ); let bounding_circle = shape.0.bounding_circle(isometry); - gizmos.circle_2d(bounding_circle.center, bounding_circle.radius(), BLUE); + gizmos.circle_2d( + Isometry2d::from_translation(bounding_circle.center), + bounding_circle.radius(), + BLUE, + ); } } diff --git a/examples/3d/3d_viewport_to_world.rs b/examples/3d/3d_viewport_to_world.rs index 9e4e4f73da..98c143c553 100644 --- a/examples/3d/3d_viewport_to_world.rs +++ b/examples/3d/3d_viewport_to_world.rs @@ -37,7 +37,14 @@ fn draw_cursor( let point = ray.get_point(distance); // Draw a circle just above the ground plane at that position. - gizmos.circle(point + ground.up() * 0.01, ground.up(), 0.2, Color::WHITE); + gizmos.circle( + Isometry3d::new( + point + ground.up() * 0.01, + Quat::from_rotation_arc(Vec3::Z, ground.up().as_vec3()), + ), + 0.2, + Color::WHITE, + ); } #[derive(Component)] diff --git a/examples/ecs/observers.rs b/examples/ecs/observers.rs index cd06fef3ab..8820ce4919 100644 --- a/examples/ecs/observers.rs +++ b/examples/ecs/observers.rs @@ -1,6 +1,7 @@ //! Demonstrates how to observe life-cycle triggers as well as define custom ones. use bevy::{ + math::Isometry2d, prelude::*, utils::{HashMap, HashSet}, }; @@ -165,7 +166,7 @@ fn explode_mine(trigger: Trigger, query: Query<&Mine>, mut commands: Co fn draw_shapes(mut gizmos: Gizmos, mines: Query<&Mine>) { for mine in &mines { gizmos.circle_2d( - mine.pos, + Isometry2d::from_translation(mine.pos), mine.size, Color::hsl((mine.size - 4.0) / 16.0 * 360.0, 1.0, 0.8), ); diff --git a/examples/gizmos/2d_gizmos.rs b/examples/gizmos/2d_gizmos.rs index b935b2fb7a..45cee66372 100644 --- a/examples/gizmos/2d_gizmos.rs +++ b/examples/gizmos/2d_gizmos.rs @@ -49,8 +49,7 @@ fn draw_example_collection( gizmos .grid_2d( - Vec2::ZERO, - 0.0, + Isometry2d::IDENTITY, UVec2::new(16, 9), Vec2::new(80., 80.), // Dark gray @@ -66,21 +65,26 @@ fn draw_example_collection( (Vec2::Y * 300., BLUE), ]); - gizmos.rect_2d(Vec2::ZERO, 0., Vec2::splat(650.), BLACK); + gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::splat(650.), BLACK); - gizmos.cross_2d(Vec2::new(-160., 120.), 0., 12., FUCHSIA); + gizmos.cross_2d( + Isometry2d::from_translation(Vec2::new(-160., 120.)), + 12., + FUCHSIA, + ); my_gizmos - .rounded_rect_2d(Vec2::ZERO, 0., Vec2::splat(630.), BLACK) + .rounded_rect_2d(Isometry2d::IDENTITY, Vec2::splat(630.), BLACK) .corner_radius((time.elapsed_seconds() / 3.).cos() * 100.); // Circles have 32 line-segments by default. // You may want to increase this for larger circles. - my_gizmos.circle_2d(Vec2::ZERO, 300., NAVY).resolution(64); + my_gizmos + .circle_2d(Isometry2d::from_translation(Vec2::ZERO), 300., NAVY) + .resolution(64); my_gizmos.ellipse_2d( - Vec2::ZERO, - time.elapsed_seconds() % TAU, + Isometry2d::new(Vec2::ZERO, Rot2::radians(time.elapsed_seconds() % TAU)), Vec2::new(100., 200.), YELLOW_GREEN, ); diff --git a/examples/gizmos/3d_gizmos.rs b/examples/gizmos/3d_gizmos.rs index d74d3dd307..e66682180c 100644 --- a/examples/gizmos/3d_gizmos.rs +++ b/examples/gizmos/3d_gizmos.rs @@ -83,41 +83,48 @@ fn draw_example_collection( time: Res