Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

110
vendor/bevy_gizmos/src/aabb.rs vendored Normal file
View File

@@ -0,0 +1,110 @@
//! A module adding debug visualization of [`Aabb`]s.
use bevy_app::{Plugin, PostUpdate};
use bevy_color::{Color, Oklcha};
use bevy_ecs::{
component::Component,
entity::Entity,
query::Without,
reflect::ReflectComponent,
schedule::IntoScheduleConfigs,
system::{Query, Res},
};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::primitives::Aabb;
use bevy_transform::{
components::{GlobalTransform, Transform},
TransformSystem,
};
use crate::{
config::{GizmoConfigGroup, GizmoConfigStore},
gizmos::Gizmos,
AppGizmoBuilder,
};
/// A [`Plugin`] that provides visualization of [`Aabb`]s for debugging.
pub struct AabbGizmoPlugin;
impl Plugin for AabbGizmoPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.register_type::<AabbGizmoConfigGroup>()
.init_gizmo_group::<AabbGizmoConfigGroup>()
.add_systems(
PostUpdate,
(
draw_aabbs,
draw_all_aabbs.run_if(|config: Res<GizmoConfigStore>| {
config.config::<AabbGizmoConfigGroup>().1.draw_all
}),
)
.after(bevy_render::view::VisibilitySystems::CalculateBounds)
.after(TransformSystem::TransformPropagate),
);
}
}
/// The [`GizmoConfigGroup`] used for debug visualizations of [`Aabb`] components on entities
#[derive(Clone, Default, Reflect, GizmoConfigGroup)]
#[reflect(Clone, Default)]
pub struct AabbGizmoConfigGroup {
/// Draws all bounding boxes in the scene when set to `true`.
///
/// To draw a specific entity's bounding box, you can add the [`ShowAabbGizmo`] component.
///
/// Defaults to `false`.
pub draw_all: bool,
/// The default color for bounding box gizmos.
///
/// A random color is chosen per box if `None`.
///
/// Defaults to `None`.
pub default_color: Option<Color>,
}
/// Add this [`Component`] to an entity to draw its [`Aabb`] component.
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component, Default, Debug)]
pub struct ShowAabbGizmo {
/// The color of the box.
///
/// The default color from the [`AabbGizmoConfigGroup`] config is used if `None`,
pub color: Option<Color>,
}
fn draw_aabbs(
query: Query<(Entity, &Aabb, &GlobalTransform, &ShowAabbGizmo)>,
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
) {
for (entity, &aabb, &transform, gizmo) in &query {
let color = gizmo
.color
.or(gizmos.config_ext.default_color)
.unwrap_or_else(|| color_from_entity(entity));
gizmos.cuboid(aabb_transform(aabb, transform), color);
}
}
fn draw_all_aabbs(
query: Query<(Entity, &Aabb, &GlobalTransform), Without<ShowAabbGizmo>>,
mut gizmos: Gizmos<AabbGizmoConfigGroup>,
) {
for (entity, &aabb, &transform) in &query {
let color = gizmos
.config_ext
.default_color
.unwrap_or_else(|| color_from_entity(entity));
gizmos.cuboid(aabb_transform(aabb, transform), color);
}
}
fn color_from_entity(entity: Entity) -> Color {
Oklcha::sequential_dispersed(entity.index()).into()
}
fn aabb_transform(aabb: Aabb, transform: GlobalTransform) -> GlobalTransform {
transform
* GlobalTransform::from(
Transform::from_translation(aabb.center.into())
.with_scale((aabb.half_extents * 2.).into()),
)
}

529
vendor/bevy_gizmos/src/arcs.rs vendored Normal file
View File

@@ -0,0 +1,529 @@
//! Additional [`GizmoBuffer`] Functions -- Arcs
//!
//! Includes the implementation of [`GizmoBuffer::arc_2d`],
//! and assorted support items.
use crate::{circles::DEFAULT_CIRCLE_RESOLUTION, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{Isometry2d, Isometry3d, Quat, Rot2, Vec2, Vec3};
use core::f32::consts::{FRAC_PI_2, TAU};
// === 2D ===
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw an arc, which is a part of the circumference of a circle, in 2D.
///
/// This should be called for each frame the arc needs to be rendered.
///
/// # Arguments
/// - `isometry` defines the translation and rotation of the arc.
/// - the translation specifies the center of the arc
/// - the rotation is counter-clockwise starting from `Vec2::Y`
/// - `arc_angle` sets the length of this arc, in radians.
/// - `radius` controls the distance from `position` to this arc, and thus its curvature.
/// - `color` sets the color to draw the arc.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use std::f32::consts::FRAC_PI_4;
/// # use bevy_color::palettes::basic::{GREEN, RED};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 1., GREEN);
///
/// // Arcs have 32 line-segments by default.
/// // You may want to increase this for larger arcs.
/// gizmos
/// .arc_2d(Isometry2d::IDENTITY, FRAC_PI_4, 5., RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn arc_2d(
&mut self,
isometry: impl Into<Isometry2d>,
arc_angle: f32,
radius: f32,
color: impl Into<Color>,
) -> Arc2dBuilder<'_, Config, Clear> {
Arc2dBuilder {
gizmos: self,
isometry: isometry.into(),
arc_angle,
radius,
color: color.into(),
resolution: None,
}
}
}
/// A builder returned by [`GizmoBuffer::arc_2d`].
pub struct Arc2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry2d,
arc_angle: f32,
radius: f32,
color: Color,
resolution: Option<u32>,
}
impl<Config, Clear> Arc2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the geometry of this arc.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution.replace(resolution);
self
}
}
impl<Config, Clear> Drop for Arc2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let resolution = self
.resolution
.unwrap_or_else(|| resolution_from_angle(self.arc_angle));
let positions =
arc_2d_inner(self.arc_angle, self.radius, resolution).map(|vec2| self.isometry * vec2);
self.gizmos.linestrip_2d(positions, self.color);
}
}
fn arc_2d_inner(arc_angle: f32, radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {
(0..=resolution)
.map(move |n| arc_angle * n as f32 / resolution as f32)
.map(|angle| angle + FRAC_PI_2)
.map(Vec2::from_angle)
.map(move |vec2| vec2 * radius)
}
// === 3D ===
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw an arc, which is a part of the circumference of a circle, in 3D. For default values
/// this is drawing a standard arc. A standard arc is defined as
///
/// - an arc with a center at `Vec3::ZERO`
/// - starting at `Vec3::X`
/// - embedded in the XZ plane
/// - rotates counterclockwise
///
/// This should be called for each frame the arc needs to be rendered.
///
/// # Arguments
/// - `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
/// - `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
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use std::f32::consts::PI;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// // rotation rotates normal to point in the direction of `Vec3::NEG_ONE`
/// let rotation = Quat::from_rotation_arc(Vec3::Y, Vec3::NEG_ONE.normalize());
///
/// gizmos
/// .arc_3d(
/// 270.0_f32.to_radians(),
/// 0.25,
/// Isometry3d::new(Vec3::ONE, rotation),
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn arc_3d(
&mut self,
angle: f32,
radius: f32,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Arc3dBuilder<'_, Config, Clear> {
Arc3dBuilder {
gizmos: self,
start_vertex: Vec3::X,
isometry: isometry.into(),
angle,
radius,
color: color.into(),
resolution: None,
}
}
/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
///
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.short_arc_3d_between(
/// Vec3::ONE,
/// Vec3::ONE + Vec3::NEG_ONE,
/// Vec3::ZERO,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case
#[inline]
pub fn short_arc_3d_between(
&mut self,
center: Vec3,
from: Vec3,
to: Vec3,
color: impl Into<Color>,
) -> Arc3dBuilder<'_, Config, Clear> {
self.arc_from_to(center, from, to, color, |x| x)
}
/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.long_arc_3d_between(
/// Vec3::ONE,
/// Vec3::ONE + Vec3::NEG_ONE,
/// Vec3::ZERO,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case.
#[inline]
pub fn long_arc_3d_between(
&mut self,
center: Vec3,
from: Vec3,
to: Vec3,
color: impl Into<Color>,
) -> Arc3dBuilder<'_, Config, Clear> {
self.arc_from_to(center, from, to, color, |angle| {
if angle > 0.0 {
TAU - angle
} else if angle < 0.0 {
-TAU - angle
} else {
0.0
}
})
}
#[inline]
fn arc_from_to(
&mut self,
center: Vec3,
from: Vec3,
to: Vec3,
color: impl Into<Color>,
angle_fn: impl Fn(f32) -> f32,
) -> Arc3dBuilder<'_, Config, Clear> {
// `from` and `to` can be the same here since in either case nothing gets rendered and the
// orientation ambiguity of `up` doesn't matter
let from_axis = (from - center).normalize_or_zero();
let to_axis = (to - center).normalize_or_zero();
let (up, angle) = Quat::from_rotation_arc(from_axis, to_axis).to_axis_angle();
let angle = angle_fn(angle);
let radius = center.distance(from);
let rotation = Quat::from_rotation_arc(Vec3::Y, up);
let start_vertex = rotation.inverse() * from_axis;
Arc3dBuilder {
gizmos: self,
start_vertex,
isometry: Isometry3d::new(center, rotation),
angle,
radius,
color: color.into(),
resolution: None,
}
}
/// Draws the shortest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
///
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.short_arc_2d_between(
/// Vec2::ZERO,
/// Vec2::X,
/// Vec2::Y,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case
#[inline]
pub fn short_arc_2d_between(
&mut self,
center: Vec2,
from: Vec2,
to: Vec2,
color: impl Into<Color>,
) -> Arc2dBuilder<'_, Config, Clear> {
self.arc_2d_from_to(center, from, to, color, core::convert::identity)
}
/// Draws the longest arc between two points (`from` and `to`) relative to a specified `center` point.
///
/// # Arguments
/// - `center`: The center point around which the arc is drawn.
/// - `from`: The starting point of the arc.
/// - `to`: The ending point of the arc.
/// - `color`: color of the arc
///
/// # Builder methods
/// The resolution of the arc (i.e. the level of detail) can be adjusted with the
/// `.resolution(...)` method.
///
/// # Examples
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::ORANGE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.long_arc_2d_between(
/// Vec2::ZERO,
/// Vec2::X,
/// Vec2::Y,
/// ORANGE
/// )
/// .resolution(100);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case.
#[inline]
pub fn long_arc_2d_between(
&mut self,
center: Vec2,
from: Vec2,
to: Vec2,
color: impl Into<Color>,
) -> Arc2dBuilder<'_, Config, Clear> {
self.arc_2d_from_to(center, from, to, color, |angle| angle - TAU)
}
#[inline]
fn arc_2d_from_to(
&mut self,
center: Vec2,
from: Vec2,
to: Vec2,
color: impl Into<Color>,
angle_fn: impl Fn(f32) -> f32,
) -> Arc2dBuilder<'_, Config, Clear> {
// `from` and `to` can be the same here since in either case nothing gets rendered and the
// orientation ambiguity of `up` doesn't matter
let from_axis = (from - center).normalize_or_zero();
let to_axis = (to - center).normalize_or_zero();
let rotation = Vec2::Y.angle_to(from_axis);
let arc_angle_raw = from_axis.angle_to(to_axis);
let arc_angle = angle_fn(arc_angle_raw);
let radius = center.distance(from);
Arc2dBuilder {
gizmos: self,
isometry: Isometry2d::new(center, Rot2::radians(rotation)),
arc_angle,
radius,
color: color.into(),
resolution: None,
}
}
}
/// A builder returned by [`GizmoBuffer::arc_2d`].
pub struct Arc3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// this is the vertex the arc starts on in the XZ plane. For the normal arc_3d method this is
// always starting at Vec3::X. For the short/long arc methods we actually need a way to start
// at the from position and this is where this internal field comes into play. Some implicit
// assumptions:
//
// 1. This is always in the XZ plane
// 2. This is always normalized
//
// DO NOT expose this field to users as it is easy to mess this up
start_vertex: Vec3,
isometry: Isometry3d,
angle: f32,
radius: f32,
color: Color,
resolution: Option<u32>,
}
impl<Config, Clear> Arc3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines for this arc.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution.replace(resolution);
self
}
}
impl<Config, Clear> Drop for Arc3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let resolution = self
.resolution
.unwrap_or_else(|| resolution_from_angle(self.angle));
let positions = arc_3d_inner(
self.start_vertex,
self.isometry,
self.angle,
self.radius,
resolution,
);
self.gizmos.linestrip(positions, self.color);
}
}
fn arc_3d_inner(
start_vertex: Vec3,
isometry: Isometry3d,
angle: f32,
radius: f32,
resolution: u32,
) -> impl Iterator<Item = Vec3> {
// drawing arcs bigger than TAU degrees or smaller than -TAU degrees makes no sense since
// we won't see the overlap and we would just decrease the level of details since the resolution
// would be larger
let angle = angle.clamp(-TAU, TAU);
(0..=resolution)
.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 |vec3| vec3 * radius)
.map(move |vec3| isometry * vec3)
}
// helper function for getting a default value for the resolution parameter
fn resolution_from_angle(angle: f32) -> u32 {
((angle.abs() / TAU) * DEFAULT_CIRCLE_RESOLUTION as f32).ceil() as u32
}

232
vendor/bevy_gizmos/src/arrows.rs vendored Normal file
View File

@@ -0,0 +1,232 @@
//! Additional [`GizmoBuffer`] Functions -- Arrows
//!
//! Includes the implementation of [`GizmoBuffer::arrow`] and [`GizmoBuffer::arrow_2d`],
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::{
palettes::basic::{BLUE, GREEN, RED},
Color,
};
use bevy_math::{Quat, Vec2, Vec3, Vec3Swizzles};
use bevy_transform::TransformPoint;
/// A builder returned by [`GizmoBuffer::arrow`] and [`GizmoBuffer::arrow_2d`]
pub struct ArrowBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
start: Vec3,
end: Vec3,
color: Color,
double_ended: bool,
tip_length: f32,
}
impl<Config, Clear> ArrowBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Change the length of the tips to be `length`.
/// The default tip length is [length of the arrow]/10.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN)
/// .with_tip_length(3.);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[doc(alias = "arrow_head_length")]
pub fn with_tip_length(mut self, length: f32) -> Self {
self.tip_length = length;
self
}
/// Adds another tip to the arrow, appended in the start point.
/// the default is only one tip at the end point.
pub fn with_double_end(mut self) -> Self {
self.double_ended = true;
self
}
}
impl<Config, Clear> Drop for ArrowBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draws the arrow, by drawing lines with the stored [`GizmoBuffer`]
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
// first, draw the body of the arrow
self.gizmos.line(self.start, self.end, self.color);
// now the hard part is to draw the head in a sensible way
// put us in a coordinate system where the arrow is pointing towards +x and ends at the origin
let pointing_end = (self.end - self.start).normalize();
let rotation_end = Quat::from_rotation_arc(Vec3::X, pointing_end);
let tips = [
Vec3::new(-1., 1., 0.),
Vec3::new(-1., 0., 1.),
Vec3::new(-1., -1., 0.),
Vec3::new(-1., 0., -1.),
];
// - extend the vectors so their length is `tip_length`
// - rotate the world so +x is facing in the same direction as the arrow
// - translate over to the tip of the arrow
let tips_end = tips.map(|v| rotation_end * (v.normalize() * self.tip_length) + self.end);
for v in tips_end {
// then actually draw the tips
self.gizmos.line(self.end, v, self.color);
}
if self.double_ended {
let pointing_start = (self.start - self.end).normalize();
let rotation_start = Quat::from_rotation_arc(Vec3::X, pointing_start);
let tips_start =
tips.map(|v| rotation_start * (v.normalize() * self.tip_length) + self.start);
for v in tips_start {
// draw the start points tips
self.gizmos.line(self.start, v, self.color);
}
}
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw an arrow in 3D, from `start` to `end`. Has four tips for convenient viewing from any direction.
///
/// This should be called for each frame the arrow needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arrow(Vec3::ZERO, Vec3::ONE, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn arrow(
&mut self,
start: Vec3,
end: Vec3,
color: impl Into<Color>,
) -> ArrowBuilder<'_, Config, Clear> {
let length = (end - start).length();
ArrowBuilder {
gizmos: self,
start,
end,
color: color.into(),
double_ended: false,
tip_length: length / 10.,
}
}
/// Draw an arrow in 2D (on the xy plane), from `start` to `end`.
///
/// This should be called for each frame the arrow needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.arrow_2d(Vec2::ZERO, Vec2::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn arrow_2d(
&mut self,
start: Vec2,
end: Vec2,
color: impl Into<Color>,
) -> ArrowBuilder<'_, Config, Clear> {
self.arrow(start.extend(0.), end.extend(0.), color)
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a set of axes local to the given transform (`transform`), with length scaled by a factor
/// of `base_length`.
///
/// This should be called for each frame the axes need to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_transform::components::Transform;
/// # #[derive(Component)]
/// # struct MyComponent;
/// fn draw_axes(
/// mut gizmos: Gizmos,
/// query: Query<&Transform, With<MyComponent>>,
/// ) {
/// for &transform in &query {
/// gizmos.axes(transform, 1.);
/// }
/// }
/// # bevy_ecs::system::assert_is_system(draw_axes);
/// ```
pub fn axes(&mut self, transform: impl TransformPoint, base_length: f32) {
let start = transform.transform_point(Vec3::ZERO);
let end_x = transform.transform_point(base_length * Vec3::X);
let end_y = transform.transform_point(base_length * Vec3::Y);
let end_z = transform.transform_point(base_length * Vec3::Z);
self.arrow(start, end_x, RED);
self.arrow(start, end_y, GREEN);
self.arrow(start, end_z, BLUE);
}
/// Draw a set of axes local to the given transform (`transform`), with length scaled by a factor
/// of `base_length`.
///
/// This should be called for each frame the axes need to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_ecs::prelude::*;
/// # use bevy_transform::components::Transform;
/// # #[derive(Component)]
/// # struct AxesComponent;
/// fn draw_axes_2d(
/// mut gizmos: Gizmos,
/// query: Query<&Transform, With<AxesComponent>>,
/// ) {
/// for &transform in &query {
/// gizmos.axes_2d(transform, 1.);
/// }
/// }
/// # bevy_ecs::system::assert_is_system(draw_axes_2d);
/// ```
pub fn axes_2d(&mut self, transform: impl TransformPoint, base_length: f32) {
let start = transform.transform_point(Vec3::ZERO);
let end_x = transform.transform_point(base_length * Vec3::X);
let end_y = transform.transform_point(base_length * Vec3::Y);
self.arrow_2d(start.xy(), end_x.xy(), RED);
self.arrow_2d(start.xy(), end_y.xy(), GREEN);
}
}

363
vendor/bevy_gizmos/src/circles.rs vendored Normal file
View File

