use proof_engine::prelude::*;
use proof_engine::audio::MusicVibe;
use proof_engine::config::ShadowQuality;
use std::f32::consts::TAU;
use glow::HasContext;
use glam::Mat4;
#[inline(always)]
fn hf(seed: usize, v: usize) -> f32 {
let n = (seed.wrapping_mul(374761393).wrapping_add(v.wrapping_mul(668265263))) as u32;
let n = n ^ (n >> 13);
let n = n.wrapping_mul(0x5851_F42D);
let n = n ^ (n >> 16);
(n & 0x00FF_FFFF) as f32 / 0x0100_0000 as f32
}
type Bone = (f32, f32, f32, f32, f32, f32);
const BONES: [Bone; 26] = [
(-1.18,-0.82, 0.00, 0.20, 4.0, 1.00), (-1.06,-0.78, 0.00, 0.16, 3.5, 1.00),
(-0.78,-0.62, 0.00, 0.07, 1.5, 0.90),
(-0.72,-0.56,-0.13, 0.10, 1.5, 0.65), (-0.72,-0.56, 0.13, 0.10, 1.5, 0.65),
(-0.68,-0.44,-0.42, 0.13, 3.0, 0.80), (-0.68,-0.44, 0.42, 0.13, 3.0, 0.80),
(-0.62, 0.10,-0.20, 0.20, 7.0, 0.60), (-0.62, 0.10, 0.20, 0.20, 7.0, 0.60), (-0.62, 0.10, 0.00, 0.04, 2.0, 0.60),
( 0.08, 0.24,-0.15, 0.15, 4.0, 0.60), ( 0.08, 0.24, 0.15, 0.15, 4.0, 0.60),
( 0.22, 0.30, 0.00, 0.24, 2.5, 0.50),
(-0.60,-0.10,-0.50, 0.10, 4.0, 0.90), (-0.60,-0.10, 0.50, 0.10, 4.0, 0.90),
(-0.10, 0.24,-0.53, 0.08, 3.0, 0.90), (-0.10, 0.24, 0.53, 0.08, 3.0, 0.90),
( 0.24, 0.36,-0.54, 0.06, 1.5, 0.90), ( 0.24, 0.36, 0.54, 0.06, 1.5, 0.90),
( 0.28, 0.62,-0.16, 0.13, 5.5, 0.80), ( 0.28, 0.62, 0.16, 0.13, 5.5, 0.80),
( 0.60, 0.86,-0.14, 0.10, 4.0, 0.80), ( 0.60, 0.86, 0.14, 0.10, 4.0, 0.80),
( 0.84, 0.98,-0.14, 0.11, 3.0, 0.80), ( 0.84, 0.98, 0.14, 0.11, 3.0, 0.80),
( 0.24, 0.34, 0.00, 0.18, 2.0, 0.65), ];
fn total_weight() -> f32 {
BONES.iter().map(|b| b.4 * (b.1 - b.0).abs()).sum()
}
const BONE_CLOTHING: [bool; 26] = [
false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, false, false, true, true, true, true, true, true, true, ];
fn leon_color(x: f32, y: f32) -> (f32, f32, f32) {
let ax = x.abs();
if y < -0.82 { return (0.20, 0.12, 0.06); }
if y < -0.56 && ax > 0.11 { return (0.22, 0.13, 0.07); }
if y >= -0.88 && y <= -0.30 {
if ax > 0.13 && y < -0.58 { return (0.20, 0.12, 0.06); } if y > -0.76 && y < -0.70 { return (0.62, 0.44, 0.32); } if y > -0.72 && y < -0.63 && ax > 0.04 && ax < 0.11 { return (0.70, 0.50, 0.36); } if y > -0.52 && y < -0.36 {
let cell = ((ax * 59.0) as i32) ^ ((y * -47.0) as i32);
if cell % 5 == 0 { return (0.65, 0.46, 0.33); } }
return (0.82, 0.60, 0.45); }
if y < -0.18 && ax < 0.09 { return (0.78, 0.56, 0.42); }
if y < -0.14 && ax > 0.08 && ax < 0.24 { return (0.40, 0.27, 0.13); }
if y < -0.06 && ax > 0.30 { return (0.60, 0.42, 0.24); }
if y >= 0.22 && y <= 0.30 {
if ax < 0.05 { return (0.72, 0.70, 0.66); } return (0.14, 0.10, 0.07); }
if ax > 0.40 {
if y > 0.24 { return (0.80, 0.58, 0.43); } return (0.54, 0.37, 0.19); }
if y > 0.28 {
if y > 0.84 { return (0.25, 0.17, 0.10); } if y > 0.56 && y < 0.72 && ax < 0.18 { return (0.36, 0.32, 0.22); } return (0.30, 0.33, 0.24); }
if ax < 0.05 && y > -0.62 && y < 0.22 { return (0.68, 0.50, 0.28); }
(0.55, 0.38, 0.20)
}
#[derive(Clone, Copy, PartialEq)]
enum MatTag { Skin, Hair, Jacket, Boot, Metal, Eye }
fn leon_tag(x: f32, y: f32) -> MatTag {
let ax = x.abs();
if y < -0.82 { return MatTag::Hair; }
if y < -0.56 && ax > 0.11 { return MatTag::Hair; }
if y >= -0.88 && y <= -0.30 { return MatTag::Skin; }
if y < -0.18 && ax < 0.09 { return MatTag::Skin; }
if y >= 0.22 && y <= 0.30 {
if ax < 0.05 { return MatTag::Metal; }
return MatTag::Boot;
}
if ax > 0.40 {
if y > 0.24 { return MatTag::Skin; }
return MatTag::Jacket;
}
if y > 0.28 {
if y > 0.84 { return MatTag::Boot; }
return MatTag::Jacket;
}
MatTag::Jacket }
#[inline(always)]
fn fresnel_response(tag: MatTag, snz: f32) -> (f32, f32, f32) {
let cos_theta = snz.max(0.0);
let f = (1.0 - cos_theta).powi(5);
let (k, tr, tg, tb) = match tag {
MatTag::Skin => (0.20, 1.00f32, 0.90f32, 0.80f32), MatTag::Hair => (0.10, 0.95f32, 0.88f32, 0.72f32), MatTag::Jacket => (0.50, 0.90f32, 0.72f32, 0.40f32), MatTag::Boot => (0.70, 0.88f32, 0.78f32, 0.55f32), MatTag::Metal => (1.00, 1.00f32, 0.97f32, 0.88f32), MatTag::Eye => (0.85, 1.00f32, 0.98f32, 0.96f32), };
let contrib = f * k * 0.38; (contrib * tr, contrib * tg, contrib * tb)
}
const FOCAL_DIST: f32 = 0.15; const DOF_RANGE: f32 = 0.25;
const TORCH_X: f32 = 3.20; const TORCH_Y: f32 = -2.00; const TORCH_Z: f32 = -1.20; const FLOOR_Y: f32 = 3.55;
fn clothing_offset(bone_idx: usize, t: f32) -> f32 {
#[inline(always)]
fn p(t: f32, knots: &[(f32, f32)]) -> f32 {
let t = t.clamp(0.0, 1.0);
let n = knots.len();
if n == 0 { return 0.0; }
if n == 1 { return knots[0].1; }
if t <= knots[0].0 { return knots[0].1; }
if t >= knots[n-1].0 { return knots[n-1].1; }
for w in knots.windows(2) {
let (t0, r0) = w[0];
let (t1, r1) = w[1];
if t <= t1 {
let s = (t - t0) / (t1 - t0);
let c = (1.0 - (s * std::f32::consts::PI).cos()) * 0.5;
return r0 + (r1 - r0) * c;
}
}
knots[n-1].1
}
match bone_idx {
0 | 1 | 2 | 17 | 18 => 0.0,
3 | 4 => p(t, &[(0.00, 0.010), (0.40, 0.014), (0.75, 0.013), (1.00, 0.011)]),
5 | 6 => p(t, &[(0.00, 0.013), (0.30, 0.015), (0.65, 0.012), (1.00, 0.010)]),
7 | 8 => p(t, &[(0.00, 0.013), (0.32, 0.015), (0.65, 0.009), (0.88, 0.009), (1.00, 0.012)]),
9 => p(t, &[(0.00, 0.005), (0.50, 0.004), (1.00, 0.004)]),
10 | 11 => p(t, &[(0.00, 0.009), (0.45, 0.009), (0.75, 0.011), (1.00, 0.013)]),
12 => p(t, &[(0.00, 0.015), (1.00, 0.015)]),
13 | 14 => p(t, &[(0.00, 0.012), (0.28, 0.013), (0.62, 0.010), (1.00, 0.008)]),
15 | 16 => p(t, &[(0.00, 0.009), (0.22, 0.010), (0.60, 0.007), (1.00, 0.005)]),
19 | 20 => p(t, &[(0.00, 0.012), (0.25, 0.012), (0.60, 0.009), (1.00, 0.007)]),
21 | 22 => p(t, &[(0.00, 0.009), (0.28, 0.010), (0.68, 0.007), (1.00, 0.005)]),
23 | 24 => p(t, &[(0.00, 0.016), (0.35, 0.015), (0.75, 0.014), (1.00, 0.013)]),
25 => p(t, &[(0.00, 0.010), (0.50, 0.012), (1.00, 0.010)]),
_ => 0.0,
}
}
fn radius_at(bone_idx: usize, t: f32) -> f32 {
#[inline(always)]
fn p(t: f32, knots: &[(f32, f32)]) -> f32 {
let t = t.clamp(0.0, 1.0);
let n = knots.len();
if n == 0 { return 1.0; }
if n == 1 { return knots[0].1; }
if t <= knots[0].0 { return knots[0].1; }
if t >= knots[n-1].0 { return knots[n-1].1; }
for w in knots.windows(2) {
let (t0, r0) = w[0];
let (t1, r1) = w[1];
if t <= t1 {
let s = (t - t0) / (t1 - t0);
let c = (1.0 - (s * std::f32::consts::PI).cos()) * 0.5;
return r0 + (r1 - r0) * c;
}
}
knots[n-1].1
}
match bone_idx {
0 => p(t, &[(0.00, 0.86), (0.50, 1.20), (1.00, 0.96)]), 1 => p(t, &[(0.00, 0.94), (0.35, 1.20), (1.00, 0.72)]),
2 => p(t, &[(0.00, 0.72), (0.50, 0.82), (1.00, 1.00)]),
3 | 4 => p(t, &[(0.00, 0.88), (0.50, 1.00), (1.00, 0.90)]),
5 | 6 => p(t, &[(0.00, 0.80), (0.40, 1.00), (1.00, 0.90)]),
7 | 8 => p(t, &[(0.00, 1.04), (0.35, 1.15), (0.70, 0.94), (1.00, 0.88)]),
9 | 12 => p(t, &[(0.00, 1.00), (1.00, 1.00)]),
10 | 11 => p(t, &[(0.00, 0.88), (0.50, 0.82), (1.00, 0.92)]),
13 | 14 => p(t, &[(0.00, 0.90), (0.22, 1.00), (0.60, 0.86), (1.00, 0.80)]),
15 | 16 => p(t, &[(0.00, 0.80), (0.28, 1.00), (0.62, 0.72), (1.00, 0.68)]),
17 | 18 => p(t, &[(0.00, 1.00), (0.55, 0.82), (1.00, 0.65)]),
19 | 20 => p(t, &[(0.00, 1.00), (0.28, 0.94), (0.58, 0.82), (1.00, 0.80)]),
21 | 22 => p(t, &[(0.00, 0.82), (0.24, 1.00), (0.68, 0.72), (1.00, 0.68)]),
23 | 24 => p(t, &[(0.00, 1.00), (0.45, 0.90), (1.00, 0.82)]),
25 => p(t, &[(0.00, 0.90), (0.50, 1.00), (1.00, 0.92)]),
_ => 1.0,
}
}
#[inline(always)]
fn plerp(t: f32, knots: &[(f32, f32)]) -> f32 {
let t = t.clamp(0.0, 1.0);
if knots.is_empty() { return 1.0; }
if t <= knots[0].0 { return knots[0].1; }
let last = *knots.last().unwrap();
if t >= last.0 { return last.1; }
for w in knots.windows(2) {
let (t0, r0) = w[0]; let (t1, r1) = w[1];
if t <= t1 { return r0 + (r1 - r0) * (t - t0) / (t1 - t0); }
}
last.1
}
#[inline(always)]
fn smin(a: f32, b: f32, k: f32) -> f32 {
let h = (k - (a - b).abs()).max(0.0) / k;
a.min(b) - h * h * k * 0.25
}
#[inline]
fn sdf_torso(px: f32, py: f32, pz: f32) -> (f32, f32, f32) {
const Y0: f32 = -0.68; const Y1: f32 = 0.32; let ty = ((py - Y0) / (Y1 - Y0)).clamp(0.0, 1.0);
let ax = plerp(ty, &[
(0.00, 0.37), (0.22, 0.33), (0.55, 0.21), (0.72, 0.24), (1.00, 0.26), ]);
let az = plerp(ty, &[
(0.00, 0.16), (0.28, 0.18), (0.55, 0.13), (1.00, 0.16), ]);
let nx = px / ax;
let nz = pz / az;
let cross_d = ((nx*nx + nz*nz).sqrt() - 1.0) * ax.min(az);
let vert_d = (py - Y1).max(0.0) + (Y0 - py).max(0.0);
(cross_d.max(vert_d), ax, az)
}
#[inline]
fn sdf_arm_r(px: f32, py: f32, pz: f32) -> (f32, f32) {
const AX: f32 = 0.500; const AY: f32 = -0.60; const BX: f32 = 0.530; const BY: f32 = -0.10; const RA: f32 = 0.095; const RB: f32 = 0.082;
let t = ((py - AY) / (BY - AY)).clamp(0.0, 1.0);
let cx = AX + t * (BX - AX);
let r = RA + t * (RB - RA);
let dx = (px - cx) / 1.05; let dz = pz / 0.95; let xz = (dx*dx + dz*dz).sqrt();
let ye = (py - BY).max(0.0) + (AY - py).max(0.0);
let d = if ye > 0.0 { (xz*xz + ye*ye).sqrt() - r } else { xz - r };
(d, t)
}
#[inline]
fn sdf_forearm_r(px: f32, py: f32, pz: f32) -> f32 {
const AX: f32 = 0.530; const AY: f32 = -0.10; const BX: f32 = 0.555; const BY: f32 = 0.24; const RA: f32 = 0.075; const RB: f32 = 0.056;
let t = ((py - AY) / (BY - AY)).clamp(0.0, 1.0);
let cx = AX + t * (BX - AX);
let r = RA + t * (RB - RA);
let dx = (px - cx) / 1.05;
let dz = pz / 0.95;
let xz = (dx*dx + dz*dz).sqrt();
let ye = (py - BY).max(0.0) + (AY - py).max(0.0);
if ye > 0.0 { (xz*xz + ye*ye).sqrt() - r } else { xz - r }
}
#[inline]
fn sdf_leg_r(px: f32, py: f32, pz: f32) -> f32 {
const CX: f32 = 0.145; let d_thigh = {
let t = ((py - 0.28f32) / 0.44).clamp(0.0, 1.0);
let r = 0.105 + t * (0.088 - 0.105);
let dx = px - CX; let dz = pz;
let xz = (dx*dx + dz*dz).sqrt();
let ye = (py - 0.72f32).max(0.0) + (0.28f32 - py).max(0.0);
if ye > 0.0 { (xz*xz + ye*ye).sqrt() - r } else { xz - r }
};
let d_shin = {
let t = ((py - 0.68f32) / 0.29).clamp(0.0, 1.0);
let r = 0.090 + t * (0.055 - 0.090);
let dx = px - CX; let dz = pz;
let xz = (dx*dx + dz*dz).sqrt();
let ye = (py - 0.97f32).max(0.0) + (0.68f32 - py).max(0.0);
if ye > 0.0 { (xz*xz + ye*ye).sqrt() - r } else { xz - r }
};
smin(d_thigh, d_shin, 0.040)
}
#[inline(always)]
fn sdf_body(px: f32, py: f32, pz: f32) -> f32 {
let d = smin(sdf_torso(px, py, pz).0, sdf_arm_r(px, py, pz).0, 0.04);
let d = smin(d, sdf_arm_r(-px, py, pz).0, 0.04); let d = smin(d, sdf_forearm_r(px, py, pz), 0.04); let d = smin(d, sdf_forearm_r(-px, py, pz), 0.04); let d = smin(d, sdf_leg_r(px, py, pz), 0.04); smin(d, sdf_leg_r(-px, py, pz), 0.04) }
#[inline]
fn sdf_curvature(px: f32, py: f32, pz: f32,
snx: f32, sny: f32, snz: f32, d0: f32) -> f32 {
const E: f32 = 0.025;
let t1x = -snz;
let t1z = snx;
let t2x = -snx * sny;
let t2y = 1.0 - sny * sny;
let t2z = -snz * sny;
let t2n = (t2x*t2x + t2y*t2y + t2z*t2z).sqrt().max(0.001);
let (t2x, t2y, t2z) = (t2x/t2n, t2y/t2n, t2z/t2n);
let d1p = sdf_body(px + E*t1x, py, pz + E*t1z);
let d1m = sdf_body(px - E*t1x, py, pz - E*t1z);
let d2p = sdf_body(px + E*t2x, py + E*t2y, pz + E*t2z);
let d2m = sdf_body(px - E*t2x, py - E*t2y, pz - E*t2z);
(d1p + d1m - 2.0*d0 + d2p + d2m - 2.0*d0) / (E * E)
}
#[inline]
fn vol_shadow(px: f32, py: f32, pz: f32, lx: f32, ly: f32, lz: f32) -> f32 {
const STEPS: usize = 6;
const STEP: f32 = 0.075; const SIGMA: f32 = 14.0; let mut tau = 0.0f32;
for k in 1..=STEPS {
let h = k as f32 * STEP;
let d = sdf_body(px + h*lx, py + h*ly, pz + h*lz);
if d < 0.0 { tau += (-d).min(0.12) * STEP; }
}
(-SIGMA * tau).exp()
}
struct SdfLights {
klx:f32, kly:f32, klz:f32,
rlx:f32, rly:f32, rlz:f32,
f1x:f32, f1y:f32, f1z:f32,
f2x:f32, f2y:f32, f2z:f32,
f3x:f32, f3y:f32, f3z:f32,
f3_int: f32,
}
#[inline(always)]
fn sky_color(dx: f32, dy: f32, dz: f32) -> (f32, f32, f32) {
let r = (dx*dx + dy*dy + dz*dz).sqrt().max(0.001);
let el = (-dy / r).clamp(0.0, 1.0);
const KLX: f32 = -0.375; const KLY: f32 = -0.515; const KLZ: f32 = 0.685;
let sun_dot = ((dx/r)*KLX + (dy/r)*KLY + (dz/r)*KLZ).max(0.0);
let sun_glow = sun_dot.powi(8) * 0.55;
let t = el;
let r_s = if t < 0.4 { 0.58*(1.0-t/0.4) + 0.20*(t/0.4) }
else { 0.20*(1.0-(t-0.4)/0.6) + 0.06*((t-0.4)/0.6) };
let g_s = if t < 0.4 { 0.38*(1.0-t/0.4) + 0.22*(t/0.4) }
else { 0.22*(1.0-(t-0.4)/0.6) + 0.08*((t-0.4)/0.6) };
let b_s = if t < 0.4 { 0.18*(1.0-t/0.4) + 0.45*(t/0.4) }
else { 0.45*(1.0-(t-0.4)/0.6) + 0.24*((t-0.4)/0.6) };
(r_s + sun_glow, g_s + sun_glow*0.72, b_s + sun_glow*0.30)
}
#[inline(always)]
fn sdf_shade(
snx: f32, sny: f32, snz: f32,
hz: f32,
br: f32, bg: f32, bb: f32,
mat_tag: MatTag,
l: &SdfLights,
) -> (f32, f32, f32) {
const F1:(f32,f32,f32)=(1.00,0.85,0.70); const F1I:f32=0.15;
const F2:(f32,f32,f32)=(0.60,0.70,1.00); const F2I:f32=0.05;
const F3:(f32,f32,f32)=(1.00,0.52,0.15);
let spec = if matches!(mat_tag, MatTag::Boot|MatTag::Metal) {
(snz*(l.klz+1.0)*0.5).max(0.0).powi(24)*0.50
} else if mat_tag == MatTag::Eye {
let hx = l.klx; let hy = l.kly; let hz_h = l.klz + 1.0;
let hn = (hx*hx + hy*hy + hz_h*hz_h).sqrt().max(0.001);
let ndoth = (snx*hx + sny*hy + snz*hz_h).max(0.0) / hn;
ndoth.powi(128) * 1.80
} else { 0.0 };
let dk = (snx*l.klx + sny*l.kly + snz*l.klz).max(0.0);
let df1 = (snx*l.f1x + sny*l.f1y + snz*l.f1z).max(0.0);
let df2 = (snx*l.f2x + sny*l.f2y + snz*l.f2z).max(0.0);
let df3 = (snx*l.f3x + sny*l.f3y + snz*l.f3z).max(0.0);
let rim = { let f=(1.0-hz)*(snx*l.rlx+sny*l.rly+snz*l.rlz).max(0.0); f*f };
let sky = (-sny).max(0.0)*0.08; let gnd = sny.max(0.0)*0.04;
let ir = dk*1.10+spec + df1*F1I*F1.0 + df2*F2I*F2.0 + df3*l.f3_int*F3.0
+ 0.03+sky*0.60+gnd*0.40;
let ig = dk*1.10+spec + df1*F1I*F1.1 + df2*F2I*F2.1 + df3*l.f3_int*F3.1
+ 0.03+sky*0.75+gnd*0.35;
let ib = dk*1.10+spec + df1*F1I*F1.2 + df2*F2I*F2.2 + df3*l.f3_int*F3.2
+ 0.03+sky*1.00+gnd*0.25;
let (fr,fg,fb) = fresnel_response(mat_tag, snz);
let (er,eg,eb) = if matches!(mat_tag, MatTag::Boot|MatTag::Metal|MatTag::Eye) {
let rx = -2.0*snz*snx; let ry = -2.0*snz*sny; let rz = 1.0 - 2.0*snz*snz;
let (sr,sg,sb) = sky_color(rx, ry, rz);
let k = match mat_tag { MatTag::Metal=>0.38, MatTag::Eye=>0.22, MatTag::Boot=>0.15, _=>0.0 };
(sr*k, sg*k, sb*k)
} else { (0.0, 0.0, 0.0) };
let tone = |c:f32| c*(1.0+c*0.12)/(1.0+c);
let sc = |c:f32| c*c*(3.0-2.0*c);
let cr = sc(tone((br*ir+rim*0.12+fr+er).min(1.4)).clamp(0.0,1.0));
let cg = sc(tone((bg*ig+rim*0.16+fg+eg).min(1.4)).clamp(0.0,1.0));
let cb = sc(tone((bb*ib+rim*0.42+fb+eb).min(1.4)).clamp(0.0,1.0));
(cr, cg, cb)
}
#[inline]
fn sdf_ao(px: f32, py: f32, pz: f32, snx: f32, sny: f32, snz: f32) -> f32 {
let mut occ = 0.0f32;
let mut sca = 1.0f32;
const STEPS: [f32; 5] = [0.010, 0.035, 0.065, 0.100, 0.140];
for &h in &STEPS {
let sx = px + h * snx;
let sy = py + h * sny;
let sz = pz + h * snz;
let d = smin(sdf_torso(sx, sy, sz).0, sdf_arm_r(sx, sy, sz).0, 0.04);
occ += (h - d) * sca;
sca *= 0.95; }
let ao = (1.0 - 3.0 * occ).clamp(0.0, 1.0);
ao * 0.75 + 0.25
}
#[inline]
fn sdf_thickness(px: f32, py: f32, pz: f32, snx: f32, sny: f32, snz: f32) -> f32 {
const PROBES: [f32; 6] = [0.020, 0.055, 0.100, 0.160, 0.250, 0.380];
for &h in &PROBES {
let sx = px - h * snx;
let sy = py - h * sny;
let sz = pz - h * snz;
let d = smin(sdf_torso(sx, sy, sz).0, sdf_arm_r(sx, sy, sz).0, 0.04);
if d >= 0.0 { return h; } }
0.380 }
#[inline(always)]
fn noise3(x: f32, y: f32, z: f32) -> f32 {
let xi = x.floor() as i32;
let yi = y.floor() as i32;
let zi = z.floor() as i32;
let xf = x - xi as f32;
let yf = y - yi as f32;
let zf = z - zi as f32;
let u = xf * xf * (3.0 - 2.0 * xf);
let v = yf * yf * (3.0 - 2.0 * yf);
let w = zf * zf * (3.0 - 2.0 * zf);
let h = |ix: i32, iy: i32, iz: i32| -> f32 {
let n = (ix.wrapping_mul(374_761_393_i32))
.wrapping_add(iy.wrapping_mul(668_265_263_i32))
.wrapping_add(iz.wrapping_mul(1_291_057_433_i32)) as u32;
let n = n ^ (n >> 13);
let n = n.wrapping_mul(0x5851_F42D);
let n = n ^ (n >> 16);
(n & 0x00FF_FFFF) as f32 / 16_777_216.0 - 0.5
};
let lp = |t: f32, a: f32, b: f32| a + t * (b - a);
lp(w,
lp(v, lp(u, h(xi, yi, zi ), h(xi+1,yi, zi )),
lp(u, h(xi, yi+1,zi ), h(xi+1,yi+1,zi ))),
lp(v, lp(u, h(xi, yi, zi+1), h(xi+1,yi, zi+1)),
lp(u, h(xi, yi+1,zi+1), h(xi+1,yi+1,zi+1))))
}
struct SkinDetail {
disp: f32,
pnx: f32, pny: f32, pnz: f32,
roughness: f32,
}
#[inline]
fn skin_detail(px: f32, py: f32, pz: f32, snx: f32, sny: f32, snz: f32) -> SkinDetail {
let fl = noise3(px * 3.0, py * 3.0, pz * 3.0)
+ noise3(px * 6.0, py * 6.0, pz * 6.0) * 0.5;
const GE: f32 = 0.009; const BUMP: f32 = 0.030;
let fc = noise3( px * 16.0, py * 16.0, pz * 16.0);
let fxp = noise3((px + GE) * 16.0, py * 16.0, pz * 16.0);
let fyp = noise3( px * 16.0, (py + GE) * 16.0, pz * 16.0);
let fzp = noise3( px * 16.0, py * 16.0, (pz + GE) * 16.0);
let gx = (fxp - fc) / GE;
let gy = (fyp - fc) / GE;
let gz = (fzp - fc) / GE;
let gdn = gx * snx + gy * sny + gz * snz;
let bx = (gx - gdn * snx) * BUMP;
let by = (gy - gdn * sny) * BUMP;
let bz = (gz - gdn * snz) * BUMP;
let pnx = snx + bx;
let pny = sny + by;
let pnz = snz + bz;
let pnn = (pnx * pnx + pny * pny + pnz * pnz).sqrt().max(0.001);
let fh = noise3(px * 52.0, py * 52.0, pz * 52.0);
SkinDetail {
disp: fl * 0.008 + fc * 0.003,
pnx: pnx / pnn,
pny: pny / pnn,
pnz: pnz / pnn,
roughness: fh * 0.5 + 0.5, }
}
const HEAD_CY: f32 = -0.600; const HEAD_RX: f32 = 0.165; const HEAD_RY: f32 = 0.320; const HEAD_RZ: f32 = 0.115;
const SOCK_CX: f32 = 0.075; const SOCK_CY: f32 = -0.675; const SOCK_CZ: f32 = 0.062; const SOCK_R: f32 = 0.038;
const EYEB_CX: f32 = 0.075; const EYEB_CY: f32 = -0.675; const EYEB_CZ: f32 = 0.088; const EYEB_R: f32 = 0.027;
const NOSE_CY: f32 = -0.500; const NOSE_CZ: f32 = 0.095; const NOSE_RX: f32 = 0.028; const NOSE_RY: f32 = 0.050; const NOSE_RZ: f32 = 0.045;
const LIPU_CY: f32 = -0.360; const LIPU_CZ: f32 = 0.074; const LIPL_CY: f32 = -0.385; const LIPL_CZ: f32 = 0.072;
const MOUTH_Y: f32 = -0.373; const MOUTH_Z: f32 = 0.073; const MOUTH_HX: f32 = 0.068; const MOUTH_R: f32 = 0.008;
#[inline(always)]
fn smax(a: f32, b: f32, k: f32) -> f32 {
let h = (k - (a - b).abs()).max(0.0) / k;
a.max(b) + h * h * k * 0.25
}
#[inline(always)]
fn smooth_sub(base: f32, carve: f32, k: f32) -> f32 {
smax(-carve, base, k)
}
#[inline(always)]
fn sdf_sphere_f(dx: f32, dy: f32, dz: f32, r: f32) -> f32 {
(dx*dx + dy*dy + dz*dz).sqrt() - r
}
#[inline(always)]
fn sdf_head_ellipsoid(px: f32, py: f32, pz: f32) -> f32 {
let nx = px / HEAD_RX;
let ny = (py - HEAD_CY) / HEAD_RY;
let nz = pz / HEAD_RZ;
((nx*nx + ny*ny + nz*nz).sqrt() - 1.0) * HEAD_RX.min(HEAD_RZ)
}
#[inline]
fn sdf_face(px: f32, py: f32, pz: f32) -> f32 {
let d = sdf_head_ellipsoid(px, py, pz);
let sr = sdf_sphere_f(px - SOCK_CX, py - SOCK_CY, pz - SOCK_CZ, SOCK_R);
let sl = sdf_sphere_f(px + SOCK_CX, py - SOCK_CY, pz - SOCK_CZ, SOCK_R);
let d = smooth_sub(d, sr, 0.020);
let d = smooth_sub(d, sl, 0.020);
let er = sdf_sphere_f(px - EYEB_CX, py - EYEB_CY, pz - EYEB_CZ, EYEB_R);
let el = sdf_sphere_f(px + EYEB_CX, py - EYEB_CY, pz - EYEB_CZ, EYEB_R);
let d = smin(d, er, 0.012);
let d = smin(d, el, 0.012);
let nose = { let nx=px/NOSE_RX; let ny=(py-NOSE_CY)/NOSE_RY; let nz=(pz-NOSE_CZ)/NOSE_RZ;
((nx*nx+ny*ny+nz*nz).sqrt()-1.0)*NOSE_RX.min(NOSE_RZ) };
let d = smin(d, nose, 0.025);
let ul = { const RX:f32=0.056; const RY:f32=0.011; const RZ:f32=0.013;
let nx=px/RX; let ny=(py-LIPU_CY)/RY; let nz=(pz-LIPU_CZ)/RZ;
((nx*nx+ny*ny+nz*nz).sqrt()-1.0)*RX.min(RZ) };
let ll = { const RX:f32=0.058; const RY:f32=0.013; const RZ:f32=0.015;
let nx=px/RX; let ny=(py-LIPL_CY)/RY; let nz=(pz-LIPL_CZ)/RZ;
((nx*nx+ny*ny+nz*nz).sqrt()-1.0)*RX.min(RZ) };
let d = smin(d, ul, 0.011);
let d = smin(d, ll, 0.011);
let mc = { let cx = px.clamp(-MOUTH_HX, MOUTH_HX);
let ddx=px-cx; let ddy=py-MOUTH_Y; let ddz=pz-MOUTH_Z;
(ddx*ddx+ddy*ddy+ddz*ddz).sqrt()-MOUTH_R };
smooth_sub(d, mc, 0.009)
}
#[inline]
fn eye_color(px: f32, py: f32, pz: f32, eye_cx: f32) -> (f32, f32, f32, bool) {
let ex = px - eye_cx;
let ey = py - EYEB_CY;
let ez = pz - EYEB_CZ;
let er = (ex*ex + ey*ey + ez*ez).sqrt().max(0.001);
let cos_t = (ez / er).clamp(-1.0, 1.0);
if cos_t > 0.950 {
return (0.05, 0.04, 0.04, true); }
if cos_t > 0.750 {
let t = (0.950 - cos_t) / 0.200; return (0.28 + t*0.14, 0.17 + t*0.06, 0.06 + t*0.03, true); }
if cos_t > 0.680 {
return (0.12, 0.09, 0.07, true); }
let (sr, sg, sb) = if cos_t > 0.50 { (0.88, 0.82, 0.75) } else { (0.94, 0.90, 0.84) };
(sr, sg, sb, false)
}
#[inline]
fn face_normal(px: f32, py: f32, pz: f32) -> (f32, f32, f32) {
const EPS: f32 = 0.005;
let nx = sdf_face(px+EPS,py,pz) - sdf_face(px-EPS,py,pz);
let ny = sdf_face(px,py+EPS,pz) - sdf_face(px,py-EPS,pz);
let nz = sdf_face(px,py,pz+EPS) - sdf_face(px,py,pz-EPS);
let nn = (nx*nx+ny*ny+nz*nz).sqrt().max(0.001);
(nx/nn, ny/nn, nz/nn)
}
#[inline]
fn sdf_face_ao(px: f32, py: f32, pz: f32, snx: f32, sny: f32, snz: f32) -> f32 {
const STEPS: [f32; 4] = [0.007, 0.018, 0.038, 0.065];
let mut occ = 0.0f32;
let mut sca = 1.0f32;
for &h in &STEPS {
let d = sdf_face(px+h*snx, py+h*sny, pz+h*snz);
occ += (h - d) * sca;
sca *= 0.92;
}
let ao = (1.0 - 3.5*occ).clamp(0.0, 1.0);
ao * 0.70 + 0.30
}
#[inline]
fn sdf_face_curvature(px: f32, py: f32, pz: f32,
snx: f32, sny: f32, snz: f32, d0: f32) -> f32 {
const E: f32 = 0.018;
let t1x = -snz; let t1z = snx;
let t2x = -snx*sny; let t2y = 1.0-sny*sny; let t2z = -snz*sny;
let t2n = (t2x*t2x+t2y*t2y+t2z*t2z).sqrt().max(0.001);
let (t2x,t2y,t2z) = (t2x/t2n, t2y/t2n, t2z/t2n);
let d1p = sdf_face(px+E*t1x, py, pz+E*t1z );
let d1m = sdf_face(px-E*t1x, py, pz-E*t1z );
let d2p = sdf_face(px+E*t2x, py+E*t2y, pz+E*t2z );
let d2m = sdf_face(px-E*t2x, py-E*t2y, pz-E*t2z );
(d1p+d1m-2.0*d0 + d2p+d2m-2.0*d0) / (E*E)
}
#[inline]
fn sdf_face_thickness(px: f32, py: f32, pz: f32, snx: f32, sny: f32, snz: f32) -> f32 {
const PROBES: [f32; 5] = [0.014, 0.038, 0.076, 0.132, 0.220];
for &h in &PROBES {
let sx = px-h*snx; let sy = py-h*sny; let sz = pz-h*snz;
if sdf_face(sx, sy, sz) >= 0.0 { return h; }
}
0.240
}
#[inline]
fn gi_bounce(px: f32, py: f32, snx: f32, sny: f32, snz: f32, illum: f32)
-> (f32, f32, f32)
{
const RH: f32 = 0.065; const RV: f32 = 0.095; const BOUNCE: f32 = 0.060;
let t1x = -snz; let t1z = snx; let t2x = -snx * sny;
let t2y = 1.0 - sny * sny;
let t2z = -snz * sny;
let t2n = (t2x*t2x + t2y*t2y + t2z*t2z).sqrt().max(0.001);
let (t2x, t2y, t2z) = (t2x / t2n, t2y / t2n, t2z / t2n);
let (ar, ag, ab) = leon_color(px + RH * t1x, py );
let (br, bg, bb) = leon_color(px - RH * t1x, py );
let (cr2, cg2, cb2) = leon_color(px + RV * t2x, py + RV * t2y );
let (dr, dg, db) = leon_color(px - RV * t2x, py - RV * t2y );
let nr = (ar + br + cr2 + dr) * 0.25;
let ng = (ag + bg + cg2 + dg) * 0.25;
let nb = (ab + bb + cb2 + db) * 0.25;
let s = illum * BOUNCE;
(nr * s, ng * s, nb * s)
}
fn render_sdf_body(engine: &mut ProofEngine, dt: f32, time: f32, pos: Vec3, hp: f32) {
const SHELL: f32 = 0.016;
const N_TORSO: usize = 210_000;
const N_ARM: usize = 34_000;
let scale = 3.2f32;
let breath = 1.0 + (time * 1.4).sin() * 0.010;
let dmg = (1.0 - hp).max(0.0);
let lts = {
let n = |x:f32,y:f32,z:f32|{ let l=(x*x+y*y+z*z).sqrt(); (x/l,y/l,z/l) };
let (klx,kly,klz) = n(-0.40,-0.55, 0.73);
let (rlx,rly,rlz) = n( 0.55,-0.18,-0.82);
let (f1x,f1y,f1z) = n( 0.30, 0.60,-0.40);
let (f3x,f3y,f3z) = n(TORCH_X,TORCH_Y,TORCH_Z);
SdfLights {
klx,kly,klz, rlx,rly,rlz,
f1x,f1y,f1z,
f2x: 0.0, f2y: -1.0, f2z: 0.0,
f3x,f3y,f3z,
f3_int: 0.28*(0.80+(time*7.3).sin()*0.11+(time*13.1).cos()*0.07),
}
};
const T_Y0: f32 = -0.68; const T_Y1: f32 = 0.32;
for i in 0..N_TORSO {
let v = hf(i, 80);
let py = T_Y0 + v * (T_Y1 - T_Y0);
let ax = plerp(v, &[(0.00,0.37),(0.22,0.33),(0.55,0.21),(0.72,0.24),(1.00,0.26)]);
let az = plerp(v, &[(0.00,0.16),(0.28,0.18),(0.55,0.13),(1.00,0.16)]);
let rx = hf(i, 81)*2.0-1.0; let rz = hf(i, 82)*2.0-1.0;
let rn = (rx*rx + rz*rz).sqrt().max(0.001);
let dx = rx/rn; let dz = rz/rn;
let noise = (hf(i, 83)*2.0 - 1.0) * SHELL;
let px = (ax + noise) * dx;
let pz = (az + noise) * dz;
let (d_t, ax_v, az_v) = sdf_torso(px, py, pz);
let (d_a, _) = sdf_arm_r(px, py, pz);
let d = smin(d_t, d_a, 0.04);
if d < -SHELL || d > SHELL { continue; }
let gx = px / (ax_v * ax_v);
let gz = pz / (az_v * az_v);
let gn = (gx*gx + gz*gz).sqrt().max(0.001);
let (snx, sny, snz) = (gx/gn, 0.0f32, gz/gn);
let kappa = sdf_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa * 0.035).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw * wrinkle_raw * (3.0 - 2.0 * wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let inset = wrinkle * 0.006;
let px = px + (sd.disp - inset) * snx;
let py = py + (sd.disp - inset) * sny;
let pz = pz + (sd.disp - inset) * snz;
let hz = sd.pnz.max(0.0);
let core = ((-d / SHELL + 1.0) * 0.5).clamp(0.0, 1.0);
let (br, bg, bb) = leon_color(px, py);
let mat_tag = {
let raw = leon_tag(px, py);
if raw == MatTag::Skin && px.abs() > 0.09 { MatTag::Jacket } else { raw }
};
let (cr, cg, cb) = sdf_shade(sd.pnx, sd.pny, sd.pnz, hz, br, bg, bb, mat_tag, <s);
let ao = sdf_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr, mut cg, mut cb) = (cr * ao, cg * ao, cb * ao);
if mat_tag == MatTag::Skin {
let back_key = (-sd.pnx*lts.klx - sd.pny*lts.kly - sd.pnz*lts.klz).max(0.0);
let back_torch = (-sd.pnx*lts.f3x - sd.pny*lts.f3y - sd.pnz*lts.f3z).max(0.0) * lts.f3_int;
let back_illum = back_key * 0.80 + back_torch;
if back_illum > 0.04 {
let thick = sdf_thickness(px, py, pz, sd.pnx, sd.pny, sd.pnz);
const MU: f32 = 7.5; let trans = (-MU * thick).exp() * back_illum;
cr += trans * 1.00;
cg += trans * 0.28;
cb += trans * 0.08;
}
}
{
let dk_bump = (sd.pnx*lts.klx + sd.pny*lts.kly + sd.pnz*lts.klz).max(0.0);
let micro = sd.roughness * sd.roughness * dk_bump * 0.14;
cr += micro * 1.00;
cg += micro * 0.94;
cb += micro * 0.82;
}
if wrinkle > 0.01 {
cr *= 1.0 - wrinkle * 0.52;
cg *= 1.0 - wrinkle * 0.62;
cb *= 1.0 - wrinkle * 0.80;
}
{
let key_t = vol_shadow(px, py, pz, lts.klx, lts.kly, lts.klz);
let dk_lit = (sd.pnx*lts.klx + sd.pny*lts.kly + sd.pnz*lts.klz).max(0.0);
let vol_atten = 1.0 - dk_lit * (1.0 - key_t) * 0.72;
cr *= vol_atten;
cg *= vol_atten;
cb *= vol_atten;
}
{
let dk_gi = (sd.pnx*lts.klx + sd.pny*lts.kly + sd.pnz*lts.klz).max(0.0);
let f3_gi = (sd.pnx*lts.f3x + sd.pny*lts.f3y + sd.pnz*lts.f3z).max(0.0) * lts.f3_int;
let illum = dk_gi * 0.80 + f3_gi * 0.40 + 0.12;
let (gi_r, gi_g, gi_b) = gi_bounce(px, py, snx, sny, snz, illum);
cr += gi_r; cg += gi_g; cb += gi_b;
}
let alpha = ((0.35 + core*0.55) * hp.max(0.08) * (1.0 + wrinkle * 0.50)).min(0.97);
if alpha < 0.012 { continue; }
let jx = (hf(i, 84)-0.5) * dmg * 0.08;
let jy = (hf(i, 85)-0.5) * dmg * 0.08;
let emission = (if br>0.58&&bg>0.46 { 0.40+core*0.80 }
else if br<0.28&&bg<0.18 { 0.12+core*0.30 }
else if bb<0.14 { 0.50+core*1.50 }
else { 0.45+core*1.20 }) * (1.0 - wrinkle * 0.42);
let sz = 0.020 + core*0.018;
let ch = if core>0.7 {'*'} else if core>0.35 {'+'} else {'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x + px*scale*breath + jx,
pos.y + py*scale*breath + jy,
pos.z + hz*0.30),
color: Vec4::new(cr, cg, cb, alpha),
emission,
glow_color: Vec3::new(cr*0.55, cg*0.45, cb*0.35),
glow_radius: core*0.14,
mass: 0.0, lifetime: dt*1.5,
layer: RenderLayer::Entity, blend_mode: BlendMode::Normal,
..Default::default()
});
}
const ARM_AX:f32=0.500; const ARM_AY:f32=-0.60;
const ARM_BX:f32=0.530; const ARM_BY:f32=-0.10;
const ARM_RA:f32=0.095; const ARM_RB:f32=0.082;
for i in 0..N_ARM {
let t = hf(i, 90); let py = ARM_AY + t * (ARM_BY - ARM_AY);
let cx = ARM_AX + t * (ARM_BX - ARM_AX);
let r = ARM_RA + t * (ARM_RB - ARM_RA);
let rx = hf(i, 91)*2.0-1.0; let rz = hf(i, 92)*2.0-1.0;
let rn = (rx*rx + rz*rz).sqrt().max(0.001);
let dx = rx/rn; let dz = rz/rn;
let noise = (hf(i, 93)*2.0 - 1.0) * SHELL;
let px = cx + (r*1.05 + noise) * dx;
let pz = (r*0.95 + noise) * dz;
let (d_t, _, _) = sdf_torso(px, py, pz);
let (d_a, _) = sdf_arm_r(px, py, pz);
let d = smin(d_t, d_a, 0.04);
if d < -SHELL || d > SHELL { continue; }
let nx = (px - cx) / (1.05*1.05);
let nz = pz / (0.95*0.95);
let nn = (nx*nx + nz*nz).sqrt().max(0.001);
let (snx, sny, snz) = (nx/nn, 0.0f32, nz/nn);
let kappa = sdf_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa * 0.032).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw * wrinkle_raw * (3.0 - 2.0 * wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let inset = wrinkle * 0.005;
let px = px + (sd.disp - inset) * snx;
let py = py + (sd.disp - inset) * sny;
let pz = pz + (sd.disp - inset) * snz;
let hz = sd.pnz.max(0.0);
let core = ((-d / SHELL + 1.0) * 0.5).clamp(0.0, 1.0);
let (br, bg, bb) = (0.54f32, 0.37, 0.19);
let mat_tag = MatTag::Jacket;
let (cr, cg, cb) = sdf_shade(sd.pnx, sd.pny, sd.pnz, hz, br, bg, bb, mat_tag, <s);
let ao = sdf_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr, mut cg, mut cb) = (cr * ao, cg * ao, cb * ao);
{
let back_key = (-sd.pnx*lts.klx - sd.pny*lts.kly - sd.pnz*lts.klz).max(0.0);
let back_torch = (-sd.pnx*lts.f3x - sd.pny*lts.f3y - sd.pnz*lts.f3z).max(0.0) * lts.f3_int;
let back_illum = back_key * 0.80 + back_torch;
if back_illum > 0.06 {
let thick = sdf_thickness(px, py, pz, sd.pnx, sd.pny, sd.pnz);
const MU: f32 = 10.0; let trans = (-MU * thick).exp() * back_illum;
cr += trans * 0.85;
cg += trans * 0.48;
cb += trans * 0.12;
}
}
{
let dk_bump = (sd.pnx*lts.klx + sd.pny*lts.kly + sd.pnz*lts.klz).max(0.0);
let micro = sd.roughness * sd.roughness * dk_bump * 0.10;
cr += micro * 0.90;
cg += micro * 0.72;
cb += micro * 0.40; }
if wrinkle > 0.01 {
cr *= 1.0 - wrinkle * 0.58;
cg *= 1.0 - wrinkle * 0.65;
cb *= 1.0 - wrinkle * 0.72;
}
{
let key_t = vol_shadow(px, py, pz, lts.klx, lts.kly, lts.klz);
let dk_lit = (sd.pnx*lts.klx + sd.pny*lts.kly + sd.pnz*lts.klz).max(0.0);
let vol_atten = 1.0 - dk_lit * (1.0 - key_t) * 0.72;
cr *= vol_atten;
cg *= vol_atten;
cb *= vol_atten;
}
{
let dk_gi = (sd.pnx*lts.klx + sd.pny*lts.kly + sd.pnz*lts.klz).max(0.0);
let f3_gi = (sd.pnx*lts.f3x + sd.pny*lts.f3y + sd.pnz*lts.f3z).max(0.0) * lts.f3_int;
let illum = dk_gi * 0.80 + f3_gi * 0.40 + 0.12;
let (gi_r, gi_g, gi_b) = gi_bounce(px, py, snx, sny, snz, illum);
cr += gi_r; cg += gi_g; cb += gi_b;
}
let alpha = ((0.35 + core*0.55) * hp.max(0.08) * (1.0 + wrinkle * 0.40)).min(0.97);
if alpha < 0.012 { continue; }
let jx = (hf(i, 94)-0.5) * dmg * 0.08;
let jy = (hf(i, 95)-0.5) * dmg * 0.08;
let emission = (0.45 + core*1.20) * (1.0 - wrinkle * 0.38);
let sz = 0.020 + core*0.018;
let ch = if core>0.7 {'*'} else if core>0.35 {'+'} else {'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x + px*scale*breath + jx,
pos.y + py*scale*breath + jy,
pos.z + hz*0.30),
color: Vec4::new(cr, cg, cb, alpha),
emission,
glow_color: Vec3::new(cr*0.55, cg*0.45, cb*0.35),
glow_radius: core*0.14,
mass: 0.0, lifetime: dt*1.5,
layer: RenderLayer::Entity, blend_mode: BlendMode::Normal,
..Default::default()
});
}
const N_ARM_L: usize = 34_000;
for i in 0..N_ARM_L {
let t = hf(i, 100);
let py = ARM_AY + t * (ARM_BY - ARM_AY);
let cx = -(ARM_AX + t * (ARM_BX - ARM_AX)); let r = ARM_RA + t * (ARM_RB - ARM_RA);
let rx = hf(i, 101)*2.0-1.0; let rz = hf(i, 102)*2.0-1.0;
let rn = (rx*rx + rz*rz).sqrt().max(0.001);
let dx = rx/rn; let dz = rz/rn;
let noise = (hf(i, 103)*2.0-1.0)*SHELL;
let px = cx + (r*1.05 + noise)*dx;
let pz = (r*0.95 + noise)*dz;
let (d_t, _, _) = sdf_torso(px, py, pz);
let (d_a, _) = sdf_arm_r(-px, py, pz);
let d = smin(d_t, d_a, 0.04);
if d < -SHELL || d > SHELL { continue; }
let nx_raw = (px - cx) / (1.05*1.05);
let nz_raw = pz / (0.95*0.95);
let nn = (nx_raw*nx_raw + nz_raw*nz_raw).sqrt().max(0.001);
let (snx, sny, snz) = (nx_raw/nn, 0.0f32, nz_raw/nn);
let kappa = sdf_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa*0.032).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw*wrinkle_raw*(3.0-2.0*wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let inset = wrinkle*0.005;
let px = px+(sd.disp-inset)*snx; let py = py+(sd.disp-inset)*sny;
let pz = pz+(sd.disp-inset)*snz; let hz = sd.pnz.max(0.0);
let core = ((-d/SHELL+1.0)*0.5).clamp(0.0, 1.0);
let (br,bg,bb) = (0.54f32, 0.37, 0.19);
let mat_tag = MatTag::Jacket;
let (cr,cg,cb) = sdf_shade(sd.pnx,sd.pny,sd.pnz, hz, br,bg,bb, mat_tag, <s);
let ao = sdf_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr,mut cg,mut cb) = (cr*ao, cg*ao, cb*ao);
{ let bk=(-sd.pnx*lts.klx-sd.pny*lts.kly-sd.pnz*lts.klz).max(0.0);
let bt=(-sd.pnx*lts.f3x-sd.pny*lts.f3y-sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let bi=bk*0.80+bt; if bi>0.06 { let th=sdf_thickness(px,py,pz,sd.pnx,sd.pny,sd.pnz);
let tr=(-10.0*th).exp()*bi; cr+=tr*0.85; cg+=tr*0.48; cb+=tr*0.12; } }
{ let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let m=sd.roughness*sd.roughness*dk*0.10;
cr+=m*0.90; cg+=m*0.72; cb+=m*0.40; }
if wrinkle>0.01 { cr*=1.0-wrinkle*0.58; cg*=1.0-wrinkle*0.65; cb*=1.0-wrinkle*0.72; }
{ let kt=vol_shadow(px,py,pz,lts.klx,lts.kly,lts.klz);
let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let va=1.0-dk*(1.0-kt)*0.72; cr*=va; cg*=va; cb*=va; }
{ let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let f3=(sd.pnx*lts.f3x+sd.pny*lts.f3y+sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let (gr,gg,gb)=gi_bounce(px,py,snx,sny,snz,dk*0.80+f3*0.40+0.12);
cr+=gr; cg+=gg; cb+=gb; }
let alpha=((0.35+core*0.55)*hp.max(0.08)*(1.0+wrinkle*0.40)).min(0.97);
if alpha<0.012 { continue; }
let jx=(hf(i,104)-0.5)*dmg*0.08; let jy=(hf(i,105)-0.5)*dmg*0.08;
let emission=(0.45+core*1.20)*(1.0-wrinkle*0.38);
let sz=0.020+core*0.018; let ch=if core>0.7{'*'}else if core>0.35{'+'}else{'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x+px*scale*breath+jx, pos.y+py*scale*breath+jy, pos.z+hz*0.30),
color: Vec4::new(cr,cg,cb,alpha), emission,
glow_color: Vec3::new(cr*0.55,cg*0.45,cb*0.35), glow_radius: core*0.14,
mass:0.0, lifetime:dt*1.5,
layer:RenderLayer::Entity, blend_mode:BlendMode::Normal, ..Default::default()
});
}
const N_FA: usize = 28_000;
const FA_AX:f32=0.530; const FA_AY:f32=-0.10;
const FA_BX:f32=0.555; const FA_BY:f32= 0.36;
const FA_RA:f32=0.075; const FA_RB:f32= 0.050;
for side in 0..2usize {
let sign = if side == 0 { 1.0f32 } else { -1.0 };
let seed_base = if side == 0 { 110usize } else { 120 };
let dmg_base = if side == 0 { 114usize } else { 124 };
for i in 0..N_FA {
let t = hf(i, seed_base);
let py = FA_AY + t*(FA_BY - FA_AY);
let cx = sign*(FA_AX + t*(FA_BX - FA_AX));
let r = FA_RA + t*(FA_RB - FA_RA);
let rx = hf(i, seed_base+1)*2.0-1.0; let rz = hf(i, seed_base+2)*2.0-1.0;
let rn = (rx*rx+rz*rz).sqrt().max(0.001);
let dx = rx/rn; let dz = rz/rn;
let noise = (hf(i, seed_base+3)*2.0-1.0)*SHELL;
let px = cx + (r*1.05+noise)*dx;
let pz = (r*0.95+noise)*dz;
let d_fa = sdf_forearm_r(sign*px, py, pz);
let (d_arm,_) = sdf_arm_r(sign*px, py, pz);
let d = smin(d_fa, d_arm, 0.025);
if d < -SHELL || d > SHELL { continue; }
let nx_raw = (px-cx)/(1.05*1.05); let nz_raw = pz/(0.95*0.95);
let nn = (nx_raw*nx_raw+nz_raw*nz_raw).sqrt().max(0.001);
let (snx,sny,snz) = (nx_raw/nn, 0.0f32, nz_raw/nn);
let kappa = sdf_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa*0.032).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw*wrinkle_raw*(3.0-2.0*wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let inset = wrinkle*0.005;
let px = px+(sd.disp-inset)*snx; let py = py+(sd.disp-inset)*sny;
let pz = pz+(sd.disp-inset)*snz; let hz = sd.pnz.max(0.0);
let core = ((-d/SHELL+1.0)*0.5).clamp(0.0, 1.0);
let (br,bg,bb) = leon_color(px, py);
let mat_tag = leon_tag(px, py); let (cr,cg,cb) = sdf_shade(sd.pnx,sd.pny,sd.pnz, hz, br,bg,bb, mat_tag, <s);
let ao = sdf_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr,mut cg,mut cb) = (cr*ao, cg*ao, cb*ao);
if mat_tag == MatTag::Skin {
let bk=(-sd.pnx*lts.klx-sd.pny*lts.kly-sd.pnz*lts.klz).max(0.0);
let bt=(-sd.pnx*lts.f3x-sd.pny*lts.f3y-sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let bi=bk*0.80+bt; if bi>0.04 {
let th=sdf_thickness(px,py,pz,sd.pnx,sd.pny,sd.pnz);
let tr=(-7.5*th).exp()*bi; cr+=tr*1.00; cg+=tr*0.28; cb+=tr*0.08; }
} else {
let bk=(-sd.pnx*lts.klx-sd.pny*lts.kly-sd.pnz*lts.klz).max(0.0);
let bt=(-sd.pnx*lts.f3x-sd.pny*lts.f3y-sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let bi=bk*0.80+bt; if bi>0.06 {
let th=sdf_thickness(px,py,pz,sd.pnx,sd.pny,sd.pnz);
let tr=(-10.0*th).exp()*bi; cr+=tr*0.85; cg+=tr*0.48; cb+=tr*0.12; }
}
{ let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let m=sd.roughness*sd.roughness*dk;
let (mr,mg,mb,sc)=if mat_tag==MatTag::Skin {(1.00f32,0.94,0.82,0.14)}
else{(0.90f32,0.72,0.40,0.10)};
cr+=m*sc*mr; cg+=m*sc*mg; cb+=m*sc*mb; }
if wrinkle>0.01 {
let (wr,wg,wb)=if mat_tag==MatTag::Skin{(0.52f32,0.62,0.80)}
else{(0.58f32,0.65,0.72)};
cr*=1.0-wrinkle*wr; cg*=1.0-wrinkle*wg; cb*=1.0-wrinkle*wb; }
{ let kt=vol_shadow(px,py,pz,lts.klx,lts.kly,lts.klz);
let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let va=1.0-dk*(1.0-kt)*0.72; cr*=va; cg*=va; cb*=va; }
{ let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let f3=(sd.pnx*lts.f3x+sd.pny*lts.f3y+sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let (gr,gg,gb)=gi_bounce(px,py,snx,sny,snz,dk*0.80+f3*0.40+0.12);
cr+=gr; cg+=gg; cb+=gb; }
let alpha=((0.35+core*0.55)*hp.max(0.08)*(1.0+wrinkle*0.40)).min(0.97);
if alpha<0.012 { continue; }
let jx=(hf(i,dmg_base)-0.5)*dmg*0.08; let jy=(hf(i,dmg_base+1)-0.5)*dmg*0.08;
let emission=(0.45+core*1.20)*(1.0-wrinkle*0.38);
let sz=0.020+core*0.018; let ch=if core>0.7{'*'}else if core>0.35{'+'}else{'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x+px*scale*breath+jx, pos.y+py*scale*breath+jy, pos.z+hz*0.30),
color: Vec4::new(cr,cg,cb,alpha), emission,
glow_color: Vec3::new(cr*0.55,cg*0.45,cb*0.35), glow_radius: core*0.14,
mass:0.0, lifetime:dt*1.5,
layer:RenderLayer::Entity, blend_mode:BlendMode::Normal, ..Default::default()
});
}
}
const N_LEG: usize = 45_000;
const LEG_CX: f32 = 0.145;
const LEG_Y0: f32 = 0.28;
const LEG_Y1: f32 = 0.97;
for side in 0..2usize {
let sign = if side == 0 { 1.0f32 } else { -1.0 };
let seed_base = if side == 0 { 130usize } else { 140 };
let dmg_base = if side == 0 { 134usize } else { 144 };
for i in 0..N_LEG {
let v = hf(i, seed_base);
let py = LEG_Y0 + v*(LEG_Y1 - LEG_Y0);
let r_expected = if py < 0.72 {
let t=(py-0.28)/0.44; 0.105+t*(0.088-0.105)
} else {
let t=((py-0.68)/0.29).clamp(0.0,1.0); 0.090+t*(0.055-0.090)
};
let rx = hf(i, seed_base+1)*2.0-1.0; let rz = hf(i, seed_base+2)*2.0-1.0;
let rn = (rx*rx+rz*rz).sqrt().max(0.001);
let dx_u = rx/rn; let dz_u = rz/rn;
let noise = (hf(i, seed_base+3)*2.0-1.0)*SHELL;
let px_u = LEG_CX + (r_expected+noise)*dx_u;
let pz_u = (r_expected+noise)*dz_u*0.92;
let px = sign*px_u;
let pz = pz_u;
let d = sdf_leg_r(sign*px, py, pz);
if d < -SHELL || d > SHELL { continue; }
let nx_raw = px_u - LEG_CX;
let nz_raw = pz;
let nn = (nx_raw*nx_raw+nz_raw*nz_raw).sqrt().max(0.001);
let (snx_u,sny,snz) = (nx_raw/nn, 0.0f32, nz_raw/nn);
let snx = sign*snx_u;
let kappa = sdf_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa*0.030).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw*wrinkle_raw*(3.0-2.0*wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let inset = wrinkle*0.005;
let px = px+(sd.disp-inset)*snx; let py = py+(sd.disp-inset)*sny;
let pz = pz+(sd.disp-inset)*snz; let hz = sd.pnz.max(0.0);
let core = ((-d/SHELL+1.0)*0.5).clamp(0.0, 1.0);
let (br,bg,bb) = leon_color(px, py);
let mat_tag = leon_tag(px, py); let (cr,cg,cb) = sdf_shade(sd.pnx,sd.pny,sd.pnz, hz, br,bg,bb, mat_tag, <s);
let ao = sdf_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr,mut cg,mut cb) = (cr*ao, cg*ao, cb*ao);
if mat_tag == MatTag::Jacket {
let bk=(-sd.pnx*lts.klx-sd.pny*lts.kly-sd.pnz*lts.klz).max(0.0);
let bt=(-sd.pnx*lts.f3x-sd.pny*lts.f3y-sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let bi=bk*0.80+bt; if bi>0.06 {
let th=sdf_thickness(px,py,pz,sd.pnx,sd.pny,sd.pnz);
let tr=(-12.0*th).exp()*bi;
cr+=tr*0.55; cg+=tr*0.68; cb+=tr*0.25; } }
{ let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let m=sd.roughness*sd.roughness*dk;
let (mr,mg,mb)=if mat_tag==MatTag::Boot{(m*0.08f32,m*0.06,m*0.04)}
else{(m*0.08f32,m*0.09,m*0.05)};
cr+=mr; cg+=mg; cb+=mb; }
if wrinkle>0.01 { cr*=1.0-wrinkle*0.55; cg*=1.0-wrinkle*0.62; cb*=1.0-wrinkle*0.70; }
{ let kt=vol_shadow(px,py,pz,lts.klx,lts.kly,lts.klz);
let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let va=1.0-dk*(1.0-kt)*0.72; cr*=va; cg*=va; cb*=va; }
{ let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let f3=(sd.pnx*lts.f3x+sd.pny*lts.f3y+sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let (gr,gg,gb)=gi_bounce(px,py,snx,sny,snz,dk*0.80+f3*0.40+0.12);
cr+=gr; cg+=gg; cb+=gb; }
let alpha=((0.35+core*0.55)*hp.max(0.08)*(1.0+wrinkle*0.35)).min(0.97);
if alpha<0.012 { continue; }
let jx=(hf(i,dmg_base)-0.5)*dmg*0.08; let jy=(hf(i,dmg_base+1)-0.5)*dmg*0.08;
let emission=(if mat_tag==MatTag::Boot{0.35+core*0.80}else{0.42+core*1.10})
*(1.0-wrinkle*0.38);
let sz=0.020+core*0.018; let ch=if core>0.7{'*'}else if core>0.35{'+'}else{'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x+px*scale*breath+jx, pos.y+py*scale*breath+jy, pos.z+hz*0.30),
color: Vec4::new(cr,cg,cb,alpha), emission,
glow_color: Vec3::new(cr*0.55,cg*0.45,cb*0.35), glow_radius: core*0.14,
mass:0.0, lifetime:dt*1.5,
layer:RenderLayer::Entity, blend_mode:BlendMode::Normal, ..Default::default()
});
}
}
}
fn render_vol_scatter(engine: &mut ProofEngine, dt: f32, time: f32, pos: Vec3) {
const N: usize = 18_000;
const SCALE: f32 = 3.2;
const ATM_REACH: f32 = 0.22; let nu = |x: f32, y: f32, z: f32| {
let l = (x*x+y*y+z*z).sqrt(); (x/l, y/l, z/l)
};
let (klx, kly, klz) = nu(-0.40, -0.55, 0.73);
let (f3x, f3y, f3z) = nu(TORCH_X, TORCH_Y, TORCH_Z);
let f3_int = 0.28 * (0.80 + (time*7.3).sin()*0.11 + (time*13.1).cos()*0.07);
let breath = 1.0 + (time * 1.4).sin() * 0.010;
for i in 0..N {
let angle = hf(i, 115) * TAU;
let r = 0.05 + hf(i, 116) * 0.20;
let px = angle.cos() * r;
let pz = angle.sin() * r * 0.55 + 0.06;
let py = -1.25 + hf(i, 117) * 2.40;
let d = sdf_body(px, py, pz);
if d < 0.04 { continue; }
if d > ATM_REACH { continue; }
let prox = 1.0 - d / ATM_REACH;
let key_t = vol_shadow(px, py, pz, klx, kly, klz);
let tch_t = vol_shadow(px, py, pz, f3x, f3y, f3z);
let transmit = key_t * 0.68 + tch_t * f3_int * 0.32;
if transmit < 0.08 { continue; }
let alpha = transmit * prox * prox * (0.011 + hf(i, 118) * 0.015);
if alpha < 0.003 { continue; }
let cr = key_t * 1.00 + tch_t * f3_int * 0.90;
let cg = key_t * 0.92 + tch_t * f3_int * 0.60;
let cb = key_t * 0.72 + tch_t * f3_int * 0.20;
let cm = cr.max(cg).max(cb).max(0.001);
let (cr, cg, cb) = (cr/cm * transmit, cg/cm * transmit, cb/cm * transmit);
let sz = 0.030 + hf(i, 119) * 0.022;
engine.spawn_glyph(Glyph {
character: '.',
scale: Vec2::splat(sz),
position: Vec3::new(
pos.x + px * SCALE * breath,
pos.y + py * SCALE * breath,
pos.z + pz * SCALE * 0.85, ),
color: Vec4::new(cr * alpha, cg * alpha, cb * alpha, alpha),
emission: transmit * 2.0,
glow_color: Vec3::new(0.92, 0.84, 0.58),
glow_radius: 0.10,
mass: 0.0,
lifetime: dt * 1.5,
layer: RenderLayer::Background,
blend_mode: BlendMode::Additive,
..Default::default()
});
}
}
fn render_sdf_face(engine: &mut ProofEngine, dt: f32, time: f32, pos: Vec3, hp: f32) {
const SHELL: f32 = 0.016;
const N_MAIN: usize = 88_000; const N_EYE: usize = 26_000; const N_NOSE: usize = 9_000; const N_MOUTH: usize = 7_000;
let scale = 3.2f32;
let breath = 1.0 + (time * 1.4).sin() * 0.010;
let dmg = (1.0 - hp).max(0.0);
let lts = {
let n = |x:f32,y:f32,z:f32|{ let l=(x*x+y*y+z*z).sqrt(); (x/l,y/l,z/l) };
let (klx,kly,klz) = n(-0.40,-0.55, 0.73);
let (rlx,rly,rlz) = n( 0.55,-0.18,-0.82);
let (f1x,f1y,f1z) = n( 0.30, 0.60,-0.40);
let (f3x,f3y,f3z) = n(TORCH_X,TORCH_Y,TORCH_Z);
SdfLights {
klx,kly,klz, rlx,rly,rlz,
f1x,f1y,f1z,
f2x:0.0, f2y:-1.0, f2z:0.0,
f3x,f3y,f3z,
f3_int: 0.28*(0.80+(time*7.3).sin()*0.11+(time*13.1).cos()*0.07),
}
};
for i in 0..N_MAIN {
let rx = hf(i,240)*2.0-1.0;
let ry = hf(i,241)*2.0-1.0;
let rz = hf(i,242)*2.0-1.0;
let rn = (rx*rx+ry*ry+rz*rz).sqrt().max(0.001);
let ex = (rx/rn)*HEAD_RX;
let ey = (ry/rn)*HEAD_RY;
let ez = (rz/rn)*HEAD_RZ;
let gnx = ex/(HEAD_RX*HEAD_RX);
let gny = ey/(HEAD_RY*HEAD_RY);
let gnz = ez/(HEAD_RZ*HEAD_RZ);
let gnn = (gnx*gnx+gny*gny+gnz*gnz).sqrt().max(0.001);
let noise = (hf(i,243)*2.0-1.0)*SHELL;
let px = ex + noise*gnx/gnn;
let py = HEAD_CY + ey + noise*gny/gnn;
let pz = ez + noise*gnz/gnn;
let d = sdf_face(px, py, pz);
if d < -SHELL || d > SHELL { continue; }
let der = sdf_sphere_f(px-EYEB_CX, py-EYEB_CY, pz-EYEB_CZ, EYEB_R);
let del_ = sdf_sphere_f(px+EYEB_CX, py-EYEB_CY, pz-EYEB_CZ, EYEB_R);
if der.abs() < SHELL*2.0 || del_.abs() < SHELL*2.0 { continue; }
let (snx,sny,snz) = face_normal(px, py, pz);
let hz = snz.max(0.0);
let core = ((-d/SHELL+1.0)*0.5).clamp(0.0, 1.0);
let kappa = sdf_face_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa * 0.030).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw*wrinkle_raw*(3.0-2.0*wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let inset = wrinkle*0.004;
let px = px + (sd.disp-inset)*snx;
let py = py + (sd.disp-inset)*sny;
let pz = pz + (sd.disp-inset)*snz;
let (br,bg,bb) = leon_color(px, py);
let mat_tag = leon_tag(px, py);
let (cr,cg,cb) = sdf_shade(sd.pnx,sd.pny,sd.pnz, hz, br,bg,bb, mat_tag, <s);
let ao = sdf_face_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr,mut cg,mut cb) = (cr*ao, cg*ao, cb*ao);
if mat_tag == MatTag::Skin {
let bk = (-sd.pnx*lts.klx-sd.pny*lts.kly-sd.pnz*lts.klz).max(0.0);
let bt = (-sd.pnx*lts.f3x-sd.pny*lts.f3y-sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let bi = bk*0.80 + bt;
if bi > 0.04 {
let thick = sdf_face_thickness(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let t = (-6.5*thick).exp()*bi;
cr += t*1.00; cg += t*0.30; cb += t*0.10;
}
}
{ let dk = (sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let m = sd.roughness*sd.roughness*dk*0.12;
cr += m*1.00; cg += m*0.94; cb += m*0.82; }
if wrinkle > 0.01 { cr *= 1.0-wrinkle*0.50; cg *= 1.0-wrinkle*0.58; cb *= 1.0-wrinkle*0.74; }
let alpha = ((0.35+core*0.55)*hp.max(0.08)*(1.0+wrinkle*0.40)).min(0.97);
if alpha < 0.012 { continue; }
let jx = (hf(i,244)-0.5)*dmg*0.08;
let jy = (hf(i,245)-0.5)*dmg*0.08;
let emission = (if br>0.58&&bg>0.46 { 0.40+core*0.80 }
else if br<0.28&&bg<0.18 { 0.12+core*0.30 }
else { 0.45+core*1.20 }) * (1.0-wrinkle*0.35);
let sz = 0.018+core*0.016;
let ch = if core>0.7 {'*'} else if core>0.35 {'+'} else {'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x+px*scale*breath+jx, pos.y+py*scale*breath+jy, pos.z+hz*0.30),
color: Vec4::new(cr,cg,cb,alpha), emission,
glow_color: Vec3::new(cr*0.55, cg*0.45, cb*0.35),
glow_radius: core*0.12,
mass: 0.0, lifetime: dt*1.5,
layer: RenderLayer::Entity, blend_mode: BlendMode::Normal,
..Default::default()
});
}
const N_EYE_HALF: usize = N_EYE / 2;
for i in 0..N_EYE {
let j = i % N_EYE_HALF;
let eye_cx = if i < N_EYE_HALF { EYEB_CX } else { -EYEB_CX };
let rx = hf(j,250)*2.0-1.0;
let ry = hf(j,251)*2.0-1.0;
let rz = hf(j,252)*2.0-1.0;
let rn = (rx*rx+ry*ry+rz*rz).sqrt().max(0.001);
let noise = (hf(j,253)*2.0-1.0)*SHELL;
let r = EYEB_R + noise;
let px = eye_cx + (rx/rn)*r;
let py = EYEB_CY + (ry/rn)*r;
let pz = EYEB_CZ + (rz/rn)*r;
let d = sdf_face(px, py, pz);
if d < -SHELL || d > SHELL { continue; }
let (snx,sny,snz) = face_normal(px, py, pz);
let hz = snz.max(0.0);
let core = ((-d/SHELL+1.0)*0.5).clamp(0.0, 1.0);
let (br,bg,bb,is_iris) = eye_color(px, py, pz, eye_cx);
let (cr,cg,cb) = sdf_shade(snx,sny,snz, hz, br,bg,bb, MatTag::Eye, <s);
let (mut cr,mut cg,mut cb) = (cr,cg,cb);
if !is_iris {
let sd = skin_detail(px, py, pz, snx, sny, snz);
let dk = (sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let m = sd.roughness*sd.roughness*dk*0.05;
cr += m; cg += m*0.97; cb += m*0.95;
}
let alpha = ((0.40+core*0.50)*hp.max(0.08)).min(0.97);
if alpha < 0.012 { continue; }
let jx = (hf(j,254)-0.5)*dmg*0.06;
let jy = (hf(j,255)-0.5)*dmg*0.06;
let emission = if is_iris { 0.25+core*0.45 } else { 0.55+core*1.10 };
let sz = 0.014+core*0.013;
let ch = if is_iris { '.' } else if core>0.7 {'*'} else {'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x+px*scale*breath+jx, pos.y+py*scale*breath+jy, pos.z+hz*0.30),
color: Vec4::new(cr,cg,cb,alpha), emission,
glow_color: if is_iris { Vec3::new(0.38,0.26,0.10) }
else { Vec3::new(cr*0.40,cg*0.35,cb*0.30) },
glow_radius: if is_iris { 0.07 } else { core*0.10 },
mass: 0.0, lifetime: dt*1.5,
layer: RenderLayer::Entity, blend_mode: BlendMode::Normal,
..Default::default()
});
}
for i in 0..N_NOSE {
let px = (hf(i,260)*2.0-1.0)*0.052;
let py = -0.558 + hf(i,261)*0.118;
let pz = 0.076 + hf(i,262)*0.072;
let d = sdf_face(px, py, pz);
if d < -SHELL || d > SHELL { continue; }
let (snx,sny,snz) = face_normal(px, py, pz);
let hz = snz.max(0.0);
let core = ((-d/SHELL+1.0)*0.5).clamp(0.0, 1.0);
let kappa = sdf_face_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa*0.028).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw*wrinkle_raw*(3.0-2.0*wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let px = px + sd.disp*snx;
let py = py + sd.disp*sny;
let pz = pz + sd.disp*snz;
let (br,bg,bb) = leon_color(px, py);
let mat_tag = leon_tag(px, py);
let (cr,cg,cb) = sdf_shade(sd.pnx,sd.pny,sd.pnz, hz, br,bg,bb, mat_tag, <s);
let ao = sdf_face_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr,mut cg,mut cb) = (cr*ao, cg*ao, cb*ao);
let bk = (-sd.pnx*lts.klx-sd.pny*lts.kly-sd.pnz*lts.klz).max(0.0);
let bt = (-sd.pnx*lts.f3x-sd.pny*lts.f3y-sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let bi = bk*0.80 + bt;
if bi > 0.04 {
let thick = sdf_face_thickness(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let t = (-5.5*thick).exp()*bi;
cr += t*1.00; cg += t*0.38; cb += t*0.18;
}
{ let dk=(sd.pnx*lts.klx+sd.pny*lts.kly+sd.pnz*lts.klz).max(0.0);
let m=sd.roughness*sd.roughness*dk*0.12;
cr+=m*1.00; cg+=m*0.94; cb+=m*0.82; }
if wrinkle>0.01 { cr*=1.0-wrinkle*0.45; cg*=1.0-wrinkle*0.52; cb*=1.0-wrinkle*0.68; }
let alpha = ((0.35+core*0.55)*hp.max(0.08)*(1.0+wrinkle*0.40)).min(0.97);
if alpha < 0.012 { continue; }
let jx=(hf(i,263)-0.5)*dmg*0.08; let jy=(hf(i,264)-0.5)*dmg*0.08;
let emission = (0.42+core*0.85)*(1.0-wrinkle*0.30);
let sz=0.016+core*0.015; let ch=if core>0.6 {'*'} else {'+'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x+px*scale*breath+jx, pos.y+py*scale*breath+jy, pos.z+hz*0.30),
color: Vec4::new(cr,cg,cb,alpha), emission,
glow_color: Vec3::new(cr*0.55,cg*0.45,cb*0.35), glow_radius: core*0.12,
mass: 0.0, lifetime: dt*1.5,
layer: RenderLayer::Entity, blend_mode: BlendMode::Normal,
..Default::default()
});
}
for i in 0..N_MOUTH {
let px = (hf(i,270)*2.0-1.0)*0.082;
let py = -0.410 + hf(i,271)*0.080;
let pz = 0.060 + hf(i,272)*0.046;
let d = sdf_face(px, py, pz);
if d < -SHELL || d > SHELL { continue; }
let (snx,sny,snz) = face_normal(px, py, pz);
let hz = snz.max(0.0);
let core = ((-d/SHELL+1.0)*0.5).clamp(0.0, 1.0);
let kappa = sdf_face_curvature(px, py, pz, snx, sny, snz, d);
let wrinkle_raw = (-kappa*0.030).clamp(0.0, 1.0);
let wrinkle = wrinkle_raw*wrinkle_raw*(3.0-2.0*wrinkle_raw);
let sd = skin_detail(px, py, pz, snx, sny, snz);
let inset = wrinkle*0.003;
let px = px+(sd.disp-inset)*snx;
let py = py+(sd.disp-inset)*sny;
let pz = pz+(sd.disp-inset)*snz;
let (mut br,mut bg,mut bb) = leon_color(px, py);
if px.abs() < 0.058 && py > LIPU_CY-0.018 && py < LIPL_CY+0.020 {
br = (br*1.15).min(1.0); bg = (bg*0.92).min(1.0); bb = (bb*0.88).min(1.0);
}
let mat_tag = leon_tag(px, py);
let (cr,cg,cb) = sdf_shade(sd.pnx,sd.pny,sd.pnz, hz, br,bg,bb, mat_tag, <s);
let ao = sdf_face_ao(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let (mut cr,mut cg,mut cb) = (cr*ao, cg*ao, cb*ao);
let bk = (-sd.pnx*lts.klx-sd.pny*lts.kly-sd.pnz*lts.klz).max(0.0);
let bt = (-sd.pnx*lts.f3x-sd.pny*lts.f3y-sd.pnz*lts.f3z).max(0.0)*lts.f3_int;
let bi = bk*0.80 + bt;
if bi > 0.04 {
let thick = sdf_face_thickness(px, py, pz, sd.pnx, sd.pny, sd.pnz);
let t = (-5.0*thick).exp()*bi;
cr += t*1.00; cg += t*0.28; cb += t*0.18;
}
if wrinkle>0.01 { cr*=1.0-wrinkle*0.62; cg*=1.0-wrinkle*0.70; cb*=1.0-wrinkle*0.82; }
let alpha = ((0.35+core*0.55)*hp.max(0.08)*(1.0+wrinkle*0.55)).min(0.97);
if alpha < 0.012 { continue; }
let jx=(hf(i,273)-0.5)*dmg*0.08; let jy=(hf(i,274)-0.5)*dmg*0.08;
let emission = (0.40+core*0.90)*(1.0-wrinkle*0.40);
let sz=0.016+core*0.015; let ch=if core>0.6 {'+'} else {'.'};
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(sz),
position: Vec3::new(pos.x+px*scale*breath+jx, pos.y+py*scale*breath+jy, pos.z+hz*0.30),
color: Vec4::new(cr,cg,cb,alpha), emission,
glow_color: Vec3::new(cr*0.55,cg*0.45,cb*0.35), glow_radius: core*0.12,
mass: 0.0, lifetime: dt*1.5,
layer: RenderLayer::Entity, blend_mode: BlendMode::Normal,
..Default::default()
});
}
}
fn render_leon(
engine: &mut ProofEngine,
dt: f32,
pos: Vec3,
hp: f32,
time: f32,
tw: f32,
lag: &mut Vec<Vec3>, is_moving: bool, ) {
let (klx, kly, klz) = {
let (x, y, z) = (-0.40f32, -0.55f32, 0.73f32);
let l = (x*x + y*y + z*z).sqrt();
(x/l, y/l, z/l)
};
let (rlx, rly, rlz) = {
let (x, y, z) = (0.55f32, -0.18f32, -0.82f32);
let l = (x*x + y*y + z*z).sqrt();
(x/l, y/l, z/l)
};
let (f1x, f1y, f1z) = {
let (x, y, z) = (0.30f32, 0.60f32, -0.40f32);
let l = (x*x + y*y + z*z).sqrt();
(x/l, y/l, z/l)
};
const F1: (f32, f32, f32) = (1.00, 0.85, 0.70); const F1_INT: f32 = 0.15;
let (f2x, f2y, f2z) = (0.0f32, -1.0f32, 0.0f32); const F2: (f32, f32, f32) = (0.60, 0.70, 1.00); const F2_INT: f32 = 0.05;
let (f3x, f3y, f3z) = {
let (x, y, z) = (TORCH_X, TORCH_Y, TORCH_Z);
let l = (x*x + y*y + z*z).sqrt();
(x/l, y/l, z/l)
};
const F3: (f32, f32, f32) = (1.00, 0.52, 0.15); let f3_int = 0.28 * (0.80 + (time * 7.3).sin() * 0.11 + (time * 13.1).cos() * 0.07);
let scale = 3.2f32;
let dmg = (1.0 - hp).max(0.0);
let breath = 1.0 + (time * 1.4).sin() * 0.010;
for i in 0..500_000usize {
let mut w = hf(i, 0) * tw;
let mut bone = BONES[0];
let mut bone_idx = 0usize;
for (bi, b) in BONES.iter().enumerate() {
w -= b.4 * (b.1 - b.0).abs();
if w <= 0.0 { bone = *b; bone_idx = bi; break; }
}
let (y0, y1, cx, hw, _, ar) = bone;
if bone_idx == 0 { continue; }
if matches!(bone_idx, 1|7|8|9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25) { continue; }
let along_t = hf(i, 1);
let along = y0 + along_t * (y1 - y0);
let body_hw = hw * radius_at(bone_idx, along_t);
let cloth_add = clothing_offset(bone_idx, along_t);
let eff_hw = body_hw + cloth_add;
let spread_raw = (hf(i, 2) + hf(i, 3) - 1.0) * eff_hw;
let x = cx + spread_raw * ar;
let y = along;
let dist = (spread_raw / eff_hw.max(0.001)).abs();
let core = (1.0 - dist * 0.85).max(0.0);
let (mut br, mut bg, mut bb) = leon_color(x, y);
let mat_tag = leon_tag(x, y);
let radial_dir = if spread_raw >= 0.0 { 1.0f32 } else { -1.0 };
let x = x + radial_dir * cloth_add;
let seam_inward = if BONE_CLOTHING[bone_idx] && dist > 0.85 {
let dir_x = if spread_raw >= 0.0 { 1.0f32 } else { -1.0 };
let peek_x = cx + dir_x * hw * ar * 1.20;
let x_adj = leon_tag(peek_x, y) != mat_tag;
let along_t = (along - y0) / ((y1 - y0).abs() + 0.001);
let at_y_end = along_t < 0.15 || along_t > 0.85;
let peek_y = if along_t < 0.15 { y0 - 0.06 } else { y1 + 0.06 };
let y_adj = at_y_end && leon_tag(x, peek_y) != mat_tag;
if x_adj || y_adj {
br *= 0.6;
bg *= 0.6;
bb *= 0.6;
-dir_x * 0.003 } else {
0.0
}
} else {
0.0
};
let contact_dark: bool = match bone_idx {
13 | 15 if dist > 0.45 && spread_raw > 0.0 => true,
14 | 16 if dist > 0.45 && spread_raw < 0.0 => true,
19 | 21 if dist > 0.45 && spread_raw > 0.0 => true,
20 | 22 if dist > 0.45 && spread_raw < 0.0 => true,
2 if (along - y0) / ((y1 - y0).abs() + 0.001) > 0.65 => true,
1 if (along - y0) / ((y1 - y0).abs() + 0.001) > 0.75 => true,
_ => false,
};
if contact_dark { br *= 0.7; bg *= 0.7; bb *= 0.7; }
let hz = ((1.0 - (dist * dist).min(1.0)).sqrt() / ar).min(1.0);
let snx = (spread_raw / hw.max(0.001)) * 0.5;
let sny = (along - (y0 + y1) * 0.5) / ((y1 - y0) * 0.5 + 0.001) * 0.5;
let snz = hz;
let sn_len = (snx*snx + sny*sny + snz*snz).sqrt().max(0.001);
let (snx, sny, snz) = (snx/sn_len, sny/sn_len, snz/sn_len);
let diffuse_key = (snx*klx + sny*kly + snz*klz).max(0.0);
let diffuse_f1 = (snx*f1x + sny*f1y + snz*f1z).max(0.0);
let diffuse_f2 = (snx*f2x + sny*f2y + snz*f2z).max(0.0);
let diffuse_f3 = (snx*f3x + sny*f3y + snz*f3z).max(0.0);
let rim_fac = (1.0 - hz) * (snx*rlx + sny*rly + snz*rlz).max(0.0);
let rim = rim_fac * rim_fac;
let half_z = (klz + 1.0) * 0.5;
let spec = if matches!(mat_tag, MatTag::Boot | MatTag::Metal) {
(snz * half_z).max(0.0).powi(24) * 0.50
} else { 0.0 };
let sky_f = (-sny).max(0.0) * 0.08;
let gnd_f = ( sny).max(0.0) * 0.04;
let amb_r = 0.03 + sky_f * 0.60 + gnd_f * 0.40;
let amb_g = 0.03 + sky_f * 0.75 + gnd_f * 0.35;
let amb_b = 0.03 + sky_f * 1.00 + gnd_f * 0.25;
let diff_spec = diffuse_key * 1.10 + spec;
let light_r = (br * diff_spec + br * amb_r
+ br * diffuse_f1 * F1_INT * F1.0
+ br * diffuse_f2 * F2_INT * F2.0
+ br * diffuse_f3 * f3_int * F3.0).min(1.4);
let light_g = (bg * diff_spec + bg * amb_g
+ bg * diffuse_f1 * F1_INT * F1.1
+ bg * diffuse_f2 * F2_INT * F2.1
+ bg * diffuse_f3 * f3_int * F3.1).min(1.4);
let light_b = (bb * diff_spec + bb * amb_b
+ bb * diffuse_f1 * F1_INT * F1.2
+ bb * diffuse_f2 * F2_INT * F2.2
+ bb * diffuse_f3 * f3_int * F3.2).min(1.4);
let (fr, fg, fb) = fresnel_response(mat_tag, snz);
let mut cr = light_r + rim * 0.12 + fr;
let mut cg = light_g + rim * 0.16 + fg;
let mut cb = light_b + rim * 0.42 + fb;
let tone = |c: f32| c * (1.0 + c * 0.12) / (1.0 + c);
cr = tone(cr).clamp(0.0, 1.0);
cg = tone(cg).clamp(0.0, 1.0);
cb = tone(cb).clamp(0.0, 1.0);
let sc = |c: f32| c * c * (3.0 - 2.0 * c);
cr = sc(cr); cg = sc(cg); cb = sc(cb);
let alpha = (0.35 + core * 0.55) * hp.max(0.08);
if alpha < 0.012 { continue; }
let jx = (hf(i, 4) - 0.5) * dmg * 0.08;
let jy = (hf(i, 5) - 0.5) * dmg * 0.08;
let bone_p = Vec3::new(
(x + seam_inward) * scale * breath,
y * scale * breath,
hz * 0.30,
);
let lag_weight: f32 = match mat_tag {
MatTag::Hair => 0.50,
MatTag::Jacket if y > 0.28 => 0.85, MatTag::Jacket => 0.75, MatTag::Boot if y >= 0.22 && y <= 0.30 => 0.90, MatTag::Boot => 0.95, MatTag::Metal => 0.98,
MatTag::Skin | _ => 0.95,
};
let smoothed_p = if is_moving {
lag[i].lerp(bone_p, lag_weight)
} else {
bone_p };
lag[i] = smoothed_p;
let sz = 0.020 + core * 0.018; let ch = if core > 0.7 { '*' } else if core > 0.35 { '+' } else { '.' };
let particle_z = pos.z + smoothed_p.z;
let blur_amount = ((particle_z - FOCAL_DIST).abs() / DOF_RANGE).clamp(0.0, 1.0);
let dof_jx = (hf(i, 6) - 0.5) * blur_amount * 0.01;
let dof_jy = (hf(i, 7) - 0.5) * blur_amount * 0.01;
let sz = sz * (1.0 + blur_amount * 0.5);
let alpha = alpha / (1.0 + blur_amount);
let emission = if br > 0.58 && bg > 0.46 {
0.40 + core * 0.80 } else if br < 0.28 && bg < 0.18 {
0.12 + core * 0.30 } else if bb < 0.14 {
0.50 + core * 1.50 } else {
0.45 + core * 1.20 };
engine.spawn_glyph(Glyph {
character: ch,
scale: Vec2::splat(sz),
position: Vec3::new(
pos.x + smoothed_p.x + jx + dof_jx,
pos.y + smoothed_p.y + jy + dof_jy,
pos.z + smoothed_p.z,
),
color: Vec4::new(cr, cg, cb, alpha),
emission,
glow_color: Vec3::new(cr * 0.55, cg * 0.45, cb * 0.35),
glow_radius: core * 0.14,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Entity,
blend_mode: BlendMode::Normal,
..Default::default()
});
}
}
fn render_hair(
engine: &mut ProofEngine,
dt: f32,
pos: Vec3,
hp: f32,
hair_lag: &mut Vec<Vec3>,
is_moving: bool,
) {
const N_STRANDS: usize = 500;
const MAX_PER: usize = 20; const SCALE: f32 = 3.2;
const GOLDEN_ANGLE: f32 = 2.399_963_2;
let (klx, kly, klz) = {
let (x, y, z) = (-0.40f32, -0.55f32, 0.73f32);
let l = (x*x + y*y + z*z).sqrt();
(x/l, y/l, z/l)
};
let dmg = (1.0 - hp).max(0.0);
const SKULL_CX: f32 = 0.00;
const SKULL_CY: f32 = -1.00; const SKULL_R: f32 = 0.24;
for si in 0..N_STRANDS {
let theta = (si as f32 / N_STRANDS as f32).sqrt() * 1.15;
let phi = si as f32 * GOLDEN_ANGLE + hf(si, 60) * 0.35;
let ax = SKULL_CX + SKULL_R * theta.sin() * phi.cos();
let ay = SKULL_CY - SKULL_R * theta.cos() * 0.65;
let base_dx = theta.sin() * phi.cos() * 0.018 + 0.006;
let base_dy = theta.cos().abs() * 0.010 + 0.017;
let strand_n = (12 + (hf(si, 61) * 8.0) as usize).min(MAX_PER);
for pi in 0..strand_n {
let fi = pi as f32;
let t = fi / (strand_n - 1).max(1) as f32;
let sag_x = phi.sin() * t * t * 0.011;
let sag_y = t * t * 0.017;
let px = ax + base_dx * fi + sag_x;
let py = ay + base_dy * fi + sag_y;
let tx_r = base_dx + phi.sin() * 2.0 * t * 0.011;
let ty_r = base_dy + 2.0 * t * 0.017;
let tz_r = 0.024f32; let t_len = (tx_r*tx_r + ty_r*ty_r + tz_r*tz_r).sqrt().max(0.001);
let (tx, ty, tz) = (tx_r/t_len, ty_r/t_len, tz_r/t_len);
let dot_lt = (klx*tx + kly*ty + klz*tz).abs();
let hair_spec = (1.0 - dot_lt).powi(8) * 0.6;
let hr = (0.20 + hair_spec * 0.58).min(1.0);
let hg = (0.12 + hair_spec * 0.44).min(1.0);
let hb = (0.06 + hair_spec * 0.22).min(1.0);
let alpha = ((0.80 - t * 0.70) * hp.max(0.1)).max(0.0);
if alpha < 0.01 { continue; }
let lag_weight = 0.88 - t * 0.50;
let lag_idx = si * MAX_PER + pi;
let bone_p = Vec3::new(px * SCALE, py * SCALE, 0.18 - t * 0.06);
let smoothed_p = if is_moving && lag_idx < hair_lag.len() {
hair_lag[lag_idx].lerp(bone_p, lag_weight)
} else {
bone_p
};
if lag_idx < hair_lag.len() { hair_lag[lag_idx] = smoothed_p; }
let seed = si * 13 + pi;
let jx = (hf(seed, 0) - 0.5) * dmg * 0.05;
let jy = (hf(seed, 1) - 0.5) * dmg * 0.05;
let particle_z = pos.z + smoothed_p.z;
let blur_amount = ((particle_z - FOCAL_DIST).abs() / DOF_RANGE).clamp(0.0, 1.0);
let dof_jx = (hf(seed, 2) - 0.5) * blur_amount * 0.01;
let dof_jy = (hf(seed, 3) - 0.5) * blur_amount * 0.01;
let sz_dof = (0.024 - t * 0.012).max(0.008) * (1.0 + blur_amount * 0.5);
let alpha_dof = alpha / (1.0 + blur_amount);
if alpha_dof < 0.01 { continue; }
let ch = if pi == 0 { '*' } else if t < 0.5 { '+' } else { '.' };
engine.spawn_glyph(Glyph {
character: ch,
scale: Vec2::splat(sz_dof),
position: Vec3::new(
pos.x + smoothed_p.x + jx + dof_jx,
pos.y + smoothed_p.y + jy + dof_jy,
pos.z + smoothed_p.z,
),
color: Vec4::new(hr, hg, hb, alpha_dof),
emission: 0.10 + hair_spec * 0.70,
glow_color: Vec3::new(hr * 0.55, hg * 0.40, 0.0),
glow_radius: 0.05 + hair_spec * 0.14,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Entity,
blend_mode: BlendMode::Normal,
..Default::default()
});
}
}
}
fn spawn_burst(engine: &mut ProofEngine, dt: f32, origin: Vec3,
cr: f32, cg: f32, cb: f32, count: usize, radius: f32, seed: usize) {
for i in 0..count {
let angle = i as f32 / count as f32 * TAU + hf(seed, i) * 0.4;
let r = radius * (0.4 + hf(seed+1, i) * 0.6);
let speed = 0.5 + hf(seed+2, i) * 0.8;
engine.spawn_glyph(Glyph {
character: if i % 3 == 0 { '*' } else { '+' },
scale: Vec2::splat(0.06 + hf(seed+3, i) * 0.06),
position: Vec3::new(origin.x + angle.cos()*r, origin.y + angle.sin()*r*0.5, origin.z+0.4),
velocity: Vec3::new(angle.cos()*speed, angle.sin()*speed*0.5 - 0.2, 0.0),
color: Vec4::new(cr, cg, cb, 0.7 + hf(seed+4,i)*0.3),
emission: 2.0 + hf(seed+5,i)*2.0,
glow_color: Vec3::new(cr, cg, cb), glow_radius: 0.5,
mass: 0.0, lifetime: dt*1.5, layer: RenderLayer::Particle,
blend_mode: BlendMode::Additive, ..Default::default()
});
}
}
fn spawn_stream(engine: &mut ProofEngine, dt: f32, origin: Vec3,
cr: f32, cg: f32, cb: f32, count: usize, seed: usize, time: f32) {
for i in 0..count {
let phase = hf(seed, i) * TAU;
let sx = (hf(seed+1, i) - 0.5) * 0.30;
let rise = hf(seed+2, i) * 0.65;
engine.spawn_glyph(Glyph {
character: if i%2==0 {'*'} else {'.'},
scale: Vec2::splat(0.04 + hf(seed+3,i)*0.04),
position: Vec3::new(
origin.x + sx + (time*3.0+phase).sin()*0.09,
origin.y - rise,
origin.z + 0.3 + hf(seed+4,i)*0.4,
),
color: Vec4::new(cr, cg, cb*(0.3+hf(seed+5,i)*0.7), 0.5+hf(seed+6,i)*0.4),
emission: 2.0+hf(seed+7,i)*2.0,
glow_color: Vec3::new(cr, cg*0.7, 0.1), glow_radius: 0.38,
mass: 0.0, lifetime: dt*1.5, layer: RenderLayer::Particle,
blend_mode: BlendMode::Additive, ..Default::default()
});
}
}
fn render_ground(engine: &mut ProofEngine, dt: f32, time: f32) {
let flicker = 0.80 + (time * 7.3).sin() * 0.11 + (time * 13.1).cos() * 0.07;
const X_STEPS: usize = 24;
const Z_STEPS: usize = 16;
for zi in 0..Z_STEPS {
let gz = -5.0 + zi as f32 * 0.36; for xi in 0..X_STEPS {
let gx = -7.0 + xi as f32 * 0.61;
let d_torch = ((gx - TORCH_X).powi(2) + (gz - TORCH_Z).powi(2)).sqrt();
let d_leon = (gx.powi(2) + gz.powi(2)).sqrt();
let torch_f = flicker / (1.0 + d_torch * 0.75).powi(2);
let leon_f = 1.0 / (1.0 + d_leon * 0.55).powi(2);
let shadow_f = {
let shadow_cx = -0.30f32; let shadow_cz = 0.55f32;
let sdx = gx - shadow_cx; let sdz = gz - shadow_cz;
let r2 = sdx*sdx + sdz*sdz;
let contact = (1.0 - r2 / (1.8*1.8)).max(0.0).powi(2);
let prox = (1.0 - (gx*gx + gz*gz) / (1.4*1.4)).max(0.0).powi(3);
(contact*0.42 + prox*0.28).min(0.65)
};
let fr_raw = (0.06 + torch_f * 0.60 + leon_f * 0.10).min(1.0);
let fg_raw = (0.07 + torch_f * 0.26 + leon_f * 0.09).min(1.0);
let fb_raw = (0.10 + torch_f * 0.05 + leon_f * 0.22).min(1.0);
let fr = fr_raw * (1.0 - shadow_f);
let fg = fg_raw * (1.0 - shadow_f);
let fb = fb_raw * (1.0 - shadow_f);
let z_fade = ((gz + 5.0) / 5.8).clamp(0.0, 1.0);
let x_fade = (1.0 - gx.abs() / 7.8).clamp(0.0, 1.0);
let fa = (0.12 + torch_f * 0.55 + leon_f * 0.38) * z_fade * x_fade;
if fa < 0.01 { continue; }
let on_x = xi % 4 == 0;
let on_z = zi % 3 == 0;
let ch = if on_x && on_z { '+' } else if on_x { '|' } else if on_z { '-' } else { '.' };
engine.spawn_glyph(Glyph {
character: ch,
scale: Vec2::splat(0.07 + torch_f * 0.03),
position: Vec3::new(gx, FLOOR_Y, gz),
color: Vec4::new(fr, fg, fb, fa),
emission: (torch_f * 0.70 + leon_f * 0.28).min(1.2),
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
}
for i in 0..52usize {
let angle = i as f32 / 52.0 * TAU;
let r = 0.15 + hf(i, 20) * 1.15;
let gx = angle.cos() * r * 1.5;
let gz = angle.sin() * r * 0.55 + 0.10;
let fa = (0.80 - r / 1.35) * 0.36;
if fa < 0.01 { continue; }
engine.spawn_glyph(Glyph {
character: '.',
scale: Vec2::splat(0.09 + (1.0 - r / 1.35) * 0.05),
position: Vec3::new(gx, FLOOR_Y - 0.02, gz),
color: Vec4::new(0.48, 0.58, 0.92, fa),
emission: 0.70 - r / 1.5,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
for i in 0..36usize {
let angle = i as f32 / 36.0 * TAU;
let r = 0.10 + hf(i, 21) * 1.90;
let gx = TORCH_X + angle.cos() * r;
let gz = TORCH_Z + angle.sin() * r * 0.50;
let fa = flicker * (0.72 - r / 2.1).max(0.0) * 0.55;
if fa < 0.01 { continue; }
engine.spawn_glyph(Glyph {
character: '.',
scale: Vec2::splat(0.09),
position: Vec3::new(gx, FLOOR_Y - 0.02, gz),
color: Vec4::new(1.0, 0.50, 0.10, fa),
emission: flicker * 1.10 * (1.0 - r / 2.1).max(0.0),
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
}
fn render_environment(engine: &mut ProofEngine, dt: f32, time: f32) {
let flicker = 0.80 + (time * 7.3).sin() * 0.11 + (time * 13.1).cos() * 0.07;
for i in 0..34usize {
let py = -4.60 + i as f32 * 0.245; let cap = i < 4 || i > 29; let cap_br = if cap { 0.18 } else { 0.0 };
engine.spawn_glyph(Glyph {
character: if cap { '+' } else if i % 5 == 0 { '|' } else { '.' },
scale: Vec2::splat(0.10),
position: Vec3::new(-4.60, py, -0.85),
color: Vec4::new(0.13 + cap_br, 0.12 + cap_br * 0.8, 0.16 + cap_br * 0.9,
0.38 + if cap { 0.22 } else { 0.0 }),
emission: 0.14 + if cap { 0.32 } else { 0.0 },
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
for i in 0..34usize {
let py = -4.60 + i as f32 * 0.245;
let cap = i < 4 || i > 29;
let d_y = (py - TORCH_Y).abs();
let torch_f = flicker / (1.0 + d_y * 0.55).powi(2);
let cap_br = if cap { 0.15 } else { 0.0 };
engine.spawn_glyph(Glyph {
character: if cap { '+' } else if i % 5 == 0 { '|' } else { '.' },
scale: Vec2::splat(0.10),
position: Vec3::new(4.60, py, -0.85),
color: Vec4::new(
0.13 + torch_f * 0.58 + cap_br,
0.10 + torch_f * 0.22 + cap_br * 0.7,
0.10 + torch_f * 0.04 + cap_br * 0.8,
0.38 + torch_f * 0.38 + if cap { 0.18 } else { 0.0 },
),
emission: 0.12 + torch_f * 0.88 + if cap { 0.28 } else { 0.0 },
glow_color: Vec3::new(1.0, 0.48, 0.08) * torch_f,
glow_radius: torch_f * 0.35,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
for band in 0..4usize {
let wy_base = -3.5 + band as f32 * 1.85;
for col in 0..16usize {
let wx = -6.0 + col as f32 * 0.82;
let wy = wy_base + hf(band * 16 + col, 40) * 1.40;
let d_t = ((wx - TORCH_X).powi(2) + (wy - TORCH_Y).powi(2)).sqrt();
let tf = flicker / (1.0 + d_t * 0.50).powi(2);
let x_f = (1.0 - wx.abs() / 6.5).clamp(0.0, 1.0);
let fa = (0.12 + tf * 0.45) * x_f;
if fa < 0.01 { continue; }
let ch = ['.', '-', '+', 'x', '|', ':'][( band * 16 + col) % 6];
engine.spawn_glyph(Glyph {
character: ch,
scale: Vec2::splat(0.09 + hf(band * 16 + col, 41) * 0.04),
position: Vec3::new(wx, wy, -5.20),
color: Vec4::new(0.06 + tf * 0.52, 0.06 + tf * 0.20, 0.08 + tf * 0.04, fa),
emission: 0.08 + tf * 0.55,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
}
for i in 0..5usize {
engine.spawn_glyph(Glyph {
character: ['-', '+', '|', '+', '-'][i],
scale: Vec2::splat(0.12),
position: Vec3::new(TORCH_X + (i as f32 - 2.0) * 0.09, TORCH_Y + 0.22, TORCH_Z),
color: Vec4::new(0.38, 0.26, 0.12, 0.88),
emission: 0.22,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
for i in 0..22usize {
let angle = i as f32 / 22.0 * TAU;
let r = hf(i, 50) * 0.20;
let rise = hf(i, 51) * 0.28; let tx = TORCH_X + angle.cos() * r;
let ty = TORCH_Y - rise; let tz = TORCH_Z + angle.sin() * r * 0.45;
let core = 1.0 - r / 0.22;
let fr = 1.0f32;
let fg = 0.55 + core * 0.38;
let fb = 0.05 + core * 0.35;
engine.spawn_glyph(Glyph {
character: if hf(i, 52) > 0.65 { '*' } else { '+' },
scale: Vec2::splat(0.13 + flicker * 0.05),
position: Vec3::new(tx, ty, tz),
color: Vec4::new(fr, fg, fb, 0.60 + flicker * 0.28),
emission: flicker * (2.8 + core * 1.6),
glow_color: Vec3::new(1.0, 0.44, 0.06),
glow_radius: 0.75 + flicker * 0.42,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Particle, blend_mode: BlendMode::Additive,
..Default::default()
});
}
}
fn render_sky(engine: &mut ProofEngine, dt: f32, time: f32) {
const R: f32 = 20.0;
const N_DOME: usize = 2400;
const GOLDEN_SKY: f32 = 2.399_963_2;
for i in 0..N_DOME {
let fi = i as f32 + 0.5;
let cos_theta = 1.0 - fi / N_DOME as f32; let sin_theta = (1.0 - cos_theta*cos_theta).sqrt();
let phi = GOLDEN_SKY * fi;
let dx = phi.cos() * sin_theta;
let dy = -cos_theta; let dz = phi.sin() * sin_theta;
let jitter = hf(i, 300) * 0.06 - 0.03;
let (mut sr, mut sg, mut sb) = sky_color(dx, dy, dz);
sr = (sr + jitter * 0.20).clamp(0.0, 1.0);
sg = (sg + jitter * 0.15).clamp(0.0, 1.0);
sb = (sb + jitter * 0.10).clamp(0.0, 1.0);
let elev = cos_theta; let fa = (0.06 + (1.0 - elev) * 0.07).clamp(0.02, 0.14);
let sz = 0.18 + (1.0 - elev) * 0.17;
let drift = time * 0.008;
let cos_d = drift.cos(); let sin_d = drift.sin();
let rx = dx*cos_d - dz*sin_d;
let rz = dx*sin_d + dz*cos_d;
engine.spawn_glyph(Glyph {
character: if hf(i, 301) > 0.82 { '·' } else { '.' },
scale: Vec2::splat(sz),
position: Vec3::new(rx * R, dy * R, rz * R),
color: Vec4::new(sr, sg, sb, fa),
emission: 0.30 + elev * 0.08,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
const N_STARS: usize = 180;
for i in 0..N_STARS {
let fi = i as f32 + 0.5;
let cos_theta = 0.55 + (fi / N_STARS as f32) * 0.45; let sin_theta = (1.0 - cos_theta*cos_theta).sqrt();
let phi = GOLDEN_SKY * fi * 3.7;
let dx = phi.cos() * sin_theta;
let dy = -cos_theta;
let dz = phi.sin() * sin_theta;
let twinkle = 0.65 + (time * (2.1 + hf(i, 310) * 3.0) + hf(i, 311) * TAU).sin() * 0.35;
let fa = twinkle * (0.08 + hf(i, 312) * 0.10);
let sz = 0.10 + hf(i, 313) * 0.08;
let col = 0.82 + hf(i, 314) * 0.18;
engine.spawn_glyph(Glyph {
character: if hf(i, 315) > 0.88 { '+' } else { '·' },
scale: Vec2::splat(sz),
position: Vec3::new(dx * R, dy * R, dz * R),
color: Vec4::new(col, col * 0.94, col * 0.88, fa),
emission: twinkle * 1.20,
mass: 0.0, lifetime: dt * 1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive,
..Default::default()
});
}
}
const GPU_N_TORSO: u32 = 2_800_000;
const GPU_N_ARM_R: u32 = 900_000;
const GPU_N_ARM_L: u32 = 900_000;
const GPU_N_FA_R: u32 = 650_000;
const GPU_N_FA_L: u32 = 650_000;
const GPU_N_HAND_R: u32 = 380_000;
const GPU_N_HAND_L: u32 = 380_000;
const GPU_N_LEG_R: u32 = 1_300_000;
const GPU_N_LEG_L: u32 = 1_300_000;
const GPU_N_FOOT_R: u32 = 370_000;
const GPU_N_FOOT_L: u32 = 370_000;
const GPU_N_HEAD: u32 = 800_000; const GPU_N_TOTAL: u32 = GPU_N_TORSO + GPU_N_ARM_R + GPU_N_ARM_L
+ GPU_N_FA_R + GPU_N_FA_L
+ GPU_N_HAND_R + GPU_N_HAND_L
+ GPU_N_LEG_R + GPU_N_LEG_L
+ GPU_N_FOOT_R + GPU_N_FOOT_L
+ GPU_N_HEAD;
const GPU_MAX_PARTICLES: u32 = 10_800_000; const GPU_PARTICLE_BYTES: usize = 48;
const GPU_INDIRECT_BYTES: usize = 16;
const GPU_WG: u32 = 256;
const COMPUTE_SRC: &str = r#"
#version 430 core
layout(local_size_x = 256) in;
// ── Bindings ──────────────────────────────────────────────────────────────────
layout(binding = 0, offset = 0) uniform atomic_uint u_count;
struct GpuParticle {
vec3 position;
float size;
vec3 normal;
float emission;
vec4 color;
};
layout(std430, binding = 1) writeonly buffer ParticleSSBO {
GpuParticle particles[];
};
// ── Per-frame uniforms ────────────────────────────────────────────────────────
uniform float u_time;
uniform float u_hp;
uniform float u_scale; // = 3.2 (world scale)
uniform float u_breath; // = 1 + sin(t*1.4)*0.010
uniform float u_f3_int; // torch flicker intensity
uniform float u_dmg; // = 1 - hp
uniform int u_n; // total candidates = GPU_N_TOTAL
uniform vec3 u_cam_pos; // camera world-space position
uniform float u_exposure; // eye-adaptation exposure multiplier (1.0 = neutral)
// ── Region offsets (cumulative) ───────────────────────────────────────────────
const uint OFF_TORSO = 0u;
const uint OFF_ARM_R = 2800000u;
const uint OFF_ARM_L = 3700000u;
const uint OFF_FA_R = 4600000u;
const uint OFF_FA_L = 5250000u;
const uint OFF_HAND_R = 5900000u;
const uint OFF_HAND_L = 6280000u;
const uint OFF_LEG_R = 6660000u;
const uint OFF_LEG_L = 7960000u;
const uint OFF_FOOT_R = 9260000u;
const uint OFF_FOOT_L = 9630000u;
const uint OFF_HEAD = 10000000u; // face + neck + skull
const uint OFF_END = 10800000u;
// ── Shell half-thickness ──────────────────────────────────────────────────────
// Tight shell = particles cluster at the zero-crossing → solid surface appearance.
const float SHELL = 0.008;
// ── Light directions (constant — match CPU) ───────────────────────────────────
const vec3 KL = vec3(-0.375042, -0.484927, 0.789432); // normalize(-0.40,-0.55,0.73)
const vec3 RL = vec3( 0.559270, -0.183384,-0.808499); // normalize(0.55,-0.18,-0.82)
const vec3 F1D = vec3( 0.371391, 0.742781,-0.557086); // normalize(0.30,0.60,-0.40)
const vec3 F2D = vec3( 0.0, -1.0, 0.0);
const vec3 F3D = vec3( 0.907166, -0.568329,-0.340997); // normalize(3.20,-2.00,-1.20)
const float PI = 3.14159265;
const float TAU = 6.28318530;
// ── Integer hash (identical constants to CPU hf()) ───────────────────────────
float hf(uint seed, uint v) {
uint n = seed * 374761393u + v * 668265263u;
n ^= (n >> 13u);
n *= 0x5851F42Du;
n ^= (n >> 16u);
return float(n & 0x00FFFFFFu) / float(0x01000000u);
}
// ── Smooth-min / smooth-max ───────────────────────────────────────────────────
float smin(float a, float b, float k) {
float h = max(k - abs(a - b), 0.0) / k;
return min(a, b) - h * h * k * 0.25;
}
float smax(float a, float b, float k) {
return -smin(-a, -b, k);
}
// ── Piecewise-linear torso axes (inlined knot tables) ────────────────────────
float plerp_ax(float ty) {
ty = clamp(ty, 0.0, 1.0);
if (ty <= 0.22) return 0.33 + (0.29 - 0.33) * (ty / 0.22); // narrower shoulders
if (ty <= 0.55) return 0.29 + (0.20 - 0.29) * ((ty-0.22) / 0.33); // lean upper chest
if (ty <= 0.72) return 0.20 + (0.23 - 0.20) * ((ty-0.55) / 0.17);
return 0.23 + (0.25 - 0.23) * ((ty-0.72) / 0.28);
}
float plerp_az(float ty) {
ty = clamp(ty, 0.0, 1.0);
if (ty <= 0.28) return 0.16 + (0.18 - 0.16) * (ty / 0.28);
if (ty <= 0.55) return 0.18 + (0.13 - 0.18) * ((ty-0.28) / 0.27);
return 0.13 + (0.16 - 0.13) * ((ty-0.55) / 0.45);
}
// ── SDF primitives ────────────────────────────────────────────────────────────
float sdf_torso(float px, float py, float pz) {
const float Y0 = -0.68, Y1 = 0.32;
float ty = clamp((py - Y0) / (Y1 - Y0), 0.0, 1.0);
// S-curve lean
float lean_z = (1.0 - ty) * 0.016 - ty * 0.008;
float ax = plerp_ax(ty);
float az = plerp_az(ty);
// Rib cage forward convexity: chest pushes +Z in the upper half (ty < 0.5)
// Abdomen is flatter / slightly pushed back in ty 0.4-0.7
float chest_t = clamp((0.40 - ty) / 0.40, 0.0, 1.0); // 1=top, 0=mid
float rib_fwd = chest_t * chest_t * 0.018; // quadratic chest forward bulge
float ab_flat = clamp((ty - 0.42) / 0.25, 0.0, 1.0) * clamp((0.70 - ty) / 0.28, 0.0, 1.0);
float ab_z = lean_z - rib_fwd + ab_flat * 0.010; // abdomen slightly back from rib cage
float nx = px / ax, nz = (pz - ab_z) / az;
float cross_d = (sqrt(nx*nx + nz*nz) - 1.0) * min(ax, az);
float vert_d = max(py - Y1, 0.0) + max(Y0 - py, 0.0);
return max(cross_d, vert_d);
}
// Sternum ridge: thin vertical ellipsoid along the midline of the chest.
float sdf_sternum(float px, float py, float pz) {
// Sternum: center X=0, Y from -0.55 to -0.05, forward (+Z) lean follows chest
float ty = clamp((py + 0.55) / 0.50, 0.0, 1.0);
float chest_z = 0.082 + ty * 0.010; // front of chest varies with height
float dpx = px / 0.018;
float dpy = (py + 0.30) / 0.280;
float dpz = (pz - chest_z) / 0.020;
return (sqrt(dpx*dpx+dpy*dpy+dpz*dpz) - 1.0) * 0.018;
}
float sdf_arm_r(float px, float py, float pz) {
const float AX=0.460, AY=-0.565, BX=0.492, BY=-0.065, RA=0.092, RB=0.079;
float t = clamp((py - AY) / (BY - AY), 0.0, 1.0);
float cx = AX + t * (BX - AX);
float r = RA + t * (RB - RA);
// Bicep bulge: front (+Z) side is rounder, back (-Z) side is flatter (tricep flat)
// Achieve via asymmetric ellipse: squish back of arm slightly
float bicep_peak = exp(-((t - 0.45)*(t - 0.45)) * 18.0); // peak at 45% of upper arm
float front_bias = 0.020 * bicep_peak; // +Z offset at bicep peak
float pz_local = pz - front_bias; // shift cross-section center forward at bicep
float arm_aspect_z = 0.90 + 0.08 * bicep_peak; // rounder front at bicep
float dx = (px - cx) / 1.05;
float dz = pz_local / arm_aspect_z;
float xz = sqrt(dx*dx + dz*dz);
float ye = max(py - BY, 0.0) + max(AY - py, 0.0);
return (ye > 0.0) ? sqrt(xz*xz + ye*ye) - r : xz - r;
}
float sdf_forearm_r(float px, float py, float pz) {
const float AX=0.492, AY=-0.065, BX=0.515, BY=0.275, RA=0.072, RB=0.054;
float t = clamp((py - AY) / (BY - AY), 0.0, 1.0);
float cx = AX + t * (BX - AX);
float r = RA + t * (RB - RA);
// Forearm widest near elbow (t=0), tapers to wrist (t=1).
// Brachioradialis bulge on the lateral (+X relative to cx) side near elbow.
float brach = exp(-t * t * 4.0); // peaks at elbow, fades toward wrist
float px_local = px - cx + brach * 0.012; // lateral bulge toward elbow
float fa_aspect_z = 0.92 + brach * 0.05; // slightly rounder cross-section at elbow
float dx = px_local / 1.05;
float dz = pz / fa_aspect_z;
float xz = sqrt(dx*dx + dz*dz);
float ye = max(py - BY, 0.0) + max(AY - py, 0.0);
return (ye > 0.0) ? sqrt(xz*xz + ye*ye) - r : xz - r;
}
float sdf_leg_r(float px, float py, float pz) {
const float CX = 0.145;
float d_thigh; {
float t = clamp((py - 0.28) / 0.54, 0.0, 1.0);
float r = 0.105 + t * (0.088 - 0.105);
float dx = px - CX;
float xz = sqrt(dx*dx + pz*pz);
float ye = max(py - 0.82, 0.0) + max(0.28 - py, 0.0);
d_thigh = (ye > 0.0) ? sqrt(xz*xz + ye*ye) - r : xz - r;
}
float d_shin; {
float t = clamp((py - 0.78) / 0.40, 0.0, 1.0);
float r = 0.090 + t * (0.055 - 0.090);
float dx = px - CX;
float xz = sqrt(dx*dx + pz*pz);
float ye = max(py - 1.18, 0.0) + max(0.78 - py, 0.0);
d_shin = (ye > 0.0) ? sqrt(xz*xz + ye*ye) - r : xz - r;
}
float d = smin(d_thigh, d_shin, 0.035);
// Patella (kneecap): small forward-facing ellipsoid at front of knee
{ float dpx=(px-CX)/0.030, dpy=(py-0.800)/0.022, dpz=(pz-0.064)/0.020;
float d_pat=(sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*0.020;
d = smin(d, d_pat, 0.018); }
return d;
}
// Olecranon bump: posterior protrusion of the elbow (back of the elbow joint).
float sdf_olecranon_r(float px, float py, float pz) {
// At the elbow junction (py≈-0.065), back of arm (-Z side)
float dpx=(px-0.490)/0.018, dpy=(py+0.070)/0.016, dpz=(pz+0.042)/0.018;
return (sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*0.016;
}
// ── Fingernail plates ────────────────────────────────────────────────────────
// Flat ellipsoids on the dorsal (back) surface of each fingertip.
// Positioned at the distal phalanx of each finger (near fingertip).
float sdf_fingernails_r(float px, float py, float pz) {
float d = 1e9;
// Index through little: tip positions from sdf_hand_r distal segment ends
const float NFX[4] = float[4](0.533, 0.530, 0.524, 0.516); // index→little
const float NFZ[4] = float[4](0.040, 0.016,-0.006,-0.026); // tip Z after curl
const float NFY[4] = float[4](0.426, 0.434, 0.426, 0.408); // tip Y
for (int k = 0; k < 4; k++) {
float dpx=(px-NFX[k])/0.014, dpy=(py-NFY[k])/0.014, dpz=(pz-NFZ[k]-0.006)/0.006;
float dk=(sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*0.006;
d=min(d,dk);
}
// Thumb nail
{ float dpx=(px-0.524)/0.014, dpy=(py-0.346)/0.014, dpz=(pz+0.082-0.006)/0.006;
d=min(d,(sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*0.006); }
return d;
}
// ── Wrist bone protrusion (ulnar styloid) ────────────────────────────────────
// Small bilateral bump on the pinky side of the wrist (dorsal surface).
float sdf_wrist_bone_r(float px, float py, float pz) {
// Ulnar styloid at the wrist transition — medial wrist prominence
return seg_dist(px,py,pz, 0.512,0.258,-0.032, 0.510,0.272,-0.026, 0.014,0.010);
}
// ── Ankle bone protrusion (lateral malleolus) ─────────────────────────────────
float sdf_ankle_bone_r(float px, float py, float pz) {
// Lateral malleolus: bump on outer ankle (positive X side for right leg)
const float CX = 0.145;
return seg_dist(px,py,pz, CX+0.052,1.178,0.002, CX+0.048,1.194,-0.004, 0.018,0.014);
}
// ── Knuckle row ───────────────────────────────────────────────────────────────
// Four small ellipsoids along the MCP joint row (back of hand at base of fingers).
float sdf_knuckles_r(float px, float py, float pz) {
float d = 1e9;
// Knuckles at py≈0.322, on the dorsal side (pz slightly negative for back-of-hand)
const float KY=0.322, KZ=-0.018;
const float KX[4] = float[4](0.516, 0.524, 0.530, 0.533); // little→index
const float KR[4] = float[4](0.011, 0.012, 0.012, 0.011);
for (int k=0; k<4; k++) {
float dx=(px-KX[k])/KR[k], dy=(py-KY)/0.010, dz=(pz-KZ)/0.010;
float dk=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*KR[k];
d=min(d,dk);
}
return d;
}
// Tapered capsule SDF: distance from point P to segment A→B with radii ra→rb.
float seg_dist(float px, float py, float pz,
float ax, float ay, float az, float bx, float by, float bz,
float ra, float rb) {
float dx=bx-ax, dy=by-ay, dz=bz-az;
float len2 = dx*dx+dy*dy+dz*dz;
float t = clamp(((px-ax)*dx+(py-ay)*dy+(pz-az)*dz)/max(len2,0.0001), 0.0, 1.0);
float cx=ax+t*dx, cy=ay+t*dy, cz=az+t*dz;
return sqrt((px-cx)*(px-cx)+(py-cy)*(py-cy)+(pz-cz)*(pz-cz)) - (ra+t*(rb-ra));
}
// Right hand: flattened palm ellipsoid + 5 two-segment bent fingers.
// Each finger has a proximal phalanx (straight) + distal phalanx (curled forward).
// Wrist junction handled by sdf_body at k=0.025.
float sdf_hand_r(float px, float py, float pz) {
// Palm: flattened ellipsoid (RY < RX,RZ — thin dorsal/palmar dimension)
const float PCX=0.525, PCY=0.295, RX=0.044, RY=0.032, RZ=0.048;
float dpx=(px-PCX)/RX, dpy=(py-PCY)/RY, dpz=pz/RZ;
float d_f=(sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*min(min(RX,RY),RZ);
// little finger — proximal + distal (curls +0.018 Z)
{ float d1=seg_dist(px,py,pz, 0.516,0.320,-0.044, 0.516,0.370,-0.044, 0.014,0.011);
float d2=seg_dist(px,py,pz, 0.516,0.370,-0.044, 0.516,0.410,-0.026, 0.011,0.007);
d_f=smin(d_f,smin(d1,d2,0.008),0.010); }
// ring finger — proximal + distal (curls +0.016 Z)
{ float d1=seg_dist(px,py,pz, 0.524,0.322,-0.022, 0.524,0.380,-0.022, 0.015,0.012);
float d2=seg_dist(px,py,pz, 0.524,0.380,-0.022, 0.524,0.428,-0.006, 0.012,0.008);
d_f=smin(d_f,smin(d1,d2,0.008),0.010); }
// middle finger — proximal + distal (curls +0.014 Z)
{ float d1=seg_dist(px,py,pz, 0.530,0.322, 0.002, 0.530,0.386, 0.002, 0.016,0.012);
float d2=seg_dist(px,py,pz, 0.530,0.386, 0.002, 0.530,0.436, 0.016, 0.012,0.008);
d_f=smin(d_f,smin(d1,d2,0.008),0.010); }
// index finger — proximal + distal (curls +0.014 Z)
{ float d1=seg_dist(px,py,pz, 0.533,0.320, 0.026, 0.533,0.378, 0.026, 0.015,0.012);
float d2=seg_dist(px,py,pz, 0.533,0.378, 0.026, 0.533,0.428, 0.040, 0.012,0.008);
d_f=smin(d_f,smin(d1,d2,0.008),0.010); }
// thumb — angles outward in X (-X direction = toward body center for right hand)
// Base at palm edge, tip opposes index finger (curls inward in Z and -X)
{ float d1=seg_dist(px,py,pz, 0.540,0.268,-0.055, 0.533,0.305,-0.072, 0.018,0.014);
float d2=seg_dist(px,py,pz, 0.533,0.305,-0.072, 0.524,0.348,-0.082, 0.014,0.010);
d_f=smin(d_f,smin(d1,d2,0.010),0.012); }
return d_f;
}
// Right foot: boot shaft (tapered capsule) + toe box (forward ellipsoid) + heel raise.
// Ankle junction with shin handled in sdf_body at k=0.03.
float sdf_foot_r(float px, float py, float pz) {
const float CX = 0.145;
// Boot shaft: tapered cylinder from ankle to bottom of calf (covers the calf-boot gap)
float d_shaft = seg_dist(px,py,pz, CX,1.180,0.0, CX,1.240,0.0, 0.056,0.048);
// Toe box: wider ellipsoid — boot has squared-off toe, slightly raised heel
const float BCX=0.138, BCY=1.255, BCZ=0.035;
const float BRX=0.054, BRY=0.030, BRZ=0.082;
float bpx=(px-BCX)/BRX, bpy=(py-BCY)/BRY, bpz=(pz-BCZ)/BRZ;
float d_toe=(sqrt(bpx*bpx+bpy*bpy+bpz*bpz)-1.0)*min(BRX,min(BRY,BRZ));
// Heel: ellipsoid behind ankle axis (-Z), slightly raised (heel raise ≈ 8mm)
const float HCX=0.140, HCY=1.248, HCZ=-0.052;
const float HRX=0.046, HRY=0.032, HRZ=0.038;
float hpx=(px-HCX)/HRX, hpy=(py-HCY)/HRY, hpz=(pz-HCZ)/HRZ;
float d_heel=(sqrt(hpx*hpx+hpy*hpy+hpz*hpz)-1.0)*min(HRX,min(HRY,HRZ));
float d = smin(d_shaft, d_toe, 0.022);
d = smin(d, d_heel, 0.020);
return d;
}
// ── SCM (sternocleidomastoid) neck muscle ─────────────────────────────────────
// Diagonal bilateral neck muscle from mastoid process to sternum notch.
float sdf_scm_r(float px, float py, float pz) {
// Runs from behind the ear (px≈0.095, py≈-0.840) to the sternum top (px≈0.020, py≈-0.640)
float ax = abs(px);
return seg_dist(ax, py, pz,
0.094, -0.842, 0.032, // mastoid (behind ear base)
0.022, -0.648, 0.058, // sternum notch
0.018, 0.014); // round muscle belly
}
// ── Neck: tapered oval cylinder with trapezius slope ─────────────────────────
float sdf_neck(float px, float py, float pz) {
const float AY=-0.790, BY=-0.640, RA=0.072, RB=0.095;
float t = clamp((py - AY) / (BY - AY), 0.0, 1.0);
float r = RA + t * (RB - RA);
float xz = sqrt((px/1.00)*(px/1.00) + (pz/0.88)*(pz/0.88));
float ye = max(py - BY, 0.0) + max(AY - py, 0.0);
float d_neck = (ye > 0.0) ? sqrt(xz*xz + ye*ye) - r : xz - r;
// Trapezius slope: bilateral rounded ramp from neck base to shoulder — wider, lower
float ax = abs(px);
float trap_r = 0.055;
float trap_cx = 0.18 + ax * 0.0; // centered 18 cm lateral
float trap_t = clamp((ax - 0.10) / 0.20, 0.0, 1.0); // fades from neck center outward
float trap_cy_top = -0.660, trap_cy_bot = -0.600;
float trap_cz = -0.010;
// Trapezius runs as a ridge from neck-shoulder junction
float tpx = ax - 0.14, tpy = py - (-0.630), tpz = pz - trap_cz;
// Ellipsoid-ish sloped bump
float td = sqrt((tpx/0.14)*(tpx/0.14) + (tpy/0.055)*(tpy/0.055) + (tpz/0.080)*(tpz/0.080)) - 1.0;
float d_trap = td * 0.055 * (1.0 - trap_t * 0.5);
return smin(d_neck, d_trap, 0.030);
}
// ── Collarbone (clavicle) ridge ───────────────────────────────────────────────
// Thin bilateral ellipsoid just below the neck at the chest top.
float sdf_collarbone(float px, float py, float pz) {
// Clavicle: slight S-curve approximated as bilateral ellipsoids
// Center at roughly (±0.12, -0.640, 0.080) pointing outward
float ax = abs(px);
// Medial end near sternum, lateral end near shoulder
// Use a capsule from sternum to acromioclavicular joint
float d = seg_dist(ax, py, pz,
0.02, -0.648, 0.075, // medial: near sternum notch
0.28, -0.620, 0.040, // lateral: toward shoulder
0.012, 0.008); // thin at sternum, slightly thicker at shoulder
return d;
}
// ── Head: skull ellipsoid + cheekbones + brow ridge + jaw + nose + lips + ears ──
float sdf_head(float px, float py, float pz) {
// Main skull/face ellipsoid — center slightly forward (face protrudes)
const float HCY=-0.920, HCZ=0.018, HRX=0.175, HRY=0.222, HRZ=0.158;
float dhx=px/HRX, dhy=(py-HCY)/HRY, dhz=(pz-HCZ)/HRZ;
float d_head=(sqrt(dhx*dhx+dhy*dhy+dhz*dhz)-1.0)*min(HRX,min(HRY,HRZ));
// Cheekbones: bilateral convex bumps flanking the nose (mirror in X)
{ const float CX=0.090,CY=-0.888,CZ=0.138,CRX=0.044,CRY=0.028,CRZ=0.032;
float ax=abs(px);
float dx=(ax-CX)/CRX, dy=(py-CY)/CRY, dz=(pz-CZ)/CRZ;
float d_chk=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(CRX,min(CRY,CRZ));
d_head=smin(d_head,d_chk,0.028); }
// Brow ridge: thin horizontal ellipsoid above eye sockets
{ const float BCY=-1.012,BCZ=0.132,BRX=0.112,BRY=0.020,BRZ=0.024;
float dx=px/BRX, dy=(py-BCY)/BRY, dz=(pz-BCZ)/BRZ;
float d_brow=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(BRX,min(BRY,BRZ));
d_head=smin(d_head,d_brow,0.018); }
// Jaw: slightly wider ellipsoid at chin level (defines jaw width and chin)
{ const float JCY=-0.792,JCZ=0.030,JRX=0.132,JRY=0.040,JRZ=0.108;
float dx=px/JRX, dy=(py-JCY)/JRY, dz=(pz-JCZ)/JRZ;
float d_jaw=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(JRX,min(JRY,JRZ));
d_head=smin(d_head,d_jaw,0.025); }
// Ears: flat ellipsoids smooth-unioned to head sides (bilateral)
{ const float ECX=0.174,ECY=-0.882,ECZ=0.016,ERX=0.016,ERY=0.052,ERZ=0.012;
float ax=abs(px);
float dx=(ax-ECX)/ERX, dy=(py-ECY)/ERY, dz=(pz-ECZ)/ERZ;
float d_ear=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(ERX,min(ERY,ERZ));
d_head=smin(d_head,d_ear,0.016); }
// Nose bridge and tip: small forward ellipsoid
{ const float NCY=-0.900,NCZ=0.168,NRX=0.018,NRY=0.032,NRZ=0.022;
float dx=px/NRX, dy=(py-NCY)/NRY, dz=(pz-NCZ)/NRZ;
float d_nose=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(NRX,min(NRY,NRZ));
d_head=smin(d_head,d_nose,0.014); }
// Lips: upper and lower ridge above the chin
{ const float LCZ=0.156;
// Upper lip
{ const float ULY=-0.846,ULRX=0.062,ULRY=0.011,ULRZ=0.014;
float dx=px/ULRX, dy=(py-ULY)/ULRY, dz=(pz-LCZ)/ULRZ;
float d=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(ULRX,min(ULRY,ULRZ));
d_head=smin(d_head,d,0.010); }
// Lower lip (slightly fuller)
{ const float LLY=-0.833,LLRX=0.060,LLRY=0.013,LLRZ=0.015;
float dx=px/LLRX, dy=(py-LLY)/LLRY, dz=(pz-LCZ)/LLRZ;
float d=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(LLRX,min(LLRY,LLRZ));
d_head=smin(d_head,d,0.010); } }
// Zygomatic arch continuation: ridge from cheekbone toward ear
{ float ax3=abs(px);
float d_arch=seg_dist(ax3,py,pz, 0.096,-0.888,0.130, 0.158,-0.882,0.058, 0.014,0.010);
d_head=smin(d_head,d_arch,0.014); }
// Temporal ridge: slight bony ridge above the temple (superior temporal line)
{ float ax3=abs(px);
float d_temp=seg_dist(ax3,py,pz, 0.130,-1.010,0.068, 0.158,-0.940,0.042, 0.010,0.008);
d_head=smin(d_head,d_temp,0.012); }
// Chin cleft: very small midline groove below lower lip — philtrum area
// Implemented as two slight volume add-ons flanking center that create a midline shadow
{ // Philtrum columns: bilateral thin ridges from nose base to upper lip
float ax3=abs(px);
float dpx=(ax3-0.014)/0.010, dpy=(py+0.866)/0.020, dpz=(pz-0.162)/0.010;
float d_phil=(sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*0.009;
d_head=smin(d_head,d_phil,0.008); }
// Chin prominence: slight forward ellipsoid at the chin center
{ float dpx=px/0.038, dpy=(py+0.792)/0.022, dpz=(pz-0.136)/0.018;
float d_chin=(sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*0.016;
d_head=smin(d_head,d_chin,0.014); }
// Nasolabial fold hint: bilateral slight ridge from nostril to mouth corner
{ float ax3=abs(px);
float d_nlf=seg_dist(ax3,py,pz, 0.022,-0.876,0.165, 0.060,-0.856,0.160, 0.009,0.007);
d_head=smin(d_head,d_nlf,0.008); }
// Eyelids: bilateral thin forward-facing ellipsoids at eye position
// Upper eyelid slightly more prominent (heavy brow shadow for Leon's look)
{ const float EX=0.058,EY=-0.980,EZ=0.152,ERX=0.034,ERY=0.009,ERZ=0.012;
float ax2=abs(px);
float dx=(ax2-EX)/ERX, dy=(py-EY)/ERY, dz=(pz-EZ)/ERZ;
float d_lid=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(ERX,min(ERY,ERZ));
d_head=smin(d_head,d_lid,0.010); }
// Lower eyelid: smaller ridge below eye
{ const float EX=0.056,EY=-0.968,EZ=0.150,ERX=0.028,ERY=0.007,ERZ=0.010;
float ax2=abs(px);
float dx=(ax2-EX)/ERX, dy=(py-EY)/ERY, dz=(pz-EZ)/ERZ;
float d_lo=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(ERX,min(ERY,ERZ));
d_head=smin(d_head,d_lo,0.008); }
// Eyebrow ridge: bilateral elongated bumps above the eyes
{ const float BX=0.058,BY=-0.998,BZ=0.140,BRX=0.040,BRY=0.008,BRZ=0.012;
float ax2=abs(px);
float dx=(ax2-BX)/BRX, dy=(py-BY)/BRY, dz=(pz-BZ)/BRZ;
float d_brow2=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(BRX,min(BRY,BRZ));
d_head=smin(d_head,d_brow2,0.010); }
// Nasal wings (nostril area): bilateral small bumps at nose base
{ const float NWX=0.016,NWY=-0.876,NWZ=0.162,NWRX=0.012,NWRY=0.010,NWRZ=0.010;
float ax2=abs(px);
float dx=(ax2-NWX)/NWRX, dy=(py-NWY)/NWRY, dz=(pz-NWZ)/NWRZ;
float d_nw=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(NWRX,min(NWRY,NWRZ));
d_head=smin(d_head,d_nw,0.009); }
// Adam's apple (throat): visible at the anterior neck
{ float d_adam = seg_dist(px,py,pz, 0.0,-0.740,0.074, 0.0,-0.715,0.082, 0.012,0.010);
d_head=smin(d_head,d_adam,0.010); }
// Ear helix inner ridge — secondary ellipsoid inside the ear
{ const float ECX=0.172, ECY=-0.882, ECZ=0.016, ERX=0.009, ERY=0.038, ERZ=0.008;
float ax4=abs(px);
float dx=(ax4-ECX)/ERX, dy=(py-ECY)/ERY, dz=(pz-ECZ)/ERZ;
float d_helix=(sqrt(dx*dx+dy*dy+dz*dz)-1.0)*min(ERX,min(ERY,ERZ));
d_head=smin(d_head,d_helix,0.008); }
// SCM muscle: bilateral diagonal neck muscle
d_head=smin(d_head, sdf_scm_r(px,py,pz), 0.020);
// Blend neck smoothly into head base
d_head=smin(d_head, sdf_neck(px,py,pz), 0.038);
return d_head;
}
// ── Jacket shoulder seam ridge ────────────────────────────────────────────────
// Raised seam where sleeve meets torso at the shoulder point.
float sdf_shoulder_seam_r(float px, float py, float pz) {
// Seam runs as a short capsule at the shoulder cap (arm root-torso junction)
// Bilateral — evaluated with abs(px)
float ax = abs(px);
return seg_dist(ax, py, pz,
0.290, -0.580, 0.004, // inner seam (near neck)
0.460, -0.560, -0.006, // outer seam (shoulder tip)
0.010, 0.010); // thin seam ridge
}
// ── Jacket hem ridge ──────────────────────────────────────────────────────────
// Double-stitched hem at the bottom edge of the jacket.
float sdf_jacket_hem(float px, float py, float pz) {
// Horizontal band at jacket bottom (py≈0.20)
// Use a torus-like ring: points within a narrow Y band at the jacket surface Z
float ty = clamp((py - 0.18) / 0.025, 0.0, 1.0); // 0=just above hem, 1=at hem
float lean_z = (1.0 - 0.0) * 0.016 - 0.0 * 0.008; // at bottom of torso (ty=1)
// Points on the torso perimeter at hem Y — just check proximity to hem
float ax = plerp_ax(1.0); // torso width at bottom
float az = plerp_az(1.0);
float nx = px / ax, nz = (pz - lean_z) / az;
float cross_d = (sqrt(nx*nx + nz*nz) - 1.0) * min(ax, az);
// Hem = thin shell right at the jacket surface, Y-restricted band
float ye = max(py - 0.22, 0.0) + max(0.18 - py, 0.0);
return ye > 0.0 ? 1.0 : cross_d - 0.016; // 16mm outside torso surface
}
// ── Boot sole ridge ───────────────────────────────────────────────────────────
// Thin flat ellipsoid = outsole visible at the side of the boot.
float sdf_boot_sole_r(float px, float py, float pz) {
// Sole is a flat horizontal disc at the boot bottom plane (py≈1.260)
const float CX=0.138, CY=1.262, CZ=0.025;
const float SRX=0.055, SRY=0.008, SRZ=0.082;
float dpx=(px-CX)/SRX, dpy=(py-CY)/SRY, dpz=(pz-CZ)/SRZ;
return (sqrt(dpx*dpx+dpy*dpy+dpz*dpz)-1.0)*SRY;
}
// ── Clothing SDF layers ───────────────────────────────────────────────────────
// Jacket: torso + upper arms + forearms inflated 14 mm outward.
// This creates a physical cloth surface separate from the underlying skin.
float sdf_jacket_layer(float px, float py, float pz) {
float d = sdf_torso(px, py, pz);
d = smin(d, sdf_arm_r( px, py, pz), 0.040);
d = smin(d, sdf_arm_r(-px, py, pz), 0.040);
d = smin(d, sdf_forearm_r( px, py, pz), 0.040);
d = smin(d, sdf_forearm_r(-px, py, pz), 0.040);
return d - 0.014; // inflate 14 mm outward — jacket thickness over skin
}
// Pants: both legs inflated 10 mm outward.
float sdf_pants_layer(float px, float py, float pz) {
float d = sdf_leg_r( px, py, pz);
d = smin(d, sdf_leg_r(-px, py, pz), 0.040);
return d - 0.010; // inflate 10 mm outward — tactical trouser thickness
}
// Full-body SDF: unified smooth union of all 11 primitives + clothing layers.
float sdf_body(float px, float py, float pz) {
float d = smin(sdf_torso(px, py, pz), sdf_arm_r(px, py, pz), 0.04);
d = smin(d, sdf_arm_r(-px, py, pz), 0.04);
d = smin(d, sdf_forearm_r(px, py, pz), 0.04);
d = smin(d, sdf_forearm_r(-px, py, pz), 0.04);
d = smin(d, sdf_hand_r(px, py, pz), 0.025); // wrist k=0.025
d = smin(d, sdf_hand_r(-px, py, pz), 0.025);
// Elbow bone protrusions (bilateral)
d = smin(d, sdf_olecranon_r(px, py, pz), 0.014);
d = smin(d, sdf_olecranon_r(-px, py, pz), 0.014);
// Wrist bone (bilateral)
d = smin(d, sdf_wrist_bone_r(px, py, pz), 0.012);
d = smin(d, sdf_wrist_bone_r(-px, py, pz), 0.012);
// Knuckle row (bilateral)
d = smin(d, sdf_knuckles_r(px, py, pz), 0.010);
d = smin(d, sdf_knuckles_r(-px, py, pz), 0.010);
// Fingernails (bilateral — shallow so they catch light)
d = smin(d, sdf_fingernails_r(px, py, pz), 0.008);
d = smin(d, sdf_fingernails_r(-px, py, pz), 0.008);
d = smin(d, sdf_leg_r(px, py, pz - 0.038), 0.04); // right leg steps forward
d = smin(d, sdf_leg_r(-px, py, pz + 0.014), 0.04); // left leg slightly back
d = smin(d, sdf_foot_r(px, py, pz - 0.038), 0.030); // right foot forward
d = smin(d, sdf_foot_r(-px, py, pz + 0.014), 0.030); // left foot back
// Ankle bone protrusions (bilateral, with stance offsets)
d = smin(d, sdf_ankle_bone_r(px, py, pz - 0.038), 0.012);
d = smin(d, sdf_ankle_bone_r(-px, py, pz + 0.014), 0.012);
d = smin(d, sdf_head(px, py, pz), 0.040); // head-to-neck junction k=0.04
// Clothing layers push torso/arm/leg surfaces outward — fabric over skin
d = smin(d, sdf_jacket_layer(px, py, pz), 0.020);
d = smin(d, sdf_pants_layer(px, py, pz), 0.018);
// Collarbone ridge sits at jacket neckline — visible at collar opening
d = smin(d, sdf_collarbone(px, py, pz), 0.018);
// Sternum ridge visible at jacket opening
d = smin(d, sdf_sternum(px, py, pz), 0.016);
// Shoulder seams (bilateral raised ridge)
d = smin(d, sdf_shoulder_seam_r(px, py, pz), 0.012);
// Boot sole ridge (bilateral with stance offset)
d = smin(d, sdf_boot_sole_r(px, py, pz - 0.038), 0.012);
d = smin(d, sdf_boot_sole_r(-px, py, pz + 0.014), 0.012);
return d;
}
// Analytical normal: central differences of sdf_body (6 evaluations).
vec3 body_normal(float px, float py, float pz) {
const float E = 0.005;
float nx = sdf_body(px+E,py,pz) - sdf_body(px-E,py,pz);
float ny = sdf_body(px,py+E,pz) - sdf_body(px,py-E,pz);
float nz = sdf_body(px,py,pz+E) - sdf_body(px,py,pz-E);
return normalize(vec3(nx, ny, nz));
}
// Returns ID of closest SDF primitive for structural material zone assignment.
// 0=torso 1=arm_r 2=arm_l 3=fa_r 4=fa_l 5=hand_r 6=hand_l
// 7=leg_r 8=leg_l 9=foot_r 10=foot_l
int dominant_sdf_id(float px, float py, float pz) {
float best = sdf_torso(px, py, pz); int id = 0; float v;
v = sdf_arm_r( px, py, pz); if (v < best) { best = v; id = 1; }
v = sdf_arm_r(-px, py, pz); if (v < best) { best = v; id = 2; }
v = sdf_forearm_r( px, py, pz); if (v < best) { best = v; id = 3; }
v = sdf_forearm_r(-px, py, pz); if (v < best) { best = v; id = 4; }
v = sdf_hand_r( px, py, pz); if (v < best) { best = v; id = 5; }
v = sdf_hand_r(-px, py, pz); if (v < best) { best = v; id = 6; }
v = sdf_leg_r( px, py, pz-0.038); if (v < best) { best = v; id = 7; }
v = sdf_leg_r(-px, py, pz+0.014); if (v < best) { best = v; id = 8; }
v = sdf_foot_r( px, py, pz-0.038); if (v < best) { best = v; id = 9; }
v = sdf_foot_r(-px, py, pz+0.014); if (v < best) { best = v; id = 10; }
v = sdf_head( px, py, pz); if (v < best) { best = v; id = 11; }
// Clothing layers: jacket (id=12) and pants (id=13) push surfaces outward
v = sdf_jacket_layer(px, py, pz); if (v < best) { best = v; id = 12; }
v = sdf_pants_layer( px, py, pz); if (v < best) { best = v; id = 13; }
return id;
}
// IQ SDF AO — 5 steps along outward normal.
float sdf_ao(float px, float py, float pz, vec3 sn) {
const float STEPS[5] = float[5](0.010, 0.035, 0.065, 0.100, 0.140);
float occ = 0.0, sca = 1.0;
for (int k = 0; k < 5; k++) {
float h = STEPS[k];
float d = sdf_body(px + h*sn.x, py + h*sn.y, pz + h*sn.z);
occ += (h - d) * sca;
sca *= 0.95;
}
float ao = clamp(1.0 - 3.0*occ, 0.0, 1.0);
return ao * 0.75 + 0.25;
}
// SDF thickness probe for SSS (6 steps inward along -n).
float sdf_thickness(float px, float py, float pz, vec3 sn) {
const float PROBES[6] = float[6](0.020, 0.055, 0.100, 0.160, 0.250, 0.380);
for (int k = 0; k < 6; k++) {
float h = PROBES[k];
float d = sdf_body(px - h*sn.x, py - h*sn.y, pz - h*sn.z);
if (d >= 0.0) return h;
}
return 0.380;
}
// Beer-Lambert volumetric shadow toward key light.
float vol_shadow(float px, float py, float pz) {
const int STEPS = 6;
const float STEP = 0.075;
const float SIGMA = 14.0;
float tau = 0.0;
for (int k = 1; k <= STEPS; k++) {
float h = float(k) * STEP;
float d = sdf_body(px + h*KL.x, py + h*KL.y, pz + h*KL.z);
if (d < 0.0) tau += min(-d, 0.12) * STEP;
}
return exp(-SIGMA * tau);
}
// ── 3D value noise ────────────────────────────────────────────────────────────
float nhash(int ix, int iy, int iz) {
uint n = uint(ix)*374761393u + uint(iy)*668265263u + uint(iz)*1291057433u;
n ^= (n >> 13u);
n *= 0x5851F42Du;
n ^= (n >> 16u);
return float(n & 0x00FFFFFFu) / 16777216.0 - 0.5;
}
float noise3(float x, float y, float z) {
int xi = int(floor(x)), yi = int(floor(y)), zi = int(floor(z));
float xf = x - float(xi), yf = y - float(yi), zf = z - float(zi);
float u = xf*xf*(3.0 - 2.0*xf);
float v = yf*yf*(3.0 - 2.0*yf);
float w = zf*zf*(3.0 - 2.0*zf);
float r0 = mix(mix(nhash(xi,yi,zi), nhash(xi+1,yi,zi), u),
mix(nhash(xi,yi+1,zi), nhash(xi+1,yi+1,zi), u), v);
float r1 = mix(mix(nhash(xi,yi,zi+1), nhash(xi+1,yi,zi+1), u),
mix(nhash(xi,yi+1,zi+1), nhash(xi+1,yi+1,zi+1), u), v);
return mix(r0, r1, w);
}
// Procedural skin detail: displacement + bump normal + roughness.
// Returns: (disp, pnx, pny, pnz, roughness) packed into a vec3+vec2.
// Call convention: sn = original analytical normal (normalized).
void skin_detail(float px, float py, float pz, vec3 sn,
out float disp, out vec3 pn, out float roughness) {
float fl = noise3(px*3.0, py*3.0, pz*3.0)
+ noise3(px*6.0, py*6.0, pz*6.0) * 0.5;
const float GE = 0.009;
const float BUMP = 0.030;
float fc = noise3( px*16.0, py*16.0, pz*16.0);
float fxp = noise3((px+GE)*16.0, py*16.0, pz*16.0);
float fyp = noise3( px*16.0, (py+GE)*16.0, pz*16.0);
float fzp = noise3( px*16.0, py*16.0, (pz+GE)*16.0);
float gx = (fxp - fc) / GE;
float gy = (fyp - fc) / GE;
float gz = (fzp - fc) / GE;
float gdn = dot(vec3(gx, gy, gz), sn);
vec3 b = vec3((gx - gdn*sn.x)*BUMP, (gy - gdn*sn.y)*BUMP, (gz - gdn*sn.z)*BUMP);
pn = normalize(sn + b);
disp = fl * 0.004 + fc * 0.002;
roughness = noise3(px*52.0, py*52.0, pz*52.0) * 0.5 + 0.5;
}
// ── Material functions ────────────────────────────────────────────────────────
const int MAT_SKIN = 0;
const int MAT_HAIR = 1;
const int MAT_JACKET = 2;
const int MAT_BOOT = 3;
const int MAT_METAL = 4;
const int MAT_EYE = 5;
// sdf_id: dominant SDF primitive (see dominant_sdf_id).
// Material tag — sdf_id is primary discriminator; positional logic only for torso detail.
// The old Y-positional skin zones only applied when head/face was at py -0.88..-0.30.
// With the SDF torso occupying that same Y band, all torso pixels must default to jacket.
int leon_tag(float px, float py, int sdf_id) {
float ax = abs(px);
// Head, neck, face: all skin
if (sdf_id == 11) return MAT_SKIN;
// Hands: exposed skin
if (sdf_id == 5 || sdf_id == 6) return MAT_SKIN;
// Feet: boot leather
if (sdf_id == 9 || sdf_id == 10) return MAT_BOOT;
// Arms and forearms: jacket sleeve (no exposed skin — Leon's sleeves are full-length)
if (sdf_id == 1 || sdf_id == 2 || sdf_id == 3 || sdf_id == 4) return MAT_JACKET;
// Clothing layer ids: jacket surface (12) and pants surface (13)
if (sdf_id == 12) return MAT_JACKET;
if (sdf_id == 13) return (py >= 0.84) ? MAT_BOOT : MAT_JACKET;
// Legs: tactical pants until the boot shaft starts (extended legs: boot at 1.14)
if (sdf_id == 7 || sdf_id == 8) {
return (py >= 1.14) ? MAT_BOOT : MAT_JACKET;
}
// Torso (sdf_id == 0): jacket dominates entirely.
// The neck/collar skin is rendered by the CPU head pass, not the torso SDF.
// Only the belt row has non-jacket materials.
if (py >= 0.21 && py <= 0.31) {
return (ax < 0.05) ? MAT_METAL : MAT_BOOT; // buckle / belt leather
}
return MAT_JACKET;
}
// Color lookup — uses sdf_id to route to the correct palette zone.
// This replaces positional skin logic that misaligned with SDF geometry.
vec3 leon_color(float px, float py, int sdf_id) {
float ax = abs(px);
// ── Head / face / neck: skin tones with facial feature variation ─────────
if (sdf_id == 11) {
float ax = abs(px);
// Base skin: slight warm variation by position
float skin_r = 0.80 + ax * 0.04;
float skin_g = 0.57 + max(-py - 0.85, 0.0) * 0.10;
float skin_b = 0.42;
// Forehead: slightly cooler/less saturated (less blood near skull)
float fore_t = clamp((-py - 0.96) / 0.10, 0.0, 1.0);
skin_r -= fore_t * 0.03; skin_g -= fore_t * 0.01;
// Nose bridge: SSS warm glow (blood vessels close to surface)
float nose_d = ax*ax*800.0 + (py+0.900)*(py+0.900)*120.0;
float nose_w = exp(-nose_d * 0.6);
skin_r += nose_w * 0.05; skin_b += nose_w * 0.02;
// Cheek: rosy bilateral tint
float chk = exp(-((ax-0.088)*(ax-0.088)*160.0 + (py+0.888)*(py+0.888)*160.0));
skin_r += chk * 0.06; skin_b += chk * 0.015;
// Stubble zone: jaw/chin area darker blue-grey tint (beard shadow)
float jaw_t = clamp((-py - 0.795) / 0.060, 0.0, 1.0); // 1=jaw, 0=upper face
float stubble_mask = jaw_t * clamp(1.0 - fore_t, 0.0, 1.0);
// Stubble is more gray-green (skin + grey hair mix)
skin_r -= stubble_mask * 0.08;
skin_g -= stubble_mask * 0.04;
skin_b += stubble_mask * 0.02;
// Under-eye shadow (periorbital darkening)
float eye_d = (ax-0.050)*(ax-0.050)*200.0 + (py+0.966)*(py+0.966)*200.0;
float eye_shadow = exp(-eye_d) * 0.05;
skin_r -= eye_shadow; skin_g -= eye_shadow * 0.8;
// Lips: redder
float lip_d2 = (py + 0.840)*(py + 0.840)*320.0 + (pz - 0.156)*(pz - 0.156)*200.0;
float lip_w = exp(-lip_d2);
skin_r += lip_w * 0.14; skin_g -= lip_w * 0.05;
// Neck: slightly less saturated
float neck_t = clamp((py + 0.75) * 8.0, 0.0, 1.0);
skin_r = mix(skin_r, 0.74, neck_t * 0.22);
skin_g = mix(skin_g, 0.56, neck_t * 0.22);
return vec3(clamp(skin_r,0.0,1.0), clamp(skin_g,0.0,1.0), clamp(skin_b,0.0,1.0));
}
// ── Hands: bare skin with tendon/crease detail ───────────────────────────
if (sdf_id == 5 || sdf_id == 6) {
// Fingernail plates (dorsal/forward-facing fingertips)
if (py > 0.38 && pz > 0.008) return vec3(0.88,0.72,0.66);
// Knuckle: slightly redder/darker dorsal skin
if (py > 0.314 && py < 0.336) return vec3(0.80,0.54,0.40);
// Palm: lighter center, darker at edges (callus zone), slight crease shadow at py≈0.28
if (py < 0.30) {
// Thenar eminence (thumb mound): warm flush
float thenar_d = (ax - 0.52)*(ax-0.52) + (py-0.288)*(py-0.288);
float thenar = exp(-thenar_d * 300.0) * 0.06;
// Palm crease: subtle dark band
float crease = exp(-(py-0.280)*(py-0.280)*1200.0) * 0.04;
float pr = 0.82 + thenar - crease;
float pg = 0.60 + thenar * 0.5 - crease;
float pb = 0.45 - crease * 0.5;
return vec3(pr, pg, pb);
}
return vec3(0.78,0.56,0.42);
}
// ── Feet / boots ─────────────────────────────────────────────────────────
if (sdf_id == 9 || sdf_id == 10) {
if (py > 1.255) return vec3(0.10,0.08,0.06); // rubber sole
// Toe cap: slightly lighter/different texture at toe box front
if (py > 1.230 && pz > 0.060) return vec3(0.30,0.20,0.12); // toe cap
// Boot shaft-to-body seam line
if (abs(py - 1.186) < 0.006) return vec3(0.18,0.12,0.07); // seam
return vec3(0.25,0.17,0.10);
}
// ── Legs: tactical pants with knee-pad detail ─────────────────────────────
if (sdf_id == 7 || sdf_id == 8) {
if (py >= 1.14) return vec3(0.25,0.17,0.10); // boot shaft (extended legs)
if (py > 0.72 && py < 0.90 && ax < 0.18) // knee pad (moved down)
return vec3(0.36,0.32,0.22);
return vec3(0.30,0.33,0.24); // olive tactical pants
}
// ── Arms: jacket sleeve ───────────────────────────────────────────────────
if (sdf_id == 1 || sdf_id == 2) return vec3(0.55,0.38,0.20); // upper arm sleeve
if (sdf_id == 3 || sdf_id == 4) {
if (py > 0.20 && py < 0.30) return vec3(0.42,0.28,0.12); // cuff band at wrist
return vec3(0.52,0.36,0.18); // forearm sleeve
}
// ── Clothing layer surfaces (offset SDF shells) ───────────────────────────
if (sdf_id == 12) {
// ── Jacket detail ─────────────────────────────────────────────────────
// Zipper strip: center front, slightly lighter/metallic
if (ax < 0.030 && py > -0.58 && py < 0.18) return vec3(0.62,0.46,0.26);
// Zipper pull: small bright patch at mid-chest
if (ax < 0.018 && py > -0.18 && py < -0.10) return vec3(0.72,0.68,0.60); // metal
// Collar fold: doubled-over fabric at top of jacket
if (py < -0.50 && ax < 0.18 && py > -0.62) {
float collar_d = (-0.50 - py) * 6.0; // 0=bottom of collar, 1=top
// Fold crease: slightly darker at the bend line
if (collar_d > 0.70) return vec3(0.42,0.28,0.11); // fold shadow
return vec3(0.52,0.36,0.16); // collar body
}
// Chest pockets: bilateral rectangular patches at front chest
float pkt_d = abs(ax - 0.14);
if (pkt_d < 0.055 && py > -0.42 && py < -0.22) {
// Pocket flap bottom edge: slightly darker line
if (abs(py + 0.22) < 0.008) return vec3(0.40,0.27,0.10); // pocket seam
return vec3(0.50,0.34,0.16); // pocket flap (slightly different shade)
}
// Lapel edge: desaturated angle at jacket opening
if (py < -0.18 && ax > 0.06 && ax < 0.24) return vec3(0.48,0.33,0.15);
// Shoulder yoke: darker reinforcement panel
if (py < -0.42 && ax > 0.16) return vec3(0.44,0.30,0.13);
// Cuff band at sleeve end: forearm only (covered by forearm SDF region, not torso)
if (py > 0.22 && py < 0.29) return vec3(0.42,0.28,0.12); // cuff
// Stitching seam lines: very thin dark bands (1-2 particle widths)
// Back yoke seam horizontal (at yoke-body transition)
if (abs(py + 0.42) < 0.006 && ax > 0.05) return vec3(0.38,0.25,0.09);
// Side seam vertical: faint line at jacket edge
if (abs(ax - 0.25) < 0.005 && py > -0.50 && py < 0.18) return vec3(0.40,0.27,0.10);
return vec3(0.55,0.38,0.20); // main jacket body
}
if (sdf_id == 13) {
// Pants layer — olive tactical with thigh cargo pocket
if (py > 0.72 && py < 0.90 && ax < 0.18) return vec3(0.36,0.32,0.22); // knee pad
if (py >= 1.14) return vec3(0.25,0.17,0.10); // boot shaft bleed
// Cargo pocket on outer thigh (bilateral)
if (py > 0.35 && py < 0.60 && ax > 0.10 && ax < 0.20) {
if (abs(py-0.35) < 0.008 || abs(py-0.60) < 0.008) return vec3(0.24,0.27,0.18); // seam
return vec3(0.28,0.31,0.22); // cargo pocket flap (slightly lighter)
}
// Belt loop hints at waist: slight contrast band
if (py > 0.28 && py < 0.34) return vec3(0.26,0.29,0.21);
// Trouser crease: slight front-center press crease (midline front of leg)
if (abs(pz - 0.030) < 0.006 && py > 0.34 && py < 1.10 && ax < 0.08)
return vec3(0.34,0.37,0.28); // crease highlight (slightly lighter)
return vec3(0.30,0.33,0.24); // olive tactical trousers
}
// ── Torso: jacket with fabric detail ─────────────────────────────────────
// Belt buckle (metal)
if (py >= 0.21 && py <= 0.31 && ax < 0.05) return vec3(0.72,0.70,0.66);
// Belt leather strap
if (py >= 0.21 && py <= 0.31) return vec3(0.14,0.10,0.07);
// Centre zipper strip
if (ax < 0.05 && py > -0.62 && py < 0.21) return vec3(0.60,0.44,0.24);
// Lapel edge: slightly desaturated at jacket opening
if (py < -0.20 && ax > 0.08 && ax < 0.26) return vec3(0.48,0.33,0.15);
// Shoulder yoke: darker panel across top
if (py < -0.42 && ax > 0.18) return vec3(0.44,0.30,0.13);
// Chest panels: main olive-brown jacket body
return vec3(0.55,0.38,0.20);
}
vec3 sky_color(float dx, float dy, float dz) {
float r = max(sqrt(dx*dx + dy*dy + dz*dz), 0.001);
float el = clamp(-dy / r, 0.0, 1.0);
float sun_dot = max((dx/r)*(-0.375) + (dy/r)*(-0.515) + (dz/r)*(0.685), 0.0);
float sun_glow = pow(sun_dot, 8.0) * 0.55;
float t = el, rs, gs, bs;
if (t < 0.4) {
float s = t / 0.4;
rs = 0.58*(1.0-s) + 0.20*s; gs = 0.38*(1.0-s) + 0.22*s; bs = 0.18*(1.0-s) + 0.45*s;
} else {
float s = (t - 0.4) / 0.6;
rs = 0.20*(1.0-s) + 0.06*s; gs = 0.22*(1.0-s) + 0.08*s; bs = 0.45*(1.0-s) + 0.24*s;
}
return vec3(rs + sun_glow, gs + sun_glow*0.72, bs + sun_glow*0.30);
}
vec3 fresnel_resp(int mat, float snz) {
float f = pow(max(1.0 - snz, 0.0), 5.0);
float k, tr, tg, tb;
if (mat == MAT_SKIN) { k=0.20; tr=1.00; tg=0.90; tb=0.80; }
else if (mat == MAT_HAIR) { k=0.10; tr=0.95; tg=0.88; tb=0.72; }
else if (mat == MAT_JACKET) { k=0.50; tr=0.90; tg=0.72; tb=0.40; }
else if (mat == MAT_BOOT) { k=0.70; tr=0.88; tg=0.78; tb=0.55; }
else if (mat == MAT_METAL) { k=1.00; tr=1.00; tg=0.97; tb=0.88; }
else { k=0.85; tr=1.00; tg=0.98; tb=0.96; }
float c = f * k * 0.38;
return vec3(c*tr, c*tg, c*tb);
}
// ── Technique 12: ACES filmic tonemapping ─────────────────────────────────────
// Narkowicz 2015 approximation. Richer shadows, better highlight rolloff, more
// saturated midtones than the previous Reinhard+S-curve chain.
vec3 aces_film(vec3 x) {
const float a=2.51, b=0.03, c=2.43, d=0.59, e=0.14;
return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0);
}
// ── Technique 2: SDF analytical reflection trace ──────────────────────────────
// Exclusive to SDF-particle renderers. Marches the reflected view ray against
// the analytical implicit body — no BVH, no triangle mesh required.
// Returns the lit surface color at the first reflection hit, or sky if no hit.
vec3 sdf_reflect_trace(float px, float py, float pz, vec3 rd) {
float t = 0.020;
for (int i = 0; i < 8; i++) {
float rx=px+t*rd.x, ry=py+t*rd.y, rz=pz+t*rd.z;
float d = sdf_body(rx, ry, rz);
if (abs(d) < 0.008) {
int rid = dominant_sdf_id(rx, ry, rz);
vec3 rcol = leon_color(rx, ry, rid);
vec3 rn = body_normal(rx, ry, rz);
float rdk = max(dot(rn, KL), 0.0);
return rcol * (rdk * 0.82 + 0.16);
}
t += max(abs(d) * 0.80, 0.018);
if (t > 1.4) break;
}
return sky_color(rd.x, rd.y, rd.z) * 0.70;
}
// ── Technique 8: Volumetric light shaft measurement ───────────────────────────
// Counts unoccluded steps along the key light direction. Particles in the clear
// corridor between arm and torso (god-ray gap) receive a bright shaft bonus.
float light_shaft_factor(float px, float py, float pz) {
float clear = 0.0;
const float STEP = 0.055;
for (int k = 1; k <= 8; k++) {
float h = float(k) * STEP;
float d = sdf_body(px+h*KL.x, py+h*KL.y, pz+h*KL.z);
if (d > 0.008) clear += STEP;
}
float total = 8.0 * STEP;
float shaft = clear / total; // 0 = fully occluded, 1 = fully clear
return shaft * shaft; // square for sharper falloff
}
// Full lighting pass matching sdf_shade() CPU function.
vec3 sdf_shade(vec3 sn, float hz, vec3 base_col, int mat) {
float spec = 0.0;
if (mat == MAT_BOOT || mat == MAT_METAL) {
spec = pow(max(sn.z*(KL.z+1.0)*0.5, 0.0), 24.0) * 0.50;
} else if (mat == MAT_EYE) {
float hn = sqrt(KL.x*KL.x + KL.y*KL.y + (KL.z+1.0)*(KL.z+1.0));
float ndoth = max(dot(sn, vec3(KL.x, KL.y, KL.z+1.0)) / max(hn,0.001), 0.0);
spec = pow(ndoth, 128.0) * 1.80;
}
float dk = max(dot(sn, KL), 0.0);
float df1 = max(dot(sn, F1D), 0.0);
float df2 = max(dot(sn, F2D), 0.0);
float df3 = max(dot(sn, F3D), 0.0);
float rim_raw = max(dot(sn, RL), 0.0) * (1.0 - hz);
float rim = rim_raw * rim_raw;
float sky = max(-sn.y, 0.0) * 0.08;
float gnd = max( sn.y, 0.0) * 0.04;
const float F1I = 0.15, F2I = 0.05;
const vec3 F1C = vec3(1.00, 0.85, 0.70);
const vec3 F2C = vec3(0.60, 0.70, 1.00);
const vec3 F3C = vec3(1.00, 0.52, 0.15);
float ir = dk*1.10+spec + df1*F1I*F1C.r + df2*F2I*F2C.r + df3*u_f3_int*F3C.r + 0.03+sky*0.60+gnd*0.40;
float ig = dk*1.10+spec + df1*F1I*F1C.g + df2*F2I*F2C.g + df3*u_f3_int*F3C.g + 0.03+sky*0.75+gnd*0.35;
float ib = dk*1.10+spec + df1*F1I*F1C.b + df2*F2I*F2C.b + df3*u_f3_int*F3C.b + 0.03+sky*1.00+gnd*0.25;
vec3 fr = fresnel_resp(mat, sn.z);
vec3 er = vec3(0.0);
if (mat == MAT_BOOT || mat == MAT_METAL || mat == MAT_EYE) {
vec3 rdir = vec3(-2.0*sn.z*sn.x, -2.0*sn.z*sn.y, 1.0-2.0*sn.z*sn.z);
vec3 sc = sky_color(rdir.x, rdir.y, rdir.z);
float k = (mat==MAT_METAL) ? 0.38 : (mat==MAT_EYE) ? 0.22 : 0.15;
er = sc * k;
}
float cr = min(base_col.r*ir + rim*0.12 + fr.r + er.r, 1.6);
float cg = min(base_col.g*ig + rim*0.16 + fr.g + er.g, 1.6);
float cb = min(base_col.b*ib + rim*0.42 + fr.b + er.b, 1.6);
// Technique 12: ACES filmic tonemap — richer shadows, cleaner highlights
return aces_film(vec3(cr, cg, cb));
}
// GI color bleed from 4 tangent-plane neighbor samples.
// sdf_id passed through — neighbor samples are close enough to be the same region.
vec3 gi_bounce(float px, float py, vec3 sn, float illum, int sdf_id) {
const float RH = 0.065, RV = 0.095, BOUNCE = 0.060;
vec3 t1 = vec3(-sn.z, 0.0, sn.x);
vec3 t2raw = vec3(-sn.x*sn.y, 1.0-sn.y*sn.y, -sn.z*sn.y);
vec3 t2 = normalize(t2raw);
vec3 a = leon_color(px + RH*t1.x, py, sdf_id);
vec3 b = leon_color(px - RH*t1.x, py, sdf_id);
vec3 c = leon_color(px + RV*t2.x, py + RV*t2.y, sdf_id);
vec3 d = leon_color(px - RV*t2.x, py - RV*t2.y, sdf_id);
vec3 nb = (a + b + c + d) * 0.25;
return nb * (illum * BOUNCE);
}
// ── Main compute entry point ──────────────────────────────────────────────────
void main() {
uint idx = gl_GlobalInvocationID.x;
if (idx >= uint(u_n)) return;
float px, py, pz;
float snx_a, sny_a, snz_a; // analytical normal (before bump)
uint seed0; // base seed for this region's hf() calls
// ── Candidate point generation by region ──────────────────────────────────
if (idx < OFF_ARM_R) {
// ── TORSO ─────────────────────────────────────────────────────────────
uint i = idx - OFF_TORSO;
seed0 = 80u;
float v = hf(i, seed0);
py = -0.68 + v * 1.00;
float ty = v; // same as ty in sdf_torso
float lean_z = (1.0 - ty) * 0.016 - ty * 0.008;
float chest_t = clamp((0.40 - ty) / 0.40, 0.0, 1.0);
float rib_fwd = chest_t * chest_t * 0.018;
float ab_flat = clamp((ty-0.42)/0.25,0.0,1.0)*clamp((0.70-ty)/0.28,0.0,1.0);
float total_z = lean_z - rib_fwd + ab_flat * 0.010;
float ax = plerp_ax(v), az = plerp_az(v);
float rx = hf(i,seed0+1u)*2.0-1.0, rz = hf(i,seed0+2u)*2.0-1.0;
float rn = max(sqrt(rx*rx + rz*rz), 0.001);
float dx = rx/rn, dz = rz/rn;
float noise = (hf(i,seed0+3u)*2.0-1.0)*SHELL;
px = (ax+noise)*dx; pz = (az+noise)*dz + total_z;
float gx = px/(ax*ax), gz = (pz-total_z)/(az*az);
float gn = max(sqrt(gx*gx+gz*gz), 0.001);
snx_a = gx/gn; sny_a = 0.0; snz_a = gz/gn;
} else if (idx < OFF_ARM_L) {
// ── RIGHT UPPER ARM ───────────────────────────────────────────────────
uint i = idx - OFF_ARM_R;
seed0 = 90u;
float t = hf(i,seed0);
py = -0.565 + t*(-0.065 - -0.565); // right shoulder dropped 3.5cm
float cx = 0.460 + t*(0.492 - 0.460);
float r = 0.092 + t*(0.079 - 0.092);
float rx = hf(i,seed0+1u)*2.0-1.0, rz = hf(i,seed0+2u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+3u)*2.0-1.0)*SHELL;
px = cx + (r+noise)*(rx/rn)/1.05;
pz = (r+noise)*(rz/rn)/0.95;
snx_a = (px - cx)/1.05; sny_a = 0.0; snz_a = pz/0.95;
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
} else if (idx < OFF_FA_R) {
// ── LEFT UPPER ARM (X-mirror of right) ───────────────────────────────
uint i = idx - OFF_ARM_L;
seed0 = 100u;
float t = hf(i,seed0);
py = -0.60 + t*(-0.10 - -0.60);
float cx = 0.460 + t*(0.492 - 0.460);
float r = 0.092 + t*(0.079 - 0.092);
float rx = hf(i,seed0+1u)*2.0-1.0, rz = hf(i,seed0+2u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+3u)*2.0-1.0)*SHELL;
float px_r = cx + (r+noise)*(rx/rn)/1.05;
pz = (r+noise)*(rz/rn)/0.95;
px = -px_r; // X-mirror
snx_a = -(px_r - cx)/1.05; sny_a = 0.0; snz_a = pz/0.95;
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
} else if (idx < OFF_FA_L) {
// ── RIGHT FOREARM ─────────────────────────────────────────────────────
uint i = idx - OFF_FA_R;
seed0 = 110u;
const float AY=-0.065, BY=0.275, AX=0.492, BX=0.515, RA=0.072, RB=0.054; // cascade from dropped shoulder
float t = hf(i,seed0);
py = AY + t*(BY-AY);
float cx = AX + t*(BX-AX);
float r = RA + t*(RB-RA);
float rx = hf(i,seed0+1u)*2.0-1.0, rz = hf(i,seed0+2u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+3u)*2.0-1.0)*SHELL;
px = cx + (r+noise)*(rx/rn)/1.05;
pz = (r+noise)*(rz/rn)/0.95;
snx_a = (px - cx)/1.05; sny_a = 0.0; snz_a = pz/0.95;
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
} else if (idx < OFF_HAND_R) {
// ── LEFT FOREARM (X-mirror) ───────────────────────────────────────────
uint i = idx - OFF_FA_L;
seed0 = 120u;
const float AY=-0.10, BY=0.24, AX=0.492, BX=0.515, RA=0.072, RB=0.054;
float t = hf(i,seed0);
py = AY + t*(BY-AY);
float cx = AX + t*(BX-AX);
float r = RA + t*(RB-RA);
float rx = hf(i,seed0+1u)*2.0-1.0, rz = hf(i,seed0+2u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+3u)*2.0-1.0)*SHELL;
float px_r = cx + (r+noise)*(rx/rn)/1.05;
pz = (r+noise)*(rz/rn)/0.95;
px = -px_r;
snx_a = -(px_r - cx)/1.05; sny_a = 0.0; snz_a = pz/0.95;
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
} else if (idx < OFF_HAND_L) {
// ── RIGHT HAND ────────────────────────────────────────────────────────
uint i = idx - OFF_HAND_R;
seed0 = 150u;
// 75% palm ellipsoid surface, 25% finger region
float part = hf(i, seed0);
if (part < 0.75) {
// Palm ellipsoid — moved inward with narrower arm
const float PCX=0.525, PCY=0.295, RX=0.044, RY=0.032, RZ=0.048;
float rx = hf(i,seed0+1u)*2.0-1.0, ry = hf(i,seed0+2u)*2.0-1.0, rz = hf(i,seed0+3u)*2.0-1.0;
float rn = max(sqrt(rx*rx+ry*ry+rz*rz), 0.001);
float noise = (hf(i,seed0+4u)*2.0-1.0)*SHELL;
px = PCX + (RX+noise)*rx/rn;
py = PCY + (RY+noise)*ry/rn;
pz = (RZ+noise)*rz/rn;
snx_a=(px-PCX)/RX; sny_a=(py-PCY)/RY; snz_a=pz/RZ;
float nn=max(sqrt(snx_a*snx_a+sny_a*sny_a+snz_a*snz_a),0.001);
snx_a/=nn; sny_a/=nn; snz_a/=nn;
} else {
// Bent finger — two-phalanx: sample t along full length, interpolate Z curl
int fi = int(hf(i,seed0+5u)*4.99);
// FCX, base Z, base Y, tip Y, base radius — match sdf_hand_r
const float FCX[5] = float[5](0.516,0.524,0.530,0.533,0.540);
const float FZ[5] = float[5](-0.044,-0.022,0.002,0.026,-0.055);
const float TZ[5] = float[5](-0.026,-0.006,0.016,0.040,-0.082); // tip Z after curl
const float FBY[5] = float[5](0.320,0.322,0.322,0.320,0.268);
const float FTY[5] = float[5](0.410,0.428,0.436,0.428,0.348);
const float FRA[5] = float[5](0.014,0.015,0.016,0.015,0.018);
float fcx=FCX[fi], fbz=FZ[fi], fby=FBY[fi], fty=FTY[fi], fra=FRA[fi];
float ft = hf(i,seed0+6u);
// Center Z interpolates toward tip for curl
float cz = fbz + ft*(TZ[fi]-fbz);
py = fby + ft*(fty-fby);
float r = fra + ft*(0.008-fra);
float rx = hf(i,seed0+7u)*2.0-1.0, rz = hf(i,seed0+8u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+9u)*2.0-1.0)*SHELL;
px = fcx + (r+noise)*rx/rn;
pz = cz + (r+noise)*rz/rn;
snx_a = px-fcx; sny_a = 0.0; snz_a = pz-cz;
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
}
} else if (idx < OFF_LEG_R) {
// ── LEFT HAND (X-mirror of right) ─────────────────────────────────────
uint i = idx - OFF_HAND_L;
seed0 = 160u;
float part = hf(i, seed0);
if (part < 0.75) {
const float PCX=0.525, PCY=0.295, RX=0.044, RY=0.032, RZ=0.048;
float rx = hf(i,seed0+1u)*2.0-1.0, ry = hf(i,seed0+2u)*2.0-1.0, rz = hf(i,seed0+3u)*2.0-1.0;
float rn = max(sqrt(rx*rx+ry*ry+rz*rz), 0.001);
float noise = (hf(i,seed0+4u)*2.0-1.0)*SHELL;
float px_r = PCX + (RX+noise)*rx/rn;
py = PCY + (RY+noise)*ry/rn;
pz = (RZ+noise)*rz/rn;
px = -px_r;
snx_a=-(px_r-PCX)/RX; sny_a=(py-PCY)/RY; snz_a=pz/RZ;
float nn=max(sqrt(snx_a*snx_a+sny_a*sny_a+snz_a*snz_a),0.001);
snx_a/=nn; sny_a/=nn; snz_a/=nn;
} else {
int fi = int(hf(i,seed0+5u)*4.99);
const float FCX[5] = float[5](0.516,0.524,0.530,0.533,0.540);
const float FZ[5] = float[5](-0.044,-0.022,0.002,0.026,-0.055);
const float TZ[5] = float[5](-0.026,-0.006,0.016,0.040,-0.082);
const float FBY[5] = float[5](0.320,0.322,0.322,0.320,0.268);
const float FTY[5] = float[5](0.410,0.428,0.436,0.428,0.348);
const float FRA[5] = float[5](0.014,0.015,0.016,0.015,0.018);
float fcx=FCX[fi], fbz=FZ[fi], fby=FBY[fi], fty=FTY[fi], fra=FRA[fi];
float ft = hf(i,seed0+6u);
float cz = fbz + ft*(TZ[fi]-fbz);
py = fby + ft*(fty-fby);
float r = fra + ft*(0.008-fra);
float rx = hf(i,seed0+7u)*2.0-1.0, rz = hf(i,seed0+8u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+9u)*2.0-1.0)*SHELL;
float px_r = fcx + (r+noise)*rx/rn;
pz = cz + (r+noise)*rz/rn;
px = -px_r;
snx_a = -(px_r-fcx); sny_a = 0.0; snz_a = pz-cz;
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
}
} else if (idx < OFF_LEG_L) {
// ── RIGHT LEG ─────────────────────────────────────────────────────────
uint i = idx - OFF_LEG_R;
seed0 = 130u;
float v = hf(i,seed0);
py = 0.28 + v * (1.18 - 0.28);
const float CX = 0.145;
float r_exp = (py < 0.82)
? (0.105 + clamp((py-0.28)/0.54,0.0,1.0)*(0.088-0.105))
: (0.090 + clamp((py-0.78)/0.40,0.0,1.0)*(0.055-0.090));
float rx = hf(i,seed0+1u)*2.0-1.0, rz = hf(i,seed0+2u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+3u)*2.0-1.0)*SHELL;
float px_u = CX + (r_exp+noise)*(rx/rn);
pz = (r_exp+noise)*(rz/rn)*0.92 - 0.038; // right leg forward
px = px_u;
snx_a = px_u - CX; sny_a = 0.0; snz_a = pz + 0.038; // normal in local frame
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
} else if (idx < OFF_FOOT_R) {
// ── LEFT LEG (X-mirror) ───────────────────────────────────────────────
uint i = idx - OFF_LEG_L;
seed0 = 140u;
float v = hf(i,seed0);
py = 0.28 + v * (1.18 - 0.28);
const float CX = 0.145;
float r_exp = (py < 0.82)
? (0.105 + clamp((py-0.28)/0.54,0.0,1.0)*(0.088-0.105))
: (0.090 + clamp((py-0.78)/0.40,0.0,1.0)*(0.055-0.090));
float rx = hf(i,seed0+1u)*2.0-1.0, rz = hf(i,seed0+2u)*2.0-1.0;
float rn = max(sqrt(rx*rx+rz*rz), 0.001);
float noise = (hf(i,seed0+3u)*2.0-1.0)*SHELL;
float px_u = CX + (r_exp+noise)*(rx/rn);
pz = (r_exp+noise)*(rz/rn)*0.92 + 0.014; // left leg slightly back
px = -px_u; // X-mirror
snx_a = -(px_u - CX); sny_a = 0.0; snz_a = pz - 0.014;
float nn = max(sqrt(snx_a*snx_a+snz_a*snz_a), 0.001);
snx_a /= nn; snz_a /= nn;
} else if (idx < OFF_FOOT_L) {
// ── RIGHT FOOT ────────────────────────────────────────────────────────
uint i = idx - OFF_FOOT_R;
seed0 = 170u;
// 60% toe box, 25% heel, 15% shaft — matches new boot geometry
float part = hf(i, seed0);
if (part < 0.60) {
const float BCX=0.138, BCY=1.255, BCZ=0.035, BRX=0.054, BRY=0.030, BRZ=0.082;
float rx=hf(i,seed0+1u)*2.0-1.0, ry=hf(i,seed0+2u)*2.0-1.0, rz=hf(i,seed0+3u)*2.0-1.0;
float rn=max(sqrt(rx*rx+ry*ry+rz*rz),0.001);
float noise=(hf(i,seed0+4u)*2.0-1.0)*SHELL;
px=BCX+(BRX+noise)*rx/rn; py=BCY+(BRY+noise)*ry/rn; pz=BCZ+(BRZ+noise)*rz/rn;
snx_a=(px-BCX)/BRX; sny_a=(py-BCY)/BRY; snz_a=(pz-BCZ)/BRZ;
} else if (part < 0.85) {
const float HCX=0.140, HCY=1.248, HCZ=-0.052, HRX=0.046, HRY=0.032, HRZ=0.038;
float rx=hf(i,seed0+1u)*2.0-1.0, ry=hf(i,seed0+2u)*2.0-1.0, rz=hf(i,seed0+3u)*2.0-1.0;
float rn=max(sqrt(rx*rx+ry*ry+rz*rz),0.001);
float noise=(hf(i,seed0+4u)*2.0-1.0)*SHELL;
px=HCX+(HRX+noise)*rx/rn; py=HCY+(HRY+noise)*ry/rn; pz=HCZ+(HRZ+noise)*rz/rn;
snx_a=(px-HCX)/HRX; sny_a=(py-HCY)/HRY; snz_a=(pz-HCZ)/HRZ;
} else {
// Boot shaft ring (ankle height)
float t=hf(i,seed0+5u);
py=1.180+t*0.060; float r=0.056+t*(0.048-0.056);
float rx=hf(i,seed0+1u)*2.0-1.0, rz=hf(i,seed0+2u)*2.0-1.0;
float rn=max(sqrt(rx*rx+rz*rz),0.001); float noise=(hf(i,seed0+3u)*2.0-1.0)*SHELL;
px=0.145+(r+noise)*rx/rn; pz=(r+noise)*rz/rn;
snx_a=px-0.145; sny_a=0.0; snz_a=pz;
}
pz -= 0.038; // right foot steps forward with right leg
float nn=max(sqrt(snx_a*snx_a+sny_a*sny_a+snz_a*snz_a),0.001);
snx_a/=nn; sny_a/=nn; snz_a/=nn;
} else {
// ── LEFT FOOT (X-mirror) ──────────────────────────────────────────────
uint i = idx - OFF_FOOT_L;
seed0 = 180u;
float part = hf(i, seed0);
float px_r;
if (part < 0.60) {
const float BCX=0.138, BCY=1.255, BCZ=0.035, BRX=0.054, BRY=0.030, BRZ=0.082;
float rx=hf(i,seed0+1u)*2.0-1.0, ry=hf(i,seed0+2u)*2.0-1.0, rz=hf(i,seed0+3u)*2.0-1.0;
float rn=max(sqrt(rx*rx+ry*ry+rz*rz),0.001);
float noise=(hf(i,seed0+4u)*2.0-1.0)*SHELL;
px_r=BCX+(BRX+noise)*rx/rn; py=BCY+(BRY+noise)*ry/rn; pz=BCZ+(BRZ+noise)*rz/rn;
snx_a=-(px_r-BCX)/BRX; sny_a=(py-BCY)/BRY; snz_a=(pz-BCZ)/BRZ;
} else if (part < 0.85) {
const float HCX=0.140, HCY=1.248, HCZ=-0.052, HRX=0.046, HRY=0.032, HRZ=0.038;
float rx=hf(i,seed0+1u)*2.0-1.0, ry=hf(i,seed0+2u)*2.0-1.0, rz=hf(i,seed0+3u)*2.0-1.0;
float rn=max(sqrt(rx*rx+ry*ry+rz*rz),0.001);
float noise=(hf(i,seed0+4u)*2.0-1.0)*SHELL;
px_r=HCX+(HRX+noise)*rx/rn; py=HCY+(HRY+noise)*ry/rn; pz=HCZ+(HRZ+noise)*rz/rn;
snx_a=-(px_r-HCX)/HRX; sny_a=(py-HCY)/HRY; snz_a=(pz-HCZ)/HRZ;
} else {
float t=hf(i,seed0+5u);
py=1.180+t*0.060; float r=0.056+t*(0.048-0.056);
float rx=hf(i,seed0+1u)*2.0-1.0, rz=hf(i,seed0+2u)*2.0-1.0;
float rn=max(sqrt(rx*rx+rz*rz),0.001); float noise=(hf(i,seed0+3u)*2.0-1.0)*SHELL;
px_r=0.145+(r+noise)*rx/rn; pz=(r+noise)*rz/rn;
snx_a=-(px_r-0.145); sny_a=0.0; snz_a=pz;
}
px = -px_r; // X-mirror
pz += 0.014; // left foot slightly back
float nn=max(sqrt(snx_a*snx_a+sny_a*sny_a+snz_a*snz_a),0.001);
snx_a/=nn; sny_a/=nn; snz_a/=nn;
} else if (idx < OFF_END) {
// ── HEAD / FACE / NECK ────────────────────────────────────────────────
// Random candidates in a box bounding the skull: accept if |sdf_head| < SHELL.
// Normal derived from sdf_head gradient (6-tap central differences).
uint i = idx - OFF_HEAD;
seed0 = 190u;
// Bounding box: X in [-0.22, 0.22], Y in [-1.17, -0.70], Z in [-0.18, 0.24]
px = (hf(i, seed0 ) - 0.5) * 0.44;
py = -1.17 + hf(i, seed0+1u) * 0.47;
pz = -0.18 + hf(i, seed0+2u) * 0.42;
// Fast SDF test against head-only SDF (skip full body eval for this region)
float d_hd = sdf_head(px, py, pz);
if (abs(d_hd) >= SHELL) return;
// Analytical head-SDF gradient
const float EH = 0.004;
float gnx = sdf_head(px+EH,py,pz) - sdf_head(px-EH,py,pz);
float gny = sdf_head(px,py+EH,pz) - sdf_head(px,py-EH,pz);
float gnz = sdf_head(px,py,pz+EH) - sdf_head(px,py,pz-EH);
float gnn = max(sqrt(gnx*gnx+gny*gny+gnz*gnz), 0.001);
snx_a = gnx/gnn; sny_a = gny/gnn; snz_a = gnz/gnn;
sdf_id = 11;
}
// ── SDF acceptance test (body regions only — head already accepted above) ──
float d = (sdf_id == 11) ? 0.0 : sdf_body(px, py, pz);
if (d < -SHELL || d > SHELL) return;
vec3 sn_a = vec3(snx_a, sny_a, snz_a);
// ── Curvature → wrinkle weight ────────────────────────────────────────────
const float CE = 0.025;
float t1x = -snz_a, t1z = snx_a;
vec3 t2raw = vec3(-snx_a*sny_a, 1.0-sny_a*sny_a, -snz_a*sny_a);
vec3 t2 = normalize(t2raw);
float d1p = sdf_body(px+CE*t1x, py, pz+CE*t1z);
float d1m = sdf_body(px-CE*t1x, py, pz-CE*t1z);
float d2p = sdf_body(px+CE*t2.x, py+CE*t2.y, pz+CE*t2.z);
float d2m = sdf_body(px-CE*t2.x, py-CE*t2.y, pz-CE*t2.z);
float kappa = (d1p+d1m-2.0*d + d2p+d2m-2.0*d) / (CE*CE);
float wrinkle_raw = clamp(-kappa*0.035, 0.0, 1.0);
float wrinkle = wrinkle_raw*wrinkle_raw*(3.0-2.0*wrinkle_raw);
// ── Procedural skin detail (noise displacement + bump normal) ─────────────
float disp, roughness;
vec3 pn;
skin_detail(px, py, pz, sn_a, disp, pn, roughness);
float inset = wrinkle * 0.006;
px += (disp - inset)*snx_a;
py += (disp - inset)*sny_a;
pz += (disp - inset)*snz_a;
float hz = max(pn.z, 0.0);
float core = clamp((-d/SHELL + 1.0) * 0.5, 0.0, 1.0);
// ── Material lookup (SDF-aware) ───────────────────────────────────────────
int sdf_id = dominant_sdf_id(px, py, pz);
int mat = leon_tag(px, py, sdf_id);
vec3 base_col = leon_color(px, py, sdf_id);
// ── Full lighting ─────────────────────────────────────────────────────────
vec3 col = sdf_shade(pn, hz, base_col, mat);
// ── AO ───────────────────────────────────────────────────────────────────
float ao = sdf_ao(px, py, pz, pn);
col *= ao;
// ── SSS — skin only ───────────────────────────────────────────────────────
if (mat == MAT_SKIN) {
float bk = max(-dot(pn, KL), 0.0);
float bt = max(-dot(pn, F3D), 0.0) * u_f3_int;
float bi = bk*0.80 + bt;
if (bi > 0.04) {
float thick = sdf_thickness(px, py, pz, pn);
float tr = exp(-7.5*thick)*bi;
col.r += tr*1.00; col.g += tr*0.28; col.b += tr*0.08;
}
}
// SSS — jacket (olive transmit)
if (mat == MAT_JACKET) {
float bk = max(-dot(pn, KL), 0.0);
float bt = max(-dot(pn, F3D), 0.0) * u_f3_int;
float bi = bk*0.80 + bt;
if (bi > 0.06) {
float thick = sdf_thickness(px, py, pz, pn);
float tr = exp(-12.0*thick)*bi;
col.r += tr*0.55; col.g += tr*0.68; col.b += tr*0.25;
}
}
// ── Micro-roughness specular ──────────────────────────────────────────────
float dk_bump = max(dot(pn, KL), 0.0);
float micro = roughness*roughness*dk_bump*0.14;
col.r += micro*1.00; col.g += micro*0.94; col.b += micro*0.82;
// ── Technique 16: Kajiya-Kay anisotropic specular ─────────────────────────
// A traditional renderer needs an explicit tangent-space texture and a separate
// pass to compute per-pixel tangents. Here the SDF gradient IS the normal, so
// we derive the anisotropic tangent analytically:
// • Jacket/boot: tangent = cross(pn, up) → horizontal fiber direction (weft)
// • Hair: tangent = cross(pn, growth_dir) → along-strand direction
// KK model: spec = sin(T,L)^n * sin(T,V)^m — bright band orthogonal to tangent.
{
// Anisotropic tangent: horizontal weft for fabric, vertical for hair
vec3 guide = (mat == MAT_HAIR) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
// Guard: if normal is nearly parallel to guide, pivot to Z
if (abs(dot(pn, guide)) > 0.96) guide = vec3(0.0, 0.0, 1.0);
vec3 T = normalize(cross(pn, guide)); // tangent along fiber direction
// Kajiya-Kay cos-theta terms: sinT = sqrt(1-dot^2)
float TdotL = dot(T, KL);
float sinTL = sqrt(max(0.0, 1.0 - TdotL*TdotL));
// Camera view approximated by -KL (back-lit): conservative but cheap
float TdotV = dot(T, -KL);
float sinTV = sqrt(max(0.0, 1.0 - TdotV*TdotV));
float kk_exp = (mat == MAT_HAIR) ? 16.0 : 8.0;
float kk = pow(max(sinTL, 0.0), kk_exp) * pow(max(sinTV, 0.0), 4.0);
kk *= dk_bump * 0.45; // gate on face lit by key light
if (mat == MAT_HAIR) {
// Hair: warm gold-brown glint along strands
col.r += kk * 0.42; col.g += kk * 0.28; col.b += kk * 0.06;
} else if (mat == MAT_JACKET) {
// Jacket leather: subtle sheen — cool specular on horizontal weft
col.r += kk * 0.22; col.g += kk * 0.24; col.b += kk * 0.32;
}
// Boot/metal: anisotropic not visible on smooth leather (micro_roughness handles it)
}
// ── Technique 17: SDF micro-displacement → pore-shadow on skin ───────────
// We have the analytical SDF value (dist from surface = 0) at this particle's
// position; nearby particles at dist ≠ 0 contribute directional shadow.
// Cheaply approximated by marching two tiny steps along the key-light direction
// and measuring the SDF differential — positive differential = concave shadow.
// This is only meaningful for skin (smooth surface where micro-detail matters).
if (mat == MAT_SKIN) {
const float MICRO_D = 0.003;
float d_fwd = sdf_body(px + KL.x*MICRO_D, py + KL.y*MICRO_D, pz + KL.z*MICRO_D);
float d_bwd = sdf_body(px - KL.x*MICRO_D, py - KL.y*MICRO_D, pz - KL.z*MICRO_D);
float curvature = (d_fwd + d_bwd) * (1.0 / (MICRO_D * MICRO_D));
// Negative curvature → concave pore → shadow
float pore_shadow = clamp(-curvature * 0.00018, 0.0, 0.28);
col *= 1.0 - pore_shadow;
// Positive curvature → convex hillock → brightens
float pore_bright = clamp(curvature * 0.00008, 0.0, 0.12);
col += vec3(pore_bright * 0.85, pore_bright * 0.60, pore_bright * 0.35);
}
// ── Technique 20: SDF-curvature fresnel rim ──────────────────────────────
// Compute the Hessian diagonal approximation by sampling sdf_body at ±eps
// along each axis. The trace of the Hessian = Laplacian = mean curvature × 2.
// Convex regions (knuckles, cheekbones, collar edge) get amplified rim light;
// flat regions (chest panel, thigh) stay normal.
// Traditional renderers reconstruct curvature from depth buffer (noisy ±1px
// finite difference). Here we have the exact analytical SDF — curvature is
// accurate to floating-point precision.
{
const float EPS = 0.006;
float d0 = sdf_body(px, py, pz);
float dxx = sdf_body(px+EPS,py,pz) + sdf_body(px-EPS,py,pz) - 2.0*d0;
float dyy = sdf_body(px,py+EPS,pz) + sdf_body(px,py-EPS,pz) - 2.0*d0;
float dzz = sdf_body(px,py,pz+EPS) + sdf_body(px,py,pz-EPS) - 2.0*d0;
float laplacian = (dxx + dyy + dzz) / (EPS * EPS);
// Mean curvature ∝ laplacian; positive = convex, negative = concave
float curvature_rim = clamp(laplacian * 0.0025, -1.0, 1.0);
float rim_boost = curvature_rim * 0.18; // convex → brighter rim
// Apply fresnel-style: weight by rim factor (already computed from rim var above)
float rim_raw = max(dot(pn, RL), 0.0);
float curvature_fresnel = rim_raw * rim_raw * rim_boost;
col += vec3(curvature_fresnel * 0.18, curvature_fresnel * 0.28, curvature_fresnel * 0.48);
}
// ── Technique 21: Thickness-modulated SSS ────────────────────────────────
// March inward along -normal and measure how far inside the SDF body we travel
// before exiting the other side. Thin geometry → more SSS; thick → suppressed.
// This gives finger-web and ear-lobe translucency naturally without artist
// thickness maps — the SDF body encodes the geometry analytically.
if (mat == MAT_SKIN) {
float bk_key = max(-dot(pn, KL), 0.0);
float bk_f3 = max(-dot(pn, F3D), 0.0) * u_f3_int;
float bk = bk_key * 0.90 + bk_f3 * 0.55;
if (bk > 0.04) {
// Thickness: march inward, accumulate distance while inside (sdf<0)
float thickness = 0.0;
vec3 rin = -pn; // inward direction
const float TSTEP = 0.018;
for (int k = 1; k <= 5; k++) {
float h = float(k) * TSTEP;
float d_in = sdf_body(px+rin.x*h, py+rin.y*h, pz+rin.z*h);
if (d_in < 0.0) thickness += TSTEP; // still inside
}
// Beer-Lambert transmission: thinner → more light
float transmit = exp(-14.0 * thickness) * bk;
// Skin SSS color: warm orange-red (blood scatter)
col.r += transmit * 0.68;
col.g += transmit * 0.22;
col.b += transmit * 0.08;
}
}
// ── Wrinkle contact shadow ────────────────────────────────────────────────
if (wrinkle > 0.01) {
col.r *= 1.0 - wrinkle*0.52;
col.g *= 1.0 - wrinkle*0.62;
col.b *= 1.0 - wrinkle*0.80;
}
// ── Technique 22: SDF-analytical ambient occlusion ───────────────────────
// Traditional SSAO samples the hemisphere of the depth buffer — coarse and
// view-dependent. The analytical SDF gives exact local geometry. We sample
// 6 axis-aligned offsets: particles in concavities see nearby surface close up
// (low SDF value at offset) → occluded. Particles on convex features see open
// space → unoccluded. Fully view-independent and correct for self-shadowing.
float t22_ao;
{
const float AO_R = 0.045; // sample sphere radius in model units
float sum = 0.0;
sum += clamp(sdf_body(px+AO_R, py, pz ) / AO_R, 0.0, 1.0);
sum += clamp(sdf_body(px-AO_R, py, pz ) / AO_R, 0.0, 1.0);
sum += clamp(sdf_body(px, py+AO_R,pz ) / AO_R, 0.0, 1.0);
sum += clamp(sdf_body(px, py-AO_R,pz ) / AO_R, 0.0, 1.0);
sum += clamp(sdf_body(px, py, pz+AO_R) / AO_R, 0.0, 1.0);
sum += clamp(sdf_body(px, py, pz-AO_R) / AO_R, 0.0, 1.0);
t22_ao = sum / 6.0; // 1.0 = fully open, 0.0 = fully occluded
// Non-linear falloff: slight gamma emphasizes the crevice shadows
t22_ao = t22_ao * t22_ao * 0.80 + t22_ao * 0.20;
}
// Apply AO to ambient+fill terms only (key light has its own self-shadow)
float ao_atten = 0.55 + t22_ao * 0.45;
// ── Volumetric self-shadow ────────────────────────────────────────────────
float key_t = vol_shadow(px, py, pz);
float dk_lit = max(dot(pn, KL), 0.0);
float vol_att = 1.0 - dk_lit*(1.0-key_t)*0.72;
col *= vol_att;
// ── GI bounce ────────────────────────────────────────────────────────────
float dk_gi = max(dot(pn, KL), 0.0);
float f3_gi = max(dot(pn, F3D), 0.0) * u_f3_int;
float illum = dk_gi*0.80 + f3_gi*0.40 + 0.12;
col += gi_bounce(px, py, pn, illum * ao_atten, sdf_id); // AO modulates GI
// ── Technique 8: Volumetric light shafts / god rays ──────────────────────
// Particles in the clear corridor between arm and torso receive bright shaft.
// Head shadow on chest: dark band directly below the skull (py < -0.40, |px| < 0.22).
{
float shaft = light_shaft_factor(px, py, pz);
float dk_s = max(dot(pn, KL), 0.0);
float shaft_bonus = shaft * dk_s * 0.22;
col += vec3(shaft_bonus*1.00, shaft_bonus*0.88, shaft_bonus*0.62);
// Head shadow darkening band
float head_shadow = clamp(1.0 - (py + 0.40)*8.0, 0.0, 1.0)
* clamp(1.0 - abs(px)*4.5, 0.0, 1.0);
col *= 1.0 - head_shadow * 0.35;
}
// ── Technique 2: SDF metal reflection (particle-SDF exclusive) ───────────
// Only metal and eye particles trace a reflection ray against the SDF body.
// No geometry buffers needed — the analytical implicit surface is free to query.
if (mat == MAT_METAL || mat == MAT_EYE) {
vec3 cam_mdl = u_cam_pos / u_scale;
vec3 vdir = normalize(vec3(px,py,pz) - cam_mdl);
vec3 rd = vdir - 2.0*dot(vdir, pn)*pn;
float rw = (mat == MAT_METAL) ? 0.50 : 0.30;
vec3 refl = sdf_reflect_trace(px, py, pz, rd);
col = mix(col, refl, rw);
}
// ── Technique 23: Eye spectral refraction (real IOR dispersion) ─────────
// Human lens IOR is wavelength-dependent (chromatic aberration of the eye).
// For eye particles: refract the view ray through the iris sphere with
// n_R=1.336, n_G=1.340, n_B=1.346 (measured Abbe V-number for aqueous humor).
// Red, green, and blue diverge slightly as they exit the sphere — the iris
// boundary shows a thin spectral ring, exactly like a real eye photographed
// with a macro lens. The SDF sphere gives the exact intersection normal.
if (mat == MAT_EYE) {
vec3 cam_mdl = u_cam_pos / u_scale;
vec3 vdir = normalize(vec3(px, py, pz) - cam_mdl);
// Eye sphere center: approximately at model-space origin + small offset
const vec3 EYE_C = vec3(0.0, -0.82, 0.13);
const float EYE_R = 0.038;
vec3 snorm = normalize(vec3(px,py,pz) - EYE_C);
// Snell's law refraction for each wavelength
const float n1 = 1.0; // air
const float n_r = 1.336, n_g = 1.340, n_b = 1.346;
vec3 refr_r = refract(vdir, snorm, n1/n_r);
vec3 refr_g = refract(vdir, snorm, n1/n_g);
vec3 refr_b = refract(vdir, snorm, n1/n_b);
// Color picked from refracted direction (very approximate — just hue shift)
float r_dot = max(dot(refr_r, KL), 0.0);
float g_dot = max(dot(refr_g, KL), 0.0);
float b_dot = max(dot(refr_b, KL), 0.0);
float disp = abs(r_dot - b_dot) * 8.0; // measure of dispersion at this point
// Iris ring: bright spectral fringe near edge of eye
float iris_d = abs(length(vec2(px,py) - vec2(EYE_C.x,EYE_C.y)) - EYE_R * 0.70);
float iris_ring = exp(-iris_d * iris_d * 380.0) * disp * 2.5;
col.r += iris_ring * r_dot * 0.60;
col.g += iris_ring * g_dot * 0.30;
col.b += iris_ring * b_dot * 0.80;
}
// ── Technique 3: Atmospheric depth scattering ────────────────────────────
// Back-of-body particles (pz < 0) scatter toward cool deep blue.
// Front surface stays warm; recessed areas get atmospheric depth.
{
float back = max(-pz, 0.0) / 0.35;
float atmo_t = back * back * 0.20;
vec3 atmo = vec3(0.07, 0.10, 0.24);
col = mix(col, col*0.68 + atmo, atmo_t);
}
// ── Technique 9: Cross-material GI color bleed ───────────────────────────
// Jacket surface illuminates adjacent skin warm; pants tint jacket hem green.
if (mat == MAT_SKIN) {
// Orange-olive spill from adjacent jacket onto skin boundary
col += vec3(0.048, 0.032, 0.008) * clamp((py + 0.58)*5.0, 0.0, 1.0);
}
if (mat == MAT_JACKET && py > 0.14 && py < 0.24) {
// Cool green bleed from pants up into lower jacket hem
float t_hem = clamp((py - 0.14) / 0.10, 0.0, 1.0);
col = mix(col, col + vec3(0.010, 0.018, 0.006), t_hem * 0.30);
}
// ── Technique 13: Eye adaptation — apply frame exposure ──────────────────
col *= u_exposure;
// ── Alpha — material-specific floors ─────────────────────────────────────
// Clothing and metal are opaque materials: floor 0.95.
// Skin allows SSS light transmission but is never fully transparent: floor 0.75.
// Hair is dense: floor 0.85.
// HP damage is expressed through position jitter, not opacity — alpha floors hold.
float alpha_base = (0.82 + core*0.16) * (1.0 + wrinkle*0.18);
float alpha;
if (mat == MAT_SKIN) {
// Skin: SSS-transparent face of material, but never ghostly
alpha = clamp(alpha_base * max(u_hp, 0.55), 0.75, 0.88);
} else if (mat == MAT_HAIR) {
alpha = clamp(alpha_base * max(u_hp, 0.60), 0.85, 0.97);
} else {
// Jacket, boot, metal, eye: opaque — leather and fabric transmit nothing
alpha = clamp(alpha_base, 0.95, 0.98);
}
if (alpha < 0.50) return;
float br = base_col.r, bg = base_col.g;
float emission;
if (br>0.58 && bg>0.46) emission = (0.40+core*0.80)*(1.0-wrinkle*0.42); // skin
else if (br<0.28 && bg<0.18) emission = (0.12+core*0.30)*(1.0-wrinkle*0.42); // hair-dark
else if (base_col.b < 0.14) emission = (0.50+core*1.50)*(1.0-wrinkle*0.42); // boot
else emission = (0.45+core*1.20)*(1.0-wrinkle*0.42); // jacket
float sz = 0.020 + core*0.018;
// ── Damage jitter ─────────────────────────────────────────────────────────
float jx = (hf(idx, seed0+4u)-0.5)*u_dmg*0.08;
float jy = (hf(idx, seed0+5u)-0.5)*u_dmg*0.08;
// ── World-space position (apply scale + breath + jitter) ──────────────────
vec3 world_pos = vec3(
px * u_scale * u_breath + jx,
py * u_scale * u_breath + jy,
hz * 0.30
);
// ── Write accepted particle to SSBO ───────────────────────────────────────
uint slot = atomicCounterIncrement(u_count);
if (slot >= OFF_END) return; // safety cap = GPU_MAX_PARTICLES (10_800_000)
particles[slot].position = world_pos;
particles[slot].size = sz * u_scale; // world-space billboard radius
particles[slot].normal = pn;
particles[slot].emission = emission;
particles[slot].color = vec4(col, alpha);
}
"#;
const FINALIZE_SRC: &str = r#"
#version 430 core
layout(local_size_x = 1) in;
layout(binding = 0, offset = 0) uniform atomic_uint u_count;
layout(std430, binding = 2) buffer IndirectBuf {
uint draw_count;
uint draw_instance_count;
uint draw_first;
uint draw_base_instance;
};
uniform uint u_multiplier;
void main() {
uint n = atomicCounter(u_count);
draw_count = 6u; // 6 verts per billboard quad (2 triangles)
draw_instance_count = n * u_multiplier;
draw_first = 0u;
draw_base_instance = 0u;
}
"#;
const SDF_VERT_SRC: &str = r#"
#version 430 core
struct GpuParticle {
vec3 position;
float size;
vec3 normal;
float emission;
vec4 color;
};
layout(std430, binding = 1) readonly buffer ParticleSSBO {
GpuParticle particles[];
};
uniform mat4 u_view_proj;
uniform int u_multiplier;
uniform vec3 u_cam_right;
uniform vec3 u_cam_up;
// Technique 4: TAA sub-pixel jitter in NDC (pre-computed on CPU via Halton sequence)
uniform vec2 u_taa_jitter;
out vec4 f_color;
out float f_emission;
out vec2 f_uv;
out vec3 f_normal_w; // world-space surface normal (for SSR)
float hf(uint seed, uint v) {
uint n = seed * 374761393u + v * 668265263u;
n ^= (n >> 13u);
n *= 0x5851F42Du;
n ^= (n >> 16u);
return float(n & 0x00FFFFFFu) / float(0x01000000u);
}
void main() {
int base_idx = gl_InstanceID / u_multiplier;
int copy_idx = gl_InstanceID % u_multiplier;
// 2-triangle billboard quad
const vec2 VERTS[6] = vec2[6](
vec2(-1.0,-1.0), vec2( 1.0,-1.0), vec2(-1.0, 1.0),
vec2(-1.0, 1.0), vec2( 1.0,-1.0), vec2( 1.0, 1.0)
);
vec2 quad = VERTS[gl_VertexID];
f_uv = quad;
GpuParticle p = particles[base_idx];
// Per-copy offset: stays within ±0.001 world units of the base surface point.
// Purpose is to fill sub-pixel gaps, not to scatter copies into the volume.
float jx = (hf(uint(base_idx), uint(copy_idx*7+0)) - 0.5) * 0.002;
float jy = (hf(uint(base_idx), uint(copy_idx*7+1)) - 0.5) * 0.002;
vec3 world_pos = p.position
+ u_cam_right * (jx + quad.x * p.size * 0.50)
+ u_cam_up * (jy + quad.y * p.size * 0.50);
vec4 clip = u_view_proj * vec4(world_pos, 1.0);
// Technique 4: TAA — offset clip position by one Halton sub-pixel each frame.
// This shifts the entire particle cloud by <0.5 px; over successive frames the
// jitter pattern covers the full pixel footprint → smooth anti-aliased edges
// without a ping-pong framebuffer. Multiply by w to keep offset screen-space.
clip.xy += u_taa_jitter * clip.w;
gl_Position = clip;
f_color = p.color;
f_emission = p.emission;
f_normal_w = p.normal;
}
"#;
const SDF_FRAG_SRC: &str = r#"
#version 430 core
in vec4 f_color;
in float f_emission;
in vec2 f_uv;
in vec3 f_normal_w; // world-space surface normal (from compute SDF gradient)
layout(location = 0) out vec4 out_color;
uniform vec2 u_resolution; // window size in pixels
uniform float u_time_r; // current time for animated grain
void main() {
float d = length(f_uv);
if (d > 1.0) discard;
// ── Technique 15: Heat haze ────────────────────────────────────────────────
// High-emission particles (fire, energy) distort their own disk edge with a
// sine ripple. This is impossible in triangle rendering — requires per-particle
// emission metadata at fragment time.
float d_shaped = d;
if (f_emission > 0.50) {
float haze = (f_emission - 0.50) * 2.0;
float angle = atan(f_uv.y, f_uv.x);
d_shaped = d + sin(angle * 7.0 + f_emission * 29.3) * 0.055 * haze * d;
if (d_shaped > 1.0) discard;
}
float soft_base = max(0.0, 1.0 - d_shaped);
float soft = soft_base * soft_base;
// ── Technique 6: Chromatic depth separation ────────────────────────────────
// Particles farther from the near plane get per-channel disk radius shift,
// simulating a real lens that focuses each wavelength at a different depth.
float ndc_z = gl_FragCoord.z * 2.0 - 1.0; // −1 = near, +1 = far
float chrom = ndc_z * 0.0060 * (1.20 - f_color.a * 0.50);
float d_r = length(f_uv * (1.0 + chrom * 1.30));
float d_b = length(f_uv * (1.0 - chrom * 0.85));
float soft_r = max(0.0, 1.0 - d_r); soft_r = soft_r * soft_r;
float soft_g = soft;
float soft_b = max(0.0, 1.0 - d_b); soft_b = soft_b * soft_b;
float em = 1.0 + f_emission * 0.35;
vec4 col = vec4(
f_color.r * soft_r * em,
f_color.g * soft_g * em,
f_color.b * soft_b * em,
f_color.a * soft * (1.0 + f_emission * 0.50)
);
// ── Technique 5: Spectral bloom dispersion ────────────────────────────────
// Seed the bloom system with per-channel radial offsets so the engine's
// Gaussian blur spreads R further than G, G further than B — producing
// rainbow fringe on bright highlights (prismatic lens effect).
if (f_emission > 0.30) {
float em2 = (f_emission - 0.30) * 1.43;
float bloom_r = max(0.0, 1.0 - d * 0.87); bloom_r *= bloom_r;
float bloom_b = max(0.0, 1.0 - d * 1.13); bloom_b *= bloom_b;
col.r += bloom_r * em2 * 0.095;
col.g += soft * em2 * 0.035;
col.b += bloom_b * em2 * 0.065;
}
// ── Technique 14: Sharpen — steepen edge contrast ─────────────────────────
// Boost the ring at d ≈ 0.65 to heighten the transition between particles,
// making seam lines and high-frequency surface detail crisper.
{
float ring = exp(-pow(d - 0.64, 2.0) * 38.0) * 0.09 * (1.0 - f_emission * 0.6);
col.rgb += col.rgb * ring;
}
// ── Technique 11: Vignette ────────────────────────────────────────────────
// Darken and desaturate particles toward screen edges. Uses screen-space
// position from gl_FragCoord, so the effect is correctly anchored to the
// monitor frame regardless of where in 3D space the particle sits.
if (u_resolution.x > 0.0) {
vec2 sv = (gl_FragCoord.xy / u_resolution) * 2.0 - 1.0;
float r2 = dot(sv, sv);
float vig = 1.0 - r2 * r2 * 0.42;
float vig_sat = 1.0 - r2 * 0.18; // slight desaturation at edges
float lum_v = dot(col.rgb, vec3(0.299, 0.587, 0.114));
col.rgb = mix(vec3(lum_v), col.rgb, vig_sat) * vig;
col.a *= max(vig, 0.60);
}
// ── Technique 7: SSR — SDF-normal environment reflection ─────────────────────
// Traditional SSR requires a depth buffer and previous-frame color texture.
// This architecture has something better: every particle carries the analytical
// SDF gradient (true surface normal) in world space. We use it to importance-
// sample an implicit environment map derived from the dungeon geometry:
// n pointing up (-y) → stone vault ceiling (cool blue-grey)
// n pointing down (+y) → torchlit flagstone floor (warm amber)
// n pointing sideways → torch-glow wall scatter (desaturated amber)
// Reflection intensity is gated on emission so only metallic surfaces pick up
// environment color — skin and cloth are diffuse and ignore SSR.
{
vec3 n = normalize(f_normal_w);
float ceil_t = max(-n.y, 0.0);
float floor_t = max( n.y, 0.0);
float wall_t = sqrt(max(0.0, 1.0 - n.y * n.y));
vec3 ceil_env = vec3(0.28, 0.38, 0.62) * ceil_t;
vec3 floor_env = vec3(0.55, 0.38, 0.18) * floor_t;
vec3 wall_env = vec3(0.48, 0.30, 0.12) * wall_t * 0.55;
vec3 env = ceil_env + floor_env + wall_env;
float ssr_w = clamp((f_emission - 0.35) * 1.8, 0.0, 1.0) * 0.28;
col.rgb = mix(col.rgb, col.rgb * 0.72 + env * (0.6 + f_emission * 0.4), ssr_w);
}
// ── Technique 18: Depth-of-field bokeh ring ───────────────────────────────
// Real DoF blurs out-of-focus particles into large disks. We don't have a
// gather pass, but we can do something exclusive to per-particle rendering:
// add a bright annular ring at d ≈ bokeh_r that grows as depth diverges from
// the focal plane. This gives the characteristic bright-edge bokeh of a fast
// lens without accumulation rendering. The focal plane sits at ndc_z ≈ 0.5
// (mid-scene depth where Leon's torso lives).
{
float focal_z = 0.50; // NDC depth of focus plane
float defocus = abs(gl_FragCoord.z - focal_z); // 0 = sharp, 1 = blurred
float bokeh_r = 0.72 + defocus * 0.20; // ring radius grows with defocus
float ring_w = exp(-pow(d - bokeh_r, 2.0) * 55.0);
float ring_str = defocus * defocus * 0.14; // only visible when defocused
// Bokeh ring inherits particle hue but brightened
col.rgb += col.rgb * ring_w * ring_str;
col.a += ring_w * ring_str * 0.12;
}
// ── Technique 19: Iridescent thin-film on jacket leather ─────────────────
// Thin-film interference: path-length difference = 2*n*t*cos(theta), where
// theta = angle between surface normal and view direction. For leather the
// effective film thickness t ≈ 180 nm (oils + lacquer sheen).
// cos(theta) here approximated from f_normal_w · camera-forward (= +z in model):
// Constructive for green at cos≈0.60, red at cos≈0.80, blue at cos≈0.40.
// Result: the jacket shifts from warm olive to a faint teal sheen near glancing
// angles — exactly like aged leather under a warm torch.
{
vec3 n = normalize(f_normal_w);
float cos_t = abs(n.z); // approximate cos of view angle via normal.z
// Thin-film phase for 180 nm: wavelength-dependent
float phi_r = cos_t * 6.80; // red channel interference phase
float phi_g = cos_t * 8.40; // green channel
float phi_b = cos_t * 10.20; // blue channel
vec3 irid = vec3(
0.5 + 0.5 * cos(phi_r),
0.5 + 0.5 * cos(phi_g),
0.5 + 0.5 * cos(phi_b)
);
// Only apply to high-emission particles (jacket surface lit by key light)
// and only on normal-lit faces (not back-faces) — avoids inside-body glow
float irid_w = clamp((f_emission - 0.40) * 1.5, 0.0, 1.0) * 0.055
* (1.0 - cos_t * 0.5); // strongest at glancing angles
col.rgb = mix(col.rgb, col.rgb * (0.88 + irid * 0.22), irid_w);
}
// ── Technique 10: Luminance-based film grain ──────────────────────────────
// Dark regions get heavier grain (shadow noise), bright regions stay clean —
// exactly matching the grain curve of real photographic film stock.
{
float lum = dot(col.rgb, vec3(0.299, 0.587, 0.114));
float grain_amp = (1.0 - lum * 0.92) * 0.020;
// Seed varies with UV + time so grain animates each frame
float g = fract(sin(dot(f_uv * 87.3 + vec2(u_time_r * 0.019, lum),
vec2(127.1, 311.7))) * 43758.545);
col.rgb += (g - 0.5) * grain_amp;
}
out_color = col;
}
"#;
const COMPUTE_HAIR_SRC: &str = r#"
#version 430 core
layout(local_size_x = 256) in;
layout(binding = 0, offset = 0) uniform atomic_uint u_count;
struct GpuParticle {
vec3 position;
float size;
vec3 normal;
float emission;
vec4 color;
};
layout(std430, binding = 1) writeonly buffer ParticleSSBO {
GpuParticle particles[];
};
uniform float u_time;
uniform float u_scale;
uniform float u_breath;
uniform int u_n_hair; // = HAIR_STRANDS * HAIR_PPS
const int HAIR_STRANDS = 500;
const int HAIR_PPS = 20; // particles per strand
float hf(uint seed, uint v) {
uint n = seed * 374761393u + v * 668265263u;
n ^= (n >> 13u);
n *= 0x5851F42Du;
n ^= (n >> 16u);
return float(n & 0x00FFFFFFu) / float(0x01000000u);
}
void main() {
uint idx = gl_GlobalInvocationID.x;
if (int(idx) >= u_n_hair) return;
uint strand_idx = idx / uint(HAIR_PPS);
uint seg = idx % uint(HAIR_PPS);
float t = float(seg) / float(HAIR_PPS - 1);
const float SC_Y = -0.920; // skull center Y (model space)
// ── Leon's curtain-cut — 5 anatomical zones ───────────────────────────────
// 0-299: Crown dense short side-part strands, sweep left
// 300-399: Left drape long strands hanging from left temple, gravity-dominant
// 400-439: Right side shorter, lean slightly right (sparser)
// 440-479: Nape back of skull, hang straight down
// 480-499: Front fringe sweep forward-left over forehead
float root_x, root_y, root_z;
float gx, gy, gz;
float strand_len, grav, sw;
if (strand_idx < 300u) {
// Crown: tight cap, biased toward left hemisphere for side part
float theta = hf(strand_idx, 200u) * 0.48 + 0.03;
float phi = hf(strand_idx, 201u) * TAU * 0.65 + PI * 0.20;
float rr = 0.162 + hf(strand_idx, 202u) * 0.018;
root_x = rr * sin(theta) * cos(phi);
root_y = SC_Y - rr * cos(theta);
root_z = rr * sin(theta) * sin(phi) * 0.80;
// Growth: sweep strongly left, slight downward and forward variation
gx = -0.72 + (hf(strand_idx, 203u) - 0.5) * 0.22;
gy = 0.14 + hf(strand_idx, 204u) * 0.16;
gz = (hf(strand_idx, 205u) - 0.5) * 0.18;
strand_len = 0.09 + hf(strand_idx, 206u) * 0.07;
grav = 0.08; sw = 0.004;
} else if (strand_idx < 400u) {
// Left drape: long, hanging from left temple and side
float phi = PI + (hf(strand_idx, 201u) - 0.5) * 1.10;
float theta = 0.30 + hf(strand_idx, 200u) * 0.55;
float rr = 0.168 + hf(strand_idx, 202u) * 0.014;
root_x = rr * sin(theta) * cos(phi);
root_y = SC_Y - rr * cos(theta);
root_z = rr * sin(theta) * sin(phi) * 0.80;
// Growth: mostly downward, gravity does the rest
gx = -0.10 + (hf(strand_idx, 203u) - 0.5) * 0.06;
gy = 0.12 + hf(strand_idx, 204u) * 0.08;
gz = (hf(strand_idx, 205u) - 0.5) * 0.06;
strand_len = 0.26 + hf(strand_idx, 206u) * 0.14;
grav = 0.70; sw = 0.010;
} else if (strand_idx < 440u) {
// Right side: shorter, lean right (sparser — side part makes left fuller)
float phi = (hf(strand_idx, 201u) - 0.5) * 0.90;
float theta = 0.25 + hf(strand_idx, 200u) * 0.45;
float rr = 0.165 + hf(strand_idx, 202u) * 0.014;
root_x = rr * sin(theta) * cos(phi);
root_y = SC_Y - rr * cos(theta);
root_z = rr * sin(theta) * sin(phi) * 0.80;
gx = 0.06 + (hf(strand_idx, 203u) - 0.5) * 0.06;
gy = 0.12 + hf(strand_idx, 204u) * 0.10;
gz = (hf(strand_idx, 205u) - 0.5) * 0.06;
strand_len = 0.10 + hf(strand_idx, 206u) * 0.07;
grav = 0.30; sw = 0.006;
} else if (strand_idx < 480u) {
// Nape: back of skull (phi ≈ 3π/2 = behind head), nearly pure gravity
float phi = PI * 1.5 + (hf(strand_idx, 201u) - 0.5) * 0.70;
float theta = 0.55 + hf(strand_idx, 200u) * 0.35;
float rr = 0.168 + hf(strand_idx, 202u) * 0.014;
root_x = rr * sin(theta) * cos(phi);
root_y = SC_Y - rr * cos(theta);
root_z = rr * sin(theta) * sin(phi) * 0.80;
gx = (hf(strand_idx, 203u) - 0.5) * 0.04;
gy = 0.06 + hf(strand_idx, 204u) * 0.06;
gz = -0.05 + hf(strand_idx, 205u) * 0.04; // slightly toward back
strand_len = 0.20 + hf(strand_idx, 206u) * 0.12;
grav = 0.90; sw = 0.008;
} else {
// Front fringe: phi ≈ π/2 = front of skull, sweep down-left over forehead
float phi = PI * 0.5 + (hf(strand_idx, 201u) - 0.5) * 0.80;
float theta = 0.08 + hf(strand_idx, 200u) * 0.22;
float rr = 0.168 + hf(strand_idx, 202u) * 0.012;
root_x = rr * sin(theta) * cos(phi);
root_y = SC_Y - rr * cos(theta);
root_z = rr * sin(theta) * sin(phi) * 0.80;
gx = -0.22 + (hf(strand_idx, 203u) - 0.5) * 0.12;
gy = 0.32 + hf(strand_idx, 204u) * 0.14;
gz = 0.20 + hf(strand_idx, 205u) * 0.10; // forward over forehead
strand_len = 0.12 + hf(strand_idx, 206u) * 0.06;
grav = 0.15; sw = 0.005;
}
// Normalize growth direction
float glen = max(sqrt(gx*gx + gy*gy + gz*gz), 0.001);
gx /= glen; gy /= glen; gz /= glen;
// Wind sway — tip-heavy quadratic falloff
float sway_x = sin(u_time * 2.1 + float(strand_idx) * 0.17) * sw * t * t;
float sway_z = cos(u_time * 1.7 + float(strand_idx) * 0.23) * sw * 0.6 * t * t;
// ── Position: linear growth + quadratic gravity drape ─────────────────────
// grav adds +Y (downward) sag that increases toward tip.
// horiz decays horizontal component so gravity-dominant zones hang plumb.
float grav_y = grav * t * t * strand_len * 0.80;
float horiz = max(1.0 - grav * t * 0.45, 0.10);
float px = root_x + gx * t * strand_len * horiz + sway_x;
float py = root_y + gy * t * strand_len + grav_y;
float pz = root_z + gz * t * strand_len * horiz + sway_z;
// Normal: outward from skull center
float nx = px, ny = py - SC_Y, nz = pz;
float nn = max(sqrt(nx*nx + ny*ny + nz*nz), 0.001);
nx /= nn; ny /= nn; nz /= nn;
// Color: dark brown, per-strand variation
float br = 0.16 + hf(strand_idx, 207u) * 0.06;
float bg = 0.09 + hf(strand_idx, 208u) * 0.04;
float bb = 0.04 + hf(strand_idx, 209u) * 0.02;
float alpha = (1.0 - t * t * 0.75) * 0.88;
float emission = 0.08 + (1.0 - t) * 0.12;
float sz = (0.008 + (1.0 - t) * 0.005) * u_scale;
float world_x = px * u_scale * u_breath;
float world_y = py * u_scale * u_breath;
float world_z = pz;
uint slot = atomicCounterIncrement(u_count);
if (slot >= 10000000u) return;
particles[slot].position = vec3(world_x, world_y, world_z);
particles[slot].size = sz;
particles[slot].normal = vec3(nx, ny, nz);
particles[slot].emission = emission;
particles[slot].color = vec4(br, bg, bb, alpha);
}
"#;
const HAIR_STRANDS_RS: u32 = 500;
const HAIR_PPS_RS: u32 = 20;
const HAIR_N_TOTAL: u32 = HAIR_STRANDS_RS * HAIR_PPS_RS;
fn halton(base: u32, index: u32) -> f32 {
let mut f = 1.0_f32;
let mut r = 0.0_f32;
let mut i = index;
while i > 0 {
f /= base as f32;
r += f * (i % base) as f32;
i /= base;
}
r
}
const GL_COMPUTE_SHADER: u32 = 0x91B9;
const GL_SHADER_STORAGE_BUFFER: u32 = 0x90D2;
const GL_ATOMIC_COUNTER_BUFFER: u32 = 0x92C0;
const GL_DRAW_INDIRECT_BUFFER: u32 = 0x8F3F;
const GL_SHADER_STORAGE_BARRIER: u32 = 0x0000_2000;
const GL_ATOMIC_COUNTER_BARRIER: u32 = 0x0000_1000;
const GL_COMMAND_BARRIER: u32 = 0x0000_0040;
struct SdfGpuPipeline {
compute_prog: glow::Program,
hair_prog: glow::Program,
finalize_prog: glow::Program,
render_prog: glow::Program,
particle_ssbo: glow::Buffer,
indirect_buf: glow::Buffer,
counter_buf: glow::Buffer,
render_vao: glow::VertexArray,
u_time_c: glow::UniformLocation,
u_hp_c: glow::UniformLocation,
u_scale_c: glow::UniformLocation,
u_breath_c: glow::UniformLocation,
u_f3int_c: glow::UniformLocation,
u_dmg_c: glow::UniformLocation,
u_n_c: glow::UniformLocation,
u_cam_pos_c: glow::UniformLocation,
u_exposure_c: glow::UniformLocation,
u_time_h: glow::UniformLocation,
u_scale_h: glow::UniformLocation,
u_breath_h: glow::UniformLocation,
u_n_h: glow::UniformLocation,
u_mult_f: glow::UniformLocation,
u_vp_r: glow::UniformLocation,
u_mult_r: glow::UniformLocation,
u_right_r: glow::UniformLocation,
u_up_r: glow::UniformLocation,
u_res_r: glow::UniformLocation,
u_timef_r: glow::UniformLocation,
u_taa_jitter_v: glow::UniformLocation, timing_query: glow::Query,
query_ready: bool, exposure: f32,
frame_count: u32,
}
impl SdfGpuPipeline {
unsafe fn new(gl: &glow::Context) -> Option<Self> {
let major = gl.get_parameter_i32(glow::MAJOR_VERSION) as u32;
let minor = gl.get_parameter_i32(glow::MINOR_VERSION) as u32;
let supported = major > 4 || (major == 4 && minor >= 3);
if !supported {
log::warn!("SdfGpuPipeline: GL {major}.{minor} < 4.3 — compute shaders unavailable, falling back to CPU");
return None;
}
let compile_compute = |src: &str| -> Option<glow::Program> {
let shader = gl.create_shader(GL_COMPUTE_SHADER).ok()?;
gl.shader_source(shader, src);
gl.compile_shader(shader);
if !gl.get_shader_compile_status(shader) {
log::error!("Compute shader compile error:\n{}", gl.get_shader_info_log(shader));
gl.delete_shader(shader);
return None;
}
let prog = gl.create_program().ok()?;
gl.attach_shader(prog, shader);
gl.link_program(prog);
gl.delete_shader(shader);
if !gl.get_program_link_status(prog) {
log::error!("Compute program link error:\n{}", gl.get_program_info_log(prog));
gl.delete_program(prog);
return None;
}
Some(prog)
};
let compile_render = |vsrc: &str, fsrc: &str| -> Option<glow::Program> {
let vs = gl.create_shader(glow::VERTEX_SHADER).ok()?;
gl.shader_source(vs, vsrc);
gl.compile_shader(vs);
if !gl.get_shader_compile_status(vs) {
log::error!("SDF vert error:\n{}", gl.get_shader_info_log(vs));
gl.delete_shader(vs);
return None;
}
let fs = gl.create_shader(glow::FRAGMENT_SHADER).ok()?;
gl.shader_source(fs, fsrc);
gl.compile_shader(fs);
if !gl.get_shader_compile_status(fs) {
log::error!("SDF frag error:\n{}", gl.get_shader_info_log(fs));
gl.delete_shader(vs); gl.delete_shader(fs);
return None;
}
let prog = gl.create_program().ok()?;
gl.attach_shader(prog, vs); gl.attach_shader(prog, fs);
gl.link_program(prog);
gl.delete_shader(vs); gl.delete_shader(fs);
if !gl.get_program_link_status(prog) {
log::error!("SDF render link error:\n{}", gl.get_program_info_log(prog));
gl.delete_program(prog);
return None;
}
Some(prog)
};
let compute_prog = compile_compute(COMPUTE_SRC)?;
let hair_prog = compile_compute(COMPUTE_HAIR_SRC)?;
let finalize_prog = compile_compute(FINALIZE_SRC)?;
let render_prog = compile_render(SDF_VERT_SRC, SDF_FRAG_SRC)?;
let particle_ssbo = gl.create_buffer().ok()?;
gl.bind_buffer(GL_SHADER_STORAGE_BUFFER, Some(particle_ssbo));
gl.buffer_data_size(
GL_SHADER_STORAGE_BUFFER,
(GPU_MAX_PARTICLES as usize * GPU_PARTICLE_BYTES) as i32,
glow::DYNAMIC_DRAW,
);
let indirect_buf = gl.create_buffer().ok()?;
gl.bind_buffer(GL_DRAW_INDIRECT_BUFFER, Some(indirect_buf));
gl.buffer_data_size(GL_DRAW_INDIRECT_BUFFER, GPU_INDIRECT_BYTES as i32, glow::DYNAMIC_DRAW);
let counter_buf = gl.create_buffer().ok()?;
gl.bind_buffer(GL_ATOMIC_COUNTER_BUFFER, Some(counter_buf));
gl.buffer_data_size(GL_ATOMIC_COUNTER_BUFFER, 4, glow::DYNAMIC_DRAW);
let render_vao = gl.create_vertex_array().ok()?;
let uloc = |prog, name: &str| gl.get_uniform_location(prog, name);
let u_time_c = uloc(compute_prog, "u_time")?;
let u_hp_c = uloc(compute_prog, "u_hp")?;
let u_scale_c = uloc(compute_prog, "u_scale")?;
let u_breath_c = uloc(compute_prog, "u_breath")?;
let u_f3int_c = uloc(compute_prog, "u_f3_int")?;
let u_dmg_c = uloc(compute_prog, "u_dmg")?;
let u_n_c = uloc(compute_prog, "u_n")?;
let u_cam_pos_c = uloc(compute_prog, "u_cam_pos")?;
let u_exposure_c = uloc(compute_prog, "u_exposure")?;
let u_time_h = uloc(hair_prog, "u_time")?;
let u_scale_h = uloc(hair_prog, "u_scale")?;
let u_breath_h = uloc(hair_prog, "u_breath")?;
let u_n_h = uloc(hair_prog, "u_n_hair")?;
let u_mult_f = uloc(finalize_prog, "u_multiplier")?;
let u_vp_r = uloc(render_prog, "u_view_proj")?;
let u_mult_r = uloc(render_prog, "u_multiplier")?;
let u_right_r = uloc(render_prog, "u_cam_right")?;
let u_up_r = uloc(render_prog, "u_cam_up")?;
let u_res_r = uloc(render_prog, "u_resolution")?;
let u_timef_r = uloc(render_prog, "u_time_r")?;
let u_taa_jitter_v = uloc(render_prog, "u_taa_jitter")?;
let timing_query = gl.create_query().ok()?;
log::info!(
"SdfGpuPipeline initialized — {}M body + {}K hair candidates, {}MB SSBO",
GPU_N_TOTAL / 1_000_000,
HAIR_N_TOTAL / 1_000,
(GPU_MAX_PARTICLES as usize * GPU_PARTICLE_BYTES) / 1_048_576,
);
Some(Self {
compute_prog, hair_prog, finalize_prog, render_prog,
particle_ssbo, indirect_buf, counter_buf, render_vao,
u_time_c, u_hp_c, u_scale_c, u_breath_c, u_f3int_c, u_dmg_c, u_n_c,
u_cam_pos_c, u_exposure_c,
u_time_h, u_scale_h, u_breath_h, u_n_h,
u_mult_f, u_vp_r, u_mult_r, u_right_r, u_up_r, u_res_r, u_timef_r,
u_taa_jitter_v,
timing_query, query_ready: false,
exposure: 1.0,
frame_count: 0,
})
}
unsafe fn dispatch(&mut self, gl: &glow::Context, time: f32, hp: f32, multiplier: u32, cam_pos: Vec3) {
const GL_TIME_ELAPSED: u32 = 0x88BF;
const GL_QUERY_RESULT_AVAILABLE: u32 = 0x8867;
const GL_QUERY_RESULT: u32 = 0x8866;
let breath = 1.0_f32 + (time * 1.4).sin() * 0.010;
let f3_int = 0.28 * (0.80 + (time * 7.3).sin() * 0.11 + (time * 13.1).cos() * 0.07);
let dmg = (1.0_f32 - hp).max(0.0);
if self.query_ready {
let avail = gl.get_query_parameter_u32(self.timing_query, GL_QUERY_RESULT_AVAILABLE);
if avail != 0 {
let elapsed_ns = gl.get_query_parameter_u32(self.timing_query, GL_QUERY_RESULT) as u64;
let elapsed_ms = elapsed_ns as f64 / 1_000_000.0;
if elapsed_ms > 12.0 {
let candidates = GPU_N_TOTAL + HAIR_N_TOTAL;
log::info!(
"GPU compute: {candidates} candidates in {elapsed_ms:.2} ms \
({:.1} Mcand/s) — body×{} + hair×{} multiplier={}",
candidates as f64 / elapsed_ms / 1000.0,
GPU_N_TOTAL,
HAIR_N_TOTAL,
multiplier,
);
}
}
}
gl.bind_buffer(GL_ATOMIC_COUNTER_BUFFER, Some(self.counter_buf));
let zero: [u8; 4] = [0; 4];
gl.buffer_sub_data_u8_slice(GL_ATOMIC_COUNTER_BUFFER, 0, &zero);
gl.bind_buffer_base(GL_ATOMIC_COUNTER_BUFFER, 0, Some(self.counter_buf));
gl.bind_buffer_base(GL_SHADER_STORAGE_BUFFER, 1, Some(self.particle_ssbo));
gl.bind_buffer_base(GL_SHADER_STORAGE_BUFFER, 2, Some(self.indirect_buf));
gl.begin_query(GL_TIME_ELAPSED, self.timing_query);
gl.use_program(Some(self.compute_prog));
gl.uniform_1_f32(Some(&self.u_time_c), time);
gl.uniform_1_f32(Some(&self.u_hp_c), hp);
gl.uniform_1_f32(Some(&self.u_scale_c), 3.2);
gl.uniform_1_f32(Some(&self.u_breath_c), breath);
gl.uniform_1_f32(Some(&self.u_f3int_c), f3_int);
gl.uniform_1_f32(Some(&self.u_dmg_c), dmg);
gl.uniform_1_i32(Some(&self.u_n_c), GPU_N_TOTAL as i32);
gl.uniform_3_f32(Some(&self.u_cam_pos_c), cam_pos.x, cam_pos.y, cam_pos.z);
let target_exposure = 1.0_f32 + (1.0_f32 - hp) * 0.35; self.exposure = self.exposure * 0.95 + target_exposure * 0.05;
gl.uniform_1_f32(Some(&self.u_exposure_c), self.exposure);
let body_groups = (GPU_N_TOTAL + GPU_WG - 1) / GPU_WG;
gl.dispatch_compute(body_groups, 1, 1);
gl.memory_barrier(GL_SHADER_STORAGE_BARRIER | GL_ATOMIC_COUNTER_BARRIER);
gl.use_program(Some(self.hair_prog));
gl.uniform_1_f32(Some(&self.u_time_h), time);
gl.uniform_1_f32(Some(&self.u_scale_h), 3.2);
gl.uniform_1_f32(Some(&self.u_breath_h), breath);
gl.uniform_1_i32(Some(&self.u_n_h), HAIR_N_TOTAL as i32);
let hair_groups = (HAIR_N_TOTAL + GPU_WG - 1) / GPU_WG;
gl.dispatch_compute(hair_groups, 1, 1);
gl.memory_barrier(GL_SHADER_STORAGE_BARRIER | GL_ATOMIC_COUNTER_BARRIER | GL_COMMAND_BARRIER);
gl.end_query(GL_TIME_ELAPSED);
self.query_ready = true;
gl.use_program(Some(self.finalize_prog));
gl.uniform_1_u32(Some(&self.u_mult_f), multiplier);
gl.dispatch_compute(1, 1, 1);
gl.memory_barrier(GL_COMMAND_BARRIER);
}
unsafe fn render(
&mut self, gl: &glow::Context,
view_proj: Mat4,
cam_right: Vec3, cam_up: Vec3,
multiplier: u32,
time: f32,
window_size: (u32, u32),
) {
gl.enable(glow::BLEND);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
gl.use_program(Some(self.render_prog));
gl.uniform_matrix_4_f32_slice(
Some(&self.u_vp_r),
false,
&view_proj.to_cols_array(),
);
gl.uniform_1_i32(Some(&self.u_mult_r), multiplier as i32);
gl.uniform_3_f32(Some(&self.u_right_r), cam_right.x, cam_right.y, cam_right.z);
gl.uniform_3_f32(Some(&self.u_up_r), cam_up.x, cam_up.y, cam_up.z);
gl.uniform_2_f32(Some(&self.u_res_r), window_size.0 as f32, window_size.1 as f32);
gl.uniform_1_f32(Some(&self.u_timef_r), time);
let jx = (halton(2, self.frame_count) - 0.5) * 2.0 / window_size.0.max(1) as f32;
let jy = (halton(3, self.frame_count) - 0.5) * 2.0 / window_size.1.max(1) as f32;
gl.uniform_2_f32(Some(&self.u_taa_jitter_v), jx, jy);
self.frame_count = self.frame_count.wrapping_add(1);
gl.bind_buffer_base(GL_SHADER_STORAGE_BUFFER, 1, Some(self.particle_ssbo));
gl.bind_buffer(GL_DRAW_INDIRECT_BUFFER, Some(self.indirect_buf));
gl.bind_vertex_array(Some(self.render_vao));
gl.draw_arrays_indirect_offset(glow::TRIANGLES, 0);
gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA);
gl.bind_vertex_array(None);
gl.bind_buffer(GL_DRAW_INDIRECT_BUFFER, None);
}
}
fn main() {
env_logger::init();
let tw = total_weight();
let mut engine = ProofEngine::new(EngineConfig {
window_title: "Proof Engine — Leon Kennedy".to_string(),
window_width: 1920,
window_height: 1080,
target_fps: 60,
vsync: false,
render: proof_engine::config::RenderConfig {
bloom_enabled: true,
bloom_intensity: 3.0, bloom_radius: 18.0, chromatic_aberration: 0.0020,
film_grain: 0.020,
motion_blur_enabled: true,
motion_blur_samples: 16,
distortion_enabled: true,
scanlines_enabled: false,
antialiasing: true,
render_scale: 3.0, particle_multiplier: 16.0, shadow_quality: ShadowQuality::Ultra,
color_depth: 32,
..Default::default()
},
..Default::default()
});
engine.add_field(ForceField::Gravity {
center: Vec3::new(0.0, 0.5, 0.0), strength: 0.25, falloff: Falloff::InverseSquare,
});
engine.add_field(ForceField::Vortex {
center: Vec3::new(-5.0, 0.0, 0.0), axis: Vec3::new(0.0, 0.0, 1.0),
strength: 0.8, radius: 6.0,
});
engine.add_field(ForceField::Vortex {
center: Vec3::new( 5.0, 0.0, 0.0), axis: Vec3::new(0.0, 0.0, -1.0),
strength: 0.8, radius: 6.0,
});
engine.add_field(ForceField::StrangeAttractor {
attractor_type: AttractorType::Lorenz, scale: 0.18, strength: 0.30,
center: Vec3::new(0.0, -0.5, 0.0),
});
engine.add_field(ForceField::HeatSource {
center: Vec3::new(0.0, -0.5, 0.0), temperature: 3.0, radius: 2.5,
});
engine.add_field(ForceField::Pulsing {
center: Vec3::new(0.0, 0.0, 0.0), frequency: 1.1, amplitude: 0.45, radius: 7.0,
});
engine.add_field(ForceField::Flow {
direction: Vec3::new(0.0, -0.06, 0.0), strength: 0.08, turbulence: 0.06,
});
engine.emit_audio(AudioEvent::SetMusicVibe(MusicVibe::BossFight));
let mut time = 0.0f32;
#[allow(unused_assignments)]
let mut hp = 1.0f32;
let mut last_burst = -10.0f32;
let mut lag_cache = vec![Vec3::ZERO; 500_000];
let mut hair_lag_cache = vec![Vec3::ZERO; 500 * 20]; let mut prev_breath = 1.0f32;
const NEB: usize = 400;
let neb_ch: Vec<char> = (0..NEB).map(|i| ['.', '-', '+', 'x', '*', 'o', '~', ':'][i%8]).collect();
let mut sdf_gpu: Option<SdfGpuPipeline> = None;
let particle_multiplier: u32 = 16;
engine.run_with_overlay(move |engine, dt, gl| {
time += dt;
hp = (0.65 + (time * 0.06 * TAU).sin() * 0.33).clamp(0.08, 1.0);
let pulse = ((time * 1.8).sin() * 0.5 + 0.5f32).powi(2);
engine.config.render.bloom_intensity = 3.0 + pulse * 2.5; engine.config.render.bloom_radius = 18.0 + pulse * 12.0; engine.config.render.chromatic_aberration = 0.0016 + pulse * 0.0014;
let since_burst = time - last_burst;
if since_burst < 0.5 {
let t = 1.0 - since_burst / 0.5;
engine.config.render.bloom_intensity = 8.0 * t + 3.0 * (1.0-t); engine.config.render.bloom_radius = 48.0 * t + 18.0 * (1.0-t); engine.config.render.chromatic_aberration = 0.12 * t + 0.002 * (1.0-t); }
let cam_y = (time * 0.35).sin() * 0.04;
engine.camera.position.y.target = cam_y;
engine.camera.position.y.position = cam_y;
let breath_now = 1.0 + (time * 1.4).sin() * 0.010;
let is_moving = (breath_now - prev_breath).abs() > 0.0001;
prev_breath = breath_now;
if sdf_gpu.is_none() {
sdf_gpu = unsafe { SdfGpuPipeline::new(gl) };
}
if let Some(ref mut gpu) = sdf_gpu {
let cam_pos = engine.camera.position.position();
unsafe { gpu.dispatch(gl, time, hp, particle_multiplier, cam_pos); }
render_sdf_face(engine, dt, time, Vec3::ZERO, hp);
} else {
render_leon(engine, dt, Vec3::ZERO, hp, time, tw, &mut lag_cache, is_moving);
render_sdf_body(engine, dt, time, Vec3::ZERO, hp);
render_sdf_face(engine, dt, time, Vec3::ZERO, hp);
}
render_vol_scatter(engine, dt, time, Vec3::ZERO);
render_hair(engine, dt, Vec3::ZERO, hp, &mut hair_lag_cache, is_moving);
render_ground(engine, dt, time);
render_environment(engine, dt, time);
render_sky(engine, dt, time);
if let Some(ref mut gpu) = sdf_gpu {
let (ww, wh) = engine.window_size();
let aspect = ww as f32 / wh.max(1) as f32;
let pos = engine.camera.position.position();
let tgt = engine.camera.target.position();
let fov = engine.camera.fov.position;
let view = Mat4::look_at_rh(pos, tgt, Vec3::Y);
let proj = Mat4::perspective_rh_gl(
fov.to_radians(), aspect, engine.camera.near, engine.camera.far,
);
let view_proj = proj * view;
let cam_right = Vec3::new(view.x_axis.x, view.y_axis.x, view.z_axis.x);
let cam_up = Vec3::new(view.x_axis.y, view.y_axis.y, view.z_axis.y);
unsafe { gpu.render(gl, view_proj, cam_right, cam_up, particle_multiplier, time, (ww, wh)); }
}
if time - last_burst > 5.0 {
last_burst = time;
engine.add_trauma(0.20);
spawn_burst(engine, dt, Vec3::new(0.0, -0.5, 0.4), 1.0, 0.75, 0.18, 60, 2.2, (time*100.0) as usize);
spawn_burst(engine, dt, Vec3::new(0.0, 0.1, 0.2), 0.4, 0.70, 1.0, 36, 4.0, (time*137.0) as usize+1000);
engine.emit_audio(AudioEvent::PlaySfx {
name: "impact_heavy".to_string(), position: Vec3::new(0.0,-0.5,0.4), volume: 0.8,
});
}
spawn_stream(engine, dt, Vec3::new(-1.73, 1.15, 0.3), 1.0, 0.55, 0.08, 14, (time*80.0) as usize, time);
spawn_stream(engine, dt, Vec3::new( 1.73, 1.15, 0.3), 0.3, 0.65, 1.0, 14, (time*80.0) as usize+500, time);
let (ww, wh) = engine.window_size();
let half_w = ww as f32 / wh as f32 * 5.5;
let half_h = 5.5f32;
for i in 0..130usize {
let bx = hf(i,0)*half_w*2.0-half_w;
let by = hf(i,1)*half_h*2.0-half_h;
let ph = hf(i,4)*TAU;
let dx = (time*0.10+ph).sin()*0.11+(time*0.07+ph*1.4).cos()*0.05;
let dy = (time*0.08+ph*0.8).cos()*0.09;
let br = 0.022+hf(i,5)*0.038;
engine.spawn_glyph(Glyph {
character: neb_ch[i%neb_ch.len()], scale: Vec2::splat(0.07+hf(i,6)*0.04),
position: Vec3::new(bx+dx, by+dy, -4.5),
color: Vec4::new(0.05+hf(i,7)*0.05, 0.07+hf(i,8)*0.07, 0.22+hf(i,9)*0.14, br),
emission: br*4.5, mass:0.0, lifetime:dt*1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive, ..Default::default()
});
}
for i in 130..270usize {
let bx = hf(i,0)*half_w*1.8-half_w*0.9;
let by = hf(i,1)*half_h*1.8-half_h*0.9;
let ph = hf(i,4)*TAU;
let dx = (time*0.20+ph).sin()*0.14+(time*0.14+ph*0.7).cos()*0.07;
let dy = (time*0.16+ph*1.1).cos()*0.12;
let br = 0.018+hf(i,5)*0.030;
engine.spawn_glyph(Glyph {
character: neb_ch[i%neb_ch.len()], scale: Vec2::splat(0.08+hf(i,6)*0.05),
position: Vec3::new(bx+dx, by+dy, -3.0),
color: Vec4::new(0.24+hf(i,7)*0.16, 0.14+hf(i,8)*0.10, 0.04, br),
emission: br*5.0, mass:0.0, lifetime:dt*1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive, ..Default::default()
});
}
for i in 270..NEB {
let bx = hf(i,0)*half_w*1.4-half_w*0.7;
let by = hf(i,1)*half_h*1.4-half_h*0.7;
let ph = hf(i,4)*TAU;
let dx = (time*0.25+ph).sin()*0.18+(time*0.17+ph*1.5).cos()*0.09;
let dy = (time*0.19+ph*0.9).cos()*0.14;
let br = 0.026+hf(i,5)*0.038;
engine.spawn_glyph(Glyph {
character: neb_ch[i%neb_ch.len()], scale: Vec2::splat(0.09+hf(i,6)*0.05),
position: Vec3::new(bx+dx, by+dy, -1.8),
color: Vec4::new(0.20+hf(i,7)*0.12, 0.06+hf(i,8)*0.05, 0.30+hf(i,9)*0.18, br),
emission: br*5.5, mass:0.0, lifetime:dt*1.5,
layer: RenderLayer::Background, blend_mode: BlendMode::Additive, ..Default::default()
});
}
for i in 0..72usize {
let t = i as f32 / 72.0;
let ang = t*TAU + time*0.28;
let e = (time*2.5+t*TAU).sin()*0.5+0.5;
engine.spawn_glyph(Glyph {
character: if i%5==0 {'*'} else {'.'}, scale: Vec2::splat(0.08+e*0.03),
position: Vec3::new(ang.cos()*3.6, ang.sin()*1.1-0.1, -0.2),
color: Vec4::new(1.0, 0.72+e*0.18, 0.08, 0.13+e*0.10),
emission: 1.0+e*0.7, glow_color: Vec3::new(1.0, 0.65, 0.0), glow_radius: 0.4+e*0.3,
mass:0.0, lifetime:dt*1.5, layer: RenderLayer::Particle,
blend_mode: BlendMode::Additive, ..Default::default()
});
}
for i in 0..52usize {
let t = i as f32 / 52.0;
let ang = t*TAU - time*0.52;
let e = (time*4.0+t*TAU).sin()*0.5+0.5;
engine.spawn_glyph(Glyph {
character: if i%3==0 {'+'} else {'.'}, scale: Vec2::splat(0.07+e*0.03),
position: Vec3::new(ang.cos()*2.2, ang.sin()*0.70-0.1, 0.1),
color: Vec4::new(0.22+e*0.25, 0.52+e*0.28, 1.0, 0.14+e*0.10),
emission: 1.0+e*0.8, glow_color: Vec3::new(0.2, 0.5, 1.0), glow_radius: 0.32+e*0.28,
mass:0.0, lifetime:dt*1.5, layer: RenderLayer::Particle,
blend_mode: BlendMode::Additive, ..Default::default()
});
}
for i in 0..40usize {
let t = i as f32 / 40.0;
let ang = t*TAU + time*1.4;
let e = (time*8.0+t*TAU).sin()*0.5+0.5;
engine.spawn_glyph(Glyph {
character: if i%2==0 {'*'} else {'+'}, scale: Vec2::splat(0.06+e*0.03),
position: Vec3::new(ang.cos()*1.3, ang.sin()*0.40, 0.35),
color: Vec4::new(1.0, 0.92+e*0.08, 0.70+e*0.30, 0.16+e*0.14),
emission: 2.0+e*1.5, glow_color: Vec3::new(1.0, 0.95, 0.6), glow_radius: 0.45+e*0.38,
mass:0.0, lifetime:dt*1.5, layer: RenderLayer::Particle,
blend_mode: BlendMode::Additive, ..Default::default()
});
}
let label_y = half_h - 0.55;
let header = format!("LEON KENNEDY 160M PARTICLES HP {:.0}%", hp * 100.0);
for (ci, ch) in header.chars().enumerate() {
if ch == ' ' { continue; }
engine.spawn_glyph(Glyph {
character: ch, scale: Vec2::splat(0.15),
position: Vec3::new(-half_w+0.18+ci as f32*0.162, label_y, 2.0),
color: Vec4::new(0.82, 0.62, 0.22, 0.92), emission: 0.9,
mass:0.0, lifetime:dt*1.5, layer: RenderLayer::UI, ..Default::default()
});
}
let bar_n = 44usize;
let filled = (hp * bar_n as f32).round() as usize;
for bi in 0..bar_n {
let f = bi < filled;
engine.spawn_glyph(Glyph {
character: if f {'#'} else {'.'}, scale: Vec2::splat(0.15),
position: Vec3::new(-half_w+0.18+bi as f32*0.162, label_y-0.28, 2.0),
color: if f { Vec4::new(0.20+hp*0.80, 0.85-hp*0.40, 0.04, 0.90) }
else { Vec4::new(0.10, 0.08, 0.16, 0.28) },
emission: if f {0.75} else {0.04},
mass:0.0, lifetime:dt*1.5, layer: RenderLayer::UI, ..Default::default()
});
}
if engine.input.just_pressed(Key::Escape) { engine.request_quit(); }
});
}