feat(gioser): sol detrás, título central, drawers MD + pluma agnóstico
Visual de la chacana retrabajado contra chakana.png de referencia:
- Sol detrás (gauss + corona, masked al interior de la chacana — sólo
asoma por la superficie de la cruz, no se cuela afuera).
- Doble outline dorado (línea principal + paralela offset 0.020), color
CHACANA_LINE pasa de cyan helado a dorado-ámbar del logo.
- Interior con niebla violeta-noche (u_dark_color) y rayos radiales
sutiles desde el centro, modulados por sin(t * 0.3).
- Aro doble exterior: ring fino interior + ring grueso con 4 grupos de
3 puntos cardinales (calculados angularmente, no rayos largos).
- WORLD_SCALE 1.45→1.05, MAX_TILT 35°→28° (más sólido, menos caricaturesco).
Título "GioSer" centrado dentro de la superficie de la chacana, sin
subtítulo. Se inclina junto con la chacana vía CSS perspective +
rotateX/rotateY desde u-tilt-x/y inyectadas cada frame por WASM.
Botones (4 tips):
- Reposicionados a `arm_extent * 1.32` (entre punta y aro grueso).
- Bigger: min-width 168px, glyph 54px, label Cinzel 0.95rem.
- Doble anillo en hover (::before con border + glow).
- Cuando un drawer se abre, fade-out de tips + canvas + brand.
Drawers MD (uno por elemento):
- `<aside class="drawer drawer-{element}">` con transform-origin desde
CSS vars (--origin-x/y) seteadas por WASM al click — crece desde la
posición exacta del botón hasta fullscreen en 700ms con cubic-bezier.
- Ambience por elemento: AIRE (radial drift), FUEGO (flicker keyframe),
AGUA (tide vertical), TIERRA (warm earth gradient).
- Cerrado con botón X, Escape o data-close-drawer.
- Carga MD desde ./md/{element}.md via spawn_local + Reader::open_url.
Pluma (visor MD agnóstico, dos crates nuevos):
- `crates/modules/pluma/pluma-md` — wrapper sobre pulldown-cmark 0.12.
API: to_html(), to_themed_html(md, theme) con sanitización del theme,
events() para AST stream. GFM completo. No deps web. 5 tests.
- `crates/modules/pluma/pluma-reader-web` — toma HtmlElement, expone
open_url async (fetch via wasm-bindgen-futures), render_md sync,
show_loading/show_error. NO inyecta CSS — el host estiliza
`.pluma-doc[data-pluma-theme="..."]` con sus colores.
CSS pluma-doc completo: h1/h2/h3, code/pre con border-left accent,
blockquote, tables, lists, hr gradient. Loader spinner + error state.
Placeholders en md/{aire,fuego,tierra,agua}.md con texto seed.
Workspace verde + 18 tests (6 geom + 4 palette + 3 physics + 5 pluma-md).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,14 @@
|
||||
//! Renderer WebGL2 que compone geometría + física + paleta + shaders en pantalla.
|
||||
//!
|
||||
//! Es agnóstico del DOM: el caller monta el `<canvas>`, pasa eventos de mouse,
|
||||
//! y llama `render(time_ms)` desde un `requestAnimationFrame`.
|
||||
//! Es agnóstico del DOM: el caller monta el `<canvas>`, le pasa eventos
|
||||
//! de mouse y llama `render(time_ms)` desde un `requestAnimationFrame`.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! let mut r = Renderer::new(&canvas)?;
|
||||
//! r.resize(w, h);
|
||||
//! // por cada mousemove:
|
||||
//! r.set_mouse_px(dx, dy);
|
||||
//! // por cada frame:
|
||||
//! r.render(time_ms);
|
||||
//! ```
|
||||
//!
|
||||
//! El layout sigue el patrón de `yahweh_launcher::launch_app(title, size, factory)`:
|
||||
//! una sola línea para arrancar, escena auto-contenida. Cuando exista `yahweh-web`,
|
||||
//! este renderer es el "factory" que recibirá.
|
||||
|
||||
use gioser_geom::ChacanaSpec;
|
||||
use gioser_palette::{cosmos, Rgb};
|
||||
@@ -31,15 +25,16 @@ use web_sys::{
|
||||
};
|
||||
|
||||
const RAD: f32 = core::f32::consts::PI / 180.0;
|
||||
/// Inclinación máxima en cada eje. 35° hace que las puntas se desplacen
|
||||
/// visiblemente en pantalla — los botones DOM cabalgan ese movimiento.
|
||||
const MAX_TILT_DEG: f32 = 35.0;
|
||||
/// Escala mundo→viewport: la chacana clásica con arm_extent ≈ 0.65 ocupa
|
||||
/// ~94% del eje vertical del viewport, dejando aire para aros y glow.
|
||||
const WORLD_SCALE: f32 = 1.45;
|
||||
const DEG: f32 = 180.0 / core::f32::consts::PI;
|
||||
/// Inclinación máxima en cada eje. 28° = movimiento bien legible pero
|
||||
/// no caricaturesco; la chacana se siente "pesada y noble".
|
||||
const MAX_TILT_DEG: f32 = 28.0;
|
||||
/// Escala mundo→viewport: con arm_extent=0.65 + aro a 1.45×, la chacana
|
||||
/// + aro entran cómodos con margen para botones DOM más allá del aro.
|
||||
const WORLD_SCALE: f32 = 1.05;
|
||||
|
||||
/// Re-export para apps: identidad (id, color, label) de cada punta cardinal,
|
||||
/// en el orden `[N, E, S, W]` de `ChacanaSpec::tips()`.
|
||||
/// Identidad de cada cardinal (id, color de acento, label visible).
|
||||
/// Orden `[N, E, S, W]` coincide con `ChacanaSpec::tips()`.
|
||||
pub mod tips {
|
||||
use gioser_palette::{elements, Rgb};
|
||||
pub const ORDER: [(&str, Rgb, &str); 4] = [
|
||||
@@ -180,6 +175,7 @@ impl Renderer {
|
||||
"u_line_color",
|
||||
"u_rim_color",
|
||||
"u_sun_color",
|
||||
"u_dark_color",
|
||||
"u_sun_pulse",
|
||||
],
|
||||
)
|
||||
@@ -190,9 +186,7 @@ impl Renderer {
|
||||
let (chacana_vao, chacana_quad_count) =
|
||||
upload_quad(&gl, &chacana_quad_verts, 0).map_err(JsValue::from)?;
|
||||
|
||||
// Spring sub-crítico, frecuencia baja: sensación de cuerpo pesado
|
||||
// que se inclina hacia el cursor con overshoot orgánico (~10%).
|
||||
let tilt = SpringDamper2::new(1.8, 0.62);
|
||||
let tilt = SpringDamper2::new(1.7, 0.65);
|
||||
|
||||
Ok(Self {
|
||||
gl,
|
||||
@@ -216,7 +210,6 @@ impl Renderer {
|
||||
.viewport(0, 0, self.viewport.0 as i32, self.viewport.1 as i32);
|
||||
}
|
||||
|
||||
/// Mouse en pixeles desde el centro del canvas (+x derecha, +y arriba).
|
||||
pub fn set_mouse_px(&mut self, x: f32, y: f32) {
|
||||
let (w, h) = self.viewport;
|
||||
if h == 0 {
|
||||
@@ -227,20 +220,28 @@ impl Renderer {
|
||||
let mx = (x / half_h).clamp(-aspect, aspect);
|
||||
let my = (y / half_h).clamp(-1.0, 1.0);
|
||||
self.mouse = (mx, my);
|
||||
|
||||
let max_tilt = MAX_TILT_DEG * RAD;
|
||||
// Pitch (+rotX) cuando mouse arriba: el tope se acerca al viewer.
|
||||
// Yaw (-rotY) cuando mouse derecha: el lado derecho se acerca.
|
||||
let target = [my * max_tilt, -mx * max_tilt / aspect];
|
||||
self.tilt.set_target(target);
|
||||
}
|
||||
|
||||
/// Posición proyectada (NDC `[-1, 1]²`) de cada tip cardinal en orden N/E/S/W.
|
||||
/// El caller la usa para anclar el DOM.
|
||||
/// Posición proyectada NDC de cada tip cardinal `[N, E, S, W]`.
|
||||
pub fn tips_ndc(&self) -> [(f32, f32); 4] {
|
||||
self.points_ndc(&self.chacana.tips())
|
||||
}
|
||||
|
||||
/// Posición NDC de un punto en cualquier radio cardinal (factor sobre
|
||||
/// `arm_extent`). Útil para anclar los botones DOM más allá de la chacana
|
||||
/// pero dentro del aro.
|
||||
pub fn cardinal_positions_ndc(&self, radius_factor: f32) -> [(f32, f32); 4] {
|
||||
let r = self.chacana.arm_extent() * radius_factor;
|
||||
self.points_ndc(&[(0.0, r), (r, 0.0), (0.0, -r), (-r, 0.0)])
|
||||
}
|
||||
|
||||
fn points_ndc(&self, pts: &[(f32, f32); 4]) -> [(f32, f32); 4] {
|
||||
let mvp = self.build_mvp();
|
||||
let mut out = [(0.0_f32, 0.0_f32); 4];
|
||||
for (i, t) in self.chacana.tips().iter().enumerate() {
|
||||
for (i, t) in pts.iter().enumerate() {
|
||||
let p = mvp * Vec4::new(t.0, t.1, 0.0, 1.0);
|
||||
let w = if p.w == 0.0 { 1.0 } else { p.w };
|
||||
out[i] = (p.x / w, p.y / w);
|
||||
@@ -252,12 +253,17 @@ impl Renderer {
|
||||
&self.chacana
|
||||
}
|
||||
|
||||
/// Posición normalizada del mouse en clip-space; útil para que el caller
|
||||
/// añada parallax sutil a elementos DOM independientes de la chacana.
|
||||
pub fn mouse_clip(&self) -> (f32, f32) {
|
||||
self.mouse
|
||||
}
|
||||
|
||||
/// Devuelve `(pitch_deg, yaw_deg)` actuales del spring de tilt.
|
||||
/// El caller los inyecta como CSS vars en el contenedor del título para
|
||||
/// que el HTML se tumbe junto con la chacana renderizada en GL.
|
||||
pub fn tilt_degrees(&self) -> (f32, f32) {
|
||||
(self.tilt.position[0] * DEG, self.tilt.position[1] * DEG)
|
||||
}
|
||||
|
||||
fn build_mvp(&self) -> Mat4 {
|
||||
let (w, h) = self.viewport;
|
||||
let aspect = w as f32 / h as f32;
|
||||
@@ -276,7 +282,6 @@ impl Renderer {
|
||||
((time_ms - self.last_time_ms) as f32 / 1000.0).clamp(0.0, 1.0 / 15.0)
|
||||
};
|
||||
self.last_time_ms = time_ms;
|
||||
|
||||
let sub = 4;
|
||||
let sub_dt = dt / sub as f32;
|
||||
for _ in 0..sub {
|
||||
@@ -293,7 +298,7 @@ impl Renderer {
|
||||
gl.clear_color(0.02, 0.015, 0.04, 1.0);
|
||||
gl.clear(GL::COLOR_BUFFER_BIT);
|
||||
|
||||
// ---- Cosmos ----
|
||||
// Cosmos
|
||||
gl.use_program(Some(&self.cosmos_prog.program));
|
||||
if let Some(u) = self.cosmos_prog.u("u_resolution") {
|
||||
gl.uniform2f(Some(u), self.viewport.0 as f32, self.viewport.1 as f32);
|
||||
@@ -311,7 +316,7 @@ impl Renderer {
|
||||
gl.bind_vertex_array(Some(&self.cosmos_vao));
|
||||
gl.draw_arrays(GL::TRIANGLES, 0, 6);
|
||||
|
||||
// ---- Chacana (blend aditivo para que el glow sume luz) ----
|
||||
// Chacana (blend aditivo para que dorado y sol sumen luz al cosmos)
|
||||
gl.blend_func(GL::SRC_ALPHA, GL::ONE);
|
||||
gl.use_program(Some(&self.chacana_prog.program));
|
||||
let mvp = self.build_mvp();
|
||||
@@ -333,6 +338,7 @@ impl Renderer {
|
||||
upload_rgb(gl, self.chacana_prog.u("u_line_color"), cosmos::CHACANA_LINE);
|
||||
upload_rgb(gl, self.chacana_prog.u("u_rim_color"), cosmos::CHACANA_RIM);
|
||||
upload_rgb(gl, self.chacana_prog.u("u_sun_color"), cosmos::SUN_CORE);
|
||||
upload_rgb(gl, self.chacana_prog.u("u_dark_color"), cosmos::CHACANA_DARK);
|
||||
if let Some(u) = self.chacana_prog.u("u_sun_pulse") {
|
||||
gl.uniform1f(Some(u), self.sun_pulse);
|
||||
}
|
||||
|
||||
@@ -87,12 +87,14 @@ pub mod cosmos {
|
||||
pub const NEBULA_A: Rgb = Rgb(0.220, 0.130, 0.380);
|
||||
/// Nebulosa exterior — azul profundo.
|
||||
pub const NEBULA_B: Rgb = Rgb(0.080, 0.180, 0.320);
|
||||
/// Núcleo solar central.
|
||||
pub const SUN_CORE: Rgb = Rgb(1.000, 0.860, 0.520);
|
||||
/// Líneas de la chacana — cyan helado.
|
||||
pub const CHACANA_LINE: Rgb = Rgb(0.55, 0.92, 1.00);
|
||||
/// Aro de fuego del logo — dorado-ámbar.
|
||||
pub const CHACANA_RIM: Rgb = Rgb(0.95, 0.65, 0.32);
|
||||
/// Núcleo solar central — amarillo cálido, base del halo dorado.
|
||||
pub const SUN_CORE: Rgb = Rgb(1.000, 0.870, 0.540);
|
||||
/// Línea principal de la chacana — dorado/ámbar luminoso (color del logo).
|
||||
pub const CHACANA_LINE: Rgb = Rgb(0.96, 0.74, 0.40);
|
||||
/// Aro/rim cálido más profundo — ámbar tostado.
|
||||
pub const CHACANA_RIM: Rgb = Rgb(0.88, 0.58, 0.28);
|
||||
/// Niebla oscura del interior de la chacana — violeta-negro translúcido.
|
||||
pub const CHACANA_DARK: Rgb = Rgb(0.04, 0.03, 0.10);
|
||||
/// Polvo de estrellas.
|
||||
pub const STARDUST: Rgb = Rgb(0.85, 0.88, 1.00);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
//! Fuentes GLSL ES 3.00 para GioSer.
|
||||
//!
|
||||
//! Cada `const &str` es un shader completo, listo para pasar a
|
||||
//! `gl.shaderSource()`. No dependemos de ningún backend; el cliente
|
||||
//! decide cómo compilarlos. Convención: precision `highp float`,
|
||||
//! atributo `a_pos`, varying `v_*`, uniforms `u_*`.
|
||||
//! Cada `const &str` es un shader completo listo para `gl.shaderSource()`.
|
||||
//! Convención: precision `highp float`, atributo `a_pos`, varying `v_*`,
|
||||
//! uniforms `u_*`.
|
||||
|
||||
#![no_std]
|
||||
|
||||
@@ -20,9 +19,9 @@ void main() {
|
||||
}
|
||||
";
|
||||
|
||||
/// Fragment del fondo cósmico: nubes FBM en 3 capas, 3 estratos de
|
||||
/// estrellas con titilación independiente, viñeta, y 4 meteoros
|
||||
/// procedurales que cruzan el cielo periódicamente.
|
||||
/// Fragment del fondo cósmico: nubes FBM en 3 capas con drift visible,
|
||||
/// 3 estratos de estrellas con titilación independiente, viñeta radial,
|
||||
/// 4 meteoros procedurales con vida cíclica.
|
||||
pub const FS_COSMOS: &str = "#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_clip;
|
||||
@@ -63,7 +62,6 @@ float fbm(vec2 p) {
|
||||
return v;
|
||||
}
|
||||
|
||||
// Meteoro procedural: trazo brillante con cola, vida 1.6s, respawnea solo.
|
||||
float meteor(vec2 uv, float seed) {
|
||||
float period = 6.5 + 4.0 * hash11(seed * 17.0);
|
||||
float t_seeded = u_time + seed * 19.0;
|
||||
@@ -81,20 +79,16 @@ float meteor(vec2 uv, float seed) {
|
||||
hash21(vec2(seed + 1.0, epoch)) * 1.6 - 0.8,
|
||||
-0.7 - hash21(vec2(seed + 2.0, epoch)) * 0.6
|
||||
));
|
||||
|
||||
vec2 head = origin + dir * t * 2.1;
|
||||
vec2 tail = head - dir * 0.24;
|
||||
|
||||
vec2 pa = uv - tail;
|
||||
vec2 ba = head - tail;
|
||||
float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0);
|
||||
float dist = length(pa - ba * h);
|
||||
|
||||
float perpGlow = exp(-dist * 420.0);
|
||||
float trailFalloff = smoothstep(0.0, 1.0, h);
|
||||
float headPulse = exp(-dist * 900.0);
|
||||
float lifeFade = sin(t * 3.14159);
|
||||
|
||||
return (perpGlow * trailFalloff + headPulse * 1.4) * lifeFade;
|
||||
}
|
||||
|
||||
@@ -103,11 +97,9 @@ void main() {
|
||||
vec2 uv = v_clip;
|
||||
uv.x *= aspect;
|
||||
|
||||
// === NUBES (drift visible, 5× más rápido que la versión anterior) ===
|
||||
vec2 d1 = vec2( u_time * 0.055, u_time * 0.022) + u_parallax * 0.10;
|
||||
vec2 d2 = vec2(-u_time * 0.085, u_time * 0.058) + u_parallax * 0.22;
|
||||
vec2 d3 = vec2( u_time * 0.130, -u_time * 0.095) + u_parallax * 0.40;
|
||||
|
||||
float n1 = fbm(uv * 0.85 + d1);
|
||||
float n2 = fbm(uv * 2.05 + d2);
|
||||
float n3 = fbm(uv * 4.40 + d3);
|
||||
@@ -117,12 +109,9 @@ void main() {
|
||||
color = mix(color, u_nebula_b, pow(n2, 1.85) * 0.62);
|
||||
color += u_nebula_a * pow(n3, 3.0) * 0.28;
|
||||
|
||||
// Viñeta radial.
|
||||
float r = length(v_clip);
|
||||
color *= 1.0 - smoothstep(0.55, 1.40, r) * 0.85;
|
||||
|
||||
// === ESTRELLAS — 3 estratos con titilación distinta ===
|
||||
// Brillantes, pocas, titilan rápido.
|
||||
{
|
||||
vec2 sgrid = uv * 75.0;
|
||||
vec2 sid = floor(sgrid);
|
||||
@@ -131,7 +120,6 @@ void main() {
|
||||
float mask = smoothstep(0.9935, 0.999, sh);
|
||||
color += u_stardust * mask * tw * 1.15;
|
||||
}
|
||||
// Medianas, densas, titilan lento.
|
||||
{
|
||||
vec2 sgrid = uv * 135.0 + vec2(7.0, 11.0);
|
||||
vec2 sid = floor(sgrid);
|
||||
@@ -140,7 +128,6 @@ void main() {
|
||||
float mask = smoothstep(0.987, 0.994, sh);
|
||||
color += u_stardust * mask * tw * 0.75;
|
||||
}
|
||||
// Polvo de fondo, muchas, casi sin twinkle.
|
||||
{
|
||||
vec2 sgrid = uv * 260.0 + vec2(13.0, 3.0);
|
||||
vec2 sid = floor(sgrid);
|
||||
@@ -150,7 +137,6 @@ void main() {
|
||||
color += u_stardust * mask * tw * 0.40;
|
||||
}
|
||||
|
||||
// === METEOROS (4 procedurales, respawn independiente) ===
|
||||
float meteors = 0.0;
|
||||
meteors += meteor(uv, 0.31);
|
||||
meteors += meteor(uv, 1.73);
|
||||
@@ -174,10 +160,20 @@ void main() {
|
||||
}
|
||||
";
|
||||
|
||||
/// Fragment de la chacana mística: SDF de 2 escalones por brazo,
|
||||
/// líneas glow + aro + rayos zodiacales + sol central pulsante.
|
||||
/// Uniforms: `u_time`, `u_thickness` (s), `u_center_half` (c), `u_arm_extent`,
|
||||
/// `u_line_color`, `u_rim_color`, `u_sun_color`, `u_sun_pulse`.
|
||||
/// Fragment de la chacana mística (estética dorada del logo GioSer):
|
||||
/// 1. **Sol detrás**: halo gauss + corona, visible SÓLO dentro de la superficie
|
||||
/// de la chacana (clip por SDF), apenas asomando por las junturas de los pasos.
|
||||
/// 2. **Doble outline**: dos líneas paralelas en dorado/ámbar — la chacana se
|
||||
/// siente "grabada" sobre el cielo.
|
||||
/// 3. **Interior**: niebla oscura translúcida con sutiles rayos radiales
|
||||
/// desde el centro (el sol los proyecta a través de la superficie).
|
||||
/// 4. **Aro doble exterior**: ring fino + ring grueso (este último con marcas
|
||||
/// cardinales de 3 puntos cada una, como en el logo).
|
||||
///
|
||||
/// Uniforms:
|
||||
/// `u_time`, `u_thickness` (s), `u_center_half` (c), `u_arm_extent` (L),
|
||||
/// `u_line_color` (gold rim), `u_rim_color` (gold rim oscuro),
|
||||
/// `u_sun_color`, `u_sun_pulse`, `u_dark_color` (interior fill).
|
||||
pub const FS_CHACANA: &str = "#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_world;
|
||||
@@ -189,26 +185,26 @@ uniform float u_arm_extent;
|
||||
uniform vec3 u_line_color;
|
||||
uniform vec3 u_rim_color;
|
||||
uniform vec3 u_sun_color;
|
||||
uniform vec3 u_dark_color;
|
||||
uniform float u_sun_pulse;
|
||||
|
||||
const float PI = 3.14159265;
|
||||
|
||||
float sdBox(vec2 p, vec2 b) {
|
||||
vec2 d = abs(p) - b;
|
||||
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
|
||||
}
|
||||
|
||||
// Chacana de 2 escalones (mística clásica): centro 2c×2c + 4 brazos
|
||||
// con 2 niveles. Inner level half-width = 2s, outer (tip) = s.
|
||||
// Chacana de 2 escalones (mística clásica de Tiwanaku).
|
||||
float sdChacana(vec2 p, float s, float c) {
|
||||
float d = sdBox(p, vec2(c, c));
|
||||
float hd = 0.5 * s;
|
||||
// Nivel interno (más ancho, pegado al centro).
|
||||
float mid1 = c + 0.5 * s;
|
||||
float hw1 = 2.0 * s;
|
||||
d = min(d, sdBox(p - vec2(0.0, mid1), vec2(hw1, hd))); // N
|
||||
d = min(d, sdBox(p - vec2(0.0, -mid1), vec2(hw1, hd))); // S
|
||||
d = min(d, sdBox(p - vec2( mid1, 0.0), vec2(hd, hw1))); // E
|
||||
d = min(d, sdBox(p - vec2(-mid1, 0.0), vec2(hd, hw1))); // W
|
||||
// Punta (más angosta, externa).
|
||||
d = min(d, sdBox(p - vec2(0.0, mid1), vec2(hw1, hd)));
|
||||
d = min(d, sdBox(p - vec2(0.0, -mid1), vec2(hw1, hd)));
|
||||
d = min(d, sdBox(p - vec2( mid1, 0.0), vec2(hd, hw1)));
|
||||
d = min(d, sdBox(p - vec2(-mid1, 0.0), vec2(hd, hw1)));
|
||||
float mid2 = c + 1.5 * s;
|
||||
float hw2 = 1.0 * s;
|
||||
d = min(d, sdBox(p - vec2(0.0, mid2), vec2(hw2, hd)));
|
||||
@@ -218,66 +214,90 @@ float sdChacana(vec2 p, float s, float c) {
|
||||
return d;
|
||||
}
|
||||
|
||||
// 3 puntos pequeños en cada uno de los 4 cardinales sobre el aro grueso.
|
||||
float cardinal_dots(vec2 p, float ringR, float dotSize) {
|
||||
float r = length(p);
|
||||
float ang = atan(p.y, p.x);
|
||||
// Acercamiento al aro (gauss tight en r=ringR).
|
||||
float on_ring = exp(-((r - ringR) * (r - ringR)) / (2.0 * dotSize * dotSize));
|
||||
float dots = 0.0;
|
||||
// 4 cardinales en ángulos 0, π/2, π, -π/2.
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float base = float(i) * (PI * 0.5);
|
||||
// 3 puntos por cardinal, offset angular pequeño.
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
float a = base + float(j) * 0.075;
|
||||
float da = ang - a;
|
||||
da = da - 2.0 * PI * floor((da + PI) / (2.0 * PI));
|
||||
dots += exp(-(da * da) / (2.0 * 0.012 * 0.012));
|
||||
}
|
||||
}
|
||||
return on_ring * dots;
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = v_world;
|
||||
float d = sdChacana(p, u_thickness, u_center_half);
|
||||
float r = length(p);
|
||||
|
||||
// Línea principal: gaussiana sobre el borde de la chacana.
|
||||
float lineW = 0.011;
|
||||
float line = exp(-(d * d) / (2.0 * lineW * lineW));
|
||||
// === SOL DETRÁS ===
|
||||
// Halo grande, sólo visible dentro de la superficie de la chacana.
|
||||
float inside = 1.0 - smoothstep(-0.004, 0.004, d);
|
||||
float sunR = u_thickness * 0.42;
|
||||
float sun = exp(-(r * r) / (2.0 * sunR * sunR));
|
||||
float corR = u_center_half * 0.75;
|
||||
float corona = exp(-(r * r) / (2.0 * corR * corR));
|
||||
float halo = sun * (1.15 + 0.20 * u_sun_pulse) + corona * (0.55 + 0.15 * u_sun_pulse);
|
||||
|
||||
// Glow exterior cae suave hacia el infinito.
|
||||
float glow = exp(-max(d, 0.0) * 8.0) * 0.55;
|
||||
|
||||
// Fill interior, una niebla cyan muy tenue.
|
||||
float fill = smoothstep(0.0, -0.025, d);
|
||||
|
||||
// Aro circular que envuelve la chacana (rasgo del logo).
|
||||
float ringR_outer = u_arm_extent * 1.32;
|
||||
float ringD_outer = abs(r - ringR_outer);
|
||||
float ring_outer = exp(-(ringD_outer * ringD_outer) / (2.0 * 0.008 * 0.008)) * 0.80;
|
||||
|
||||
// Aro interior fino (segundo orbital).
|
||||
float ringR_inner = u_arm_extent * 1.18;
|
||||
float ringD_inner = abs(r - ringR_inner);
|
||||
float ring_inner = exp(-(ringD_inner * ringD_inner) / (2.0 * 0.0035 * 0.0035)) * 0.42;
|
||||
|
||||
// Ventana radial entre arm_extent y el aro exterior — para rayos y muescas.
|
||||
// Rayos radiales sutiles desde el centro, sólo visibles donde la superficie
|
||||
// de la chacana los recibe.
|
||||
float ang = atan(p.y, p.x);
|
||||
float band = smoothstep(u_arm_extent * 1.00, u_arm_extent * 1.10, r)
|
||||
* (1.0 - smoothstep(ringR_outer * 0.92, ringR_outer * 1.00, r));
|
||||
float radial = pow(abs(cos(ang * 4.0 + sin(u_time * 0.3) * 0.2)), 8.0)
|
||||
* smoothstep(0.0, u_center_half * 0.8, r)
|
||||
* (1.0 - smoothstep(u_center_half * 0.85, u_center_half * 1.2, r))
|
||||
* 0.30;
|
||||
|
||||
// Rayos: 12 divisiones (meses andinos / horas), modulados en el tiempo.
|
||||
float rays = pow(abs(cos(ang * 6.0)), 24.0) * band
|
||||
* (0.55 + 0.45 * sin(u_time * 0.7));
|
||||
// === DOBLE OUTLINE ===
|
||||
// Línea interior (sobre la SDF=0).
|
||||
float lineW1 = 0.0085;
|
||||
float line_in = exp(-(d * d) / (2.0 * lineW1 * lineW1));
|
||||
// Línea exterior paralela, offset 0.018 hacia afuera.
|
||||
float dOff = d - 0.020;
|
||||
float lineW2 = 0.005;
|
||||
float line_out = exp(-(dOff * dOff) / (2.0 * lineW2 * lineW2));
|
||||
float line = line_in * 1.0 + line_out * 0.65;
|
||||
|
||||
// Marcas cardinales (4 muescas finas) — exponente alto = picos angostos.
|
||||
float card = pow(abs(cos(ang * 2.0)), 120.0) * band * 1.10;
|
||||
// Glow exterior leve.
|
||||
float glow = exp(-max(d, 0.0) * 14.0) * 0.30;
|
||||
|
||||
// Sol central: gauss tight + corona suave + pulso.
|
||||
float sunR = u_thickness * 0.50;
|
||||
float sunDist = r;
|
||||
float sun = exp(-(sunDist * sunDist) / (2.0 * sunR * sunR));
|
||||
float corR = sunR * 5.0;
|
||||
float corona = exp(-(sunDist * sunDist) / (2.0 * corR * corR)) * 0.50;
|
||||
float sunMix = sun * (1.0 + 0.2 * u_sun_pulse) + corona * (0.7 + 0.3 * u_sun_pulse);
|
||||
// === AROS EXTERIORES ===
|
||||
float ringR_main = u_arm_extent * 1.45;
|
||||
float ringD_main = abs(r - ringR_main);
|
||||
float ring_main = exp(-(ringD_main * ringD_main) / (2.0 * 0.005 * 0.005));
|
||||
|
||||
// Halo del centro: cuadrado oscuro detrás de la chacana para profundidad.
|
||||
float coreShadow = smoothstep(u_center_half * 0.95, u_center_half * 0.3, max(abs(p.x), abs(p.y))) * 0.20;
|
||||
float ringR_inner = u_arm_extent * 1.30;
|
||||
float ringD_inner = abs(r - ringR_inner);
|
||||
float ring_inner = exp(-(ringD_inner * ringD_inner) / (2.0 * 0.003 * 0.003)) * 0.40;
|
||||
|
||||
// 4 grupos de 3 puntos cardinales sobre el aro principal.
|
||||
float dots = cardinal_dots(p, ringR_main, 0.008) * 1.10;
|
||||
|
||||
// === COMPOSICIÓN ===
|
||||
vec3 col = vec3(0.0);
|
||||
col += u_line_color * line * 1.55;
|
||||
col += u_rim_color * glow * 1.05;
|
||||
col += u_line_color * ring_outer * 1.00;
|
||||
col += u_rim_color * ring_inner * 1.15;
|
||||
col += u_rim_color * rays * 1.20;
|
||||
col += u_line_color * card * 1.30;
|
||||
col += u_sun_color * sunMix * 1.45;
|
||||
col += vec3(0.05, 0.08, 0.14) * (fill + coreShadow) * 0.6;
|
||||
// Sol detrás (clip a interior).
|
||||
col += u_sun_color * halo * inside * 1.55;
|
||||
col += u_line_color * radial * inside * 0.6;
|
||||
// Niebla oscura translúcida en el interior para profundidad.
|
||||
col += u_dark_color * inside * 0.20;
|
||||
// Líneas y aros.
|
||||
col += u_line_color * line * 1.70;
|
||||
col += u_line_color * glow * 0.95;
|
||||
col += u_line_color * ring_main * 1.45;
|
||||
col += u_rim_color * ring_inner * 1.05;
|
||||
col += u_line_color * dots * 1.85;
|
||||
|
||||
float alpha = clamp(
|
||||
line * 1.2 + glow + ring_outer + ring_inner + rays + card + sunMix + fill * 0.5,
|
||||
halo * inside + line + glow + ring_main + ring_inner + dots + inside * 0.12,
|
||||
0.0, 1.0);
|
||||
fragColor = vec4(col, alpha);
|
||||
}
|
||||
@@ -288,10 +308,9 @@ pub const FULLSCREEN_QUAD: [f32; 12] = [
|
||||
-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0,
|
||||
];
|
||||
|
||||
/// Quad ligeramente mayor que la chacana para no recortar aros ni glow.
|
||||
/// `arm_extent` es la distancia centro→punta; multiplicamos por un factor
|
||||
/// que cubre el aro exterior (1.32×) más halo.
|
||||
/// Quad ligeramente mayor que la chacana + aros + glow.
|
||||
pub fn chacana_quad(arm_extent: f32) -> [f32; 12] {
|
||||
let e = arm_extent * 1.65;
|
||||
// Aro principal vive a 1.45 * arm_extent; sumamos margen para el glow.
|
||||
let e = arm_extent * 1.70;
|
||||
[-e, -e, e, -e, e, e, -e, -e, e, e, -e, e]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user