//! This example shows how to align the orientations of objects in 3D space along two axes using the `Transform::align` API. use bevy::{ color::palettes::basic::{GRAY, RED, WHITE}, input::mouse::{AccumulatedMouseMotion, MouseButtonInput}, math::StableInterpolate, prelude::*, }; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; fn main() { App::new() .add_plugins(DefaultPlugins) .add_systems(Startup, setup) .add_systems(Update, (draw_ship_axes, draw_random_axes)) .add_systems(Update, (handle_keypress, handle_mouse, rotate_ship).chain()) .run(); } /// This struct stores metadata for a single rotational move of the ship #[derive(Component, Default)] struct Ship { /// The target transform of the ship move, the endpoint of interpolation target_transform: Transform, /// Whether the ship is currently in motion; allows motion to be paused in_motion: bool, } #[derive(Component)] struct RandomAxes(Dir3, Dir3); #[derive(Component)] struct Instructions; #[derive(Resource)] struct MousePressed(bool); #[derive(Resource)] struct SeededRng(ChaCha8Rng); // Setup fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, asset_server: Res, ) { // 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. let mut seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712); // A camera looking at the origin commands.spawn(( Camera3d::default(), Transform::from_xyz(3., 2.5, 4.).looking_at(Vec3::ZERO, Vec3::Y), )); // A plane that we can sit on top of commands.spawn(( Mesh3d(meshes.add(Plane3d::default().mesh().size(100.0, 100.0))), MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), Transform::from_xyz(0., -2., 0.), )); // A light source commands.spawn(( PointLight { shadows_enabled: true, ..default() }, Transform::from_xyz(4.0, 7.0, -4.0), )); // Initialize random axes let first = seeded_rng.gen(); let second = seeded_rng.gen(); commands.spawn(RandomAxes(first, second)); // Finally, our ship that is going to rotate commands.spawn(( SceneRoot( asset_server .load(GltfAssetLabel::Scene(0).from_asset("models/ship/craft_speederD.gltf")), ), Ship { target_transform: random_axes_target_alignment(&RandomAxes(first, second)), ..default() }, )); // Instructions for the example commands.spawn(( Text::new( "The bright red axis is the primary alignment axis, and it will always be\n\ made to coincide with the primary target direction (white) exactly.\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\ Press 'R' to generate random target directions.\n\ Press 'T' to align the ship to those directions.\n\ Click and drag the mouse to rotate the camera.\n\ Press 'H' to hide/show these instructions.", ), Style { position_type: PositionType::Absolute, top: Val::Px(12.0), left: Val::Px(12.0), ..default() }, Instructions, )); commands.insert_resource(MousePressed(false)); commands.insert_resource(SeededRng(seeded_rng)); } // Update systems // Draw the main and secondary axes on the rotating ship fn draw_ship_axes(mut gizmos: Gizmos, query: Query<&Transform, With>) { let ship_transform = query.single(); // Local Z-axis arrow, negative direction let z_ends = arrow_ends(ship_transform, Vec3::NEG_Z, 1.5); gizmos.arrow(z_ends.0, z_ends.1, RED); // local X-axis arrow let x_ends = arrow_ends(ship_transform, Vec3::X, 1.5); gizmos.arrow(x_ends.0, x_ends.1, Color::srgb(0.65, 0., 0.)); } // Draw the randomly generated axes fn draw_random_axes(mut gizmos: Gizmos, query: Query<&RandomAxes>) { let RandomAxes(v1, v2) = query.single(); gizmos.arrow(Vec3::ZERO, 1.5 * *v1, WHITE); gizmos.arrow(Vec3::ZERO, 1.5 * *v2, GRAY); } // Actually update the ship's transform according to its initial source and target fn rotate_ship(mut ship: Query<(&mut Ship, &mut Transform)>, time: Res