Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/transforms/align.rs
6596 views
1
//! This example shows how to align the orientations of objects in 3D space along two axes using the `Transform::align` API.
2
3
use bevy::{
4
color::palettes::basic::{GRAY, RED, WHITE},
5
input::mouse::{AccumulatedMouseMotion, MouseButtonInput},
6
math::StableInterpolate,
7
prelude::*,
8
};
9
use rand::{Rng, SeedableRng};
10
use rand_chacha::ChaCha8Rng;
11
12
fn main() {
13
App::new()
14
.add_plugins(DefaultPlugins)
15
.add_systems(Startup, setup)
16
.add_systems(Update, (draw_ship_axes, draw_random_axes))
17
.add_systems(Update, (handle_keypress, handle_mouse, rotate_ship).chain())
18
.run();
19
}
20
21
/// This struct stores metadata for a single rotational move of the ship
22
#[derive(Component, Default)]
23
struct Ship {
24
/// The target transform of the ship move, the endpoint of interpolation
25
target_transform: Transform,
26
27
/// Whether the ship is currently in motion; allows motion to be paused
28
in_motion: bool,
29
}
30
31
#[derive(Component)]
32
struct RandomAxes(Dir3, Dir3);
33
34
#[derive(Component)]
35
struct Instructions;
36
37
#[derive(Resource)]
38
struct MousePressed(bool);
39
40
#[derive(Resource)]
41
struct SeededRng(ChaCha8Rng);
42
43
// Setup
44
45
fn setup(
46
mut commands: Commands,
47
mut meshes: ResMut<Assets<Mesh>>,
48
mut materials: ResMut<Assets<StandardMaterial>>,
49
asset_server: Res<AssetServer>,
50
) {
51
// We're seeding the PRNG here to make this example deterministic for testing purposes.
52
// This isn't strictly required in practical use unless you need your app to be deterministic.
53
let mut seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
54
55
// A camera looking at the origin
56
commands.spawn((
57
Camera3d::default(),
58
Transform::from_xyz(3., 2.5, 4.).looking_at(Vec3::ZERO, Vec3::Y),
59
));
60
61
// A plane that we can sit on top of
62
commands.spawn((
63
Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0))),
64
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
65
Transform::from_xyz(0., -2., 0.),
66
));
67
68
// A light source
69
commands.spawn((
70
PointLight {
71
shadows_enabled: true,
72
..default()
73
},
74
Transform::from_xyz(4.0, 7.0, -4.0),
75
));
76
77
// Initialize random axes
78
let first = seeded_rng.random();
79
let second = seeded_rng.random();
80
commands.spawn(RandomAxes(first, second));
81
82
// Finally, our ship that is going to rotate
83
commands.spawn((
84
SceneRoot(
85
asset_server
86
.load(GltfAssetLabel::Scene(0).from_asset("models/ship/craft_speederD.gltf")),
87
),
88
Ship {
89
target_transform: random_axes_target_alignment(&RandomAxes(first, second)),
90
..default()
91
},
92
));
93
94
// Instructions for the example
95
commands.spawn((
96
Text::new(
97
"The bright red axis is the primary alignment axis, and it will always be\n\
98
made to coincide with the primary target direction (white) exactly.\n\
99
The fainter red axis is the secondary alignment axis, and it is made to\n\
100
line up with the secondary target direction (gray) as closely as possible.\n\
101
Press 'R' to generate random target directions.\n\
102
Press 'T' to align the ship to those directions.\n\
103
Click and drag the mouse to rotate the camera.\n\
104
Press 'H' to hide/show these instructions.",
105
),
106
Node {
107
position_type: PositionType::Absolute,
108
top: px(12),
109
left: px(12),
110
..default()
111
},
112
Instructions,
113
));
114
115
commands.insert_resource(MousePressed(false));
116
commands.insert_resource(SeededRng(seeded_rng));
117
}
118
119
// Update systems
120
121
// Draw the main and secondary axes on the rotating ship
122
fn draw_ship_axes(mut gizmos: Gizmos, ship_transform: Single<&Transform, With<Ship>>) {
123
// Local Z-axis arrow, negative direction
124
let z_ends = arrow_ends(*ship_transform, Vec3::NEG_Z, 1.5);
125
gizmos.arrow(z_ends.0, z_ends.1, RED);
126
127
// local X-axis arrow
128
let x_ends = arrow_ends(*ship_transform, Vec3::X, 1.5);
129
gizmos.arrow(x_ends.0, x_ends.1, Color::srgb(0.65, 0., 0.));
130
}
131
132
// Draw the randomly generated axes
133
fn draw_random_axes(mut gizmos: Gizmos, random_axes: Single<&RandomAxes>) {
134
let RandomAxes(v1, v2) = *random_axes;
135
gizmos.arrow(Vec3::ZERO, 1.5 * *v1, WHITE);
136
gizmos.arrow(Vec3::ZERO, 1.5 * *v2, GRAY);
137
}
138
139
// Actually update the ship's transform according to its initial source and target
140
fn rotate_ship(ship: Single<(&mut Ship, &mut Transform)>, time: Res<Time>) {
141
let (mut ship, mut ship_transform) = ship.into_inner();
142
143
if !ship.in_motion {
144
return;
145
}
146
147
let target_rotation = ship.target_transform.rotation;
148
149
ship_transform
150
.rotation
151
.smooth_nudge(&target_rotation, 3.0, time.delta_secs());
152
153
if ship_transform.rotation.angle_between(target_rotation) <= f32::EPSILON {
154
ship.in_motion = false;
155
}
156
}
157
158
// Handle user inputs from the keyboard for dynamically altering the scenario
159
fn handle_keypress(
160
mut ship: Single<&mut Ship>,
161
mut random_axes: Single<&mut RandomAxes>,
162
mut instructions_viz: Single<&mut Visibility, With<Instructions>>,
163
keyboard: Res<ButtonInput<KeyCode>>,
164
mut seeded_rng: ResMut<SeededRng>,
165
) {
166
if keyboard.just_pressed(KeyCode::KeyR) {
167
// Randomize the target axes
168
let first = seeded_rng.0.random();
169
let second = seeded_rng.0.random();
170
**random_axes = RandomAxes(first, second);
171
172
// Stop the ship and set it up to transform from its present orientation to the new one
173
ship.in_motion = false;
174
ship.target_transform = random_axes_target_alignment(&random_axes);
175
}
176
177
if keyboard.just_pressed(KeyCode::KeyT) {
178
ship.in_motion ^= true;
179
}
180
181
if keyboard.just_pressed(KeyCode::KeyH) {
182
if *instructions_viz.as_ref() == Visibility::Hidden {
183
**instructions_viz = Visibility::Visible;
184
} else {
185
**instructions_viz = Visibility::Hidden;
186
}
187
}
188
}
189
190
// Handle user mouse input for panning the camera around
191
fn handle_mouse(
192
accumulated_mouse_motion: Res<AccumulatedMouseMotion>,
193
mut button_events: EventReader<MouseButtonInput>,
194
mut camera_transform: Single<&mut Transform, With<Camera>>,
195
mut mouse_pressed: ResMut<MousePressed>,
196
) {
197
// Store left-pressed state in the MousePressed resource
198
for button_event in button_events.read() {
199
if button_event.button != MouseButton::Left {
200
continue;
201
}
202
*mouse_pressed = MousePressed(button_event.state.is_pressed());
203
}
204
205
// If the mouse is not pressed, just ignore motion events
206
if !mouse_pressed.0 {
207
return;
208
}
209
if accumulated_mouse_motion.delta != Vec2::ZERO {
210
let displacement = accumulated_mouse_motion.delta.x;
211
camera_transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(-displacement / 75.));
212
}
213
}
214
215
// Helper functions (i.e. non-system functions)
216
217
fn arrow_ends(transform: &Transform, axis: Vec3, length: f32) -> (Vec3, Vec3) {
218
let local_vector = length * (transform.rotation * axis);
219
(transform.translation, transform.translation + local_vector)
220
}
221
222
// This is where `Transform::align` is actually used!
223
// Note that the choice of `Vec3::X` and `Vec3::Y` here matches the use of those in `draw_ship_axes`.
224
fn random_axes_target_alignment(random_axes: &RandomAxes) -> Transform {
225
let RandomAxes(first, second) = random_axes;
226
Transform::IDENTITY.aligned_by(Vec3::NEG_Z, *first, Vec3::X, *second)
227
}
228
229