use bevy::prelude::*;
use bevy_a5::camera::FlyCam;
use bevy_a5::coord::tangent_frame;
use bevy_a5::geometry::build_grid_line_mesh;
use bevy_a5::prelude::*;
use bevy_a5::WORLD_CELL;
use bevy_math::DVec3;
const PLANET_RADIUS: f64 = 100.0;
const RESOLUTION: i32 = 5;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(BevyA5Plugins)
.insert_resource(PlanetSettings::earth().with_radius(PLANET_RADIUS))
.add_systems(Startup, setup)
.add_systems(Update, update_hud)
.run();
}
#[derive(Component)]
struct Hud;
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Mesh3d(meshes.add(Sphere::new(PLANET_RADIUS as f32 * 0.99))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::linear_rgb(0.05, 0.10, 0.20),
..default()
})),
Transform::default(),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 0.0, PLANET_RADIUS as f32 * 2.5)
.looking_at(Vec3::ZERO, Vec3::Y),
FlyCam {
speed: 80.0,
boost: 4.0,
sensitivity: 0.003,
mouse_look: true,
},
));
commands.spawn((
DirectionalLight {
illuminance: 18_000.0,
shadows_enabled: false,
..default()
},
Transform::from_xyz(300.0, 500.0, 300.0).looking_at(Vec3::ZERO, Vec3::Y),
));
let world = GeoCell::new(WORLD_CELL);
let cells = world.children(RESOLUTION).unwrap_or_default();
let grid = build_grid_line_mesh(&cells, PLANET_RADIUS);
commands.spawn((
Mesh3d(meshes.add(grid)),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: Color::srgb(0.0, 1.0, 0.75),
emissive: LinearRgba::new(0.0, 1.8, 1.2, 1.0),
unlit: true,
..default()
})),
));
commands.spawn((
Hud,
Text::new(""),
TextFont {
font_size: 14.0,
..default()
},
TextColor(Color::WHITE),
Node {
position_type: PositionType::Absolute,
top: Val::Px(8.0),
left: Val::Px(12.0),
padding: UiRect::axes(Val::Px(10.0), Val::Px(8.0)),
..default()
},
BackgroundColor(Color::linear_rgba(0.0, 0.0, 0.0, 0.65)),
));
info!(
"A5 planet grid ready — {} cells at resolution {} on a sphere of radius {}",
cells.len(),
RESOLUTION,
PLANET_RADIUS,
);
info!("Controls: WASD to fly, Q/E down/up, Shift to boost, mouse to look (Esc releases cursor)");
}
fn update_hud(
camera_q: Query<&Transform, With<FlyCam>>,
mut hud_q: Query<&mut Text, With<Hud>>,
) {
let Ok(transform) = camera_q.single() else {
return;
};
let Ok(mut text) = hud_q.single_mut() else {
return;
};
let world_pos = transform.translation;
let world_pos_d = world_pos.as_dvec3();
let cell = GeoCell::from_world_pos(world_pos_d, RESOLUTION);
let cell_str = cell
.map(|c| format!("0x{:016x} (res {})", c.raw(), c.resolution()))
.unwrap_or_else(|| "n/a".to_string());
let ll = bevy_a5::coord::dvec3_to_lonlat(world_pos_d);
let (lon_str, lat_str, local_heading_str) = match ll {
Some(ll) => {
let lon = ll.longitude();
let lat = ll.latitude();
let (east, up, north) = tangent_frame(&ll);
let fwd_world: DVec3 = Vec3::from(transform.forward()).as_dvec3();
let fwd_tangent = fwd_world - up * fwd_world.dot(up);
let bearing_str = if fwd_tangent.length_squared() < 1e-12 {
"— (looking straight up/down)".to_string()
} else {
let n = fwd_tangent.dot(north);
let e = fwd_tangent.dot(east);
let mut deg = e.atan2(n).to_degrees();
if deg < 0.0 {
deg += 360.0;
}
format!("{:>6.1}° ({})", deg, compass_label(deg))
};
(
format!("{:>9.4}°", lon),
format!("{:>9.4}°", lat),
bearing_str,
)
}
None => (" n/a".into(), " n/a".into(), "n/a".into()),
};
let altitude = world_pos_d.length() - PLANET_RADIUS;
let fwd = Vec3::from(transform.forward());
text.0 = format!(
"Camera\n\
\n\
Cell: {}\n\
\n\
Local heading: {}\n\
Global heading: ({:>6.3}, {:>6.3}, {:>6.3})\n\
\n\
Position:\n\
World XYZ: ({:>8.2}, {:>8.2}, {:>8.2})\n\
Lon / Lat: {} / {}\n\
Altitude: {:>8.2}\n\
\n\
WASD/QE fly Shift boost Mouse look Esc release",
cell_str,
local_heading_str,
fwd.x,
fwd.y,
fwd.z,
world_pos.x,
world_pos.y,
world_pos.z,
lon_str,
lat_str,
altitude,
);
}
fn compass_label(deg: f64) -> &'static str {
let bins = [
(22.5, "N"),
(67.5, "NE"),
(112.5, "E"),
(157.5, "SE"),
(202.5, "S"),
(247.5, "SW"),
(292.5, "W"),
(337.5, "NW"),
];
for (limit, label) in bins {
if deg < limit {
return label;
}
}
"N"
}