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,
prelude::{GizmoConfigGroup, Gizmos},
};
const DEFAULT_RESOLUTION: u32 = 5;
const INFINITE_LEN: f32 = 10_000.0;
pub trait GizmoPrimitive3d<P: Primitive3d> {
type Output<'a>
where
Self: 'a;
fn primitive_3d(
&mut self,
primitive: &P,
isometry: impl Into<Isometry3d>,
color: impl Into<Color>,
) -> Self::Output<'_>;
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Dir3> for Gizmos<'w, 's, 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);
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Sphere> for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= SphereBuilder<'a, 'w, 's, 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)
}
}
pub struct Plane3dBuilder<'a, 'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
normal: Dir3,
isometry: Isometry3d,
color: Color,
cell_count: UVec2,
spacing: Vec2,
}
impl<Config, Clear> Plane3dBuilder<'_, '_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub fn cell_count(mut self, cell_count: UVec2) -> Self {
self.cell_count = cell_count;
self
}
pub fn spacing(mut self, spacing: Vec2) -> Self {
self.spacing = spacing;
self
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Plane3d> for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Plane3dBuilder<'a, 'w, 's, 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);
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,
);
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Line3d> for Gizmos<'w, 's, 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);
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Segment3d> for Gizmos<'w, 's, 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 isometry = isometry.into();
let direction = primitive.direction.as_vec3();
self.line(isometry * direction, isometry * (-direction), color);
}
}
impl<'w, 's, const N: usize, Config, Clear> GizmoPrimitive3d<Polyline3d<N>>
for Gizmos<'w, 's, 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);
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<BoxedPolyline3d> for Gizmos<'w, 's, 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,
);
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Triangle3d> for Gizmos<'w, 's, 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);
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Cuboid> for Gizmos<'w, 's, 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();
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);
let upper = [a, b, c, d]
.into_iter()
.zip([a, b, c, d].into_iter().cycle().skip(1));
let lower = [e, f, g, h]
.into_iter()
.zip([e, f, g, h].into_iter().cycle().skip(1));
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);
});
}
}
pub struct Cylinder3dBuilder<'a, 'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
radius: f32,
half_height: f32,
isometry: Isometry3d,
color: Color,
resolution: u32,
}
impl<Config, Clear> Cylinder3dBuilder<'_, '_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Cylinder> for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Cylinder3dBuilder<'a, 'w, 's, 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);
}
}
pub struct Capsule3dBuilder<'a, 'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
radius: f32,
half_length: f32,
isometry: Isometry3d,
color: Color,
resolution: u32,
}
impl<Config, Clear> Capsule3dBuilder<'_, '_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Capsule3d> for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Capsule3dBuilder<'a, 'w, 's, 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);
});
}
}
pub struct Cone3dBuilder<'a, 'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
radius: f32,
height: f32,
isometry: Isometry3d,
color: Color,
base_resolution: u32,
height_resolution: u32,
}
impl<Config, Clear> Cone3dBuilder<'_, '_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub fn resolution(mut self, resolution: u32) -> Self {
self.base_resolution = resolution;
self.height_resolution = resolution;
self
}
pub fn base_resolution(mut self, resolution: u32) -> Self {
self.base_resolution = resolution;
self
}
pub fn height_resolution(mut self, resolution: u32) -> Self {
self.height_resolution = resolution;
self
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Cone> for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Cone3dBuilder<'a, 'w, 's, 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<_>>();
circle_coords
.iter()
.skip(1)
.map(|vec3| (*vec3, apex))
.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
circle_coords
.windows(2)
.map(|win| (win[0], win[1]))
.for_each(|(start, end)| {
self.gizmos.line(start, end, self.color);
});
}
}
pub struct ConicalFrustum3dBuilder<'a, 'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
radius_top: f32,
radius_bottom: f32,
height: f32,
isometry: Isometry3d,
color: Color,
resolution: u32,
}
impl<Config, Clear> ConicalFrustum3dBuilder<'_, '_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub fn resolution(mut self, resolution: u32) -> Self {
self.resolution = resolution;
self
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<ConicalFrustum> for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= ConicalFrustum3dBuilder<'a, 'w, 's, 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);
});
}
}
pub struct Torus3dBuilder<'a, 'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
gizmos: &'a mut Gizmos<'w, 's, Config, Clear>,
minor_radius: f32,
major_radius: f32,
isometry: Isometry3d,
color: Color,
minor_resolution: u32,
major_resolution: u32,
}
impl<Config, Clear> Torus3dBuilder<'_, '_, '_, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
pub fn minor_resolution(mut self, minor_resolution: u32) -> Self {
self.minor_resolution = minor_resolution;
self
}
pub fn major_resolution(mut self, major_resolution: u32) -> Self {
self.major_resolution = major_resolution;
self
}
}
impl<'w, 's, Config, Clear> GizmoPrimitive3d<Torus> for Gizmos<'w, 's, Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
type Output<'a>
= Torus3dBuilder<'a, 'w, 's, 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;
}
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);
});
}
}
impl<'w, 's, T: GizmoConfigGroup> GizmoPrimitive3d<Tetrahedron> for Gizmos<'w, 's, T> {
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);
});
}
}