Skip to content

Instantly share code, notes, and snippets.

@Hermitao
Last active February 4, 2026 20:11
Show Gist options
  • Select an option

  • Save Hermitao/0a908f8af19b11132e3bdb5ba4ef99f0 to your computer and use it in GitHub Desktop.

Select an option

Save Hermitao/0a908f8af19b11132e3bdb5ba4ef99f0 to your computer and use it in GitHub Desktop.
Bevy flight model - prototype
use avian3d::parry::na::clamp;
use avian3d::prelude::*;
use bevy::prelude::*;
use bevy::text::FontSmoothing;
use bevy::window::PresentMode;
use bevy::dev_tools::fps_overlay::{
FpsOverlayConfig,
FpsOverlayPlugin,
FrameTimeGraphConfig
};
use bevy::time::Fixed;
// use bevy_sky_gradient::prelude::*;
use std::thread;
use chrono::Local;
pub fn run() {
App::new()
// .insert_resource(Time::<Fixed>::from_seconds(1.0 / 60.0))
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
// Set the present mode to AutoNoVsync to disable V-Sync
present_mode: PresentMode::Immediate, // or PresentMode::Immediate
..default()
}),
..default()
}),
FpsOverlayPlugin {
config: FpsOverlayConfig {
text_config: TextFont {
// Here we define size of our overlay
font_size: 42.0,
// If we want, we can use a custom font
font: default(),
// We could also disable font smoothing,
font_smoothing: FontSmoothing::default(),
..default()
},
// We can also change color of the overlay
text_color: Color::srgb(1.0, 1.0, 1.0),
// We can also set the refresh interval for the FPS counter
refresh_interval: core::time::Duration::from_millis(100),
enabled: true,
frame_time_graph_config: FrameTimeGraphConfig {
enabled: true,
// The minimum acceptable fps
min_fps: 30.0,
// The target fps
target_fps: 1000.0,
},
},
},
PhysicsPlugins::default(),
// PhysicsDebugPlugin,
// SkyPlugin::default()
))
.add_systems(Startup, setup)
// .add_systems(Update, orbit_camera)
.add_systems(FixedUpdate, (flight_camera, apply_jump_force))
.add_systems(Update, (keyboard_input, camera_stabilizer, camera_fov_system, debug_text))
.add_message::<JumpEvent>()
.add_message::<ThrottleUpEvent>()
.add_message::<ThrottleDownEvent>()
.add_message::<ThrottleCutEvent>()
.add_message::<PullUpEvent>()
.add_message::<PullDownEvent>()
.add_message::<RollLeftEvent>()
.add_message::<RollRightEvent>()
.add_message::<YawLeftEvent>()
.add_message::<YawRightEvent>()
.run();
}
fn keyboard_input(
keys: Res<ButtonInput<KeyCode>>,
mut exit: MessageWriter<AppExit>,
mut jump_writer: MessageWriter<JumpEvent>,
mut throttle_up_writer: MessageWriter<ThrottleUpEvent>,
mut throttle_down_writer: MessageWriter<ThrottleDownEvent>,
mut throttle_cut_writer: MessageWriter<ThrottleCutEvent>,
mut pull_up_writer: MessageWriter<PullUpEvent>,
mut pull_down_writer: MessageWriter<PullDownEvent>,
mut yaw_left_writer: MessageWriter<YawLeftEvent>,
mut yaw_right_writer: MessageWriter<YawRightEvent>,
mut roll_left_writer: MessageWriter<RollLeftEvent>,
mut roll_right_writer: MessageWriter<RollRightEvent>,
mut query: Query<Forces, With<PlayerBody>>,
) {
if keys.just_pressed(KeyCode::Escape) {
exit.write(AppExit::Success);
}
if keys.just_pressed(KeyCode::Space) {
jump_writer.write(JumpEvent);
}
if keys.pressed(KeyCode::ShiftLeft) {
throttle_up_writer.write(ThrottleUpEvent);
}
if keys.pressed(KeyCode::ControlLeft) {
throttle_down_writer.write(ThrottleDownEvent);
}
if keys.pressed(KeyCode::KeyX) {
throttle_cut_writer.write(ThrottleCutEvent);
}
if keys.pressed(KeyCode::KeyS) {
pull_up_writer.write(PullUpEvent);
}
if keys.pressed(KeyCode::KeyW) {
pull_down_writer.write(PullDownEvent);
}
if keys.pressed(KeyCode::KeyA) {
yaw_left_writer.write(YawLeftEvent);
}
if keys.pressed(KeyCode::KeyD) {
yaw_right_writer.write(YawRightEvent);
}
if keys.pressed(KeyCode::KeyQ) {
roll_left_writer.write(RollLeftEvent);
}
if keys.pressed(KeyCode::KeyE) {
roll_right_writer.write(RollRightEvent);
}
}
#[derive(Message)]
struct JumpEvent;
#[derive(Message)]
struct ThrottleUpEvent;
#[derive(Message)]
struct ThrottleDownEvent;
#[derive(Message)]
struct ThrottleCutEvent;
#[derive(Message)]
struct PullUpEvent;
#[derive(Message)]
struct PullDownEvent;
#[derive(Message)]
struct RollLeftEvent;
#[derive(Message)]
struct RollRightEvent;
#[derive(Message)]
struct YawLeftEvent;
#[derive(Message)]
struct YawRightEvent;
fn apply_jump_force(
events: MessageReader<JumpEvent>,
mut query: Query<Forces, With<PlayerBody>>,
) {
if events.is_empty() {
return;
}
for mut forces in &mut query {
forces.apply_linear_impulse(Vec3::new(0.0, 1.0, 0.0));
}
}
#[derive(Component)]
struct OrbitCamera;
#[derive(Component)]
struct BentoText;
#[derive(Component)]
struct FlightCamera{
grounded: bool,
grounded_distance: f32,
max_fuel: f32,
fuel: f32,
consumption_rate: f32,
last_grounded: bool,
last_compression: f32,
throttle: f32,
pull_strength: f32,
roll_strength: f32,
yaw_strength: f32,
}
#[derive(Component)]
struct FollowCamera {
target: Entity,
lerp_factor: f32,
min_distance: f32, // Distance at 0 speed
max_distance: f32, // Distance at max speed
}
#[derive(Component)]
struct Wheel;
#[derive(Component)]
struct SpeedText;
impl FlightCamera {
fn default() -> Self {
let max_fuel = 80.0;
Self {
grounded: false,
grounded_distance: 0.0,
max_fuel,
fuel: max_fuel,
consumption_rate: 0.05,
last_grounded: false,
last_compression: 0.0,
throttle: 0.0,
pull_strength: 0.0,
roll_strength: 0.0,
yaw_strength: 0.0,
}
}
fn add_throttle(&mut self, amount: f32) {
self.throttle += amount;
self.throttle = clamp(self.throttle, 0.0, 1.0);
}
fn cut_throttle(&mut self) {
self.throttle = 0.0;
}
}
fn orbit_camera(
time: Res<Time>,
mut query: Query<&mut Transform, With<OrbitCamera>>,
) {
let speed = 0.5; // radians per second
for mut transform in &mut query {
// Rotate around Y axis at the origin
transform.rotate_around(
Vec3::ZERO,
Quat::from_rotation_y(speed * time.delta_secs()),
);
// Always look at the center
transform.look_at(Vec3::ZERO, Vec3::Y);
}
}
fn camera_fov_system(
plane_query: Query<&LinearVelocity, With<FlightCamera>>,
// Use mut here to allow modifying the projection
mut camera_query: Query<&mut Projection, With<FollowCamera>>,
) {
// 1. Get the plane's velocity (Using LinearVelocity directly is cleaner)
let Ok(velocity) = plane_query.single() else { return };
// 2. Get the camera's projection
let Ok(mut projection) = camera_query.single_mut() else { return };
if let Projection::Perspective(ref mut perspective) = *projection {
let speed = velocity.length(); // LinearVelocity has .length() directly in Avian
let min_fov = 90.0_f32.to_radians();
let max_fov = 100.0_f32.to_radians();
let speed_threshold = 55.0;
let speed_factor = (speed / speed_threshold).min(1.0);
let target_fov = min_fov + (max_fov - min_fov) * speed_factor;
perspective.fov = target_fov;
}
}
fn camera_stabilizer(
time: Res<Time>,
mut camera_query: Query<(&mut Transform, &FollowCamera)>,
target_query: Query<(&Transform, &LinearVelocity), (With<FlightCamera>, Without<FollowCamera>)>,
) {
for (mut cam_trans, follow) in &mut camera_query {
if let Ok((target_trans, velocity)) = target_query.get(follow.target) {
// 1. Calculate speed factor
let speed = velocity.length();
let speed_threshold = 55.0;
let speed_factor = (speed / speed_threshold).min(1.0);
// 2. Handle Rotation (Your existing logic)
let (yaw, pitch, _) = target_trans.rotation.to_euler(EulerRot::YXZ);
let target_rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch * 0.2, 0.0);
let rotation_speed = 5.0;
cam_trans.rotation = cam_trans.rotation.slerp(target_rotation, rotation_speed * time.delta_secs());
// 3. Dynamic Distance calculation
// Linearly interpolate between min and max distance based on speed
let dynamic_distance = follow.min_distance + (follow.max_distance - follow.min_distance) * speed_factor;
// 4. Position the camera using the dynamic distance
// We use the Y value 1.0 (height) and our new dynamic_distance for Z (depth)
let offset = cam_trans.rotation.mul_vec3(Vec3::new(0.0, 1.0, dynamic_distance));
cam_trans.translation = target_trans.translation + offset;
}
}
}
const M_TO_FT: f32 = 3.28084;
const MPS_TO_KT: f32 = 1.94384;
const ASPECT_RATIO: f32 = 7.0;
const OSWALD_E: f32 = 0.8;
const PLANE_MASS_EMPTY: f32 = 580.0;
const WHEEL_RADIUS: f32 = 0.25;
const GRAVITY: f32 = 9.81;
const GROUND_DISTANCE: f32 = 1.50;
const PLANE_BODY_HEIGHT: f32 = 1.0;
const WWEATHERVANE_PITCH_ENABLED: bool = true;
fn flight_camera(
time: Res<Time<Fixed>>,
throttle_up_events: MessageReader<ThrottleUpEvent>,
throttle_down_events: MessageReader<ThrottleDownEvent>,
throttle_cut_events: MessageReader<ThrottleCutEvent>,
pull_up_events: MessageReader<PullUpEvent>,
pull_down_events: MessageReader<PullDownEvent>,
roll_left_events: MessageReader<RollLeftEvent>,
roll_right_events: MessageReader<RollRightEvent>,
yaw_left_events: MessageReader<YawLeftEvent>,
yaw_right_events: MessageReader<YawRightEvent>,
mut query: Query<(Forces, &mut FlightCamera, &Transform, &mut Mass, &ComputedMass)>,
mut query_wheels: Query<(&Wheel, &mut Transform), Without<FlightCamera>>,
mut query_text: Query<Entity, With<SpeedText>>,
mut query_hits: Query<(&RayCaster, &RayHits)>,
mut writer: TextUiWriter,
mut gizmos: Gizmos,
) {
let dt = time.delta_secs();
// Throttle ---
if !throttle_up_events.is_empty() {
for (_, mut flight_camera, _, _, _) in &mut query {
flight_camera.add_throttle(0.005);
}
}
if !throttle_down_events.is_empty() {
for (_, mut flight_camera, _, _, _) in &mut query {
flight_camera.add_throttle(-0.005);
}
}
if !throttle_cut_events.is_empty() {
for (_, mut flight_camera, _, _, _) in &mut query {
flight_camera.cut_throttle();
}
}
const PULL_ACCELERATION: f32 = 1.0;
const ROLL_ACCELERATION: f32 = 1.0;
const YAW_ACCELERATION: f32 = 1.0;
if !pull_up_events.is_empty () {
for (mut forces, mut flight_camera, _, _, _) in &mut query {
let angular_acc = Vec3::new(PULL_ACCELERATION, 0.0, 0.0);
forces.apply_local_angular_acceleration(angular_acc);
}
}
if !pull_down_events.is_empty () {
for (mut forces, mut flight_camera, _, _, _) in &mut query {
let angular_acc = Vec3::new(-PULL_ACCELERATION, 0.0, 0.0);
forces.apply_local_angular_acceleration(angular_acc);
}
}
if !yaw_left_events.is_empty () {
for (mut forces, mut flight_camera, _, _, _) in &mut query {
let angular_acc = Vec3::new(0.0, YAW_ACCELERATION, 0.0);
forces.apply_local_angular_acceleration(angular_acc);
}
}
if !yaw_right_events.is_empty () {
for (mut forces, mut flight_camera, _, _, _) in &mut query {
let angular_acc = Vec3::new(0.0, -YAW_ACCELERATION, 0.0);
forces.apply_local_angular_acceleration(angular_acc);
}
}
if !roll_left_events.is_empty () {
for (mut forces, mut flight_camera, _, _, _) in &mut query {
let angular_acc = Vec3::new(0.0, 0.0, ROLL_ACCELERATION);
forces.apply_local_angular_acceleration(angular_acc);
}
}
if !roll_right_events.is_empty () {
for (mut forces, mut flight_camera, _, _, _) in &mut query {
let angular_acc = Vec3::new(0.0, 0.0, -ROLL_ACCELERATION);
forces.apply_local_angular_acceleration(angular_acc);
}
}
// -------
// thrust, drag
const MAX_THRUST: f32 = 1600.0;
for (mut forces, mut camera, transform, mut mass, computed_mass) in &mut query {
for (ray, hits) in &query_hits {
let mut hit_ground_flag = false;
for hit in hits.iter_sorted() {
// println!(
// "Hit entity {} at {} with normal {}",
// hit.entity,
// ray.origin + *ray.direction * hit.distance,
// hit.normal,
// );
if hit.distance <= GROUND_DISTANCE {
camera.grounded_distance = hit.distance;
hit_ground_flag = true;
}
}
camera.grounded = hit_ground_flag;
}
let mut thrust = camera.throttle * MAX_THRUST;
if camera.fuel <= 0.0 {
thrust = 0.0;
}
else {
camera.fuel -= camera.throttle * camera.consumption_rate * dt;
}
camera.fuel = camera.fuel.max(0.0);
mass.0 = PLANE_MASS_EMPTY + camera.fuel;
let forward = transform.forward();
let thrust_vector = forward * thrust;
forces.apply_force(thrust_vector);
let velocity = forces.linear_velocity();
let velocity_dir = velocity.normalize_or_zero();
let speed: f32 = velocity.length();
let forward = transform.forward();
let right = transform.right();
let sin = forward.cross(velocity_dir).dot(right.as_vec3());
let cos = forward.dot(velocity_dir);
let aoa_rad = -sin.atan2(cos);
let aoa_deg = aoa_rad.to_degrees();
let grounded_dist = camera.grounded_distance;
let gravity_force: f32 = mass.0 * GRAVITY;
let gravity_vector = Vec3::new(0.0, -gravity_force, 0.0);
forces.apply_force(gravity_vector);
// suspension
if camera.grounded {
let rest_length = GROUND_DISTANCE * 0.8;
let compression = (rest_length - grounded_dist).max(0.0);
// 2. Damping (C)
// Use the actual velocity of the body projected onto the spring axis (up/down)
let velocity = forces.linear_velocity();
// Empirically, I found that 14.793103448 is a good multiplier for the spring
// damping, relative to plane mass.
let magic_damping_coefficent_multiplier = 14.793103448;
let damping_coefficient = PLANE_MASS_EMPTY * magic_damping_coefficent_multiplier;
let damping_force = -velocity.y * damping_coefficient;
// 1. Stiffness (K)
// Empirically, found that a stiffness of 15.625 * the damping coeffiicent is a good number,
// independent of plane mass.
let magic_stiffness_multiplier = 15.625;
let stiffness = damping_coefficient * magic_stiffness_multiplier;
let spring_force = compression * stiffness;
// 3. Total Force
// We add a "Spring Law" force.
// We also ensure we don't pull the plane DOWN if the damping is too high
let total_force_magnitude = (spring_force + damping_force).max(0.0);
// Apply force at the raycast origin (or just to the body for now)
forces.apply_force(Vec3::Y * total_force_magnitude);
// Update wheel visual
for (_wheel, mut wheel_transform) in &mut query_wheels {
let target_y = -grounded_dist - WHEEL_RADIUS * 0.5;
let smoothness = 0.45;
wheel_transform.translation.y =
wheel_transform.translation.y.lerp(target_y, smoothness);
}
camera.last_compression = compression;
} else {
camera.last_compression = 0.0;
}
if camera.grounded && !camera.last_grounded {
camera.last_grounded = true;
}
// Fd = 0.5 * p * u^2 * cd * A
// Drag Force = 0.5 * mass density * flow velocity^2 * drag coefficient * Area
let p: f32 = 1.2041;
let u_squared: f32 = speed*speed;
let cd0: f32 = 0.55;
let cl = match aoa_deg {
d if d < 15.0 => d / 15.0 * 1.0 + 0.5,
d if d < 20.0 => 1.2 * (1.0 - (d - 15.0) / 5.0),
_ => 0.2, // stalled
};
// add induced drag to the standard drag coefficient (cd)
// Induced drag generated in the real world as a byproduct of generating lift.
// More lift = more drag.
let cd = cd0 + cl*cl / (std::f32::consts::PI * ASPECT_RATIO * OSWALD_E);
let area = 1.6;
let drag_force = 0.5 * p * u_squared * cd * area;
let move_dir = forces.linear_velocity().normalize_or_zero();
let drag_vector = -move_dir * drag_force;
forces.apply_force(-move_dir * drag_force);
let forward = transform.forward();
let right = transform.right();
let up = transform.up();
// Calculate velocity components
let side_speed = velocity.dot(right.as_vec3());
// 1. Kill "Skidding" (Lateral Drag)
// This force pushes back against any sideways movement.
// The higher the multiplier, the more the plane "carves" through the air.
let lateral_friction_strength = 5.0;
let side_drag_vector = -right.as_vec3() * side_speed * lateral_friction_strength * speed;
forces.apply_force(side_drag_vector);
// Weathervane effect -- rotate towards moving direction
if speed > 2.0 {
let velocity_dir = velocity.normalize();
let forward = transform.forward().as_vec3();
// 1. Find the rotation difference
let stability_error = forward.cross(velocity_dir);
// 2. Convert the error into LOCAL space so we can isolate Pitch
// We transform the world-space error vector by the inverse of the plane's rotation
let local_error = transform.rotation.inverse().mul_vec3(stability_error);
// 3. ZERO OUT the Pitch (X) component
// This ensures the weathervane effect doesn't try to pull the nose up or down
let stabilized_local_error = Vec3::new(if WWEATHERVANE_PITCH_ENABLED {local_error.x} else {0.0}, local_error.y, local_error.z);
// 4. Define the strength
let snap_intensity = 0.0015;
let stability_accel = stabilized_local_error * speed * speed * snap_intensity;
// 5. Apply as LOCAL angular acceleration
forces.apply_local_angular_acceleration(stability_accel);
}
// L = Cl * p * (v^2/2) * A
// Lift = coefficient * density * (airspeed^2 / 2) * wing area
let wing_area = 15.0;
let dot_air = transform.forward().dot(move_dir);
let dot_air_clamped = clamp(dot_air, 0.0, 1.0);
let airspeed = dot_air_clamped * forces.linear_velocity().length();
let lift_force = cl * p * ((airspeed*airspeed)*0.5) * wing_area;
let lift_dir = transform.up();
let lift_vector = lift_dir * lift_force;
forces.apply_force(lift_vector);
let (pitch, yaw, roll) = forces.rotation().to_euler(EulerRot::XYZ);
let pitch_deg = pitch.to_degrees();
let yaw_deg = yaw.to_degrees();
let roll_deg = roll.to_degrees();
let grounded = camera.grounded;
let total_mass = computed_mass.value();
for entity in &mut query_text {
*writer.text(entity, 2) =
format!("Drag force: {drag_force:.4}N\n");
*writer.text(entity, 4) =
format!("Pitch: {pitch_deg:.1}o Roll: {roll_deg:.1}o Yaw: {yaw_deg:.1}o\nAoA: {aoa_deg:.1}o\nTotal mass: {total_mass:.1}kg\n");
*writer.text(entity, 6) =
format!("Lift force: {lift_force:.4}N\n");
*writer.text(entity, 7) =
format!("grounded: {grounded}\n");
}
const GIZMOS_LENGTH_MULTIPLIER: f32 = 0.0005;
// gravity gizmo
gizmos.arrow(
transform.translation,
transform.translation + gravity_vector * GIZMOS_LENGTH_MULTIPLIER,
Color::linear_rgb(0.5, 0.5, 0.5),
);
// movement gizmo
gizmos.arrow(
transform.translation,
transform.translation + forces.linear_velocity().normalize_or_zero() * 2.0,
Color::linear_rgb(0.0, 1.0, 0.0),
);
// thrust gizmo
gizmos.arrow(
transform.translation,
transform.translation + thrust_vector *GIZMOS_LENGTH_MULTIPLIER,
Color::linear_rgb(0.4, 0.0, 0.8),
);
// lift gizmo
gizmos.arrow(
transform.translation,
transform.translation + lift_vector *GIZMOS_LENGTH_MULTIPLIER,
Color::linear_rgb(0.0, 0.5, 0.5),
);
// side resistance gizmo
gizmos.arrow(
transform.translation,
transform.translation + side_drag_vector *GIZMOS_LENGTH_MULTIPLIER,
Color::linear_rgb(0.7, 0.15, 0.0),
);
// drag gizmo
gizmos.arrow(
transform.translation,
transform.translation + drag_vector *GIZMOS_LENGTH_MULTIPLIER,
Color::linear_rgb(1.0, 0.0, 0.0),
);
// true horizon gizmo
gizmos.circle(
Isometry3d::new(transform.translation, Quat::from_rotation_x(90.0_f32.to_radians())),
100.0,
Color::linear_rgba(1.0, 1.0, 1.0, 0.02),
);
}
// -----
}
fn debug_text(
mut query: Query<Entity, With<SpeedText>>,
mut flight_camera_query: Query<(Forces, &FlightCamera, &mut Transform)>,
mut writer: TextUiWriter,
) {
for entity in &mut query {
for (force, camera, transform) in &mut flight_camera_query {
let speed = force.linear_velocity().length();
let speed_kt = speed * MPS_TO_KT;
let descent = force.linear_velocity().y;
let descent_fpm = descent * M_TO_FT * 60.0;
let throttle = camera.throttle * 100.0;
let fuel = camera.fuel;
let max_fuel = camera.max_fuel;
let time = Local::now().format("%H:%M:%S").to_string();
let (x, y, z) = (
transform.translation.x,
transform.translation.y,
transform.translation.z
);
let altitude = y * M_TO_FT;
let mut fuel_time_left_secs = camera.fuel / (1.0 * camera.consumption_rate);
if camera.throttle > 0.01 {
fuel_time_left_secs = camera.fuel / (camera.throttle * camera.consumption_rate);
}
// Calculate formatting components
let hours = (fuel_time_left_secs / 3600.0) as u32;
let minutes = ((fuel_time_left_secs % 3600.0) / 60.0) as u32;
let seconds = fuel_time_left_secs % 60.0; // Keep as f32 for the trailing digit
// :04.1 means: 4 characters wide total, padded with 0, 1 decimal place.
// This prevents the text from "shaking" when seconds go from 9.9 to 10.0
let fuel_timer = format!("{:02}:{:02}:{:04.1}", hours, minutes, seconds);
*writer.text(entity, 0) =
format!("Speed: {speed_kt:.2}kt | {speed:.1}m/s\nAltitude: {altitude:.0}ft | Climb: {descent_fpm:.0}ft/min | {descent:.1}m/s\n",);
*writer.text(entity, 1) =
format!("Throttle: {throttle:.2}% | Fuel: {fuel:.1}kg/{max_fuel:.1}kg | Fuel time: {fuel_timer}\n");
*writer.text(entity, 3) =
format!("Time: {time}\n");
*writer.text(entity, 5) =
format!("XYZ: {x:.3} / {y:.3} / {z:.3}\n");
}
}
}
#[derive(Component)]
struct PlayerBody;
/// set up a simple 3D scene
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// runway1
commands.spawn((
RigidBody::Static,
SweptCcd::new_with_mode(SweepMode::NonLinear),
Friction::new(0.0),
ColliderConstructor::ConvexHullFromMesh,
Mesh3d(meshes.add(Cuboid::new(40.0, 2.0, 2400.0))),
MeshMaterial3d(materials.add(Color::linear_rgb(0.42, 0.42, 0.45))),
Transform {
translation: Vec3::new(0.0, 0.0, -1195.0),
..default()
},
));
// runway2
commands.spawn((
RigidBody::Static,
SweptCcd::new_with_mode(SweepMode::NonLinear),
Friction::new(0.0),
ColliderConstructor::ConvexHullFromMesh,
Mesh3d(meshes.add(Cuboid::new(40.0, 2.0, 2400.0))),
MeshMaterial3d(materials.add(Color::linear_rgb(0.13, 0.13, 0.16))),
Transform {
translation: Vec3::new(2000.0, 0.0, -8000.0),
rotation: Quat::from_rotation_y(-22.5_f32.to_radians()),
..default()
},
));
// cube
commands.spawn((
RigidBody::Dynamic,
Collider::cuboid(1.0, 1.0, 1.0),
PlayerBody,
Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),
AngularVelocity(Vec3::new(2.5, 3.5, 1.5)),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.5, 0.1, 0.2),
..default()
})),
Transform::from_xyz(5.0, 15.0, -10.0),
));
// light
commands.spawn((
PointLight {
shadows_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, -4.0),
));
// directional light
commands.spawn((
DirectionalLight {
illuminance: 1_200.0,
shadows_enabled: true,
..default()
},
Transform::from_xyz(20000.0, 100000.0, 0.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
));
// plane
let aircraft_id = commands.spawn((
RigidBody::Dynamic,
SweptCcd::new_with_mode(SweepMode::NonLinear),
AngularDamping(1.6),
ColliderConstructor::ConvexHullFromMesh,
Mesh3d(meshes.add(Cuboid::new(1.0, PLANE_BODY_HEIGHT, 2.0))),
Friction::new(0.0),
FlightCamera::default(),
GravityScale(0.0),
Mass(PLANE_MASS_EMPTY),
Transform::from_xyz(0.0, 10.0, 0.0),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgba(0.1, 0.1, 0.5, 0.75),
alpha_mode: AlphaMode::Blend,
..default()
})),
)).id();
commands.spawn((
Camera3d::default(),
// SkyboxMagnetTag,
Projection::from(PerspectiveProjection {
fov: 95.0_f32.to_radians(),
..default()
}),
FollowCamera {
target: aircraft_id,
lerp_factor: 0.1,
min_distance: 3.0,
max_distance: 4.0,
},
));
let wheel_y: f32 = -(PLANE_BODY_HEIGHT/2.0) - GROUND_DISTANCE;
commands.entity(aircraft_id).with_children( |p| {
// p.spawn((
// Camera3d::default(),
// Projection::from(PerspectiveProjection {
// fov: 95.0_f32.to_radians(),
// ..default()
// }),
// ));
p.spawn((
RayCaster::new(Vec3::ZERO, Dir3::NEG_Y)
.with_query_filter(
SpatialQueryFilter::default().with_excluded_entities([aircraft_id])
),
Transform::from_xyz(0.0, -(PLANE_BODY_HEIGHT/2.0), 0.0),
));
p.spawn((
Mesh3d(meshes.add(Cylinder::new(WHEEL_RADIUS, 0.1))),
Transform {
translation: Vec3::new(0.5, wheel_y, 0.25),
rotation: Quat::from_rotation_z(90.0_f32.to_radians()),
..default()
},
Wheel,
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgba(0.05, 0.05, 0.06, 0.75),
alpha_mode: AlphaMode::Blend,
..default()
})),
));
p.spawn((
Mesh3d(meshes.add(Cylinder::new(WHEEL_RADIUS, 0.1))),
Transform {
translation: Vec3::new(-0.5, wheel_y, 0.25),
rotation: Quat::from_rotation_z(-90.0_f32.to_radians()),
..default()
},
Wheel,
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgba(0.05, 0.05, 0.06, 0.75),
alpha_mode: AlphaMode::Blend,
..default()
})),
));
p.spawn((
Mesh3d(meshes.add(Cylinder::new(WHEEL_RADIUS, 0.1))),
Transform {
translation: Vec3::new(0.0, wheel_y, -0.25),
rotation: Quat::from_rotation_z(90.0_f32.to_radians()),
..default()
},
Wheel,
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgba(0.05, 0.05, 0.06, 0.75),
alpha_mode: AlphaMode::Blend,
..default()
})),
));
});
// text
commands.spawn((
Text::new("Shouldnt show up like this\n"),
SpeedText,
TextLayout::new_with_justify(Justify::Right),
Node {
position_type: PositionType::Absolute,
bottom: px(5),
right: px(5),
..default()
}
))
.with_children( |p| {
p.spawn((
TextSpan::new("This is the throttle text -- shouldnt show up like this"),
));
})
.with_children( |p| {
p.spawn((
TextSpan::new("This is the drag text -- shouldnt show up like this"),
));
})
.with_children( |p| {
p.spawn((
TextSpan::new("This is the clock text -- shouldnt show up like this"),
));
})
.with_children( |p| {
p.spawn((
TextSpan::new("This is the angles text -- shouldnt show up like this"),
));
})
.with_children( |p| {
p.spawn((
TextSpan::new("This is the positions text -- shouldnt show up like this"),
));
})
.with_children( |p| {
p.spawn((
TextSpan::new("This is the lift force text -- shouldnt show up like this"),
));
})
.with_children( |p| {
p.spawn((
TextSpan::new("This is the grounded text -- shouldnt show up like this"),
));
});
// Bento text
commands.spawn((
Text::new("Bento text -- Shouldnt show up like this\n"),
BentoText,
TextLayout::new_with_justify(Justify::Right),
Node {
position_type: PositionType::Absolute,
bottom: px(5),
left: px(5),
..default()
}
));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment