use crate::components::ImageOverlayEntity;
use crate::plugin::MapStateResource;
use crate::systems::frame_change_detection::{frame_unchanged, FrameChangeDetection};
use bevy::asset::RenderAssetUsages;
use bevy::image::Image;
use bevy::mesh::{Indices, PrimitiveTopology};
use bevy::prelude::*;
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
use glam::DVec3;
#[derive(Resource, Default)]
pub struct ImageOverlaySyncState {
overlay_count: usize,
last_origin: [i64; 3],
data_ptrs: Vec<usize>,
}
fn quantise_origin(origin: DVec3) -> [i64; 3] {
[
(origin.x * 100.0) as i64,
(origin.y * 100.0) as i64,
(origin.z * 100.0) as i64,
]
}
#[allow(clippy::too_many_arguments)]
pub fn sync_image_overlays(
mut commands: Commands,
state: Res<MapStateResource>,
mut existing: Query<(Entity, &ImageOverlayEntity, &mut Transform)>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut images: ResMut<Assets<Image>>,
mut sync_state: ResMut<ImageOverlaySyncState>,
detection: Res<FrameChangeDetection>,
) {
if frame_unchanged(&detection, &state.0) {
return;
}
let camera_origin: DVec3 = state.0.scene_world_origin();
let frame = state.0.frame_output();
let overlays = &frame.image_overlays;
if overlays.is_empty() {
for (entity, _, _) in existing.iter() {
commands.entity(entity).despawn();
}
sync_state.overlay_count = 0;
sync_state.last_origin = [0; 3];
sync_state.data_ptrs.clear();
return;
}
let current_ptrs: Vec<usize> = overlays
.iter()
.map(|o| std::sync::Arc::as_ptr(&o.data) as usize)
.collect();
let data_changed = current_ptrs != sync_state.data_ptrs;
if overlays.len() == sync_state.overlay_count && !data_changed {
let origin_key = quantise_origin(camera_origin);
if origin_key == sync_state.last_origin {
return;
}
for (_, overlay_entity, mut transform) in existing.iter_mut() {
let offset = overlay_entity.spawn_origin - camera_origin;
transform.translation = Vec3::new(offset.x as f32, offset.y as f32, offset.z as f32);
}
sync_state.last_origin = origin_key;
return;
}
for (entity, _, _) in existing.iter() {
commands.entity(entity).despawn();
}
for overlay in overlays.iter() {
if overlay.width == 0 || overlay.height == 0 || overlay.data.is_empty() {
continue;
}
let positions: Vec<[f32; 3]> = overlay
.corners
.iter()
.map(|c| {
[
(c[0] - camera_origin.x) as f32,
(c[1] - camera_origin.y) as f32,
(c[2] - camera_origin.z) as f32,
]
})
.collect();
let uvs = vec![[0.0f32, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let normals = vec![[0.0f32, 0.0, 1.0]; 4];
let indices = vec![0u32, 1, 2, 0, 2, 3];
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, Default::default());
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh.insert_indices(Indices::U32(indices));
let mesh_handle = meshes.add(mesh);
let image = Image::new(
Extent3d {
width: overlay.width,
height: overlay.height,
depth_or_array_layers: 1,
},
TextureDimension::D2,
overlay.data.to_vec(),
TextureFormat::Rgba8UnormSrgb,
RenderAssetUsages::default(),
);
let image_handle = images.add(image);
let material_handle = materials.add(StandardMaterial {
base_color_texture: Some(image_handle),
base_color: Color::linear_rgba(1.0, 1.0, 1.0, overlay.opacity),
alpha_mode: AlphaMode::Blend,
unlit: true,
double_sided: true,
..Default::default()
});
commands.spawn((
Mesh3d(mesh_handle),
MeshMaterial3d(material_handle),
Transform::default(),
Visibility::default(),
ImageOverlayEntity {
layer_id: overlay.layer_id,
spawn_origin: camera_origin,
},
));
}
sync_state.overlay_count = overlays.len();
sync_state.last_origin = quantise_origin(camera_origin);
sync_state.data_ptrs = current_ptrs;
}