@@ -0,0 +1,363 @@
//! Additional [`GizmoBuffer`] Functions -- Circles
//!
//! Includes the implementation of [`GizmoBuffer::circle`] and [`GizmoBuffer::circle_2d`],
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{ops, Isometry2d, Isometry3d, Quat, Vec2, Vec3};
use core::f32::consts::TAU;
pub(crate) const DEFAULT_CIRCLE_RESOLUTION: u32 = 32;
fn ellipse_inner(half_size: Vec2, resolution: u32) -> impl Iterator<Item = Vec2> {
(0..resolution + 1).map(move |i| {
let angle = i as f32 * TAU / resolution as f32;
let (x, y) = ops::sin_cos(angle);
Vec2::new(x, y) * half_size
})
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// 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(Isometry3d::IDENTITY, Vec2::new(5., 1.), RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ellipse(
&mut self,
isometry: impl Into<Isometry3d>,
half_size: Vec2,
color: impl Into<Color>,
) -> EllipseBuilder<'_, Config, Clear> {
EllipseBuilder {
gizmos: self,
isometry: isometry.into(),
half_size,
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
/// 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// 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(Isometry2d::from_rotation(Rot2::degrees(180.0)), Vec2::new(5., 1.), RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ellipse_2d(
&mut self,
isometry: impl Into<Isometry2d>,
half_size: Vec2,
color: impl Into<Color>,
) -> Ellipse2dBuilder<'_, Config, Clear> {
Ellipse2dBuilder {
gizmos: self,
isometry: isometry.into(),
half_size,
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
/// Draw a circle in 3D with the given `isometry` applied.
///
/// If `isometry == Isometry3d::IDENTITY` then
///
/// - the center is at `Vec3::ZERO`
/// - the radius is aligned with the `Vec3::X` and `Vec3::Y` axes.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.circle(Isometry3d::IDENTITY, 1., GREEN);
///
/// // Circles have 32 line-segments by default.
/// // You may want to increase this for larger circles.
/// gizmos
/// .circle(Isometry3d::IDENTITY, 5., RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn circle(
&mut self,
isometry: impl Into<Isometry3d>,
radius: f32,
color: impl Into<Color>,
) -> EllipseBuilder<'_, Config, Clear> {
EllipseBuilder {
gizmos: self,
isometry: isometry.into(),
half_size: Vec2::splat(radius),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
/// 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// 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(Isometry2d::IDENTITY, 5., RED)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn circle_2d(
&mut self,
isometry: impl Into<Isometry2d>,
radius: f32,
color: impl Into<Color>,
) -> Ellipse2dBuilder<'_, Config, Clear> {
Ellipse2dBuilder {
gizmos: self,
isometry: isometry.into(),
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 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::Color;
/// fn system(mut gizmos: Gizmos) {
/// 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(Isometry3d::IDENTITY, 5., Color::BLACK)
/// .resolution(64);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn sphere(
&mut self,
isometry: impl Into<Isometry3d>,
radius: f32,
color: impl Into<Color>,
) -> SphereBuilder<'_, Config, Clear> {
SphereBuilder {
gizmos: self,
radius,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_CIRCLE_RESOLUTION,
}
}
}
/// A builder returned by [`GizmoBuffer::ellipse`].
pub struct EllipseBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
half_size: Vec2,
color: Color,
resolution: u32,
}
impl<Config, Clear> EllipseBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the geometry of this ellipse.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> Drop for EllipseBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let positions = ellipse_inner(self.half_size, self.resolution)
.map(|vec2| self.isometry * vec2.extend(0.));
self.gizmos.linestrip(positions, self.color);
}
}
/// A builder returned by [`GizmoBuffer::ellipse_2d`].
pub struct Ellipse2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry2d,
half_size: Vec2,
color: Color,
resolution: u32,
}
impl<Config, Clear> Ellipse2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments used to approximate the geometry of this ellipse.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> Drop for Ellipse2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments for this ellipse.
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
};
let positions =
ellipse_inner(self.half_size, self.resolution).map(|vec2| self.isometry * vec2);
self.gizmos.linestrip_2d(positions, self.color);
}
}
/// A builder returned by [`GizmoBuffer::sphere`].
pub struct SphereBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the sphere
radius: f32,
isometry: Isometry3d,
// Color of the sphere
color: Color,
// Number of line-segments used to approximate the sphere geometry
resolution: u32,
}
impl<Config, Clear> SphereBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments used to approximate the sphere geometry.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> Drop for SphereBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
// draws one great circle around each of the local axes
Vec3::AXES.into_iter().for_each(|axis| {
let axis_rotation = Isometry3d::from_rotation(Quat::from_rotation_arc(Vec3::Z, axis));
self.gizmos
.circle(self.isometry * axis_rotation, self.radius, self.color)
.resolution(self.resolution);
});
}
}

258
vendor/bevy_gizmos/src/config.rs vendored Normal file
View File

@@ -0,0 +1,258 @@
//! A module for the [`GizmoConfig<T>`] [`Resource`].
pub use bevy_gizmos_macros::GizmoConfigGroup;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
use {crate::GizmoAsset, bevy_asset::Handle, bevy_ecs::component::Component};
use bevy_ecs::{reflect::ReflectResource, resource::Resource};
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use bevy_utils::TypeIdMap;
use core::{
any::TypeId,
hash::Hash,
ops::{Deref, DerefMut},
panic,
};
/// An enum configuring how line joints will be drawn.
#[derive(Debug, Default, Copy, Clone, Reflect, PartialEq, Eq, Hash)]
#[reflect(Default, PartialEq, Hash, Clone)]
pub enum GizmoLineJoint {
/// Does not draw any line joints.
#[default]
None,
/// Extends both lines at the joining point until they meet in a sharp point.
Miter,
/// Draws a round corner with the specified resolution between the two lines.
///
/// The resolution determines the amount of triangles drawn per joint,
/// e.g. `GizmoLineJoint::Round(4)` will draw 4 triangles at each line joint.
Round(u32),
/// Draws a bevel, a straight line in this case, to connect the ends of both lines.
Bevel,
}
/// An enum used to configure the style of gizmo lines, similar to CSS line-style
#[derive(Copy, Clone, Debug, Default, PartialEq, Reflect)]
#[reflect(Default, PartialEq, Hash, Clone)]
#[non_exhaustive]
pub enum GizmoLineStyle {
/// A solid line without any decorators
#[default]
Solid,
/// A dotted line
Dotted,
/// A dashed line with configurable gap and line sizes
Dashed {
/// The length of the gap in `line_width`s
gap_scale: f32,
/// The length of the visible line in `line_width`s
line_scale: f32,
},
}
impl Eq for GizmoLineStyle {}
impl Hash for GizmoLineStyle {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
match self {
Self::Solid => {
0u64.hash(state);
}
Self::Dotted => 1u64.hash(state),
Self::Dashed {
gap_scale,
line_scale,
} => {
2u64.hash(state);
gap_scale.to_bits().hash(state);
line_scale.to_bits().hash(state);
}
}
}
}
/// A trait used to create gizmo configs groups.
///
/// Here you can store additional configuration for you gizmo group not covered by [`GizmoConfig`]
///
/// Make sure to derive [`Default`] + [`Reflect`] and register in the app using `app.init_gizmo_group::<T>()`
pub trait GizmoConfigGroup: Reflect + TypePath + Default {}
/// The default gizmo config group.
#[derive(Default, Reflect, GizmoConfigGroup)]
#[reflect(Default)]
pub struct DefaultGizmoConfigGroup;
/// Used when the gizmo config group needs to be type-erased.
/// Also used for retained gizmos, which can't have a gizmo config group.
#[derive(Default, Reflect, GizmoConfigGroup, Debug, Clone)]
#[reflect(Default, Clone)]
pub struct ErasedGizmoConfigGroup;
/// A [`Resource`] storing [`GizmoConfig`] and [`GizmoConfigGroup`] structs
///
/// Use `app.init_gizmo_group::<T>()` to register a custom config group.
#[derive(Reflect, Resource, Default)]
#[reflect(Resource, Default)]
pub struct GizmoConfigStore {
// INVARIANT: must map TypeId::of::<T>() to correct type T
#[reflect(ignore)]
store: TypeIdMap<(GizmoConfig, Box<dyn Reflect>)>,
}
impl GizmoConfigStore {
/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
pub fn get_config_dyn(&self, config_type_id: &TypeId) -> Option<(&GizmoConfig, &dyn Reflect)> {
let (config, ext) = self.store.get(config_type_id)?;
Some((config, ext.deref()))
}
/// Returns [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
pub fn config<T: GizmoConfigGroup>(&self) -> (&GizmoConfig, &T) {
let Some((config, ext)) = self.get_config_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
};
// hash map invariant guarantees that &dyn Reflect is of correct type T
let ext = ext.as_any().downcast_ref().unwrap();
(config, ext)
}
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`TypeId`] of a [`GizmoConfigGroup`]
pub fn get_config_mut_dyn(
&mut self,
config_type_id: &TypeId,
) -> Option<(&mut GizmoConfig, &mut dyn Reflect)> {
let (config, ext) = self.store.get_mut(config_type_id)?;
Some((config, ext.deref_mut()))
}
/// Returns mutable [`GizmoConfig`] and [`GizmoConfigGroup`] associated with [`GizmoConfigGroup`] `T`
pub fn config_mut<T: GizmoConfigGroup>(&mut self) -> (&mut GizmoConfig, &mut T) {
let Some((config, ext)) = self.get_config_mut_dyn(&TypeId::of::<T>()) else {
panic!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?", T::type_path());
};
// hash map invariant guarantees that &dyn Reflect is of correct type T
let ext = ext.as_any_mut().downcast_mut().unwrap();
(config, ext)
}
/// Returns an iterator over all [`GizmoConfig`]s.
pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &GizmoConfig, &dyn Reflect)> + '_ {
self.store
.iter()
.map(|(id, (config, ext))| (id, config, ext.deref()))
}
/// Returns an iterator over all [`GizmoConfig`]s, by mutable reference.
pub fn iter_mut(
&mut self,
) -> impl Iterator<Item = (&TypeId, &mut GizmoConfig, &mut dyn Reflect)> + '_ {
self.store
.iter_mut()
.map(|(id, (config, ext))| (id, config, ext.deref_mut()))
}
/// Inserts [`GizmoConfig`] and [`GizmoConfigGroup`] replacing old values
pub fn insert<T: GizmoConfigGroup>(&mut self, config: GizmoConfig, ext_config: T) {
// INVARIANT: hash map must correctly map TypeId::of::<T>() to &dyn Reflect of type T
self.store
.insert(TypeId::of::<T>(), (config, Box::new(ext_config)));
}
pub(crate) fn register<T: GizmoConfigGroup>(&mut self) {
self.insert(GizmoConfig::default(), T::default());
}
}
/// A struct that stores configuration for gizmos.
#[derive(Clone, Reflect, Debug)]
#[reflect(Clone, Default)]
pub struct GizmoConfig {
/// Set to `false` to stop drawing gizmos.
///
/// Defaults to `true`.
pub enabled: bool,
/// Line settings.
pub line: GizmoLineConfig,
/// How closer to the camera than real geometry the gizmos should be.
///
/// In 2D this setting has no effect and is effectively always -1.
///
/// Value between -1 and 1 (inclusive).
/// * 0 means that there is no change to the line position when rendering
/// * 1 means it is furthest away from camera as possible
/// * -1 means that it will always render in front of other things.
///
/// This is typically useful if you are drawing wireframes on top of polygons
/// and your wireframe is z-fighting (flickering on/off) with your main model.
/// You would set this value to a negative number close to 0.
pub depth_bias: f32,
/// Describes which rendering layers gizmos will be rendered to.
///
/// Gizmos will only be rendered to cameras with intersecting layers.
#[cfg(feature = "bevy_render")]
pub render_layers: bevy_render::view::RenderLayers,
}
impl Default for GizmoConfig {
fn default() -> Self {
Self {
enabled: true,
line: Default::default(),
depth_bias: 0.,
#[cfg(feature = "bevy_render")]
render_layers: Default::default(),
}
}
}
/// A struct that stores configuration for gizmos.
#[derive(Clone, Reflect, Debug)]
#[reflect(Clone, Default)]
pub struct GizmoLineConfig {
/// Line width specified in pixels.
///
/// If `perspective` is `true` then this is the size in pixels at the camera's near plane.
///
/// Defaults to `2.0`.
pub width: f32,
/// Apply perspective to gizmo lines.
///
/// This setting only affects 3D, non-orthographic cameras.
///
/// Defaults to `false`.
pub perspective: bool,
/// Determine the style of gizmo lines.
pub style: GizmoLineStyle,
/// Describe how lines should join.
pub joints: GizmoLineJoint,
}
impl Default for GizmoLineConfig {
fn default() -> Self {
Self {
width: 2.,
perspective: false,
style: GizmoLineStyle::Solid,
joints: GizmoLineJoint::None,
}
}
}
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
#[derive(Component)]
pub(crate) struct GizmoMeshConfig {
pub line_perspective: bool,
pub line_style: GizmoLineStyle,
pub line_joints: GizmoLineJoint,
pub render_layers: bevy_render::view::RenderLayers,
pub handle: Handle<GizmoAsset>,
}

84
vendor/bevy_gizmos/src/cross.rs vendored Normal file
View File

