bevy_a5 0.1.2

A Bevy plugin providing A5 geospatial pentagonal cells for floating origin use and spatial queries
Documentation
//! # bevy_a5
//!
//! A Bevy plugin providing [A5](https://github.com/felixpalmer/a5-rs) geospatial
//! pentagonal cell coordinates for planetary games.
//!
//! `bevy_a5` is to **spherical / planetary** games what
//! [`big_space`](https://docs.rs/big_space) is to deep-space games — the same
//! floating-origin idea, a different cell space, with A5-specific spatial
//! queries on top.
//!
//! ## What it provides
//!
//! 1. **Cell-anchored coordinates.** Position entities as `(GeoCell, Transform)`
//!    — *which* A5 cell, plus a small offset within that cell. The
//!    `FloatingOrigin`-tagged entity (your camera) defines the world's
//!    reference cell; everyone else's `GlobalTransform` is computed relative to
//!    it. When the origin's local `Transform` exceeds its
//!    [`FloatingOrigin::recenter_threshold`](origin::FloatingOrigin::recenter_threshold), the plugin hops to the
//!    neighbouring cell automatically.
//!
//! 2. **Spatial queries on the sphere.** [`grid_disk`](query::grid_disk),
//!    [`neighbors`](query::neighbors), [`spherical_cap`](query::spherical_cap),
//!    parent/child/compact/uncompact — none of which big_space has, because A5
//!    cells tile a sphere.
//!
//! 3. **Display helpers.** [`build_grid_line_mesh`](geometry::build_grid_line_mesh)
//!    builds a ready-to-use `Mesh` for a slice of cells in one call;
//!    [`DrawCellOutline`](debug::DrawCellOutline) is a marker component that
//!    gizmos a cell's pentagon every frame.
//!
//! ## Cell-local frame convention
//!
//! Within a cell, the local frame is **vertex-aligned**: the cell centre is
//! `(0, 0, 0)`, `+Y` is radial-up, and `-Z` (Bevy "forward") points from the
//! centre to the cell's first boundary vertex. So a child entity at
//! `Transform::from_xyz(0.0, 0.0, -d)` sits `d` metres along the cell's
//! "north" — the line from the centre to the first vertex. Adjacent cells
//! have rotated frames; that's the price of a cell-local convention.
//!
//! Use [`orientation::cell_orientation`] / [`coord::tangent_frame`] if you
//! need the geographic (north-up) frame instead.
//!
//! ## Big-space rendering convention
//!
//! The [`FloatingOrigin`](origin::FloatingOrigin)-tagged entity always
//! renders at world `(0, 0, 0)` (rotation and scale preserved). Every other
//! cell-anchored entity is placed in the origin's local frame at its true
//! sphere-space position relative to the origin. Move the origin by
//! mutating its `Transform.translation` — never its `GeoCell` directly. The
//! recenter system rebalances the `(GeoCell, Transform)` pair when the
//! translation grows past the per-origin threshold, *without* moving the
//! origin in world space. Recentres are visually invisible.
//!
//! ## Quick start
//!
//! ```rust,ignore
//! use bevy::prelude::*;
//! use bevy_a5::prelude::*;
//!
//! fn main() {
//!     App::new()
//!         .add_plugins(DefaultPlugins)
//!         .add_plugins(BevyA5Plugins)             // plugin group: core + camera + hash
//!         .insert_resource(PlanetSettings::earth())
//!         .add_systems(Startup, setup)
//!         .run();
//! }
//!
//! fn setup(mut commands: Commands) {
//!     // Spawn the floating origin (camera) at Paris.
//!     let cell = GeoCell::from_lon_lat(2.3522, 48.8566, 9).unwrap();
//!     commands.spawn_floating_origin(cell)
//!             .insert((Camera3d::default(), FlyCam::default()));
//!
//!     // Spawn an entity at a neighbouring cell's centre.
//!     let neighbour = GeoCell::from_lon_lat(2.3525, 48.8570, 9).unwrap();
//!     commands.spawn_cell_anchor(neighbour);
//! }
//! ```
//!
//! ## Roadmap
//!
//! - **Multi-planet support.** `PlanetSettings` is a global resource today, so
//!   apps are restricted to one planet. A `Planet` component on a root entity
//!   with per-planet hierarchies is planned.

