//! `physics_stack` — a runnable tour of the Stage 9 rigid-body simulation.
//!
//! Run with:
//! ```sh
//! cargo run -p oxide-examples --bin physics_stack
//! ```
//!
//! This example has no window or GPU dependency. It builds a static floor and a
//! stack of dynamic boxes, steps the physics simulation at a fixed 60 Hz, and
//! prints the boxes' heights over time — so you can see them settle into a
//! stable stack (and a dropped ball land and come to rest) by eye.

use oxide_engine::prelude::*;
use oxide_physics::{Collider, PhysicsModule, PhysicsWorld, RigidBody};

fn main() {
    env_logger::init();
    println!("Oxide physics demo — Stage 9 rigid-body stack\n");

    let mut app = App::new();
    app.add_modules(DefaultModules);
    app.add_module(PhysicsModule);

    // A static ground plane (a wide, thin box; its top surface is at y = 0.5).
    let floor = app
        .scene
        .spawn("floor", Transform::from_translation(Vec3::ZERO));
    app.scene
        .world_mut()
        .insert_one(floor, RigidBody::static_body())
        .unwrap();
    app.scene
        .world_mut()
        .insert_one(floor, Collider::cuboid(Vec3::new(10.0, 0.5, 10.0)))
        .unwrap();

    // A stack of three unit boxes, each starting a little above its rest height
    // so they drop and settle onto one another.
    let mut boxes = Vec::new();
    for i in 0..3 {
        let y = 1.2 + i as f32 * 1.05;
        let e = app.scene.spawn(
            format!("box{i}"),
            Transform::from_translation(Vec3::new(0.0, y, 0.0)),
        );
        app.scene
            .world_mut()
            .insert_one(e, RigidBody::default())
            .unwrap();
        app.scene
            .world_mut()
            .insert_one(e, Collider::cuboid(Vec3::splat(0.5)))
            .unwrap();
        boxes.push(e);
    }

    // A ball dropped from higher up, to land on the top box.
    let ball = app.scene.spawn(
        "ball",
        Transform::from_translation(Vec3::new(0.0, 6.0, 0.0)),
    );
    app.scene
        .world_mut()
        .insert_one(ball, RigidBody::default())
        .unwrap();
    app.scene
        .world_mut()
        .insert_one(ball, Collider::ball(0.5))
        .unwrap();

    println!("Stepping the simulation at 60 Hz (boxes start stacked, ball falls in):\n");
    println!(
        "  {:>5}  {:>7} {:>7} {:>7}  {:>7}",
        "step", "box0", "box1", "box2", "ball"
    );

    let dt = 1.0 / 60.0;
    for step in 0..=180 {
        if step % 20 == 0 {
            let ys: Vec<f32> = boxes.iter().map(|&e| height(&app, e)).collect();
            println!(
                "  {:>5}  {:>7.3} {:>7.3} {:>7.3}  {:>7.3}",
                step,
                ys[0],
                ys[1],
                ys[2],
                height(&app, ball)
            );
        }
        app.update(dt);
    }

    // Report the resting state.
    let world = app.get_resource::<PhysicsWorld>().unwrap();
    let resting = boxes
        .iter()
        .all(|&e| world.linear_velocity(e).length() < 0.05);
    println!("\nAfter ~3 s: boxes rest near y ≈ 1.0 / 2.0 / 3.0, all settled = {resting}.");
    println!("The stack stays standing — stable contact, no jitter or explosion.");
}

/// The current world-space height (y) of an entity.
fn height(app: &App, e: Entity) -> f32 {
    app.scene.world_transform(e).unwrap().translation.y
}
