axes/
axes.rs

1//! This example demonstrates the implementation and behavior of the axes gizmo.
2
3use bevy::{camera::primitives::Aabb, prelude::*};
4use rand::{Rng, SeedableRng};
5use rand_chacha::ChaCha8Rng;
6use std::f32::consts::PI;
7
8const TRANSITION_DURATION: f32 = 2.0;
9
10fn 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)]
21struct 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)]
26struct 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)]
38struct SeededRng(ChaCha8Rng);
39
40fn 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            shadows_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).
99fn 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
107fn 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
131const TRANSLATION_BOUND_LOWER_X: f32 = -5.;
132const TRANSLATION_BOUND_UPPER_X: f32 = 5.;
133const TRANSLATION_BOUND_LOWER_Y: f32 = -1.;
134const TRANSLATION_BOUND_UPPER_Y: f32 = 1.;
135const TRANSLATION_BOUND_LOWER_Z: f32 = -2.;
136const TRANSLATION_BOUND_UPPER_Z: f32 = 6.;
137
138const SCALING_BOUND_LOWER_LOG: f32 = -1.2;
139const SCALING_BOUND_UPPER_LOG: f32 = 1.2;
140
141fn random_transform(rng: &mut impl Rng) -> Transform {
142    Transform {
143        translation: random_translation(rng),
144        rotation: random_rotation(rng),
145        scale: random_scale(rng),
146    }
147}
148
149fn random_translation(rng: &mut impl Rng) -> 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
160fn random_scale(rng: &mut impl Rng) -> 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
175fn 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
187fn random_rotation(rng: &mut impl Rng) -> 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
194fn random_direction(rng: &mut impl Rng) -> 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
201fn 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
210fn 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}