docs/bevy/examples/gizmos/axes.rs 6.8 K · 220 lines · raw · history

1 //! This example demonstrates the implementation and behavior of the axes gizmo.
2
3 use bevy::{camera::primitives::Aabb, prelude::*};
4 use chacha20::ChaCha8Rng;
5 use rand::{RngExt, SeedableRng};
6 use std::f32::consts::PI;
7
8 const TRANSITION_DURATION: f32 = 2.0;
9
10 fn main() {
11 App::new()
12 .add_plugins(DefaultPlugins)
13 .add_systems(Startup, setup)
14 .add_systems(Update, (move_cubes, draw_axes).chain())
15 .run();
16 }
17
18 /// The `ShowAxes` component is attached to an entity to get the `draw_axes` system to
19 /// display axes according to its Transform component.
20 #[derive(Component)]
21 struct ShowAxes;
22
23 /// The `TransformTracking` component keeps track of the data we need to interpolate
24 /// between two transforms in our example.
25 #[derive(Component)]
26 struct TransformTracking {
27 /// The initial transform of the cube during the move
28 initial_transform: Transform,
29
30 /// The target transform of the cube during the move
31 target_transform: Transform,
32
33 /// The progress of the cube during the move in seconds
34 progress: f32,
35 }
36
37 #[derive(Resource)]
38 struct SeededRng(ChaCha8Rng);
39
40 fn setup(
41 mut commands: Commands,
42 mut meshes: ResMut<Assets<Mesh>>,
43 mut materials: ResMut<Assets<StandardMaterial>>,
44 ) {
45 // We're seeding the PRNG here to make this example deterministic for testing purposes.
46 // This isn't strictly required in practical use unless you need your app to be deterministic.
47 let mut rng = ChaCha8Rng::seed_from_u64(19878367467713);
48
49 // Lights...
50 commands.spawn((
51 PointLight {
52 shadow_maps_enabled: true,
53 ..default()
54 },
55 Transform::from_xyz(2., 6., 0.),
56 ));
57
58 // Camera...
59 commands.spawn((
60 Camera3d::default(),
61 Transform::from_xyz(0., 1.5, -8.).looking_at(Vec3::new(0., -0.5, 0.), Vec3::Y),
62 ));
63
64 // Action! (Our cubes that are going to move)
65 commands.spawn((
66 Mesh3d(meshes.add(Cuboid::new(1., 1., 1.))),
67 MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),
68 ShowAxes,
69 TransformTracking {
70 initial_transform: default(),
71 target_transform: random_transform(&mut rng),
72 progress: 0.0,
73 },
74 ));
75
76 commands.spawn((
77 Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))),
78 MeshMaterial3d(materials.add(Color::srgb(0.6, 0.7, 0.8))),
79 ShowAxes,
80 TransformTracking {
81 initial_transform: default(),
82 target_transform: random_transform(&mut rng),
83 progress: 0.0,
84 },
85 ));
86
87 // A plane to give a sense of place
88 commands.spawn((
89 Mesh3d(meshes.add(Plane3d::default().mesh().size(20., 20.))),
90 MeshMaterial3d(materials.add(Color::srgb(0.1, 0.1, 0.1))),
91 Transform::from_xyz(0., -2., 0.),
92 ));
93
94 commands.insert_resource(SeededRng(rng));
95 }
96
97 // This system draws the axes based on the cube's transform, with length based on the size of
98 // the entity's axis-aligned bounding box (AABB).
99 fn draw_axes(mut gizmos: Gizmos, query: Query<(&Transform, &Aabb), With<ShowAxes>>) {
100 for (&transform, &aabb) in &query {
101 let length = aabb.half_extents.length();
102 gizmos.axes(transform, length);
103 }
104 }
105
106 // This system changes the cubes' transforms to interpolate between random transforms
107 fn move_cubes(
108 mut query: Query<(&mut Transform, &mut TransformTracking)>,
109 time: Res<Time>,
110 mut rng: ResMut<SeededRng>,
111 ) {
112 for (mut transform, mut tracking) in &mut query {
113 *transform = interpolate_transforms(
114 tracking.initial_transform,
115 tracking.target_transform,
116 tracking.progress / TRANSITION_DURATION,
117 );
118
119 if tracking.progress < TRANSITION_DURATION {
120 tracking.progress += time.delta_secs();
121 } else {
122 tracking.initial_transform = *transform;
123 tracking.target_transform = random_transform(&mut rng.0);
124 tracking.progress = 0.0;
125 }
126 }
127 }
128
129 // Helper functions for random transforms and interpolation:
130
131 const TRANSLATION_BOUND_LOWER_X: f32 = -5.;
132 const TRANSLATION_BOUND_UPPER_X: f32 = 5.;
133 const TRANSLATION_BOUND_LOWER_Y: f32 = -1.;
134 const TRANSLATION_BOUND_UPPER_Y: f32 = 1.;
135 const TRANSLATION_BOUND_LOWER_Z: f32 = -2.;
136 const TRANSLATION_BOUND_UPPER_Z: f32 = 6.;
137
138 const SCALING_BOUND_LOWER_LOG: f32 = -1.2;
139 const SCALING_BOUND_UPPER_LOG: f32 = 1.2;
140
141 fn random_transform(rng: &mut impl RngExt) -> Transform {
142 Transform {
143 translation: random_translation(rng),
144 rotation: random_rotation(rng),
145 scale: random_scale(rng),
146 }
147 }
148
149 fn random_translation(rng: &mut impl RngExt) -> Vec3 {
150 let x = rng.random::<f32>() * (TRANSLATION_BOUND_UPPER_X - TRANSLATION_BOUND_LOWER_X)
151 + TRANSLATION_BOUND_LOWER_X;
152 let y = rng.random::<f32>() * (TRANSLATION_BOUND_UPPER_Y - TRANSLATION_BOUND_LOWER_Y)
153 + TRANSLATION_BOUND_LOWER_Y;
154 let z = rng.random::<f32>() * (TRANSLATION_BOUND_UPPER_Z - TRANSLATION_BOUND_LOWER_Z)
155 + TRANSLATION_BOUND_LOWER_Z;
156
157 Vec3::new(x, y, z)
158 }
159
160 fn random_scale(rng: &mut impl RngExt) -> Vec3 {
161 let x_factor_log = rng.random::<f32>() * (SCALING_BOUND_UPPER_LOG - SCALING_BOUND_LOWER_LOG)
162 + SCALING_BOUND_LOWER_LOG;
163 let y_factor_log = rng.random::<f32>() * (SCALING_BOUND_UPPER_LOG - SCALING_BOUND_LOWER_LOG)
164 + SCALING_BOUND_LOWER_LOG;
165 let z_factor_log = rng.random::<f32>() * (SCALING_BOUND_UPPER_LOG - SCALING_BOUND_LOWER_LOG)
166 + SCALING_BOUND_LOWER_LOG;
167
168 Vec3::new(
169 ops::exp2(x_factor_log),
170 ops::exp2(y_factor_log),
171 ops::exp2(z_factor_log),
172 )
173 }
174
175 fn elerp(v1: Vec3, v2: Vec3, t: f32) -> Vec3 {
176 let x_factor_log = (1. - t) * ops::log2(v1.x) + t * ops::log2(v2.x);
177 let y_factor_log = (1. - t) * ops::log2(v1.y) + t * ops::log2(v2.y);
178 let z_factor_log = (1. - t) * ops::log2(v1.z) + t * ops::log2(v2.z);
179
180 Vec3::new(
181 ops::exp2(x_factor_log),
182 ops::exp2(y_factor_log),
183 ops::exp2(z_factor_log),
184 )
185 }
186
187 fn random_rotation(rng: &mut impl RngExt) -> Quat {
188 let dir = random_direction(rng);
189 let angle = rng.random::<f32>() * 2. * PI;
190
191 Quat::from_axis_angle(dir, angle)
192 }
193
194 fn random_direction(rng: &mut impl RngExt) -> Vec3 {
195 let height = rng.random::<f32>() * 2. - 1.;
196 let theta = rng.random::<f32>() * 2. * PI;
197
198 build_direction(height, theta)
199 }
200
201 fn build_direction(height: f32, theta: f32) -> Vec3 {
202 let z = height;
203 let m = ops::sin(ops::acos(z));
204 let x = ops::cos(theta) * m;
205 let y = ops::sin(theta) * m;
206
207 Vec3::new(x, y, z)
208 }
209
210 fn interpolate_transforms(t1: Transform, t2: Transform, t: f32) -> Transform {
211 let translation = t1.translation.lerp(t2.translation, t);
212 let rotation = t1.rotation.slerp(t2.rotation, t);
213 let scale = elerp(t1.scale, t2.scale, t);
214
215 Transform {
216 translation,
217 rotation,
218 scale,
219 }
220 }