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