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