@@ -0,0 +1,84 @@
//! Additional [`GizmoBuffer`] Functions -- Crosses
//!
//! Includes the implementation of [`GizmoBuffer::cross`] and [`GizmoBuffer::cross_2d`],
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3};
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::WHITE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cross(Isometry3d::IDENTITY, 0.5, WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross(
&mut self,
isometry: impl Into<Isometry3d>,
half_size: f32,
color: impl Into<Color>,
) {
let isometry = isometry.into();
let color: Color = color.into();
[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 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::WHITE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cross_2d(Isometry2d::IDENTITY, 0.5, WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross_2d(
&mut self,
isometry: impl Into<Isometry2d>,
half_size: f32,
color: impl Into<Color>,
) {
let isometry = isometry.into();
let color: Color = color.into();
[Vec2::X, Vec2::Y]
.map(|axis| axis * half_size)
.into_iter()
.for_each(|axis| {
self.line_2d(isometry * axis, isometry * (-axis), color);
});
}
}

178
vendor/bevy_gizmos/src/curves.rs vendored Normal file
View File

@@ -0,0 +1,178 @@
//! Additional [`GizmoBuffer`] Functions -- Curves
//!
//! Includes the implementation of [`GizmoBuffer::curve_2d`],
//! [`GizmoBuffer::curve_3d`] and assorted support items.
use bevy_color::Color;
use bevy_math::{
curve::{Curve, CurveExt},
Vec2, Vec3,
};
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
impl<Config, Clear> GizmoBuffer<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 = FunctionCurve::new(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 = FunctionCurve::new(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 = FunctionCurve::new(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 = FunctionCurve::new(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))),
);
}
}

844
vendor/bevy_gizmos/src/gizmos.rs vendored Normal file
View File

@@ -0,0 +1,844 @@
//! A module for the [`Gizmos`] [`SystemParam`].
use core::{
iter,
marker::PhantomData,
mem,
ops::{Deref, DerefMut},
};
use bevy_color::{Color, LinearRgba};
use bevy_ecs::{
component::Tick,
resource::Resource,
system::{
Deferred, ReadOnlySystemParam, Res, SystemBuffer, SystemMeta, SystemParam,
SystemParamValidationError,
},
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::TransformPoint;
use bevy_utils::default;
use crate::{
config::{DefaultGizmoConfigGroup, GizmoConfigGroup, GizmoConfigStore},
prelude::GizmoConfig,
};
/// Storage of gizmo primitives.
#[derive(Resource)]
pub struct GizmoStorage<Config, Clear> {
pub(crate) list_positions: Vec<Vec3>,
pub(crate) list_colors: Vec<LinearRgba>,
pub(crate) strip_positions: Vec<Vec3>,
pub(crate) strip_colors: Vec<LinearRgba>,
marker: PhantomData<(Config, Clear)>,
}
impl<Config, Clear> Default for GizmoStorage<Config, Clear> {
fn default() -> Self {
Self {
list_positions: default(),
list_colors: default(),
strip_positions: default(),
strip_colors: default(),
marker: PhantomData,
}
}
}
impl<Config, Clear> GizmoStorage<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Combine the other gizmo storage with this one.
pub fn append_storage<OtherConfig, OtherClear>(
&mut self,
other: &GizmoStorage<OtherConfig, OtherClear>,
) {
self.list_positions.extend(other.list_positions.iter());
self.list_colors.extend(other.list_colors.iter());
self.strip_positions.extend(other.strip_positions.iter());
self.strip_colors.extend(other.strip_colors.iter());
}
pub(crate) fn swap<OtherConfig, OtherClear>(
&mut self,
other: &mut GizmoStorage<OtherConfig, OtherClear>,
) {
mem::swap(&mut self.list_positions, &mut other.list_positions);
mem::swap(&mut self.list_colors, &mut other.list_colors);
mem::swap(&mut self.strip_positions, &mut other.strip_positions);
mem::swap(&mut self.strip_colors, &mut other.strip_colors);
}
/// Clear this gizmo storage of any requested gizmos.
pub fn clear(&mut self) {
self.list_positions.clear();
self.list_colors.clear();
self.strip_positions.clear();
self.strip_colors.clear();
}
}
/// Swap buffer for a specific clearing context.
///
/// This is to stash/store the default/requested gizmos so another context can
/// be substituted for that duration.
pub struct Swap<Clear>(PhantomData<Clear>);
/// A [`SystemParam`] for drawing gizmos.
///
/// They are drawn in immediate mode, which means they will be rendered only for
/// the frames, or ticks when in [`FixedMain`](bevy_app::FixedMain), in which
/// they are spawned.
///
/// A system in [`Main`](bevy_app::Main) will be cleared each rendering
/// frame, while a system in [`FixedMain`](bevy_app::FixedMain) will be
/// cleared each time the [`RunFixedMainLoop`](bevy_app::RunFixedMainLoop)
/// schedule is run.
///
/// Gizmos should be spawned before the [`Last`](bevy_app::Last) schedule
/// to ensure they are drawn.
///
/// To set up your own clearing context (useful for custom scheduling similar
/// to [`FixedMain`](bevy_app::FixedMain)):
///
/// ```
/// use bevy_gizmos::{prelude::*, *, gizmos::GizmoStorage};
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::{schedule::ScheduleLabel, prelude::*};
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct StartOfMyContext;
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct EndOfMyContext;
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct StartOfRun;
/// # #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
/// # struct EndOfRun;
/// # struct MyContext;
/// struct ClearContextSetup;
/// impl Plugin for ClearContextSetup {
/// fn build(&self, app: &mut App) {
/// app.init_resource::<GizmoStorage<DefaultGizmoConfigGroup, MyContext>>()
/// // Make sure this context starts/ends cleanly if inside another context. E.g. it
/// // should start after the parent context starts and end after the parent context ends.
/// .add_systems(StartOfMyContext, start_gizmo_context::<DefaultGizmoConfigGroup, MyContext>)
/// // If not running multiple times, put this with [`start_gizmo_context`].
/// .add_systems(StartOfRun, clear_gizmo_context::<DefaultGizmoConfigGroup, MyContext>)
/// // If not running multiple times, put this with [`end_gizmo_context`].
/// .add_systems(EndOfRun, collect_requested_gizmos::<DefaultGizmoConfigGroup, MyContext>)
/// .add_systems(EndOfMyContext, end_gizmo_context::<DefaultGizmoConfigGroup, MyContext>)
/// .add_systems(
/// Last,
/// propagate_gizmos::<DefaultGizmoConfigGroup, MyContext>.before(UpdateGizmoMeshes),
/// );
/// }
/// }
/// ```
pub struct Gizmos<'w, 's, Config = DefaultGizmoConfigGroup, Clear = ()>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
buffer: Deferred<'s, GizmoBuffer<Config, Clear>>,
/// The currently used [`GizmoConfig`]
pub config: &'w GizmoConfig,
/// The currently used [`GizmoConfigGroup`]
pub config_ext: &'w Config,
}
impl<'w, 's, Config, Clear> Deref for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Target = GizmoBuffer<Config, Clear>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl<'w, 's, Config, Clear> DerefMut for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
type GizmosState<Config, Clear> = (
Deferred<'static, GizmoBuffer<Config, Clear>>,
Res<'static, GizmoConfigStore>,
);
#[doc(hidden)]
pub struct GizmosFetchState<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
state: <GizmosState<Config, Clear> as SystemParam>::State,
}
#[expect(
unsafe_code,
reason = "We cannot implement SystemParam without using unsafe code."
)]
// SAFETY: All methods are delegated to existing `SystemParam` implementations
unsafe impl<Config, Clear> SystemParam for Gizmos<'_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type State = GizmosFetchState<Config, Clear>;
type Item<'w, 's> = Gizmos<'w, 's, Config, Clear>;
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
GizmosFetchState {
state: GizmosState::<Config, Clear>::init_state(world, system_meta),
}
}
unsafe fn new_archetype(
state: &mut Self::State,
archetype: &bevy_ecs::archetype::Archetype,
system_meta: &mut SystemMeta,
) {
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
unsafe {
GizmosState::<Config, Clear>::new_archetype(&mut state.state, archetype, system_meta);
};
}
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
GizmosState::<Config, Clear>::apply(&mut state.state, system_meta, world);
}
#[inline]
unsafe fn validate_param(
state: &Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError> {
// SAFETY: Delegated to existing `SystemParam` implementations.
unsafe { GizmosState::<Config, Clear>::validate_param(&state.state, system_meta, world) }
}
#[inline]
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
change_tick: Tick,
) -> Self::Item<'w, 's> {
// SAFETY: Delegated to existing `SystemParam` implementations.
let (mut f0, f1) = unsafe {
GizmosState::<Config, Clear>::get_param(
&mut state.state,
system_meta,
world,
change_tick,
)
};
// Accessing the GizmoConfigStore in every API call reduces performance significantly.
// Implementing SystemParam manually allows us to cache whether the config is currently enabled.
// Having this available allows for cheap early returns when gizmos are disabled.
let (config, config_ext) = f1.into_inner().config::<Config>();
f0.enabled = config.enabled;
Gizmos {
buffer: f0,
config,
config_ext,
}
}
}
#[expect(
unsafe_code,
reason = "We cannot implement ReadOnlySystemParam without using unsafe code."
)]
// Safety: Each field is `ReadOnlySystemParam`, and Gizmos SystemParam does not mutate world
unsafe impl<'w, 's, Config, Clear> ReadOnlySystemParam for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
Deferred<'s, GizmoBuffer<Config, Clear>>: ReadOnlySystemParam,
Res<'w, GizmoConfigStore>: ReadOnlySystemParam,
{
}
/// Buffer for gizmo vertex data.
#[derive(Debug, Clone, Reflect)]
#[reflect(Default)]
pub struct GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub(crate) enabled: bool,
pub(crate) list_positions: Vec<Vec3>,
pub(crate) list_colors: Vec<LinearRgba>,
pub(crate) strip_positions: Vec<Vec3>,
pub(crate) strip_colors: Vec<LinearRgba>,
#[reflect(ignore, clone)]
pub(crate) marker: PhantomData<(Config, Clear)>,
}
impl<Config, Clear> Default for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn default() -> Self {
GizmoBuffer {
enabled: true,
list_positions: Vec::new(),
list_colors: Vec::new(),
strip_positions: Vec::new(),
strip_colors: Vec::new(),
marker: PhantomData,
}
}
}
/// Read-only view into [`GizmoBuffer`] data.
pub struct GizmoBufferView<'a> {
/// Vertex positions for line-list topology.
pub list_positions: &'a Vec<Vec3>,
/// Vertex colors for line-list topology.
pub list_colors: &'a Vec<LinearRgba>,
/// Vertex positions for line-strip topology.
pub strip_positions: &'a Vec<Vec3>,
/// Vertex colors for line-strip topology.
pub strip_colors: &'a Vec<LinearRgba>,
}
impl<Config, Clear> SystemBuffer for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) {
let mut storage = world.resource_mut::<GizmoStorage<Config, Clear>>();
storage.list_positions.append(&mut self.list_positions);
storage.list_colors.append(&mut self.list_colors);
storage.strip_positions.append(&mut self.strip_positions);
storage.strip_colors.append(&mut self.strip_colors);
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Clear all data.
pub fn clear(&mut self) {
self.list_positions.clear();
self.list_colors.clear();
self.strip_positions.clear();
self.strip_colors.clear();
}
/// Read-only view into the buffers data.
pub fn buffer(&self) -> GizmoBufferView {
let GizmoBuffer {
list_positions,
list_colors,
strip_positions,
strip_colors,
..
} = self;
GizmoBufferView {
list_positions,
list_colors,
strip_positions,
strip_colors,
}
}
/// Draw a line in 3D from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line(Vec3::ZERO, Vec3::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line(&mut self, start: Vec3, end: Vec3, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.extend_list_positions([start, end]);
self.add_list_color(color, 2);
}
/// Draw a line in 3D with a color gradient from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_gradient(Vec3::ZERO, Vec3::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line_gradient<C: Into<Color>>(
&mut self,
start: Vec3,
end: Vec3,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.extend_list_positions([start, end]);
self.extend_list_colors([start_color, end_color]);
}
/// Draw a line in 3D from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ray(Vec3::Y, Vec3::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray(&mut self, start: Vec3, vector: Vec3, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.line(start, start + vector, color);
}
/// Draw a line in 3D with a color gradient from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ray_gradient(Vec3::Y, Vec3::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray_gradient<C: Into<Color>>(
&mut self,
start: Vec3,
vector: Vec3,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.line_gradient(start, start + vector, start_color, end_color);
}
/// Draw a line in 3D made of straight segments between the points.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip([Vec3::ZERO, Vec3::X, Vec3::Y], GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip(
&mut self,
positions: impl IntoIterator<Item = Vec3>,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
self.extend_strip_positions(positions);
let len = self.strip_positions.len();
let linear_color = LinearRgba::from(color.into());
self.strip_colors.resize(len - 1, linear_color);
self.strip_colors.push(LinearRgba::NAN);
}
/// Draw a line in 3D made of straight segments between the points, with a color gradient.
///
/// This should be called for each frame the lines need to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{BLUE, GREEN, RED};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip_gradient([
/// (Vec3::ZERO, GREEN),
/// (Vec3::X, RED),
/// (Vec3::Y, BLUE)
/// ]);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip_gradient<C: Into<Color>>(
&mut self,
points: impl IntoIterator<Item = (Vec3, C)>,
) {
if !self.enabled {
return;
}
let points = points.into_iter();
let GizmoBuffer {
strip_positions,
strip_colors,
..
} = self;
let (min, _) = points.size_hint();
strip_positions.reserve(min);
strip_colors.reserve(min);
for (position, color) in points {
strip_positions.push(position);
strip_colors.push(LinearRgba::from(color.into()));
}
strip_positions.push(Vec3::NAN);
strip_colors.push(LinearRgba::NAN);
}
/// 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rect(Isometry3d::IDENTITY, Vec2::ONE, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn rect(&mut self, isometry: impl Into<Isometry3d>, size: Vec2, color: impl Into<Color>) {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * vec2.extend(0.));
self.linestrip([tl, tr, br, bl, tl], color);
}
/// Draw a wireframe cube in 3D.
///
/// This should be called for each frame the cube needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_transform::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cuboid(Transform::IDENTITY, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn cuboid(&mut self, transform: impl TransformPoint, color: impl Into<Color>) {
let polymorphic_color: Color = color.into();
if !self.enabled {
return;
}
let rect = rect_inner(Vec2::ONE);
// Front
let [tlf, trf, brf, blf] = rect.map(|vec2| transform.transform_point(vec2.extend(0.5)));
// Back
let [tlb, trb, brb, blb] = rect.map(|vec2| transform.transform_point(vec2.extend(-0.5)));
let strip_positions = [
tlf, trf, brf, blf, tlf, // Front
tlb, trb, brb, blb, tlb, // Back
];
self.linestrip(strip_positions, polymorphic_color);
let list_positions = [
trf, trb, brf, brb, blf, blb, // Front to back
];
self.extend_list_positions(list_positions);
self.add_list_color(polymorphic_color, 6);
}
/// Draw a line in 2D from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_2d(Vec2::ZERO, Vec2::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line_2d(&mut self, start: Vec2, end: Vec2, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.line(start.extend(0.), end.extend(0.), color);
}
/// Draw a line in 2D with a color gradient from `start` to `end`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_gradient_2d(Vec2::ZERO, Vec2::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn line_gradient_2d<C: Into<Color>>(
&mut self,
start: Vec2,
end: Vec2,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.line_gradient(start.extend(0.), end.extend(0.), start_color, end_color);
}
/// Draw a line in 2D made of straight segments between the points.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip_2d([Vec2::ZERO, Vec2::X, Vec2::Y], GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip_2d(
&mut self,
positions: impl IntoIterator<Item = Vec2>,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
self.linestrip(positions.into_iter().map(|vec2| vec2.extend(0.)), color);
}
/// Draw a line in 2D made of straight segments between the points, with a color gradient.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN, BLUE};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.linestrip_gradient_2d([
/// (Vec2::ZERO, GREEN),
/// (Vec2::X, RED),
/// (Vec2::Y, BLUE)
/// ]);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn linestrip_gradient_2d<C: Into<Color>>(
&mut self,
positions: impl IntoIterator<Item = (Vec2, C)>,
) {
if !self.enabled {
return;
}
self.linestrip_gradient(
positions
.into_iter()
.map(|(vec2, color)| (vec2.extend(0.), color)),
);
}
/// Draw a line in 2D from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.ray_2d(Vec2::Y, Vec2::X, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray_2d(&mut self, start: Vec2, vector: Vec2, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.line_2d(start, start + vector, color);
}
/// Draw a line in 2D with a color gradient from `start` to `start + vector`.
///
/// This should be called for each frame the line needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::{RED, GREEN};
/// fn system(mut gizmos: Gizmos) {
/// gizmos.line_gradient(Vec3::Y, Vec3::X, GREEN, RED);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn ray_gradient_2d<C: Into<Color>>(
&mut self,
start: Vec2,
vector: Vec2,
start_color: C,
end_color: C,
) {
if !self.enabled {
return;
}
self.line_gradient_2d(start, start + vector, start_color, end_color);
}
/// 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.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rect_2d(Isometry2d::IDENTITY, Vec2::ONE, GREEN);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
#[inline]
pub fn rect_2d(
&mut self,
isometry: impl Into<Isometry2d>,
size: Vec2,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| isometry * vec2);
self.linestrip_2d([tl, tr, br, bl, tl], color);
}
#[inline]
fn extend_list_positions(&mut self, positions: impl IntoIterator<Item = Vec3>) {
self.list_positions.extend(positions);
}
#[inline]
fn extend_list_colors(&mut self, colors: impl IntoIterator<Item = impl Into<Color>>) {
self.list_colors.extend(
colors
.into_iter()
.map(|color| LinearRgba::from(color.into())),
);
}
#[inline]
fn add_list_color(&mut self, color: impl Into<Color>, count: usize) {
let polymorphic_color: Color = color.into();
let linear_color = LinearRgba::from(polymorphic_color);
self.list_colors.extend(iter::repeat_n(linear_color, count));
}
#[inline]
fn extend_strip_positions(&mut self, positions: impl IntoIterator<Item = Vec3>) {
self.strip_positions.extend(positions);
self.strip_positions.push(Vec3::NAN);
}
}
fn rect_inner(size: Vec2) -> [Vec2; 4] {
let half_size = size / 2.;
let tl = Vec2::new(-half_size.x, half_size.y);
let tr = Vec2::new(half_size.x, half_size.y);
let bl = Vec2::new(-half_size.x, -half_size.y);
let br = Vec2::new(half_size.x, -half_size.y);
[tl, tr, br, bl]
}

430
vendor/bevy_gizmos/src/grid.rs vendored Normal file
View File

@@ -0,0 +1,430 @@
//! Additional [`GizmoBuffer`] Functions -- Grids
//!
//! Includes the implementation of [`GizmoBuffer::grid`] and [`GizmoBuffer::grid_2d`].
//! and assorted support items.
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{ops, Isometry2d, Isometry3d, Quat, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles};
/// A builder returned by [`GizmoBuffer::grid_3d`]
pub struct GridBuilder3d<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
spacing: Vec3,
cell_count: UVec3,
skew: Vec3,
outer_edges: [bool; 3],
color: Color,
}
/// A builder returned by [`GizmoBuffer::grid`] and [`GizmoBuffer::grid_2d`]
pub struct GridBuilder2d<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
spacing: Vec2,
cell_count: UVec2,
skew: Vec2,
outer_edges: [bool; 2],
color: Color,
}
impl<Config, Clear> GridBuilder3d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Skews the grid by `tan(skew)` in the x direction.
/// `skew` is in radians
pub fn skew_x(mut self, skew: f32) -> Self {
self.skew.x = skew;
self
}
/// Skews the grid by `tan(skew)` in the y direction.
/// `skew` is in radians
pub fn skew_y(mut self, skew: f32) -> Self {
self.skew.y = skew;
self
}
/// Skews the grid by `tan(skew)` in the z direction.
/// `skew` is in radians
pub fn skew_z(mut self, skew: f32) -> Self {
self.skew.z = skew;
self
}
/// Skews the grid by `tan(skew)` in the x, y and z directions.
/// `skew` is in radians
pub fn skew(mut self, skew: Vec3) -> Self {
self.skew = skew;
self
}
/// Declare that the outer edges of the grid along the x axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_x(mut self) -> Self {
self.outer_edges[0] = true;
self
}
/// Declare that the outer edges of the grid along the y axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_y(mut self) -> Self {
self.outer_edges[1] = true;
self
}
/// Declare that the outer edges of the grid along the z axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_z(mut self) -> Self {
self.outer_edges[2] = true;
self
}
/// Declare that all outer edges of the grid should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges(mut self) -> Self {
self.outer_edges.fill(true);
self
}
}
impl<Config, Clear> GridBuilder2d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Skews the grid by `tan(skew)` in the x direction.
/// `skew` is in radians
pub fn skew_x(mut self, skew: f32) -> Self {
self.skew.x = skew;
self
}
/// Skews the grid by `tan(skew)` in the y direction.
/// `skew` is in radians
pub fn skew_y(mut self, skew: f32) -> Self {
self.skew.y = skew;
self
}
/// Skews the grid by `tan(skew)` in the x and y directions.
/// `skew` is in radians
pub fn skew(mut self, skew: Vec2) -> Self {
self.skew = skew;
self
}
/// Declare that the outer edges of the grid along the x axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_x(mut self) -> Self {
self.outer_edges[0] = true;
self
}
/// Declare that the outer edges of the grid along the y axis should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges_y(mut self) -> Self {
self.outer_edges[1] = true;
self
}
/// Declare that all outer edges of the grid should be drawn.
/// By default, the outer edges will not be drawn.
pub fn outer_edges(mut self) -> Self {
self.outer_edges.fill(true);
self
}
}
impl<Config, Clear> Drop for GridBuilder3d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draws a grid, by drawing lines with the stored [`GizmoBuffer`]
fn drop(&mut self) {
draw_grid(
self.gizmos,
self.isometry,
self.spacing,
self.cell_count,
self.skew,
self.outer_edges,
self.color,
);
}
}
impl<Config, Clear> Drop for GridBuilder2d<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
draw_grid(
self.gizmos,
self.isometry,
self.spacing.extend(0.),
self.cell_count.extend(0),
self.skew.extend(0.),
[self.outer_edges[0], self.outer_edges[1], true],
self.color,
);
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a 2D grid in 3D.
///
/// This should be called for each frame the grid needs to be rendered.
///
/// The grid's default orientation aligns with the XY-plane.
///
/// # Arguments
///
/// - `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
///
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)` or `.skew_y(...)` methods. They behave very similar to their CSS equivalents.
/// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)` or `.outer_edges_y(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.grid(
/// Isometry3d::IDENTITY,
/// UVec2::new(10, 10),
/// Vec2::splat(2.),
/// GREEN
/// )
/// .skew_x(0.25)
/// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn grid(
&mut self,
isometry: impl Into<Isometry3d>,
cell_count: UVec2,
spacing: Vec2,
color: impl Into<Color>,
) -> GridBuilder2d<'_, Config, Clear> {
GridBuilder2d {
gizmos: self,
isometry: isometry.into(),
spacing,
cell_count,
skew: Vec2::ZERO,
outer_edges: [false, false],
color: color.into(),
}
}
/// Draw a 3D grid of voxel-like cells.
///
/// This should be called for each frame the grid needs to be rendered.
///
/// # Arguments
///
/// - `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
///
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)`, `.skew_y(...)` or `.skew_z(...)` methods. They behave very similar to their CSS equivalents.
/// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)`, `.outer_edges_y(...)` or `.outer_edges_z(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.grid_3d(
/// Isometry3d::IDENTITY,
/// UVec3::new(10, 2, 10),
/// Vec3::splat(2.),
/// GREEN
/// )
/// .skew_x(0.25)
/// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn grid_3d(
&mut self,
isometry: impl Into<Isometry3d>,
cell_count: UVec3,
spacing: Vec3,
color: impl Into<Color>,
) -> GridBuilder3d<'_, Config, Clear> {
GridBuilder3d {
gizmos: self,
isometry: isometry.into(),
spacing,
cell_count,
skew: Vec3::ZERO,
outer_edges: [false, false, false],
color: color.into(),
}
}
/// Draw a grid in 2D.
///
/// This should be called for each frame the grid needs to be rendered.
///
/// # Arguments
///
/// - `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
///
/// # Builder methods
///
/// - The skew of the grid can be adjusted using the `.skew(...)`, `.skew_x(...)` or `.skew_y(...)` methods. They behave very similar to their CSS equivalents.
/// - All outer edges can be toggled on or off using `.outer_edges(...)`. Alternatively you can use `.outer_edges_x(...)` or `.outer_edges_y(...)` to toggle the outer edges along an axis.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.grid_2d(
/// Isometry2d::IDENTITY,
/// UVec2::new(10, 10),
/// Vec2::splat(1.),
/// GREEN
/// )
/// .skew_x(0.25)
/// .outer_edges();
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn grid_2d(
&mut self,
isometry: impl Into<Isometry2d>,
cell_count: UVec2,
spacing: Vec2,
color: impl Into<Color>,
) -> GridBuilder2d<'_, Config, Clear> {
let isometry = isometry.into();
GridBuilder2d {
gizmos: self,
isometry: Isometry3d::new(
isometry.translation.extend(0.0),
Quat::from_rotation_z(isometry.rotation.as_radians()),
),
spacing,
cell_count,
skew: Vec2::ZERO,
outer_edges: [false, false],
color: color.into(),
}
}
}
fn draw_grid<Config, Clear>(
gizmos: &mut GizmoBuffer<Config, Clear>,
isometry: Isometry3d,
spacing: Vec3,
cell_count: UVec3,
skew: Vec3,
outer_edges: [bool; 3],
color: Color,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
if !gizmos.enabled {
return;
}
#[inline]
fn or_zero(cond: bool, val: Vec3) -> Vec3 {
if cond {
val
} else {
Vec3::ZERO
}
}
// Offset between two adjacent grid cells along the x/y-axis and accounting for skew.
let skew_tan = Vec3::from(skew.to_array().map(ops::tan));
let dx = or_zero(
cell_count.x != 0,
spacing.x * Vec3::new(1., skew_tan.y, skew_tan.z),
);
let dy = or_zero(
cell_count.y != 0,
spacing.y * Vec3::new(skew_tan.x, 1., skew_tan.z),
);
let dz = or_zero(
cell_count.z != 0,
spacing.z * Vec3::new(skew_tan.x, skew_tan.y, 1.),
);
// Bottom-left-front corner of the grid
let cell_count_half = cell_count.as_vec3() * 0.5;
let grid_start = -cell_count_half.x * dx - cell_count_half.y * dy - cell_count_half.z * dz;
let outer_edges_u32 = UVec3::from(outer_edges.map(|v| v as u32));
let line_count = outer_edges_u32 * cell_count.saturating_add(UVec3::ONE)
+ (UVec3::ONE - outer_edges_u32) * cell_count.saturating_sub(UVec3::ONE);
let x_start = grid_start + or_zero(!outer_edges[0], dy + dz);
let y_start = grid_start + or_zero(!outer_edges[1], dx + dz);
let z_start = grid_start + or_zero(!outer_edges[2], dx + dy);
fn iter_lines(
delta_a: Vec3,
delta_b: Vec3,
delta_c: Vec3,
line_count: UVec2,
cell_count: u32,
start: Vec3,
) -> impl Iterator<Item = [Vec3; 2]> {
let dline = delta_a * cell_count as f32;
(0..line_count.x).map(|v| v as f32).flat_map(move |b| {
(0..line_count.y).map(|v| v as f32).map(move |c| {
let line_start = start + b * delta_b + c * delta_c;
let line_end = line_start + dline;
[line_start, line_end]
})
})
}
// Lines along the x direction
let x_lines = iter_lines(dx, dy, dz, line_count.yz(), cell_count.x, x_start);
// Lines along the y direction
let y_lines = iter_lines(dy, dz, dx, line_count.zx(), cell_count.y, y_start);
// Lines along the z direction
let z_lines = iter_lines(dz, dx, dy, line_count.xy(), cell_count.z, z_start);
x_lines
.chain(y_lines)
.chain(z_lines)
.map(|vec3s| vec3s.map(|vec3| isometry * vec3))
.for_each(|[start, end]| {
gizmos.line(start, end, color);
});
}

888
vendor/bevy_gizmos/src/lib.rs vendored Executable file
View File

