tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
refresh: stack al día (vello 0.7 / wgpu 27 / parley 0.6) + motor 3D voxel
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}