use core::{
iter,
marker::PhantomData,
mem,
ops::{Deref, DerefMut},
};
use bevy_color::{Color, LinearRgba};
use bevy_ecs::{
change_detection::Tick,
resource::Resource,
system::{
Deferred, ReadOnlySystemParam, Res, SystemAccess, SystemBuffer, SystemMeta, SystemParam,
SystemParamValidationError,
},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
};
use bevy_math::{Isometry2d, Isometry3d, Vec2, Vec3};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_shape::Aabb3d;
use bevy_transform::TransformPoint;
use bevy_utils::default;
use crate::{
config::{DefaultGizmoConfigGroup, GizmoConfigGroup, GizmoConfigStore},
prelude::GizmoConfig,
};
#[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,
{
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);
}
pub fn clear(&mut self) {
self.list_positions.clear();
self.list_colors.clear();
self.strip_positions.clear();
self.strip_colors.clear();
}
}
pub struct Swap<Clear>(PhantomData<Clear>);
pub struct Gizmos<'w, 's, Config = DefaultGizmoConfigGroup, Clear = ()>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
buffer: Deferred<'s, GizmoBuffer<Config, Clear>>,
pub config: &'w GizmoConfig,
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."
)]
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) -> Self::State {
GizmosFetchState {
state: GizmosState::<Config, Clear>::init_state(world),
}
}
fn init_access(
state: &Self::State,
system_meta: &mut SystemMeta,
system_access: &mut SystemAccess,
world: &mut World,
) {
GizmosState::<Config, Clear>::init_access(&state.state, system_meta, system_access, world);
}
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
GizmosState::<Config, Clear>::apply(&mut state.state, system_meta, world);
}
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
GizmosState::<Config, Clear>::queue(&mut 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,
) -> Result<Self::Item<'w, 's>, SystemParamValidationError> {
let (mut f0, f1) = unsafe {
GizmosState::<Config, Clear>::get_param(
&mut state.state,
system_meta,
world,
change_tick,
)?
};
let (config, config_ext) = f1.into_inner().get_config::<Config>().ok_or_else(|| {
SystemParamValidationError::invalid::<Self>(
format!("Requested config {} does not exist in `GizmoConfigStore`! Did you forget to add it using `app.init_gizmo_group<T>()`?",
Config::type_path()))
})?;
f0.enabled = config.enabled;
Ok(Gizmos {
buffer: f0,
config,
config_ext,
})
}
}
#[expect(
unsafe_code,
reason = "We cannot implement ReadOnlySystemParam without using unsafe code."
)]
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,
{
}
#[derive(Debug, Clone, Reflect)]
#[reflect(Default)]
pub struct GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub(crate) enabled: bool,
pub list_positions: Vec<Vec3>,
pub list_colors: Vec<LinearRgba>,
pub strip_positions: Vec<Vec3>,
pub 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::new()
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub const fn new() -> Self {
GizmoBuffer {
enabled: true,
list_positions: Vec::new(),
list_colors: Vec::new(),
strip_positions: Vec::new(),
strip_colors: Vec::new(),
marker: PhantomData,
}
}
}
pub struct GizmoBufferView<'a> {
pub list_positions: &'a Vec<Vec3>,
pub list_colors: &'a Vec<LinearRgba>,
pub strip_positions: &'a Vec<Vec3>,
pub strip_colors: &'a Vec<LinearRgba>,
}
impl<Config, Clear> SystemBuffer for GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) {
if let Some(mut storage) = world.get_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);
} else {
self.list_positions.clear();
self.list_colors.clear();
self.strip_positions.clear();
self.strip_colors.clear();
}
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub fn clear(&mut self) {
self.list_positions.clear();
self.list_colors.clear();
self.strip_positions.clear();
self.strip_colors.clear();
}
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,
}
}
#[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);
}
#[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]);
}
#[inline]
pub fn ray(&mut self, start: Vec3, vector: Vec3, color: impl Into<Color>) {
if !self.enabled {
return;
}
self.line(start, start + vector, color);
}
#[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);
}
#[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);
}
#[inline]
pub fn lineloop(&mut self, positions: impl IntoIterator<Item = Vec3>, color: impl Into<Color>) {
if !self.enabled {
return;
}
let mut positions = positions.into_iter();
let first = positions.next();
let second = positions.next();
self.linestrip(
first
.into_iter()
.chain(second)
.chain(positions)
.chain(first)
.chain(second),
color,
);
}
#[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);
}
#[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.lineloop([tl, tr, br, bl], color);
}
#[inline]
pub fn cube(&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);
let [tlf, trf, brf, blf] = rect.map(|vec2| transform.transform_point(vec2.extend(0.5)));
let [tlb, trb, brb, blb] = rect.map(|vec2| transform.transform_point(vec2.extend(-0.5)));
let strip_positions = [
tlf, trf, brf, blf, tlf, tlb, trb, brb, blb, tlb, ];
self.linestrip(strip_positions, polymorphic_color);
let list_positions = [
trf, trb, brf, brb, blf, blb, ];
self.extend_list_positions(list_positions);
self.add_list_color(polymorphic_color, 6);
}
#[inline]
pub fn aabb_3d(
&mut self,
aabb: impl Into<Aabb3d>,
transform: impl TransformPoint,
color: impl Into<Color>,
) {
let polymorphic_color: Color = color.into();
if !self.enabled {
return;
}
let aabb = aabb.into();
let [tlf, trf, brf, blf, tlb, trb, brb, blb] = [
Vec3::new(aabb.min.x, aabb.max.y, aabb.max.z),
Vec3::new(aabb.max.x, aabb.max.y, aabb.max.z),
Vec3::new(aabb.max.x, aabb.min.y, aabb.max.z),
Vec3::new(aabb.min.x, aabb.min.y, aabb.max.z),
Vec3::new(aabb.min.x, aabb.max.y, aabb.min.z),
Vec3::new(aabb.max.x, aabb.max.y, aabb.min.z),
Vec3::new(aabb.max.x, aabb.min.y, aabb.min.z),
Vec3::new(aabb.min.x, aabb.min.y, aabb.min.z),
]
.map(|v| transform.transform_point(v));
let strip_positions = [
tlf, trf, brf, blf, tlf, tlb, trb, brb, blb, tlb, ];
self.linestrip(strip_positions, polymorphic_color);
let list_positions = [
trf, trb, brf, brb, blf, blb, ];
self.extend_list_positions(list_positions);
self.add_list_color(polymorphic_color, 6);
}
#[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);
}
#[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);
}
#[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);
}
#[inline]
pub fn lineloop_2d(
&mut self,
positions: impl IntoIterator<Item = Vec2>,
color: impl Into<Color>,
) {
if !self.enabled {
return;
}
self.lineloop(positions.into_iter().map(|vec2| vec2.extend(0.)), color);
}
#[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)),
);
}
#[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);
}
#[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);
}
#[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.lineloop_2d([tl, tr, br, bl], 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]
}