@@ -0,0 +1,888 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
)]
//! This crate adds an immediate mode drawing api to Bevy for visual debugging.
//!
//! # Example
//! ```
//! # use bevy_gizmos::prelude::*;
//! # use bevy_math::prelude::*;
//! # use bevy_color::palettes::basic::GREEN;
//! fn system(mut gizmos: Gizmos) {
//! gizmos.line(Vec3::ZERO, Vec3::X, GREEN);
//! }
//! # bevy_ecs::system::assert_is_system(system);
//! ```
//!
//! See the documentation on [Gizmos](crate::gizmos::Gizmos) for more examples.
// Required to make proc macros work in bevy itself.
extern crate self as bevy_gizmos;
/// System set label for the systems handling the rendering of gizmos.
#[derive(SystemSet, Clone, Debug, Hash, PartialEq, Eq)]
pub enum GizmoRenderSystem {
/// Adds gizmos to the [`Transparent2d`](bevy_core_pipeline::core_2d::Transparent2d) render phase
#[cfg(feature = "bevy_sprite")]
QueueLineGizmos2d,
/// Adds gizmos to the [`Transparent3d`](bevy_core_pipeline::core_3d::Transparent3d) render phase
#[cfg(feature = "bevy_pbr")]
QueueLineGizmos3d,
}
#[cfg(feature = "bevy_render")]
pub mod aabb;
pub mod arcs;
pub mod arrows;
pub mod circles;
pub mod config;
pub mod cross;
pub mod curves;
pub mod gizmos;
pub mod grid;
pub mod primitives;
pub mod retained;
pub mod rounded_box;
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
pub mod light;
#[cfg(all(feature = "bevy_sprite", feature = "bevy_render"))]
mod pipeline_2d;
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
mod pipeline_3d;
/// The gizmos prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[cfg(feature = "bevy_render")]
pub use crate::aabb::{AabbGizmoConfigGroup, ShowAabbGizmo};
#[doc(hidden)]
pub use crate::{
config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore,
GizmoLineConfig, GizmoLineJoint, GizmoLineStyle,
},
gizmos::Gizmos,
primitives::{dim2::GizmoPrimitive2d, dim3::GizmoPrimitive3d},
retained::Gizmo,
AppGizmoBuilder, GizmoAsset,
};
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
pub use crate::light::{LightGizmoColor, LightGizmoConfigGroup, ShowLightGizmo};
}
use bevy_app::{App, FixedFirst, FixedLast, Last, Plugin, RunFixedMainLoop};
use bevy_asset::{weak_handle, Asset, AssetApp, AssetId, Assets, Handle};
use bevy_ecs::{
resource::Resource,
schedule::{IntoScheduleConfigs, SystemSet},
system::{Res, ResMut},
};
use bevy_math::{Vec3, Vec4};
use bevy_reflect::TypePath;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
use crate::config::GizmoMeshConfig;
use crate::{config::ErasedGizmoConfigGroup, gizmos::GizmoBuffer};
#[cfg(feature = "bevy_render")]
use {
crate::retained::extract_linegizmos,
bevy_ecs::{
component::Component,
entity::Entity,
query::ROQueryItem,
system::{
lifetimeless::{Read, SRes},
Commands, SystemParamItem,
},
},
bevy_math::{Affine3, Affine3A},
bevy_render::{
extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_phase::{PhaseItem, RenderCommand, RenderCommandResult, TrackedRenderPass},
render_resource::{
binding_types::uniform_buffer, BindGroup, BindGroupEntries, BindGroupLayout,
BindGroupLayoutEntries, Buffer, BufferInitDescriptor, BufferUsages, Shader,
ShaderStages, ShaderType, VertexFormat,
},
renderer::RenderDevice,
sync_world::{MainEntity, TemporaryRenderEntity},
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
},
bytemuck::cast_slice,
};
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite"),
))]
use bevy_render::render_resource::{VertexAttribute, VertexBufferLayout, VertexStepMode};
use bevy_time::Fixed;
use bevy_utils::TypeIdMap;
use config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoLineJoint,
};
use core::{any::TypeId, marker::PhantomData, mem};
use gizmos::{GizmoStorage, Swap};
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
use light::LightGizmoPlugin;
#[cfg(feature = "bevy_render")]
const LINE_SHADER_HANDLE: Handle<Shader> = weak_handle!("15dc5869-ad30-4664-b35a-4137cb8804a1");
#[cfg(feature = "bevy_render")]
const LINE_JOINT_SHADER_HANDLE: Handle<Shader> =
weak_handle!("7b5bdda5-df81-4711-a6cf-e587700de6f2");
/// A [`Plugin`] that provides an immediate mode drawing api for visual debugging.
///
/// Requires to be loaded after [`PbrPlugin`](bevy_pbr::PbrPlugin) or [`SpritePlugin`](bevy_sprite::SpritePlugin).
#[derive(Default)]
pub struct GizmoPlugin;
impl Plugin for GizmoPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "bevy_render")]
{
use bevy_asset::load_internal_asset;
load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
LINE_JOINT_SHADER_HANDLE,
"line_joints.wgsl",
Shader::from_wgsl
);
}
app.register_type::<GizmoConfig>()
.register_type::<GizmoConfigStore>()
.init_asset::<GizmoAsset>()
.init_resource::<GizmoHandles>()
// We insert the Resource GizmoConfigStore into the world implicitly here if it does not exist.
.init_gizmo_group::<DefaultGizmoConfigGroup>();
#[cfg(feature = "bevy_render")]
app.add_plugins(aabb::AabbGizmoPlugin)
.add_plugins(UniformComponentPlugin::<LineGizmoUniform>::default())
.add_plugins(RenderAssetPlugin::<GpuLineGizmo>::default());
#[cfg(all(feature = "bevy_pbr", feature = "bevy_render"))]
app.add_plugins(LightGizmoPlugin);
#[cfg(feature = "bevy_render")]
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
render_app.add_systems(
Render,
prepare_line_gizmo_bind_group.in_set(RenderSet::PrepareBindGroups),
);
render_app.add_systems(ExtractSchedule, (extract_gizmo_data, extract_linegizmos));
#[cfg(feature = "bevy_sprite")]
if app.is_plugin_added::<bevy_sprite::SpritePlugin>() {
app.add_plugins(pipeline_2d::LineGizmo2dPlugin);
} else {
tracing::warn!("bevy_sprite feature is enabled but bevy_sprite::SpritePlugin was not detected. Are you sure you loaded GizmoPlugin after SpritePlugin?");
}
#[cfg(feature = "bevy_pbr")]
if app.is_plugin_added::<bevy_pbr::PbrPlugin>() {
app.add_plugins(pipeline_3d::LineGizmo3dPlugin);
} else {
tracing::warn!("bevy_pbr feature is enabled but bevy_pbr::PbrPlugin was not detected. Are you sure you loaded GizmoPlugin after PbrPlugin?");
}
} else {
tracing::warn!("bevy_render feature is enabled but RenderApp was not detected. Are you sure you loaded GizmoPlugin after RenderPlugin?");
}
}
#[cfg(feature = "bevy_render")]
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
let render_device = render_app.world().resource::<RenderDevice>();
let line_layout = render_device.create_bind_group_layout(
"LineGizmoUniform layout",
&BindGroupLayoutEntries::single(
ShaderStages::VERTEX,
uniform_buffer::<LineGizmoUniform>(true),
),
);
render_app.insert_resource(LineGizmoUniformBindgroupLayout {
layout: line_layout,
});
}
}
/// A extension trait adding `App::init_gizmo_group` and `App::insert_gizmo_config`.
pub trait AppGizmoBuilder {
/// Registers [`GizmoConfigGroup`] in the app enabling the use of [Gizmos&lt;Config&gt;](crate::gizmos::Gizmos).
///
/// Configurations can be set using the [`GizmoConfigStore`] [`Resource`].
fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self;
/// Insert a [`GizmoConfig`] into a specific [`GizmoConfigGroup`].
///
/// This method should be preferred over [`AppGizmoBuilder::init_gizmo_group`] if and only if you need to configure fields upon initialization.
fn insert_gizmo_config<Config: GizmoConfigGroup>(
&mut self,
group: Config,
config: GizmoConfig,
) -> &mut Self;
}
impl AppGizmoBuilder for App {
fn init_gizmo_group<Config: GizmoConfigGroup>(&mut self) -> &mut Self {
if self.world().contains_resource::<GizmoStorage<Config, ()>>() {
return self;
}
self.world_mut()
.get_resource_or_init::<GizmoConfigStore>()
.register::<Config>();
let mut handles = self.world_mut().get_resource_or_init::<GizmoHandles>();
handles.handles.insert(TypeId::of::<Config>(), None);
// These handles are safe to mutate in any order
self.allow_ambiguous_resource::<GizmoHandles>();
self.init_resource::<GizmoStorage<Config, ()>>()
.init_resource::<GizmoStorage<Config, Fixed>>()
.init_resource::<GizmoStorage<Config, Swap<Fixed>>>()
.add_systems(
RunFixedMainLoop,
start_gizmo_context::<Config, Fixed>
.in_set(bevy_app::RunFixedMainLoopSystem::BeforeFixedMainLoop),
)
.add_systems(FixedFirst, clear_gizmo_context::<Config, Fixed>)
.add_systems(FixedLast, collect_requested_gizmos::<Config, Fixed>)
.add_systems(
RunFixedMainLoop,
end_gizmo_context::<Config, Fixed>
.in_set(bevy_app::RunFixedMainLoopSystem::AfterFixedMainLoop),
)
.add_systems(
Last,
(
propagate_gizmos::<Config, Fixed>.before(UpdateGizmoMeshes),
update_gizmo_meshes::<Config>.in_set(UpdateGizmoMeshes),
),
);
self
}
fn insert_gizmo_config<Config: GizmoConfigGroup>(
&mut self,
group: Config,
config: GizmoConfig,
) -> &mut Self {
self.init_gizmo_group::<Config>();
self.world_mut()
.get_resource_or_init::<GizmoConfigStore>()
.insert(config, group);
self
}
}
/// Holds handles to the line gizmos for each gizmo configuration group
// As `TypeIdMap` iteration order depends on the order of insertions and deletions, this uses
// `Option<Handle>` to be able to reserve the slot when creating the gizmo configuration group.
// That way iteration order is stable across executions and depends on the order of configuration
// group creation.
#[derive(Resource, Default)]
struct GizmoHandles {
handles: TypeIdMap<Option<Handle<GizmoAsset>>>,
}
/// Start a new gizmo clearing context.
///
/// Internally this pushes the parent default context into a swap buffer.
/// Gizmo contexts should be handled like a stack, so if you push a new context,
/// you must pop the context before the parent context ends.
pub fn start_gizmo_context<Config, Clear>(
mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
mut default: ResMut<GizmoStorage<Config, ()>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
default.swap(&mut *swap);
}
/// End this gizmo clearing context.
///
/// Pop the default gizmos context out of the [`Swap<Clear>`] gizmo storage.
///
/// This must be called before [`UpdateGizmoMeshes`] in the [`Last`] schedule.
pub fn end_gizmo_context<Config, Clear>(
mut swap: ResMut<GizmoStorage<Config, Swap<Clear>>>,
mut default: ResMut<GizmoStorage<Config, ()>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
default.clear();
default.swap(&mut *swap);
}
/// Collect the requested gizmos into a specific clear context.
pub fn collect_requested_gizmos<Config, Clear>(
mut update: ResMut<GizmoStorage<Config, ()>>,
mut context: ResMut<GizmoStorage<Config, Clear>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
context.append_storage(&update);
update.clear();
}
/// Clear out the contextual gizmos.
pub fn clear_gizmo_context<Config, Clear>(mut context: ResMut<GizmoStorage<Config, Clear>>)
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
context.clear();
}
/// Propagate the contextual gizmo into the `Update` storage for rendering.
///
/// This should be before [`UpdateGizmoMeshes`].
pub fn propagate_gizmos<Config, Clear>(
mut update_storage: ResMut<GizmoStorage<Config, ()>>,
contextual_storage: Res<GizmoStorage<Config, Clear>>,
) where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
update_storage.append_storage(&*contextual_storage);
}
/// System set for updating the rendering meshes for drawing gizmos.
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct UpdateGizmoMeshes;
/// Prepare gizmos for rendering.
///
/// This also clears the default `GizmoStorage`.
fn update_gizmo_meshes<Config: GizmoConfigGroup>(
mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
mut handles: ResMut<GizmoHandles>,
mut storage: ResMut<GizmoStorage<Config, ()>>,
) {
if storage.list_positions.is_empty() && storage.strip_positions.is_empty() {
handles.handles.insert(TypeId::of::<Config>(), None);
} else if let Some(handle) = handles.handles.get_mut(&TypeId::of::<Config>()) {
if let Some(handle) = handle {
let gizmo = gizmo_assets.get_mut(handle.id()).unwrap();
gizmo.buffer.list_positions = mem::take(&mut storage.list_positions);
gizmo.buffer.list_colors = mem::take(&mut storage.list_colors);
gizmo.buffer.strip_positions = mem::take(&mut storage.strip_positions);
gizmo.buffer.strip_colors = mem::take(&mut storage.strip_colors);
} else {
let gizmo = GizmoAsset {
config_ty: TypeId::of::<Config>(),
buffer: GizmoBuffer {
enabled: true,
list_positions: mem::take(&mut storage.list_positions),
list_colors: mem::take(&mut storage.list_colors),
strip_positions: mem::take(&mut storage.strip_positions),
strip_colors: mem::take(&mut storage.strip_colors),
marker: PhantomData,
},
};
*handle = Some(gizmo_assets.add(gizmo));
}
}
}
#[cfg(feature = "bevy_render")]
fn extract_gizmo_data(
mut commands: Commands,
handles: Extract<Res<GizmoHandles>>,
config: Extract<Res<GizmoConfigStore>>,
) {
use bevy_utils::once;
use config::GizmoLineStyle;
use tracing::warn;
for (group_type_id, handle) in &handles.handles {
let Some((config, _)) = config.get_config_dyn(group_type_id) else {
continue;
};
if !config.enabled {
continue;
}
let Some(handle) = handle else {
continue;
};
let joints_resolution = if let GizmoLineJoint::Round(resolution) = config.line.joints {
resolution
} else {
0
};
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
gap_scale,
line_scale,
} = config.line.style
{
if gap_scale <= 0.0 {
once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero."));
}
if line_scale <= 0.0 {
once!(warn!("When using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero."));
}
(gap_scale, line_scale)
} else {
(1.0, 1.0)
};
commands.spawn((
LineGizmoUniform {
world_from_local: Affine3::from(&Affine3A::IDENTITY).to_transpose(),
line_width: config.line.width,
depth_bias: config.depth_bias,
joints_resolution,
gap_scale,
line_scale,
#[cfg(feature = "webgl")]
_padding: Default::default(),
},
#[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))]
GizmoMeshConfig {
line_perspective: config.line.perspective,
line_style: config.line.style,
line_joints: config.line.joints,
render_layers: config.render_layers.clone(),
handle: handle.clone(),
},
// The immediate mode API does not have a main world entity to refer to,
// but we do need MainEntity on this render entity for the systems to find it.
MainEntity::from(Entity::PLACEHOLDER),
TemporaryRenderEntity,
));
}
}
#[cfg(feature = "bevy_render")]
#[derive(Component, ShaderType, Clone, Copy)]
struct LineGizmoUniform {
world_from_local: [Vec4; 3],
line_width: f32,
depth_bias: f32,
// Only used by gizmo line t if the current configs `line_joints` is set to `GizmoLineJoint::Round(_)`
joints_resolution: u32,
// Only used if the current configs `line_style` is set to `GizmoLineStyle::Dashed{_}`
gap_scale: f32,
line_scale: f32,
/// WebGL2 structs must be 16 byte aligned.
#[cfg(feature = "webgl")]
_padding: Vec3,
}
/// A collection of gizmos.
///
/// Has the same gizmo drawing API as [`Gizmos`](crate::gizmos::Gizmos).
#[derive(Asset, Debug, Clone, TypePath)]
pub struct GizmoAsset {
/// vertex buffers.
buffer: GizmoBuffer<ErasedGizmoConfigGroup, ()>,
config_ty: TypeId,
}
impl GizmoAsset {
/// Create a new [`GizmoAsset`].
pub fn new() -> Self {
GizmoAsset {
buffer: GizmoBuffer::default(),
config_ty: TypeId::of::<ErasedGizmoConfigGroup>(),
}
}
/// The type of the gizmo's configuration group.
pub fn config_typeid(&self) -> TypeId {
self.config_ty
}
}
impl Default for GizmoAsset {
fn default() -> Self {
GizmoAsset::new()
}
}
#[cfg(feature = "bevy_render")]
#[derive(Debug, Clone)]
struct GpuLineGizmo {
list_position_buffer: Buffer,
list_color_buffer: Buffer,
list_vertex_count: u32,
strip_position_buffer: Buffer,
strip_color_buffer: Buffer,
strip_vertex_count: u32,
}
#[cfg(feature = "bevy_render")]
impl RenderAsset for GpuLineGizmo {
type SourceAsset = GizmoAsset;
type Param = SRes<RenderDevice>;
fn prepare_asset(
gizmo: Self::SourceAsset,
_: AssetId<Self::SourceAsset>,
render_device: &mut SystemParamItem<Self::Param>,
) -> Result<Self, PrepareAssetError<Self::SourceAsset>> {
let list_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Position Buffer"),
contents: cast_slice(&gizmo.buffer.list_positions),
});
let list_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Color Buffer"),
contents: cast_slice(&gizmo.buffer.list_colors),
});
let strip_position_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Strip Position Buffer"),
contents: cast_slice(&gizmo.buffer.strip_positions),
});
let strip_color_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
usage: BufferUsages::VERTEX,
label: Some("LineGizmo Strip Color Buffer"),
contents: cast_slice(&gizmo.buffer.strip_colors),
});
Ok(GpuLineGizmo {
list_position_buffer,
list_color_buffer,
list_vertex_count: gizmo.buffer.list_positions.len() as u32,
strip_position_buffer,
strip_color_buffer,
strip_vertex_count: gizmo.buffer.strip_positions.len() as u32,
})
}
}
#[cfg(feature = "bevy_render")]
#[derive(Resource)]
struct LineGizmoUniformBindgroupLayout {
layout: BindGroupLayout,
}
#[cfg(feature = "bevy_render")]
#[derive(Resource)]
struct LineGizmoUniformBindgroup {
bindgroup: BindGroup,
}
#[cfg(feature = "bevy_render")]
fn prepare_line_gizmo_bind_group(
mut commands: Commands,
line_gizmo_uniform_layout: Res<LineGizmoUniformBindgroupLayout>,
render_device: Res<RenderDevice>,
line_gizmo_uniforms: Res<ComponentUniforms<LineGizmoUniform>>,
) {
if let Some(binding) = line_gizmo_uniforms.uniforms().binding() {
commands.insert_resource(LineGizmoUniformBindgroup {
bindgroup: render_device.create_bind_group(
"LineGizmoUniform bindgroup",
&line_gizmo_uniform_layout.layout,
&BindGroupEntries::single(binding),
),
});
}
}
#[cfg(feature = "bevy_render")]
struct SetLineGizmoBindGroup<const I: usize>;
#[cfg(feature = "bevy_render")]
impl<const I: usize, P: PhaseItem> RenderCommand<P> for SetLineGizmoBindGroup<I> {
type Param = SRes<LineGizmoUniformBindgroup>;
type ViewQuery = ();
type ItemQuery = Read<DynamicUniformIndex<LineGizmoUniform>>;
#[inline]
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, Self::ViewQuery>,
uniform_index: Option<ROQueryItem<'w, Self::ItemQuery>>,
bind_group: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(uniform_index) = uniform_index else {
return RenderCommandResult::Skip;
};
pass.set_bind_group(
I,
&bind_group.into_inner().bindgroup,
&[uniform_index.index()],
);
RenderCommandResult::Success
}
}
#[cfg(feature = "bevy_render")]
struct DrawLineGizmo<const STRIP: bool>;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
impl<P: PhaseItem, const STRIP: bool> RenderCommand<P> for DrawLineGizmo<STRIP> {
type Param = SRes<RenderAssets<GpuLineGizmo>>;
type ViewQuery = ();
type ItemQuery = Read<GizmoMeshConfig>;
#[inline]
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, Self::ViewQuery>,
config: Option<ROQueryItem<'w, Self::ItemQuery>>,
line_gizmos: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(config) = config else {
return RenderCommandResult::Skip;
};
let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
return RenderCommandResult::Skip;
};
let vertex_count = if STRIP {
line_gizmo.strip_vertex_count
} else {
line_gizmo.list_vertex_count
};
if vertex_count < 2 {
return RenderCommandResult::Success;
}
let instances = if STRIP {
let item_size = VertexFormat::Float32x3.size();
let buffer_size = line_gizmo.strip_position_buffer.size() - item_size;
pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size));
pass.set_vertex_buffer(1, line_gizmo.strip_position_buffer.slice(item_size..));
let item_size = VertexFormat::Float32x4.size();
let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
pass.set_vertex_buffer(2, line_gizmo.strip_color_buffer.slice(..buffer_size));
pass.set_vertex_buffer(3, line_gizmo.strip_color_buffer.slice(item_size..));
vertex_count - 1
} else {
pass.set_vertex_buffer(0, line_gizmo.list_position_buffer.slice(..));
pass.set_vertex_buffer(1, line_gizmo.list_color_buffer.slice(..));
vertex_count / 2
};
pass.draw(0..6, 0..instances);
RenderCommandResult::Success
}
}
#[cfg(feature = "bevy_render")]
struct DrawLineJointGizmo;
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
impl<P: PhaseItem> RenderCommand<P> for DrawLineJointGizmo {
type Param = SRes<RenderAssets<GpuLineGizmo>>;
type ViewQuery = ();
type ItemQuery = Read<GizmoMeshConfig>;
#[inline]
fn render<'w>(
_item: &P,
_view: ROQueryItem<'w, Self::ViewQuery>,
config: Option<ROQueryItem<'w, Self::ItemQuery>>,
line_gizmos: SystemParamItem<'w, '_, Self::Param>,
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let Some(config) = config else {
return RenderCommandResult::Skip;
};
let Some(line_gizmo) = line_gizmos.into_inner().get(&config.handle) else {
return RenderCommandResult::Skip;
};
if line_gizmo.strip_vertex_count <= 2 {
return RenderCommandResult::Success;
};
if config.line_joints == GizmoLineJoint::None {
return RenderCommandResult::Success;
};
let instances = {
let item_size = VertexFormat::Float32x3.size();
// position_a
let buffer_size_a = line_gizmo.strip_position_buffer.size() - item_size * 2;
pass.set_vertex_buffer(0, line_gizmo.strip_position_buffer.slice(..buffer_size_a));
// position_b
let buffer_size_b = line_gizmo.strip_position_buffer.size() - item_size;
pass.set_vertex_buffer(
1,
line_gizmo
.strip_position_buffer
.slice(item_size..buffer_size_b),
);
// position_c
pass.set_vertex_buffer(2, line_gizmo.strip_position_buffer.slice(item_size * 2..));
// color
let item_size = VertexFormat::Float32x4.size();
let buffer_size = line_gizmo.strip_color_buffer.size() - item_size;
// This corresponds to the color of position_b, hence starts from `item_size`
pass.set_vertex_buffer(
3,
line_gizmo.strip_color_buffer.slice(item_size..buffer_size),
);
line_gizmo.strip_vertex_count - 2
};
let vertices = match config.line_joints {
GizmoLineJoint::None => unreachable!(),
GizmoLineJoint::Miter => 6,
GizmoLineJoint::Round(resolution) => resolution * 3,
GizmoLineJoint::Bevel => 3,
};
pass.draw(0..vertices, 0..instances);
RenderCommandResult::Success
}
}
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
fn line_gizmo_vertex_buffer_layouts(strip: bool) -> Vec<VertexBufferLayout> {
use VertexFormat::*;
let mut position_layout = VertexBufferLayout {
array_stride: Float32x3.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x3,
offset: 0,
shader_location: 0,
}],
};
let mut color_layout = VertexBufferLayout {
array_stride: Float32x4.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x4,
offset: 0,
shader_location: 2,
}],
};
if strip {
vec![
position_layout.clone(),
{
position_layout.attributes[0].shader_location = 1;
position_layout
},
color_layout.clone(),
{
color_layout.attributes[0].shader_location = 3;
color_layout
},
]
} else {
position_layout.array_stride *= 2;
position_layout.attributes.push(VertexAttribute {
format: Float32x3,
offset: Float32x3.size(),
shader_location: 1,
});
color_layout.array_stride *= 2;
color_layout.attributes.push(VertexAttribute {
format: Float32x4,
offset: Float32x4.size(),
shader_location: 3,
});
vec![position_layout, color_layout]
}
}
#[cfg(all(
feature = "bevy_render",
any(feature = "bevy_pbr", feature = "bevy_sprite")
))]
fn line_joint_gizmo_vertex_buffer_layouts() -> Vec<VertexBufferLayout> {
use VertexFormat::*;
let mut position_layout = VertexBufferLayout {
array_stride: Float32x3.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x3,
offset: 0,
shader_location: 0,
}],
};
let color_layout = VertexBufferLayout {
array_stride: Float32x4.size(),
step_mode: VertexStepMode::Instance,
attributes: vec![VertexAttribute {
format: Float32x4,
offset: 0,
shader_location: 3,
}],
};
vec![
position_layout.clone(),
{
position_layout.attributes[0].shader_location = 1;
position_layout.clone()
},
{
position_layout.attributes[0].shader_location = 2;
position_layout
},
color_layout.clone(),
]
}

