1#![allow(clippy::match_same_arms)]
4
5use bevy::{input::common_conditions::input_just_pressed, math::Isometry2d, prelude::*};
6
7const LEFT_RIGHT_OFFSET_2D: f32 = 200.0;
8const LEFT_RIGHT_OFFSET_3D: f32 = 2.0;
9
10fn main() {
11 let mut app = App::new();
12
13 app.add_plugins(DefaultPlugins)
14 .init_state::<PrimitiveSelected>()
15 .init_state::<CameraActive>();
16
17 app.add_systems(Startup, (setup_cameras, setup_lights, setup_ambient_light))
19 .add_systems(
20 Update,
21 (
22 update_active_cameras.run_if(state_changed::<CameraActive>),
23 switch_cameras.run_if(input_just_pressed(KeyCode::KeyC)),
24 ),
25 );
26
27 app.add_systems(PostStartup, setup_text);
31 app.add_systems(
32 Update,
33 (update_text.run_if(state_changed::<PrimitiveSelected>),),
34 );
35
36 app.add_systems(Startup, (spawn_primitive_2d, spawn_primitive_3d))
38 .add_systems(
39 Update,
40 (
41 switch_to_next_primitive.run_if(input_just_pressed(KeyCode::ArrowUp)),
42 switch_to_previous_primitive.run_if(input_just_pressed(KeyCode::ArrowDown)),
43 draw_gizmos_2d.run_if(in_mode(CameraActive::Dim2)),
44 draw_gizmos_3d.run_if(in_mode(CameraActive::Dim3)),
45 update_primitive_meshes
46 .run_if(state_changed::<PrimitiveSelected>.or(state_changed::<CameraActive>)),
47 rotate_primitive_2d_meshes,
48 rotate_primitive_3d_meshes,
49 ),
50 );
51
52 app.run();
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect)]
57enum CameraActive {
58 #[default]
59 Dim2,
61 Dim3,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States, Default, Reflect)]
67enum PrimitiveSelected {
68 #[default]
69 RectangleAndCuboid,
70 CircleAndSphere,
71 Ellipse,
72 Triangle,
73 Plane,
74 Line,
75 Segment,
76 Polyline,
77 Polygon,
78 RegularPolygon,
79 Capsule,
80 Cylinder,
81 Cone,
82 ConicalFrustum,
83 Torus,
84 Tetrahedron,
85 Arc,
86 CircularSector,
87 CircularSegment,
88}
89
90impl std::fmt::Display for PrimitiveSelected {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 let name = match self {
93 PrimitiveSelected::RectangleAndCuboid => String::from("Rectangle/Cuboid"),
94 PrimitiveSelected::CircleAndSphere => String::from("Circle/Sphere"),
95 other => format!("{other:?}"),
96 };
97 write!(f, "{name}")
98 }
99}
100
101impl PrimitiveSelected {
102 const ALL: [Self; 19] = [
103 Self::RectangleAndCuboid,
104 Self::CircleAndSphere,
105 Self::Ellipse,
106 Self::Triangle,
107 Self::Plane,
108 Self::Line,
109 Self::Segment,
110 Self::Polyline,
111 Self::Polygon,
112 Self::RegularPolygon,
113 Self::Capsule,
114 Self::Cylinder,
115 Self::Cone,
116 Self::ConicalFrustum,
117 Self::Torus,
118 Self::Tetrahedron,
119 Self::Arc,
120 Self::CircularSector,
121 Self::CircularSegment,
122 ];
123
124 fn next(self) -> Self {
125 Self::ALL
126 .into_iter()
127 .cycle()
128 .skip_while(|&x| x != self)
129 .nth(1)
130 .unwrap()
131 }
132
133 fn previous(self) -> Self {
134 Self::ALL
135 .into_iter()
136 .rev()
137 .cycle()
138 .skip_while(|&x| x != self)
139 .nth(1)
140 .unwrap()
141 }
142}
143
144const SMALL_2D: f32 = 50.0;
145const BIG_2D: f32 = 100.0;
146
147const SMALL_3D: f32 = 0.5;
148const BIG_3D: f32 = 1.0;
149
150const RECTANGLE: Rectangle = Rectangle {
152 half_size: Vec2::new(SMALL_2D, BIG_2D),
153};
154const CUBOID: Cuboid = Cuboid {
155 half_size: Vec3::new(BIG_3D, SMALL_3D, BIG_3D),
156};
157
158const CIRCLE: Circle = Circle { radius: BIG_2D };
159const SPHERE: Sphere = Sphere { radius: BIG_3D };
160
161const ELLIPSE: Ellipse = Ellipse {
162 half_size: Vec2::new(BIG_2D, SMALL_2D),
163};
164
165const TRIANGLE_2D: Triangle2d = Triangle2d {
166 vertices: [
167 Vec2::new(BIG_2D, 0.0),
168 Vec2::new(0.0, BIG_2D),
169 Vec2::new(-BIG_2D, 0.0),
170 ],
171};
172
173const TRIANGLE_3D: Triangle3d = Triangle3d {
174 vertices: [
175 Vec3::new(BIG_3D, 0.0, 0.0),
176 Vec3::new(0.0, BIG_3D, 0.0),
177 Vec3::new(-BIG_3D, 0.0, 0.0),
178 ],
179};
180
181const PLANE_2D: Plane2d = Plane2d { normal: Dir2::Y };
182const PLANE_3D: Plane3d = Plane3d {
183 normal: Dir3::Y,
184 half_size: Vec2::new(BIG_3D, BIG_3D),
185};
186
187const LINE2D: Line2d = Line2d { direction: Dir2::X };
188const LINE3D: Line3d = Line3d { direction: Dir3::X };
189
190const SEGMENT_2D: Segment2d = Segment2d {
191 direction: Dir2::X,
192 half_length: BIG_2D,
193};
194const SEGMENT_3D: Segment3d = Segment3d {
195 direction: Dir3::X,
196 half_length: BIG_3D,
197};
198
199const POLYLINE_2D: Polyline2d<4> = Polyline2d {
200 vertices: [
201 Vec2::new(-BIG_2D, -SMALL_2D),
202 Vec2::new(-SMALL_2D, SMALL_2D),
203 Vec2::new(SMALL_2D, -SMALL_2D),
204 Vec2::new(BIG_2D, SMALL_2D),
205 ],
206};
207const POLYLINE_3D: Polyline3d<4> = Polyline3d {
208 vertices: [
209 Vec3::new(-BIG_3D, -SMALL_3D, -SMALL_3D),
210 Vec3::new(SMALL_3D, SMALL_3D, 0.0),
211 Vec3::new(-SMALL_3D, -SMALL_3D, 0.0),
212 Vec3::new(BIG_3D, SMALL_3D, SMALL_3D),
213 ],
214};
215
216const POLYGON_2D: Polygon<5> = Polygon {
217 vertices: [
218 Vec2::new(-BIG_2D, -SMALL_2D),
219 Vec2::new(BIG_2D, -SMALL_2D),
220 Vec2::new(BIG_2D, SMALL_2D),
221 Vec2::new(0.0, 0.0),
222 Vec2::new(-BIG_2D, SMALL_2D),
223 ],
224};
225
226const REGULAR_POLYGON: RegularPolygon = RegularPolygon {
227 circumcircle: Circle { radius: BIG_2D },
228 sides: 5,
229};
230
231const CAPSULE_2D: Capsule2d = Capsule2d {
232 radius: SMALL_2D,
233 half_length: SMALL_2D,
234};
235const CAPSULE_3D: Capsule3d = Capsule3d {
236 radius: SMALL_3D,
237 half_length: SMALL_3D,
238};
239
240const CYLINDER: Cylinder = Cylinder {
241 radius: SMALL_3D,
242 half_height: SMALL_3D,
243};
244
245const CONE: Cone = Cone {
246 radius: BIG_3D,
247 height: BIG_3D,
248};
249
250const CONICAL_FRUSTUM: ConicalFrustum = ConicalFrustum {
251 radius_top: BIG_3D,
252 radius_bottom: SMALL_3D,
253 height: BIG_3D,
254};
255
256const ANNULUS: Annulus = Annulus {
257 inner_circle: Circle { radius: SMALL_2D },
258 outer_circle: Circle { radius: BIG_2D },
259};
260
261const TORUS: Torus = Torus {
262 minor_radius: SMALL_3D / 2.0,
263 major_radius: SMALL_3D * 1.5,
264};
265
266const TETRAHEDRON: Tetrahedron = Tetrahedron {
267 vertices: [
268 Vec3::new(-BIG_3D, 0.0, 0.0),
269 Vec3::new(BIG_3D, 0.0, 0.0),
270 Vec3::new(0.0, 0.0, -BIG_3D * 1.67),
271 Vec3::new(0.0, BIG_3D * 1.67, -BIG_3D * 0.5),
272 ],
273};
274
275const ARC: Arc2d = Arc2d {
276 radius: BIG_2D,
277 half_angle: std::f32::consts::FRAC_PI_4,
278};
279
280const CIRCULAR_SECTOR: CircularSector = CircularSector {
281 arc: Arc2d {
282 radius: BIG_2D,
283 half_angle: std::f32::consts::FRAC_PI_4,
284 },
285};
286
287const CIRCULAR_SEGMENT: CircularSegment = CircularSegment {
288 arc: Arc2d {
289 radius: BIG_2D,
290 half_angle: std::f32::consts::FRAC_PI_4,
291 },
292};
293
294fn setup_cameras(mut commands: Commands) {
295 let start_in_2d = true;
296 let make_camera = |is_active| Camera {
297 is_active,
298 ..Default::default()
299 };
300
301 commands.spawn((Camera2d, make_camera(start_in_2d)));
302
303 commands.spawn((
304 Camera3d::default(),
305 make_camera(!start_in_2d),
306 Transform::from_xyz(0.0, 10.0, 0.0).looking_at(Vec3::ZERO, Vec3::Z),
307 ));
308}
309
310fn setup_ambient_light(mut ambient_light: ResMut<AmbientLight>) {
311 ambient_light.brightness = 50.0;
312}
313
314fn setup_lights(mut commands: Commands) {
315 commands.spawn((
316 PointLight {
317 intensity: 5000.0,
318 ..default()
319 },
320 Transform::from_translation(Vec3::new(-LEFT_RIGHT_OFFSET_3D, 2.0, 0.0))
321 .looking_at(Vec3::new(-LEFT_RIGHT_OFFSET_3D, 0.0, 0.0), Vec3::Y),
322 ));
323}
324
325#[derive(Debug, Clone, Component, Default, Reflect)]
327pub struct HeaderText;
328
329#[derive(Debug, Clone, Component, Default, Reflect)]
331pub struct HeaderNode;
332
333fn update_active_cameras(
334 state: Res<State<CameraActive>>,
335 camera_2d: Single<(Entity, &mut Camera), With<Camera2d>>,
336 camera_3d: Single<(Entity, &mut Camera), (With<Camera3d>, Without<Camera2d>)>,
337 mut text: Query<&mut TargetCamera, With<HeaderNode>>,
338) {
339 let (entity_2d, mut cam_2d) = camera_2d.into_inner();
340 let (entity_3d, mut cam_3d) = camera_3d.into_inner();
341 let is_camera_2d_active = matches!(*state.get(), CameraActive::Dim2);
342
343 cam_2d.is_active = is_camera_2d_active;
344 cam_3d.is_active = !is_camera_2d_active;
345
346 let active_camera = if is_camera_2d_active {
347 entity_2d
348 } else {
349 entity_3d
350 };
351
352 text.iter_mut().for_each(|mut target_camera| {
353 *target_camera = TargetCamera(active_camera);
354 });
355}
356
357fn switch_cameras(current: Res<State<CameraActive>>, mut next: ResMut<NextState<CameraActive>>) {
358 let next_state = match current.get() {
359 CameraActive::Dim2 => CameraActive::Dim3,
360 CameraActive::Dim3 => CameraActive::Dim2,
361 };
362 next.set(next_state);
363}
364
365fn setup_text(mut commands: Commands, cameras: Query<(Entity, &Camera)>) {
366 let active_camera = cameras
367 .iter()
368 .find_map(|(entity, camera)| camera.is_active.then_some(entity))
369 .expect("run condition ensures existence");
370 commands
371 .spawn((
372 HeaderNode,
373 Node {
374 justify_self: JustifySelf::Center,
375 top: Val::Px(5.0),
376 ..Default::default()
377 },
378 TargetCamera(active_camera),
379 ))
380 .with_children(|p| {
381 p.spawn((
382 Text::default(),
383 HeaderText,
384 TextLayout::new_with_justify(JustifyText::Center),
385 ))
386 .with_children(|p| {
387 p.spawn(TextSpan::new("Primitive: "));
388 p.spawn(TextSpan(format!(
389 "{text}",
390 text = PrimitiveSelected::default()
391 )));
392 p.spawn(TextSpan::new("\n\n"));
393 p.spawn(TextSpan::new(
394 "Press 'C' to switch between 2D and 3D mode\n\
395 Press 'Up' or 'Down' to switch to the next/previous primitive",
396 ));
397 p.spawn(TextSpan::new("\n\n"));
398 p.spawn(TextSpan::new(
399 "(If nothing is displayed, there's no rendering support yet)",
400 ));
401 });
402 });
403}
404
405fn update_text(
406 primitive_state: Res<State<PrimitiveSelected>>,
407 header: Query<Entity, With<HeaderText>>,
408 mut writer: TextUiWriter,
409) {
410 let new_text = format!("{text}", text = primitive_state.get());
411 header.iter().for_each(|header_text| {
412 if let Some(mut text) = writer.get_text(header_text, 2) {
413 (*text).clone_from(&new_text);
414 };
415 });
416}
417
418fn switch_to_next_primitive(
419 current: Res<State<PrimitiveSelected>>,
420 mut next: ResMut<NextState<PrimitiveSelected>>,
421) {
422 let next_state = current.get().next();
423 next.set(next_state);
424}
425
426fn switch_to_previous_primitive(
427 current: Res<State<PrimitiveSelected>>,
428 mut next: ResMut<NextState<PrimitiveSelected>>,
429) {
430 let next_state = current.get().previous();
431 next.set(next_state);
432}
433
434fn in_mode(active: CameraActive) -> impl Fn(Res<State<CameraActive>>) -> bool {
435 move |state| *state.get() == active
436}
437
438fn draw_gizmos_2d(mut gizmos: Gizmos, state: Res<State<PrimitiveSelected>>, time: Res<Time>) {
439 const POSITION: Vec2 = Vec2::new(-LEFT_RIGHT_OFFSET_2D, 0.0);
440 let angle = time.elapsed_secs();
441 let isometry = Isometry2d::new(POSITION, Rot2::radians(angle));
442 let color = Color::WHITE;
443
444 match state.get() {
445 PrimitiveSelected::RectangleAndCuboid => {
446 gizmos.primitive_2d(&RECTANGLE, isometry, color);
447 }
448 PrimitiveSelected::CircleAndSphere => {
449 gizmos.primitive_2d(&CIRCLE, isometry, color);
450 }
451 PrimitiveSelected::Ellipse => drop(gizmos.primitive_2d(&ELLIPSE, isometry, color)),
452 PrimitiveSelected::Triangle => gizmos.primitive_2d(&TRIANGLE_2D, isometry, color),
453 PrimitiveSelected::Plane => gizmos.primitive_2d(&PLANE_2D, isometry, color),
454 PrimitiveSelected::Line => drop(gizmos.primitive_2d(&LINE2D, isometry, color)),
455 PrimitiveSelected::Segment => {
456 drop(gizmos.primitive_2d(&SEGMENT_2D, isometry, color));
457 }
458 PrimitiveSelected::Polyline => gizmos.primitive_2d(&POLYLINE_2D, isometry, color),
459 PrimitiveSelected::Polygon => gizmos.primitive_2d(&POLYGON_2D, isometry, color),
460 PrimitiveSelected::RegularPolygon => {
461 gizmos.primitive_2d(®ULAR_POLYGON, isometry, color);
462 }
463 PrimitiveSelected::Capsule => gizmos.primitive_2d(&CAPSULE_2D, isometry, color),
464 PrimitiveSelected::Cylinder => {}
465 PrimitiveSelected::Cone => {}
466 PrimitiveSelected::ConicalFrustum => {}
467 PrimitiveSelected::Torus => drop(gizmos.primitive_2d(&ANNULUS, isometry, color)),
468 PrimitiveSelected::Tetrahedron => {}
469 PrimitiveSelected::Arc => gizmos.primitive_2d(&ARC, isometry, color),
470 PrimitiveSelected::CircularSector => {
471 gizmos.primitive_2d(&CIRCULAR_SECTOR, isometry, color);
472 }
473 PrimitiveSelected::CircularSegment => {
474 gizmos.primitive_2d(&CIRCULAR_SEGMENT, isometry, color);
475 }
476 }
477}
478
479#[derive(Debug, Clone, Component, Default, Reflect)]
481pub struct PrimitiveData {
482 camera_mode: CameraActive,
483 primitive_state: PrimitiveSelected,
484}
485
486#[derive(Debug, Clone, Component, Default)]
488pub struct MeshDim2;
489
490#[derive(Debug, Clone, Component, Default)]
492pub struct MeshDim3;
493
494fn spawn_primitive_2d(
495 mut commands: Commands,
496 mut materials: ResMut<Assets<ColorMaterial>>,
497 mut meshes: ResMut<Assets<Mesh>>,
498) {
499 const POSITION: Vec3 = Vec3::new(LEFT_RIGHT_OFFSET_2D, 0.0, 0.0);
500 let material: Handle<ColorMaterial> = materials.add(Color::WHITE);
501 let camera_mode = CameraActive::Dim2;
502 [
503 Some(RECTANGLE.mesh().build()),
504 Some(CIRCLE.mesh().build()),
505 Some(ELLIPSE.mesh().build()),
506 Some(TRIANGLE_2D.mesh().build()),
507 None, None, None, None, None, Some(REGULAR_POLYGON.mesh().build()),
513 Some(CAPSULE_2D.mesh().build()),
514 None, None, None, Some(ANNULUS.mesh().build()),
518 None, ]
520 .into_iter()
521 .zip(PrimitiveSelected::ALL)
522 .for_each(|(maybe_mesh, state)| {
523 if let Some(mesh) = maybe_mesh {
524 commands.spawn((
525 MeshDim2,
526 PrimitiveData {
527 camera_mode,
528 primitive_state: state,
529 },
530 Mesh2d(meshes.add(mesh)),
531 MeshMaterial2d(material.clone()),
532 Transform::from_translation(POSITION),
533 ));
534 }
535 });
536}
537
538fn spawn_primitive_3d(
539 mut commands: Commands,
540 mut materials: ResMut<Assets<StandardMaterial>>,
541 mut meshes: ResMut<Assets<Mesh>>,
542) {
543 const POSITION: Vec3 = Vec3::new(-LEFT_RIGHT_OFFSET_3D, 0.0, 0.0);
544 let material: Handle<StandardMaterial> = materials.add(Color::WHITE);
545 let camera_mode = CameraActive::Dim3;
546 [
547 Some(CUBOID.mesh().build()),
548 Some(SPHERE.mesh().build()),
549 None, Some(TRIANGLE_3D.mesh().build()),
551 Some(PLANE_3D.mesh().build()),
552 None, None, None, None, None, Some(CAPSULE_3D.mesh().build()),
558 Some(CYLINDER.mesh().build()),
559 None, None, Some(TORUS.mesh().build()),
562 Some(TETRAHEDRON.mesh().build()),
563 ]
564 .into_iter()
565 .zip(PrimitiveSelected::ALL)
566 .for_each(|(maybe_mesh, state)| {
567 if let Some(mesh) = maybe_mesh {
568 commands.spawn((
569 MeshDim3,
570 PrimitiveData {
571 camera_mode,
572 primitive_state: state,
573 },
574 Mesh3d(meshes.add(mesh)),
575 MeshMaterial3d(material.clone()),
576 Transform::from_translation(POSITION),
577 ));
578 }
579 });
580}
581
582fn update_primitive_meshes(
583 camera_state: Res<State<CameraActive>>,
584 primitive_state: Res<State<PrimitiveSelected>>,
585 mut primitives: Query<(&mut Visibility, &PrimitiveData)>,
586) {
587 primitives.iter_mut().for_each(|(mut vis, primitive)| {
588 let visible = primitive.camera_mode == *camera_state.get()
589 && primitive.primitive_state == *primitive_state.get();
590 *vis = if visible {
591 Visibility::Inherited
592 } else {
593 Visibility::Hidden
594 };
595 });
596}
597
598fn rotate_primitive_2d_meshes(
599 mut primitives_2d: Query<
600 (&mut Transform, &ViewVisibility),
601 (With<PrimitiveData>, With<MeshDim2>),
602 >,
603 time: Res<Time>,
604) {
605 let rotation_2d = Quat::from_mat3(&Mat3::from_angle(time.elapsed_secs()));
606 primitives_2d
607 .iter_mut()
608 .filter(|(_, vis)| vis.get())
609 .for_each(|(mut transform, _)| {
610 transform.rotation = rotation_2d;
611 });
612}
613
614fn rotate_primitive_3d_meshes(
615 mut primitives_3d: Query<
616 (&mut Transform, &ViewVisibility),
617 (With<PrimitiveData>, With<MeshDim3>),
618 >,
619 time: Res<Time>,
620) {
621 let rotation_3d = Quat::from_rotation_arc(
622 Vec3::Z,
623 Vec3::new(
624 ops::sin(time.elapsed_secs()),
625 ops::cos(time.elapsed_secs()),
626 ops::sin(time.elapsed_secs()) * 0.5,
627 )
628 .try_normalize()
629 .unwrap_or(Vec3::Z),
630 );
631 primitives_3d
632 .iter_mut()
633 .filter(|(_, vis)| vis.get())
634 .for_each(|(mut transform, _)| {
635 transform.rotation = rotation_3d;
636 });
637}
638
639fn draw_gizmos_3d(mut gizmos: Gizmos, state: Res<State<PrimitiveSelected>>, time: Res<Time>) {
640 const POSITION: Vec3 = Vec3::new(LEFT_RIGHT_OFFSET_3D, 0.0, 0.0);
641 let rotation = Quat::from_rotation_arc(
642 Vec3::Z,
643 Vec3::new(
644 ops::sin(time.elapsed_secs()),
645 ops::cos(time.elapsed_secs()),
646 ops::sin(time.elapsed_secs()) * 0.5,
647 )
648 .try_normalize()
649 .unwrap_or(Vec3::Z),
650 );
651 let isometry = Isometry3d::new(POSITION, rotation);
652 let color = Color::WHITE;
653 let resolution = 10;
654
655 match state.get() {
656 PrimitiveSelected::RectangleAndCuboid => {
657 gizmos.primitive_3d(&CUBOID, isometry, color);
658 }
659 PrimitiveSelected::CircleAndSphere => drop(
660 gizmos
661 .primitive_3d(&SPHERE, isometry, color)
662 .resolution(resolution),
663 ),
664 PrimitiveSelected::Ellipse => {}
665 PrimitiveSelected::Triangle => gizmos.primitive_3d(&TRIANGLE_3D, isometry, color),
666 PrimitiveSelected::Plane => drop(gizmos.primitive_3d(&PLANE_3D, isometry, color)),
667 PrimitiveSelected::Line => gizmos.primitive_3d(&LINE3D, isometry, color),
668 PrimitiveSelected::Segment => gizmos.primitive_3d(&SEGMENT_3D, isometry, color),
669 PrimitiveSelected::Polyline => gizmos.primitive_3d(&POLYLINE_3D, isometry, color),
670 PrimitiveSelected::Polygon => {}
671 PrimitiveSelected::RegularPolygon => {}
672 PrimitiveSelected::Capsule => drop(
673 gizmos
674 .primitive_3d(&CAPSULE_3D, isometry, color)
675 .resolution(resolution),
676 ),
677 PrimitiveSelected::Cylinder => drop(
678 gizmos
679 .primitive_3d(&CYLINDER, isometry, color)
680 .resolution(resolution),
681 ),
682 PrimitiveSelected::Cone => drop(
683 gizmos
684 .primitive_3d(&CONE, isometry, color)
685 .resolution(resolution),
686 ),
687 PrimitiveSelected::ConicalFrustum => {
688 gizmos.primitive_3d(&CONICAL_FRUSTUM, isometry, color);
689 }
690
691 PrimitiveSelected::Torus => drop(
692 gizmos
693 .primitive_3d(&TORUS, isometry, color)
694 .minor_resolution(resolution)
695 .major_resolution(resolution),
696 ),
697 PrimitiveSelected::Tetrahedron => {
698 gizmos.primitive_3d(&TETRAHEDRON, isometry, color);
699 }
700
701 PrimitiveSelected::Arc => {}
702 PrimitiveSelected::CircularSector => {}
703 PrimitiveSelected::CircularSegment => {}
704 }
705}