feat(cosmobiologia): esfera 3D — switch de constelaciones + luz de la Vía Láctea
- Switch de constelaciones: botón flotante «● Constelaciones» (o tecla B) que las enciende y apaga en la esfera 3D. - La luminosidad se reparte: el brillo especular fijo a la pantalla se bajó mucho (no giraba, se sentía despegado), y en su lugar la Vía Láctea aporta un resplandor difuso a lo largo del plano galáctico — que SÍ gira con la esfera. Más intenso hacia el centro galáctico (Sagitario, como en el cielo real) y atenuado bajo el horizonte local: la franja como se ve desde la Tierra esa noche. 42 tests verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -171,6 +171,8 @@ pub struct CanvasState {
|
||||
pub sphere_3d: bool,
|
||||
/// Orientación de la esfera 3D — la muta el drag.
|
||||
pub sphere_view: SphereView,
|
||||
/// Si se dibujan las figuras de constelaciones en la esfera 3D.
|
||||
pub show_constellations: bool,
|
||||
drag_jog: Option<JogDragState>,
|
||||
drag_pan: Option<PanDragState>,
|
||||
drag_sphere: Option<SphereDragState>,
|
||||
@@ -258,6 +260,7 @@ impl Default for CanvasState {
|
||||
rectificacion: None,
|
||||
sphere_3d: false,
|
||||
sphere_view: SphereView::default(),
|
||||
show_constellations: true,
|
||||
drag_jog: None,
|
||||
drag_pan: None,
|
||||
drag_sphere: None,
|
||||
@@ -383,6 +386,12 @@ impl AstrologyCanvas {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Enciende o apaga las figuras de constelaciones en la esfera 3D.
|
||||
pub fn toggle_constellations(&mut self, cx: &mut Context<'_, Self>) {
|
||||
self.state.show_constellations = !self.state.show_constellations;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Resetea zoom y pan a sus defaults (1.0 y 0,0). No toca rotation
|
||||
/// ni time offset — esos son ortogonales y tienen su propio reset.
|
||||
pub fn reset_view(&mut self, cx: &mut Context<'_, Self>) {
|
||||
@@ -821,6 +830,10 @@ impl AstrologyCanvas {
|
||||
self.toggle_sphere(cx);
|
||||
return;
|
||||
}
|
||||
"b" | "B" => {
|
||||
self.toggle_constellations(cx);
|
||||
return;
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
self.toggle_layer(kind, cx);
|
||||
@@ -914,6 +927,7 @@ impl Render for AstrologyCanvas {
|
||||
&theme,
|
||||
render,
|
||||
self.state.sphere_view,
|
||||
self.state.show_constellations,
|
||||
self.state.view_scale,
|
||||
self.state.view_pan_x,
|
||||
self.state.view_pan_y,
|
||||
@@ -965,6 +979,36 @@ impl Render for AstrologyCanvas {
|
||||
)
|
||||
});
|
||||
|
||||
// Switch de constelaciones — solo en modo esfera 3D.
|
||||
let constellations_toggle = (matches!(self.state.mode, CanvasMode::Wheel { .. })
|
||||
&& self.state.sphere_3d)
|
||||
.then(|| {
|
||||
let on = self.state.show_constellations;
|
||||
let label = if on {
|
||||
"● Constelaciones"
|
||||
} else {
|
||||
"○ Constelaciones"
|
||||
};
|
||||
div()
|
||||
.absolute()
|
||||
.top(px(44.0))
|
||||
.right(px(12.0))
|
||||
.px(px(11.0))
|
||||
.py(px(5.0))
|
||||
.rounded(px(6.0))
|
||||
.bg(theme.bg_panel_alt.clone())
|
||||
.border_1()
|
||||
.border_color(theme.border)
|
||||
.text_size(px(11.0))
|
||||
.text_color(if on { theme.fg_text } else { theme.fg_muted })
|
||||
.cursor_pointer()
|
||||
.child(label)
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(|this, _, _w, cx| this.toggle_constellations(cx)),
|
||||
)
|
||||
});
|
||||
|
||||
// Depth field: capa absoluta detrás del body, ocupa todo el
|
||||
// canvas. Vignette radial — el centro queda claro y los
|
||||
// bordes se oscurecen, dando profundidad sin "ruido" de
|
||||
@@ -1005,6 +1049,7 @@ impl Render for AstrologyCanvas {
|
||||
.child(body),
|
||||
)
|
||||
.children(sphere_toggle)
|
||||
.children(constellations_toggle)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1080,6 +1125,7 @@ fn render_sphere(
|
||||
theme: &Theme,
|
||||
render: &RenderModel,
|
||||
view: SphereView,
|
||||
show_constellations: bool,
|
||||
view_scale: f32,
|
||||
view_pan_x: f32,
|
||||
view_pan_y: f32,
|
||||
@@ -1093,6 +1139,7 @@ fn render_sphere(
|
||||
} else {
|
||||
Palette::light()
|
||||
},
|
||||
show_constellations,
|
||||
..Default::default()
|
||||
};
|
||||
let commands = compose_sphere(render, &view, &opts);
|
||||
|
||||
@@ -313,15 +313,16 @@ fn add_sphere_shading(
|
||||
cy: center,
|
||||
r: rad * (0.95 - 0.95 * t),
|
||||
stroke: None,
|
||||
fill: Some(glow.with_alpha(0.04)),
|
||||
fill: Some(glow.with_alpha(0.028)),
|
||||
stroke_w: 0.0,
|
||||
},
|
||||
));
|
||||
}
|
||||
// Brillo especular desplazado hacia la luz.
|
||||
// Brillo especular desplazado hacia la luz — tenue: la luminosidad
|
||||
// viva la reparte la Vía Láctea, que sí gira con la esfera.
|
||||
let hx = center - rad * 0.34;
|
||||
let hy = center - rad * 0.34;
|
||||
const HALO: usize = 7;
|
||||
const HALO: usize = 6;
|
||||
for i in 0..HALO {
|
||||
let t = i as f32 / (HALO - 1) as f32;
|
||||
items.push((
|
||||
@@ -331,7 +332,7 @@ fn add_sphere_shading(
|
||||
cy: hy,
|
||||
r: rad * 0.5 * (1.0 - t),
|
||||
stroke: None,
|
||||
fill: Some(highlight.with_alpha(0.05)),
|
||||
fill: Some(highlight.with_alpha(0.018)),
|
||||
stroke_w: 0.0,
|
||||
},
|
||||
));
|
||||
@@ -562,6 +563,10 @@ fn add_point_marker(
|
||||
/// 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;
|
||||
/// Centro galáctico (Sgr A*, J2000): AR 266.405°, Dec −28.936°. Hacia
|
||||
/// ahí la Vía Láctea es más brillante.
|
||||
const GAL_CENTER_RA: f32 = 266.405;
|
||||
const GAL_CENTER_DEC: f32 = -28.936;
|
||||
|
||||
/// Hash entero → f32 en [0,1). Determinista (variante de splitmix32):
|
||||
/// la misma entrada da siempre el mismo valor, así el campo de
|
||||
@@ -659,6 +664,46 @@ fn add_starfield(items: &mut Vec<(f32, DrawCommand)>, proj: &Projector, size: f3
|
||||
}
|
||||
}
|
||||
|
||||
/// El resplandor difuso de la Vía Láctea — una luminosidad repartida a
|
||||
/// lo largo del plano galáctico, no un brillo fijo a la pantalla. Gira
|
||||
/// con la esfera. Es más intensa hacia el centro galáctico (en
|
||||
/// Sagitario, como en el cielo real) y, si hay horizonte, se atenúa en
|
||||
/// la parte que queda bajo tierra esa noche — la franja como se ve
|
||||
/// desde la Tierra ese día.
|
||||
fn add_milky_way_glow(
|
||||
items: &mut Vec<(f32, DrawCommand)>,
|
||||
proj: &Projector,
|
||||
eps: f32,
|
||||
size: f32,
|
||||
zenith: Option<Vec3>,
|
||||
) {
|
||||
let gpole = rot_x(equatorial_dir(GAL_POLE_RA, GAL_POLE_DEC), eps);
|
||||
let gcenter = rot_x(equatorial_dir(GAL_CENTER_RA, GAL_CENTER_DEC), eps);
|
||||
let band = Rgba::opaque(0.78, 0.82, 0.96);
|
||||
for p3 in great_circle_perp(gpole, 54) {
|
||||
// Más brillo hacia el centro galáctico.
|
||||
let toward = (p3.dot(gcenter) * 0.5 + 0.5).clamp(0.0, 1.0);
|
||||
let bright = 0.28 + 0.72 * toward * toward;
|
||||
// Atenuada bajo el horizonte local (no se ve esa noche).
|
||||
let vis = match zenith {
|
||||
Some(z) if p3.dot(z) < 0.0 => 0.40,
|
||||
_ => 1.0,
|
||||
};
|
||||
let p = proj.project(p3);
|
||||
items.push((
|
||||
p.depth - 4.0,
|
||||
DrawCommand::Circle {
|
||||
cx: p.x,
|
||||
cy: p.y,
|
||||
r: size * 0.045,
|
||||
stroke: None,
|
||||
fill: Some(band.with_alpha(0.030 * bright * vis * depth_alpha(p.depth))),
|
||||
stroke_w: 0.0,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Estrellas fijas notables ----------------------------------------
|
||||
|
||||
/// Latitud eclíptica (grados, J2000) de las estrellas fijas notables
|
||||
@@ -982,8 +1027,9 @@ 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) ---
|
||||
// --- Cielo de fondo: Vía Láctea + estrellas (solo tema oscuro) ---
|
||||
if opts.show_sky && pal.is_dark {
|
||||
add_milky_way_glow(&mut items, &proj, eps, size, zenith);
|
||||
add_starfield(&mut items, &proj, size, eps);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user