311
vendor/bevy_gizmos/src/light.rs vendored Normal file
View File

@@ -0,0 +1,311 @@
//! A module adding debug visualization of [`PointLight`]s, [`SpotLight`]s and [`DirectionalLight`]s.
use core::f32::consts::PI;
use crate::primitives::dim3::GizmoPrimitive3d;
use bevy_app::{Plugin, PostUpdate};
use bevy_color::{
palettes::basic::{BLUE, GREEN, RED},
Color, Oklcha,
};
use bevy_ecs::{
component::Component,
entity::Entity,
query::Without,
reflect::ReflectComponent,
schedule::IntoScheduleConfigs,
system::{Query, Res},
};
use bevy_math::{
ops,
primitives::{Cone, Sphere},
Isometry3d, Quat, Vec3,
};
use bevy_pbr::{DirectionalLight, PointLight, SpotLight};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::{components::GlobalTransform, TransformSystem};
use crate::{
config::{GizmoConfigGroup, GizmoConfigStore},
gizmos::Gizmos,
AppGizmoBuilder,
};
/// Draws a standard sphere for the radius and an axis sphere for the range.
fn point_light_gizmo(
transform: &GlobalTransform,
point_light: &PointLight,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let position = transform.translation();
gizmos
.primitive_3d(&Sphere::new(point_light.radius), position, color)
.resolution(16);
gizmos
.sphere(position, point_light.range, color)
.resolution(32);
}
/// Draws a sphere for the radius, two cones for the inner and outer angles, plus two 3d arcs crossing the
/// farthest point of effect of the spot light along its direction.
fn spot_light_gizmo(
transform: &GlobalTransform,
spot_light: &SpotLight,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos
.primitive_3d(&Sphere::new(spot_light.radius), translation, color)
.resolution(16);
// Offset the tip of the cone to the light position.
for angle in [spot_light.inner_angle, spot_light.outer_angle] {
let height = spot_light.range * ops::cos(angle);
let position = translation + rotation * Vec3::NEG_Z * height / 2.0;
gizmos
.primitive_3d(
&Cone {
radius: spot_light.range * ops::sin(angle),
height,
},
Isometry3d::new(position, rotation * Quat::from_rotation_x(PI / 2.0)),
color,
)
.height_resolution(4)
.base_resolution(32);
}
for arc_rotation in [
Quat::from_rotation_y(PI / 2.0 - spot_light.outer_angle),
Quat::from_euler(
bevy_math::EulerRot::XZY,
0.0,
PI / 2.0,
PI / 2.0 - spot_light.outer_angle,
),
] {
gizmos
.arc_3d(
2.0 * spot_light.outer_angle,
spot_light.range,
Isometry3d::new(translation, rotation * arc_rotation),
color,
)
.resolution(16);
}
}
/// Draws an arrow alongside the directional light direction.
fn directional_light_gizmo(
transform: &GlobalTransform,
color: Color,
gizmos: &mut Gizmos<LightGizmoConfigGroup>,
) {
let (_, rotation, translation) = transform.to_scale_rotation_translation();
gizmos
.arrow(translation, translation + rotation * Vec3::NEG_Z, color)
.with_tip_length(0.3);
}
/// A [`Plugin`] that provides visualization of [`PointLight`]s, [`SpotLight`]s
/// and [`DirectionalLight`]s for debugging.
pub struct LightGizmoPlugin;
impl Plugin for LightGizmoPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.register_type::<LightGizmoConfigGroup>()
.init_gizmo_group::<LightGizmoConfigGroup>()
.add_systems(
PostUpdate,
(
draw_lights,
draw_all_lights.run_if(|config: Res<GizmoConfigStore>| {
config.config::<LightGizmoConfigGroup>().1.draw_all
}),
)
.after(TransformSystem::TransformPropagate),
);
}
}
/// Configures how a color is attributed to a light gizmo.
#[derive(Debug, Clone, Copy, Default, Reflect)]
#[reflect(Clone, Default)]
pub enum LightGizmoColor {
/// User-specified color.
Manual(Color),
/// Random color derived from the light's [`Entity`].
Varied,
/// Take the color of the represented light.
#[default]
MatchLightColor,
/// Take the color provided by [`LightGizmoConfigGroup`] depending on the light kind.
ByLightType,
}
/// The [`GizmoConfigGroup`] used to configure the visualization of lights.
#[derive(Clone, Reflect, GizmoConfigGroup)]
#[reflect(Clone, Default)]
pub struct LightGizmoConfigGroup {
/// Draw a gizmo for all lights if true.
///
/// Defaults to `false`.
pub draw_all: bool,
/// Default color strategy for all light gizmos.
///
/// Defaults to [`LightGizmoColor::MatchLightColor`].
pub color: LightGizmoColor,
/// [`Color`] to use for drawing a [`PointLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`RED`].
pub point_light_color: Color,
/// [`Color`] to use for drawing a [`SpotLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`GREEN`].
pub spot_light_color: Color,
/// [`Color`] to use for drawing a [`DirectionalLight`] gizmo when [`LightGizmoColor::ByLightType`] is used.
///
/// Defaults to [`BLUE`].
pub directional_light_color: Color,
}
impl Default for LightGizmoConfigGroup {
fn default() -> Self {
Self {
draw_all: false,
color: LightGizmoColor::MatchLightColor,
point_light_color: RED.into(),
spot_light_color: GREEN.into(),
directional_light_color: BLUE.into(),
}
}
}
/// Add this [`Component`] to an entity to draw any of its lights components
/// ([`PointLight`], [`SpotLight`] and [`DirectionalLight`]).
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component, Default, Debug)]
pub struct ShowLightGizmo {
/// Default color strategy for this light gizmo. if [`None`], use the one provided by [`LightGizmoConfigGroup`].
///
/// Defaults to [`None`].
pub color: Option<LightGizmoColor>,
}
fn draw_lights(
point_query: Query<(Entity, &PointLight, &GlobalTransform, &ShowLightGizmo)>,
spot_query: Query<(Entity, &SpotLight, &GlobalTransform, &ShowLightGizmo)>,
directional_query: Query<(Entity, &DirectionalLight, &GlobalTransform, &ShowLightGizmo)>,
mut gizmos: Gizmos<LightGizmoConfigGroup>,
) {
let color = |entity: Entity, gizmo_color: Option<LightGizmoColor>, light_color, type_color| {
match gizmo_color.unwrap_or(gizmos.config_ext.color) {
LightGizmoColor::Manual(color) => color,
LightGizmoColor::Varied => Oklcha::sequential_dispersed(entity.index()).into(),
LightGizmoColor::MatchLightColor => light_color,
LightGizmoColor::ByLightType => type_color,
}
};
for (entity, light, transform, light_gizmo) in &point_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.point_light_color,
);
point_light_gizmo(transform, light, color, &mut gizmos);
}
for (entity, light, transform, light_gizmo) in &spot_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.spot_light_color,
);
spot_light_gizmo(transform, light, color, &mut gizmos);
}
for (entity, light, transform, light_gizmo) in &directional_query {
let color = color(
entity,
light_gizmo.color,
light.color,
gizmos.config_ext.directional_light_color,
);
directional_light_gizmo(transform, color, &mut gizmos);
}
}
fn draw_all_lights(
point_query: Query<(Entity, &PointLight, &GlobalTransform), Without<ShowLightGizmo>>,
spot_query: Query<(Entity, &SpotLight, &GlobalTransform), Without<ShowLightGizmo>>,
directional_query: Query<
(Entity, &DirectionalLight, &GlobalTransform),
Without<ShowLightGizmo>,
>,
mut gizmos: Gizmos<LightGizmoConfigGroup>,
) {
match gizmos.config_ext.color {
LightGizmoColor::Manual(color) => {
for (_, light, transform) in &point_query {
point_light_gizmo(transform, light, color, &mut gizmos);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(transform, light, color, &mut gizmos);
}
for (_, _, transform) in &directional_query {
directional_light_gizmo(transform, color, &mut gizmos);
}
}
LightGizmoColor::Varied => {
let color = |entity: Entity| Oklcha::sequential_dispersed(entity.index()).into();
for (entity, light, transform) in &point_query {
point_light_gizmo(transform, light, color(entity), &mut gizmos);
}
for (entity, light, transform) in &spot_query {
spot_light_gizmo(transform, light, color(entity), &mut gizmos);
}
for (entity, _, transform) in &directional_query {
directional_light_gizmo(transform, color(entity), &mut gizmos);
}
}
LightGizmoColor::MatchLightColor => {
for (_, light, transform) in &point_query {
point_light_gizmo(transform, light, light.color, &mut gizmos);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(transform, light, light.color, &mut gizmos);
}
for (_, light, transform) in &directional_query {
directional_light_gizmo(transform, light.color, &mut gizmos);
}
}
LightGizmoColor::ByLightType => {
for (_, light, transform) in &point_query {
point_light_gizmo(
transform,
light,
gizmos.config_ext.point_light_color,
&mut gizmos,
);
}
for (_, light, transform) in &spot_query {
spot_light_gizmo(
transform,
light,
gizmos.config_ext.spot_light_color,
&mut gizmos,
);
}
for (_, _, transform) in &directional_query {
directional_light_gizmo(
transform,
gizmos.config_ext.directional_light_color,
&mut gizmos,
);
}
}
}
}

255
vendor/bevy_gizmos/src/line_joints.wgsl vendored Normal file
View File

@@ -0,0 +1,255 @@
#import bevy_render::{view::View, maths::affine3_to_square}
@group(0) @binding(0) var<uniform> view: View;
struct LineGizmoUniform {
world_from_local: mat3x4<f32>,
line_width: f32,
depth_bias: f32,
resolution: u32,
#ifdef SIXTEEN_BYTE_ALIGNMENT
// WebGL2 structs must be 16 byte aligned.
_padding: f32,
#endif
}
@group(1) @binding(0) var<uniform> joints_gizmo: LineGizmoUniform;
struct VertexInput {
@location(0) position_a: vec3<f32>,
@location(1) position_b: vec3<f32>,
@location(2) position_c: vec3<f32>,
@location(3) color: vec4<f32>,
@builtin(vertex_index) index: u32,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
};
const EPSILON: f32 = 4.88e-04;
@vertex
fn vertex_bevel(vertex: VertexInput) -> VertexOutput {
var positions = array<vec2<f32>, 3>(
vec2(0, 0),
vec2(0, 0.5),
vec2(0.5, 0),
);
var position = positions[vertex.index];
let world_from_local = affine3_to_square(joints_gizmo.world_from_local);
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
var clip_c = view.clip_from_world * world_from_local * vec4(vertex.position_c, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_c);
clip_b = clip_near_plane(clip_b, clip_a);
clip_c = clip_near_plane(clip_c, clip_b);
clip_a = clip_near_plane(clip_a, clip_c);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
var color = vertex.color;
var line_width = joints_gizmo.line_width;
#ifdef PERSPECTIVE
line_width /= clip_b.w;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let ab = normalize(screen_b - screen_a);
let cb = normalize(screen_b - screen_c);
let ab_norm = vec2(-ab.y, ab.x);
let cb_norm = vec2(cb.y, -cb.x);
let tangent = normalize(ab - cb);
let normal = vec2(-tangent.y, tangent.x);
let sigma = sign(dot(ab + cb, normal));
var p0 = line_width * sigma * ab_norm;
var p1 = line_width * sigma * cb_norm;
let screen = screen_b + position.x * p0 + position.y * p1;
let depth = depth(clip_b);
var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
return VertexOutput(clip_position, color);
}
@vertex
fn vertex_miter(vertex: VertexInput) -> VertexOutput {
var positions = array<vec3<f32>, 6>(
vec3(0, 0, 0),
vec3(0.5, 0, 0),
vec3(0, 0.5, 0),
vec3(0, 0, 0),
vec3(0, 0.5, 0),
vec3(0, 0, 0.5),
);
var position = positions[vertex.index];
let world_from_local = affine3_to_square(joints_gizmo.world_from_local);
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
var clip_c = view.clip_from_world * world_from_local * vec4(vertex.position_c, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_c);
clip_b = clip_near_plane(clip_b, clip_a);
clip_c = clip_near_plane(clip_c, clip_b);
clip_a = clip_near_plane(clip_a, clip_c);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
var color = vertex.color;
var line_width = joints_gizmo.line_width;
#ifdef PERSPECTIVE
line_width /= clip_b.w;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let ab = normalize(screen_b - screen_a);
let cb = normalize(screen_b - screen_c);
let ab_norm = vec2(-ab.y, ab.x);
let cb_norm = vec2(cb.y, -cb.x);
let tangent = normalize(ab - cb);
let normal = vec2(-tangent.y, tangent.x);
let sigma = sign(dot(ab + cb, normal));
var p0 = line_width * sigma * ab_norm;
var p1 = line_width * sigma * normal / dot(normal, ab_norm);
var p2 = line_width * sigma * cb_norm;
var screen = screen_b + position.x * p0 + position.y * p1 + position.z * p2;
var depth = depth(clip_b);
var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
return VertexOutput(clip_position, color);
}
@vertex
fn vertex_round(vertex: VertexInput) -> VertexOutput {
let world_from_local = affine3_to_square(joints_gizmo.world_from_local);
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
var clip_c = view.clip_from_world * world_from_local * vec4(vertex.position_c, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_c);
clip_b = clip_near_plane(clip_b, clip_a);
clip_c = clip_near_plane(clip_c, clip_b);
clip_a = clip_near_plane(clip_a, clip_c);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let screen_c = resolution * (0.5 * clip_c.xy / clip_c.w + 0.5);
var color = vertex.color;
var line_width = joints_gizmo.line_width;
#ifdef PERSPECTIVE
line_width /= clip_b.w;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let ab = normalize(screen_b - screen_a);
let cb = normalize(screen_b - screen_c);
let ab_norm = vec2(-ab.y, ab.x);
let cb_norm = vec2(cb.y, -cb.x);
// We render `joints_gizmo.resolution`triangles. The vertices in each triangle are ordered as follows:
// - 0: The 'center' vertex at `screen_b`.
// - 1: The vertex closer to the ab line.
// - 2: The vertex closer to the cb line.
var in_triangle_index = f32(vertex.index) % 3.0;
var tri_index = floor(f32(vertex.index) / 3.0);
var radius = sign(in_triangle_index) * 0.5 * line_width;
var theta = acos(dot(ab_norm, cb_norm));
let sigma = sign(dot(ab_norm, cb));
var angle = theta * (tri_index + in_triangle_index - 1) / f32(joints_gizmo.resolution);
var position_x = sigma * radius * cos(angle);
var position_y = radius * sin(angle);
var screen = screen_b + position_x * ab_norm + position_y * ab;
var depth = depth(clip_b);
var clip_position = vec4(clip_b.w * ((2. * screen) / resolution - 1.), depth, clip_b.w);
return VertexOutput(clip_position, color);
}
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
// Move a if a is behind the near plane and b is in front.
if a.z > a.w && b.z <= b.w {
// Interpolate a towards b until it's at the near plane.
let distance_a = a.z - a.w;
let distance_b = b.z - b.w;
// Add an epsilon to the interpolator to ensure that the point is
// not just behind the clip plane due to floating-point imprecision.
let t = distance_a / (distance_a - distance_b) + EPSILON;
return mix(a, b, t);
}
return a;
}
fn depth(clip: vec4<f32>) -> f32 {
var depth: f32;
if joints_gizmo.depth_bias >= 0. {
depth = clip.z * (1. - joints_gizmo.depth_bias);
} else {
// depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
// and when equal to 0.0, it is exactly equal to depth.
// the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0
// clip.w represents the near plane in homogeneous clip space in bevy, having a depth
// of this value means nothing can be in front of this
// The reason this uses an exponential function is that it makes it much easier for the
// user to chose a value that is convenient for them
depth = clip.z * exp2(-joints_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON));
}
return depth;
}
struct FragmentInput {
@location(0) color: vec4<f32>,
};
struct FragmentOutput {
@location(0) color: vec4<f32>,
};
@fragment
fn fragment(in: FragmentInput) -> FragmentOutput {
// return FragmentOutput(vec4(1, 1, 1, 1));
return FragmentOutput(in.color);
}

