Skip to main content

3d_gizmos/
3d_gizmos.rs

1//! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging.
2
3use bevy::{
4    camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
5    color::palettes::css::*,
6    prelude::*,
7};
8use std::f32::consts::PI;
9
10fn main() {
11    App::new()
12        .add_plugins((DefaultPlugins, FreeCameraPlugin))
13        .init_gizmo_group::<MyRoundGizmos>()
14        .add_systems(Startup, setup)
15        .add_systems(
16            Update,
17            (
18                draw_example_collection,
19                update_config,
20                drive_gizmos_animation,
21            ),
22        )
23        .run();
24}
25
26// We can create our own gizmo config group!
27#[derive(Default, Reflect, GizmoConfigGroup)]
28struct MyRoundGizmos;
29
30fn setup(
31    mut commands: Commands,
32    mut gizmo_assets: ResMut<Assets<GizmoAsset>>,
33    mut meshes: ResMut<Assets<Mesh>>,
34    mut materials: ResMut<Assets<StandardMaterial>>,
35) {
36    let mut gizmo = GizmoAsset::new();
37
38    // When drawing a lot of static lines a Gizmo component can have
39    // far better performance than the Gizmos system parameter,
40    // but the system parameter will perform better for smaller lines that update often.
41
42    // A sphere made out of 30_000 lines!
43    gizmo
44        .sphere(Isometry3d::IDENTITY, 0.5, CRIMSON)
45        .resolution(30_000 / 3);
46
47    commands.spawn((
48        Gizmo {
49            handle: gizmo_assets.add(gizmo),
50            line_config: GizmoLineConfig {
51                width: 5.,
52                ..default()
53            },
54            ..default()
55        },
56        Transform::from_xyz(4., 1., 0.),
57    ));
58
59    commands.spawn((
60        Camera3d::default(),
61        Transform::from_xyz(0., 1.5, 6.).looking_at(Vec3::ZERO, Vec3::Y),
62        FreeCamera::default(),
63    ));
64    // plane
65    commands.spawn((
66        Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))),
67        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
68    ));
69    // cube
70    commands.spawn((
71        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
72        MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),
73        Transform::from_xyz(0.0, 0.5, 0.0),
74    ));
75    // light
76    commands.spawn((
77        PointLight {
78            shadow_maps_enabled: true,
79            ..default()
80        },
81        Transform::from_xyz(4.0, 8.0, 4.0),
82    ));
83
84    // example instructions
85    commands.spawn((
86        Text::new(
87            "Press 'T' to toggle drawing gizmos on top of everything else in the scene\n\
88            Press 'P' to toggle perspective for line gizmos\n\
89            Hold 'Left' or 'Right' to change the line width of straight gizmos\n\
90            Hold 'Up' or 'Down' to change the line width of round gizmos\n\
91            Press '1' or '2' to toggle the visibility of straight gizmos or round gizmos\n\
92            Press 'B' to show all AABB boxes\n\
93            Press 'U' or 'I' to cycle through line styles for straight or round gizmos\n\
94            Press 'J' or 'K' to cycle through line joins for straight or round gizmos\n\
95            Press 'L' to cycle through gizmos animations (only for dotted/dashed round gizmos)\n\
96            Press 'Spacebar' to toggle pause",
97        ),
98        Node {
99            position_type: PositionType::Absolute,
100            top: px(12),
101            left: px(12),
102            ..default()
103        },
104    ));
105}
106
107fn draw_example_collection(
108    mut gizmos: Gizmos,
109    mut my_gizmos: Gizmos<MyRoundGizmos>,
110    time: Res<Time>,
111) {
112    gizmos.grid(
113        Quat::from_rotation_x(PI / 2.),
114        UVec2::splat(20),
115        Vec2::new(2., 2.),
116        // Light gray
117        LinearRgba::gray(0.65),
118    );
119    gizmos.grid(
120        Isometry3d::new(Vec3::splat(10.0), Quat::from_rotation_x(PI / 3. * 2.)),
121        UVec2::splat(20),
122        Vec2::new(2., 2.),
123        PURPLE,
124    );
125    gizmos.sphere(Vec3::splat(10.0), 1.0, PURPLE);
126
127    gizmos
128        .primitive_3d(
129            &Plane3d {
130                normal: Dir3::Y,
131                half_size: Vec2::splat(1.0),
132            },
133            Isometry3d::new(
134                Vec3::splat(4.0) + Vec2::from(ops::sin_cos(time.elapsed_secs())).extend(0.0),
135                Quat::from_rotation_x(PI / 2. + time.elapsed_secs()),
136            ),
137            GREEN,
138        )
139        .cell_count(UVec2::new(5, 10))
140        .spacing(Vec2::new(0.2, 0.1));
141
142    gizmos.cube(
143        Transform::from_translation(Vec3::Y * 0.5).with_scale(Vec3::splat(1.25)),
144        BLACK,
145    );
146    gizmos.rect(
147        Isometry3d::new(
148            Vec3::new(ops::cos(time.elapsed_secs()) * 2.5, 1., 0.),
149            Quat::from_rotation_y(PI / 2.),
150        ),
151        Vec2::splat(2.),
152        LIME,
153    );
154
155    gizmos.cross(Vec3::new(-1., 1., 1.), 0.5, FUCHSIA);
156
157    let domain = Interval::EVERYWHERE;
158    let curve = FunctionCurve::new(domain, |t| {
159        (Vec2::from(ops::sin_cos(t * 10.0))).extend(t - 6.0)
160    });
161    let resolution = ((ops::sin(time.elapsed_secs()) + 1.0) * 100.0) as usize;
162    let times_and_colors = (0..=resolution)
163        .map(|n| n as f32 / resolution as f32)
164        .map(|t| t * 5.0)
165        .map(|t| (t, TEAL.mix(&HOT_PINK, t / 5.0)));
166    gizmos.curve_gradient_3d(curve, times_and_colors);
167
168    gizmos.primitive_3d(
169        &Capsule3d::new(0.35, 0.5),
170        Isometry3d::new(
171            Vec3::new(-1.75, 0.75, 0.75),
172            Quat::from_rotation_y(ops::cos(time.elapsed_secs() / 2.0) * 10.0)
173                * Quat::from_rotation_z(PI / 3.0),
174        ),
175        YELLOW_GREEN,
176    );
177
178    my_gizmos.sphere(Vec3::new(1., 0.5, 0.), 0.5, RED);
179
180    my_gizmos
181        .rounded_cuboid(Vec3::new(-2.0, 0.75, -0.75), Vec3::splat(0.9), TURQUOISE)
182        .edge_radius(0.1)
183        .arc_resolution(4);
184
185    for y in [0., 0.5, 1.] {
186        gizmos.ray(
187            Vec3::new(1., y, 0.),
188            Vec3::new(-3., ops::sin(time.elapsed_secs() * 3.), 0.),
189            BLUE,
190        );
191    }
192
193    my_gizmos
194        .arc_3d(
195            180.0_f32.to_radians(),
196            0.2,
197            Isometry3d::new(
198                Vec3::ONE,
199                Quat::from_rotation_arc(Vec3::Y, Vec3::ONE.normalize()),
200            ),
201            ORANGE,
202        )
203        .resolution(10);
204
205    // Circles have 32 line-segments by default.
206    my_gizmos.circle(Quat::from_rotation_arc(Vec3::Z, Vec3::Y), 3., BLACK);
207
208    // You may want to increase this for larger circles or spheres.
209    my_gizmos
210        .circle(Quat::from_rotation_arc(Vec3::Z, Vec3::Y), 3.1, NAVY)
211        .resolution(64);
212    my_gizmos
213        .sphere(Isometry3d::IDENTITY, 3.2, BLACK)
214        .resolution(64);
215
216    gizmos.arrow(Vec3::ZERO, Vec3::splat(1.5), YELLOW);
217
218    // You can create more complex arrows using the arrow builder.
219    gizmos
220        .arrow(Vec3::new(2., 0., 2.), Vec3::new(2., 2., 2.), ORANGE_RED)
221        .with_double_end()
222        .with_tip_length(0.5);
223
224    let from = Vec3::new(1.0, 2.0, 3.0);
225    let to = Vec3::new(3.0, 2.5, 4.0);
226    gizmos.rect(from, Vec2::ONE, RED);
227    my_gizmos.short_arc_3d_between((from + to) / 2.0, from, to, YELLOW_GREEN);
228    gizmos.rect(to, Vec2::ONE, RED);
229}
230
231fn update_config(
232    mut config_store: ResMut<GizmoConfigStore>,
233    keyboard: Res<ButtonInput<KeyCode>>,
234    real_time: Res<Time<Real>>,
235    mut virtual_time: ResMut<Time<Virtual>>,
236) {
237    if keyboard.just_pressed(KeyCode::KeyT) {
238        for (_, config, _) in config_store.iter_mut() {
239            config.depth_bias = if config.depth_bias == 0. { -1. } else { 0. };
240        }
241    }
242    if keyboard.just_pressed(KeyCode::KeyP) {
243        for (_, config, _) in config_store.iter_mut() {
244            // Toggle line perspective
245            config.line.perspective ^= true;
246            // Increase the line width when line perspective is on
247            config.line.width *= if config.line.perspective { 5. } else { 1. / 5. };
248        }
249    }
250
251    let (config, _) = config_store.config_mut::<DefaultGizmoConfigGroup>();
252    if keyboard.pressed(KeyCode::ArrowRight) {
253        config.line.width += 5. * real_time.delta_secs();
254        config.line.width = config.line.width.clamp(0., 50.);
255    }
256    if keyboard.pressed(KeyCode::ArrowLeft) {
257        config.line.width -= 5. * real_time.delta_secs();
258        config.line.width = config.line.width.clamp(0., 50.);
259    }
260    if keyboard.just_pressed(KeyCode::Digit1) {
261        config.enabled ^= true;
262    }
263    if keyboard.just_pressed(KeyCode::KeyU) {
264        config.line.style = match config.line.style {
265            GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
266            GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
267                gap_scale: 3.0,
268                line_scale: 5.0,
269            },
270            _ => GizmoLineStyle::Solid,
271        };
272    }
273    if keyboard.just_pressed(KeyCode::KeyJ) {
274        config.line.joints = match config.line.joints {
275            GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
276            GizmoLineJoint::Miter => GizmoLineJoint::Round(4),
277            GizmoLineJoint::Round(_) => GizmoLineJoint::None,
278            GizmoLineJoint::None => GizmoLineJoint::Bevel,
279        };
280    }
281
282    let (my_config, _) = config_store.config_mut::<MyRoundGizmos>();
283    if keyboard.pressed(KeyCode::ArrowUp) {
284        my_config.line.width += 5. * real_time.delta_secs();
285        my_config.line.width = my_config.line.width.clamp(0., 50.);
286    }
287    if keyboard.pressed(KeyCode::ArrowDown) {
288        my_config.line.width -= 5. * real_time.delta_secs();
289        my_config.line.width = my_config.line.width.clamp(0., 50.);
290    }
291    if keyboard.just_pressed(KeyCode::Digit2) {
292        my_config.enabled ^= true;
293    }
294    if keyboard.just_pressed(KeyCode::KeyI) {
295        my_config.line.style = match my_config.line.style {
296            GizmoLineStyle::Solid => GizmoLineStyle::Dotted,
297            GizmoLineStyle::Dotted => GizmoLineStyle::Dashed {
298                gap_scale: 3.0,
299                line_scale: 5.0,
300            },
301            _ => GizmoLineStyle::Solid,
302        };
303    }
304    if keyboard.just_pressed(KeyCode::KeyK) {
305        my_config.line.joints = match my_config.line.joints {
306            GizmoLineJoint::Bevel => GizmoLineJoint::Miter,
307            GizmoLineJoint::Miter => GizmoLineJoint::Round(4),
308            GizmoLineJoint::Round(_) => GizmoLineJoint::None,
309            GizmoLineJoint::None => GizmoLineJoint::Bevel,
310        };
311    }
312
313    if keyboard.just_pressed(KeyCode::KeyB) {
314        // AABB gizmos are normally only drawn on entities with a ShowAabbGizmo component
315        // We can change this behavior in the configuration of AabbGizmoGroup
316        config_store.config_mut::<AabbGizmoConfigGroup>().1.draw_all ^= true;
317    }
318    if keyboard.just_pressed(KeyCode::Space) {
319        virtual_time.toggle();
320    }
321}
322
323enum GizmosAnimationType {
324    Linear,
325    BackAndForth,
326    Stutter,
327}
328
329fn drive_gizmos_animation(
330    mut config_store: ResMut<GizmoConfigStore>,
331    virtual_time: ResMut<Time<Virtual>>,
332    keyboard: Res<ButtonInput<KeyCode>>,
333    mut animation_type: Local<Option<GizmosAnimationType>>,
334) {
335    if keyboard.just_pressed(KeyCode::KeyL) {
336        *animation_type = match *animation_type {
337            None => Some(GizmosAnimationType::Linear),
338            Some(GizmosAnimationType::Linear) => Some(GizmosAnimationType::BackAndForth),
339            Some(GizmosAnimationType::BackAndForth) => Some(GizmosAnimationType::Stutter),
340            Some(GizmosAnimationType::Stutter) => None,
341        };
342    }
343
344    if let Some(animation_type) = animation_type.as_ref() {
345        let (my_config, _) = config_store.config_mut::<MyRoundGizmos>();
346        match animation_type {
347            GizmosAnimationType::Linear => {
348                my_config.line.animation_offset = virtual_time.elapsed_secs() * 10.0;
349            }
350            GizmosAnimationType::BackAndForth => {
351                my_config.line.animation_offset = ops::sin(virtual_time.elapsed_secs()) * 10.0;
352            }
353            GizmosAnimationType::Stutter => {
354                my_config.line.animation_offset = (virtual_time.elapsed_secs() * 4.0).round();
355            }
356        }
357    }
358}