use crate::components::DeferredAssetDrop;
use crate::components::TileEntity;
use crate::plugin::MapStateResource;
use crate::systems::frame_change_detection::{frame_unchanged, FrameChangeDetection};
use crate::systems::terrain_sync::TerrainHeightPlaceholderTexture;
use crate::tile_fog_material::TileFogMaterial;
use bevy::camera::visibility::NoFrustumCulling;
use bevy::mesh::{Indices, PrimitiveTopology};
use bevy::prelude::*;
use glam::DVec3;
use rustial_engine::{CameraProjection, TileId};
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct CachedTileKey {
tile_id: TileId,
projection: CachedProjectionKey,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum CachedProjectionKey {
WebMercator,
Equirectangular,
}
impl CachedProjectionKey {
fn from_projection(projection: CameraProjection) -> Option<Self> {
match projection {
CameraProjection::WebMercator => Some(Self::WebMercator),
CameraProjection::Equirectangular => Some(Self::Equirectangular),
_ => None,
}
}
}
#[derive(Resource, Default)]
pub struct CachedTileAssets {
meshes: HashMap<CachedTileKey, Handle<Mesh>>,
}
pub fn sync_tiles(
mut commands: Commands,
state: Res<MapStateResource>,
mut existing: Query<(Entity, &TileEntity, &mut Transform, &MeshMaterial3d<TileFogMaterial>, &Mesh3d)>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<TileFogMaterial>>,
mut deferred: ResMut<DeferredAssetDrop>,
height_placeholder: Option<Res<TerrainHeightPlaceholderTexture>>,
mut cache: ResMut<CachedTileAssets>,
detection: Res<FrameChangeDetection>,
) {
if frame_unchanged(&detection, &state.0) {
return;
}
if state.0.terrain().enabled() {
for (entity, _, _, mat_handle, mesh_handle) in existing.iter() {
deferred.keep_mesh(mesh_handle.0.clone());
deferred.keep_material(mat_handle.0.clone());
commands.entity(entity).despawn();
}
return;
}
let camera_origin = state.0.scene_world_origin();
let projection = state.0.camera().projection();
let desired_set: HashSet<TileId> = state.0.visible_tiles().iter().map(|vt| vt.target).collect();
for (entity, tile, _, mat_handle, mesh_handle) in existing.iter() {
if !desired_set.contains(&tile.tile_id) || tile.projection != projection {
deferred.keep_mesh(mesh_handle.0.clone());
deferred.keep_material(mat_handle.0.clone());
commands.entity(entity).despawn();
log::trace!("tile_sync: despawned tile {:?}", tile.tile_id);
}
}
let mut existing_ids: HashSet<TileId> = HashSet::with_capacity(desired_set.len());
for (_, tile, mut transform, _, _) in existing.iter_mut() {
if tile.projection != projection {
continue;
}
existing_ids.insert(tile.tile_id);
transform.translation = tile_translation(tile.tile_id, projection, camera_origin);
}
let Some(height_placeholder) = height_placeholder else {
return;
};
let Some(cached_projection) = CachedProjectionKey::from_projection(projection) else {
return;
};
for tile_id in &desired_set {
if existing_ids.contains(tile_id) {
continue;
}
let mesh_handle = get_or_create_tile_mesh_handle(
&mut meshes,
&mut cache,
*tile_id,
projection,
cached_projection,
);
let material_handle = materials.add(TileFogMaterial {
height_texture: height_placeholder.0.clone(),
..TileFogMaterial::default()
});
commands.spawn((
Mesh3d(mesh_handle),
MeshMaterial3d(material_handle),
Transform::from_translation(tile_translation(*tile_id, projection, camera_origin)),
Visibility::Hidden,
NoFrustumCulling,
TileEntity {
tile_id: *tile_id,
spawn_origin: camera_origin,
projection,
has_exact_texture: false,
},
));
log::trace!("tile_sync: spawned tile {:?}", tile_id);
}
}
fn get_or_create_tile_mesh_handle(
meshes: &mut Assets<Mesh>,
cache: &mut CachedTileAssets,
tile_id: TileId,
projection: CameraProjection,
cached_projection: CachedProjectionKey,
) -> Handle<Mesh> {
let key = CachedTileKey {
tile_id,
projection: cached_projection,
};
if let Some(handle) = cache.meshes.get(&key) {
return handle.clone();
}
let (positions, uvs) = centered_projected_tile_quad(tile_id, projection);
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, Default::default());
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0, 0.0, 1.0]; 4]);
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
mesh.insert_indices(Indices::U32(vec![0, 1, 2, 0, 2, 3]));
let handle = meshes.add(mesh);
cache.meshes.insert(key, handle.clone());
handle
}
fn centered_projected_tile_quad(
tile_id: TileId,
projection: CameraProjection,
) -> (Vec<[f32; 3]>, Vec<[f32; 2]>) {
let southwest = DVec3::from_array(projection.project_tile_corner(&tile_id, 0.0, 1.0));
let southeast = DVec3::from_array(projection.project_tile_corner(&tile_id, 1.0, 1.0));
let northeast = DVec3::from_array(projection.project_tile_corner(&tile_id, 1.0, 0.0));
let northwest = DVec3::from_array(projection.project_tile_corner(&tile_id, 0.0, 0.0));
let center = DVec3::from_array(projection.project_tile_center(&tile_id));
(
vec![
relative_vertex(southwest, center),
relative_vertex(southeast, center),
relative_vertex(northeast, center),
relative_vertex(northwest, center),
],
vec![[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]],
)
}
fn relative_vertex(position: DVec3, center: DVec3) -> [f32; 3] {
let relative = position - center;
[relative.x as f32, relative.y as f32, relative.z as f32]
}
fn tile_translation(tile_id: TileId, projection: CameraProjection, origin: DVec3) -> Vec3 {
let center = DVec3::from_array(projection.project_tile_center(&tile_id)) - origin;
Vec3::new(center.x as f32, center.y as f32, center.z as f32)
}