📄 src/main.rs
mod camera_controller;
mod face_detection;
mod physical_monitor;
mod virtual_window;

use std::f32::consts::FRAC_PI_4;

use bevy::{
    DefaultPlugins,
    app::{App, Startup, Update},
    asset::{AssetServer, Assets, Handle},
    camera::{Camera3d, PerspectiveProjection, Projection},
    ecs::{
        resource::Resource,
        system::{Commands, Res, ResMut, Single},
    },
    gltf::GltfAssetLabel,
    image::Image,
    light::{DirectionalLight, DirectionalLightShadowMap, Skybox},
    log::error,
    math::{Dir3, Vec3},
    render::render_resource::{TextureViewDescriptor, TextureViewDimension},
    transform::components::Transform,
    world_serialization::WorldAssetRoot,
};

use crate::{
    camera_controller::CameraControllerPlugin,
    face_detection::FaceDetectionPlugin,
    virtual_window::{VirtualWindow, VirtualWindowPlugin},
};

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
            CameraControllerPlugin,
            VirtualWindowPlugin,
            FaceDetectionPlugin,
        ))
        .add_systems(Startup, setup)
        .add_systems(Update, load_skybox)
        .run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
    let handle = asset_server.load("Skybox.png");
    commands.insert_resource(SkyboxHandle(handle.clone(), false));
    commands.spawn((
        Camera3d::default(),
        VirtualWindow::new(
            Vec3::new(-6.5, 3.3, 13.1),
            Dir3::from_xyz_unchecked(-0.48564294, 0.0, 0.87415725),
        ),
        Projection::from(PerspectiveProjection {
            fov: FRAC_PI_4,
            ..Default::default()
        }),
        Transform::IDENTITY,
        Skybox {
            image: Some(handle),
            brightness: 1000.0,
            ..Skybox::default()
        },
    ));
    commands.insert_resource(DirectionalLightShadowMap { size: 4096 });

    commands.spawn((
        DirectionalLight {
            illuminance: 1000.0,
            shadow_maps_enabled: true,
            ..DirectionalLight::default()
        },
        Transform::default().looking_to(Vec3::new(-2.5, -1.0, 0.0), Vec3::Y),
    ));

    commands.spawn(WorldAssetRoot(
        asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/Village.glb")),
    ));
}

#[derive(Resource)]
struct SkyboxHandle(Handle<Image>, bool);

fn load_skybox(
    asset_server: Res<AssetServer>,
    mut images: ResMut<Assets<Image>>,
    mut skybox: Single<&mut Skybox>,
    mut skybox_handle: ResMut<SkyboxHandle>,
) {
    if skybox_handle.1 {
        return;
    }
    if !asset_server.load_state(&skybox_handle.0).is_loaded() {
        return;
    }

    let Some(mut image) = images.get_mut(&skybox_handle.0) else {
        return;
    };
    if let Err(err) = image.reinterpret_stacked_2d_as_array(6) {
        error!("Failed to reinterpret cubemap texture as 6 layers array: {err}");
        skybox_handle.1 = true;
        return;
    }
    image.texture_view_descriptor = Some(TextureViewDescriptor {
        dimension: Some(TextureViewDimension::Cube),
        ..TextureViewDescriptor::default()
    });
    skybox.image = Some(skybox_handle.0.clone());
    skybox_handle.1 = true;
}