189
vendor/bevy_gizmos/src/lines.wgsl vendored Normal file
View File

@@ -0,0 +1,189 @@
// TODO use common view binding
#import bevy_render::{view::View, maths::affine3_to_square}
@group(0) @binding(0) var<uniform> view: View;
struct LineGizmoUniform {
world_from_local: mat3x4<f32>,
line_width: f32,
depth_bias: f32,
_joints_resolution: u32,
gap_scale: f32,
line_scale: f32,
#ifdef SIXTEEN_BYTE_ALIGNMENT
// WebGL2 structs must be 16 byte aligned.
_padding: vec3<f32>,
#endif
}
@group(1) @binding(0) var<uniform> line_gizmo: LineGizmoUniform;
struct VertexInput {
@location(0) position_a: vec3<f32>,
@location(1) position_b: vec3<f32>,
@location(2) color_a: vec4<f32>,
@location(3) color_b: vec4<f32>,
@builtin(vertex_index) index: u32,
};
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
@location(2) line_fraction: f32,
};
const EPSILON: f32 = 4.88e-04;
@vertex
fn vertex(vertex: VertexInput) -> VertexOutput {
var positions = array<vec2<f32>, 6>(
vec2(-0.5, 0.),
vec2(-0.5, 1.),
vec2(0.5, 1.),
vec2(-0.5, 0.),
vec2(0.5, 1.),
vec2(0.5, 0.)
);
let position = positions[vertex.index];
let world_from_local = affine3_to_square(line_gizmo.world_from_local);
// algorithm based on https://wwwtyro.net/2019/11/18/instanced-lines.html
var clip_a = view.clip_from_world * world_from_local * vec4(vertex.position_a, 1.);
var clip_b = view.clip_from_world * world_from_local * vec4(vertex.position_b, 1.);
// Manual near plane clipping to avoid errors when doing the perspective divide inside this shader.
clip_a = clip_near_plane(clip_a, clip_b);
clip_b = clip_near_plane(clip_b, clip_a);
let clip = mix(clip_a, clip_b, position.y);
let resolution = view.viewport.zw;
let screen_a = resolution * (0.5 * clip_a.xy / clip_a.w + 0.5);
let screen_b = resolution * (0.5 * clip_b.xy / clip_b.w + 0.5);
let y_basis = normalize(screen_b - screen_a);
let x_basis = vec2(-y_basis.y, y_basis.x);
var color = mix(vertex.color_a, vertex.color_b, position.y);
var line_width = line_gizmo.line_width;
var alpha = 1.;
var uv: f32;
#ifdef PERSPECTIVE
line_width /= clip.w;
// get height of near clipping plane in world space
let pos0 = view.view_from_clip * vec4(0, -1, 0, 1); // Bottom of the screen
let pos1 = view.view_from_clip * vec4(0, 1, 0, 1); // Top of the screen
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);
// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
let position_a = view.world_from_clip * clip_a;
let position_b = view.world_from_clip * clip_b;
let world_distance = length(position_a.xyz - position_b.xyz);
// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.
let clipped_offset = length(position_a.xyz - vertex.position_a);
uv = (clipped_offset + position.y * world_distance) * resolution.y / near_clipping_plane_height / line_gizmo.line_width;
#else
// Get the distance of b to the camera along camera axes
let camera_b = view.view_from_clip * clip_b;
// This differentiates between orthographic and perspective cameras.
// For orthographic cameras no depth adaptment (depth_adaptment = 1) is needed.
var depth_adaptment: f32;
if (clip_b.w == 1.0) {
depth_adaptment = 1.0;
}
else {
depth_adaptment = -camera_b.z;
}
uv = position.y * depth_adaptment * length(screen_b - screen_a) / line_gizmo.line_width;
#endif
// Line thinness fade from https://acegikmo.com/shapes/docs/#anti-aliasing
if line_width > 0.0 && line_width < 1. {
color.a *= line_width;
line_width = 1.;
}
let x_offset = line_width * position.x * x_basis;
let screen = mix(screen_a, screen_b, position.y) + x_offset;
var depth: f32;
if line_gizmo.depth_bias >= 0. {
depth = clip.z * (1. - line_gizmo.depth_bias);
} else {
// depth * (clip.w / depth)^-depth_bias. So that when -depth_bias is 1.0, this is equal to clip.w
// and when equal to 0.0, it is exactly equal to depth.
// the epsilon is here to prevent the depth from exceeding clip.w when -depth_bias = 1.0
// clip.w represents the near plane in homogeneous clip space in bevy, having a depth
// of this value means nothing can be in front of this
// The reason this uses an exponential function is that it makes it much easier for the
// user to chose a value that is convenient for them
depth = clip.z * exp2(-line_gizmo.depth_bias * log2(clip.w / clip.z - EPSILON));
}
var clip_position = vec4(clip.w * ((2. * screen) / resolution - 1.), depth, clip.w);
let line_fraction = 2.0 * line_gizmo.line_scale / (line_gizmo.gap_scale + line_gizmo.line_scale);
uv /= (line_gizmo.gap_scale + line_gizmo.line_scale) / 2.0;
return VertexOutput(clip_position, color, uv, line_fraction);
}
fn clip_near_plane(a: vec4<f32>, b: vec4<f32>) -> vec4<f32> {
// Move a if a is behind the near plane and b is in front.
if a.z > a.w && b.z <= b.w {
// Interpolate a towards b until it's at the near plane.
let distance_a = a.z - a.w;
let distance_b = b.z - b.w;
// Add an epsilon to the interpolator to ensure that the point is
// not just behind the clip plane due to floating-point imprecision.
let t = distance_a / (distance_a - distance_b) + EPSILON;
return mix(a, b, t);
}
return a;
}
struct FragmentInput {
@builtin(position) position: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) uv: f32,
@location(2) line_fraction: f32,
};
struct FragmentOutput {
@location(0) color: vec4<f32>,
};
@fragment
fn fragment_solid(in: FragmentInput) -> FragmentOutput {
return FragmentOutput(in.color);
}
@fragment
fn fragment_dotted(in: FragmentInput) -> FragmentOutput {
var alpha: f32;
#ifdef PERSPECTIVE
alpha = 1 - floor(in.uv % 2.0);
#else
alpha = 1 - floor((in.uv * in.position.w) % 2.0);
#endif
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}
@fragment
fn fragment_dashed(in: FragmentInput) -> FragmentOutput {
#ifdef PERSPECTIVE
let uv = in.uv;
#else
let uv = in.uv * in.position.w;
#endif
let alpha = 1.0 - floor(min((uv % 2.0) / in.line_fraction, 1.0));
return FragmentOutput(vec4(in.color.xyz, in.color.w * alpha));
}

431
vendor/bevy_gizmos/src/pipeline_2d.rs vendored Normal file
View File

@@ -0,0 +1,431 @@
use crate::{
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
use bevy_app::{App, Plugin};
use bevy_core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT};
use bevy_ecs::{
prelude::Entity,
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Query, Res, ResMut},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_math::FloatOrd;
use bevy_render::sync_world::MainEntity;
use bevy_render::{
render_asset::{prepare_assets, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
ViewSortedRenderPhases,
},
render_resource::*,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
Render, RenderApp, RenderSet,
};
use bevy_sprite::{Mesh2dPipeline, Mesh2dPipelineKey, SetMesh2dViewBindGroup};
use tracing::error;
pub struct LineGizmo2dPlugin;
impl Plugin for LineGizmo2dPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.add_render_command::<Transparent2d, DrawLineGizmo2d>()
.add_render_command::<Transparent2d, DrawLineGizmo2dStrip>()
.add_render_command::<Transparent2d, DrawLineJointGizmo2d>()
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
.init_resource::<SpecializedRenderPipelines<LineJointGizmoPipeline>>()
.configure_sets(
Render,
GizmoRenderSystem::QueueLineGizmos2d
.in_set(RenderSet::Queue)
.ambiguous_with(bevy_sprite::queue_sprites)
.ambiguous_with(
bevy_sprite::queue_material2d_meshes::<bevy_sprite::ColorMaterial>,
),
)
.add_systems(
Render,
(queue_line_gizmos_2d, queue_line_joint_gizmos_2d)
.in_set(GizmoRenderSystem::QueueLineGizmos2d)
.after(prepare_assets::<GpuLineGizmo>),
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<LineGizmoPipeline>();
render_app.init_resource::<LineJointGizmoPipeline>();
}
}
#[derive(Clone, Resource)]
struct LineGizmoPipeline {
mesh_pipeline: Mesh2dPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineGizmoPipeline {
mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineGizmoPipelineKey {
mesh_key: Mesh2dPipelineKey,
strip: bool,
line_style: GizmoLineStyle,
}
impl SpecializedRenderPipeline for LineGizmoPipeline {
type Key = LineGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
let layout = vec![
self.mesh_pipeline.view_layout.clone(),
self.uniform_layout.clone(),
];
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: line_gizmo_vertex_buffer_layouts(key.strip),
},
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: false,
depth_compare: CompareFunction::Always,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: key.mesh_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
#[derive(Clone, Resource)]
struct LineJointGizmoPipeline {
mesh_pipeline: Mesh2dPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineJointGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineJointGizmoPipeline {
mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineJointGizmoPipelineKey {
mesh_key: Mesh2dPipelineKey,
joints: GizmoLineJoint,
}
impl SpecializedRenderPipeline for LineJointGizmoPipeline {
type Key = LineJointGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let format = if key.mesh_key.contains(Mesh2dPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
let layout = vec![
self.mesh_pipeline.view_layout.clone(),
self.uniform_layout.clone(),
];
if key.joints == GizmoLineJoint::None {
error!("There is no entry point for line joints with GizmoLineJoints::None. Please consider aborting the drawing process before reaching this stage.");
};
let entry_point = match key.joints {
GizmoLineJoint::Miter => "vertex_miter",
GizmoLineJoint::Round(_) => "vertex_round",
GizmoLineJoint::None | GizmoLineJoint::Bevel => "vertex_bevel",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_JOINT_SHADER_HANDLE,
entry_point: entry_point.into(),
shader_defs: shader_defs.clone(),
buffers: line_joint_gizmo_vertex_buffer_layouts(),
},
fragment: Some(FragmentState {
shader: LINE_JOINT_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_2D_DEPTH_FORMAT,
depth_write_enabled: false,
depth_compare: CompareFunction::Always,
stencil: StencilState {
front: StencilFaceState::IGNORE,
back: StencilFaceState::IGNORE,
read_mask: 0,
write_mask: 0,
},
bias: DepthBiasState {
constant: 0,
slope_scale: 0.0,
clamp: 0.0,
},
}),
multisample: MultisampleState {
count: key.mesh_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineJointGizmo Pipeline 2D".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
type DrawLineGizmo2d = (
SetItemPipeline,
SetMesh2dViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<false>,
);
type DrawLineGizmo2dStrip = (
SetItemPipeline,
SetMesh2dViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<true>,
);
type DrawLineJointGizmo2d = (
SetItemPipeline,
SetMesh2dViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineJointGizmo,
);
fn queue_line_gizmos_2d(
draw_functions: Res<DrawFunctions<Transparent2d>>,
pipeline: Res<LineGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
mut views: Query<(&ExtractedView, &Msaa, Option<&RenderLayers>)>,
) {
let draw_function = draw_functions.read().get_id::<DrawLineGizmo2d>().unwrap();
let draw_function_strip = draw_functions
.read()
.get_id::<DrawLineGizmo2dStrip>()
.unwrap();
for (view, msaa, render_layers) in &mut views {
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
| Mesh2dPipelineKey::from_hdr(view.hdr);
let render_layers = render_layers.unwrap_or_default();
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.list_vertex_count > 0 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
mesh_key,
strip: false,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent2d {
entity: (entity, *main_entity),
draw_function,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
indexed: false,
});
}
if line_gizmo.strip_vertex_count >= 2 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
mesh_key,
strip: true,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent2d {
entity: (entity, *main_entity),
draw_function: draw_function_strip,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
indexed: false,
});
}
}
}
}
fn queue_line_joint_gizmos_2d(
draw_functions: Res<DrawFunctions<Transparent2d>>,
pipeline: Res<LineJointGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineJointGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent2d>>,
mut views: Query<(&ExtractedView, &Msaa, Option<&RenderLayers>)>,
) {
let draw_function = draw_functions
.read()
.get_id::<DrawLineJointGizmo2d>()
.unwrap();
for (view, msaa, render_layers) in &mut views {
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let mesh_key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples())
| Mesh2dPipelineKey::from_hdr(view.hdr);
let render_layers = render_layers.unwrap_or_default();
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.strip_vertex_count < 3 || config.line_joints == GizmoLineJoint::None {
continue;
}
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineJointGizmoPipelineKey {
mesh_key,
joints: config.line_joints,
},
);
transparent_phase.add(Transparent2d {
entity: (entity, *main_entity),
draw_function,
pipeline,
sort_key: FloatOrd(f32::INFINITY),
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
extracted_index: usize::MAX,
indexed: false,
});
}
}
}

500
vendor/bevy_gizmos/src/pipeline_3d.rs vendored Normal file
View File

@@ -0,0 +1,500 @@
use crate::{
config::{GizmoLineJoint, GizmoLineStyle, GizmoMeshConfig},
line_gizmo_vertex_buffer_layouts, line_joint_gizmo_vertex_buffer_layouts, DrawLineGizmo,
DrawLineJointGizmo, GizmoRenderSystem, GpuLineGizmo, LineGizmoUniformBindgroupLayout,
SetLineGizmoBindGroup, LINE_JOINT_SHADER_HANDLE, LINE_SHADER_HANDLE,
};
use bevy_app::{App, Plugin};
use bevy_core_pipeline::{
core_3d::{Transparent3d, CORE_3D_DEPTH_FORMAT},
oit::OrderIndependentTransparencySettings,
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
};
use bevy_ecs::{
prelude::Entity,
query::Has,
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Query, Res, ResMut},
world::{FromWorld, World},
};
use bevy_image::BevyDefault as _;
use bevy_pbr::{MeshPipeline, MeshPipelineKey, SetMeshViewBindGroup};
use bevy_render::sync_world::MainEntity;
use bevy_render::{
render_asset::{prepare_assets, RenderAssets},
render_phase::{
AddRenderCommand, DrawFunctions, PhaseItemExtraIndex, SetItemPipeline,
ViewSortedRenderPhases,
},
render_resource::*,
view::{ExtractedView, Msaa, RenderLayers, ViewTarget},
Render, RenderApp, RenderSet,
};
use tracing::error;
pub struct LineGizmo3dPlugin;
impl Plugin for LineGizmo3dPlugin {
fn build(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app
.add_render_command::<Transparent3d, DrawLineGizmo3d>()
.add_render_command::<Transparent3d, DrawLineGizmo3dStrip>()
.add_render_command::<Transparent3d, DrawLineJointGizmo3d>()
.init_resource::<SpecializedRenderPipelines<LineGizmoPipeline>>()
.init_resource::<SpecializedRenderPipelines<LineJointGizmoPipeline>>()
.configure_sets(
Render,
GizmoRenderSystem::QueueLineGizmos3d
.in_set(RenderSet::Queue)
.ambiguous_with(bevy_pbr::queue_material_meshes::<bevy_pbr::StandardMaterial>),
)
.add_systems(
Render,
(queue_line_gizmos_3d, queue_line_joint_gizmos_3d)
.in_set(GizmoRenderSystem::QueueLineGizmos3d)
.after(prepare_assets::<GpuLineGizmo>),
);
}
fn finish(&self, app: &mut App) {
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<LineGizmoPipeline>();
render_app.init_resource::<LineJointGizmoPipeline>();
}
}
#[derive(Clone, Resource)]
struct LineGizmoPipeline {
mesh_pipeline: MeshPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineGizmoPipeline {
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineGizmoPipelineKey {
view_key: MeshPipelineKey,
strip: bool,
perspective: bool,
line_style: GizmoLineStyle,
}
impl SpecializedRenderPipeline for LineGizmoPipeline {
type Key = LineGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
if key.perspective {
shader_defs.push("PERSPECTIVE".into());
}
let format = if key.view_key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let view_layout = self
.mesh_pipeline
.get_view_layout(key.view_key.into())
.clone();
let layout = vec![view_layout, self.uniform_layout.clone()];
let fragment_entry_point = match key.line_style {
GizmoLineStyle::Solid => "fragment_solid",
GizmoLineStyle::Dotted => "fragment_dotted",
GizmoLineStyle::Dashed { .. } => "fragment_dashed",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_SHADER_HANDLE,
entry_point: "vertex".into(),
shader_defs: shader_defs.clone(),
buffers: line_gizmo_vertex_buffer_layouts(key.strip),
},
fragment: Some(FragmentState {
shader: LINE_SHADER_HANDLE,
shader_defs,
entry_point: fragment_entry_point.into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: key.view_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineGizmo 3d Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
#[derive(Clone, Resource)]
struct LineJointGizmoPipeline {
mesh_pipeline: MeshPipeline,
uniform_layout: BindGroupLayout,
}
impl FromWorld for LineJointGizmoPipeline {
fn from_world(render_world: &mut World) -> Self {
LineJointGizmoPipeline {
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
uniform_layout: render_world
.resource::<LineGizmoUniformBindgroupLayout>()
.layout
.clone(),
}
}
}
#[derive(PartialEq, Eq, Hash, Clone)]
struct LineJointGizmoPipelineKey {
view_key: MeshPipelineKey,
perspective: bool,
joints: GizmoLineJoint,
}
impl SpecializedRenderPipeline for LineJointGizmoPipeline {
type Key = LineJointGizmoPipelineKey;
fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
let mut shader_defs = vec![
#[cfg(feature = "webgl")]
"SIXTEEN_BYTE_ALIGNMENT".into(),
];
if key.perspective {
shader_defs.push("PERSPECTIVE".into());
}
let format = if key.view_key.contains(MeshPipelineKey::HDR) {
ViewTarget::TEXTURE_FORMAT_HDR
} else {
TextureFormat::bevy_default()
};
let view_layout = self
.mesh_pipeline
.get_view_layout(key.view_key.into())
.clone();
let layout = vec![view_layout, self.uniform_layout.clone()];
if key.joints == GizmoLineJoint::None {
error!("There is no entry point for line joints with GizmoLineJoints::None. Please consider aborting the drawing process before reaching this stage.");
};
let entry_point = match key.joints {
GizmoLineJoint::Miter => "vertex_miter",
GizmoLineJoint::Round(_) => "vertex_round",
GizmoLineJoint::None | GizmoLineJoint::Bevel => "vertex_bevel",
};
RenderPipelineDescriptor {
vertex: VertexState {
shader: LINE_JOINT_SHADER_HANDLE,
entry_point: entry_point.into(),
shader_defs: shader_defs.clone(),
buffers: line_joint_gizmo_vertex_buffer_layouts(),
},
fragment: Some(FragmentState {
shader: LINE_JOINT_SHADER_HANDLE,
shader_defs,
entry_point: "fragment".into(),
targets: vec![Some(ColorTargetState {
format,
blend: Some(BlendState::ALPHA_BLENDING),
write_mask: ColorWrites::ALL,
})],
}),
layout,
primitive: PrimitiveState::default(),
depth_stencil: Some(DepthStencilState {
format: CORE_3D_DEPTH_FORMAT,
depth_write_enabled: true,
depth_compare: CompareFunction::Greater,
stencil: StencilState::default(),
bias: DepthBiasState::default(),
}),
multisample: MultisampleState {
count: key.view_key.msaa_samples(),
mask: !0,
alpha_to_coverage_enabled: false,
},
label: Some("LineJointGizmo 3d Pipeline".into()),
push_constant_ranges: vec![],
zero_initialize_workgroup_memory: false,
}
}
}
type DrawLineGizmo3d = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<false>,
);
type DrawLineGizmo3dStrip = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineGizmo<true>,
);
type DrawLineJointGizmo3d = (
SetItemPipeline,
SetMeshViewBindGroup<0>,
SetLineGizmoBindGroup<1>,
DrawLineJointGizmo,
);
fn queue_line_gizmos_3d(
draw_functions: Res<DrawFunctions<Transparent3d>>,
pipeline: Res<LineGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
views: Query<(
&ExtractedView,
&Msaa,
Option<&RenderLayers>,
(
Has<NormalPrepass>,
Has<DepthPrepass>,
Has<MotionVectorPrepass>,
Has<DeferredPrepass>,
Has<OrderIndependentTransparencySettings>,
),
)>,
) {
let draw_function = draw_functions.read().get_id::<DrawLineGizmo3d>().unwrap();
let draw_function_strip = draw_functions
.read()
.get_id::<DrawLineGizmo3dStrip>()
.unwrap();
for (
view,
msaa,
render_layers,
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass, oit),
) in &views
{
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let render_layers = render_layers.unwrap_or_default();
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
| MeshPipelineKey::from_hdr(view.hdr);
if normal_prepass {
view_key |= MeshPipelineKey::NORMAL_PREPASS;
}
if depth_prepass {
view_key |= MeshPipelineKey::DEPTH_PREPASS;
}
if motion_vector_prepass {
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}
if deferred_prepass {
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
}
if oit {
view_key |= MeshPipelineKey::OIT_ENABLED;
}
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.list_vertex_count > 0 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
view_key,
strip: false,
perspective: config.line_perspective,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent3d {
entity: (entity, *main_entity),
draw_function,
pipeline,
distance: 0.,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
});
}
if line_gizmo.strip_vertex_count >= 2 {
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineGizmoPipelineKey {
view_key,
strip: true,
perspective: config.line_perspective,
line_style: config.line_style,
},
);
transparent_phase.add(Transparent3d {
entity: (entity, *main_entity),
draw_function: draw_function_strip,
pipeline,
distance: 0.,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
});
}
}
}
}
fn queue_line_joint_gizmos_3d(
draw_functions: Res<DrawFunctions<Transparent3d>>,
pipeline: Res<LineJointGizmoPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<LineJointGizmoPipeline>>,
pipeline_cache: Res<PipelineCache>,
line_gizmos: Query<(Entity, &MainEntity, &GizmoMeshConfig)>,
line_gizmo_assets: Res<RenderAssets<GpuLineGizmo>>,
mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,
views: Query<(
&ExtractedView,
&Msaa,
Option<&RenderLayers>,
(
Has<NormalPrepass>,
Has<DepthPrepass>,
Has<MotionVectorPrepass>,
Has<DeferredPrepass>,
),
)>,
) {
let draw_function = draw_functions
.read()
.get_id::<DrawLineJointGizmo3d>()
.unwrap();
for (
view,
msaa,
render_layers,
(normal_prepass, depth_prepass, motion_vector_prepass, deferred_prepass),
) in &views
{
let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)
else {
continue;
};
let render_layers = render_layers.unwrap_or_default();
let mut view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())
| MeshPipelineKey::from_hdr(view.hdr);
if normal_prepass {
view_key |= MeshPipelineKey::NORMAL_PREPASS;
}
if depth_prepass {
view_key |= MeshPipelineKey::DEPTH_PREPASS;
}
if motion_vector_prepass {
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}
if deferred_prepass {
view_key |= MeshPipelineKey::DEFERRED_PREPASS;
}
for (entity, main_entity, config) in &line_gizmos {
if !config.render_layers.intersects(render_layers) {
continue;
}
let Some(line_gizmo) = line_gizmo_assets.get(&config.handle) else {
continue;
};
if line_gizmo.strip_vertex_count < 3 || config.line_joints == GizmoLineJoint::None {
continue;
}
let pipeline = pipelines.specialize(
&pipeline_cache,
&pipeline,
LineJointGizmoPipelineKey {
view_key,
perspective: config.line_perspective,
joints: config.line_joints,
},
);
transparent_phase.add(Transparent3d {
entity: (entity, *main_entity),
draw_function,
pipeline,
distance: 0.,
batch_range: 0..1,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
});
}
}
}

