use crate::gfx::{camera::Camera3D, depth::DepthQueue};
use std::f32::consts::TAU;
const DEPTH_BIAS: f32 = -0.06;
#[inline]
fn p2w(
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
u: f32,
v: f32,
) -> (f32, f32, f32) {
(
cx + ux * u + vx * v,
cy + uy * u + vy * v,
cz + uz * u + vz * v,
)
}
#[inline]
fn push_seg(
q: &mut DepthQueue,
cam: &Camera3D,
color: u32,
mut ax: f32,
mut ay: f32,
mut az: f32,
mut bx: f32,
mut by: f32,
mut bz: f32,
) {
let near = -cam.zdist + 0.05;
let da = cam.depth(ax, ay, az);
let db = cam.depth(bx, by, bz);
if da <= near && db <= near {
return;
}
if da <= near {
let t = (near - da) / (db - da);
ax += t * (bx - ax);
ay += t * (by - ay);
az += t * (bz - az);
}
else if db <= near {
let t = (near - da) / (db - da);
bx = ax + t * (bx - ax);
by = ay + t * (by - ay);
bz = az + t * (bz - az);
}
let (sax, say, da2) = cam.project(ax, ay, az);
let (sbx, sby, db2) = cam.project(bx, by, bz);
q.push_line((da2 + db2) * 0.5 + DEPTH_BIAS, color, sax, say, sbx, sby);
}
#[inline]
pub fn cycle(phase: f32) -> u32 {
let r = (phase.sin() * 127.0 + 128.0) as u32;
let g = ((phase + 2.094).sin() * 127.0 + 128.0) as u32;
let b = ((phase + 4.189).sin() * 127.0 + 128.0) as u32;
(r << 16) | (g << 8) | b
}
pub fn draw_grid(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
cols: usize,
rows: usize,
cw: f32,
ch: f32,
fr: f32,
hue: f32,
) {
let hw = cols as f32 * cw * 0.5;
let hh = rows as f32 * ch * 0.5;
for i in 0..=cols {
let u = i as f32 * cw - hw;
let color = cycle(fr * 0.03 + hue + u * 0.15);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u, -hh);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u, hh);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
for j in 0..=rows {
let v = j as f32 * ch - hh;
let color = cycle(fr * 0.03 + hue + v * 0.15);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, -hw, v);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, hw, v);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
pub fn draw_rings(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
n_rings: usize,
n_sides: usize,
max_r: f32,
twist: f32,
fr: f32,
hue: f32,
) {
let step = max_r / n_rings as f32;
for ring in 0..n_rings {
let r = (ring + 1) as f32 * step;
let rot = fr * 0.012 + ring as f32 * twist;
let color = cycle(fr * 0.025 + hue + ring as f32 * 0.52);
for side in 0..n_sides {
let a0 = rot + side as f32 * TAU / n_sides as f32;
let a1 = rot + (side + 1) as f32 * TAU / n_sides as f32;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r,
a0.sin() * r,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r,
a1.sin() * r,
);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
}
pub fn draw_star(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
n_points: usize,
r_outer: f32,
r_inner: f32,
rot_speed: f32,
fr: f32,
hue: f32,
) {
let total = n_points * 2;
let rot = fr * rot_speed;
let color = cycle(fr * 0.025 + hue);
let mut pu = 0_f32;
let mut pv = 0_f32;
let mut first = true;
for i in 0..=total {
let a = rot + i as f32 * TAU / total as f32;
let r = if i % 2 == 0 { r_outer } else { r_inner };
let u = a.cos() * r;
let v = a.sin() * r;
if !first {
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, pu, pv);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u, v);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
pu = u;
pv = v;
first = false;
}
}
pub fn draw_spiral(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
n_turns: f32,
max_r: f32,
n_steps: usize,
fr: f32,
hue: f32,
) {
let base_rot = fr * 0.008;
let mut prev: Option<(f32, f32, f32)> = None;
for step in 0..=n_steps {
let t = step as f32 / n_steps as f32;
let a = base_rot + t * n_turns * TAU;
let r = t * max_r;
let (wx, wy, wz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, a.cos() * r, a.sin() * r);
let color = cycle(fr * 0.02 + hue + t * 5.0);
if let Some((px, py, pz)) = prev {
push_seg(q, cam, color, px, py, pz, wx, wy, wz);
}
prev = Some((wx, wy, wz));
}
}
pub fn draw_flower(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
radius: f32,
n_sides: usize,
fr: f32,
hue: f32,
) {
draw_ngon(
q, cam, cx, cy, cz, ux, uy, uz, vx, vy, vz, 0.0, 0.0, radius, n_sides, fr, hue,
);
for i in 0..6 {
let a = i as f32 * TAU / 6.0;
let ou = a.cos() * radius;
let ov = a.sin() * radius;
draw_ngon(
q,
cam,
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
ou,
ov,
radius,
n_sides,
fr,
hue + i as f32 * 0.45,
);
}
}
fn draw_ngon(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
ou: f32,
ov: f32,
r: f32,
n_sides: usize,
fr: f32,
hue: f32,
) {
let color = cycle(fr * 0.022 + hue);
let rot = fr * 0.009;
for i in 0..n_sides {
let a0 = rot + i as f32 * TAU / n_sides as f32;
let a1 = rot + (i + 1) as f32 * TAU / n_sides as f32;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
ou + a0.cos() * r,
ov + a0.sin() * r,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
ou + a1.cos() * r,
ov + a1.sin() * r,
);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
pub fn draw_halftone(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
cols: usize,
rows: usize,
cell_w: f32,
cell_h: f32,
density: f32,
fr: f32,
hue: f32,
) {
let hw = cols as f32 * cell_w * 0.5;
let hh = rows as f32 * cell_h * 0.5;
let half = density * 0.5;
for row in 0..rows {
let v_c = (row as f32 + 0.5) * cell_h - hh; for col in 0..cols {
let u_c = (col as f32 + 0.5) * cell_w - hw;
let phase = fr * 0.022 + hue + (col as f32 * 0.31 + row as f32 * 0.19); let color = cycle(phase);
let hu = cell_w * half;
let hv = cell_h * half;
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u_c - hu, v_c - hv);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u_c + hu, v_c + hv);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u_c + hu, v_c - hv);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u_c - hu, v_c + hv);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
}
pub fn draw_tessellated(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
cols: usize,
rows: usize,
cell: f32,
amplitude: f32,
freq: f32,
fr: f32,
hue: f32,
) {
let hw = cols as f32 * cell * 0.5;
let hh = rows as f32 * cell * 0.5;
let time = fr * 0.016;
let vert = |ci: usize, ri: usize| -> (f32, f32) {
let u0 = ci as f32 * cell - hw;
let v0 = ri as f32 * cell - hh;
let d = amplitude
* cell
* (freq * (u0 * 0.5 + time)).sin()
* (freq * (v0 * 0.5 + time * 0.7)).cos();
(u0 + d, v0 - d)
};
for ri in 0..rows {
for ci in 0..cols {
let (u00, v00) = vert(ci, ri);
let (u10, v10) = vert(ci + 1, ri);
let (u01, v01) = vert(ci, ri + 1);
let (u11, v11) = vert(ci + 1, ri + 1);
let phase = time + hue + (ci as f32 * 0.28 + ri as f32 * 0.37);
let c0 = cycle(phase);
let c1 = cycle(phase + 1.047);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u00, v00);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u10, v10);
let (ex, ey, ez) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u01, v01);
push_seg(q, cam, c0, ax, ay, az, bx, by, bz);
push_seg(q, cam, c0, bx, by, bz, ex, ey, ez);
push_seg(q, cam, c0, ex, ey, ez, ax, ay, az);
let (fx, fy, fz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u11, v11);
push_seg(q, cam, c1, bx, by, bz, fx, fy, fz);
push_seg(q, cam, c1, fx, fy, fz, ex, ey, ez);
}
}
}
pub fn draw_lotus(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
r_inner: f32,
r_outer: f32,
n_petals: usize,
fr: f32,
hue: f32,
) {
let rot = fr * 0.006;
let spread = TAU / (n_petals as f32 * 2.0);
for i in 0..n_petals {
let a_mid = rot + i as f32 * TAU / n_petals as f32;
let a_left = a_mid - spread * 0.55;
let a_right = a_mid + spread * 0.55;
let color = cycle(fr * 0.020 + hue + i as f32 * (TAU / n_petals as f32));
let (alx, aly, alz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_left.cos() * r_inner,
a_left.sin() * r_inner,
);
let (arx, ary, arz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_right.cos() * r_inner,
a_right.sin() * r_inner,
);
let (tipx, tipy, tipz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_mid.cos() * r_outer,
a_mid.sin() * r_outer,
);
push_seg(q, cam, color, alx, aly, alz, tipx, tipy, tipz);
push_seg(q, cam, color, tipx, tipy, tipz, arx, ary, arz);
push_seg(q, cam, color, arx, ary, arz, alx, aly, alz);
}
let hub_color = cycle(fr * 0.020 + hue + 2.1);
for i in 0..24_usize {
let a0 = rot + i as f32 * TAU / 24.0;
let a1 = rot + (i + 1) as f32 * TAU / 24.0;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r_inner,
a0.sin() * r_inner,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r_inner,
a1.sin() * r_inner,
);
push_seg(q, cam, hub_color, ax, ay, az, bx, by, bz);
}
}
pub fn draw_chakra(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
r: f32,
n_spokes: usize,
fr: f32,
hue: f32,
) {
let rot = fr * 0.009;
let r_hub = r * 0.18;
let c_rim = cycle(fr * 0.018 + hue);
for i in 0..36_usize {
let a0 = rot + i as f32 * TAU / 36.0;
let a1 = rot + (i + 1) as f32 * TAU / 36.0;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r,
a0.sin() * r,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r,
a1.sin() * r,
);
push_seg(q, cam, c_rim, ax, ay, az, bx, by, bz);
}
let c_hub = cycle(fr * 0.018 + hue + 1.8);
for i in 0..12_usize {
let a0 = rot + i as f32 * TAU / 12.0;
let a1 = rot + (i + 1) as f32 * TAU / 12.0;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r_hub,
a0.sin() * r_hub,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r_hub,
a1.sin() * r_hub,
);
push_seg(q, cam, c_hub, ax, ay, az, bx, by, bz);
}
for i in 0..n_spokes {
let a_spoke = rot + i as f32 * TAU / n_spokes as f32;
let c_spoke = cycle(fr * 0.018 + hue + i as f32 * 0.72);
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_spoke.cos() * r_hub,
a_spoke.sin() * r_hub,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_spoke.cos() * r,
a_spoke.sin() * r,
);
push_seg(q, cam, c_spoke, ax, ay, az, bx, by, bz);
let a_tick = rot + (i as f32 + 0.5) * TAU / n_spokes as f32;
let r_tick0 = r * 0.87;
let (tx0, ty0, tz0) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_tick.cos() * r_tick0,
a_tick.sin() * r_tick0,
);
let (tx1, ty1, tz1) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_tick.cos() * r,
a_tick.sin() * r,
);
push_seg(q, cam, c_rim, tx0, ty0, tz0, tx1, ty1, tz1);
}
}
pub fn draw_yantra(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
n_layers: usize,
max_r: f32,
fr: f32,
hue: f32,
) {
let rot = fr * 0.003;
for layer in 0..n_layers {
let scale = max_r * (1.0 - layer as f32 * 0.20);
let c_up = cycle(fr * 0.015 + hue + layer as f32 * 0.65);
let c_down = cycle(fr * 0.015 + hue + layer as f32 * 0.65 + 3.14);
draw_equi_tri(
q,
cam,
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
rot + 3.14159 * 0.5, scale,
c_up,
);
draw_equi_tri(
q,
cam,
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
rot + 3.14159 * 0.5 + TAU / 6.0,
scale * 0.92,
c_down,
);
}
let sq = max_r * 1.40;
let sq2 = max_r * 1.18;
let gate = max_r * 0.28;
let c_sq = cycle(fr * 0.015 + hue + 5.5);
draw_bhupura(
q, cam, cx, cy, cz, ux, uy, uz, vx, vy, vz, sq, sq2, gate, c_sq,
);
}
fn draw_equi_tri(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
apex_rot: f32,
r: f32,
color: u32,
) {
for i in 0..3 {
let a0 = apex_rot + i as f32 * TAU / 3.0;
let a1 = apex_rot + (i + 1) as f32 * TAU / 3.0;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r,
a0.sin() * r,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r,
a1.sin() * r,
);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
fn draw_bhupura(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
sq: f32,
sq2: f32,
gate: f32,
color: u32,
) {
let oc = [(-sq, -sq), (sq, -sq), (sq, sq), (-sq, sq)];
for i in 0..4 {
let (u0, v0) = oc[i];
let (u1, v1) = oc[(i + 1) % 4];
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u0, v0);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u1, v1);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
let ic = [(-sq2, -sq2), (sq2, -sq2), (sq2, sq2), (-sq2, sq2)];
for i in 0..4 {
let (u0, v0) = ic[i];
let (u1, v1) = ic[(i + 1) % 4];
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u0, v0);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u1, v1);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
let posts: &[(f32, f32, f32, f32)] = &[
(-gate, -sq2, -gate, -sq), (gate, -sq2, gate, -sq), (-gate, sq2, -gate, sq), (gate, sq2, gate, sq), (-sq, -gate, -sq2, -gate), (-sq, gate, -sq2, gate), (sq2, -gate, sq, -gate), (sq2, gate, sq, gate), ];
for &(u0, v0, u1, v1) in posts {
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u0, v0);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u1, v1);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
pub fn draw_spiked_cog(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
n_teeth: usize,
r_body: f32,
r_spike: f32,
r_hub: f32,
n_spokes: usize,
fr: f32,
hue: f32,
) {
let rot = fr * 0.007;
let arc = TAU / n_teeth as f32;
let half = arc * 0.38;
let c_body = cycle(fr * 0.018 + hue);
let c_spike = cycle(fr * 0.018 + hue + 1.05);
let c_hub = cycle(fr * 0.018 + hue + 2.10);
for i in 0..n_teeth {
let a_mid = rot + i as f32 * arc;
let a_l = a_mid - half;
let a_r = a_mid + half;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_l.cos() * r_body,
a_l.sin() * r_body,
);
let (tx, ty, tz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_mid.cos() * r_spike,
a_mid.sin() * r_spike,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a_r.cos() * r_body,
a_r.sin() * r_body,
);
push_seg(q, cam, c_spike, ax, ay, az, tx, ty, tz);
push_seg(q, cam, c_spike, tx, ty, tz, bx, by, bz);
push_seg(q, cam, c_body, bx, by, bz, ax, ay, az);
}
let sides_body = (n_teeth * 3).max(24);
for i in 0..sides_body {
let a0 = rot + i as f32 * TAU / sides_body as f32;
let a1 = rot + (i + 1) as f32 * TAU / sides_body as f32;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r_body * 0.72,
a0.sin() * r_body * 0.72,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r_body * 0.72,
a1.sin() * r_body * 0.72,
);
push_seg(q, cam, c_body, ax, ay, az, bx, by, bz);
}
let sides_hub = 16usize;
for i in 0..sides_hub {
let a0 = rot + i as f32 * TAU / sides_hub as f32;
let a1 = rot + (i + 1) as f32 * TAU / sides_hub as f32;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r_hub,
a0.sin() * r_hub,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r_hub,
a1.sin() * r_hub,
);
push_seg(q, cam, c_hub, ax, ay, az, bx, by, bz);
}
for i in 0..n_spokes {
let a = rot + i as f32 * TAU / n_spokes as f32;
let c = cycle(fr * 0.018 + hue + i as f32 * (TAU / n_spokes as f32));
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a.cos() * r_hub,
a.sin() * r_hub,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a.cos() * r_body * 0.70,
a.sin() * r_body * 0.70,
);
push_seg(q, cam, c, ax, ay, az, bx, by, bz);
}
}
pub fn draw_torii(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
width: f32,
height: f32,
fr: f32,
hue: f32,
) {
let hw = width * 0.5;
let hh = height * 0.5;
let rot = fr * 0.0003; let overhang = width * 0.14; let nuki_v = hh * 0.55;
let c0 = cycle(fr * 0.012 + hue);
let c1 = cycle(fr * 0.012 + hue + 1.2);
let c2 = cycle(fr * 0.012 + hue + 2.4);
{
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - hw, -hh);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - hw, hh);
push_seg(q, cam, c0, ax, ay, az, bx, by, bz);
}
{
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + hw, -hh);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + hw, hh);
push_seg(q, cam, c0, ax, ay, az, bx, by, bz);
}
{
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - hw, nuki_v);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + hw, nuki_v);
push_seg(q, cam, c1, ax, ay, az, bx, by, bz);
}
let n_seg = 20usize;
let kasagi_v = hh + height * 0.06; let sag = height * 0.04; let mut prev: Option<(f32, f32, f32)> = None;
for i in 0..=n_seg {
let t = i as f32 / n_seg as f32;
let u = rot + (t - 0.5) * (width + overhang * 2.0);
let up = sag * (1.0 - 4.0 * (t - 0.5) * (t - 0.5));
let v = kasagi_v + up;
let (wx, wy, wz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, u, v);
let color = cycle(fr * 0.012 + hue + 2.4 + t * 0.8);
if let Some((px, py, pz)) = prev {
push_seg(q, cam, color, px, py, pz, wx, wy, wz);
}
prev = Some((wx, wy, wz));
}
{
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
rot - hw - overhang,
kasagi_v - height * 0.05,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
rot + hw + overhang,
kasagi_v - height * 0.05,
);
push_seg(q, cam, c2, ax, ay, az, bx, by, bz);
}
{
let sv = hh + height * 0.01;
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - hw, sv);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + hw, sv);
push_seg(q, cam, c1, ax, ay, az, bx, by, bz);
}
}
pub fn draw_pagoda(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
n_tiers: usize,
base_w: f32,
tier_h: f32,
taper: f32,
eave_out: f32,
fr: f32,
hue: f32,
) {
let mut w = base_w;
let mut v0 = -(n_tiers as f32 * tier_h * 0.5); let rot = fr * 0.0002;
for tier in 0..n_tiers {
let tf = tier as f32;
let v1 = v0 + tier_h; let eave = w * eave_out;
let c_wall = cycle(fr * 0.010 + hue + tf * 0.62);
let c_eave = cycle(fr * 0.010 + hue + tf * 0.62 + 1.8);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - w, v0);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - w, v1);
push_seg(q, cam, c_wall, ax, ay, az, bx, by, bz);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + w, v0);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + w, v1);
push_seg(q, cam, c_wall, ax, ay, az, bx, by, bz);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - w, v0);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + w, v0);
push_seg(q, cam, c_wall, ax, ay, az, bx, by, bz);
let e_y = v1 + tier_h * 0.08; let e_tip = e_y + tier_h * 0.06; let ew = w + eave;
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - ew, e_tip);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot, e_y);
push_seg(q, cam, c_eave, ax, ay, az, bx, by, bz);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot, e_y);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + ew, e_tip);
push_seg(q, cam, c_eave, ax, ay, az, bx, by, bz);
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
rot - ew,
e_y - tier_h * 0.03,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
rot + ew,
e_y - tier_h * 0.03,
);
push_seg(q, cam, c_eave, ax, ay, az, bx, by, bz);
v0 = v1;
w *= taper;
}
let spire_h = tier_h * 1.4;
let c_spire = cycle(fr * 0.010 + hue + 4.5);
let (ax, ay, az) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot - w, v0);
let (bx, by, bz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot + w, v0);
let (sx, sy, sz) = p2w(cx, cy, cz, ux, uy, uz, vx, vy, vz, rot, v0 + spire_h);
push_seg(q, cam, c_spire, ax, ay, az, sx, sy, sz);
push_seg(q, cam, c_spire, sx, sy, sz, bx, by, bz);
push_seg(q, cam, c_spire, ax, ay, az, bx, by, bz);
}
static GLYPHS: &[&[(f32, f32, f32, f32)]] = &[
&[
(-0.38, 0.12, 0.38, 0.12), (0.38, 0.12, 0.38, -0.30), (-0.08, -0.30, 0.38, -0.30), (-0.38, 0.12, -0.38, 0.44), (-0.38, 0.44, 0.06, 0.44),
], &[
(-0.30, 0.30, 0.18, 0.30),
(0.18, 0.30, 0.38, 0.00),
(0.38, 0.00, 0.18, -0.30),
(0.18, -0.30, -0.30, -0.30),
(-0.30, -0.30, -0.30, 0.30),
(-0.38, 0.44, 0.02, 0.44),
], &[
(-0.32, 0.00, 0.00, 0.32),
(0.00, 0.32, 0.32, 0.00),
(0.32, 0.00, 0.00, -0.32),
(0.00, -0.32, -0.32, 0.00),
(0.00, -0.32, 0.00, -0.44),
],
&[
(-0.10, 0.44, 0.34, 0.22),
(0.34, 0.22, 0.00, 0.00),
(0.00, 0.00, -0.34, -0.22),
(-0.34, -0.22, 0.10, -0.44),
],
&[
(0.34, 0.32, -0.18, 0.32),
(-0.18, 0.32, -0.36, 0.00),
(-0.36, 0.00, -0.18, -0.30),
(-0.18, -0.30, 0.18, -0.30),
(0.18, -0.30, 0.18, -0.44),
],
&[
(-0.32, 0.30, 0.32, 0.30),
(0.32, 0.30, 0.32, 0.00),
(-0.32, 0.00, 0.32, 0.00),
(-0.32, 0.30, -0.32, 0.00),
(0.00, 0.00, 0.00, -0.44),
(0.00, -0.44, 0.28, -0.44),
],
&[
(-0.26, 0.32, 0.26, 0.32),
(0.26, 0.32, 0.26, 0.02),
(0.26, 0.02, -0.26, 0.02),
(-0.26, 0.02, -0.26, 0.32),
(0.00, 0.02, 0.00, -0.44),
],
&[
(-0.36, 0.32, 0.36, 0.32),
(0.36, 0.32, 0.44, 0.00),
(0.44, 0.00, 0.36, -0.30),
(0.36, -0.30, 0.00, -0.44),
],
];
pub fn draw_letter_rain(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
n_cols: usize,
n_visible: usize,
col_w: f32,
row_h: f32,
speed: f32,
fr: f32,
hue: f32,
) {
let half_w = (n_cols as f32 * col_w) * 0.5;
let half_h = (n_visible as f32 * row_h) * 0.5;
let total_h = n_visible as f32 * row_h;
let lw = col_w * 0.36; let lh = row_h * 0.38;
for ci in 0..n_cols {
let u = ci as f32 * col_w - half_w + col_w * 0.5;
let speed_k = speed * (1.0 + 0.45 * (ci as f32 * 1.618_f32).sin());
let phase_k = ci as f32 * 2.73;
let hex_offset = if ci % 2 == 1 { row_h * 0.5 } else { 0.0 };
let scroll = (fr * speed_k + phase_k * 11.3 + hex_offset).rem_euclid(total_h);
for ri in 0..n_visible {
let v_from_top = (ri as f32 * row_h + scroll).rem_euclid(total_h);
let v = half_h - v_from_top;
if v < -half_h - row_h {
continue;
}
let brightness = 1.0 - v_from_top / total_h;
if brightness < 0.07 {
continue;
}
let time_slot = (fr * 0.028 + phase_k) as usize;
let glyph_idx = ci
.wrapping_mul(7)
.wrapping_add(ri.wrapping_mul(13))
.wrapping_add(time_slot)
% GLYPHS.len();
let color = cycle_dark(
fr * 0.018 + hue + ci as f32 * 0.33 + brightness * 1.8,
brightness,
);
for &(du0, dv0, du1, dv1) in GLYPHS[glyph_idx] {
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
u + du0 * lw,
v + dv0 * lh,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
u + du1 * lw,
v + dv1 * lh,
);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
}
}
#[inline]
fn cycle_dark(phase: f32, brightness: f32) -> u32 {
let b = brightness.clamp(0.0, 1.0);
let r = ((phase.sin() * 127.0 + 128.0) * b) as u32;
let g = (((phase + 2.094).sin() * 127.0 + 128.0) * b) as u32;
let b_ch = (((phase + 4.189).sin() * 127.0 + 128.0) * b) as u32;
(r.min(255) << 16) | (g.min(255) << 8) | b_ch.min(255)
}
pub fn draw_hyperbolic_uv(
q: &mut DepthQueue,
cam: &Camera3D,
cx: f32,
cy: f32,
cz: f32,
ux: f32,
uy: f32,
uz: f32,
vx: f32,
vy: f32,
vz: f32,
max_r: f32,
n_circles: usize,
n_rays: usize,
fr: f32,
hue: f32,
) {
let rot = fr * 0.004;
let c_ramp = 2.8_f32;
for k in 1..=n_circles {
let t = k as f32 / (n_circles + 1) as f32;
let r = max_r * (c_ramp * t).tanh() / c_ramp.tanh();
let color = cycle(fr * 0.014 + hue + k as f32 * 0.35);
let n_seg = 48usize;
for i in 0..n_seg {
let a0 = rot + i as f32 * TAU / n_seg as f32;
let a1 = rot + (i + 1) as f32 * TAU / n_seg as f32;
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r,
a0.sin() * r,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r,
a1.sin() * r,
);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
let wobble = 0.07_f32;
for k in 0..n_rays {
let base_angle = rot * 0.5 + k as f32 * TAU / n_rays as f32;
let color = cycle(fr * 0.014 + hue + 1.8 + k as f32 * TAU / n_rays as f32);
let n_seg = 18usize;
for i in 0..n_seg {
let t0 = i as f32 / n_seg as f32;
let t1 = (i + 1) as f32 / n_seg as f32;
let r0 = max_r * (c_ramp * t0).tanh() / c_ramp.tanh();
let r1 = max_r * (c_ramp * t1).tanh() / c_ramp.tanh();
let a0 = base_angle + wobble * (t0 * TAU).sin();
let a1 = base_angle + wobble * (t1 * TAU).sin();
let (ax, ay, az) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a0.cos() * r0,
a0.sin() * r0,
);
let (bx, by, bz) = p2w(
cx,
cy,
cz,
ux,
uy,
uz,
vx,
vy,
vz,
a1.cos() * r1,
a1.sin() * r1,
);
push_seg(q, cam, color, ax, ay, az, bx, by, bz);
}
}
}