pub mod camera;
pub mod cell;
pub mod commands_ext;
pub mod coord;
pub mod debug;
pub mod geometry;
pub mod hash;
pub mod orientation;
pub mod origin;
pub mod planet;
pub mod query;
pub mod systems;

use bevy_app::{prelude::*, PluginGroupBuilder};
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_transform::{TransformPlugin, TransformSystems};

/// Core `bevy_a5` plugin: floating-origin recentre, cell→world transform
/// propagation, debug gizmos. No camera, no spatial hash. Use
/// [`BevyA5Plugins`] for the full default set.
pub struct BevyA5Plugin;

impl Plugin for BevyA5Plugin {
    fn build(&self, app: &mut App) {
        if !app.is_plugin_added::<TransformPlugin>() {
            app.add_plugins(TransformPlugin);
        }

        app.init_resource::<planet::PlanetSettings>();

        app.register_type::<cell::GeoCell>();
        app.register_type::<cell::CellTracker>();
        app.register_type::<origin::FloatingOrigin>();
        app.register_type::<planet::PlanetSettings>();
        app.register_type::<debug::DrawCellOutline>();

        // CellTracker maintenance: runs after Bevy's TransformSystems::Propagate
        // (so GlobalTransform reflects the entity's user-set Transform) and
        // before our own propagate_geo_transforms (so the freshly-resolved
        // GeoCell feeds into the cell→world pass).
        app.add_systems(
            PostUpdate,
            cell::update_geo_cells_from_world_pos
                .after(TransformSystems::Propagate)
                .before(systems::recenter_floating_origin),
        );

        // Run AFTER bevy's own transform propagation so cell-relative writes
        // always overwrite the default Transform→GlobalTransform pass for
        // entities with a GeoCell.
        app.add_systems(
            PostUpdate,
            (
                systems::recenter_floating_origin,
                systems::propagate_geo_transforms,
                systems::update_origin_global_transform,
            )
                .chain()
                .after(TransformSystems::Propagate),
        );

        // Debug helpers run after our propagation so GlobalTransform is fresh.
        app.add_systems(
            PostUpdate,
            debug::draw_cell_outlines.after(systems::propagate_geo_transforms),
        );
    }
}

/// Default plugin group. Combines:
///
/// - [`BevyA5Plugin`] — core transform / floating-origin systems
/// - [`camera::CameraPlugin`] — fly-camera controller
/// - [`hash::CellHashPlugin`] — `CellEntityIndex` spatial hash
///
/// Add individual plugins instead if you don't want the camera or hash.
pub struct BevyA5Plugins;

impl PluginGroup for BevyA5Plugins {
    fn build(self) -> PluginGroupBuilder {
        PluginGroupBuilder::start::<Self>()
            .add(BevyA5Plugin)
            .add(camera::CameraPlugin)
            .add(hash::CellHashPlugin)
    }
}

/// Convenient re-exports for common usage.
pub mod prelude {
    pub use crate::camera::{CameraPlugin, FlyCam};
    pub use crate::cell::{CellTracker, GeoCell};
    pub use crate::commands_ext::BevyA5Commands;
    pub use crate::debug::DrawCellOutline;
    pub use crate::geometry::build_grid_line_mesh;
    pub use crate::hash::{CellEntityIndex, CellHashPlugin};
    pub use crate::origin::FloatingOrigin;
    pub use crate::planet::PlanetSettings;
    pub use crate::{BevyA5Plugin, BevyA5Plugins};
}

// Re-export a5 types that appear in our public signatures so users don't
// need a direct `a5` dependency. `LonLat` is the return type of
// `GeoCell::boundary` / `GeoCell::center`; `WORLD_CELL` is the resolution-0
// root cell index used by the planet_grid example.
pub use a5::{LonLat, WORLD_CELL};