tawasuyu

tawasuyu/brahman

Rust 332TOML 127JSON 17Markdown 154710 Definitions

feat(cosmobiologia): esfera 3D — la piel real del cielo: estrellas + Vía Láctea

La «piel» de una esfera celeste no son continentes —esos van en la
Tierra— sino las estrellas y la Vía Láctea. Y a diferencia del brillo
especular (fijo a la pantalla), esta piel gira CON la esfera, así que
delata la rotación de un vistazo.

- Campo de estrellas isótropo, decorativo (no un catálogo real),
  generado con un hash determinista — no titila entre frames.
- Vía Láctea: una sobredensidad de estrellas tenues a lo largo del
  plano galáctico, ubicado con el polo galáctico real (J2000, AR
  192.859° / Dec +27.128°).
- Estrellas con brillo y tinte variables (azuladas / cálidas),
  atenuadas por profundidad. Van detrás de la rejilla, delante del
  sombreado — un fondo de planetario. Solo en tema oscuro.

36 tests verdes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

93856cd4d7617025de1837fcdca42c6ad9dceb59 · by sergio · (5 months ago) · parent 65c88ccf2 · browse the tree at this commit

Semantic delta · 13

crates/modules/cosmobiologia/cosmobiologia-render/src/sphere3d.rs

1 files · +136 −0

diff --git a/crates/modules/cosmobiologia/cosmobiologia-render/src/sphere3d.rs b/crates/modules/cosmobiologia/cosmobiologia-render/src/sphere3d.rs
index 96c68d3..23ecec2 100644
--- a/crates/modules/cosmobiologia/cosmobiologia-render/src/sphere3d.rs
+++ b/crates/modules/cosmobiologia/cosmobiologia-render/src/sphere3d.rs
@@ -86,6 +86,9 @@ pub struct SphereOpts {
     /// El horizonte local, el cénit del observador y el meridiano.
     /// Necesita `RenderModel::geo_latitude_deg`.
     pub show_horizon: bool,
+    /// El cielo de fondo: campo de estrellas + Vía Láctea. Solo se
+    /// dibuja en tema oscuro (en papel rompería la metáfora de imprenta).
+    pub show_sky: bool,
 }
 
 impl Default for SphereOpts {
@@ -99,6 +102,7 @@ impl Default for SphereOpts {
             show_bodies: true,
             show_signs: true,
             show_horizon: true,
+            show_sky: true,
         }
     }
 }
@@ -447,6 +451,109 @@ fn add_point_marker(
     ));
 }
 
