chore: import upstream snapshot with attribution
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "jcode-tui-anim"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
publish = false
|
||||
|
||||
# Pure, dependency-free math kernels for the TUI idle animations (3D ray-marching
|
||||
# samplers, the 3x3 subpixel glyph chooser and HSV->RGB). Split out of
|
||||
# `jcode-tui` so it can be pinned to `opt-level = 3` via per-package profile
|
||||
# overrides in the workspace Cargo.toml, keeping the hot trig loops fully
|
||||
# optimized even in debug/selfdev builds while only recompiling when this code
|
||||
# changes.
|
||||
|
||||
[lib]
|
||||
name = "jcode_tui_anim"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,201 @@
|
||||
// Throughput benchmark: old "trig every iteration" samplers vs the new
|
||||
// precomputed-angle-table samplers. Run with:
|
||||
// cargo run --profile selfdev --example bench_anim -p jcode-tui-anim
|
||||
// The jcode-tui-anim lib is pinned to opt-level=3 in every profile, so this
|
||||
// measures the table speedup at the optimization level the TUI actually uses.
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
fn rotate_xyz(x: f32, y: f32, z: f32, ax: f32, ay: f32, az: f32) -> (f32, f32, f32) {
|
||||
let (sx, cx) = ax.sin_cos();
|
||||
let (sy, cy) = ay.sin_cos();
|
||||
let (sz, cz) = az.sin_cos();
|
||||
let y1 = y * cx - z * sx;
|
||||
let z1 = y * sx + z * cx;
|
||||
let x1 = x * cy + z1 * sy;
|
||||
let z2 = -x * sy + z1 * cy;
|
||||
let x2 = x1 * cz - y1 * sz;
|
||||
let y2 = x1 * sz + y1 * cz;
|
||||
(x2, y2, z2)
|
||||
}
|
||||
|
||||
// Original donut: cos/sin recomputed every theta/phi iteration.
|
||||
fn old_donut(e: f32, sw: usize, sh: usize, hit: &mut [bool], lum: &mut [f32], z: &mut [f32]) {
|
||||
let a = e * 1.0;
|
||||
let b = e * 0.5;
|
||||
let (ca, sa, cb, sb) = (a.cos(), a.sin(), b.cos(), b.sin());
|
||||
let aspect = 0.5;
|
||||
let (r1, r2, k2) = (1.0f32, 2.0f32, 5.0f32);
|
||||
let k1 = (sw as f32).min(sh as f32 / aspect) * k2 * 0.35 / (r1 + r2);
|
||||
let mut theta = 0.0f32;
|
||||
while theta < std::f32::consts::TAU {
|
||||
let (ct, st) = (theta.cos(), theta.sin());
|
||||
let mut phi = 0.0f32;
|
||||
while phi < std::f32::consts::TAU {
|
||||
let (cp, sp) = (phi.cos(), phi.sin());
|
||||
let cx = r2 + r1 * ct;
|
||||
let cy = r1 * st;
|
||||
let x = cx * (cb * cp + sa * sb * sp) - cy * ca * sb;
|
||||
let y = cx * (sb * cp - sa * cb * sp) + cy * ca * cb;
|
||||
let zz = k2 + ca * cx * sp + cy * sa;
|
||||
let ooz = 1.0 / zz;
|
||||
let xp = (sw as f32 / 2.0 + k1 * ooz * x) as isize;
|
||||
let yp = (sh as f32 / 2.0 - k1 * ooz * y * aspect) as isize;
|
||||
let l = cp * ct * sb - ca * ct * sp - sa * st + cb * (ca * st - ct * sa * sp);
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let i = yp as usize * sw + xp as usize;
|
||||
if ooz > z[i] {
|
||||
z[i] = ooz;
|
||||
lum[i] = l;
|
||||
hit[i] = true;
|
||||
}
|
||||
}
|
||||
phi += 0.014;
|
||||
}
|
||||
theta += 0.04;
|
||||
}
|
||||
}
|
||||
|
||||
fn old_orbit(e: f32, sw: usize, sh: usize, hit: &mut [bool], lum: &mut [f32], z: &mut [f32]) {
|
||||
let rot_x = e * 0.32 + (e * 0.45).sin() * 0.30;
|
||||
let rot_y = e * 0.56;
|
||||
let rot_z = e * 0.22 + (e * 0.38).cos() * 0.22;
|
||||
let cam = 8.8f32;
|
||||
let aspect = 0.5;
|
||||
let sb = (sw as f32).min(sh as f32 / aspect) * 0.29;
|
||||
let rings = [
|
||||
(0u8, 2.35f32, 0.10f32, 0.32f32, 0.0f32),
|
||||
(1u8, 1.78f32, 0.11f32, 0.26f32, std::f32::consts::TAU / 3.0),
|
||||
(
|
||||
2u8,
|
||||
1.22f32,
|
||||
0.09f32,
|
||||
0.20f32,
|
||||
2.0 * std::f32::consts::TAU / 3.0,
|
||||
),
|
||||
(1u8, 2.70f32, 0.08f32, 0.36f32, std::f32::consts::TAU / 6.0),
|
||||
];
|
||||
for (ri, &(axis, major, tube, orbit, po)) in rings.iter().enumerate() {
|
||||
let phase = e * (0.30 + ri as f32 * 0.10) + po;
|
||||
let cxm = orbit * phase.cos() * 0.55;
|
||||
let cym = orbit * (phase * 0.7).sin() * 0.30;
|
||||
let czm = orbit * phase.sin() * 0.50;
|
||||
let pulse = 1.0 + 0.08 * (e * 1.1 + po).sin();
|
||||
let mut u = 0.0f32;
|
||||
while u < std::f32::consts::TAU {
|
||||
let uu = u + phase * 0.7;
|
||||
let (cu, su) = (uu.cos(), uu.sin());
|
||||
let mut v = 0.0f32;
|
||||
while v < std::f32::consts::TAU {
|
||||
let (cv, sv) = (v.cos(), v.sin());
|
||||
let rr = major * pulse + tube * cv;
|
||||
let (x, y, zz, nx, ny, nz) = match axis {
|
||||
0 => (
|
||||
cxm + tube * sv,
|
||||
cym + rr * cu,
|
||||
czm + rr * su,
|
||||
sv,
|
||||
cv * cu,
|
||||
cv * su,
|
||||
),
|
||||
1 => (
|
||||
cxm + rr * cu,
|
||||
cym + tube * sv,
|
||||
czm + rr * su,
|
||||
cv * cu,
|
||||
sv,
|
||||
cv * su,
|
||||
),
|
||||
_ => (
|
||||
cxm + rr * cu,
|
||||
cym + rr * su,
|
||||
czm + tube * sv,
|
||||
cv * cu,
|
||||
cv * su,
|
||||
sv,
|
||||
),
|
||||
};
|
||||
let (rx, ry, rz) = rotate_xyz(x, y, zz, rot_x, rot_y, rot_z);
|
||||
let d = cam + rz;
|
||||
if d < 0.1 {
|
||||
v += 0.22;
|
||||
continue;
|
||||
}
|
||||
let proj = cam / d;
|
||||
let xp = (sw as f32 / 2.0 + rx * proj * sb) as isize;
|
||||
let yp = (sh as f32 / 2.0 - ry * proj * sb * aspect) as isize;
|
||||
let depth = 1.0 / d;
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let i = yp as usize * sw + xp as usize;
|
||||
if depth > z[i] {
|
||||
z[i] = depth;
|
||||
let (rnx, rny, rnz) = rotate_xyz(nx, ny, nz, rot_x, rot_y, rot_z);
|
||||
let glow = (phase.cos() * 0.10 + ri as f32 * 0.03).clamp(-0.2, 0.2);
|
||||
lum[i] =
|
||||
(rnx * 0.42 + rny * 0.33 + rnz * 0.25 + 0.18 + glow).clamp(-1.0, 1.0);
|
||||
hit[i] = true;
|
||||
}
|
||||
}
|
||||
v += 0.22;
|
||||
}
|
||||
u += 0.032;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type S = fn(f32, usize, usize, &mut [bool], &mut [f32], &mut [f32]);
|
||||
|
||||
fn time(name: &str, f: S, frames: usize, sw: usize, sh: usize) -> f64 {
|
||||
let n = sw * sh;
|
||||
let (mut h, mut l, mut z) = (vec![false; n], vec![0.0f32; n], vec![0.0f32; n]);
|
||||
// warmup
|
||||
for i in 0..50 {
|
||||
f(i as f32 * 0.05, sw, sh, &mut h, &mut l, &mut z);
|
||||
h.fill(false);
|
||||
l.fill(0.0);
|
||||
z.fill(0.0);
|
||||
}
|
||||
let t = Instant::now();
|
||||
let mut sink = 0u64;
|
||||
for i in 0..frames {
|
||||
h.fill(false);
|
||||
l.fill(0.0);
|
||||
z.fill(0.0);
|
||||
f(i as f32 * 0.016, sw, sh, &mut h, &mut l, &mut z);
|
||||
sink = sink.wrapping_add(h.iter().filter(|&&b| b).count() as u64);
|
||||
}
|
||||
let dt = t.elapsed().as_secs_f64();
|
||||
let per = dt / frames as f64 * 1e6;
|
||||
std::hint::black_box(sink);
|
||||
println!(
|
||||
" {name:<22} {per:8.2} us/frame ({:.0} frames/s)",
|
||||
1.0 / (dt / frames as f64)
|
||||
);
|
||||
per
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Typical idle viewport: ~120 cols x ~40 rows, 3x subpixels -> 360 x 120.
|
||||
let (sw, sh) = (360usize, 120usize);
|
||||
let frames = 2000;
|
||||
println!("donut @ {sw}x{sh}, {frames} frames:");
|
||||
let od = time("old (trig/iter)", old_donut, frames, sw, sh);
|
||||
let nd = time(
|
||||
"new (angle table)",
|
||||
jcode_tui_anim::sample_donut,
|
||||
frames,
|
||||
sw,
|
||||
sh,
|
||||
);
|
||||
println!(" -> {:.2}x faster\n", od / nd);
|
||||
println!("orbit_rings @ {sw}x{sh}, {frames} frames:");
|
||||
let oo = time("old (trig/iter)", old_orbit, frames, sw, sh);
|
||||
let no = time(
|
||||
"new (angle table)",
|
||||
jcode_tui_anim::sample_orbit_rings,
|
||||
frames,
|
||||
sw,
|
||||
sh,
|
||||
);
|
||||
println!(" -> {:.2}x faster", oo / no);
|
||||
}
|
||||
@@ -0,0 +1,930 @@
|
||||
//! Pure, dependency-free math kernels for the TUI idle animations.
|
||||
//!
|
||||
//! This crate intentionally contains only hot, self-contained numeric code
|
||||
//! (3D ray-marching samplers, the 3x3 subpixel glyph chooser and the HSV->RGB
|
||||
//! conversion). It is split out of `jcode-tui` for two reasons:
|
||||
//!
|
||||
//! 1. It can be pinned to `opt-level = 3` via per-package profile overrides in
|
||||
//! the workspace `Cargo.toml`, so the trig-heavy inner loops stay fully
|
||||
//! optimized even in `dev`/`selfdev` debug builds. Because the code rarely
|
||||
//! changes, the optimized object is reused across iterative rebuilds.
|
||||
//! 2. The angle sequences swept by the samplers are fixed constants, so their
|
||||
//! `cos`/`sin` values are precomputed once into lookup tables. This removes
|
||||
//! ~99% of the per-frame transcendental calls (the donut alone evaluated
|
||||
//! ~142k `cos`/`sin` per frame, i.e. ~8.5M/s at 60 FPS) while keeping the
|
||||
//! rendered output bit-for-bit identical: the tables are generated by
|
||||
//! replaying the exact same floating-point accumulation the loops used, and
|
||||
//! Rust never enables fast-math, so results are deterministic regardless of
|
||||
//! optimization level.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Build a `(cos, sin)` table for the angle sequence `0, step, 2*step, ...`
|
||||
/// produced by the original `while a < TAU { ...; a += step }` loops.
|
||||
///
|
||||
/// The accumulation is replayed exactly (same `f32` start, same `f32` step,
|
||||
/// same iteration bound and the same separate `cos()` / `sin()` calls in the
|
||||
/// same order), so every entry is bit-identical to what the inline loop
|
||||
/// computed.
|
||||
fn angle_table(step: f32) -> Vec<(f32, f32)> {
|
||||
let mut table = Vec::new();
|
||||
let mut a: f32 = 0.0;
|
||||
while a < std::f32::consts::TAU {
|
||||
table.push((a.cos(), a.sin()));
|
||||
a += step;
|
||||
}
|
||||
table
|
||||
}
|
||||
|
||||
fn rotate_xyz(x: f32, y: f32, z: f32, ax: f32, ay: f32, az: f32) -> (f32, f32, f32) {
|
||||
let (sx, cx) = ax.sin_cos();
|
||||
let (sy, cy) = ay.sin_cos();
|
||||
let (sz, cz) = az.sin_cos();
|
||||
let y1 = y * cx - z * sx;
|
||||
let z1 = y * sx + z * cx;
|
||||
let x1 = x * cy + z1 * sy;
|
||||
let z2 = -x * sy + z1 * cy;
|
||||
let x2 = x1 * cz - y1 * sz;
|
||||
let y2 = x1 * sz + y1 * cz;
|
||||
(x2, y2, z2)
|
||||
}
|
||||
|
||||
pub fn sample_donut(
|
||||
elapsed: f32,
|
||||
sw: usize,
|
||||
sh: usize,
|
||||
hit: &mut [bool],
|
||||
lum_map: &mut [f32],
|
||||
z_buf: &mut [f32],
|
||||
) {
|
||||
// `theta` (step 0.04) and `phi` (step 0.014) sweep fixed angle sequences,
|
||||
// so their cos/sin are precomputed once. This was the dominant idle-CPU
|
||||
// hotspot: ~157 * ~449 = ~70k points/frame, each calling cos+sin twice.
|
||||
static THETA: OnceLock<Vec<(f32, f32)>> = OnceLock::new();
|
||||
static PHI: OnceLock<Vec<(f32, f32)>> = OnceLock::new();
|
||||
let theta_tab = THETA.get_or_init(|| angle_table(0.04));
|
||||
let phi_tab = PHI.get_or_init(|| angle_table(0.014));
|
||||
|
||||
let a_rot = elapsed * 1.0;
|
||||
let b_rot = elapsed * 0.5;
|
||||
let cos_a = a_rot.cos();
|
||||
let sin_a = a_rot.sin();
|
||||
let cos_b = b_rot.cos();
|
||||
let sin_b = b_rot.sin();
|
||||
|
||||
let aspect = 0.5;
|
||||
let r1 = 1.0f32;
|
||||
let r2 = 2.0f32;
|
||||
let k2 = 5.0f32;
|
||||
let k1 = (sw as f32).min(sh as f32 / aspect) * k2 * 0.35 / (r1 + r2);
|
||||
|
||||
let half_w = sw as f32 / 2.0;
|
||||
let half_h = sh as f32 / 2.0;
|
||||
|
||||
for &(ct, st) in theta_tab.iter() {
|
||||
let cx = r2 + r1 * ct;
|
||||
let cy = r1 * st;
|
||||
|
||||
for &(cp, sp) in phi_tab.iter() {
|
||||
let x = cx * (cos_b * cp + sin_a * sin_b * sp) - cy * cos_a * sin_b;
|
||||
let y = cx * (sin_b * cp - sin_a * cos_b * sp) + cy * cos_a * cos_b;
|
||||
let z = k2 + cos_a * cx * sp + cy * sin_a;
|
||||
let ooz = 1.0 / z;
|
||||
|
||||
let xp = (half_w + k1 * ooz * x) as isize;
|
||||
let yp = (half_h - k1 * ooz * y * aspect) as isize;
|
||||
|
||||
let lum = cp * ct * sin_b - cos_a * ct * sp - sin_a * st
|
||||
+ cos_b * (cos_a * st - ct * sin_a * sp);
|
||||
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let idx = yp as usize * sw + xp as usize;
|
||||
if ooz > z_buf[idx] {
|
||||
z_buf[idx] = ooz;
|
||||
lum_map[idx] = lum;
|
||||
hit[idx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_black_hole(
|
||||
elapsed: f32,
|
||||
sw: usize,
|
||||
sh: usize,
|
||||
hit: &mut [bool],
|
||||
lum_map: &mut [f32],
|
||||
z_buf: &mut [f32],
|
||||
) {
|
||||
let cx = sw as f32 / 2.0;
|
||||
let cy = sh as f32 / 2.0;
|
||||
let aspect = 0.5f32;
|
||||
let disk_half_len = sw as f32 * 0.35;
|
||||
let disk_half_thickness = (sh as f32 * 0.052).max(1.0);
|
||||
let horizon_r = (sh as f32).min(sw as f32 / 3.2) * 0.16;
|
||||
let halo_r = horizon_r * 1.8;
|
||||
let shimmer = elapsed * 2.35;
|
||||
|
||||
for y in 0..sh {
|
||||
for x in 0..sw {
|
||||
let dx = x as f32 - cx;
|
||||
let dy = (y as f32 - cy) / aspect;
|
||||
let r = (dx * dx + dy * dy).sqrt();
|
||||
let idx = y * sw + x;
|
||||
|
||||
let abs_x = dx.abs();
|
||||
let abs_y = dy.abs();
|
||||
|
||||
let disk_falloff_x = (1.0 - abs_x / disk_half_len).clamp(0.0, 1.0);
|
||||
let disk_core = (1.0 - abs_y / disk_half_thickness).clamp(0.0, 1.0);
|
||||
let disk_glow =
|
||||
(1.0 - abs_y / (disk_half_thickness * 3.8 + 1.0)).clamp(0.0, 1.0) * 0.42;
|
||||
let lens_band = (1.0 - ((abs_y - horizon_r * 0.72).abs() / (horizon_r * 0.55 + 0.1)))
|
||||
.clamp(0.0, 1.0)
|
||||
* (1.0 - abs_x / (halo_r * 1.5 + 1.0)).clamp(0.0, 1.0);
|
||||
let halo =
|
||||
(1.0 - ((r - halo_r).abs() / (horizon_r * 0.95 + 0.1))).clamp(0.0, 1.0) * 0.38;
|
||||
|
||||
let streak_phase = shimmer + dx * 0.33;
|
||||
let streaks = ((streak_phase.sin() * 0.5 + 0.5) * 0.55
|
||||
+ ((streak_phase * 0.47 + 1.7).sin() * 0.5 + 0.5) * 0.45)
|
||||
* disk_falloff_x;
|
||||
let relativistic_beam = (1.0
|
||||
- ((dx - disk_half_len * 0.34).abs() / (disk_half_len * 0.52 + 0.1)))
|
||||
.clamp(0.0, 1.0)
|
||||
* 0.32;
|
||||
|
||||
let mut brightness = disk_core * (0.55 + 0.45 * streaks) * disk_falloff_x
|
||||
+ disk_glow * disk_falloff_x
|
||||
+ lens_band * 0.62
|
||||
+ halo * 0.28
|
||||
+ relativistic_beam * disk_core;
|
||||
|
||||
if r <= horizon_r {
|
||||
brightness = 0.0;
|
||||
}
|
||||
|
||||
if abs_x <= horizon_r * 0.95 && abs_y <= disk_half_thickness * 1.2 {
|
||||
brightness *= (abs_x / (horizon_r * 0.95 + 0.1)).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
brightness = brightness.clamp(0.0, 1.0);
|
||||
if brightness > 0.06 {
|
||||
hit[idx] = true;
|
||||
lum_map[idx] = brightness * 2.0 - 1.0;
|
||||
z_buf[idx] = brightness + lens_band * 0.2;
|
||||
} else {
|
||||
hit[idx] = false;
|
||||
lum_map[idx] = -1.0;
|
||||
z_buf[idx] = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_gyroscope(
|
||||
elapsed: f32,
|
||||
sw: usize,
|
||||
sh: usize,
|
||||
hit: &mut [bool],
|
||||
lum_map: &mut [f32],
|
||||
z_buf: &mut [f32],
|
||||
) {
|
||||
// The tube sweep `v` (step 0.24) is a fixed angle sequence; precompute it.
|
||||
static V_TAB: OnceLock<Vec<(f32, f32)>> = OnceLock::new();
|
||||
let v_tab = V_TAB.get_or_init(|| angle_table(0.24));
|
||||
|
||||
let rot_x = elapsed * 0.45 + (elapsed * 0.7).sin() * 0.25;
|
||||
let rot_y = elapsed * 0.70;
|
||||
let rot_z = elapsed * 0.28 + (elapsed * 0.5).cos() * 0.18;
|
||||
let cam_dist = 8.5f32;
|
||||
let aspect = 0.5;
|
||||
let scale_base = (sw as f32).min(sh as f32 / aspect) * 0.20;
|
||||
|
||||
let rings = [(0u8, 2.0f32, 0.17f32), (1, 1.45, 0.15), (2, 0.95, 0.13)];
|
||||
|
||||
for (ring_idx, &(axis, major_r, tube_r)) in rings.iter().enumerate() {
|
||||
let phase = elapsed * (0.35 + ring_idx as f32 * 0.15);
|
||||
let mut u: f32 = 0.0;
|
||||
while u < std::f32::consts::TAU {
|
||||
let uu = u + phase;
|
||||
let cu = uu.cos();
|
||||
let su = uu.sin();
|
||||
|
||||
for &(cv, sv) in v_tab.iter() {
|
||||
let ring_r = major_r + tube_r * cv;
|
||||
|
||||
let (x, y, z, nx, ny, nz) = match axis {
|
||||
0 => {
|
||||
let x = tube_r * sv;
|
||||
let y = ring_r * cu;
|
||||
let z = ring_r * su;
|
||||
let nx = sv;
|
||||
let ny = cv * cu;
|
||||
let nz = cv * su;
|
||||
(x, y, z, nx, ny, nz)
|
||||
}
|
||||
1 => {
|
||||
let x = ring_r * cu;
|
||||
let y = tube_r * sv;
|
||||
let z = ring_r * su;
|
||||
let nx = cv * cu;
|
||||
let ny = sv;
|
||||
let nz = cv * su;
|
||||
(x, y, z, nx, ny, nz)
|
||||
}
|
||||
_ => {
|
||||
let x = ring_r * cu;
|
||||
let y = ring_r * su;
|
||||
let z = tube_r * sv;
|
||||
let nx = cv * cu;
|
||||
let ny = cv * su;
|
||||
let nz = sv;
|
||||
(x, y, z, nx, ny, nz)
|
||||
}
|
||||
};
|
||||
|
||||
let (rx, ry, rz) = rotate_xyz(x, y, z, rot_x, rot_y, rot_z);
|
||||
let d = cam_dist + rz;
|
||||
if d < 0.1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let proj = cam_dist / d;
|
||||
let xp = (sw as f32 / 2.0 + rx * proj * scale_base) as isize;
|
||||
let yp = (sh as f32 / 2.0 - ry * proj * scale_base * aspect) as isize;
|
||||
let depth = 1.0 / d;
|
||||
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let idx = yp as usize * sw + xp as usize;
|
||||
if depth > z_buf[idx] {
|
||||
z_buf[idx] = depth;
|
||||
let (rnx, rny, rnz) = rotate_xyz(nx, ny, nz, rot_x, rot_y, rot_z);
|
||||
let lum = (rnx * 0.45 + rny * 0.35 + rnz * 0.20 + 0.20).clamp(-1.0, 1.0);
|
||||
lum_map[idx] = lum;
|
||||
hit[idx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u += 0.035;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_orbit_rings(
|
||||
elapsed: f32,
|
||||
sw: usize,
|
||||
sh: usize,
|
||||
hit: &mut [bool],
|
||||
lum_map: &mut [f32],
|
||||
z_buf: &mut [f32],
|
||||
) {
|
||||
// The tube sweep `v` (step 0.22) is a fixed angle sequence; precompute it.
|
||||
static V_TAB: OnceLock<Vec<(f32, f32)>> = OnceLock::new();
|
||||
let v_tab = V_TAB.get_or_init(|| angle_table(0.22));
|
||||
|
||||
let rot_x = elapsed * 0.32 + (elapsed * 0.45).sin() * 0.30;
|
||||
let rot_y = elapsed * 0.56;
|
||||
let rot_z = elapsed * 0.22 + (elapsed * 0.38).cos() * 0.22;
|
||||
let cam_dist = 8.8f32;
|
||||
let aspect = 0.5;
|
||||
let scale_base = (sw as f32).min(sh as f32 / aspect) * 0.29;
|
||||
|
||||
let rings = [
|
||||
(0u8, 2.35f32, 0.10f32, 0.32f32, 0.0f32),
|
||||
(1u8, 1.78f32, 0.11f32, 0.26f32, std::f32::consts::TAU / 3.0),
|
||||
(
|
||||
2u8,
|
||||
1.22f32,
|
||||
0.09f32,
|
||||
0.20f32,
|
||||
2.0 * std::f32::consts::TAU / 3.0,
|
||||
),
|
||||
(1u8, 2.70f32, 0.08f32, 0.36f32, std::f32::consts::TAU / 6.0),
|
||||
];
|
||||
|
||||
for (ring_idx, &(axis, major_r, tube_r, orbit_r, phase_offset)) in rings.iter().enumerate() {
|
||||
let phase = elapsed * (0.30 + ring_idx as f32 * 0.10) + phase_offset;
|
||||
let center_x = orbit_r * phase.cos() * 0.55;
|
||||
let center_y = orbit_r * (phase * 0.7).sin() * 0.30;
|
||||
let center_z = orbit_r * phase.sin() * 0.50;
|
||||
let radius_pulse = 1.0 + 0.08 * (elapsed * 1.1 + phase_offset).sin();
|
||||
|
||||
let mut u: f32 = 0.0;
|
||||
while u < std::f32::consts::TAU {
|
||||
let uu = u + phase * 0.7;
|
||||
let cu = uu.cos();
|
||||
let su = uu.sin();
|
||||
|
||||
for &(cv, sv) in v_tab.iter() {
|
||||
let ring_r = major_r * radius_pulse + tube_r * cv;
|
||||
|
||||
let (x, y, z, nx, ny, nz) = match axis {
|
||||
0 => {
|
||||
let x = center_x + tube_r * sv;
|
||||
let y = center_y + ring_r * cu;
|
||||
let z = center_z + ring_r * su;
|
||||
let nx = sv;
|
||||
let ny = cv * cu;
|
||||
let nz = cv * su;
|
||||
(x, y, z, nx, ny, nz)
|
||||
}
|
||||
1 => {
|
||||
let x = center_x + ring_r * cu;
|
||||
let y = center_y + tube_r * sv;
|
||||
let z = center_z + ring_r * su;
|
||||
let nx = cv * cu;
|
||||
let ny = sv;
|
||||
let nz = cv * su;
|
||||
(x, y, z, nx, ny, nz)
|
||||
}
|
||||
_ => {
|
||||
let x = center_x + ring_r * cu;
|
||||
let y = center_y + ring_r * su;
|
||||
let z = center_z + tube_r * sv;
|
||||
let nx = cv * cu;
|
||||
let ny = cv * su;
|
||||
let nz = sv;
|
||||
(x, y, z, nx, ny, nz)
|
||||
}
|
||||
};
|
||||
|
||||
let (rx, ry, rz) = rotate_xyz(x, y, z, rot_x, rot_y, rot_z);
|
||||
let d = cam_dist + rz;
|
||||
if d < 0.1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let proj = cam_dist / d;
|
||||
let xp = (sw as f32 / 2.0 + rx * proj * scale_base) as isize;
|
||||
let yp = (sh as f32 / 2.0 - ry * proj * scale_base * aspect) as isize;
|
||||
let depth = 1.0 / d;
|
||||
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let idx = yp as usize * sw + xp as usize;
|
||||
if depth > z_buf[idx] {
|
||||
z_buf[idx] = depth;
|
||||
let (rnx, rny, rnz) = rotate_xyz(nx, ny, nz, rot_x, rot_y, rot_z);
|
||||
let glow = (phase.cos() * 0.10 + ring_idx as f32 * 0.03).clamp(-0.2, 0.2);
|
||||
let lum =
|
||||
(rnx * 0.42 + rny * 0.33 + rnz * 0.25 + 0.18 + glow).clamp(-1.0, 1.0);
|
||||
lum_map[idx] = lum;
|
||||
hit[idx] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u += 0.032;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shape_char_3x3(pattern: u16, brightness: f32) -> char {
|
||||
if pattern == 0 {
|
||||
return ' ';
|
||||
}
|
||||
|
||||
let top_l = pattern & 1 != 0;
|
||||
let top_c = pattern & 2 != 0;
|
||||
let top_r = pattern & 4 != 0;
|
||||
let mid_l = pattern & 8 != 0;
|
||||
let mid_c = pattern & 16 != 0;
|
||||
let mid_r = pattern & 32 != 0;
|
||||
let bot_l = pattern & 64 != 0;
|
||||
let bot_c = pattern & 128 != 0;
|
||||
let bot_r = pattern & 256 != 0;
|
||||
|
||||
let count = pattern.count_ones();
|
||||
let top = (top_l as u8) + (top_c as u8) + (top_r as u8);
|
||||
let mid = (mid_l as u8) + (mid_c as u8) + (mid_r as u8);
|
||||
let bot = (bot_l as u8) + (bot_c as u8) + (bot_r as u8);
|
||||
let left = (top_l as u8) + (mid_l as u8) + (bot_l as u8);
|
||||
let center = (top_c as u8) + (mid_c as u8) + (bot_c as u8);
|
||||
let right = (top_r as u8) + (mid_r as u8) + (bot_r as u8);
|
||||
|
||||
let bl = if brightness > 0.65 {
|
||||
2u8
|
||||
} else if brightness > 0.35 {
|
||||
1u8
|
||||
} else {
|
||||
0u8
|
||||
};
|
||||
|
||||
if count >= 8 {
|
||||
return match bl {
|
||||
2 => '@',
|
||||
1 => '#',
|
||||
_ => '%',
|
||||
};
|
||||
}
|
||||
if count >= 7 {
|
||||
return match bl {
|
||||
2 => '#',
|
||||
1 => '%',
|
||||
_ => '*',
|
||||
};
|
||||
}
|
||||
|
||||
if top_l && mid_c && bot_r && !top_r && !bot_l {
|
||||
return match bl {
|
||||
2 => '\\',
|
||||
1 => '\\',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
if top_r && mid_c && bot_l && !top_l && !bot_r {
|
||||
return match bl {
|
||||
2 => '/',
|
||||
1 => '/',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
|
||||
if mid >= 2 && top <= 1 && bot <= 1 && mid > top && mid > bot {
|
||||
return match bl {
|
||||
2 => '=',
|
||||
1 => '-',
|
||||
_ => '~',
|
||||
};
|
||||
}
|
||||
if top >= 2 && mid <= 1 && bot == 0 {
|
||||
return match bl {
|
||||
2 => '=',
|
||||
1 => '-',
|
||||
_ => '~',
|
||||
};
|
||||
}
|
||||
if bot >= 2 && mid <= 1 && top == 0 {
|
||||
return match bl {
|
||||
2 => '=',
|
||||
1 => '_',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
|
||||
if center >= 2 && left <= 1 && right <= 1 && center > left && center > right {
|
||||
return match bl {
|
||||
2 => '|',
|
||||
1 => '|',
|
||||
_ => ':',
|
||||
};
|
||||
}
|
||||
if left >= 2 && center <= 1 && right == 0 {
|
||||
return match bl {
|
||||
2 => '|',
|
||||
1 => '|',
|
||||
_ => ':',
|
||||
};
|
||||
}
|
||||
if right >= 2 && center <= 1 && left == 0 {
|
||||
return match bl {
|
||||
2 => '|',
|
||||
1 => '|',
|
||||
_ => ':',
|
||||
};
|
||||
}
|
||||
|
||||
if top >= 2 && bot == 0 {
|
||||
return match bl {
|
||||
2 => '"',
|
||||
1 => '^',
|
||||
_ => '\'',
|
||||
};
|
||||
}
|
||||
if bot >= 2 && top == 0 {
|
||||
return match bl {
|
||||
2 => ',',
|
||||
1 => '.',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
|
||||
if left >= 2 && right == 0 {
|
||||
return match bl {
|
||||
2 => '(',
|
||||
1 => '(',
|
||||
_ => ':',
|
||||
};
|
||||
}
|
||||
if right >= 2 && left == 0 {
|
||||
return match bl {
|
||||
2 => ')',
|
||||
1 => ')',
|
||||
_ => ':',
|
||||
};
|
||||
}
|
||||
|
||||
if count >= 6 {
|
||||
return match bl {
|
||||
2 => '%',
|
||||
1 => '*',
|
||||
_ => '+',
|
||||
};
|
||||
}
|
||||
if count >= 5 {
|
||||
return match bl {
|
||||
2 => '*',
|
||||
1 => '+',
|
||||
_ => ':',
|
||||
};
|
||||
}
|
||||
|
||||
if mid_c && count <= 3 {
|
||||
return match bl {
|
||||
2 => 'o',
|
||||
1 => '*',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
|
||||
if top_r && bot_l && count <= 3 {
|
||||
return match bl {
|
||||
2 => '/',
|
||||
1 => '/',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
if top_l && bot_r && count <= 3 {
|
||||
return match bl {
|
||||
2 => '\\',
|
||||
1 => '\\',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
if bot_c || bot_l || bot_r {
|
||||
return match bl {
|
||||
2 => '.',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
if top_c || top_l || top_r {
|
||||
return match bl {
|
||||
2 => '\'',
|
||||
1 => '\'',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
return match bl {
|
||||
2 => ':',
|
||||
1 => '.',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
|
||||
if count <= 3 {
|
||||
return match bl {
|
||||
2 => ':',
|
||||
1 => ':',
|
||||
_ => '.',
|
||||
};
|
||||
}
|
||||
|
||||
match bl {
|
||||
2 => '+',
|
||||
1 => ':',
|
||||
_ => '.',
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (u8, u8, u8) {
|
||||
let c = v * s;
|
||||
let h2 = h / 60.0;
|
||||
let x = c * (1.0 - (h2 % 2.0 - 1.0).abs());
|
||||
let (r1, g1, b1) = match h2 as u32 {
|
||||
0 => (c, x, 0.0),
|
||||
1 => (x, c, 0.0),
|
||||
2 => (0.0, c, x),
|
||||
3 => (0.0, x, c),
|
||||
4 => (x, 0.0, c),
|
||||
_ => (c, 0.0, x),
|
||||
};
|
||||
let m = v - c;
|
||||
(
|
||||
((r1 + m) * 255.0) as u8,
|
||||
((g1 + m) * 255.0) as u8,
|
||||
((b1 + m) * 255.0) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Reference implementations: the EXACT original inline-loop versions, before
|
||||
// the angle-table optimization. The optimized samplers must reproduce these
|
||||
// buffers bit-for-bit. Rust never enables fast-math, so replaying the same
|
||||
// f32 accumulation and the same per-iteration cos()/sin() calls yields
|
||||
// identical results regardless of opt-level.
|
||||
|
||||
fn ref_rotate_xyz(x: f32, y: f32, z: f32, ax: f32, ay: f32, az: f32) -> (f32, f32, f32) {
|
||||
let (sx, cx) = ax.sin_cos();
|
||||
let (sy, cy) = ay.sin_cos();
|
||||
let (sz, cz) = az.sin_cos();
|
||||
let y1 = y * cx - z * sx;
|
||||
let z1 = y * sx + z * cx;
|
||||
let x1 = x * cy + z1 * sy;
|
||||
let z2 = -x * sy + z1 * cy;
|
||||
let x2 = x1 * cz - y1 * sz;
|
||||
let y2 = x1 * sz + y1 * cz;
|
||||
(x2, y2, z2)
|
||||
}
|
||||
|
||||
fn ref_donut(
|
||||
elapsed: f32,
|
||||
sw: usize,
|
||||
sh: usize,
|
||||
hit: &mut [bool],
|
||||
lum_map: &mut [f32],
|
||||
z_buf: &mut [f32],
|
||||
) {
|
||||
let a_rot = elapsed * 1.0;
|
||||
let b_rot = elapsed * 0.5;
|
||||
let cos_a = a_rot.cos();
|
||||
let sin_a = a_rot.sin();
|
||||
let cos_b = b_rot.cos();
|
||||
let sin_b = b_rot.sin();
|
||||
|
||||
let aspect = 0.5;
|
||||
let r1 = 1.0f32;
|
||||
let r2 = 2.0f32;
|
||||
let k2 = 5.0f32;
|
||||
let k1 = (sw as f32).min(sh as f32 / aspect) * k2 * 0.35 / (r1 + r2);
|
||||
|
||||
let mut theta: f32 = 0.0;
|
||||
while theta < std::f32::consts::TAU {
|
||||
let ct = theta.cos();
|
||||
let st = theta.sin();
|
||||
|
||||
let mut phi: f32 = 0.0;
|
||||
while phi < std::f32::consts::TAU {
|
||||
let cp = phi.cos();
|
||||
let sp = phi.sin();
|
||||
|
||||
let cx = r2 + r1 * ct;
|
||||
let cy = r1 * st;
|
||||
|
||||
let x = cx * (cos_b * cp + sin_a * sin_b * sp) - cy * cos_a * sin_b;
|
||||
let y = cx * (sin_b * cp - sin_a * cos_b * sp) + cy * cos_a * cos_b;
|
||||
let z = k2 + cos_a * cx * sp + cy * sin_a;
|
||||
let ooz = 1.0 / z;
|
||||
|
||||
let xp = (sw as f32 / 2.0 + k1 * ooz * x) as isize;
|
||||
let yp = (sh as f32 / 2.0 - k1 * ooz * y * aspect) as isize;
|
||||
|
||||
let lum = cp * ct * sin_b - cos_a * ct * sp - sin_a * st
|
||||
+ cos_b * (cos_a * st - ct * sin_a * sp);
|
||||
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let idx = yp as usize * sw + xp as usize;
|
||||
if ooz > z_buf[idx] {
|
||||
z_buf[idx] = ooz;
|
||||
lum_map[idx] = lum;
|
||||
hit[idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
phi += 0.014;
|
||||
}
|
||||
theta += 0.04;
|
||||
}
|
||||
}
|
||||
|
||||
fn ref_gyroscope(
|
||||
elapsed: f32,
|
||||
sw: usize,
|
||||
sh: usize,
|
||||
hit: &mut [bool],
|
||||
lum_map: &mut [f32],
|
||||
z_buf: &mut [f32],
|
||||
) {
|
||||
let rot_x = elapsed * 0.45 + (elapsed * 0.7).sin() * 0.25;
|
||||
let rot_y = elapsed * 0.70;
|
||||
let rot_z = elapsed * 0.28 + (elapsed * 0.5).cos() * 0.18;
|
||||
let cam_dist = 8.5f32;
|
||||
let aspect = 0.5;
|
||||
let scale_base = (sw as f32).min(sh as f32 / aspect) * 0.20;
|
||||
|
||||
let rings = [(0u8, 2.0f32, 0.17f32), (1, 1.45, 0.15), (2, 0.95, 0.13)];
|
||||
|
||||
for (ring_idx, &(axis, major_r, tube_r)) in rings.iter().enumerate() {
|
||||
let phase = elapsed * (0.35 + ring_idx as f32 * 0.15);
|
||||
let mut u: f32 = 0.0;
|
||||
while u < std::f32::consts::TAU {
|
||||
let uu = u + phase;
|
||||
let cu = uu.cos();
|
||||
let su = uu.sin();
|
||||
|
||||
let mut v: f32 = 0.0;
|
||||
while v < std::f32::consts::TAU {
|
||||
let cv = v.cos();
|
||||
let sv = v.sin();
|
||||
let ring_r = major_r + tube_r * cv;
|
||||
|
||||
let (x, y, z, nx, ny, nz) = match axis {
|
||||
0 => {
|
||||
let x = tube_r * sv;
|
||||
let y = ring_r * cu;
|
||||
let z = ring_r * su;
|
||||
(x, y, z, sv, cv * cu, cv * su)
|
||||
}
|
||||
1 => {
|
||||
let x = ring_r * cu;
|
||||
let y = tube_r * sv;
|
||||
let z = ring_r * su;
|
||||
(x, y, z, cv * cu, sv, cv * su)
|
||||
}
|
||||
_ => {
|
||||
let x = ring_r * cu;
|
||||
let y = ring_r * su;
|
||||
let z = tube_r * sv;
|
||||
(x, y, z, cv * cu, cv * su, sv)
|
||||
}
|
||||
};
|
||||
|
||||
let (rx, ry, rz) = ref_rotate_xyz(x, y, z, rot_x, rot_y, rot_z);
|
||||
let d = cam_dist + rz;
|
||||
if d < 0.1 {
|
||||
v += 0.24;
|
||||
continue;
|
||||
}
|
||||
|
||||
let proj = cam_dist / d;
|
||||
let xp = (sw as f32 / 2.0 + rx * proj * scale_base) as isize;
|
||||
let yp = (sh as f32 / 2.0 - ry * proj * scale_base * aspect) as isize;
|
||||
let depth = 1.0 / d;
|
||||
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let idx = yp as usize * sw + xp as usize;
|
||||
if depth > z_buf[idx] {
|
||||
z_buf[idx] = depth;
|
||||
let (rnx, rny, rnz) = ref_rotate_xyz(nx, ny, nz, rot_x, rot_y, rot_z);
|
||||
let lum =
|
||||
(rnx * 0.45 + rny * 0.35 + rnz * 0.20 + 0.20).clamp(-1.0, 1.0);
|
||||
lum_map[idx] = lum;
|
||||
hit[idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
v += 0.24;
|
||||
}
|
||||
|
||||
u += 0.035;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ref_orbit_rings(
|
||||
elapsed: f32,
|
||||
sw: usize,
|
||||
sh: usize,
|
||||
hit: &mut [bool],
|
||||
lum_map: &mut [f32],
|
||||
z_buf: &mut [f32],
|
||||
) {
|
||||
let rot_x = elapsed * 0.32 + (elapsed * 0.45).sin() * 0.30;
|
||||
let rot_y = elapsed * 0.56;
|
||||
let rot_z = elapsed * 0.22 + (elapsed * 0.38).cos() * 0.22;
|
||||
let cam_dist = 8.8f32;
|
||||
let aspect = 0.5;
|
||||
let scale_base = (sw as f32).min(sh as f32 / aspect) * 0.29;
|
||||
|
||||
let rings = [
|
||||
(0u8, 2.35f32, 0.10f32, 0.32f32, 0.0f32),
|
||||
(1u8, 1.78f32, 0.11f32, 0.26f32, std::f32::consts::TAU / 3.0),
|
||||
(
|
||||
2u8,
|
||||
1.22f32,
|
||||
0.09f32,
|
||||
0.20f32,
|
||||
2.0 * std::f32::consts::TAU / 3.0,
|
||||
),
|
||||
(1u8, 2.70f32, 0.08f32, 0.36f32, std::f32::consts::TAU / 6.0),
|
||||
];
|
||||
|
||||
for (ring_idx, &(axis, major_r, tube_r, orbit_r, phase_offset)) in rings.iter().enumerate()
|
||||
{
|
||||
let phase = elapsed * (0.30 + ring_idx as f32 * 0.10) + phase_offset;
|
||||
let center_x = orbit_r * phase.cos() * 0.55;
|
||||
let center_y = orbit_r * (phase * 0.7).sin() * 0.30;
|
||||
let center_z = orbit_r * phase.sin() * 0.50;
|
||||
let radius_pulse = 1.0 + 0.08 * (elapsed * 1.1 + phase_offset).sin();
|
||||
|
||||
let mut u: f32 = 0.0;
|
||||
while u < std::f32::consts::TAU {
|
||||
let uu = u + phase * 0.7;
|
||||
let cu = uu.cos();
|
||||
let su = uu.sin();
|
||||
|
||||
let mut v: f32 = 0.0;
|
||||
while v < std::f32::consts::TAU {
|
||||
let cv = v.cos();
|
||||
let sv = v.sin();
|
||||
let ring_r = major_r * radius_pulse + tube_r * cv;
|
||||
|
||||
let (x, y, z, nx, ny, nz) = match axis {
|
||||
0 => {
|
||||
let x = center_x + tube_r * sv;
|
||||
let y = center_y + ring_r * cu;
|
||||
let z = center_z + ring_r * su;
|
||||
(x, y, z, sv, cv * cu, cv * su)
|
||||
}
|
||||
1 => {
|
||||
let x = center_x + ring_r * cu;
|
||||
let y = center_y + tube_r * sv;
|
||||
let z = center_z + ring_r * su;
|
||||
(x, y, z, cv * cu, sv, cv * su)
|
||||
}
|
||||
_ => {
|
||||
let x = center_x + ring_r * cu;
|
||||
let y = center_y + ring_r * su;
|
||||
let z = center_z + tube_r * sv;
|
||||
(x, y, z, cv * cu, cv * su, sv)
|
||||
}
|
||||
};
|
||||
|
||||
let (rx, ry, rz) = ref_rotate_xyz(x, y, z, rot_x, rot_y, rot_z);
|
||||
let d = cam_dist + rz;
|
||||
if d < 0.1 {
|
||||
v += 0.22;
|
||||
continue;
|
||||
}
|
||||
|
||||
let proj = cam_dist / d;
|
||||
let xp = (sw as f32 / 2.0 + rx * proj * scale_base) as isize;
|
||||
let yp = (sh as f32 / 2.0 - ry * proj * scale_base * aspect) as isize;
|
||||
let depth = 1.0 / d;
|
||||
|
||||
if xp >= 0 && (xp as usize) < sw && yp >= 0 && (yp as usize) < sh {
|
||||
let idx = yp as usize * sw + xp as usize;
|
||||
if depth > z_buf[idx] {
|
||||
z_buf[idx] = depth;
|
||||
let (rnx, rny, rnz) = ref_rotate_xyz(nx, ny, nz, rot_x, rot_y, rot_z);
|
||||
let glow =
|
||||
(phase.cos() * 0.10 + ring_idx as f32 * 0.03).clamp(-0.2, 0.2);
|
||||
let lum = (rnx * 0.42 + rny * 0.33 + rnz * 0.25 + 0.18 + glow)
|
||||
.clamp(-1.0, 1.0);
|
||||
lum_map[idx] = lum;
|
||||
hit[idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
v += 0.22;
|
||||
}
|
||||
|
||||
u += 0.032;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Sampler = fn(f32, usize, usize, &mut [bool], &mut [f32], &mut [f32]);
|
||||
|
||||
fn assert_bit_identical(name: &str, reference: Sampler, optimized: Sampler) {
|
||||
for &(sw, sh) in &[(120usize, 60usize), (90, 36), (108, 42), (57, 23)] {
|
||||
for &elapsed in &[0.0f32, 0.4, 0.8, 1.6, 2.4, 3.3, 5.7, 9.1] {
|
||||
let n = sw * sh;
|
||||
let (mut h0, mut l0, mut z0) = (vec![false; n], vec![0.0f32; n], vec![0.0f32; n]);
|
||||
let (mut h1, mut l1, mut z1) = (vec![false; n], vec![0.0f32; n], vec![0.0f32; n]);
|
||||
reference(elapsed, sw, sh, &mut h0, &mut l0, &mut z0);
|
||||
optimized(elapsed, sw, sh, &mut h1, &mut l1, &mut z1);
|
||||
|
||||
assert_eq!(h0, h1, "{name}: hit mismatch at {sw}x{sh} t={elapsed}");
|
||||
// Compare float buffers by exact bit pattern.
|
||||
for i in 0..n {
|
||||
assert_eq!(
|
||||
l0[i].to_bits(),
|
||||
l1[i].to_bits(),
|
||||
"{name}: lum[{i}] mismatch at {sw}x{sh} t={elapsed}: {} vs {}",
|
||||
l0[i],
|
||||
l1[i]
|
||||
);
|
||||
assert_eq!(
|
||||
z0[i].to_bits(),
|
||||
z1[i].to_bits(),
|
||||
"{name}: z[{i}] mismatch at {sw}x{sh} t={elapsed}: {} vs {}",
|
||||
z0[i],
|
||||
z1[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn donut_table_is_bit_identical_to_reference() {
|
||||
assert_bit_identical("donut", ref_donut, sample_donut);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gyroscope_table_is_bit_identical_to_reference() {
|
||||
assert_bit_identical("gyroscope", ref_gyroscope, sample_gyroscope);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orbit_rings_table_is_bit_identical_to_reference() {
|
||||
assert_bit_identical("orbit_rings", ref_orbit_rings, sample_orbit_rings);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user