rustial-renderer-bevy 1.0.0

Bevy Engine renderer for the rustial 2.5D map engine
//! Dedicated hillshade overlay entity sync.

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::{TileId, materialize_terrain_mesh};
use std::collections::HashSet;

/// Synchronise prepared hillshade overlays to Bevy mesh entities.
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)
            .map_or(false, |&generation| generation != hillshade.mesh_generation);
        let gpu_path_changed = engine_gpu_path
            .get(&hillshade.tile_id)
            .map_or(false, |&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)
            .map_or(false, |&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,
            },
        ));
    }
}