rustial-renderer-bevy 0.0.1

Bevy Engine renderer for the rustial 2.5D map engine
//! # Geographic entity positioning system
//!
//! Keeps user-spawned Bevy entities pinned to their geographic
//! coordinates as the map camera pans, zooms, and rotates.
//!
//! ## Usage
//!
//! Attach [`MapEntity`] + [`GeoTransform`] to any Bevy entity:
//!
//! ```rust,ignore
//! commands.spawn((
//!     MapEntity,
//!     GeoTransform::from_lat_lon(48.8566, 2.3522), // Paris
//!     Transform::default(),
//!     Visibility::default(),
//!     // ... mesh, sprite, etc.
//! ));
//! ```
//!
//! Each frame this system rewrites `Transform.translation` so the
//! entity stays at the correct screen position.  User-supplied
//! rotation and scale on the `Transform` are **preserved** -- only
//! the translation is overwritten.
//!
//! ## Coordinate pipeline
//!
//! ```text
//! GeoCoord (lat/lon/alt)
//!     |
//!     |  WebMercator::project()
//!     v
//! WorldCoord (x, y, z)  -- metres, absolute
//!     |
//!     |  subtract camera_origin
//!     v
//! camera-relative (f32)  -- small values, no jitter
//! ```
//!
//! The camera-relative model is shared by all sync systems (tiles,
//! terrain, vectors, models).  See the
//! [camera_sync](super::camera_sync) module docs for the full
//! explanation.
//!
//! ## Altitude modes
//!
//! | [`GeoAltitudeMode`] | Z value |
//! |----------------------|---------|
//! | `ClampToGround` | terrain elevation (or 0 when unavailable) |
//! | `RelativeToGround` | terrain elevation + `GeoCoord.alt` |
//! | `Absolute` | `GeoCoord.alt` directly |
//!
//! Terrain elevation is queried via
//! [`MapState::elevation_at`](rustial_engine::MapState::elevation_at).
//! When terrain is disabled or the tile covering the coordinate has
//! not loaded yet, the query returns `None` and the system falls back
//! to ground = 0 m.
//!
//! ## Scheduling
//!
//! Registered in [`Update`](bevy::prelude::Update) so it runs after
//! the camera and engine state have been synchronised in `PreUpdate`.

use crate::components::{GeoAltitudeMode, GeoTransform, MapEntity};
use crate::plugin::MapStateResource;
use bevy::prelude::*;

/// Reposition every [`MapEntity`] to camera-relative coordinates each
/// frame.
///
/// Iterates all entities carrying both [`MapEntity`] and
/// [`GeoTransform`], projects the geographic coordinate to Web
/// Mercator world space, subtracts the camera origin to get an f32
/// camera-relative position, and resolves the Z component according
/// to the entity's [`GeoAltitudeMode`].
///
/// Only `Transform.translation` is written -- rotation and scale set
/// by the user (or other systems) are left untouched.
pub fn sync_geo_entities(
    state: Res<MapStateResource>,
    mut query: Query<(&GeoTransform, &mut Transform), With<MapEntity>>,
) {
    let camera_origin = state.0.scene_world_origin();
    let projection = state.0.camera().projection();

    for (geo, mut transform) in query.iter_mut() {
        // -- Horizontal position (X / Y) ----------------------------------
        // Project lat/lon to Web Mercator metres and subtract the camera
        // origin so the result is small enough for f32.
        let world = projection.project(&geo.coord);

        let x = (world.position.x - camera_origin.x) as f32;
        let y = (world.position.y - camera_origin.y) as f32;

        // -- Vertical position (Z) ----------------------------------------
        // Resolve altitude according to the mode.  The terrain query may
        // return `None` if terrain is disabled or the elevation tile has
        // not loaded yet; in that case ground = 0.
        let z = match geo.altitude_mode {
            GeoAltitudeMode::ClampToGround => {
                // Place directly on the terrain surface.
                let ground = state.0.elevation_at(&geo.coord).unwrap_or(0.0);
                ground as f32
            }
            GeoAltitudeMode::RelativeToGround => {
                // Offset above the terrain surface by `coord.alt` metres.
                let ground = state.0.elevation_at(&geo.coord).unwrap_or(0.0);
                (ground + geo.coord.alt) as f32
            }
            GeoAltitudeMode::Absolute => {
                // Use the coordinate's altitude directly (world-space metres).
                geo.coord.alt as f32
            }
        };

        transform.translation = Vec3::new(x, y, z);
    }
}