nightshade 0.13.0

A cross-platform data-oriented game engine.
Documentation
//! 3D transformation gizmos for editor-style entity manipulation.
//!
//! Provides visual handles for translating, rotating, and scaling entities interactively:
//!
//! - [`GizmoState`]: Active gizmo instance tracking target entity and mode
//! - [`GizmoMode`]: Translation (local/global), rotation, or scale mode
//! - [`GizmoConfig`]: Visual appearance settings (sizes, colors)
//! - `create_translation_gizmo`: Spawn translation arrows
//! - `create_rotation_gizmo`: Spawn rotation rings
//! - `create_scale_gizmo`: Spawn scale cubes
//! - `update_gizmo_transforms`: Sync gizmo position to target
//! - `gizmo_drag_system`: Handle mouse interaction
//!
//! # Creating a Gizmo
//!
//! ```ignore
//! // Create translation gizmo attached to an entity
//! let gizmo = create_translation_gizmo(world, selected_entity, GizmoMode::LocalTranslation);
//! self.active_gizmo = Some(gizmo);
//!
//! // Switch to rotation mode
//! let gizmo = create_rotation_gizmo(world, selected_entity);
//! self.active_gizmo = Some(gizmo);
//!
//! // Switch to scale mode
//! let gizmo = create_scale_gizmo(world, selected_entity);
//! self.active_gizmo = Some(gizmo);
//! ```
//!
//! # Updating Gizmo Each Frame
//!
//! ```ignore
//! fn run_systems(&mut self, world: &mut World) {
//!     if let Some(gizmo) = &self.active_gizmo {
//!         // Keep gizmo positioned at target entity
//!         update_gizmo_transforms(world, gizmo);
//!
//!         // Scale gizmo based on camera distance (constant screen size)
//!         scale_gizmo_for_camera(world, gizmo);
//!
//!         // Handle mouse dragging
//!         gizmo_drag_system(world, gizmo, &mut self.drag_state);
//!     }
//! }
//! ```
//!
//! # Gizmo Modes
//!
//! | Mode | Description |
//! |------|-------------|
//! | `LocalTranslation` | Move along entity's local axes |
//! | `GlobalTranslation` | Move along world axes |
//! | `Rotation` | Rotate around axes (ring handles) |
//! | `Scale` | Scale along axes (cube handles) |
//!
//! # Mouse Interaction
//!
//! ```ignore
//! fn on_mouse_input(&mut self, world: &mut World, button: MouseButton, state: KeyState) {
//!     if button == MouseButton::Left {
//!         if state == KeyState::Pressed {
//!             // Check if mouse is over a gizmo axis
//!             if let Some(axis) = pick_gizmo_axis(world, &self.gizmo) {
//!                 self.drag_state = Some(GizmoDragState::new(axis));
//!             }
//!         } else {
//!             self.drag_state = None;
//!         }
//!     }
//! }
//! ```
//!
//! # Removing Gizmo
//!
//! ```ignore
//! if let Some(gizmo) = self.active_gizmo.take() {
//!     destroy_gizmo(world, &gizmo);
//! }
//! ```
//!
//! # Gizmo Configuration
//!
//! ```ignore
//! let config = GizmoConfig {
//!     arrow_length: 2.0,      // Length of translation arrows
//!     arrow_radius: 0.06,     // Thickness of arrow shafts
//!     cone_height: 0.5,       // Arrow head height
//!     cone_radius: 0.15,      // Arrow head radius
//!     scale_factor: 0.08,     // Screen-size scaling factor
//!     ..Default::default()
//! };
//! ```
//!
//! # Axis Colors
//!
//! | Axis | Color |
//! |------|-------|
//! | X | Red |
//! | Y | Green |
//! | Z | Blue |
//! | XY Plane | Yellow |
//! | YZ Plane | Cyan |
//! | XZ Plane | Magenta |

mod creation;
mod interaction;
mod lifecycle;

pub use creation::{create_rotation_gizmo, create_scale_gizmo, create_translation_gizmo};
pub use interaction::{
    GizmoDragMode, check_gizmo_axis_hit, highlight_gizmo_axis, set_gizmo_drag_visibility,
};
pub use lifecycle::{
    destroy_gizmo, hide_entities, is_gizmo_entity, show_entities, update_gizmo_transform,
};

use crate::prelude::*;

#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum GizmoMode {
    #[default]
    LocalTranslation,
    GlobalTranslation,
    Rotation,
    Scale,
}

pub struct GizmoState {
    pub root: Entity,
    pub target: Entity,
    pub mode: GizmoMode,
    pub axis_entities: GizmoAxisEntities,
}

#[derive(Debug, Clone, Default)]
pub struct GizmoAxisEntities {
    pub x_axis: Option<Entity>,
    pub y_axis: Option<Entity>,
    pub z_axis: Option<Entity>,
    pub center_cube: Option<Entity>,
    pub xy_plane: Option<Entity>,
    pub yz_plane: Option<Entity>,
    pub xz_plane: Option<Entity>,
    pub x_axis_line: Option<Entity>,
    pub y_axis_line: Option<Entity>,
    pub z_axis_line: Option<Entity>,
}

#[derive(Debug, Clone, Copy)]
pub struct GizmoConfig {
    pub arrow_length: f32,
    pub arrow_radius: f32,
    pub cone_height: f32,
    pub cone_radius: f32,
    pub highlight_factor: f32,
    pub normal_factor: f32,
    pub scale_factor: f32,
    pub min_scale: f32,
    pub max_scale: f32,
}

impl Default for GizmoConfig {
    fn default() -> Self {
        Self {
            arrow_length: 2.0,
            arrow_radius: 0.06,
            cone_height: 0.5,
            cone_radius: 0.15,
            highlight_factor: 1.0,
            normal_factor: 1.0,
            scale_factor: 0.08,
            min_scale: 0.3,
            max_scale: 50.0,
        }
    }
}

fn set_gizmo_layer(world: &mut World, entity: Entity) {
    if let Some(render_layer) = world.core.get_render_layer_mut(entity) {
        render_layer.0 = RenderLayer::OVERLAY;
    }
}

struct AxisArrowParams {
    parent: Entity,
    direction: Vec3,
    color: Vec3,
    length: f32,
    cylinder_radius: f32,
    cone_height: f32,
    cone_radius: f32,
    axis_name: String,
}

struct AxisWithCubeParams {
    parent: Entity,
    direction: Vec3,
    color: Vec3,
    length: f32,
    cylinder_radius: f32,
    cube_size: f32,
    axis_name: String,
}

struct RotationRingParams {
    parent: Entity,
    axis: Vec3,
    color: Vec3,
    ring_radius: f32,
    axis_name: String,
}