Skip to main content

wde_terrain/render/
renderer.rs

1use std::collections::HashMap;
2
3use bevy::prelude::*;
4use wde_renderer::prelude::*;
5
6use crate::manager::{CHUNK_COUNT, CHUNK_RENDER_SUBDIVISIONS, ChunkPos, Terrain, TerrainDirtyTile};
7
8/// CPU-side terrain renderer.
9/// Holds the two shared texture arrays (one heightmap array, one splatmap array) and
10/// the dirty queue that gets forwarded to the GPU each frame.
11#[derive(Component, Reflect)]
12#[reflect(Component)]
13pub struct TerrainRenderer {
14    /// Maps chunk position → array layer index (= tile index).
15    pub pos_to_layer: HashMap<ChunkPos, u32>,
16    /// Single R8Unorm texture array: one layer per chunk.
17    #[reflect(ignore)]
18    pub heightmap_array: Option<Handle<Texture>>,
19    /// Single Rgba8Unorm texture array: one layer per chunk.
20    #[reflect(ignore)]
21    pub splatmap_array: Option<Handle<Texture>>,
22    /// Dirty tiles forwarded from `Terrain` each frame.
23    #[reflect(ignore)]
24    pub dirty: Vec<TerrainDirtyTile>
25}
26
27impl TerrainRenderer {
28    /// Create the two shared texture arrays.
29    pub fn new(asset_server: &AssetServer) -> Self {
30        let layer_count = CHUNK_COUNT * CHUNK_COUNT;
31        let usages = TextureUsages::COPY_SRC
32            | TextureUsages::COPY_DST
33            | TextureUsages::TEXTURE_BINDING
34            | TextureUsages::STORAGE_BINDING;
35
36        let heightmap_array = asset_server.add(Texture {
37            label: "terrain_heightmap_array".to_string(),
38            size: (CHUNK_RENDER_SUBDIVISIONS, CHUNK_RENDER_SUBDIVISIONS),
39            format: TextureFormat::R8Unorm,
40            usages,
41            layer_count,
42            ..Default::default()
43        });
44
45        let splatmap_array = asset_server.add(Texture {
46            label: "terrain_splatmap_array".to_string(),
47            size: (CHUNK_RENDER_SUBDIVISIONS, CHUNK_RENDER_SUBDIVISIONS),
48            format: TextureFormat::Rgba8Unorm,
49            usages,
50            layer_count,
51            ..Default::default()
52        });
53
54        // Build the position→layer map in the same iteration order used by Terrain::load.
55        let mut pos_to_layer = HashMap::new();
56        let mut layer = 0u32;
57        for i in 0..CHUNK_COUNT {
58            for j in 0..CHUNK_COUNT {
59                let px = i as i32 - (CHUNK_COUNT as i32) / 2;
60                let pz = j as i32 - (CHUNK_COUNT as i32) / 2;
61                pos_to_layer.insert(IVec2::new(px, pz), layer);
62                layer += 1;
63            }
64        }
65
66        Self {
67            pos_to_layer,
68            heightmap_array: Some(heightmap_array),
69            splatmap_array: Some(splatmap_array),
70            dirty: Vec::new()
71        }
72    }
73
74    /// Move dirty tiles from `Terrain` into this renderer's queue.
75    pub fn extract_dirty(
76        mut renderer: Query<&mut TerrainRenderer>,
77        mut terrain: Query<&mut Terrain>
78    ) {
79        let (Ok(mut renderer), Ok(mut terrain)) = (renderer.single_mut(), terrain.single_mut())
80        else {
81            return;
82        };
83        renderer.dirty.clear();
84        renderer.dirty.append(&mut terrain.dirty_render);
85    }
86}