Skip to content

Commit 4673fb3

Browse files
authored
Example for axes gizmos (#12299)
# Objective - Follow-up to #12211 - Introduces an example project that demonstrates the implementation and behavior of `Gizmos::axes` for an entity with a `Transform` component. ## Solution In order to demonstrate how `Gizmo::axes` can be used and behaves in practice, we introduce an example of a simple scene containing a pair of cuboids locked in a grotesque, inscrutable dance: the two are repeatedly given random `Transform`s which they interpolate to, showing how the axes move with objects as they translate, rotate, and scale. <img width="1023" alt="Screenshot 2024-03-04 at 1 16 33 PM" src="https://github.com/bevyengine/bevy/assets/2975848/c1ff4794-6722-491c-8522-f59801645139"> On the implementation side, we demonstrate how to draw axes for entities, automatically sizing them according to their bounding boxes (so that the axes will be visible): ````rust fn draw_axes(mut gizmos: Gizmos, query: Query<(&Transform, &Aabb), With<ShowAxes>>) { for (&transform, &aabb) in &query { let length = aabb.half_extents.length(); gizmos.axes(transform, length); } } ```` --- ## Changelog - Created examples/gizmos/axes.rs. - Added 'axes' example to Cargo.toml.
1 parent b02a2ef commit 4673fb3

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2690,6 +2690,17 @@ description = "A scene showcasing 3D gizmos"
26902690
category = "Gizmos"
26912691
wasm = true
26922692

2693+
[[example]]
2694+
name = "axes"
2695+
path = "examples/gizmos/axes.rs"
2696+
doc-scrape-examples = true
2697+
2698+
[package.metadata.example.axes]
2699+
name = "Axes"
2700+
description = "Demonstrates the function of axes gizmos"
2701+
category = "Gizmos"
2702+
wasm = true
2703+
26932704
[[example]]
26942705
name = "light_gizmos"
26952706
path = "examples/gizmos/light_gizmos.rs"

examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ Example | Description
267267
--- | ---
268268
[2D Gizmos](../examples/gizmos/2d_gizmos.rs) | A scene showcasing 2D gizmos
269269
[3D Gizmos](../examples/gizmos/3d_gizmos.rs) | A scene showcasing 3D gizmos
270+
[Axes](../examples/gizmos/axes.rs) | Demonstrates the function of axes gizmos
270271
[Light Gizmos](../examples/gizmos/light_gizmos.rs) | A scene showcasing light gizmos
271272

272273
## Input

examples/gizmos/axes.rs

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

0 commit comments

Comments
 (0)