Curve gizmos integration (#14971)

# Objective

- Add gizmos integration for the new `Curve` things in the math lib

## Solution

- Add the following methods
  - `curve_2d(curve, sample_times, color)`
  - `curve_3d(curve, sample_times, color)`
  - `curve_gradient_2d(curve, sample_times_with_colors)`
  - `curve_gradient_3d(curve, sample_times_with_colors)`

## Testing

- I added examples of the 2D and 3D variants of the gradient curve
gizmos to the gizmos examples.

## Showcase

### 2D


![image](https://github.com/user-attachments/assets/01a75706-a7b4-4fc5-98d5-18018185c877)

```rust
    let domain = Interval::EVERYWHERE;
    let curve = function_curve(domain, |t| Vec2::new(t, (t / 25.0).sin() * 100.0));
    let resolution = ((time.elapsed_seconds().sin() + 1.0) * 50.0) as usize;
    let times_and_colors = (0..=resolution)
        .map(|n| n as f32 / resolution as f32)
        .map(|t| (t - 0.5) * 600.0)
        .map(|t| (t, TEAL.mix(&HOT_PINK, (t + 300.0) / 600.0)));
    gizmos.curve_gradient_2d(curve, times_and_colors);
```

### 3D


![image](https://github.com/user-attachments/assets/3fd23983-1ec9-46cd-baed-5b5e2dc935d0)

```rust
    let domain = Interval::EVERYWHERE;
    let curve = function_curve(domain, |t| {
        (Vec2::from((t * 10.0).sin_cos())).extend(t - 6.0)
    });
    let resolution = ((time.elapsed_seconds().sin() + 1.0) * 100.0) as usize;
    let times_and_colors = (0..=resolution)
        .map(|n| n as f32 / resolution as f32)
        .map(|t| t * 5.0)
        .map(|t| (t, TEAL.mix(&HOT_PINK, t / 5.0)));
    gizmos.curve_gradient_3d(curve, times_and_colors);
```
This commit is contained in:
Robert Walter 2024-08-29 16:48:22 +00:00 committed by GitHub
parent 1cca4f2968
commit 9e78433427
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 197 additions and 0 deletions

View file

@ -0,0 +1,175 @@
//! Additional [`Gizmos`] Functions -- Curves
//!
//! Includes the implementation of [`Gizmos::curve_2d`],
//! [`Gizmos::curve_3d`] and assorted support items.
use bevy_color::Color;
use bevy_math::{curve::Curve, Vec2, Vec3};
use crate::prelude::{GizmoConfigGroup, Gizmos};
impl<'w, 's, Config, Clear> Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a curve, at the given time points, sampling in 2D.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_2d` some type that implements the [`Curve`] trait and samples `Vec2`s
/// - `times` some iterable type yielding `f32` which will be used for sampling the curve
/// - `color` the color of the curve
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_2d(curve, (0..=100).map(|n| n as f32 / 100.0), RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_2d(
&mut self,
curve_2d: impl Curve<Vec2>,
times: impl IntoIterator<Item = f32>,
color: impl Into<Color>,
) {
self.linestrip_2d(curve_2d.sample_iter(times).flatten(), color);
}
/// Draw a curve, at the given time points, sampling in 3D.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_3d` some type that implements the [`Curve`] trait and samples `Vec3`s
/// - `times` some iterable type yielding `f32` which will be used for sampling the curve
/// - `color` the color of the curve
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| {
/// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t)
/// });
/// gizmos.curve_3d(curve, (0..=100).map(|n| n as f32 / 100.0), RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_3d(
&mut self,
curve_3d: impl Curve<Vec3>,
times: impl IntoIterator<Item = f32>,
color: impl Into<Color>,
) {
self.linestrip(curve_3d.sample_iter(times).flatten(), color);
}
/// Draw a curve, at the given time points, sampling in 2D, with a color gradient.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_2d` some type that implements the [`Curve`] trait and samples `Vec2`s
/// - `times_with_colors` some iterable type yielding `f32` which will be used for sampling
/// the curve together with the color at this position
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| Vec2::from(t.sin_cos()));
/// gizmos.curve_gradient_2d(
/// curve,
/// (0..=100).map(|n| n as f32 / 100.0)
/// .map(|t| (t, GREEN.mix(&RED, t)))
/// );
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_gradient_2d<C>(
&mut self,
curve_2d: impl Curve<Vec2>,
times_with_colors: impl IntoIterator<Item = (f32, C)>,
) where
C: Into<Color>,
{
self.linestrip_gradient_2d(
times_with_colors
.into_iter()
.filter_map(|(time, color)| curve_2d.sample(time).map(|sample| (sample, color))),
);
}
/// Draw a curve, at the given time points, sampling in 3D, with a color gradient.
///
/// This should be called for each frame the curve needs to be rendered.
///
/// Samples of time points outside of the curve's domain will be filtered out and won't
/// contribute to the rendering. If you wish to render the curve outside of its domain you need
/// to create a new curve with an extended domain.
///
/// # Arguments
/// - `curve_3d` some type that implements the [`Curve`] trait and samples `Vec3`s
/// - `times_with_colors` some iterable type yielding `f32` which will be used for sampling
/// the curve together with the color at this position
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::{Mix, palettes::basic::{GREEN, RED}};
/// fn system(mut gizmos: Gizmos) {
/// let domain = Interval::UNIT;
/// let curve = function_curve(domain, |t| {
/// let (x,y) = t.sin_cos();
/// Vec3::new(x, y, t)
/// });
/// gizmos.curve_gradient_3d(
/// curve,
/// (0..=100).map(|n| n as f32 / 100.0)
/// .map(|t| (t, GREEN.mix(&RED, t)))
/// );
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn curve_gradient_3d<C>(
&mut self,
curve_3d: impl Curve<Vec3>,
times_with_colors: impl IntoIterator<Item = (f32, C)>,
) where
C: Into<Color>,
{
self.linestrip_gradient(
times_with_colors
.into_iter()
.filter_map(|(time, color)| curve_3d.sample(time).map(|sample| (sample, color))),
);
}
}

View file

@ -37,6 +37,7 @@ pub mod arrows;
pub mod circles;
pub mod config;
pub mod cross;
pub mod curves;
pub mod gizmos;
pub mod grid;
pub mod primitives;

View file

@ -55,6 +55,7 @@ pub mod prelude {
CubicHermite, CubicNurbs, CubicNurbsError, CubicSegment, CyclicCubicGenerator,
RationalCurve, RationalGenerator, RationalSegment,
},
curve::*,
direction::{Dir2, Dir3, Dir3A},
primitives::*,
BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Isometry2d,

View file

@ -73,6 +73,15 @@ fn draw_example_collection(
FUCHSIA,
);
let domain = Interval::EVERYWHERE;
let curve = function_curve(domain, |t| Vec2::new(t, (t / 25.0).sin() * 100.0));
let resolution = ((time.elapsed_seconds().sin() + 1.0) * 50.0) as usize;
let times_and_colors = (0..=resolution)
.map(|n| n as f32 / resolution as f32)
.map(|t| (t - 0.5) * 600.0)
.map(|t| (t, TEAL.mix(&HOT_PINK, (t + 300.0) / 600.0)));
gizmos.curve_gradient_2d(curve, times_and_colors);
my_gizmos
.rounded_rect_2d(Isometry2d::IDENTITY, Vec2::splat(630.), BLACK)
.corner_radius((time.elapsed_seconds() / 3.).cos() * 100.);

View file

@ -131,6 +131,17 @@ fn draw_example_collection(
FUCHSIA,
);
let domain = Interval::EVERYWHERE;
let curve = function_curve(domain, |t| {
(Vec2::from((t * 10.0).sin_cos())).extend(t - 6.0)
});
let resolution = ((time.elapsed_seconds().sin() + 1.0) * 100.0) as usize;
let times_and_colors = (0..=resolution)
.map(|n| n as f32 / resolution as f32)
.map(|t| t * 5.0)
.map(|t| (t, TEAL.mix(&HOT_PINK, t / 5.0)));
gizmos.curve_gradient_3d(curve, times_and_colors);
my_gizmos.sphere(
Isometry3d::from_translation(Vec3::new(1., 0.5, 0.)),
0.5,