Use a ship in Transform::align example (#13935)

# Objective

The documentation for
[`Transform::align`](https://docs.rs/bevy/0.14.0-rc.3/bevy/transform/components/struct.Transform.html#method.align)
mentions a hypothetical ship model. Showing this concretely would be a
nice improvement over using a cube.

> For example, if a spaceship model has its nose pointing in the
X-direction in its own local coordinates and its dorsal fin pointing in
the Y-direction, then align(Dir3::X, v, Dir3::Y, w) will make the
spaceship’s nose point in the direction of v, while the dorsal fin does
its best to point in the direction w.


## Solution

This commit makes the ship less hypothetical by using a kenney ship
model in the example.

The local axes for the ship needed to change to accommodate the gltf, so
the hypothetical in the documentation and this example's local axes
don't necessarily match. Docs use `align(Dir3::X, v, Dir3::Y, w)` and
this example now uses `(Vec3::NEG_Z, *first, Vec3::X, *second)`.

I manually modified the `craft_speederD` Node's `translation` to be
0,0,0 in the gltf file, which means it now differs from kenney's
original model.

Original ship from: https://kenney.nl/assets/space-kit

## Testing

```
cargo run --example align
```

![screenshot-2024-06-19-at-14 27
05@2x](https://github.com/bevyengine/bevy/assets/551247/ab1afc8f-76b2-42b6-b455-f0d1c77cfed7)
![screenshot-2024-06-19-at-14 27
12@2x](https://github.com/bevyengine/bevy/assets/551247/4a01031c-4ea1-43ab-8078-3656db67efe0)
![screenshot-2024-06-19-at-14 27
20@2x](https://github.com/bevyengine/bevy/assets/551247/06830f38-ba2b-4e3a-a265-2d10f9ea9de9)
This commit is contained in:
Chris Biscardi 2024-06-19 17:58:00 -07:00 committed by GitHub
parent 31af724944
commit 4d3f43131e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 333 additions and 44 deletions

View file

@ -0,0 +1,288 @@
{
"extensionsUsed": [
"KHR_materials_unlit"
],
"asset": {
"generator": "UniGLTF-1.27",
"version": "2.0"
},
"buffers": [
{
"uri": "craft_speederD_data.bin",
"byteLength": 20120
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 6096,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 6096,
"byteLength": 6096,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 12192,
"byteLength": 4064,
"target": 34962
},
{
"buffer": 0,
"byteOffset": 16256,
"byteLength": 732,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 16988,
"byteLength": 1368,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 18356,
"byteLength": 456,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 18812,
"byteLength": 1308,
"target": 34963
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 508,
"max": [
1.4,
0.9,
1.11283529
],
"min": [
-1.4,
0,
-1.11283529
],
"normalized": false
},
{
"bufferView": 1,
"byteOffset": 0,
"type": "VEC3",
"componentType": 5126,
"count": 508,
"normalized": false
},
{
"bufferView": 2,
"byteOffset": 0,
"type": "VEC2",
"componentType": 5126,
"count": 508,
"normalized": false
},
{
"bufferView": 3,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 183,
"normalized": false
},
{
"bufferView": 4,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 342,
"normalized": false
},
{
"bufferView": 5,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 114,
"normalized": false
},
{
"bufferView": 6,
"byteOffset": 0,
"type": "SCALAR",
"componentType": 5125,
"count": 327,
"normalized": false
}
],
"materials": [
{
"name": "metal",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.843137264,
0.870588243,
0.9098039,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
},
{
"name": "metalDark",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.6750623,
0.7100219,
0.7735849,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
},
{
"name": "dark",
"pbrMetallicRoughness": {
"baseColorFactor": [
0.274509817,
0.298039228,
0.34117648,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
},
{
"name": "metalRed",
"pbrMetallicRoughness": {
"baseColorFactor": [
1,
0.628524244,
0.2028302,
1
],
"metallicFactor": 1,
"roughnessFactor": 1
},
"doubleSided": false,
"alphaMode": "OPAQUE"
}
],
"meshes": [
{
"name": "Mesh craft_speederD",
"primitives": [
{
"mode": 4,
"indices": 3,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 0
},
{
"mode": 4,
"indices": 4,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 1
},
{
"mode": 4,
"indices": 5,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 2
},
{
"mode": 4,
"indices": 6,
"attributes": {
"POSITION": 0,
"NORMAL": 1,
"TEXCOORD_0": 2
},
"material": 3
}
]
}
],
"nodes": [
{
"children": [
1
],
"name": "tmpParent",
"translation": [
0,
0,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
]
},
{
"name": "craft_speederD",
"translation": [
0,
0,
0
],
"rotation": [
0,
0,
0,
1
],
"scale": [
1,
1,
1
],
"mesh": 0
}
],
"scenes": [
{
"nodes": [
1
]
}
],
"scene": 0
}

Binary file not shown.

View file

@ -10,24 +10,24 @@ fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins)
.add_systems(Startup, setup) .add_systems(Startup, setup)
.add_systems(Update, (draw_cube_axes, draw_random_axes)) .add_systems(Update, (draw_ship_axes, draw_random_axes))
.add_systems(Update, (handle_keypress, handle_mouse, rotate_cube).chain()) .add_systems(Update, (handle_keypress, handle_mouse, rotate_ship).chain())
.run(); .run();
} }
/// This struct stores metadata for a single rotational move of the cube /// This struct stores metadata for a single rotational move of the ship
#[derive(Component, Default)] #[derive(Component, Default)]
struct Cube { struct Ship {
/// The initial transform of the cube move, the starting point of interpolation /// The initial transform of the ship move, the starting point of interpolation
initial_transform: Transform, initial_transform: Transform,
/// The target transform of the cube move, the endpoint of interpolation /// The target transform of the ship move, the endpoint of interpolation
target_transform: Transform, target_transform: Transform,
/// The progress of the cube move in percentage points /// The progress of the ship move in percentage points
progress: u16, progress: u16,
/// Whether the cube is currently in motion; allows motion to be paused /// Whether the ship is currently in motion; allows motion to be paused
in_motion: bool, in_motion: bool,
} }
@ -49,6 +49,7 @@ fn setup(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
asset_server: Res<AssetServer>,
) { ) {
// We're seeding the PRNG here to make this example deterministic for testing purposes. // We're seeding the PRNG here to make this example deterministic for testing purposes.
// This isn't strictly required in practical use unless you need your app to be deterministic. // This isn't strictly required in practical use unless you need your app to be deterministic.
@ -83,14 +84,14 @@ fn setup(
let second = seeded_rng.gen(); let second = seeded_rng.gen();
commands.spawn(RandomAxes(first, second)); commands.spawn(RandomAxes(first, second));
// Finally, our cube that is going to rotate // Finally, our ship that is going to rotate
commands.spawn(( commands.spawn((
PbrBundle { SceneBundle {
mesh: meshes.add(Cuboid::new(1.0, 1.0, 1.0)), scene: asset_server
material: materials.add(Color::srgb(0.5, 0.5, 0.5)), .load(GltfAssetLabel::Scene(0).from_asset("models/ship/craft_speederD.gltf")),
..default() ..default()
}, },
Cube { Ship {
initial_transform: Transform::IDENTITY, initial_transform: Transform::IDENTITY,
target_transform: random_axes_target_alignment(&RandomAxes(first, second)), target_transform: random_axes_target_alignment(&RandomAxes(first, second)),
..default() ..default()
@ -105,7 +106,7 @@ fn setup(
The fainter red axis is the secondary alignment axis, and it is made to\n\ The fainter red axis is the secondary alignment axis, and it is made to\n\
line up with the secondary target direction (gray) as closely as possible.\n\ line up with the secondary target direction (gray) as closely as possible.\n\
Press 'R' to generate random target directions.\n\ Press 'R' to generate random target directions.\n\
Press 'T' to align the cube to those directions.\n\ Press 'T' to align the ship to those directions.\n\
Click and drag the mouse to rotate the camera.\n\ Click and drag the mouse to rotate the camera.\n\
Press 'H' to hide/show these instructions.", Press 'H' to hide/show these instructions.",
TextStyle::default(), TextStyle::default(),
@ -125,17 +126,17 @@ fn setup(
// Update systems // Update systems
// Draw the main and secondary axes on the rotating cube // Draw the main and secondary axes on the rotating ship
fn draw_cube_axes(mut gizmos: Gizmos, query: Query<&Transform, With<Cube>>) { fn draw_ship_axes(mut gizmos: Gizmos, query: Query<&Transform, With<Ship>>) {
let cube_transform = query.single(); let ship_transform = query.single();
// Local X-axis arrow // Local Z-axis arrow, negative direction
let x_ends = arrow_ends(cube_transform, Vec3::X, 1.5); let z_ends = arrow_ends(ship_transform, Vec3::NEG_Z, 1.5);
gizmos.arrow(x_ends.0, x_ends.1, RED); gizmos.arrow(z_ends.0, z_ends.1, RED);
// local Y-axis arrow // local X-axis arrow
let y_ends = arrow_ends(cube_transform, Vec3::Y, 1.5); let x_ends = arrow_ends(ship_transform, Vec3::X, 1.5);
gizmos.arrow(y_ends.0, y_ends.1, Color::srgb(0.65, 0., 0.)); gizmos.arrow(x_ends.0, x_ends.1, Color::srgb(0.65, 0., 0.));
} }
// Draw the randomly generated axes // Draw the randomly generated axes
@ -145,38 +146,38 @@ fn draw_random_axes(mut gizmos: Gizmos, query: Query<&RandomAxes>) {
gizmos.arrow(Vec3::ZERO, 1.5 * *v2, GRAY); gizmos.arrow(Vec3::ZERO, 1.5 * *v2, GRAY);
} }
// Actually update the cube's transform according to its initial source and target // Actually update the ship's transform according to its initial source and target
fn rotate_cube(mut cube: Query<(&mut Cube, &mut Transform)>) { fn rotate_ship(mut ship: Query<(&mut Ship, &mut Transform)>) {
let (mut cube, mut cube_transform) = cube.single_mut(); let (mut ship, mut ship_transform) = ship.single_mut();
if !cube.in_motion { if !ship.in_motion {
return; return;
} }
let start = cube.initial_transform.rotation; let start = ship.initial_transform.rotation;
let end = cube.target_transform.rotation; let end = ship.target_transform.rotation;
let p: f32 = cube.progress.into(); let p: f32 = ship.progress.into();
let t = p / 100.; let t = p / 100.;
*cube_transform = Transform::from_rotation(start.slerp(end, t)); *ship_transform = Transform::from_rotation(start.slerp(end, t));
if cube.progress == 100 { if ship.progress == 100 {
cube.in_motion = false; ship.in_motion = false;
} else { } else {
cube.progress += 1; ship.progress += 1;
} }
} }
// Handle user inputs from the keyboard for dynamically altering the scenario // Handle user inputs from the keyboard for dynamically altering the scenario
fn handle_keypress( fn handle_keypress(
mut cube: Query<(&mut Cube, &Transform)>, mut ship: Query<(&mut Ship, &Transform)>,
mut random_axes: Query<&mut RandomAxes>, mut random_axes: Query<&mut RandomAxes>,
mut instructions: Query<&mut Visibility, With<Instructions>>, mut instructions: Query<&mut Visibility, With<Instructions>>,
keyboard: Res<ButtonInput<KeyCode>>, keyboard: Res<ButtonInput<KeyCode>>,
mut seeded_rng: ResMut<SeededRng>, mut seeded_rng: ResMut<SeededRng>,
) { ) {
let (mut cube, cube_transform) = cube.single_mut(); let (mut ship, ship_transform) = ship.single_mut();
let mut random_axes = random_axes.single_mut(); let mut random_axes = random_axes.single_mut();
if keyboard.just_pressed(KeyCode::KeyR) { if keyboard.just_pressed(KeyCode::KeyR) {
@ -185,15 +186,15 @@ fn handle_keypress(
let second = seeded_rng.0.gen(); let second = seeded_rng.0.gen();
*random_axes = RandomAxes(first, second); *random_axes = RandomAxes(first, second);
// Stop the cube and set it up to transform from its present orientation to the new one // Stop the ship and set it up to transform from its present orientation to the new one
cube.in_motion = false; ship.in_motion = false;
cube.initial_transform = *cube_transform; ship.initial_transform = *ship_transform;
cube.target_transform = random_axes_target_alignment(&random_axes); ship.target_transform = random_axes_target_alignment(&random_axes);
cube.progress = 0; ship.progress = 0;
} }
if keyboard.just_pressed(KeyCode::KeyT) { if keyboard.just_pressed(KeyCode::KeyT) {
cube.in_motion ^= true; ship.in_motion ^= true;
} }
if keyboard.just_pressed(KeyCode::KeyH) { if keyboard.just_pressed(KeyCode::KeyH) {
@ -240,8 +241,8 @@ fn arrow_ends(transform: &Transform, axis: Vec3, length: f32) -> (Vec3, Vec3) {
} }
// This is where `Transform::align` is actually used! // This is where `Transform::align` is actually used!
// Note that the choice of `Vec3::X` and `Vec3::Y` here matches the use of those in `draw_cube_axes`. // Note that the choice of `Vec3::X` and `Vec3::Y` here matches the use of those in `draw_ship_axes`.
fn random_axes_target_alignment(random_axes: &RandomAxes) -> Transform { fn random_axes_target_alignment(random_axes: &RandomAxes) -> Transform {
let RandomAxes(first, second) = random_axes; let RandomAxes(first, second) = random_axes;
Transform::IDENTITY.aligned_by(Vec3::X, *first, Vec3::Y, *second) Transform::IDENTITY.aligned_by(Vec3::NEG_Z, *first, Vec3::X, *second)
} }