celestialsim/surface.rs
1//! Neutral CPU-baked surface type + the provider trait that supplies it.
2//!
3//! The chunked planet bakes each chunk's surface into three per-slot GPU
4//! buffers — colour (rgba8), height (`f32`, in the provider's own vertical unit)
5//! and normal (rgba8-packed) — and the realize/bake shaders read them when the
6//! surface is enabled. TWO paths fill those buffers:
7//!
8//! * **GPU procedural noise** — a compute shader computes them in place (the
9//! default; no provider, `surface_enabled == 0`).
10//! * **CPU async** — a worker pool ([`crate::bake_pool::BakePool`]) calls a
11//! [`CpuSurfaceProvider`] off the main thread to produce a [`ChunkSurface`],
12//! which the planet uploads into buffers 4/5/6 and the shaders read when the
13//! surface is enabled. The built-in provider is [`crate::noise_provider`]
14//! (CPU fBm terrain), used by a [`crate::builder::CesBuilder`] on the
15//! [`crate::builder::BuilderRoute::CpuNoise`] route.
16//!
17//! This module holds ONLY the neutral data type and the trait — no concrete
18//! surface source, no Godot rendering.
19
20use std::collections::HashSet;
21
22use godot::builtin::Vector3;
23
24use celestial_algo::clipmap::FaceFrame;
25use celestial_algo::quadtree::{Chunk, ChunkId};
26
27/// A resampled per-chunk surface: `color` is `tile_res·tile_res·4` RGBA8,
28/// `height` is `tile_res·tile_res` elevations (the provider's vertical unit,
29/// scaled by [`CpuSurfaceProvider::height_scale`] on the GPU), and `normal` is
30/// `tile_res·tile_res·4` rgba8-packed world normals — all row-major. The chunk's
31/// real footprint is the lower-left triangle, but the WHOLE square holds valid
32/// data (texels past the diagonal replicate the nearest diagonal sample; see
33/// [`crate::surface_tiles::build_patch`]).
34#[derive(Clone, Debug)]
35pub struct ChunkSurface {
36 pub color: Vec<u8>,
37 pub height: Vec<f32>,
38 pub normal: Vec<u8>,
39}
40
41/// A source of CPU-baked chunk surfaces (colour/height/normal). Implementors run
42/// on the bake worker pool ([`bake`](Self::bake) is called off the main thread,
43/// so it must be `Send + Sync` and use interior mutability for any shared state).
44/// The other hooks run on the main thread.
45pub trait CpuSurfaceProvider: Send + Sync {
46 /// Resample this chunk's surface at `tile_res × tile_res`. Called on a bake
47 /// worker thread. A streaming provider may also enqueue any data it still
48 /// needs (a side effect) so [`poll_refresh`](Self::poll_refresh) can later
49 /// report the chunk for a re-bake once that data arrives.
50 fn bake(&self, frame: &FaceFrame, chunk: &Chunk, tile_res: u32) -> ChunkSurface;
51
52 /// Surface displacement at `dir`, in the height buffer's unit (multiply by
53 /// [`height_scale`](Self::height_scale) · radius for world units). Steers LOD
54 /// toward the displaced surface. `None` ⇒ no data (treated as sea level).
55 fn sample_height(&self, _dir: Vector3) -> Option<f32> {
56 None
57 }
58
59 /// Chunks whose awaited data has arrived and should be re-baked (streaming).
60 /// Called each frame on the main thread; a non-streaming provider returns
61 /// empty. Returns whole [`Chunk`]s (not just ids) so the caller can re-queue
62 /// a bake directly.
63 fn poll_refresh(&self) -> Vec<Chunk> {
64 Vec::new()
65 }
66
67 /// Camera-driven cancellation hint: the set of chunk ids still in view.
68 /// Streaming providers drop bookkeeping / downloads for anything else.
69 fn set_wanted(&self, _ids: &HashSet<ChunkId>) {}
70
71 /// Is enough data present for the surface to be shown (the GPU
72 /// `surface_enabled` gate)? A streaming provider returns `false` until its base
73 /// map lands; a self-contained provider (noise) is always ready.
74 fn base_ready(&self) -> bool {
75 true
76 }
77
78 /// Per-height-unit displaced-radius factor for the GPU: the surface radius
79 /// becomes `radius · (1 + height · height_scale)`.
80 fn height_scale(&self) -> f32 {
81 0.0
82 }
83
84 /// External resource fetches in flight (streaming providers; for the HUD).
85 fn resources_in_flight(&self) -> usize {
86 0
87 }
88
89 /// Absolute path of any on-disk cache this provider uses (for the HUD).
90 fn cache_dir(&self) -> Option<String> {
91 None
92 }
93}
94