use crate::_bevy::*;
use avian3d::prelude::{
ComputedCenterOfMass, ComputedMass, ConstantForce, ConstantTorque, Rotation,
};
use super::configuration::{FdmDebugRender, FdmGizmos};
use crate::components::{
AeroZone, AircraftGeometry, Failure, FlightState, GizmoContours, GizmoShape, ZoneForce,
};
pub(super) fn debug_render_cg(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<(&GlobalTransform, &Rotation, &ComputedCenterOfMass), With<AircraftGeometry>>,
) {
let config = store.config::<FdmGizmos>().1;
let Some(color) = config.cg_color else { return };
for (gt, rot, com) in &query {
let cg = gt.translation() + rot.0 * com.0;
gizmos.sphere(
Isometry3d::from_translation(cg),
config.marker_radius,
color,
);
}
}
pub(super) fn debug_render_ac(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<(&GlobalTransform, &AeroZone)>,
) {
let config = store.config::<FdmGizmos>().1;
let Some(color) = config.ac_color else { return };
let arm = config.marker_radius;
for (gt, zone) in &query {
let ac = gt.transform_point(zone.ac_offset);
gizmos.line(ac - Vec3::X * arm, ac + Vec3::X * arm, color);
gizmos.line(ac - Vec3::Y * arm, ac + Vec3::Y * arm, color);
gizmos.line(ac - Vec3::Z * arm, ac + Vec3::Z * arm, color);
}
}
pub(super) fn debug_render_zone_forces(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<(&ZoneForce, &GlobalTransform, &AeroZone)>,
) {
let config = store.config::<FdmGizmos>().1;
let Some(color) = config.lift_color else {
return;
};
for (zf, zone_gt, zone) in &query {
if zf.force.length_squared() < 100.0 {
continue;
}
let start = zone_gt.transform_point(zone.ac_offset);
gizmos.arrow(start, start + zf.force * config.force_scale, color);
}
}
pub(super) fn debug_render_thrust(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<(&ZoneForce, &GlobalTransform), With<crate::components::EngineZone>>,
) {
let config = store.config::<FdmGizmos>().1;
let Some(color) = config.thrust_color else {
return;
};
for (zf, zone_gt) in &query {
if zf.force.length_squared() < 1.0 {
continue;
}
let start = zone_gt.translation();
gizmos.arrow(start, start + zf.force * config.force_scale, color);
}
}
pub(super) fn debug_render_resultant(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<
(
&Transform,
&Rotation,
&ConstantForce,
&ComputedMass,
&ComputedCenterOfMass,
),
With<AircraftGeometry>,
>,
) {
let config = store.config::<FdmGizmos>().1;
for (tf, rot, cf, mass, com) in &query {
let cg = tf.translation + rot.0 * com.0;
let scale = config.force_scale;
if let Some(color) = config.total_force_color {
gizmos.arrow(cg, cg + cf.0 * scale, color);
}
let weight = Vec3::new(0.0, -mass.value() * 9.806_65, 0.0);
if let Some(color) = config.weight_color {
gizmos.arrow(cg, cg + weight * scale, color);
}
if let Some(net_color) = config.resultant_color {
let net = cf.0 + weight;
if net.length_squared() > 1.0 {
gizmos.arrow(cg, cg + net * scale, net_color);
}
}
}
}
pub(super) fn debug_render_moments(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<
(
&Transform,
&Rotation,
&ComputedCenterOfMass,
&ConstantTorque,
),
With<AircraftGeometry>,
>,
) {
let config = store.config::<FdmGizmos>().1;
for (tf, rot, com, torque) in &query {
let cg = tf.translation + rot.0 * com.0;
let scale = config.force_scale;
let t = torque.0;
if t.length_squared() < 1.0 {
continue;
}
let body_x = rot.0 * Vec3::X; let body_y = rot.0 * Vec3::Y; let body_z = rot.0 * Vec3::Z;
let t_roll = t.dot(body_x);
let t_pitch = t.dot(body_y);
let t_yaw = t.dot(body_z);
if let Some(color) = config.roll_moment_color {
if t_roll.abs() > 0.1 {
gizmos.arrow(cg, cg + body_x * t_roll * scale, color);
}
}
if let Some(color) = config.pitch_moment_color {
if t_pitch.abs() > 0.1 {
gizmos.arrow(cg, cg + body_y * t_pitch * scale, color);
}
}
if let Some(color) = config.yaw_moment_color {
if t_yaw.abs() > 0.1 {
gizmos.arrow(cg, cg + body_z * t_yaw * scale, color);
}
}
}
}
#[allow(clippy::unnecessary_cast)]
#[allow(clippy::type_complexity)]
pub(super) fn debug_render_zones(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<
(
&GlobalTransform,
Option<&AeroZone>,
Option<&GizmoContours>,
Option<&GizmoShape>,
Option<&FdmDebugRender>,
Option<&Failure>,
),
Or<(With<GizmoShape>, With<GizmoContours>)>,
>,
) {
let config = store.config::<FdmGizmos>().1;
if config.zone_color.is_none() {
return;
}
for (zone_gt, aero, contours, shape, dbg_render, failure) in &query {
let base_color = dbg_render
.and_then(|d| d.zone_color)
.unwrap_or_else(|| zone_type_color(aero));
let remaining = failure.map_or(1.0, |f| f.remaining as f32);
let color = damage_tint(base_color, remaining);
let wt = zone_gt.compute_transform();
let zone_to_world = |local: Vec3| wt.translation + wt.rotation * local;
let iso_at = |extra_rot: Quat| Isometry3d::new(wt.translation, wt.rotation * extra_rot);
if let Some(contour_data) = contours {
for line in &contour_data.lines {
if line.len() < 2 {
continue;
}
let pts: Vec<Vec3> = line.iter().map(|p| zone_to_world(*p)).collect();
gizmos.linestrip(pts, color);
}
if shape.is_none() {
continue;
}
}
if let Some(gs) = shape {
draw_zone_shape(&mut gizmos, gs, &iso_at, &zone_to_world, color);
}
}
}
fn zone_type_color(aero: Option<&AeroZone>) -> Color {
if aero.is_none() {
Color::srgba(0.95, 0.65, 0.15, 0.9) } else if aero.is_some_and(|a| a.control_role.is_some()) {
Color::srgba(0.3, 1.0, 0.5, 0.7) } else if aero.is_some_and(|a| a.cl.evaluate(0.1, 2e6).abs() > 0.01) {
Color::srgba(0.4, 0.85, 1.0, 0.7) } else {
Color::srgba(0.6, 0.6, 0.6, 0.6) }
}
fn damage_tint(color: Color, remaining: f32) -> Color {
let grey = Color::srgba(0.55, 0.55, 0.55, 0.6);
color.mix(&grey, 1.0 - remaining)
}
fn draw_zone_shape(
gizmos: &mut Gizmos<FdmGizmos>,
shape: &GizmoShape,
iso_at: &impl Fn(Quat) -> Isometry3d,
zone_to_world: &impl Fn(Vec3) -> Vec3,
color: Color,
) {
match shape {
GizmoShape::Box { x, y, z } => {
gizmos.primitive_3d(&Cuboid::new(*x, *y, *z), iso_at(Quat::IDENTITY), color);
}
GizmoShape::Cylinder {
radius,
length,
axis,
} => {
gizmos
.primitive_3d(
&Cylinder::new(*radius, *length),
iso_at(Quat::from_rotation_arc(Vec3::Y, *axis)),
color,
)
.resolution(32);
}
GizmoShape::Cone { radius, length } => {
gizmos.primitive_3d(
&Cone {
radius: *radius,
height: *length,
},
iso_at(Quat::from_rotation_z(-std::f32::consts::FRAC_PI_2)),
color,
);
}
GizmoShape::Sphere { radius } => {
gizmos
.primitive_3d(
&bevy_math::primitives::Sphere::new(*radius),
iso_at(Quat::IDENTITY),
color,
)
.resolution(32);
}
GizmoShape::Quad { corners } => {
let pts: Vec<Vec3> = corners
.iter()
.chain(std::iter::once(&corners[0]))
.map(|c| zone_to_world(*c))
.collect();
gizmos.linestrip(pts, color);
}
GizmoShape::Strut { start, end } => {
gizmos.line(zone_to_world(*start), zone_to_world(*end), color);
}
}
}
#[allow(clippy::unnecessary_cast)]
pub(super) fn debug_render_wind(
mut gizmos: Gizmos<FdmGizmos>,
store: Res<GizmoConfigStore>,
query: Query<
(&Transform, &Rotation, &ComputedCenterOfMass, &FlightState),
With<AircraftGeometry>,
>,
) {
let config = store.config::<FdmGizmos>().1;
let Some(color) = config.wind_color else {
return;
};
for (tf, rot, com, fs) in &query {
if fs.airspeed_ms < 1.0 {
continue;
}
let cg = tf.translation + rot.0 * com.0;
let nose_dir = rot.0 * Vec3::X;
let (sa, ca) = (fs.alpha_rad.sin() as f32, fs.alpha_rad.cos() as f32);
let (sb, cb) = (fs.beta_rad.sin() as f32, fs.beta_rad.cos() as f32);
let vel_body_dir = Vec3::new(ca * cb, sb, sa * cb); let vel_world_dir = rot.0 * vel_body_dir;
let arm = 2.0_f32;
gizmos.arrow(cg, cg - vel_world_dir * arm, color);
gizmos.arrow(cg, cg + nose_dir * arm, color.with_alpha(0.5));
}
}