use crate::components::{DeferredAssetDrop, HillshadeEntity, TerrainEntity};
use crate::hillshade_material::HillshadeMaterial;
use crate::painter::{PainterPass, PainterPlanResource};
use crate::plugin::MapStateResource;
use crate::systems::terrain_sync::{
get_or_create_height_texture_handle, get_or_create_shared_grid_mesh_handle,
scene_origin_uniform, supports_gpu_terrain_path, terrain_uniforms_for_mesh,
HillshadePlaceholderTexture, SharedTerrainGridMeshes, TerrainHeightPlaceholderTexture,
UploadedTerrainHeightTextures,
};
use bevy::camera::visibility::NoFrustumCulling;
use bevy::mesh::{Indices, PrimitiveTopology};
use bevy::prelude::*;
use rustial_engine::{materialize_terrain_mesh, TileId};
use std::collections::HashSet;
#[allow(clippy::too_many_arguments, clippy::type_complexity)]
pub fn sync_hillshade(
mut commands: Commands,
state: Res<MapStateResource>,
plan: Res<PainterPlanResource>,
mut existing: Query<
(
Entity,
&HillshadeEntity,
&mut Transform,
&Mesh3d,
&MeshMaterial3d<HillshadeMaterial>,
),
Without<TerrainEntity>,
>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<HillshadeMaterial>>,
mut images: ResMut<Assets<Image>>,
mut deferred: ResMut<DeferredAssetDrop>,
mut shared_grids: ResMut<SharedTerrainGridMeshes>,
mut height_cache: ResMut<UploadedTerrainHeightTextures>,
placeholder: Option<Res<HillshadePlaceholderTexture>>,
height_placeholder: Option<Res<TerrainHeightPlaceholderTexture>>,
) {
let terrain_meshes = state.0.terrain_meshes();
let camera_origin = state.0.scene_world_origin();
let projection = state.0.camera().projection();
let hillshade_enabled = plan.contains(PainterPass::HillshadeOverlay);
if !hillshade_enabled {
for (entity, _, _, mesh_handle, mat_handle) in existing.iter() {
deferred.keep_mesh(mesh_handle.0.clone());
deferred.keep_hillshade_material(mat_handle.0.clone());
commands.entity(entity).despawn();
}
return;
}
let desired: HashSet<TileId> = terrain_meshes.iter().map(|m| m.tile).collect();
let engine_generations: std::collections::HashMap<TileId, u64> = terrain_meshes
.iter()
.map(|m| (m.tile, m.generation))
.collect();
let engine_gpu_path: std::collections::HashMap<TileId, bool> = terrain_meshes
.iter()
.map(|m| (m.tile, supports_gpu_terrain_path(m, projection)))
.collect();
for (entity, hillshade, _, mesh_handle, mat_handle) in existing.iter() {
let dominated = !desired.contains(&hillshade.tile_id);
let stale = engine_generations
.get(&hillshade.tile_id)
.is_some_and(|&generation| generation != hillshade.mesh_generation);
let gpu_path_changed = engine_gpu_path
.get(&hillshade.tile_id)
.is_some_and(|&gpu| gpu != hillshade.gpu_displaced);
if dominated || stale || hillshade.projection != projection || gpu_path_changed {
deferred.keep_mesh(mesh_handle.0.clone());
deferred.keep_hillshade_material(mat_handle.0.clone());
commands.entity(entity).despawn();
}
}
let mut existing_ids = HashSet::with_capacity(desired.len());
for (_, hillshade, mut transform, _, material_handle) in existing.iter_mut() {
if !desired.contains(&hillshade.tile_id) {
continue;
}
if engine_generations
.get(&hillshade.tile_id)
.is_some_and(|&generation| generation != hillshade.mesh_generation)
{
continue;
}
existing_ids.insert(hillshade.tile_id);
if hillshade.gpu_displaced {
transform.translation = Vec3::ZERO;
if let Some(material) = materials.get_mut(&material_handle.0) {
material.terrain.scene_origin = scene_origin_uniform(camera_origin, projection);
}
} else {
let offset = hillshade.spawn_origin - camera_origin;
transform.translation = Vec3::new(offset.x as f32, offset.y as f32, offset.z as f32);
}
}
let Some(placeholder) = placeholder else {
return;
};
let Some(height_placeholder) = height_placeholder else {
return;
};
for terrain_mesh in terrain_meshes {
if existing_ids.contains(&terrain_mesh.tile) {
continue;
}
let gpu_displaced = supports_gpu_terrain_path(terrain_mesh, projection);
let (mesh_handle, material, transform) = if gpu_displaced {
let mesh_handle = get_or_create_shared_grid_mesh_handle(
&mut meshes,
&mut shared_grids,
terrain_mesh.grid_resolution,
);
let height_handle = get_or_create_height_texture_handle(
&mut images,
&mut height_cache,
terrain_mesh,
&height_placeholder.0,
);
let material = HillshadeMaterial {
hillshade_texture: placeholder.0.clone(),
terrain: terrain_uniforms_for_mesh(terrain_mesh, camera_origin, projection),
height_texture: height_handle,
..HillshadeMaterial::default()
};
(mesh_handle, material, Transform::IDENTITY)
} else {
let materialized = materialize_terrain_mesh(
terrain_mesh,
projection,
rustial_engine::skirt_height(
terrain_mesh.tile.zoom,
terrain_mesh.vertical_exaggeration as f64,
),
);
if materialized.positions.is_empty() || materialized.indices.is_empty() {
continue;
}
let positions: Vec<[f32; 3]> = materialized
.positions
.iter()
.map(|p| {
[
(p[0] - camera_origin.x) as f32,
(p[1] - camera_origin.y) as f32,
(p[2] - camera_origin.z) as f32,
]
})
.collect();
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, Default::default());
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, materialized.normals.clone());
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, materialized.uvs.clone());
mesh.insert_indices(Indices::U32(materialized.indices.clone()));
let mesh_handle = meshes.add(mesh);
let material = HillshadeMaterial {
hillshade_texture: placeholder.0.clone(),
height_texture: height_placeholder.0.clone(),
..HillshadeMaterial::default()
};
(mesh_handle, material, Transform::IDENTITY)
};
let material_handle = materials.add(material);
commands.spawn((
Mesh3d(mesh_handle),
MeshMaterial3d(material_handle),
transform,
Visibility::Hidden,
NoFrustumCulling,
HillshadeEntity {
tile_id: terrain_mesh.tile,
spawn_origin: camera_origin,
projection,
gpu_displaced,
mesh_generation: terrain_mesh.generation,
},
));
}
}