//! cube3d: the road to 3D, walked (v0.3 milestone).
//!
//! A lit, spinning cube LOADED FROM A glTF FILE, viewed through a
//! PerspectiveCamera, with an unlit 2D HUD sprite in the same frame
//! through an OrthographicCamera. That one frame exercises everything
//! v0.3 added: the depth buffer, back-face culling (lit pipeline), the
//! layout-v2 normals, the Lambert light, glTF loading, and the claim the
//! seams made from day one: 2D and 3D differ by which camera you pass,
//! not by architecture.
//!
//! Run: RUST_LOG=info cargo run --features gltf --example cube3d
//! (from the repo root, so assets/ resolves)

use mawida::camera::OrbitCamera;
use mawida::math::{Quat, Vec3};
use mawida::drawable::Model;
use mawida::{
    run, AppConfig, Camera, Color, Context, Game, GameTime, Key, OrthographicCamera, Result,
    Sprite,
};

struct Cube3d {
    cube: Option<Model>,
    orbit: OrbitCamera, // item 13: arrow keys orbit, +/- zooms
    ortho: OrthographicCamera,
    hud: Sprite,
}

impl Game for Cube3d {
    fn init(&mut self, ctx: &mut Context) -> Result<()> {
        // glTF in: a mesh + a material (lit, with the file's base color).
        let loaded = ctx.content_mut().load_gltf("assets/models/cube.gltf")?;
        if let Some(tex) = loaded.material.texture {
            ctx.upload_texture(tex)?; // models with textures ride the same path
        }
        let mut cube = Model { mesh: loaded.mesh, material: loaded.material,
                               transform: Default::default() };
        cube.transform.scale = mawida::Vec3::splat(2.0); // a 2-unit cube
        self.cube = Some(cube);
        Ok(())
    }

    fn update(&mut self, ctx: &mut Context, time: GameTime) -> Result<()> {
        let dt = time.delta_seconds();
        if let Some(cube) = &mut self.cube {
            // seam 1: math is 3D and always was; this is just more of it
            cube.transform.rotation *=
                Quat::from_rotation_y(0.9 * dt) * Quat::from_rotation_x(0.35 * dt);
        }
        // item 13: the orbit camera. Three scalars steer the whole view.
        let input = ctx.input();
        let (mut dy, mut dp) = (0.0, 0.0);
        if input.is_down(Key::Left) { dy -= 1.5 * dt; }
        if input.is_down(Key::Right) { dy += 1.5 * dt; }
        if input.is_down(Key::Up) { dp += 1.0 * dt; }
        if input.is_down(Key::Down) { dp -= 1.0 * dt; }
        self.orbit.orbit(dy, dp);
        self.orbit.zoom(input.scroll_delta * 0.1);
        Ok(())
    }

    fn draw(&mut self, ctx: &mut Context) -> Result<()> {
        // 3D: the cube through the perspective camera (lit pipeline,
        // back-cull, strict depth)...
        if let Some(cube) = &self.cube {
            ctx.draw(cube, &self.orbit as &dyn Camera)?;
        }
        // ...and 2D in the SAME frame: an unlit HUD sprite through the
        // ortho camera. Two cameras, two pipelines, one draw method.
        ctx.draw(&self.hud, &self.ortho as &dyn Camera)
    }
}

fn main() -> Result<()> {
    run(
        Cube3d {
            cube: None,
            orbit: OrbitCamera::new(Vec3::ZERO, 6.0, 800.0 / 600.0),
            ortho: OrthographicCamera::new(800.0, 600.0),
            hud: Sprite::solid(Color::rgb(0.2, 0.9, 0.5), 16.0, 16.0, 120.0, 24.0),
        },
        AppConfig {
            title: "Mawida - cube3d (glTF + lit, the road to 3D)".to_string(),
            ..Default::default()
        },
    )
}
