//! 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, system::{Deferred, ReadOnlySystemParam, Res, Resource, SystemBuffer, SystemMeta, SystemParam}, world::{unsafe_world_cell::UnsafeWorldCell, World}, }; use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3}; use bevy_reflect::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 { pub(crate) list_positions: Vec, pub(crate) list_colors: Vec, pub(crate) strip_positions: Vec, pub(crate) strip_colors: Vec, marker: PhantomData<(Config, Clear)>, } impl Default for GizmoStorage { fn default() -> Self { Self { list_positions: default(), list_colors: default(), strip_positions: default(), strip_colors: default(), marker: PhantomData, } } } impl GizmoStorage where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { /// Combine the other gizmo storage with this one. pub fn append_storage( &mut self, other: &GizmoStorage, ) { 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( &mut self, other: &mut GizmoStorage, ) { 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(PhantomData); /// 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::>() /// // 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::) /// // If not running multiple times, put this with [`start_gizmo_context`]. /// .add_systems(StartOfRun, clear_gizmo_context::) /// // If not running multiple times, put this with [`end_gizmo_context`]. /// .add_systems(EndOfRun, collect_requested_gizmos::) /// .add_systems(EndOfMyContext, end_gizmo_context::) /// .add_systems( /// Last, /// propagate_gizmos::.before(UpdateGizmoMeshes), /// ); /// } /// } /// ``` pub struct Gizmos<'w, 's, Config = DefaultGizmoConfigGroup, Clear = ()> where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { buffer: Deferred<'s, GizmoBuffer>, /// 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; 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 = ( Deferred<'static, GizmoBuffer>, Res<'static, GizmoConfigStore>, ); #[doc(hidden)] pub struct GizmosFetchState where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { state: as SystemParam>::State, } #[allow(unsafe_code)] // SAFETY: All methods are delegated to existing `SystemParam` implementations unsafe impl SystemParam for Gizmos<'_, '_, Config, Clear> where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { type State = GizmosFetchState; type Item<'w, 's> = Gizmos<'w, 's, Config, Clear>; fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { GizmosFetchState { state: GizmosState::::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::::new_archetype(&mut state.state, archetype, system_meta); }; } fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { GizmosState::::apply(&mut state.state, system_meta, world); } #[inline] unsafe fn validate_param( state: &Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> bool { // SAFETY: Delegated to existing `SystemParam` implementations. unsafe { GizmosState::::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::::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::(); f0.enabled = config.enabled; Gizmos { buffer: f0, config, config_ext, } } } #[allow(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>: ReadOnlySystemParam, Res<'w, GizmoConfigStore>: ReadOnlySystemParam, { } /// Buffer for gizmo vertex data. #[derive(Debug, Clone, Reflect)] pub struct GizmoBuffer where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { pub(crate) enabled: bool, pub(crate) list_positions: Vec, pub(crate) list_colors: Vec, pub(crate) strip_positions: Vec, pub(crate) strip_colors: Vec, #[reflect(ignore)] pub(crate) marker: PhantomData<(Config, Clear)>, } impl Default for GizmoBuffer 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, /// Vertex colors for line-list topology. pub list_colors: &'a Vec, /// Vertex positions for line-strip topology. pub strip_positions: &'a Vec, /// Vertex colors for line-strip topology. pub strip_colors: &'a Vec, } impl SystemBuffer for GizmoBuffer where Config: GizmoConfigGroup, Clear: 'static + Send + Sync, { fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { let mut storage = world.resource_mut::>(); 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 GizmoBuffer 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) { 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>( &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) { 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>( &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, color: impl Into, ) { 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>( &mut self, points: impl IntoIterator, ) { 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, size: Vec2, color: impl Into) { 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) { 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) { 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>( &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, color: impl Into, ) { 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>( &mut self, positions: impl IntoIterator, ) { 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) { 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>( &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, size: Vec2, color: impl Into, ) { 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) { self.list_positions.extend(positions); } #[inline] fn extend_list_colors(&mut self, colors: impl IntoIterator>) { self.list_colors.extend( colors .into_iter() .map(|color| LinearRgba::from(color.into())), ); } #[inline] fn add_list_color(&mut self, color: impl Into, count: usize) { let polymorphic_color: Color = color.into(); let linear_color = LinearRgba::from(polymorphic_color); self.list_colors .extend(iter::repeat(linear_color).take(count)); } #[inline] fn extend_strip_positions(&mut self, positions: impl IntoIterator) { 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] }