View File

@@ -0,0 +1,902 @@
//! A module for rendering each of the 2D [`bevy_math::primitives`] with [`GizmoBuffer`].
use core::f32::consts::{FRAC_PI_2, PI};
use super::helpers::*;
use bevy_color::Color;
use bevy_math::{
primitives::{
Annulus, Arc2d, BoxedPolygon, BoxedPolyline2d, Capsule2d, Circle, CircularSector,
CircularSegment, Ellipse, Line2d, Plane2d, Polygon, Polyline2d, Primitive2d, Rectangle,
RegularPolygon, Rhombus, Segment2d, Triangle2d,
},
Dir2, Isometry2d, Rot2, Vec2,
};
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
// some magic number since using directions as offsets will result in lines of length 1 pixel
const MIN_LINE_LEN: f32 = 50.0;
const HALF_MIN_LINE_LEN: f32 = 25.0;
// length used to simulate infinite lines
const INFINITE_LEN: f32 = 100_000.0;
/// A trait for rendering 2D geometric primitives (`P`) with [`GizmoBuffer`].
pub trait GizmoPrimitive2d<P: Primitive2d> {
/// The output of `primitive_2d`. This is a builder to set non-default values.
type Output<'a>
where
Self: 'a;
/// Renders a 2D primitive with its associated details.
fn primitive_2d(
&mut self,
primitive: &P,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_>;
}
// direction 2d
impl<Config, Clear> GizmoPrimitive2d<Dir2> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Dir2,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let start = Vec2::ZERO;
let end = *primitive * MIN_LINE_LEN;
self.arrow_2d(isometry * start, isometry * end, color);
}
}
// arc 2d
impl<Config, Clear> GizmoPrimitive2d<Arc2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Arc2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let start_iso = isometry * Isometry2d::from_rotation(Rot2::radians(-primitive.half_angle));
self.arc_2d(
start_iso,
primitive.half_angle * 2.0,
primitive.radius,
color,
);
}
}
// circle 2d
impl<Config, Clear> GizmoPrimitive2d<Circle> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= crate::circles::Ellipse2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Circle,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.circle_2d(isometry, primitive.radius, color)
}
}
// circular sector 2d
impl<Config, Clear> GizmoPrimitive2d<CircularSector> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &CircularSector,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
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(
start_iso,
primitive.arc.half_angle * 2.0,
primitive.arc.radius,
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);
}
}
// circular segment 2d
impl<Config, Clear> GizmoPrimitive2d<CircularSegment> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &CircularSegment,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
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(
start_iso,
primitive.arc.half_angle * 2.0,
primitive.arc.radius,
color,
);
let position = primitive.arc.radius * Vec2::Y;
self.line_2d(start_iso * position, end_iso * position, color);
}
}
// ellipse 2d
impl<Config, Clear> GizmoPrimitive2d<Ellipse> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= crate::circles::Ellipse2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d<'a>(
&mut self,
primitive: &Ellipse,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.ellipse_2d(isometry, primitive.half_size, color)
}
}
// annulus 2d
/// Builder for configuring the drawing options of [`Annulus`].
pub struct Annulus2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
isometry: Isometry2d,
inner_radius: f32,
outer_radius: f32,
color: Color,
inner_resolution: u32,
outer_resolution: u32,
}
impl<Config, Clear> Annulus2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of line-segments for each circle of the annulus.
pub fn resolution(mut self, resolution: u32) -> Self {
self.outer_resolution = resolution;
self.inner_resolution = resolution;
self
}
/// Set the number of line-segments for the outer circle of the annulus.
pub fn outer_resolution(mut self, resolution: u32) -> Self {
self.outer_resolution = resolution;
self
}
/// Set the number of line-segments for the inner circle of the annulus.
pub fn inner_resolution(mut self, resolution: u32) -> Self {
self.inner_resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive2d<Annulus> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Annulus2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Annulus,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Annulus2dBuilder {
gizmos: self,
isometry: isometry.into(),
inner_radius: primitive.inner_circle.radius,
outer_radius: primitive.outer_circle.radius,
color: color.into(),
inner_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
outer_resolution: crate::circles::DEFAULT_CIRCLE_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Annulus2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let Annulus2dBuilder {
gizmos,
isometry,
inner_radius,
outer_radius,
inner_resolution,
outer_resolution,
color,
..
} = self;
gizmos
.circle_2d(*isometry, *outer_radius, *color)
.resolution(*outer_resolution);
gizmos
.circle_2d(*isometry, *inner_radius, *color)
.resolution(*inner_resolution);
}
}
// rhombus 2d
impl<Config, Clear> GizmoPrimitive2d<Rhombus> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Rhombus,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
};
let isometry = isometry.into();
let [a, b, c, d] =
[(1.0, 0.0), (0.0, 1.0), (-1.0, 0.0), (0.0, -1.0)].map(|(sign_x, sign_y)| {
Vec2::new(
primitive.half_diagonals.x * sign_x,
primitive.half_diagonals.y * sign_y,
)
});
let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);
self.linestrip_2d(positions, color);
}
}
// capsule 2d
impl<Config, Clear> GizmoPrimitive2d<Capsule2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Capsule2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let polymorphic_color: Color = color.into();
if !self.enabled {
return;
}
// transform points from the reference unit square to capsule "rectangle"
let [top_left, top_right, bottom_left, bottom_right, top_center, bottom_center] = [
[-1.0, 1.0],
[1.0, 1.0],
[-1.0, -1.0],
[1.0, -1.0],
// just reuse the pipeline for these points as well
[0.0, 1.0],
[0.0, -1.0],
]
.map(|[sign_x, sign_y]| Vec2::X * sign_x + Vec2::Y * sign_y)
.map(|reference_point| {
let scaling = Vec2::X * primitive.radius + Vec2::Y * primitive.half_length;
reference_point * scaling
})
.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 = isometry.rotation.as_radians() - FRAC_PI_2;
let start_angle_bottom = isometry.rotation.as_radians() + FRAC_PI_2;
// draw arcs
self.arc_2d(
Isometry2d::new(top_center, Rot2::radians(start_angle_top)),
PI,
primitive.radius,
polymorphic_color,
);
self.arc_2d(
Isometry2d::new(bottom_center, Rot2::radians(start_angle_bottom)),
PI,
primitive.radius,
polymorphic_color,
);
}
}
// line 2d
//
/// Builder for configuring the drawing options of [`Line2d`].
pub struct Line2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
direction: Dir2, // Direction 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
}
impl<Config, Clear> Line2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the drawing mode of the line (arrow vs. plain line)
pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
self.draw_arrow = is_enabled;
self
}
}
impl<Config, Clear> GizmoPrimitive2d<Line2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Line2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Line2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Line2dBuilder {
gizmos: self,
direction: primitive.direction,
isometry: isometry.into(),
color: color.into(),
draw_arrow: false,
}
}
}
impl<Config, Clear> Drop for Line2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
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| 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.isometry * (-self.direction * MIN_LINE_LEN),
self.isometry * Vec2::ZERO,
self.color,
);
}
}
}
// plane 2d
impl<Config, Clear> GizmoPrimitive2d<Plane2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Plane2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let polymorphic_color: Color = color.into();
if !self.enabled {
return;
}
// draw normal of the plane (orthogonal to the plane itself)
let normal = primitive.normal;
let normal_segment = Segment2d::from_direction_and_length(normal, HALF_MIN_LINE_LEN * 2.);
self.primitive_2d(
&normal_segment,
// offset the normal so it starts on the plane line
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 }, 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(
isometry * Vec2::ZERO,
isometry * (MIN_LINE_LEN * direction),
polymorphic_color,
);
}
}
// segment 2d
/// Builder for configuring the drawing options of [`Segment2d`].
pub struct Segment2dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
point1: Vec2, // First point of the segment
point2: Vec2, // Second point of the 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
}
impl<Config, Clear> Segment2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the drawing mode of the line (arrow vs. plain line)
pub fn draw_arrow(mut self, is_enabled: bool) -> Self {
self.draw_arrow = is_enabled;
self
}
}
impl<Config, Clear> GizmoPrimitive2d<Segment2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Segment2dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Segment2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Segment2dBuilder {
gizmos: self,
point1: primitive.point1(),
point2: primitive.point2(),
isometry: isometry.into(),
color: color.into(),
draw_arrow: Default::default(),
}
}
}
impl<Config, Clear> Drop for Segment2dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let segment = Segment2d::new(self.point1, self.point2).transformed(self.isometry);
if self.draw_arrow {
self.gizmos
.arrow_2d(segment.point1(), segment.point2(), self.color);
} else {
self.gizmos
.line_2d(segment.point1(), segment.point2(), self.color);
}
}
}
// polyline 2d
impl<const N: usize, Config, Clear> GizmoPrimitive2d<Polyline2d<N>> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Polyline2d<N>,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.map(|vec2| isometry * vec2),
color,
);
}
}
// boxed polyline 2d
impl<Config, Clear> GizmoPrimitive2d<BoxedPolyline2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &BoxedPolyline2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.map(|vec2| isometry * vec2),
color,
);
}
}
// triangle 2d
impl<Config, Clear> GizmoPrimitive2d<Triangle2d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Triangle2d,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c] = primitive.vertices;
let positions = [a, b, c, a].map(|vec2| isometry * vec2);
self.linestrip_2d(positions, color);
}
}
// rectangle 2d
impl<Config, Clear> GizmoPrimitive2d<Rectangle> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Rectangle,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c, d] =
[(1.0, 1.0), (1.0, -1.0), (-1.0, -1.0), (-1.0, 1.0)].map(|(sign_x, sign_y)| {
Vec2::new(
primitive.half_size.x * sign_x,
primitive.half_size.y * sign_y,
)
});
let positions = [a, b, c, d, a].map(|vec2| isometry * vec2);
self.linestrip_2d(positions, color);
}
}
// polygon 2d
impl<const N: usize, Config, Clear> GizmoPrimitive2d<Polygon<N>> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &Polygon<N>,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
// Check if the polygon needs a closing point
let closing_point = {
let first = primitive.vertices.first();
(primitive.vertices.last() != first)
.then_some(first)
.flatten()
.cloned()
};
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.chain(closing_point)
.map(|vec2| isometry * vec2),
color,
);
}
}
// boxed polygon 2d
impl<Config, Clear> GizmoPrimitive2d<BoxedPolygon> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &BoxedPolygon,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let closing_point = {
let first = primitive.vertices.first();
(primitive.vertices.last() != first)
.then_some(first)
.flatten()
.cloned()
};
self.linestrip_2d(
primitive
.vertices
.iter()
.copied()
.chain(closing_point)
.map(|vec2| isometry * vec2),
color,
);
}
}
// regular polygon 2d
impl<Config, Clear> GizmoPrimitive2d<RegularPolygon> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_2d(
&mut self,
primitive: &RegularPolygon,
isometry: impl Into<Isometry2d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let points = (0..=primitive.sides)
.map(|n| single_circle_coordinate(primitive.circumcircle.radius, primitive.sides, n))
.map(|vec2| isometry * vec2);
self.linestrip_2d(points, color);
}
}

View File

@@ -0,0 +1,965 @@
//! A module for rendering each of the 3D [`bevy_math::primitives`] with [`GizmoBuffer`].
use super::helpers::*;
use bevy_color::Color;
use bevy_math::{
primitives::{
BoxedPolyline3d, Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d,
Polyline3d, Primitive3d, Segment3d, Sphere, Tetrahedron, Torus, Triangle3d,
},
Dir3, Isometry3d, Quat, UVec2, Vec2, Vec3,
};
use crate::{circles::SphereBuilder, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
const DEFAULT_RESOLUTION: u32 = 5;
// length used to simulate infinite lines
const INFINITE_LEN: f32 = 10_000.0;
/// A trait for rendering 3D geometric primitives (`P`) with [`GizmoBuffer`].
pub trait GizmoPrimitive3d<P: Primitive3d> {
/// The output of `primitive_3d`. This is a builder to set non-default values.
type Output<'a>
where
Self: 'a;
/// Renders a 3D primitive with its associated details.
fn primitive_3d(
&mut self,
primitive: &P,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_>;
}
// direction 3d
impl<Config, Clear> GizmoPrimitive3d<Dir3> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Dir3,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
let isometry = isometry.into();
let start = Vec3::ZERO;
let end = primitive.as_vec3();
self.arrow(isometry * start, isometry * end, color);
}
}
// sphere
impl<Config, Clear> GizmoPrimitive3d<Sphere> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= SphereBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Sphere,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
self.sphere(isometry, primitive.radius, color)
}
}
// plane 3d
/// Builder for configuring the drawing options of [`Plane3d`].
pub struct Plane3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Direction of the normal orthogonal to the plane
normal: Dir3,
isometry: Isometry3d,
// Color of the plane
color: Color,
// Defines the amount of cells in the x and y axes
cell_count: UVec2,
// Defines the distance between cells along the x and y axes
spacing: Vec2,
}
impl<Config, Clear> Plane3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of cells in the x and y axes direction.
pub fn cell_count(mut self, cell_count: UVec2) -> Self {
self.cell_count = cell_count;
self
}
/// Set the distance between cells along the x and y axes.
pub fn spacing(mut self, spacing: Vec2) -> Self {
self.spacing = spacing;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Plane3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Plane3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Plane3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Plane3dBuilder {
gizmos: self,
normal: primitive.normal,
isometry: isometry.into(),
color: color.into(),
cell_count: UVec2::splat(3),
spacing: Vec2::splat(1.0),
}
}
}
impl<Config, Clear> Drop for Plane3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
self.gizmos
.primitive_3d(&self.normal, self.isometry, self.color);
// the default orientation of the grid is Z-up
let rot = Quat::from_rotation_arc(Vec3::Z, self.normal.as_vec3());
self.gizmos.grid(
Isometry3d::new(self.isometry.translation, self.isometry.rotation * rot),
self.cell_count,
self.spacing,
self.color,
);
}
}
// line 3d
impl<Config, Clear> GizmoPrimitive3d<Line3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Line3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let color = color.into();
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| primitive.direction * length)
.map(|offset| isometry * offset);
self.line(start, end, color);
}
}
// segment 3d
impl<Config, Clear> GizmoPrimitive3d<Segment3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Segment3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let transformed = primitive.transformed(isometry);
self.line(transformed.point1(), transformed.point2(), color);
}
}
// polyline 3d
impl<const N: usize, Config, Clear> GizmoPrimitive3d<Polyline3d<N>> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Polyline3d<N>,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip(primitive.vertices.map(|vec3| isometry * vec3), color);
}
}
// boxed polyline 3d
impl<Config, Clear> GizmoPrimitive3d<BoxedPolyline3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &BoxedPolyline3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
self.linestrip(
primitive
.vertices
.iter()
.copied()
.map(|vec3| isometry * vec3),
color,
);
}
}
// triangle 3d
impl<Config, Clear> GizmoPrimitive3d<Triangle3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Triangle3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
let [a, b, c] = primitive.vertices;
self.linestrip([a, b, c, a].map(|vec3| isometry * vec3), color);
}
}
// cuboid
impl<Config, Clear> GizmoPrimitive3d<Cuboid> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Cuboid,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
// 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],
[-1.0, 1.0, 1.0],
[-1.0, -1.0, 1.0],
[1.0, -1.0, 1.0],
[1.0, 1.0, -1.0],
[-1.0, 1.0, -1.0],
[-1.0, -1.0, -1.0],
[1.0, -1.0, -1.0],
]
.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]
.into_iter()
.zip([a, b, c, d].into_iter().cycle().skip(1));
// lines for the lower rectangle of the cuboid
let lower = [e, f, g, h]
.into_iter()
.zip([e, f, g, h].into_iter().cycle().skip(1));
// lines connecting upper and lower rectangles of the cuboid
let connections = vertices.into_iter().zip(vertices.into_iter().skip(4));
let color = color.into();
upper
.chain(lower)
.chain(connections)
.for_each(|(start, end)| {
self.line(start, end, color);
});
}
}
// cylinder 3d
/// Builder for configuring the drawing options of [`Cylinder`].
pub struct Cylinder3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the cylinder
radius: f32,
// Half height of the cylinder
half_height: f32,
isometry: Isometry3d,
// Color of the cylinder
color: Color,
// Number of lines used to approximate the cylinder geometry
resolution: u32,
}
impl<Config, Clear> Cylinder3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the top and bottom of the cylinder geometry.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Cylinder> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Cylinder3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Cylinder,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Cylinder3dBuilder {
gizmos: self,
radius: primitive.radius,
half_height: primitive.half_height,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Cylinder3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
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);
}
}
// capsule 3d
/// Builder for configuring the drawing options of [`Capsule3d`].
pub struct Capsule3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the capsule
radius: f32,
// Half length of the capsule
half_length: f32,
isometry: Isometry3d,
// Color of the capsule
color: Color,
// Number of lines used to approximate the capsule geometry
resolution: u32,
}
impl<Config, Clear> Capsule3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the capsule geometry.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Capsule3d> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Capsule3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Capsule3d,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Capsule3dBuilder {
gizmos: self,
radius: primitive.radius,
half_length: primitive.half_length,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Capsule3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
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::<Vec<_>>()
});
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);
});
let circle_rotation = self
.isometry
.rotation
.mul_quat(Quat::from_rotation_x(core::f32::consts::FRAC_PI_2));
self.gizmos.circle(
Isometry3d::new(upper_center, circle_rotation),
self.radius,
self.color,
);
self.gizmos.circle(
Isometry3d::new(lower_center, circle_rotation),
self.radius,
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);
});
}
}
// cone 3d
/// Builder for configuring the drawing options of [`Cone`].
pub struct Cone3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the cone
radius: f32,
// Height of the cone
height: f32,
isometry: Isometry3d,
// Color of the cone
color: Color,
// Number of lines used to approximate the cone base geometry
base_resolution: u32,
// Number of lines used to approximate the cone height geometry
height_resolution: u32,
}
impl<Config, Clear> Cone3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the cone geometry for its base and height.
pub fn resolution(mut self, resolution: u32) -> Self {
self.base_resolution = resolution;
self.height_resolution = resolution;
self
}
/// Set the number of lines used to approximate the height of the cone geometry.
///
/// `resolution` should be a multiple of the value passed to [`Self::height_resolution`]
/// for the height to connect properly with the base.
pub fn base_resolution(mut self, resolution: u32) -> Self {
self.base_resolution = resolution;
self
}
/// Set the number of lines used to approximate the height of the cone geometry.
///
/// `resolution` should be a divisor of the value passed to [`Self::base_resolution`]
/// for the height to connect properly with the base.
pub fn height_resolution(mut self, resolution: u32) -> Self {
self.height_resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Cone> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Cone3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Cone,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Cone3dBuilder {
gizmos: self,
radius: primitive.radius,
height: primitive.height,
isometry: isometry.into(),
color: color.into(),
base_resolution: DEFAULT_RESOLUTION,
height_resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Cone3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
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::<Vec<_>>();
// connections to apex
circle_coords
.iter()
.skip(1)
.map(|vec3| (*vec3, apex))
.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
// base circle
circle_coords
.windows(2)
.map(|win| (win[0], win[1]))
.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
}
}
// conical frustum 3d
/// Builder for configuring the drawing options of [`ConicalFrustum`].
pub struct ConicalFrustum3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the top circle
radius_top: f32,
// Radius of the bottom circle
radius_bottom: f32,
// Height of the conical frustum
height: f32,
isometry: Isometry3d,
// Color of the conical frustum
color: Color,
// Number of lines used to approximate the curved surfaces
resolution: u32,
}
impl<Config, Clear> ConicalFrustum3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines used to approximate the curved surfaces.
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<ConicalFrustum> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ConicalFrustum3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &ConicalFrustum,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
ConicalFrustum3dBuilder {
gizmos: self,
radius_top: primitive.radius_top,
radius_bottom: primitive.radius_bottom,
height: primitive.height,
isometry: isometry.into(),
color: color.into(),
resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for ConicalFrustum3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
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::<Vec<_>>()
});
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);
});
}
}
// torus 3d
/// Builder for configuring the drawing options of [`Torus`].
pub struct Torus3dBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut GizmoBuffer<Config, Clear>,
// Radius of the minor circle (tube)
minor_radius: f32,
// Radius of the major circle (ring)
major_radius: f32,
isometry: Isometry3d,
// Color of the torus
color: Color,
// Number of lines in the minor (tube) direction
minor_resolution: u32,
// Number of lines in the major (ring) direction
major_resolution: u32,
}
impl<Config, Clear> Torus3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Set the number of lines in the minor (tube) direction.
pub fn minor_resolution(mut self, minor_resolution: u32) -> Self {
self.minor_resolution = minor_resolution;
self
}
/// Set the number of lines in the major (ring) direction.
pub fn major_resolution(mut self, major_resolution: u32) -> Self {
self.major_resolution = major_resolution;
self
}
}
impl<Config, Clear> GizmoPrimitive3d<Torus> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Torus3dBuilder<'a, Config, Clear>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Torus,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
Torus3dBuilder {
gizmos: self,
minor_radius: primitive.minor_radius,
major_radius: primitive.major_radius,
isometry: isometry.into(),
color: color.into(),
minor_resolution: DEFAULT_RESOLUTION,
major_resolution: DEFAULT_RESOLUTION,
}
}
}
impl<Config, Clear> Drop for Torus3dBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
// draw 4 circles with major_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),
]
.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::<Vec<_>>()
});
[&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);
});
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(|(from, to, center)| {
self.gizmos
.short_arc_3d_between(center, from, to, self.color)
.resolution(self.minor_resolution);
});
}
}
// tetrahedron
impl<Config, Clear> GizmoPrimitive3d<Tetrahedron> for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ()
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &Tetrahedron,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_> {
if !self.enabled {
return;
}
let isometry = isometry.into();
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();
lines.into_iter().for_each(|(start, end)| {
self.line(start, end, color);
});
}
}

