codecraft 0.2.0

A minimalist 3D game engine built on parts of Bevy (ECS, color) with wgpu and winit: OpenPBR materials, clustered lighting, a yakui-drawn UI, audio and gamepad haptics; its binary maps any folder, and the symbols of its Rust files, as a 3D wall of boxes
Documentation
//! Turning a cursor position into a point on the board. Everything the player
//! does with the mouse in 3D goes through this, so it is worth pinning.
use codecraft::glam::Vec3;
use codecraft::render3d::{Camera, Ray};

const WIDTH: f32 = 800.0;
const HEIGHT: f32 = 600.0;

fn camera() -> Camera {
    Camera::looking_at(Vec3::new(0.0, 0.28, 0.34), Vec3::new(0.0, 0.015, 0.0))
}

#[test]
fn the_centre_of_the_screen_looks_at_what_the_camera_looks_at() {
    let camera = camera();
    let ray = camera.ray((WIDTH * 0.5, HEIGHT * 0.5), WIDTH, HEIGHT);
    let hit = ray
        .plane_hit(0.015)
        .expect("the camera points at the board");

    assert!(
        hit.x.abs() < 1e-3,
        "dead centre horizontally, got {}",
        hit.x
    );
    assert!(hit.z.abs() < 1e-3, "dead centre in depth, got {}", hit.z);
}

#[test]
fn the_ray_starts_at_the_eye_and_points_into_the_scene() {
    let camera = camera();
    let ray = camera.ray((WIDTH * 0.5, HEIGHT * 0.5), WIDTH, HEIGHT);

    assert!(
        (ray.origin - camera.eye).length() < 0.05,
        "the ray should start at the near plane, right by the eye",
    );
    assert!(ray.direction.y < 0.0, "the camera looks down at the board");
    assert!(
        (ray.direction.length() - 1.0).abs() < 1e-5,
        "direction is a unit vector"
    );
}

#[test]
fn moving_the_cursor_moves_the_hit_the_same_way() {
    let camera = camera();
    let at = |x: f32, y: f32| {
        camera
            .ray((x, y), WIDTH, HEIGHT)
            .plane_hit(0.0)
            .expect("looking at the board")
    };

    let centre = at(WIDTH * 0.5, HEIGHT * 0.5);
    let right = at(WIDTH * 0.75, HEIGHT * 0.5);
    let down = at(WIDTH * 0.5, HEIGHT * 0.75);

    assert!(right.x > centre.x, "cursor right, hit right");
    // Down the screen is towards the camera, which is +z on this board.
    assert!(down.z > centre.z, "cursor down, hit nearer the viewer");
}

#[test]
fn a_ray_pointing_away_from_the_board_hits_nothing() {
    let upwards = Ray {
        origin: Vec3::new(0.0, 1.0, 0.0),
        direction: Vec3::Y,
    };
    assert!(upwards.plane_hit(0.0).is_none());

    let parallel = Ray {
        origin: Vec3::new(0.0, 1.0, 0.0),
        direction: Vec3::X,
    };
    assert!(parallel.plane_hit(0.0).is_none());

    let leaving = Ray {
        origin: Vec3::ZERO,
        direction: Vec3::Y,
    };
    assert_eq!(leaving.plane_hit(0.0), Some(Vec3::ZERO));
}

#[test]
fn a_plane_hit_lands_on_the_plane_it_was_asked_for() {
    let camera = camera();
    let ray = camera.ray((WIDTH * 0.4, HEIGHT * 0.6), WIDTH, HEIGHT);

    for height in [0.0, 0.02, 0.05] {
        let hit = ray.plane_hit(height).unwrap();
        assert!(
            (hit.y - height).abs() < 1e-5,
            "asked for y = {height}, got {}",
            hit.y,
        );
    }
}

fn overhead() -> Camera {
    Camera::looking_at(Vec3::new(0.0, 0.5, 0.0), Vec3::ZERO)
        .orthographic(-0.18, 0.18, -0.18, 0.18, 0.01, 10.0)
}

#[test]
fn picking_still_works_looking_straight_down_through_a_box() {
    let camera = overhead();
    let at = |x: f32, y: f32| {
        camera
            .ray((x, y), WIDTH, HEIGHT)
            .plane_hit(0.0)
            .expect("the whole board is under this camera")
    };

    let centre = at(WIDTH * 0.5, HEIGHT * 0.5);
    assert!(centre.x.abs() < 1e-5 && centre.z.abs() < 1e-5, "{centre:?}");

    let right = at(WIDTH, HEIGHT * 0.5);
    assert!((right.x - 0.18).abs() < 1e-4, "{}", right.x);
    let up = at(WIDTH * 0.5, 0.0);
    assert!((up.z + 0.18).abs() < 1e-4, "{}", up.z);
}

#[test]
fn a_box_shows_the_same_size_wherever_the_cursor_is() {
    let camera = overhead();
    let hit = |x: f32| {
        camera
            .ray((x, HEIGHT * 0.5), WIDTH, HEIGHT)
            .plane_hit(0.0)
            .unwrap()
    };

    let inner = hit(WIDTH * 0.75).x - hit(WIDTH * 0.5).x;
    let outer = hit(WIDTH) - hit(WIDTH * 0.75);
    assert!((inner - outer.x).abs() < 1e-4, "{inner} vs {}", outer.x);
}

#[test]
fn every_ray_through_a_box_points_the_same_way() {
    let camera = overhead();
    let middle = camera.ray((WIDTH * 0.5, HEIGHT * 0.5), WIDTH, HEIGHT);
    let corner = camera.ray((0.0, 0.0), WIDTH, HEIGHT);

    assert!(
        (middle.direction - corner.direction).length() < 1e-5,
        "parallel projection means parallel rays: {:?} vs {:?}",
        middle.direction,
        corner.direction,
    );
    assert!(
        middle.direction.y < -0.99,
        "and they all point straight down"
    );
    assert!((middle.origin - corner.origin).length() > 0.1);
}

#[test]
fn looking_straight_down_gives_a_picture_rather_than_nothing() {
    // `up` is parallel to the view direction here; the camera must pick another or the matrix is NaN.
    let camera = overhead();
    let matrix = camera.view_proj(WIDTH / HEIGHT);

    assert!(
        matrix.to_cols_array().iter().all(|value| value.is_finite()),
        "{matrix:?}",
    );
    let far = matrix * Vec3::new(0.0, 0.0, -0.1).extend(1.0);
    assert!(far.y / far.w > 0.0, "{far:?}");
}

#[test]
fn a_box_draws_things_at_the_same_size_however_far_away_they_are() {
    let camera = overhead();
    let matrix = camera.view_proj(WIDTH / HEIGHT);
    let width = |height: f32| {
        let left = matrix * Vec3::new(-0.09, height, 0.0).extend(1.0);
        let right = matrix * Vec3::new(0.09, height, 0.0).extend(1.0);
        right.x / right.w - left.x / left.w
    };

    assert!(
        (width(0.0) - width(0.05)).abs() < 1e-5,
        "{} vs {}",
        width(0.0),
        width(0.05)
    );
}