📄 src/virtual_window.rs
use bevy::{
    app::{App, Plugin, PreUpdate},
    camera::{Camera, RenderTarget},
    ecs::{
        component::Component,
        entity::Entity,
        query::With,
        system::{NonSendMarker, Query},
    },
    log,
    math::{Dir3, Vec3},
    transform::components::Transform,
    window::{Monitor, OnMonitor, PrimaryWindow, WindowRef},
    winit::WINIT_WINDOWS,
};
use dpi::{PhysicalPosition, PhysicalSize};

use crate::physical_monitor::PhysicalMonitor;

pub struct VirtualWindowPlugin;

impl Plugin for VirtualWindowPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(PreUpdate, update_window);
    }
}

#[derive(Component)]
pub struct VirtualWindow {
    camera_position: Vec3,
    camera_direction: Dir3,
    center: Vec3,
    previous_cache_key: Option<MonitorWindowCacheKey>,
}

#[derive(PartialEq, Eq)]
struct MonitorWindowCacheKey {
    position_in_pixels: PhysicalPosition<i32>,
    size_in_pixels: PhysicalSize<u32>,
    monitor_entity: Entity,
}

impl VirtualWindow {
    pub fn new(camera_position: Vec3, camera_direction: Dir3) -> Self {
        Self {
            camera_position,
            camera_direction,
            center: camera_position,
            previous_cache_key: None,
        }
    }

    pub fn camera_position(&self) -> Vec3 {
        self.camera_position
    }

    pub fn camera_normal(&self) -> Dir3 {
        self.camera_direction
    }

    pub fn center(&self) -> Vec3 {
        self.center
    }
}

fn update_window(
    camera_window: Query<(&RenderTarget, &mut VirtualWindow), With<Camera>>,
    on_monitors: Query<&OnMonitor>,
    primary_window: Query<Entity, With<PrimaryWindow>>,
    monitors: Query<&Monitor>,
    _main_thread: NonSendMarker,
) {
    for (camera, mut virtual_window) in camera_window {
        let RenderTarget::Window(window_ref) = camera else {
            log::warn!("Window of camera cannot be found");
            break;
        };
        let Ok((on_monitor, window_entity)) = (match window_ref {
            WindowRef::Primary if let Ok(entity) = primary_window.single() => {
                on_monitors.get(entity).map(|m| (m, entity))
            }
            WindowRef::Entity(entity) => on_monitors.get(*entity).map(|m| (m, *entity)),
            WindowRef::Primary => {
                log::warn!("Primary camera without a primary window");
                break;
            }
        }) else {
            log::warn!("Window is not on any monitor");
            break;
        };
        let Some((position_in_pixels, size_in_pixels)) = WINIT_WINDOWS.with_borrow(|windows| {
            windows.get_window(window_entity).and_then(|w| {
                match w.inner_position().map(|p| (p, w.inner_size())) {
                    Ok(a) => Some(a),
                    Err(err) => {
                        log::warn!("Failed to get position and size of window: {err:?}");
                        None
                    }
                }
            })
        }) else {
            log::warn!("Cannot get position and size of window");
            break;
        };

        let cache_key = Some(MonitorWindowCacheKey {
            position_in_pixels,
            size_in_pixels,
            monitor_entity: on_monitor.0,
        });
        if virtual_window.previous_cache_key == cache_key {
            break;
        }

        let Ok(monitor) = monitors.get(on_monitor.0) else {
            log::warn!("Monitor of window cannot be found");
            break;
        };
        let monitor_size_in_pixels = monitor.physical_size();
        let Some(monitor_size_in_millimeters) = monitor.physical_size_millimeters() else {
            log::warn!("No physical size for monitor");
            break;
        };

        let window_center_in_pixels = -Vec3::new(
            (position_in_pixels.x as f32) + (size_in_pixels.width as f32) / 2.0
                - (monitor_size_in_pixels.x as f32) / 2.0,
            (position_in_pixels.y as f32) + (size_in_pixels.height as f32) / 2.0,
            0.0,
        );
        let meters_per_pixel =
            (monitor_size_in_millimeters.as_vec2() / 1000.0) / monitor_size_in_pixels.as_vec2();
        let window_center_in_meters = window_center_in_pixels * meters_per_pixel.extend(1.0);
        let window_offset_in_meters = Transform::IDENTITY
            .looking_to(virtual_window.camera_direction, Dir3::Y)
            .rotation
            * window_center_in_meters;

        virtual_window.center = virtual_window.camera_position + window_offset_in_meters;
        virtual_window.previous_cache_key = cache_key;
    }
}