gioser

tawasuyu/llimphi

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

Rust 484Markdown 258TOML 131RON 38118 Definitions

⎇ main llimphi-voxel/src/potential.rs Rust · 160 lines · 6.1 KB History Who touched what raw

chore: refresco desde el monorepo — 27 crates nuevos (anim, lottie, svg, mesh, image, layer, test, wire-view, widgets rag-sidebar/router/lazy-list/rive-button) y sync de los 95 existentes 93253cc22 · Sergio ·

1//! **Objeto potencial**: una cosa colocada en el mundo en su forma *latente* que,
2//! ante un disparador (aquí: el aterrizaje/nacimiento), **eclosiona** en la entidad
3//! que será. El primero es el [`Egg`] — un *huevo* que lleva adentro su potencial
4//! ([`Hatchling`]: qué nace, con qué edad y colores) y al abrirse **da a luz** un
5//! [`Actor`] recién nacido.
6//!
7//! Es la pieza del corto: la cámara cae, ve el huevo, y al tocar suelo el huevo se
8//! abre y nace el niño. La forma generaliza (otros objetos potenciales podrían nacer
9//! plantas, animales, estructuras); hoy el caso es el *huevito humano*.
10
11use llimphi_3d::glam::{Mat4, Vec3};
12use llimphi_3d::{push_cube, Vertex3d};
13
14use crate::actor::{Actor, Age};
15
16/// `T(center) · S(size)` — caja centrada en `center` escalada a `size`.
17fn trs(center: Vec3, size: Vec3) -> Mat4 {
18    Mat4::from_translation(center) * Mat4::from_scale(size)
19}
20
21/// **Lo que un huevo va a ser**: el potencial latente. Hoy un personaje (edad +
22/// colores); el motor lo materializa como [`Actor`] al nacer.
23#[derive(Debug, Clone, Copy)]
24pub struct Hatchling {
25    /// Edad con la que nace (el corto: [`Age::Baby`]).
26    pub age: Age,
27    pub skin: [f32; 3],
28    pub shirt: [f32; 3],
29    pub pants: [f32; 3],
30}
31
32impl Hatchling {
33    /// Un **huevito humano**: nace bebé, con una paleta cálida por defecto.
34    pub fn human(age: Age) -> Self {
35        Self {
36            age,
37            skin: [0.90, 0.74, 0.60],
38            shirt: [0.86, 0.46, 0.44],
39            pants: [0.22, 0.24, 0.32],
40        }
41    }
42}
43
44/// **Huevo** — un objeto potencial con forma de ovoide voxel. `hatch` va de `0`
45/// (intacto) a `1` (abierto: la tapa superior se levanta y se inclina, dejando
46/// salir al recién nacido). `pos` es la **base** del huevo en mundo.
47#[derive(Debug, Clone, Copy)]
48pub struct Egg {
49    /// Base del huevo, en mundo (mismas coords que el terreno/actor).
50    pub pos: Vec3,
51    /// Rumbo (yaw) — orienta al recién nacido y la apertura.
52    pub facing: f32,
53    /// Altura del huevo.
54    pub size: f32,
55    /// Color de la cáscara.
56    pub shell: [f32; 3],
57    /// Progreso de eclosión `[0,1]` (0 = intacto, 1 = abierto).
58    pub hatch: f32,
59    /// Qué nace de él.
60    pub becomes: Hatchling,
61}
62
63/// Fracción de la altura donde el huevo se "raja" (la tapa de arriba es la que se
64/// abre).
65const CRACK: f32 = 0.55;
66/// Slabs verticales con los que se aproxima el ovoide.
67const SLABS: usize = 9;
68
69impl Egg {
70    /// Huevo intacto en `pos` (base) de altura `size`, con el potencial `becomes`.
71    pub fn new(pos: Vec3, size: f32, becomes: Hatchling) -> Self {
72        Self { pos, facing: 0.0, size, shell: [0.94, 0.92, 0.86], hatch: 0.0, becomes }
73    }
74
75    /// Avanza la eclosión `dt` segundos a `rate` (`1/seg` ⇒ se abre en ~1 s).
76    pub fn advance(&mut self, dt: f32, rate: f32) {
77        self.hatch = (self.hatch + dt * rate).clamp(0.0, 1.0);
78    }
79
80    /// `true` cuando el huevo terminó de abrirse.
81    pub fn is_open(&self) -> bool {
82        self.hatch >= 1.0
83    }
84
85    /// Matriz de ubicación en mundo (igual rol que [`Actor::model`]).
86    pub fn model(&self) -> Mat4 {
87        Mat4::from_translation(self.pos) * Mat4::from_rotation_y(self.facing)
88    }
89
90    /// **Da a luz**: materializa el potencial como un [`Actor`] recién nacido, en la
91    /// posición y rumbo del huevo, con la edad/colores de [`Hatchling`].
92    pub fn newborn(&self) -> Actor {
93        Actor::new(self.pos, self.facing)
94            .with_age(self.becomes.age)
95            .with_colors(self.becomes.skin, self.becomes.shirt, self.becomes.pants)
96    }
97
98    /// Malla del cascarón en espacio local (base en el origen, ubicar con
99    /// [`model`](Self::model)). Ovoide por slabs horizontales; la tapa por encima de
100    /// [`CRACK`] **se separa** (se levanta y se inclina) según `hatch`, bisagra en la
101    /// línea de la rajadura.
102    pub fn mesh(&self) -> (Vec<Vertex3d>, Vec<u16>) {
103        let mut v = Vec::with_capacity(8 * SLABS);
104        let mut i = Vec::with_capacity(36 * SLABS);
105        let half_w = self.size * 0.42; // medio-ancho máximo (en la panza)
106        let thick = self.size / SLABS as f32;
107        let pivot = Vec3::new(0.0, CRACK * self.size, 0.0);
108
109        // Transform de la tapa: bisagra en la rajadura, se levanta y se inclina.
110        let lift = self.hatch * self.size * 0.75;
111        let tilt = self.hatch * 0.9;
112        let cap = Mat4::from_translation(pivot + Vec3::new(0.0, lift, 0.0))
113            * Mat4::from_rotation_z(tilt)
114            * Mat4::from_translation(-pivot);
115
116        for k in 0..SLABS {
117            let tc = (k as f32 + 0.5) / SLABS as f32; // centro del slab en [0,1]
118            // Medio-ancho del ovoide (elipse): 0 en los polos, máx en la panza.
119            let r = (half_w * (4.0 * tc * (1.0 - tc)).sqrt()).max(self.size * 0.04);
120            let y = tc * self.size;
121            let base = trs(Vec3::new(0.0, y, 0.0), Vec3::new(r * 2.0, thick * 1.04, r * 2.0));
122            let m = if tc > CRACK { cap * base } else { base };
123            push_cube(&mut v, &mut i, m, self.shell);
124        }
125        (v, i)
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn el_recien_nacido_hereda_edad_y_lugar() {
135        let pos = Vec3::new(5.0, -2.0, 3.0);
136        let egg = Egg::new(pos, 1.0, Hatchling::human(Age::Baby));
137        let baby = egg.newborn();
138        assert_eq!(baby.age, Age::Baby);
139        assert_eq!(baby.pos, pos);
140    }
141
142    #[test]
143    fn la_eclosion_avanza_y_se_satura() {
144        let mut egg = Egg::new(Vec3::ZERO, 1.0, Hatchling::human(Age::Baby));
145        assert!(!egg.is_open());
146        egg.advance(0.5, 1.0);
147        assert!((egg.hatch - 0.5).abs() < 1e-5);
148        egg.advance(10.0, 1.0); // se pasa → se clampa
149        assert!(egg.is_open() && egg.hatch == 1.0);
150    }
151
152    #[test]
153    fn al_abrirse_la_tapa_se_levanta() {
154        let intact = Egg::new(Vec3::ZERO, 2.0, Hatchling::human(Age::Baby));
155        let mut open = intact;
156        open.hatch = 1.0;
157        let top = |e: &Egg| e.mesh().0.iter().map(|v| v.pos[1]).fold(f32::MIN, f32::max);
158        assert!(top(&open) > top(&intact) + 0.5, "la tapa abierta queda más alta: {} vs {}", top(&open), top(&intact));
159    }
160}