Fix arc_2d Gizmos (#14731)

# Objective

`arc_2d` wasn't actually doing what the docs were saying. The arc wasn't
offset by what was previously `direction_angle` but by `direction_angle
- arc_angle / 2.0`. This meant that the arcs center was laying on the
`Vec2::Y` axis and then it was offset. This was probably done to fit the
behavior of the `Arc2D` primitive. I would argue that this isn't
desirable for the plain `arc_2d` gizmo method since

- a) the docs get longer to explain the weird centering
- b) the mental model the user has to know gets bigger with more
implicit assumptions

given the code

```rust
    my_gizmos.arc_2d(Vec2::ZERO, 0.0, FRAC_PI_2, 75.0, ORANGE_RED);
```

we get


![image](https://github.com/user-attachments/assets/84894c6d-42e4-451b-b3e2-811266486ede)

where after the fix with

```rust
    my_gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_2, 75.0, ORANGE_RED);
```

we get


![image](https://github.com/user-attachments/assets/16b0aba0-f7b5-4600-ac49-a22be0315c40)

To get the same result with the previous implementation you would have
to randomly add `arc_angle / 2.0` to the `direction_angle`.

```rust
    my_gizmos.arc_2d(Vec2::ZERO, FRAC_PI_4, FRAC_PI_2, 75.0, ORANGE_RED);
```

This makes constructing similar helping functions as they already exist
in 3D like

- `long_arc_2d_between`
- `short_arc_2d_between`

 much harder.

## Solution

- Make the arc really start at `Vec2::Y * radius` in counter-clockwise
direction + offset by an angle as the docs state it
- Use `Isometry2d` instead of `position : Vec2` and `direction_angle :
f32` to reduce the chance of messing up rotation/translation
- Adjust the docs for the changes above
- Adjust the gizmo rendering of some primitives

## Testing

- check `2d_gizmos.rs` and `render_primitives.rs` examples

## Migration Guide

- users have to adjust their usages of `arc_2d`:
  - before: 
  ```rust
  arc_2d(
    pos,
    angle,
    arc_angle,
    radius,
    color
  )
  ```
  - after: 
  ```rust
  arc_2d(
// this `+ arc_angle * 0.5` quirk is only if you want to preserve the
previous behavior
    // with the new API.
// feel free to try to fix this though since your current calls to this
function most likely
// involve some computations to counter-act that quirk in the first
place
    Isometry2d::new(pos, Rot2::radians(angle + arc_angle * 0.5),
    arc_angle,
    radius,
    color
  )
  ```
This commit is contained in:
Robert Walter 2024-08-26 17:57:57 +00:00 committed by GitHub
parent 2e36b2719c
commit 6819e998c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 41 additions and 55 deletions

View file

@ -6,8 +6,8 @@
use crate::circles::DEFAULT_CIRCLE_RESOLUTION; use crate::circles::DEFAULT_CIRCLE_RESOLUTION;
use crate::prelude::{GizmoConfigGroup, Gizmos}; use crate::prelude::{GizmoConfigGroup, Gizmos};
use bevy_color::Color; use bevy_color::Color;
use bevy_math::{Quat, Vec2, Vec3}; use bevy_math::{Isometry2d, Quat, Vec2, Vec3};
use std::f32::consts::TAU; use std::f32::consts::{FRAC_PI_2, TAU};
// === 2D === // === 2D ===
@ -21,9 +21,9 @@ where
/// This should be called for each frame the arc needs to be rendered. /// This should be called for each frame the arc needs to be rendered.
/// ///
/// # Arguments /// # Arguments
/// - `position` sets the center of this circle. /// - `isometry` defines the translation and rotation of the arc.
/// - `direction_angle` sets the counter-clockwise angle in radians between `Vec2::Y` and /// - the translation specifies the center of the arc
/// the vector from `position` to the midpoint of the arc. /// - the rotation is counter-clockwise starting from `Vec2::Y`
/// - `arc_angle` sets the length of this arc, in radians. /// - `arc_angle` sets the length of this arc, in radians.
/// - `radius` controls the distance from `position` to this arc, and thus its curvature. /// - `radius` controls the distance from `position` to this arc, and thus its curvature.
/// - `color` sets the color to draw the arc. /// - `color` sets the color to draw the arc.
@ -32,15 +32,15 @@ where
/// ``` /// ```
/// # use bevy_gizmos::prelude::*; /// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*; /// # use bevy_math::prelude::*;
/// # use std::f32::consts::PI; /// # use std::f32::consts::FRAC_PI_4;
/// # use bevy_color::palettes::basic::{GREEN, RED}; /// # use bevy_color::palettes::basic::{GREEN, RED};
/// fn system(mut gizmos: Gizmos) { /// fn system(mut gizmos: Gizmos) {
/// gizmos.arc_2d(Vec2::ZERO, 0., PI / 4., 1., GREEN); /// gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 1., GREEN);
/// ///
/// // Arcs have 32 line-segments by default. /// // Arcs have 32 line-segments by default.
/// // You may want to increase this for larger arcs. /// // You may want to increase this for larger arcs.
/// gizmos /// gizmos
/// .arc_2d(Vec2::ZERO, 0., PI / 4., 5., RED) /// .arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 5., RED)
/// .resolution(64); /// .resolution(64);
/// } /// }
/// # bevy_ecs::system::assert_is_system(system); /// # bevy_ecs::system::assert_is_system(system);
@ -48,16 +48,14 @@ where
#[inline] #[inline]
pub fn arc_2d( pub fn arc_2d(
&mut self, &mut self,
position: Vec2, isometry: Isometry2d,
direction_angle: f32,
arc_angle: f32, arc_angle: f32,
radius: f32, radius: f32,
color: impl Into<Color>, color: impl Into<Color>,
) -> Arc2dBuilder<'_, 'w, 's, Config, Clear> { ) -> Arc2dBuilder<'_, 'w, 's, Config, Clear> {
Arc2dBuilder { Arc2dBuilder {
gizmos: self, gizmos: self,
position, isometry,
direction_angle,
arc_angle, arc_angle,
radius, radius,
color: color.into(), color: color.into(),
@ -73,8 +71,7 @@ where
Clear: 'static + Send + Sync, Clear: 'static + Send + Sync,
{ {
gizmos: &'a mut Gizmos<'w, 's, Config, Clear>, gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
position: Vec2, isometry: Isometry2d,
direction_angle: f32,
arc_angle: f32, arc_angle: f32,
radius: f32, radius: f32,
color: Color, color: Color,
@ -107,31 +104,19 @@ where
.resolution .resolution
.unwrap_or_else(|| resolution_from_angle(self.arc_angle)); .unwrap_or_else(|| resolution_from_angle(self.arc_angle));
let positions = arc_2d_inner( let positions =
self.direction_angle, arc_2d_inner(self.arc_angle, self.radius, resolution).map(|vec2| self.isometry * vec2);
self.arc_angle,
self.radius,
resolution,
)
.map(|vec2| (vec2 + self.position));
self.gizmos.linestrip_2d(positions, self.color); self.gizmos.linestrip_2d(positions, self.color);
} }
} }
fn arc_2d_inner( fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {
direction_angle: f32, (0..=resolution)
arc_angle: f32, .map(move |n| arc_angle * n as f32 / resolution as f32)
radius: f32, .map(|angle| angle + FRAC_PI_2)
resolution: u32, .map(f32::sin_cos)
) -> impl Iterator<Item = Vec2> { .map(|(sin, cos)| Vec2::new(cos, sin))
(0..resolution + 1).map(move |i| { .map(move |vec2| vec2 * radius)
let start = direction_angle - arc_angle / 2.;
let angle =
start + (i as f32 * (arc_angle / resolution as f32)) + std::f32::consts::FRAC_PI_2;
Vec2::new(angle.cos(), angle.sin()) * radius
})
} }
// === 3D === // === 3D ===

View file

@ -1,6 +1,6 @@
//! A module for rendering each of the 2D [`bevy_math::primitives`] with [`Gizmos`]. //! A module for rendering each of the 2D [`bevy_math::primitives`] with [`Gizmos`].
use std::f32::consts::PI; use std::f32::consts::{FRAC_PI_2, PI};
use super::helpers::*; use super::helpers::*;
@ -10,7 +10,7 @@ use bevy_math::primitives::{
CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Primitive2d, Rectangle, CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Primitive2d, Rectangle,
RegularPolygon, Rhombus, Segment2d, Triangle2d, RegularPolygon, Rhombus, Segment2d, Triangle2d,
}; };
use bevy_math::{Dir2, Mat2, Vec2}; use bevy_math::{Dir2, Isometry2d, Mat2, Rot2, Vec2};
use crate::prelude::{GizmoConfigGroup, Gizmos}; use crate::prelude::{GizmoConfigGroup, Gizmos};
@ -86,8 +86,7 @@ where
} }
self.arc_2d( self.arc_2d(
position, Isometry2d::new(position, Rot2::radians(angle - primitive.half_angle)),
angle,
primitive.half_angle * 2.0, primitive.half_angle * 2.0,
primitive.radius, primitive.radius,
color, color,
@ -139,8 +138,7 @@ where
// we need to draw the arc part of the sector, and the two lines connecting the arc and the center // we need to draw the arc part of the sector, and the two lines connecting the arc and the center
self.arc_2d( self.arc_2d(
position, Isometry2d::new(position, Rot2::radians(angle - primitive.arc.half_angle)),
angle,
primitive.arc.half_angle * 2.0, primitive.arc.half_angle * 2.0,
primitive.arc.radius, primitive.arc.radius,
color, color,
@ -179,8 +177,7 @@ where
// we need to draw the arc part of the segment, and the line connecting the two ends // we need to draw the arc part of the segment, and the line connecting the two ends
self.arc_2d( self.arc_2d(
position, Isometry2d::new(position, Rot2::radians(angle - primitive.arc.half_angle)),
angle,
primitive.arc.half_angle * 2.0, primitive.arc.half_angle * 2.0,
primitive.arc.radius, primitive.arc.radius,
color, color,
@ -386,20 +383,18 @@ where
self.line_2d(bottom_left, top_left, polymorphic_color); self.line_2d(bottom_left, top_left, polymorphic_color);
self.line_2d(bottom_right, top_right, polymorphic_color); self.line_2d(bottom_right, top_right, polymorphic_color);
let start_angle_top = angle; let start_angle_top = angle - FRAC_PI_2;
let start_angle_bottom = PI + angle; let start_angle_bottom = angle + FRAC_PI_2;
// draw arcs // draw arcs
self.arc_2d( self.arc_2d(
top_center, Isometry2d::new(top_center, Rot2::radians(start_angle_top)),
start_angle_top,
PI, PI,
primitive.radius, primitive.radius,
polymorphic_color, polymorphic_color,
); );
self.arc_2d( self.arc_2d(
bottom_center, Isometry2d::new(bottom_center, Rot2::radians(start_angle_bottom)),
start_angle_bottom,
PI, PI,
primitive.radius, primitive.radius,
polymorphic_color, polymorphic_color,

View file

@ -57,9 +57,9 @@ pub mod prelude {
}, },
direction::{Dir2, Dir3, Dir3A}, direction::{Dir2, Dir3, Dir3A},
primitives::*, primitives::*,
BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4, BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d,
Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2, Isometry3d, Mat2, Mat3, Mat4, Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect,
Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles,
}; };
} }

View file

@ -1,8 +1,8 @@
//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging. //! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging.
use std::f32::consts::{PI, TAU}; use std::f32::consts::{FRAC_PI_2, PI, TAU};
use bevy::{color::palettes::css::*, prelude::*}; use bevy::{color::palettes::css::*, math::Isometry2d, prelude::*};
fn main() { fn main() {
App::new() App::new()
@ -87,7 +87,13 @@ fn draw_example_collection(
// Arcs default resolution is linearly interpolated between // Arcs default resolution is linearly interpolated between
// 1 and 32, using the arc length as scalar. // 1 and 32, using the arc length as scalar.
my_gizmos.arc_2d(Vec2::ZERO, sin / 10., PI / 2., 310., ORANGE_RED); my_gizmos.arc_2d(
Isometry2d::from_rotation(Rot2::radians(sin / 10.)),
FRAC_PI_2,
310.,
ORANGE_RED,
);
my_gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_2, 75.0, ORANGE_RED);
gizmos.arrow_2d( gizmos.arrow_2d(
Vec2::ZERO, Vec2::ZERO,