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
//! The orbit rig reaching the screen: the renderer resets [`Camera`] from the
//! first of the [`Views`] each frame, so the rig has to point the view itself.
use codecraft::dev::DevPlugin;
use codecraft::ecs::Application;
use codecraft::gizmos::GizmosPlugin;
use codecraft::glam::{Vec2, Vec3};
use codecraft::render3d::Render3dPlugin;
use codecraft::ui::{MouseInput, ScreenSize, UiPlugin};
use codecraft::{Camera, OrbitCamera, Views};

fn app() -> Application {
    let mut app = Application::new();
    app.add_plugin(UiPlugin);
    app.add_plugin(Render3dPlugin);
    app.add_plugin(GizmosPlugin);
    app.add_plugin(DevPlugin);
    app.world.insert_resource(ScreenSize {
        width: 800.0,
        height: 600.0,
    });
    app.world.spawn(OrbitCamera::new(Vec3::ZERO, 10.0));
    app
}

fn render(app: &mut Application) {
    let mut views = app.world.resource::<Views>().clone();
    views.fit(800.0, 600.0);
    let camera = views.camera();
    app.world.insert_resource(views);
    app.world.insert_resource(camera);
}

/// Two frames of right-drag, as the raw mouse counts the rig turns from.
fn drag(app: &mut Application) {
    for _ in 0..2 {
        let mut mouse = app.world.resource_mut::<MouseInput>();
        mouse.right_down = true;
        mouse.motion = Vec2::new(100.0, 25.0);
        app.update();
        render(app);
    }
}

#[test]
fn the_rig_points_the_view_it_is_drawn_from() {
    let mut app = app();
    let before = app.world.resource::<Views>().camera();
    drag(&mut app);
    let views = app.world.resource::<Views>();
    assert_ne!(views.camera(), before, "the drag never reached the view");
    assert_eq!(
        views.camera(),
        *app.world.resource::<Camera>(),
        "the view and the camera resource disagree",
    );
}

#[test]
fn the_rig_survives_the_scene_handing_it_a_fresh_view_every_frame() {
    // A dev-mode scene calls `set_views(Views::one(app.camera()))` every frame; that used to pin the rig.
    let mut app = app();
    let mut seen = Vec::new();
    for x in [20.0, 40.0, 60.0, 80.0] {
        let camera = *app.world.resource::<Camera>();
        app.world.insert_resource(Views::one(camera));
        let mut mouse = app.world.resource_mut::<MouseInput>();
        mouse.right_down = true;
        mouse.motion = Vec2::new(x, 0.0);
        app.update();
        render(&mut app);
        seen.push(app.world.resource::<Views>().camera().eye);
    }
    assert!(
        seen.windows(2).skip(1).all(|pair| pair[0] != pair[1]),
        "the eye stopped moving: {seen:?}",
    );
}

#[test]
fn a_split_screen_is_left_to_the_scene() {
    let mut app = app();
    let (left, right) = (
        Camera::looking_at(Vec3::new(-5.0, 2.0, 5.0), Vec3::ZERO),
        Camera::looking_at(Vec3::new(5.0, 2.0, 5.0), Vec3::ZERO),
    );
    app.world
        .insert_resource(Views::split(left, right, 800.0, 600.0));
    drag(&mut app);
    let views = app.world.resource::<Views>();
    let cameras: Vec<Camera> = views.iter().map(|view| view.camera).collect();
    assert_eq!(cameras, [left, right], "the rig took over a split screen");
}