gioser

tawasuyu/llimphi

Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.

Rust 660Markdown 302TOML 157RON 312382 Definiciones

⎇ main llimphi-3d/src/dimensions.rs Rust · 156 líneas · 5.2 KB Historia Quién tocó qué crudo

refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel ccab39f14 · Sergio ·

1//! Dimensiones / mundos paralelos (M5) — `MOTOR-VOXEL.md` §3.8.
2//!
3//! Una **dimensión = un mundo voxel independiente** con su propio grid, su sol,
4//! su cielo (color de fondo) y sus entidades. "Viajar" = cambiar qué dimensión
5//! renderiza la cámara (un portal = un `switch`). No agrega complejidad de motor
6//! (cada dimensión reusa el `VoxelRenderer` sparse tal cual): es contenido.
7//!
8//! El [`Multiverse`] mantiene N dimensiones y la activa; cada una materializa su
9//! `VoxelRenderer` (su brick pool) perezosamente la primera vez que se la pinta,
10//! y queda "tibia" en memoria para que el switch sea instantáneo.
11
12use crate::camera::Camera3d;
13use crate::voxel::VoxelGrid;
14use crate::voxel_renderer::{Atmosphere, Entity3d, VoxelRenderer};
15
16/// Formato de la textura intermedia de Llimphi (target de `gpu_paint_with`).
17const FMT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
18
19/// Un mundo voxel independiente.
20pub struct Dimension {
21    pub name: String,
22    pub grid: VoxelGrid,
23    /// Color de fondo (cielo) sugerido para la pasada vello base.
24    pub sky: [u8; 3],
25    /// Dirección hacia el sol de esta dimensión.
26    pub sun_dir: [f32; 3],
27    /// Atmósfera (cielo + niebla) de esta dimensión. Default = niebla off, así
28    /// una dimensión sin configurar se comporta como en M5 (miss → discard).
29    pub atmosphere: Atmosphere,
30    /// Entidades (agentes) de esta dimensión; se copian al renderer por frame.
31    pub entities: Vec<Entity3d>,
32    renderer: Option<VoxelRenderer>,
33}
34
35impl Dimension {
36    /// Dimensión nueva con cielo/sol por defecto y sin entidades.
37    pub fn new(name: impl Into<String>, grid: VoxelGrid) -> Self {
38        Self {
39            name: name.into(),
40            grid,
41            sky: [18, 22, 32],
42            sun_dir: [0.5, 1.0, 0.35],
43            atmosphere: Atmosphere::default(),
44            entities: Vec::new(),
45            renderer: None,
46        }
47    }
48
49    pub fn with_sky(mut self, sky: [u8; 3]) -> Self {
50        self.sky = sky;
51        self
52    }
53    pub fn with_sun(mut self, sun_dir: [f32; 3]) -> Self {
54        self.sun_dir = sun_dir;
55        self
56    }
57    /// Activa cielo + niebla propios para esta dimensión (el `render` los aplica
58    /// al renderer). Con `fog_density > 0`, el motor pinta su propio cielo en los
59    /// misses (ya no se ve el fondo vello).
60    pub fn with_atmosphere(mut self, atmosphere: Atmosphere) -> Self {
61        self.atmosphere = atmosphere;
62        self
63    }
64    pub fn with_entities(mut self, entities: Vec<Entity3d>) -> Self {
65        self.entities = entities;
66        self
67    }
68}
69
70/// Conjunto de dimensiones con una activa. La cámara siempre ve la activa.
71pub struct Multiverse {
72    dims: Vec<Dimension>,
73    active: usize,
74    format: wgpu::TextureFormat,
75}
76
77impl Multiverse {
78    pub fn new(dims: Vec<Dimension>) -> Self {
79        Self {
80            dims,
81            active: 0,
82            format: FMT,
83        }
84    }
85
86    /// Cambia el formato de color del target (default `Rgba8Unorm`, la
87    /// intermedia de Llimphi). Sólo afecta a renderers aún no materializados.
88    pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
89        self.format = format;
90        self
91    }
92
93    pub fn count(&self) -> usize {
94        self.dims.len()
95    }
96    pub fn active(&self) -> usize {
97        self.active
98    }
99    pub fn active_name(&self) -> &str {
100        &self.dims[self.active].name
101    }
102    pub fn names(&self) -> Vec<String> {
103        self.dims.iter().map(|d| d.name.clone()).collect()
104    }
105    pub fn skies(&self) -> Vec<[u8; 3]> {
106        self.dims.iter().map(|d| d.sky).collect()
107    }
108
109    /// Viaja a la dimensión `i` (no-op si fuera de rango).
110    pub fn switch(&mut self, i: usize) {
111        if i < self.dims.len() {
112            self.active = i;
113        }
114    }
115    pub fn next(&mut self) {
116        self.active = (self.active + 1) % self.dims.len();
117    }
118    pub fn prev(&mut self) {
119        self.active = (self.active + self.dims.len() - 1) % self.dims.len();
120    }
121
122    pub fn active_dim(&self) -> &Dimension {
123        &self.dims[self.active]
124    }
125    pub fn active_dim_mut(&mut self) -> &mut Dimension {
126        &mut self.dims[self.active]
127    }
128
129    /// Ray-marchea la dimensión activa sobre `target`. Materializa su brick pool
130    /// la primera vez. Firma compatible con la closure de `gpu_paint_with`.
131    pub fn render(
132        &mut self,
133        device: &wgpu::Device,
134        queue: &wgpu::Queue,
135        encoder: &mut wgpu::CommandEncoder,
136        target: &wgpu::TextureView,
137        viewport: (u32, u32),
138        camera: &Camera3d,
139    ) {
140        let fmt = self.format;
141        let d = &mut self.dims[self.active];
142        let r = d
143            .renderer
144            .get_or_insert_with(|| VoxelRenderer::new(device, queue, fmt, &d.grid));
145        r.sun_dir = d.sun_dir;
146        r.atmosphere = d.atmosphere;
147        r.entities = d.entities.clone();
148        r.render(device, queue, encoder, target, viewport, camera);
149    }
150
151    /// Acceso al renderer ya materializado de la dimensión activa (para `sync`
152    /// incremental de mutaciones, stats, etc.). `None` si aún no se pintó.
153    pub fn active_renderer_mut(&mut self) -> Option<&mut VoxelRenderer> {
154        self.dims[self.active].renderer.as_mut()
155    }
156}