Path: blob/main/crates/bevy_gizmos/src/primitives/dim3.rs
6596 views
//! A module for rendering each of the 3D [`bevy_math::primitives`] with [`GizmoBuffer`].12use super::helpers::*;34use bevy_color::Color;5use bevy_math::{6primitives::{7Capsule3d, Cone, ConicalFrustum, Cuboid, Cylinder, Line3d, Plane3d, Polyline3d,8Primitive3d, Segment3d, Sphere, Tetrahedron, Torus, Triangle3d,9},10Dir3, Isometry3d, Quat, UVec2, Vec2, Vec3,11};1213use crate::{circles::SphereBuilder, gizmos::GizmoBuffer, prelude::GizmoConfigGroup};1415const DEFAULT_RESOLUTION: u32 = 5;16// length used to simulate infinite lines17const INFINITE_LEN: f32 = 10_000.0;1819/// A trait for rendering 3D geometric primitives (`P`) with [`GizmoBuffer`].20pub trait GizmoPrimitive3d<P: Primitive3d> {21/// The output of `primitive_3d`. This is a builder to set non-default values.22type Output<'a>23where24Self: 'a;2526/// Renders a 3D primitive with its associated details.27fn primitive_3d(28&mut self,29primitive: &P,30isometry: impl Into<Isometry3d>,31color: impl Into<Color>,32) -> Self::Output<'_>;33}3435// direction 3d3637impl<Config, Clear> GizmoPrimitive3d<Dir3> for GizmoBuffer<Config, Clear>38where39Config: GizmoConfigGroup,40Clear: 'static + Send + Sync,41{42type Output<'a>43= ()44where45Self: 'a;4647fn primitive_3d(48&mut self,49primitive: &Dir3,50isometry: impl Into<Isometry3d>,51color: impl Into<Color>,52) -> Self::Output<'_> {53let isometry = isometry.into();54let start = Vec3::ZERO;55let end = primitive.as_vec3();56self.arrow(isometry * start, isometry * end, color);57}58}5960// sphere6162impl<Config, Clear> GizmoPrimitive3d<Sphere> for GizmoBuffer<Config, Clear>63where64Config: GizmoConfigGroup,65Clear: 'static + Send + Sync,66{67type Output<'a>68= SphereBuilder<'a, Config, Clear>69where70Self: 'a;7172fn primitive_3d(73&mut self,74primitive: &Sphere,75isometry: impl Into<Isometry3d>,76color: impl Into<Color>,77) -> Self::Output<'_> {78self.sphere(isometry, primitive.radius, color)79}80}8182// plane 3d8384/// Builder for configuring the drawing options of [`Plane3d`].85pub struct Plane3dBuilder<'a, Config, Clear>86where87Config: GizmoConfigGroup,88Clear: 'static + Send + Sync,89{90gizmos: &'a mut GizmoBuffer<Config, Clear>,9192// Direction of the normal orthogonal to the plane93normal: Dir3,9495isometry: Isometry3d,96// Color of the plane97color: Color,9899// Defines the amount of cells in the x and y axes100cell_count: UVec2,101// Defines the distance between cells along the x and y axes102spacing: Vec2,103}104105impl<Config, Clear> Plane3dBuilder<'_, Config, Clear>106where107Config: GizmoConfigGroup,108Clear: 'static + Send + Sync,109{110/// Set the number of cells in the x and y axes direction.111pub fn cell_count(mut self, cell_count: UVec2) -> Self {112self.cell_count = cell_count;113self114}115116/// Set the distance between cells along the x and y axes.117pub fn spacing(mut self, spacing: Vec2) -> Self {118self.spacing = spacing;119self120}121}122123impl<Config, Clear> GizmoPrimitive3d<Plane3d> for GizmoBuffer<Config, Clear>124where125Config: GizmoConfigGroup,126Clear: 'static + Send + Sync,127{128type Output<'a>129= Plane3dBuilder<'a, Config, Clear>130where131Self: 'a;132133fn primitive_3d(134&mut self,135primitive: &Plane3d,136isometry: impl Into<Isometry3d>,137color: impl Into<Color>,138) -> Self::Output<'_> {139Plane3dBuilder {140gizmos: self,141normal: primitive.normal,142isometry: isometry.into(),143color: color.into(),144cell_count: UVec2::splat(3),145spacing: Vec2::splat(1.0),146}147}148}149150impl<Config, Clear> Drop for Plane3dBuilder<'_, Config, Clear>151where152Config: GizmoConfigGroup,153Clear: 'static + Send + Sync,154{155fn drop(&mut self) {156if !self.gizmos.enabled {157return;158}159160self.gizmos161.primitive_3d(&self.normal, self.isometry, self.color);162// the default orientation of the grid is Z-up163let rot = Quat::from_rotation_arc(Vec3::Z, self.normal.as_vec3());164self.gizmos.grid(165Isometry3d::new(self.isometry.translation, self.isometry.rotation * rot),166self.cell_count,167self.spacing,168self.color,169);170}171}172173// line 3d174175impl<Config, Clear> GizmoPrimitive3d<Line3d> for GizmoBuffer<Config, Clear>176where177Config: GizmoConfigGroup,178Clear: 'static + Send + Sync,179{180type Output<'a>181= ()182where183Self: 'a;184185fn primitive_3d(186&mut self,187primitive: &Line3d,188isometry: impl Into<Isometry3d>,189color: impl Into<Color>,190) -> Self::Output<'_> {191if !self.enabled {192return;193}194195let isometry = isometry.into();196let color = color.into();197let direction = primitive.direction.as_vec3();198self.arrow(isometry * Vec3::ZERO, isometry * direction, color);199200let [start, end] = [1.0, -1.0]201.map(|sign| sign * INFINITE_LEN)202.map(|length| primitive.direction * length)203.map(|offset| isometry * offset);204self.line(start, end, color);205}206}207208// segment 3d209210impl<Config, Clear> GizmoPrimitive3d<Segment3d> for GizmoBuffer<Config, Clear>211where212Config: GizmoConfigGroup,213Clear: 'static + Send + Sync,214{215type Output<'a>216= ()217where218Self: 'a;219220fn primitive_3d(221&mut self,222primitive: &Segment3d,223isometry: impl Into<Isometry3d>,224color: impl Into<Color>,225) -> Self::Output<'_> {226if !self.enabled {227return;228}229230let transformed = primitive.transformed(isometry);231self.line(transformed.point1(), transformed.point2(), color);232}233}234235// polyline 3d236237impl<Config, Clear> GizmoPrimitive3d<Polyline3d> for GizmoBuffer<Config, Clear>238where239Config: GizmoConfigGroup,240Clear: 'static + Send + Sync,241{242type Output<'a>243= ()244where245Self: 'a;246247fn primitive_3d(248&mut self,249primitive: &Polyline3d,250isometry: impl Into<Isometry3d>,251color: impl Into<Color>,252) -> Self::Output<'_> {253if !self.enabled {254return;255}256257let isometry = isometry.into();258self.linestrip(259primitive.vertices.iter().map(|vec3| isometry * *vec3),260color,261);262}263}264265// triangle 3d266267impl<Config, Clear> GizmoPrimitive3d<Triangle3d> for GizmoBuffer<Config, Clear>268where269Config: GizmoConfigGroup,270Clear: 'static + Send + Sync,271{272type Output<'a>273= ()274where275Self: 'a;276277fn primitive_3d(278&mut self,279primitive: &Triangle3d,280isometry: impl Into<Isometry3d>,281color: impl Into<Color>,282) -> Self::Output<'_> {283if !self.enabled {284return;285}286287let isometry = isometry.into();288let [a, b, c] = primitive.vertices;289self.linestrip([a, b, c, a].map(|vec3| isometry * vec3), color);290}291}292293// cuboid294295impl<Config, Clear> GizmoPrimitive3d<Cuboid> for GizmoBuffer<Config, Clear>296where297Config: GizmoConfigGroup,298Clear: 'static + Send + Sync,299{300type Output<'a>301= ()302where303Self: 'a;304305fn primitive_3d(306&mut self,307primitive: &Cuboid,308isometry: impl Into<Isometry3d>,309color: impl Into<Color>,310) -> Self::Output<'_> {311if !self.enabled {312return;313}314315let isometry = isometry.into();316317// transform the points from the reference unit cube to the cuboid coords318let vertices @ [a, b, c, d, e, f, g, h] = [319[1.0, 1.0, 1.0],320[-1.0, 1.0, 1.0],321[-1.0, -1.0, 1.0],322[1.0, -1.0, 1.0],323[1.0, 1.0, -1.0],324[-1.0, 1.0, -1.0],325[-1.0, -1.0, -1.0],326[1.0, -1.0, -1.0],327]328.map(Vec3::from)329.map(|vec3| vec3 * primitive.half_size)330.map(|vec3| isometry * vec3);331332// lines for the upper rectangle of the cuboid333let upper = [a, b, c, d]334.into_iter()335.zip([a, b, c, d].into_iter().cycle().skip(1));336337// lines for the lower rectangle of the cuboid338let lower = [e, f, g, h]339.into_iter()340.zip([e, f, g, h].into_iter().cycle().skip(1));341342// lines connecting upper and lower rectangles of the cuboid343let connections = vertices.into_iter().zip(vertices.into_iter().skip(4));344345let color = color.into();346upper347.chain(lower)348.chain(connections)349.for_each(|(start, end)| {350self.line(start, end, color);351});352}353}354355// cylinder 3d356357/// Builder for configuring the drawing options of [`Cylinder`].358pub struct Cylinder3dBuilder<'a, Config, Clear>359where360Config: GizmoConfigGroup,361Clear: 'static + Send + Sync,362{363gizmos: &'a mut GizmoBuffer<Config, Clear>,364365// Radius of the cylinder366radius: f32,367// Half height of the cylinder368half_height: f32,369370isometry: Isometry3d,371// Color of the cylinder372color: Color,373374// Number of lines used to approximate the cylinder geometry375resolution: u32,376}377378impl<Config, Clear> Cylinder3dBuilder<'_, Config, Clear>379where380Config: GizmoConfigGroup,381Clear: 'static + Send + Sync,382{383/// Set the number of lines used to approximate the top and bottom of the cylinder geometry.384pub fn resolution(mut self, resolution: u32) -> Self {385self.resolution = resolution;386self387}388}389390impl<Config, Clear> GizmoPrimitive3d<Cylinder> for GizmoBuffer<Config, Clear>391where392Config: GizmoConfigGroup,393Clear: 'static + Send + Sync,394{395type Output<'a>396= Cylinder3dBuilder<'a, Config, Clear>397where398Self: 'a;399400fn primitive_3d(401&mut self,402primitive: &Cylinder,403isometry: impl Into<Isometry3d>,404color: impl Into<Color>,405) -> Self::Output<'_> {406Cylinder3dBuilder {407gizmos: self,408radius: primitive.radius,409half_height: primitive.half_height,410isometry: isometry.into(),411color: color.into(),412resolution: DEFAULT_RESOLUTION,413}414}415}416417impl<Config, Clear> Drop for Cylinder3dBuilder<'_, Config, Clear>418where419Config: GizmoConfigGroup,420Clear: 'static + Send + Sync,421{422fn drop(&mut self) {423if !self.gizmos.enabled {424return;425}426427self.gizmos428.primitive_3d(429&ConicalFrustum {430radius_top: self.radius,431radius_bottom: self.radius,432height: self.half_height * 2.0,433},434self.isometry,435self.color,436)437.resolution(self.resolution);438}439}440441// capsule 3d442443/// Builder for configuring the drawing options of [`Capsule3d`].444pub struct Capsule3dBuilder<'a, Config, Clear>445where446Config: GizmoConfigGroup,447Clear: 'static + Send + Sync,448{449gizmos: &'a mut GizmoBuffer<Config, Clear>,450451// Radius of the capsule452radius: f32,453// Half length of the capsule454half_length: f32,455456isometry: Isometry3d,457// Color of the capsule458color: Color,459460// Number of lines used to approximate the capsule geometry461resolution: u32,462}463464impl<Config, Clear> Capsule3dBuilder<'_, Config, Clear>465where466Config: GizmoConfigGroup,467Clear: 'static + Send + Sync,468{469/// Set the number of lines used to approximate the capsule geometry.470pub fn resolution(mut self, resolution: u32) -> Self {471self.resolution = resolution;472self473}474}475476impl<Config, Clear> GizmoPrimitive3d<Capsule3d> for GizmoBuffer<Config, Clear>477where478Config: GizmoConfigGroup,479Clear: 'static + Send + Sync,480{481type Output<'a>482= Capsule3dBuilder<'a, Config, Clear>483where484Self: 'a;485486fn primitive_3d(487&mut self,488primitive: &Capsule3d,489isometry: impl Into<Isometry3d>,490color: impl Into<Color>,491) -> Self::Output<'_> {492Capsule3dBuilder {493gizmos: self,494radius: primitive.radius,495half_length: primitive.half_length,496isometry: isometry.into(),497color: color.into(),498resolution: DEFAULT_RESOLUTION,499}500}501}502503impl<Config, Clear> Drop for Capsule3dBuilder<'_, Config, Clear>504where505Config: GizmoConfigGroup,506Clear: 'static + Send + Sync,507{508fn drop(&mut self) {509if !self.gizmos.enabled {510return;511}512513let [upper_apex, lower_apex] = [-1.0, 1.0]514.map(|sign| Vec3::Y * sign * (self.half_length + self.radius))515.map(|vec3| self.isometry * vec3);516let [upper_center, lower_center] = [-1.0, 1.0]517.map(|sign| Vec3::Y * sign * self.half_length)518.map(|vec3| self.isometry * vec3);519let [upper_points, lower_points] = [-1.0, 1.0]520.map(|sign| Vec3::Y * sign * self.half_length)521.map(|vec3| {522circle_coordinates_closed(self.radius, self.resolution)523.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + vec3)524.map(|vec3| self.isometry * vec3)525.collect::<Vec<_>>()526});527528upper_points.iter().skip(1).copied().for_each(|start| {529self.gizmos530.short_arc_3d_between(upper_center, start, upper_apex, self.color);531});532lower_points.iter().skip(1).copied().for_each(|start| {533self.gizmos534.short_arc_3d_between(lower_center, start, lower_apex, self.color);535});536537let circle_rotation = self538.isometry539.rotation540.mul_quat(Quat::from_rotation_x(core::f32::consts::FRAC_PI_2));541self.gizmos.circle(542Isometry3d::new(upper_center, circle_rotation),543self.radius,544self.color,545);546self.gizmos.circle(547Isometry3d::new(lower_center, circle_rotation),548self.radius,549self.color,550);551552let connection_lines = upper_points.into_iter().zip(lower_points).skip(1);553connection_lines.for_each(|(start, end)| {554self.gizmos.line(start, end, self.color);555});556}557}558559// cone 3d560561/// Builder for configuring the drawing options of [`Cone`].562pub struct Cone3dBuilder<'a, Config, Clear>563where564Config: GizmoConfigGroup,565Clear: 'static + Send + Sync,566{567gizmos: &'a mut GizmoBuffer<Config, Clear>,568569// Radius of the cone570radius: f32,571// Height of the cone572height: f32,573574isometry: Isometry3d,575// Color of the cone576color: Color,577578// Number of lines used to approximate the cone base geometry579base_resolution: u32,580581// Number of lines used to approximate the cone height geometry582height_resolution: u32,583}584585impl<Config, Clear> Cone3dBuilder<'_, Config, Clear>586where587Config: GizmoConfigGroup,588Clear: 'static + Send + Sync,589{590/// Set the number of lines used to approximate the cone geometry for its base and height.591pub fn resolution(mut self, resolution: u32) -> Self {592self.base_resolution = resolution;593self.height_resolution = resolution;594self595}596597/// Set the number of lines used to approximate the height of the cone geometry.598///599/// `resolution` should be a multiple of the value passed to [`Self::height_resolution`]600/// for the height to connect properly with the base.601pub fn base_resolution(mut self, resolution: u32) -> Self {602self.base_resolution = resolution;603self604}605606/// Set the number of lines used to approximate the height of the cone geometry.607///608/// `resolution` should be a divisor of the value passed to [`Self::base_resolution`]609/// for the height to connect properly with the base.610pub fn height_resolution(mut self, resolution: u32) -> Self {611self.height_resolution = resolution;612self613}614}615616impl<Config, Clear> GizmoPrimitive3d<Cone> for GizmoBuffer<Config, Clear>617where618Config: GizmoConfigGroup,619Clear: 'static + Send + Sync,620{621type Output<'a>622= Cone3dBuilder<'a, Config, Clear>623where624Self: 'a;625626fn primitive_3d(627&mut self,628primitive: &Cone,629isometry: impl Into<Isometry3d>,630color: impl Into<Color>,631) -> Self::Output<'_> {632Cone3dBuilder {633gizmos: self,634radius: primitive.radius,635height: primitive.height,636isometry: isometry.into(),637color: color.into(),638base_resolution: DEFAULT_RESOLUTION,639height_resolution: DEFAULT_RESOLUTION,640}641}642}643644impl<Config, Clear> Drop for Cone3dBuilder<'_, Config, Clear>645where646Config: GizmoConfigGroup,647Clear: 'static + Send + Sync,648{649fn drop(&mut self) {650if !self.gizmos.enabled {651return;652}653654let half_height = self.height * 0.5;655let apex = self.isometry * (Vec3::Y * half_height);656let circle_center = half_height * Vec3::NEG_Y;657let circle_coords = circle_coordinates_closed(self.radius, self.height_resolution)658.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + circle_center)659.map(|vec3| self.isometry * vec3)660.collect::<Vec<_>>();661662// connections to apex663circle_coords664.iter()665.skip(1)666.map(|vec3| (*vec3, apex))667.for_each(|(start, end)| {668self.gizmos.line(start, end, self.color);669});670671// base circle672circle_coords673.windows(2)674.map(|win| (win[0], win[1]))675.for_each(|(start, end)| {676self.gizmos.line(start, end, self.color);677});678}679}680681// conical frustum 3d682683/// Builder for configuring the drawing options of [`ConicalFrustum`].684pub struct ConicalFrustum3dBuilder<'a, Config, Clear>685where686Config: GizmoConfigGroup,687Clear: 'static + Send + Sync,688{689gizmos: &'a mut GizmoBuffer<Config, Clear>,690691// Radius of the top circle692radius_top: f32,693// Radius of the bottom circle694radius_bottom: f32,695// Height of the conical frustum696height: f32,697698isometry: Isometry3d,699// Color of the conical frustum700color: Color,701702// Number of lines used to approximate the curved surfaces703resolution: u32,704}705706impl<Config, Clear> ConicalFrustum3dBuilder<'_, Config, Clear>707where708Config: GizmoConfigGroup,709Clear: 'static + Send + Sync,710{711/// Set the number of lines used to approximate the curved surfaces.712pub fn resolution(mut self, resolution: u32) -> Self {713self.resolution = resolution;714self715}716}717718impl<Config, Clear> GizmoPrimitive3d<ConicalFrustum> for GizmoBuffer<Config, Clear>719where720Config: GizmoConfigGroup,721Clear: 'static + Send + Sync,722{723type Output<'a>724= ConicalFrustum3dBuilder<'a, Config, Clear>725where726Self: 'a;727728fn primitive_3d(729&mut self,730primitive: &ConicalFrustum,731isometry: impl Into<Isometry3d>,732color: impl Into<Color>,733) -> Self::Output<'_> {734ConicalFrustum3dBuilder {735gizmos: self,736radius_top: primitive.radius_top,737radius_bottom: primitive.radius_bottom,738height: primitive.height,739isometry: isometry.into(),740color: color.into(),741resolution: DEFAULT_RESOLUTION,742}743}744}745746impl<Config, Clear> Drop for ConicalFrustum3dBuilder<'_, Config, Clear>747where748Config: GizmoConfigGroup,749Clear: 'static + Send + Sync,750{751fn drop(&mut self) {752if !self.gizmos.enabled {753return;754}755756let half_height = self.height * 0.5;757let [upper_points, lower_points] = [(-1.0, self.radius_bottom), (1.0, self.radius_top)]758.map(|(sign, radius)| {759let translation = Vec3::Y * sign * half_height;760circle_coordinates_closed(radius, self.resolution)761.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)762.map(|vec3| self.isometry * vec3)763.collect::<Vec<_>>()764});765766let upper_lines = upper_points.windows(2).map(|win| (win[0], win[1]));767let lower_lines = lower_points.windows(2).map(|win| (win[0], win[1]));768upper_lines.chain(lower_lines).for_each(|(start, end)| {769self.gizmos.line(start, end, self.color);770});771772let connection_lines = upper_points.into_iter().zip(lower_points).skip(1);773connection_lines.for_each(|(start, end)| {774self.gizmos.line(start, end, self.color);775});776}777}778779// torus 3d780781/// Builder for configuring the drawing options of [`Torus`].782pub struct Torus3dBuilder<'a, Config, Clear>783where784Config: GizmoConfigGroup,785Clear: 'static + Send + Sync,786{787gizmos: &'a mut GizmoBuffer<Config, Clear>,788789// Radius of the minor circle (tube)790minor_radius: f32,791// Radius of the major circle (ring)792major_radius: f32,793794isometry: Isometry3d,795// Color of the torus796color: Color,797798// Number of lines in the minor (tube) direction799minor_resolution: u32,800// Number of lines in the major (ring) direction801major_resolution: u32,802}803804impl<Config, Clear> Torus3dBuilder<'_, Config, Clear>805where806Config: GizmoConfigGroup,807Clear: 'static + Send + Sync,808{809/// Set the number of lines in the minor (tube) direction.810pub fn minor_resolution(mut self, minor_resolution: u32) -> Self {811self.minor_resolution = minor_resolution;812self813}814815/// Set the number of lines in the major (ring) direction.816pub fn major_resolution(mut self, major_resolution: u32) -> Self {817self.major_resolution = major_resolution;818self819}820}821822impl<Config, Clear> GizmoPrimitive3d<Torus> for GizmoBuffer<Config, Clear>823where824Config: GizmoConfigGroup,825Clear: 'static + Send + Sync,826{827type Output<'a>828= Torus3dBuilder<'a, Config, Clear>829where830Self: 'a;831832fn primitive_3d(833&mut self,834primitive: &Torus,835isometry: impl Into<Isometry3d>,836color: impl Into<Color>,837) -> Self::Output<'_> {838Torus3dBuilder {839gizmos: self,840minor_radius: primitive.minor_radius,841major_radius: primitive.major_radius,842isometry: isometry.into(),843color: color.into(),844minor_resolution: DEFAULT_RESOLUTION,845major_resolution: DEFAULT_RESOLUTION,846}847}848}849850impl<Config, Clear> Drop for Torus3dBuilder<'_, Config, Clear>851where852Config: GizmoConfigGroup,853Clear: 'static + Send + Sync,854{855fn drop(&mut self) {856if !self.gizmos.enabled {857return;858}859860// draw 4 circles with major_radius861let [inner, outer, top, bottom] = [862(self.major_radius - self.minor_radius, 0.0),863(self.major_radius + self.minor_radius, 0.0),864(self.major_radius, self.minor_radius),865(self.major_radius, -self.minor_radius),866]867.map(|(radius, height)| {868let translation = height * Vec3::Y;869circle_coordinates_closed(radius, self.major_resolution)870.map(|vec2| Vec3::new(vec2.x, 0.0, vec2.y) + translation)871.map(|vec3| self.isometry * vec3)872.collect::<Vec<_>>()873});874875[&inner, &outer, &top, &bottom]876.iter()877.flat_map(|points| points.windows(2).map(|win| (win[0], win[1])))878.for_each(|(start, end)| {879self.gizmos.line(start, end, self.color);880});881882inner883.into_iter()884.zip(top)885.zip(outer)886.zip(bottom)887.flat_map(|(((inner, top), outer), bottom)| {888let center = (inner + top + outer + bottom) * 0.25;889[(inner, top), (top, outer), (outer, bottom), (bottom, inner)]890.map(|(start, end)| (start, end, center))891})892.for_each(|(from, to, center)| {893self.gizmos894.short_arc_3d_between(center, from, to, self.color)895.resolution(self.minor_resolution);896});897}898}899900// tetrahedron901902impl<Config, Clear> GizmoPrimitive3d<Tetrahedron> for GizmoBuffer<Config, Clear>903where904Config: GizmoConfigGroup,905Clear: 'static + Send + Sync,906{907type Output<'a>908= ()909where910Self: 'a;911912fn primitive_3d(913&mut self,914primitive: &Tetrahedron,915isometry: impl Into<Isometry3d>,916color: impl Into<Color>,917) -> Self::Output<'_> {918if !self.enabled {919return;920}921922let isometry = isometry.into();923924let [a, b, c, d] = primitive.vertices.map(|vec3| isometry * vec3);925926let lines = [(a, b), (a, c), (a, d), (b, c), (b, d), (c, d)];927928let color = color.into();929lines.into_iter().for_each(|(start, end)| {930self.line(start, end, color);931});932}933}934935936