View File

@@ -0,0 +1,43 @@
use core::f32::consts::TAU;
use bevy_math::{ops, Vec2};
/// Calculates the `nth` coordinate of a circle.
///
/// Given a circle's radius and its resolution, this function computes the position
/// of the `nth` point along the circumference of the circle. The rotation starts at `(0.0, radius)`
/// and proceeds counter-clockwise.
pub(crate) fn single_circle_coordinate(radius: f32, resolution: u32, nth_point: u32) -> Vec2 {
let angle = nth_point as f32 * TAU / resolution as f32;
let (x, y) = ops::sin_cos(angle);
Vec2::new(x, y) * radius
}
/// Generates an iterator over the coordinates of a circle.
///
/// The coordinates form an 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.
pub(crate) fn circle_coordinates(radius: f32, resolution: u32) -> impl Iterator<Item = Vec2> {
(0..)
.map(move |p| single_circle_coordinate(radius, resolution, p))
.take(resolution as usize)
}
/// Generates an iterator over the coordinates of a circle.
///
/// The coordinates form a closed circle, meaning the first and last points are 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.
pub(crate) fn circle_coordinates_closed(
radius: f32,
resolution: u32,
) -> impl Iterator<Item = Vec2> {
circle_coordinates(radius, resolution).chain(core::iter::once(single_circle_coordinate(
radius, resolution, resolution,
)))
}

View File

@@ -0,0 +1,5 @@
//! A module for rendering each of the 2D and 3D [`bevy_math::primitives`] with [`crate::prelude::Gizmos`].
pub mod dim2;
pub mod dim3;
pub(crate) mod helpers;

160
vendor/bevy_gizmos/src/retained.rs vendored Normal file
View File

@@ -0,0 +1,160 @@
//! This module is for 'retained' alternatives to the 'immediate mode' [`Gizmos`](crate::gizmos::Gizmos) system parameter.
use core::ops::{Deref, DerefMut};
use bevy_asset::Handle;
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::components::Transform;
#[cfg(feature = "bevy_render")]
use {
crate::{config::GizmoLineJoint, LineGizmoUniform},
bevy_ecs::{
entity::Entity,
system::{Commands, Local, Query},
},
bevy_render::{view::RenderLayers, Extract},
bevy_transform::components::GlobalTransform,
};
use crate::{
config::{ErasedGizmoConfigGroup, GizmoLineConfig},
gizmos::GizmoBuffer,
GizmoAsset,
};
impl Deref for GizmoAsset {
type Target = GizmoBuffer<ErasedGizmoConfigGroup, ()>;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl DerefMut for GizmoAsset {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
/// A component that draws the gizmos of a [`GizmoAsset`].
///
/// When drawing a greater number of static lines a [`Gizmo`] component can
/// have far better performance than the [`Gizmos`] system parameter,
/// but the system parameter will perform better for smaller lines that update often.
///
/// ## Example
/// ```
/// # use bevy_ecs::prelude::*;
/// # use bevy_gizmos::prelude::*;
/// # use bevy_asset::prelude::*;
/// # use bevy_color::palettes::css::*;
/// # use bevy_utils::default;
/// # use bevy_math::prelude::*;
/// fn system(
/// mut commands: Commands,
/// mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
/// ) {
/// let mut gizmo = GizmoAsset::default();
///
/// gizmo.sphere(Vec3::ZERO, 1., RED);
///
/// commands.spawn(Gizmo {
/// handle: gizmo_assets.add(gizmo),
/// line_config: GizmoLineConfig {
/// width: 4.,
/// ..default()
/// },
/// ..default()
/// });
/// }
/// ```
///
/// [`Gizmos`]: crate::gizmos::Gizmos
#[derive(Component, Clone, Debug, Default, Reflect)]
#[reflect(Component, Clone, Default)]
#[require(Transform)]
pub struct Gizmo {
/// The handle to the gizmo to draw.
pub handle: Handle<GizmoAsset>,
/// The line specific configuration for this gizmo.
pub line_config: GizmoLineConfig,
/// How closer to the camera than real geometry the gizmo should be.
///
/// In 2D this setting has no effect and is effectively always -1.
///
/// Value between -1 and 1 (inclusive).
/// * 0 means that there is no change to the gizmo position when rendering
/// * 1 means it is furthest away from camera as possible
/// * -1 means that it will always render in front of other things.
///
/// This is typically useful if you are drawing wireframes on top of polygons
/// and your wireframe is z-fighting (flickering on/off) with your main model.
/// You would set this value to a negative number close to 0.
pub depth_bias: f32,
}
#[cfg(feature = "bevy_render")]
pub(crate) fn extract_linegizmos(
mut commands: Commands,
mut previous_len: Local<usize>,
query: Extract<Query<(Entity, &Gizmo, &GlobalTransform, Option<&RenderLayers>)>>,
) {
use bevy_math::Affine3;
use bevy_render::sync_world::{MainEntity, TemporaryRenderEntity};
use bevy_utils::once;
use tracing::warn;
use crate::config::GizmoLineStyle;
let mut values = Vec::with_capacity(*previous_len);
for (entity, gizmo, transform, render_layers) in &query {
let joints_resolution = if let GizmoLineJoint::Round(resolution) = gizmo.line_config.joints
{
resolution
} else {
0
};
let (gap_scale, line_scale) = if let GizmoLineStyle::Dashed {
gap_scale,
line_scale,
} = gizmo.line_config.style
{
if gap_scale <= 0.0 {
once!(warn!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the gap scale should be greater than zero"));
}
if line_scale <= 0.0 {
once!(warn!("when using gizmos with the line style `GizmoLineStyle::Dashed{{..}}` the line scale should be greater than zero"));
}
(gap_scale, line_scale)
} else {
(1.0, 1.0)
};
values.push((
LineGizmoUniform {
world_from_local: Affine3::from(&transform.affine()).to_transpose(),
line_width: gizmo.line_config.width,
depth_bias: gizmo.depth_bias,
joints_resolution,
gap_scale,
line_scale,
#[cfg(feature = "webgl")]
_padding: Default::default(),
},
#[cfg(any(feature = "bevy_pbr", feature = "bevy_sprite"))]
crate::config::GizmoMeshConfig {
line_perspective: gizmo.line_config.perspective,
line_style: gizmo.line_config.style,
line_joints: gizmo.line_config.joints,
render_layers: render_layers.cloned().unwrap_or_default(),
handle: gizmo.handle.clone_weak(),
},
MainEntity::from(entity),
TemporaryRenderEntity,
));
}
*previous_len = values.len();
commands.spawn_batch(values);
}

400
vendor/bevy_gizmos/src/rounded_box.rs vendored Normal file
View File

@@ -0,0 +1,400 @@
//! Additional [`GizmoBuffer`] Functions -- Rounded cuboids and rectangles
//!
//! Includes the implementation of [`GizmoBuffer::rounded_rect`], [`GizmoBuffer::rounded_rect_2d`] and [`GizmoBuffer::rounded_cuboid`].
//! and assorted support items.
use core::f32::consts::FRAC_PI_2;
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{Isometry2d, Isometry3d, Quat, Vec2, Vec3};
use bevy_transform::components::Transform;
/// A builder returned by [`GizmoBuffer::rounded_rect`] and [`GizmoBuffer::rounded_rect_2d`]
pub struct RoundedRectBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
size: Vec2,
gizmos: &'a mut GizmoBuffer<Config, Clear>,
config: RoundedBoxConfig,
}
/// A builder returned by [`GizmoBuffer::rounded_cuboid`]
pub struct RoundedCuboidBuilder<'a, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
size: Vec3,
gizmos: &'a mut GizmoBuffer<Config, Clear>,
config: RoundedBoxConfig,
}
struct RoundedBoxConfig {
isometry: Isometry3d,
color: Color,
corner_radius: f32,
arc_resolution: u32,
}
impl<Config, Clear> RoundedRectBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Change the radius of the corners to be `corner_radius`.
/// The default corner radius is [min axis of size] / 10.0
pub fn corner_radius(mut self, corner_radius: f32) -> Self {
self.config.corner_radius = corner_radius;
self
}
/// Change the resolution of the arcs at the corners of the rectangle.
/// The default value is 8
pub fn arc_resolution(mut self, arc_resolution: u32) -> Self {
self.config.arc_resolution = arc_resolution;
self
}
}
impl<Config, Clear> RoundedCuboidBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Change the radius of the edges to be `edge_radius`.
/// The default edge radius is [min axis of size] / 10.0
pub fn edge_radius(mut self, edge_radius: f32) -> Self {
self.config.corner_radius = edge_radius;
self
}
/// Change the resolution of the arcs at the edges of the cuboid.
/// The default value is 8
pub fn arc_resolution(mut self, arc_resolution: u32) -> Self {
self.config.arc_resolution = arc_resolution;
self
}
}
impl<Config, Clear> Drop for RoundedRectBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let config = &self.config;
// Calculate inner and outer half size and ensure that the edge_radius is <= any half_length
let mut outer_half_size = self.size.abs() / 2.0;
let inner_half_size =
(outer_half_size - Vec2::splat(config.corner_radius.abs())).max(Vec2::ZERO);
let corner_radius = (outer_half_size - inner_half_size).min_element();
let mut inner_half_size = outer_half_size - Vec2::splat(corner_radius);
if config.corner_radius < 0. {
core::mem::swap(&mut outer_half_size, &mut inner_half_size);
}
// Handle cases where the rectangle collapses into simpler shapes
if outer_half_size.x * outer_half_size.y == 0. {
self.gizmos.line(
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.isometry, self.size, config.color);
return;
}
let vertices = [
// top right
Vec3::new(inner_half_size.x, outer_half_size.y, 0.),
Vec3::new(inner_half_size.x, inner_half_size.y, 0.),
Vec3::new(outer_half_size.x, inner_half_size.y, 0.),
// bottom right
Vec3::new(outer_half_size.x, -inner_half_size.y, 0.),
Vec3::new(inner_half_size.x, -inner_half_size.y, 0.),
Vec3::new(inner_half_size.x, -outer_half_size.y, 0.),
// bottom left
Vec3::new(-inner_half_size.x, -outer_half_size.y, 0.),
Vec3::new(-inner_half_size.x, -inner_half_size.y, 0.),
Vec3::new(-outer_half_size.x, -inner_half_size.y, 0.),
// top left
Vec3::new(-outer_half_size.x, inner_half_size.y, 0.),
Vec3::new(-inner_half_size.x, inner_half_size.y, 0.),
Vec3::new(-inner_half_size.x, outer_half_size.y, 0.),
]
.map(|vec3| config.isometry * vec3);
for chunk in vertices.chunks_exact(3) {
self.gizmos
.short_arc_3d_between(chunk[1], chunk[0], chunk[2], config.color)
.resolution(config.arc_resolution);
}
let edges = if config.corner_radius > 0. {
[
(vertices[2], vertices[3]),
(vertices[5], vertices[6]),
(vertices[8], vertices[9]),
(vertices[11], vertices[0]),
]
} else {
[
(vertices[0], vertices[5]),
(vertices[3], vertices[8]),
(vertices[6], vertices[11]),
(vertices[9], vertices[2]),
]
};
for (start, end) in edges {
self.gizmos.line(start, end, config.color);
}
}
}
impl<Config, Clear> Drop for RoundedCuboidBuilder<'_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn drop(&mut self) {
if !self.gizmos.enabled {
return;
}
let config = &self.config;
// Calculate inner and outer half size and ensure that the edge_radius is <= any half_length
let outer_half_size = self.size.abs() / 2.0;
let inner_half_size =
(outer_half_size - Vec3::splat(config.corner_radius.abs())).max(Vec3::ZERO);
let mut edge_radius = (outer_half_size - inner_half_size).min_element();
let inner_half_size = outer_half_size - Vec3::splat(edge_radius);
edge_radius *= config.corner_radius.signum();
// Handle cases where the rounded cuboid collapses into simpler shapes
if edge_radius == 0.0 {
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;
}
let rects = [
(
Vec3::X,
Vec2::new(self.size.z, self.size.y),
Quat::from_rotation_y(FRAC_PI_2),
),
(
Vec3::Y,
Vec2::new(self.size.x, self.size.z),
Quat::from_rotation_x(FRAC_PI_2),
),
(Vec3::Z, Vec2::new(self.size.x, self.size.y), Quat::IDENTITY),
];
for (position, size, rotation) in rects {
let local_position = position * inner_half_size;
self.gizmos
.rounded_rect(
config.isometry * Isometry3d::new(local_position, rotation),
size,
config.color,
)
.arc_resolution(config.arc_resolution)
.corner_radius(edge_radius);
self.gizmos
.rounded_rect(
config.isometry * Isometry3d::new(-local_position, rotation),
size,
config.color,
)
.arc_resolution(config.arc_resolution)
.corner_radius(edge_radius);
}
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw a wireframe rectangle with rounded corners in 3D.
///
/// This should be called for each frame the rectangle needs to be rendered.
///
/// # Arguments
///
/// - `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
///
/// # Builder methods
///
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rounded_rect(
/// Isometry3d::IDENTITY,
/// Vec2::ONE,
/// GREEN
/// )
/// .corner_radius(0.25)
/// .arc_resolution(10);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn rounded_rect(
&mut self,
isometry: impl Into<Isometry3d>,
size: Vec2,
color: impl Into<Color>,
) -> RoundedRectBuilder<'_, Config, Clear> {
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
RoundedRectBuilder {
gizmos: self,
config: RoundedBoxConfig {
isometry: isometry.into(),
color: color.into(),
corner_radius,
arc_resolution: DEFAULT_ARC_RESOLUTION,
},
size,
}
}
/// Draw a wireframe rectangle with rounded corners in 2D.
///
/// This should be called for each frame the rectangle needs to be rendered.
///
/// # Arguments
///
/// - `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
///
/// # Builder methods
///
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rounded_rect_2d(
/// Isometry2d::IDENTITY,
/// Vec2::ONE,
/// GREEN
/// )
/// .corner_radius(0.25)
/// .arc_resolution(10);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn rounded_rect_2d(
&mut self,
isometry: impl Into<Isometry2d>,
size: Vec2,
color: impl Into<Color>,
) -> RoundedRectBuilder<'_, Config, Clear> {
let isometry = isometry.into();
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
RoundedRectBuilder {
gizmos: self,
config: RoundedBoxConfig {
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,
},
size,
}
}
/// Draw a wireframe cuboid with rounded corners in 3D.
///
/// This should be called for each frame the cuboid needs to be rendered.
///
/// # Arguments
///
/// - `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
///
/// # Builder methods
///
/// - The edge radius can be adjusted with the `.edge_radius(...)` method.
/// - The resolution of the arcs at each edge (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::css::GREEN;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.rounded_cuboid(
/// Isometry3d::IDENTITY,
/// Vec3::ONE,
/// GREEN
/// )
/// .edge_radius(0.25)
/// .arc_resolution(10);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn rounded_cuboid(
&mut self,
isometry: impl Into<Isometry3d>,
size: Vec3,
color: impl Into<Color>,
) -> RoundedCuboidBuilder<'_, Config, Clear> {
let corner_radius = size.min_element() * DEFAULT_CORNER_RADIUS;
RoundedCuboidBuilder {
gizmos: self,
config: RoundedBoxConfig {
isometry: isometry.into(),
color: color.into(),
corner_radius,
arc_resolution: DEFAULT_ARC_RESOLUTION,
},
size,
}
}
}
const DEFAULT_ARC_RESOLUTION: u32 = 8;
const DEFAULT_CORNER_RADIUS: f32 = 0.1;