+// --- Cielo de fondo: estrellas decorativas + Vía Láctea --------------
+
+/// Polo norte galáctico (J2000): AR 192.859°, Dec +27.128° — constante
+/// estándar IAU que fija el plano de la Vía Láctea.
+const GAL_POLE_RA: f32 = 192.859;
+const GAL_POLE_DEC: f32 = 27.128;
+
+/// Hash entero → f32 en [0,1). Determinista (variante de splitmix32):
+/// la misma entrada da siempre el mismo valor, así el campo de
+/// estrellas no titila ni salta entre frames.
+fn hash01(n: u32) -> f32 {
+    let mut x = n.wrapping_mul(0x9E37_79B9);
+    x ^= x >> 16;
+    x = x.wrapping_mul(0x85EB_CA6B);
+    x ^= x >> 13;
+    x = x.wrapping_mul(0xC2B2_AE35);
+    x ^= x >> 16;
+    (x as f32) / (u32::MAX as f32)
+}
+
+/// Punto uniforme sobre la esfera unidad a partir de dos uniformes.
+fn sphere_point(u1: f32, u2: f32) -> Vec3 {
+    let z = 2.0 * u1 - 1.0;
+    let rho = (1.0 - z * z).max(0.0).sqrt();
+    let theta = std::f32::consts::TAU * u2;
+    Vec3::new(rho * theta.cos(), rho * theta.sin(), z)
+}
+
+/// Vector unitario de una dirección ecuatorial (AR, Dec en grados).
+fn equatorial_dir(ra_deg: f32, dec_deg: f32) -> Vec3 {
+    let (sr, cr) = ra_deg.to_radians().sin_cos();
+    let (sd, cd) = dec_deg.to_radians().sin_cos();
+    Vec3::new(cd * cr, cd * sr, sd)
+}
+
+/// Empuja una estrella: un disco diminuto con brillo y un leve tinte
+/// (azulado o cálido). Va detrás de la rejilla pero delante del
+/// sombreado — un fondo de planetario.
+fn push_star(
+    items: &mut Vec<(f32, DrawCommand)>,
+    proj: &Projector,
+    size: f32,
+    pos: Vec3,
+    brightness: f32,
+    tint: f32,
+) {
+    let p = proj.project(pos);
+    let bright = brightness * brightness; // sesga hacia las tenues
+    let r = size * (0.0011 + 0.0026 * bright);
+    let alpha = (0.20 + 0.62 * bright) * depth_alpha(p.depth);
+    let col = if tint < 0.22 {
+        Rgba { r: 0.74, g: 0.81, b: 1.0, a: alpha }
+    } else if tint > 0.86 {
+        Rgba { r: 1.0, g: 0.86, b: 0.72, a: alpha }
+    } else {
+        Rgba { r: 0.95, g: 0.96, b: 1.0, a: alpha }
+    };
+    items.push((
+        p.depth - 3.0,
+        DrawCommand::Circle {
+            cx: p.x,
+            cy: p.y,
+            r,
+            stroke: None,
+            fill: Some(col),
+            stroke_w: 0.0,
+        },
+    ));
+}
+
+/// El cielo de fondo: un campo de estrellas isótropo —decorativo, no un
+/// catálogo real— más una sobredensidad de estrellas tenues a lo largo
+/// del plano galáctico, que dibuja la Vía Láctea. Ambos giran con la
+/// esfera, así que delatan su rotación de un vistazo.
+fn add_starfield(items: &mut Vec<(f32, DrawCommand)>, proj: &Projector, size: f32, eps: f32) {
+    const FONDO: u32 = 210;
+    for i in 0..FONDO {
+        let pos = sphere_point(hash01(i * 3), hash01(i * 3 + 1));
+        push_star(items, proj, size, pos, hash01(i * 3 + 2), hash01(i * 7 + 1));
+    }
+    // Vía Láctea — el plano galáctico ubicado con el polo galáctico real.
+    let gpole = rot_x(equatorial_dir(GAL_POLE_RA, GAL_POLE_DEC), eps);
+    let geq = great_circle_perp(gpole, 256);
+    const VIA: u32 = 240;
+    for i in 0..VIA {
+        let s = 9001 + i;
+        let idx = (hash01(s * 5) * geq.len() as f32) as usize % geq.len();
+        let on_eq = geq[idx];
+        // Latitud galáctica pequeña, concentrada cerca de 0 — producto
+        // de dos uniformes centrados → densa en el plano.
+        let u = hash01(s * 5 + 1) - 0.5;
+        let v = hash01(s * 5 + 2) - 0.5;
+        let b = (u * v * 4.0 * 13.0).to_radians();
+        let (sb, cb) = b.sin_cos();
+        let pos = Vec3::new(
+            on_eq.x * cb + gpole.x * sb,
+            on_eq.y * cb + gpole.y * sb,
+            on_eq.z * cb + gpole.z * sb,
+        );
+        push_star(items, proj, size, pos, hash01(s * 5 + 3) * 0.55, hash01(s * 5 + 4));
+    }
+}
+
 // =====================================================================
 // Composición
 // =====================================================================
@@ -480,6 +587,11 @@ pub fn compose_sphere(
     // --- Cuerpo de la esfera: sombreado con volumen ---
     add_sphere_shading(&mut items, pal, center, rad);
 
+    // --- Cielo de fondo: estrellas + Vía Láctea (solo tema oscuro) ---
+    if opts.show_sky && pal.is_dark {
+        add_starfield(&mut items, &proj, size, eps);
+    }
+
     // --- Rejilla: meridianos + paralelos de la eclíptica ---
     if opts.show_grid {
         let grid = pal.fg_muted.with_alpha(0.16);
@@ -858,6 +970,30 @@ mod tests {
         }
     }
 
+    #[test]
+    fn el_cielo_dibuja_un_campo_de_estrellas() {
+        let modelo = modelo_demo();
+        let con = compose_sphere(
+            &modelo,
+            &SphereView::default(),
+            &SphereOpts { show_sky: true, ..Default::default() },
+        );
+        let sin = compose_sphere(
+            &modelo,
+            &SphereView::default(),
+            &SphereOpts { show_sky: false, ..Default::default() },
+        );
+        let discos = |c: &[DrawCommand]| {
+            c.iter().filter(|d| matches!(d, DrawCommand::Circle { .. })).count()
+        };
+        assert!(
+            discos(&con) > discos(&sin) + 300,
+            "el cielo agrega cientos de estrellas: {} vs {}",
+            discos(&con),
+            discos(&sin),
+        );
+    }
+
     #[test]
     fn el_meridiano_contiene_cenit_polo_y_medio_cielo() {
         let eps = OBLICUIDAD_DEG.to_radians();