#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_precision_loss,
clippy::similar_names,
clippy::too_many_arguments,
clippy::too_many_lines,
clippy::cast_ptr_alignment, // _mm_loadl_epi64 / _mm_storeu_si128 are intentionally unaligned
clippy::doc_markdown,
clippy::no_effect_underscore_binding, // SSE intrinsic side-effect-only stores
clippy::no_effect, // the discarded pmaddwd intermediate
clippy::ref_as_ptr,
clippy::float_cmp_const,
clippy::float_cmp,
)]
use roxlap_formats::kv6::{Kv6, Voxel};
use roxlap_formats::sprite::{Sprite, SPRITE_FLAG_INVISIBLE, SPRITE_FLAG_KFA, SPRITE_FLAG_NO_Z};
use crate::camera_math::CameraState;
use crate::engine::{Engine, LightSrc, DEFAULT_KV6COL};
use crate::equivec::iunivec;
use crate::fixed::ftol;
use crate::opticast::OpticastSettings;
use crate::ptfaces16::PTFACES16;
const MAX_LIGHTS: usize = 16;
pub(crate) const KV6_MIPFACTOR_DEFAULT: i32 = 128;
#[derive(Debug, Clone)]
#[allow(dead_code)] pub(crate) struct Kv6DrawSetup<'a> {
pub kv: &'a Kv6,
pub ts: [f32; 3],
pub th: [f32; 3],
pub tf: [f32; 3],
pub mip: u32,
}
pub(crate) fn kv6_draw_prepare<'a>(
sprite: &'a Sprite,
cam: &CameraState,
) -> Option<Kv6DrawSetup<'a>> {
let kv = &sprite.kv6;
let dx = sprite.p[0] - cam.pos[0];
let dy = sprite.p[1] - cam.pos[1];
let dz = sprite.p[2] - cam.pos[2];
let dist_estimate = ftol(dx * cam.forward[0] + dy * cam.forward[1] + dz * cam.forward[2]);
let _ = (dist_estimate, KV6_MIPFACTOR_DEFAULT);
let mip = 0u32;
let ts = sprite.s;
let th = sprite.h;
let tf = sprite.f;
#[allow(clippy::cast_precision_loss)]
let half_x = kv.xsiz as f32 * 0.5;
#[allow(clippy::cast_precision_loss)]
let half_y = kv.ysiz as f32 * 0.5;
#[allow(clippy::cast_precision_loss)]
let half_z = kv.zsiz as f32 * 0.5;
let off_x = half_x - kv.xpiv;
let off_y = half_y - kv.ypiv;
let off_z = half_z - kv.zpiv;
let npos = [
off_x * ts[0] + off_y * th[0] + off_z * tf[0] + dx,
off_x * ts[1] + off_y * th[1] + off_z * tf[1] + dy,
off_x * ts[2] + off_y * th[2] + off_z * tf[2] + dz,
];
let nstr = [ts[0] * half_x, ts[1] * half_x, ts[2] * half_x];
let nhei = [th[0] * half_y, th[1] * half_y, th[2] * half_y];
let nfor = [tf[0] * half_z, tf[1] * half_z, tf[2] * half_z];
for n in &cam.nor {
let proj_str = (nstr[0] * n[0] + nstr[1] * n[1] + nstr[2] * n[2]).abs();
let proj_hei = (nhei[0] * n[0] + nhei[1] * n[1] + nhei[2] * n[2]).abs();
let proj_for = (nfor[0] * n[0] + nfor[1] * n[1] + nfor[2] * n[2]).abs();
let proj_pos = npos[0] * n[0] + npos[1] * n[1] + npos[2] * n[2];
if proj_str + proj_hei + proj_for + proj_pos < 0.0 {
return None;
}
}
Some(Kv6DrawSetup {
kv,
ts,
th,
tf,
mip,
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn mat2(
a_s: [f32; 3],
a_h: [f32; 3],
a_f: [f32; 3],
a_o: [f32; 3],
b_s: [f32; 3],
b_h: [f32; 3],
b_f: [f32; 3],
b_o: [f32; 3],
) -> ([f32; 3], [f32; 3], [f32; 3], [f32; 3]) {
let c_s = [
a_s[0] * b_s[0] + a_h[0] * b_s[1] + a_f[0] * b_s[2],
a_s[1] * b_s[0] + a_h[1] * b_s[1] + a_f[1] * b_s[2],
a_s[2] * b_s[0] + a_h[2] * b_s[1] + a_f[2] * b_s[2],
];
let c_h = [
a_s[0] * b_h[0] + a_h[0] * b_h[1] + a_f[0] * b_h[2],
a_s[1] * b_h[0] + a_h[1] * b_h[1] + a_f[1] * b_h[2],
a_s[2] * b_h[0] + a_h[2] * b_h[1] + a_f[2] * b_h[2],
];
let c_f = [
a_s[0] * b_f[0] + a_h[0] * b_f[1] + a_f[0] * b_f[2],
a_s[1] * b_f[0] + a_h[1] * b_f[1] + a_f[1] * b_f[2],
a_s[2] * b_f[0] + a_h[2] * b_f[1] + a_f[2] * b_f[2],
];
let c_o = [
a_s[0] * b_o[0] + a_h[0] * b_o[1] + a_f[0] * b_o[2] + a_o[0],
a_s[1] * b_o[0] + a_h[1] * b_o[1] + a_f[1] * b_o[2] + a_o[1],
a_s[2] * b_o[0] + a_h[2] * b_o[1] + a_f[2] * b_o[2] + a_o[2],
];
(c_s, c_h, c_f, c_o)
}
#[inline]
fn lbound(a: i32, b: i32, c: i32) -> i32 {
a.clamp(b, c)
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub(crate) struct Kv6IterState<'a> {
pub kv: &'a Kv6,
pub inx: i32,
pub iny: i32,
pub inz: i32,
pub nxplanemin: i32,
pub nxplanemax: i32,
}
#[derive(Debug, Clone)]
pub(crate) struct Kv6FullState<'a> {
pub iter: Kv6IterState<'a>,
pub cadd4: [[f32; 4]; 8],
pub ztab4_per_z: Vec<[f32; 4]>,
pub r1_initial: [f32; 4],
pub r2: [f32; 4],
pub scisdist: f32,
#[allow(dead_code)]
pub qsum0: [i16; 4],
#[allow(dead_code)]
pub qsum1: [i16; 4],
#[allow(dead_code)]
pub qbplbpp: [i16; 4],
pub kv6colmul: Box<[u64; 256]>,
pub kv6coladd: u64,
}
#[derive(Clone, Copy, Debug)]
pub struct DrawTarget<'a> {
fb_ptr: *mut u32,
fb_len: usize,
zb_ptr: *mut f32,
zb_len: usize,
pub pitch_pixels: usize,
pub width: u32,
pub height: u32,
_marker: std::marker::PhantomData<&'a mut [u32]>,
}
unsafe impl Send for DrawTarget<'_> {}
unsafe impl Sync for DrawTarget<'_> {}
impl<'a> DrawTarget<'a> {
#[must_use]
pub fn new(
framebuffer: &'a mut [u32],
zbuffer: &'a mut [f32],
pitch_pixels: usize,
width: u32,
height: u32,
) -> Self {
Self {
fb_ptr: framebuffer.as_mut_ptr(),
fb_len: framebuffer.len(),
zb_ptr: zbuffer.as_mut_ptr(),
zb_len: zbuffer.len(),
pitch_pixels,
width,
height,
_marker: std::marker::PhantomData,
}
}
#[inline]
pub unsafe fn fb_write(self, idx: usize, color: u32) {
debug_assert!(idx < self.fb_len, "fb idx {} >= len {}", idx, self.fb_len);
unsafe { self.fb_ptr.add(idx).write(color) };
}
#[inline]
#[must_use]
pub unsafe fn fb_read(self, idx: usize) -> u32 {
debug_assert!(idx < self.fb_len, "fb idx {} >= len {}", idx, self.fb_len);
unsafe { self.fb_ptr.add(idx).read() }
}
#[inline]
pub unsafe fn z_test_write(self, idx: usize, color: u32, z: f32) -> bool {
debug_assert!(idx < self.fb_len, "fb idx {} >= len {}", idx, self.fb_len);
debug_assert!(idx < self.zb_len, "zb idx {} >= len {}", idx, self.zb_len);
unsafe {
let zp = self.zb_ptr.add(idx);
let cur_z = zp.read();
if z < cur_z {
zp.write(z);
self.fb_ptr.add(idx).write(color);
true
} else {
false
}
}
}
}
#[inline]
fn vec4_add(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2], a[3] + b[3]]
}
#[inline]
fn vec4_sub(a: [f32; 4], b: [f32; 4]) -> [f32; 4] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2], a[3] - b[3]]
}
#[inline]
fn vec4_scale(a: [f32; 4], s: f32) -> [f32; 4] {
[a[0] * s, a[1] * s, a[2] * s, a[3] * s]
}
#[derive(Debug, Clone, Copy)]
pub struct SpriteLighting<'a> {
pub kv6col: u32,
pub lightmode: u32,
pub lights: &'a [LightSrc],
}
impl<'a> SpriteLighting<'a> {
#[must_use]
pub fn from_engine(engine: &'a Engine) -> Self {
Self {
kv6col: engine.kv6col(),
lightmode: engine.lightmode(),
lights: engine.lights(),
}
}
}
impl SpriteLighting<'static> {
#[must_use]
pub fn default_oracle() -> Self {
Self {
kv6col: DEFAULT_KV6COL,
lightmode: 0,
lights: &[],
}
}
}
fn update_reflects(sprite: &Sprite, lighting: &SpriteLighting<'_>) -> (Box<[u64; 256]>, u64) {
let fogmul_lo: u32 = 0;
let kv6coladd: u64 = 0;
let kv6col = lighting.kv6col;
let g_pre = ((((fogmul_lo & 0x7fff) ^ 0x7fff) as i32) as f32) * (16.0 * 8.0 / 65536.0);
let mut kv6colmul = Box::new([0u64; 256]);
if lighting.lightmode < 2 {
let tp_x = sprite.s[0] + sprite.h[0] + sprite.f[0];
let tp_y = sprite.s[1] + sprite.h[1] + sprite.f[1];
let tp_z = sprite.s[2] + sprite.h[2] + sprite.f[2];
let f0 = 64.0_f32 / (tp_x * tp_x + tp_y * tp_y + tp_z * tp_z).sqrt();
let lo16 = kv6col & 0xffff;
let mid24 = kv6col & 0x00ff_ff00;
let is_grey = ((lo16 << 8) ^ mid24) == 0;
if is_grey {
let g = g_pre * (((kv6col & 0xff) as f32) / 256.0);
let f = f0 * g;
let l0 = (tp_x * f) as i16; let l1 = (tp_y * f) as i16;
let l2 = (tp_z * f) as i16;
let l3 = (g * 128.0) as i16;
let iu = iunivec();
for k in 0..256 {
let w = dot_iunivec_i16x4(iu[k], [l0, l1, l2, l3]);
let w64 = u64::from(w);
kv6colmul[k] = w64 | (w64 << 16) | (w64 << 32) | (w64 << 48);
}
} else {
let f = f0 * g_pre;
let l0 = (tp_x * f) as i16;
let l1 = (tp_y * f) as i16;
let l2 = (tp_z * f) as i16;
let l3 = (g_pre * 128.0) as i16;
let m = kv6col_channel_mods(kv6col);
let iu = iunivec();
for k in 0..256 {
let w = dot_iunivec_i16x4(iu[k], [l0, l1, l2, l3]);
kv6colmul[k] = pack_modulated_word(w, m);
}
}
} else {
let m = kv6col_channel_mods(kv6col);
build_kv6colmul_lightmode2(sprite, lighting.lights, &mut kv6colmul, fogmul_lo, m);
}
(kv6colmul, kv6coladd)
}
#[inline]
fn dot_iunivec_i16x4(u: [i16; 4], l: [i16; 4]) -> u16 {
let u0 = i32::from(u[0]);
let u1 = i32::from(u[1]);
let u2 = i32::from(u[2]);
let u3 = i32::from(u[3]);
let lo = (u0.wrapping_mul(l[0].into())) as u32;
let lo = lo.wrapping_add((u1.wrapping_mul(l[1].into())) as u32);
let hi = (u2.wrapping_mul(l[2].into())) as u32;
let hi = hi.wrapping_add((u3.wrapping_mul(l[3].into())) as u32);
((lo.wrapping_add(hi)) >> 16) as u16
}
#[inline]
fn kv6col_channel_mods(kv6col: u32) -> [u16; 4] {
[
((kv6col & 0xff) << 8) as u16,
(((kv6col >> 8) & 0xff) << 8) as u16,
(((kv6col >> 16) & 0xff) << 8) as u16,
(((kv6col >> 24) & 0xff) << 8) as u16,
]
}
#[inline]
fn pack_modulated_word(w_dot: u16, m: [u16; 4]) -> u64 {
let w = u32::from(w_dot);
let w0 = ((w * u32::from(m[0])) >> 16) as u16;
let w1 = ((w * u32::from(m[1])) >> 16) as u16;
let w2 = ((w * u32::from(m[2])) >> 16) as u16;
let w3 = ((w * u32::from(m[3])) >> 16) as u16;
u64::from(w0) | (u64::from(w1) << 16) | (u64::from(w2) << 32) | (u64::from(w3) << 48)
}
fn build_kv6colmul_lightmode2(
sprite: &Sprite,
lights: &[LightSrc],
kv6colmul: &mut [u64; 256],
fogmul_lo: u32,
m: [u16; 4],
) {
let sprs = normalise(sprite.s);
let sprh = normalise(sprite.h);
let sprf = normalise(sprite.f);
let hh_initial = ((((fogmul_lo & 0x7fff) ^ 0x7fff) as i32) as f32) * (2.0 / 65536.0);
let mut lightlist: [[i16; 4]; MAX_LIGHTS + 1] = [[0; 4]; MAX_LIGHTS + 1];
let mut lightcnt: usize = 0;
for light in lights.iter().rev() {
if lightcnt >= MAX_LIGHTS {
break;
}
let fx = light.pos[0] - sprite.p[0];
let fy = light.pos[1] - sprite.p[1];
let fz = light.pos[2] - sprite.p[2];
let gg = fx * fx + fy * fy + fz * fz;
let ff = light.r2;
if gg >= ff || gg <= 0.0 {
continue;
}
let f = ff.sqrt();
let g = gg.sqrt();
let mut h = (f * ff - g * gg) / (f * ff * g * gg) * light.sc * 16.0;
if g * h > 4096.0 {
h = 4096.0 / g; }
h *= hh_initial;
let l0 = (fx * sprs[0] + fy * sprs[1] + fz * sprs[2]) * h;
let l1 = (fx * sprh[0] + fy * sprh[1] + fz * sprh[2]) * h;
let l2 = (fx * sprf[0] + fy * sprf[1] + fz * sprf[2]) * h;
lightlist[lightcnt] = [l0 as i16, l1 as i16, l2 as i16, 0];
lightcnt += 1;
}
let amb_fx = 0.0_f32;
let amb_fy = 0.5_f32;
let amb_fz = 1.0_f32;
let hh = hh_initial * (16.0 * 16.0 * 8.0 / 2.0);
let al0 = (sprs[0] * amb_fx + sprs[1] * amb_fy + sprs[2] * amb_fz) * hh;
let al1 = (sprh[0] * amb_fx + sprh[1] * amb_fy + sprh[2] * amb_fz) * hh;
let al2 = (sprf[0] * amb_fx + sprf[1] * amb_fy + sprf[2] * amb_fz) * hh;
let al3 = hh * (48.0 / 16.0);
lightlist[lightcnt] = [al0 as i16, al1 as i16, al2 as i16, al3 as i16];
let iu = iunivec();
for idx in 0..256 {
let u = iu[idx];
let u0 = i32::from(u[0]);
let u1 = i32::from(u[1]);
let u2 = i32::from(u[2]);
let u3 = i32::from(u[3]);
let amb = lightlist[lightcnt];
let base_lo = (u0.wrapping_mul(i32::from(amb[0]))) as u32;
let base_lo = base_lo.wrapping_add((u1.wrapping_mul(i32::from(amb[1]))) as u32);
let base_hi = (u2.wrapping_mul(i32::from(amb[2]))) as u32;
let base_hi = base_hi.wrapping_add((u3.wrapping_mul(i32::from(amb[3]))) as u32);
let mut base = base_lo.wrapping_add(base_hi);
for k in (0..lightcnt).rev() {
let l = lightlist[k];
let klo = (u0.wrapping_mul(i32::from(l[0]))) as u32;
let klo = klo.wrapping_add((u1.wrapping_mul(i32::from(l[1]))) as u32);
let khi = (u2.wrapping_mul(i32::from(l[2]))) as u32;
let khi = khi.wrapping_add((u3.wrapping_mul(i32::from(l[3]))) as u32);
let dot = klo.wrapping_add(khi);
let lo16 = (dot & 0xffff) as i16;
let hi16 = ((dot >> 16) & 0xffff) as i16;
let lo16c: u16 = if lo16 < 0 { lo16 as u16 } else { 0 };
let hi16c: u16 = if hi16 < 0 { hi16 as u16 } else { 0 };
let sub = (u32::from(hi16c) << 16) | u32::from(lo16c);
base = base.wrapping_sub(sub);
}
let w_dot = (base >> 16) as u16;
kv6colmul[idx] = pack_modulated_word(w_dot, m);
}
}
#[inline]
fn normalise(v: [f32; 3]) -> [f32; 3] {
let len_sq = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
if len_sq <= 0.0 {
return v;
}
let inv = 1.0 / len_sq.sqrt();
[v[0] * inv, v[1] * inv, v[2] * inv]
}
pub(crate) fn kv6_compute_full_state<'a>(
setup: &Kv6DrawSetup<'a>,
sprite: &Sprite,
lighting: &SpriteLighting<'_>,
cam: &CameraState,
settings: &OpticastSettings,
fb_width: u32,
fb_height: u32,
fb_pitch_pixels: usize,
) -> Kv6FullState<'a> {
let sprite_pos = sprite.p;
let kv = setup.kv;
let (nstr, mut nhei, mut nfor, mut npos) = mat2(
cam.xs, cam.ys, cam.zs, cam.add, setup.ts, setup.th, setup.tf, sprite_pos,
);
npos[0] -= kv.xpiv * nstr[0] + kv.ypiv * nhei[0] + kv.zpiv * nfor[0];
npos[1] -= kv.xpiv * nstr[1] + kv.ypiv * nhei[1] + kv.zpiv * nfor[1];
npos[2] -= kv.xpiv * nstr[2] + kv.ypiv * nhei[2] + kv.zpiv * nfor[2];
let tp = [
nhei[1] * nfor[2] - nfor[1] * nhei[2],
nfor[1] * nstr[2] - nstr[1] * nfor[2],
nstr[1] * nhei[2] - nhei[1] * nstr[2],
];
let det = nstr[0] * tp[0] + nhei[0] * tp[1] + nfor[0] * tp[2];
let (raw_inx, raw_iny, raw_inz) = if det.to_bits() & 0x7fff_ffff != 0 {
let f_inv = -1.0 / det;
let tp2 = [
npos[1] * nfor[2] - nfor[1] * npos[2],
nhei[1] * npos[2] - npos[1] * nhei[2],
npos[1] * nstr[2] - nstr[1] * npos[2],
];
(
ftol((npos[0] * tp[0] - nhei[0] * tp2[0] - nfor[0] * tp2[1]) * f_inv),
ftol((npos[0] * tp[1] + nstr[0] * tp2[0] - nfor[0] * tp2[2]) * f_inv),
ftol((npos[0] * tp[2] + nstr[0] * tp2[1] + nhei[0] * tp2[2]) * f_inv),
)
} else {
(-1, -1, -1)
};
let xsiz_i = kv.xsiz as i32;
let ysiz_i = kv.ysiz as i32;
let zsiz_i = kv.zsiz as i32;
let iter = Kv6IterState {
kv,
inx: lbound(raw_inx, -1, xsiz_i),
iny: lbound(raw_iny, -1, ysiz_i),
inz: lbound(raw_inz, -1, zsiz_i),
nxplanemin: 0,
nxplanemax: i32::MAX,
};
let swap_x = nhei[0];
nhei[0] = nfor[0];
nfor[0] = -swap_x;
let swap_y = nhei[1];
nhei[1] = nfor[1];
nfor[1] = -swap_y;
let swap_z = nhei[2];
nhei[2] = nfor[2];
nfor[2] = -swap_z;
let xres_i = settings.xres as i32;
let yres_i = settings.yres as i32;
let hx_i = ftol(settings.hx);
let hy_i = ftol(settings.hy);
let qsum0_x = (0x7fff - (xres_i - hx_i)) as i16;
let qsum0_y = (0x7fff - (yres_i - hy_i)) as i16;
let qsum0 = [qsum0_x, qsum0_y, qsum0_x, qsum0_y];
let mut scisdist = 0.0f32;
if (nstr[2].to_bits() as i32) < 0 {
scisdist -= nstr[2];
}
if (nhei[2].to_bits() as i32) < 0 {
scisdist -= nhei[2];
}
if (nfor[2].to_bits() as i32) < 0 {
scisdist -= nfor[2];
}
let gihz = settings.hz;
let cadd1 = [nstr[0] * gihz, nstr[1] * gihz, nstr[2], nstr[2]];
let cadd2 = [nhei[0] * gihz, nhei[1] * gihz, nhei[2], nhei[2]];
let cadd4_axis = [nfor[0] * gihz, nfor[1] * gihz, nfor[2], nfor[2]];
let cadd3 = vec4_add(cadd1, cadd2);
let cadd5 = vec4_add(cadd1, cadd4_axis);
let cadd6 = vec4_add(cadd2, cadd4_axis);
let cadd7 = vec4_add(cadd3, cadd4_axis);
let cadd4 = [
[0.0; 4], cadd1, cadd2, cadd3, cadd4_axis, cadd5, cadd6, cadd7,
];
let zsiz = kv.zsiz as usize;
let mut ztab4_per_z = Vec::with_capacity(zsiz);
if zsiz > 0 {
ztab4_per_z.push([0.0f32; 4]);
for i in 1..zsiz {
let prev = ztab4_per_z[i - 1];
ztab4_per_z.push(vec4_add(prev, cadd4[2]));
}
}
let r1_pre = [npos[0] * gihz, npos[1] * gihz, npos[2], npos[2]];
let r1_initial = vec4_sub(r1_pre, cadd4[4]);
let r2 = vec4_scale(cadd4[4], -(ysiz_i as f32));
let pitch_bytes = (fb_pitch_pixels as i32).saturating_mul(4);
let qsum1_x = 0x7fff_i32 - fb_width as i32;
let qsum1_y = 0x7fff_i32 - fb_height as i32;
let qsum1 = [
qsum1_x as i16,
qsum1_y as i16,
qsum1_x as i16,
qsum1_y as i16,
];
let qbplbpp = [4i16, pitch_bytes as i16, 4, pitch_bytes as i16];
let (kv6colmul, kv6coladd) = update_reflects(sprite, lighting);
Kv6FullState {
iter,
cadd4,
ztab4_per_z,
r1_initial,
r2,
scisdist,
qsum0,
qsum1,
qbplbpp,
kv6colmul,
kv6coladd,
}
}
#[cfg(target_arch = "x86_64")]
#[allow(clippy::trivially_copy_pass_by_ref)] pub(crate) fn drawboundcubesse(
v: &Voxel,
mask: u32,
state: &Kv6FullState<'_>,
r0: [f32; 4],
mm5_tail: &mut u32,
target: &mut DrawTarget<'_>,
) -> u32 {
use core::arch::x86_64::{
__m128, __m128i, _mm_add_epi16, _mm_add_ps, _mm_adds_epi16, _mm_cvtsi128_si32,
_mm_cvtsi32_si128, _mm_cvttps_epi32, _mm_loadl_epi64, _mm_loadu_ps, _mm_madd_epi16,
_mm_max_epi16, _mm_min_epi16, _mm_movehl_ps, _mm_movelh_ps, _mm_mul_ps, _mm_mulhi_epu16,
_mm_packs_epi32, _mm_packus_epi16, _mm_rcp_ps, _mm_setzero_si128, _mm_shufflelo_epi16,
_mm_storeu_ps, _mm_storeu_si128, _mm_subs_epu16, _mm_unpackhi_epi64, _mm_unpacklo_epi32,
_mm_unpacklo_epi8,
};
let effmask = (mask & u32::from(v.vis)) as usize;
if effmask == 0 || effmask >= PTFACES16.len() {
return 0;
}
let face = PTFACES16[effmask];
if face[0] == 0 {
return 0;
}
let z_idx = v.z as usize;
if z_idx >= state.ztab4_per_z.len() {
return 0;
}
let ztep = state.ztab4_per_z[z_idx];
unsafe {
let r0_v = _mm_loadu_ps(r0.as_ptr());
let ztep_v = _mm_loadu_ps(ztep.as_ptr());
let origin_v: __m128 = _mm_add_ps(r0_v, ztep_v);
let mut origin_arr = [0.0f32; 4];
_mm_storeu_ps(origin_arr.as_mut_ptr(), origin_v);
if origin_arr[2] < state.scisdist {
return 0;
}
let project = |off_a: u8, off_b: u8| -> __m128 {
let a = state.cadd4[(off_a >> 4) as usize];
let b = state.cadd4[(off_b >> 4) as usize];
let wva = _mm_add_ps(_mm_loadu_ps(a.as_ptr()), origin_v);
let wvb = _mm_add_ps(_mm_loadu_ps(b.as_ptr()), origin_v);
let wv0 = _mm_movehl_ps(wva, wvb); let wv1 = _mm_movelh_ps(wvb, wva); let wv0_inv = _mm_rcp_ps(wv0);
_mm_mul_ps(wv0_inv, wv1)
};
let pair01 = project(face[1], face[2]);
let pair23 = project(face[3], face[4]);
let p01_i32 = _mm_cvttps_epi32(pair01);
let p23_i32 = _mm_cvttps_epi32(pair23);
let pack_lo = _mm_packs_epi32(p01_i32, p23_i32);
let pack01 = pack_lo;
let pack23 = _mm_unpackhi_epi64(pack_lo, _mm_setzero_si128());
let mut mm_min = _mm_min_epi16(pack01, pack23);
let mut mm_max = _mm_max_epi16(pack01, pack23);
if face[0] != 4 {
let pair45 = project(face[5], face[6]);
let p45_i32 = _mm_cvttps_epi32(pair45);
let pack45 = _mm_packs_epi32(p45_i32, _mm_setzero_si128());
mm_min = _mm_min_epi16(mm_min, pack45);
mm_max = _mm_max_epi16(mm_max, pack45);
}
let mm_min_hi = _mm_shufflelo_epi16(mm_min, 0x0e);
let mm_max_hi = _mm_shufflelo_epi16(mm_max, 0x0e);
let mm_min_red = _mm_min_epi16(mm_min, mm_min_hi);
let mm_max_red = _mm_max_epi16(mm_max, mm_max_hi);
let bounds = _mm_unpacklo_epi32(mm_min_red, mm_max_red);
let qsum0_v = _mm_loadl_epi64(state.qsum0.as_ptr().cast::<__m128i>());
let qsum1_v = _mm_loadl_epi64(state.qsum1.as_ptr().cast::<__m128i>());
let bounds = _mm_adds_epi16(bounds, qsum0_v);
let bounds = _mm_max_epi16(bounds, qsum1_v);
let bounds_hi = _mm_shufflelo_epi16(bounds, 0xee);
let dxdy = _mm_subs_epu16(bounds_hi, bounds);
let dxdy_low = _mm_cvtsi128_si32(dxdy) as u32;
let dx = (dxdy_low & 0xffff) as i32;
if dx == 0 {
return 0;
}
let dy = ((dxdy_low >> 16) as i32) - 1;
if dy < 0 {
return 0;
}
let mut bounds_arr = [0i16; 8];
_mm_storeu_si128(bounds_arr.as_mut_ptr().cast::<__m128i>(), bounds);
let pixel_min_x = i32::from(bounds_arr[0]) - i32::from(state.qsum1[0]);
let pixel_min_y = i32::from(bounds_arr[1]) - i32::from(state.qsum1[1]);
let qbplbpp_v = _mm_loadl_epi64(state.qbplbpp.as_ptr().cast::<__m128i>());
let _ = _mm_madd_epi16(bounds, qbplbpp_v);
let tail_in = *mm5_tail;
let mm5 = _mm_cvtsi32_si128(tail_in as i32);
let col_v = _mm_cvtsi32_si128(v.col as i32);
let mm5 = _mm_unpacklo_epi8(mm5, col_v);
let kvm = state.kv6colmul[v.dir as usize];
let kvm_v = _mm_loadl_epi64(std::ptr::addr_of!(kvm).cast::<__m128i>());
let mm5 = _mm_mulhi_epu16(mm5, kvm_v);
let kva_v = _mm_loadl_epi64(std::ptr::addr_of!(state.kv6coladd).cast::<__m128i>());
let mm5 = _mm_add_epi16(mm5, kva_v);
let mm5 = _mm_packus_epi16(mm5, mm5);
let color = _mm_cvtsi128_si32(mm5) as u32;
*mm5_tail = color;
let z_val = origin_arr[2];
let pitch = target.pitch_pixels;
let x0 = pixel_min_x as usize;
let x_end = x0 + dx as usize;
let mut written: u32 = 0;
for row in 0..=(dy as usize) {
let y = pixel_min_y as usize + row;
let row_start = y * pitch;
for x in x0..x_end {
let idx = row_start + x;
if target.z_test_write(idx, color, z_val) {
written += 1;
}
}
}
written
}
}
#[cfg(not(target_arch = "x86_64"))]
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn drawboundcubesse(
v: &Voxel,
mask: u32,
state: &Kv6FullState<'_>,
r0: [f32; 4],
mm5_tail: &mut u32,
target: &mut DrawTarget<'_>,
) -> u32 {
let effmask = (mask & u32::from(v.vis)) as usize;
if effmask == 0 || effmask >= PTFACES16.len() {
return 0;
}
let face = PTFACES16[effmask];
if face[0] == 0 {
return 0;
}
let z_idx = v.z as usize;
if z_idx >= state.ztab4_per_z.len() {
return 0;
}
let origin = vec4_add(r0, state.ztab4_per_z[z_idx]);
if origin[2] < state.scisdist {
return 0;
}
let hx = (i32::from(state.qsum0[0]) - i32::from(state.qsum1[0])) as f32;
let hy = (i32::from(state.qsum0[1]) - i32::from(state.qsum1[1])) as f32;
let project = |off: u8| -> (f32, f32) {
let wv = vec4_add(state.cadd4[(off >> 4) as usize], origin);
let inv_z = 1.0 / wv[2];
(wv[0] * inv_z + hx, wv[1] * inv_z + hy)
};
let (a0x, a0y) = project(face[1]);
let (a1x, a1y) = project(face[2]);
let (a2x, a2y) = project(face[3]);
let (a3x, a3y) = project(face[4]);
let mut min_x = a0x.min(a1x).min(a2x).min(a3x) as i32;
let mut min_y = a0y.min(a1y).min(a2y).min(a3y) as i32;
let mut max_x = a0x.max(a1x).max(a2x).max(a3x) as i32;
let mut max_y = a0y.max(a1y).max(a2y).max(a3y) as i32;
if face[0] != 4 {
let (a4x, a4y) = project(face[5]);
let (a5x, a5y) = project(face[6]);
min_x = min_x.min(a4x as i32).min(a5x as i32);
min_y = min_y.min(a4y as i32).min(a5y as i32);
max_x = max_x.max(a4x as i32).max(a5x as i32);
max_y = max_y.max(a4y as i32).max(a5y as i32);
}
let fb_w = target.width as i32;
let fb_h = target.height as i32;
min_x = min_x.max(0);
min_y = min_y.max(0);
max_x = max_x.min(fb_w - 1);
max_y = max_y.min(fb_h - 1);
if min_x > max_x || min_y > max_y {
return 0;
}
let t = mm5_tail.to_le_bytes();
let c = v.col.to_le_bytes();
let interleaved: [u16; 4] = [
(u16::from(c[0]) << 8) | u16::from(t[0]),
(u16::from(c[1]) << 8) | u16::from(t[1]),
(u16::from(c[2]) << 8) | u16::from(t[2]),
(u16::from(c[3]) << 8) | u16::from(t[3]),
];
let kvm = state.kv6colmul[v.dir as usize];
let kva = state.kv6coladd;
let mut color_bytes = [0u8; 4];
for i in 0..4 {
let km = ((kvm >> (i * 16)) & 0xffff) as u16;
let ka = ((kva >> (i * 16)) & 0xffff) as u16;
let hi = ((u32::from(interleaved[i]) * u32::from(km)) >> 16) as u16;
let val = hi.wrapping_add(ka) as i16;
color_bytes[i] = val.clamp(0, 255) as u8;
}
let color = u32::from_le_bytes(color_bytes);
*mm5_tail = color;
let z_val = origin[2];
let pitch = target.pitch_pixels;
let mut written: u32 = 0;
for y in min_y..=max_y {
let row_start = y as usize * pitch;
for x in min_x..=max_x {
let idx = row_start + x as usize;
unsafe {
if target.z_test_write(idx, color, z_val) {
written += 1;
}
}
}
}
written
}
fn draw_boundcube_line<F: FnMut(&Voxel, u32, [f32; 4])>(
voxels: &[Voxel],
range_start: usize,
range_end: usize,
inz: i32,
base_mask: u32,
r0: [f32; 4],
callback: &mut F,
) {
if range_end <= range_start {
return;
}
let mut v0 = range_start;
let mut v1_excl = range_end;
while v0 < v1_excl && i32::from(voxels[v0].z) < inz {
callback(&voxels[v0], base_mask | 0x20, r0);
v0 += 1;
}
while v0 < v1_excl && i32::from(voxels[v1_excl - 1].z) > inz {
callback(&voxels[v1_excl - 1], base_mask | 0x10, r0);
v1_excl -= 1;
}
if v0 + 1 == v1_excl {
callback(&voxels[v0], base_mask, r0);
}
}
#[allow(clippy::too_many_lines)]
pub(crate) fn kv6_iterate<F: FnMut(&Voxel, u32, [f32; 4])>(
state: &Kv6FullState<'_>,
mut callback: F,
) {
let kv = state.iter.kv;
let xsiz = kv.xsiz as i32;
let ysiz = kv.ysiz as i32;
let inx = state.iter.inx;
let iny = state.iter.iny;
let inz = state.iter.inz;
let nxplanemin = state.iter.nxplanemin;
let nxplanemax = state.iter.nxplanemax;
let cadd1 = state.cadd4[1];
let cadd_y = state.cadd4[4];
let r2 = state.r2;
let mut xv: usize = 0;
let mut r1 = state.r1_initial;
let mut x: i32 = 0;
while x < inx {
let xu = x as usize;
let xlen = kv.xlen[xu] as usize;
if x < nxplanemin || x >= nxplanemax {
xv += xlen;
r1 = vec4_add(r1, cadd1);
x += 1;
continue;
}
let yv_initial = xv + xlen;
let mut r0 = r1;
let mut xv_local = xv;
let mut y: i32 = 0;
while y < iny {
let yu = y as usize;
let len = kv.ylen[xu][yu] as usize;
let v0 = xv_local;
xv_local += len;
draw_boundcube_line(&kv.voxels, v0, xv_local, inz, 0xa, r0, &mut callback);
r0 = vec4_sub(r0, cadd_y); y += 1;
}
let mut yv_local = yv_initial;
r0 = vec4_add(r1, r2);
r1 = vec4_add(r1, cadd1);
let mut y = ysiz - 1;
while y > iny {
r0 = vec4_add(r0, cadd_y); let yu = y as usize;
let len = kv.ylen[xu][yu] as usize;
let v1_excl = yv_local;
yv_local -= len;
draw_boundcube_line(&kv.voxels, yv_local, v1_excl, inz, 0x6, r0, &mut callback);
y -= 1;
}
if iny >= 0 && (iny as u32) < kv.ysiz {
r0 = vec4_add(r0, cadd_y);
let yu = iny as usize;
let len = kv.ylen[xu][yu] as usize;
let v1_excl = yv_local;
yv_local -= len;
draw_boundcube_line(&kv.voxels, yv_local, v1_excl, inz, 0x2, r0, &mut callback);
}
xv += xlen;
x += 1;
}
let dx_remain = (xsiz - x) as f32;
r1 = vec4_add(r1, vec4_scale(cadd1, dx_remain));
let mut xv2: usize = kv.voxels.len();
let mut x = xsiz - 1;
while x > inx {
let xu = x as usize;
let xlen = kv.xlen[xu] as usize;
if x < nxplanemin || x >= nxplanemax {
xv2 -= xlen;
r1 = vec4_sub(r1, cadd1);
x -= 1;
continue;
}
let yv_initial = xv2 - xlen;
r1 = vec4_sub(r1, cadd1);
let mut r0 = vec4_add(r1, r2);
let mut xv_local = xv2;
let mut y = ysiz - 1;
while y > iny {
r0 = vec4_add(r0, cadd_y);
let yu = y as usize;
let len = kv.ylen[xu][yu] as usize;
let v1_excl = xv_local;
xv_local -= len;
draw_boundcube_line(&kv.voxels, xv_local, v1_excl, inz, 0x5, r0, &mut callback);
y -= 1;
}
let mut yv_local = yv_initial;
r0 = r1;
let mut y: i32 = 0;
while y < iny {
let yu = y as usize;
let len = kv.ylen[xu][yu] as usize;
let v0 = yv_local;
yv_local += len;
draw_boundcube_line(&kv.voxels, v0, yv_local, inz, 0x9, r0, &mut callback);
r0 = vec4_sub(r0, cadd_y);
y += 1;
}
if iny >= 0 && (iny as u32) < kv.ysiz {
let yu = iny as usize;
let len = kv.ylen[xu][yu] as usize;
let v0 = yv_local;
yv_local += len;
draw_boundcube_line(&kv.voxels, v0, yv_local, inz, 0x1, r0, &mut callback);
}
xv2 -= xlen;
x -= 1;
}
if inx >= 0 && (inx as u32) < kv.xsiz {
let xu = inx as usize;
if inx < nxplanemin || inx >= nxplanemax {
return;
}
let xlen = kv.xlen[xu] as usize;
let yv_initial = xv2 - xlen;
r1 = vec4_sub(r1, cadd1);
let mut r0 = vec4_add(r1, r2);
let mut xv_local = xv2;
let mut y = ysiz - 1;
while y > iny {
r0 = vec4_add(r0, cadd_y);
let yu = y as usize;
let len = kv.ylen[xu][yu] as usize;
let v1_excl = xv_local;
xv_local -= len;
draw_boundcube_line(&kv.voxels, xv_local, v1_excl, inz, 0x4, r0, &mut callback);
y -= 1;
}
let mut yv_local = yv_initial;
r0 = r1;
let mut y: i32 = 0;
while y < iny {
let yu = y as usize;
let len = kv.ylen[xu][yu] as usize;
let v0 = yv_local;
yv_local += len;
draw_boundcube_line(&kv.voxels, v0, yv_local, inz, 0x8, r0, &mut callback);
r0 = vec4_sub(r0, cadd_y);
y += 1;
}
if iny >= 0 && (iny as u32) < kv.ysiz {
let yu = iny as usize;
let len = kv.ylen[xu][yu] as usize;
let v0 = yv_local;
yv_local += len;
draw_boundcube_line(&kv.voxels, v0, yv_local, inz, 0x0, r0, &mut callback);
}
}
}
#[allow(clippy::module_name_repetitions)]
pub fn draw_sprites_parallel(
target: DrawTarget<'_>,
cam: &CameraState,
settings: &OpticastSettings,
lighting: &SpriteLighting<'_>,
sprites: &[Sprite],
) -> u32 {
let render_one = |sprite: &Sprite| {
let mut t = target;
draw_sprite(&mut t, cam, settings, lighting, sprite)
};
use rayon::prelude::*;
sprites.par_iter().map(render_one).sum()
}
pub fn draw_sprite(
target: &mut DrawTarget<'_>,
cam: &CameraState,
settings: &OpticastSettings,
lighting: &SpriteLighting<'_>,
sprite: &Sprite,
) -> u32 {
if sprite.flags & SPRITE_FLAG_INVISIBLE != 0 {
return 0;
}
if sprite.flags & SPRITE_FLAG_KFA != 0 {
return 0;
}
if sprite.flags & SPRITE_FLAG_NO_Z != 0 {
return 0;
}
let Some(setup) = kv6_draw_prepare(sprite, cam) else {
return 0;
};
let state = kv6_compute_full_state(
&setup,
sprite,
lighting,
cam,
settings,
target.width,
target.height,
target.pitch_pixels,
);
let mut mm5_tail: u32 = 0;
let mut total_written: u32 = 0;
kv6_iterate(&state, |voxel, mask, r0| {
total_written += drawboundcubesse(voxel, mask, &state, r0, &mut mm5_tail, target);
});
total_written
}
#[cfg(test)]
mod tests {
use super::*;
use crate::camera_math;
use crate::Camera;
use roxlap_formats::kv6::Kv6;
fn empty_kv6() -> Kv6 {
Kv6 {
xsiz: 1,
ysiz: 1,
zsiz: 1,
xpiv: 0.5,
ypiv: 0.5,
zpiv: 0.5,
voxels: Vec::new(),
xlen: vec![0],
ylen: vec![vec![0]],
palette: None,
}
}
fn cube_kv6() -> Kv6 {
Kv6 {
xsiz: 17,
ysiz: 17,
zsiz: 17,
xpiv: 8.5,
ypiv: 8.5,
zpiv: 8.5,
voxels: Vec::new(),
xlen: vec![0; 17],
ylen: vec![vec![0; 17]; 17],
palette: None,
}
}
fn oracle_sprite_front_camera() -> camera_math::CameraState {
let camera = Camera {
pos: [1020.0, 1050.0, 175.0],
right: [0.0, 1.0, 0.0],
down: [0.0, 0.0, 1.0],
forward: [1.0, 0.0, 0.0],
};
camera_math::derive(&camera, 640, 480, 320.0, 240.0, 320.0)
}
fn oracle_settings() -> OpticastSettings {
OpticastSettings::for_oracle_framebuffer(640, 480)
}
fn compute_state_for_test<'a>(
setup: &Kv6DrawSetup<'a>,
sprite: &Sprite,
cam: &camera_math::CameraState,
) -> Kv6FullState<'a> {
let lighting = SpriteLighting::default_oracle();
kv6_compute_full_state(
setup,
sprite,
&lighting,
cam,
&oracle_settings(),
640,
480,
640,
)
}
fn alloc_target() -> (Vec<u32>, Vec<f32>) {
let pixels = 640usize * 480usize;
(vec![0u32; pixels], vec![f32::INFINITY; pixels])
}
fn make_target<'a>(fb: &'a mut [u32], zb: &'a mut [f32]) -> DrawTarget<'a> {
DrawTarget::new(fb, zb, 640, 640, 480)
}
fn bits4(a: [f32; 4]) -> [u32; 4] {
a.map(f32::to_bits)
}
const SPRITE_MELTSPHERE_KV6: &[u8] = include_bytes!("../tests/fixtures/sprite_meltsphere.kv6");
#[test]
fn axis_aligned_sets_identity_basis() {
let bits = |a: [f32; 3]| a.map(f32::to_bits);
let s = Sprite::axis_aligned(empty_kv6(), [10.0, 20.0, 30.0]);
assert_eq!(bits(s.p), bits([10.0, 20.0, 30.0]));
assert_eq!(bits(s.s), bits([1.0, 0.0, 0.0]));
assert_eq!(bits(s.h), bits([0.0, 1.0, 0.0]));
assert_eq!(bits(s.f), bits([0.0, 0.0, 1.0]));
assert_eq!(s.flags, 0);
}
#[test]
fn invisible_flag_skips_dispatch() {
let cam = oracle_sprite_front_camera();
let mut s = Sprite::axis_aligned(cube_kv6(), [1050.0, 1050.0, 175.0]);
s.flags = SPRITE_FLAG_INVISIBLE;
let (mut fb, mut zb) = alloc_target();
let mut target = make_target(&mut fb, &mut zb);
let lighting = SpriteLighting::default_oracle();
assert_eq!(
draw_sprite(&mut target, &cam, &oracle_settings(), &lighting, &s),
0
);
}
#[test]
fn kfa_flag_skips_dispatch() {
let cam = oracle_sprite_front_camera();
let mut s = Sprite::axis_aligned(cube_kv6(), [1050.0, 1050.0, 175.0]);
s.flags = SPRITE_FLAG_KFA;
let (mut fb, mut zb) = alloc_target();
let mut target = make_target(&mut fb, &mut zb);
let lighting = SpriteLighting::default_oracle();
assert_eq!(
draw_sprite(&mut target, &cam, &oracle_settings(), &lighting, &s),
0
);
}
#[test]
fn cull_keeps_oracle_sprite_in_front_of_camera() {
let cam = oracle_sprite_front_camera();
let s = Sprite::axis_aligned(cube_kv6(), [1050.0, 1050.0, 175.0]);
assert!(
kv6_draw_prepare(&s, &cam).is_some(),
"front-of-camera sprite must NOT be culled"
);
}
#[test]
fn cull_removes_sprite_far_behind_camera() {
let cam = oracle_sprite_front_camera();
let s = Sprite::axis_aligned(cube_kv6(), [1020.0 - 500.0, 1050.0, 175.0]);
assert!(
kv6_draw_prepare(&s, &cam).is_none(),
"behind-camera sprite must be culled"
);
}
#[test]
fn cull_removes_sprite_far_to_the_right() {
let cam = oracle_sprite_front_camera();
let s = Sprite::axis_aligned(cube_kv6(), [1050.0, 1050.0 + 200.0, 175.0]);
assert!(
kv6_draw_prepare(&s, &cam).is_none(),
"far-right sprite must be culled"
);
}
#[test]
fn cull_keeps_sprite_at_camera_position() {
let cam = oracle_sprite_front_camera();
let s = Sprite::axis_aligned(cube_kv6(), cam.pos);
assert!(
kv6_draw_prepare(&s, &cam).is_some(),
"sprite at camera position must not be culled"
);
}
#[test]
fn iterate_visits_each_voxel_exactly_once() {
let xsiz: u32 = 3;
let ysiz: u32 = 3;
let zsiz: u32 = 3;
let mut voxels = Vec::new();
let mut xlen = vec![0u32; xsiz as usize];
let mut ylen = vec![vec![0u16; ysiz as usize]; xsiz as usize];
for x in 0..xsiz {
for y in 0..ysiz {
let z = ((x + y) % 3) as u16;
voxels.push(Voxel {
col: 0x0080_0000,
z,
vis: 63,
dir: 0,
});
xlen[x as usize] += 1;
ylen[x as usize][y as usize] = 1;
}
}
let kv = Kv6 {
xsiz,
ysiz,
zsiz,
xpiv: 1.5,
ypiv: 1.5,
zpiv: 1.5,
voxels,
xlen,
ylen,
palette: None,
};
let setup = Kv6DrawSetup {
kv: &kv,
ts: [1.0, 0.0, 0.0],
th: [0.0, 1.0, 0.0],
tf: [0.0, 0.0, 1.0],
mip: 0,
};
let cam = oracle_sprite_front_camera();
let synth_sprite = Sprite::axis_aligned(empty_kv6(), [1050.0, 1050.0, 175.0]);
let state = compute_state_for_test(&setup, &synth_sprite, &cam);
let voxels_ptr = kv.voxels.as_ptr();
let mut visited = vec![0u32; kv.voxels.len()];
let mut total: u32 = 0;
kv6_iterate(&state, |v, _mask, _r0| {
let idx = unsafe { std::ptr::from_ref::<Voxel>(v).offset_from(voxels_ptr) } as usize;
visited[idx] += 1;
total += 1;
});
assert_eq!(total as usize, kv.voxels.len(), "total callback fires");
for (i, &n) in visited.iter().enumerate() {
assert_eq!(n, 1, "voxel {i} visited {n} times (want 1)");
}
}
#[test]
fn iterate_meltsphere_oracle_visits_each_voxel_once() {
let kv = roxlap_formats::kv6::parse(SPRITE_MELTSPHERE_KV6).expect("parse fixture");
assert_eq!(kv.voxels.len(), 401, "fixture voxel count");
let sprite = Sprite::axis_aligned(kv, [1050.0, 1050.0, 175.0]);
let cam = oracle_sprite_front_camera();
let setup = kv6_draw_prepare(&sprite, &cam).expect("oracle sprite must pass cull");
let state = compute_state_for_test(&setup, &sprite, &cam);
let voxels_ptr = sprite.kv6.voxels.as_ptr();
let mut visited = vec![0u32; sprite.kv6.voxels.len()];
let mut total: u32 = 0;
kv6_iterate(&state, |v, _mask, _r0| {
let idx = unsafe { std::ptr::from_ref::<Voxel>(v).offset_from(voxels_ptr) } as usize;
visited[idx] += 1;
total += 1;
});
assert_eq!(total, 401);
let max = visited.iter().copied().max().unwrap();
let min = visited.iter().copied().min().unwrap();
assert_eq!(max, 1, "no voxel may be visited twice");
assert_eq!(min, 1, "no voxel may be skipped");
}
#[test]
fn full_state_basic_invariants() {
let kv = roxlap_formats::kv6::parse(SPRITE_MELTSPHERE_KV6).expect("parse fixture");
let sprite = Sprite::axis_aligned(kv, [1050.0, 1050.0, 175.0]);
let cam = oracle_sprite_front_camera();
let setup = kv6_draw_prepare(&sprite, &cam).expect("cull pass");
let state = compute_state_for_test(&setup, &sprite, &cam);
assert_eq!(bits4(state.ztab4_per_z[0]), bits4([0.0; 4]));
for z in 1..state.ztab4_per_z.len() {
let want = vec4_add(state.ztab4_per_z[z - 1], state.cadd4[2]);
assert_eq!(bits4(state.ztab4_per_z[z]), bits4(want), "ztab4_per_z[{z}]");
}
assert_eq!(
bits4(state.cadd4[3]),
bits4(vec4_add(state.cadd4[1], state.cadd4[2]))
);
assert_eq!(
bits4(state.cadd4[5]),
bits4(vec4_add(state.cadd4[1], state.cadd4[4]))
);
assert_eq!(
bits4(state.cadd4[6]),
bits4(vec4_add(state.cadd4[2], state.cadd4[4]))
);
assert_eq!(
bits4(state.cadd4[7]),
bits4(vec4_add(state.cadd4[3], state.cadd4[4]))
);
assert_eq!(bits4(state.cadd4[0]), bits4([0.0; 4]));
let want_r2 = vec4_scale(state.cadd4[4], -(state.iter.kv.ysiz as f32));
assert_eq!(bits4(state.r2), bits4(want_r2));
}
#[test]
fn drawboundcubesse_culls_invisible_face_mask() {
let v = Voxel {
col: 0,
z: 0,
vis: 0,
dir: 0,
};
let kv = roxlap_formats::kv6::parse(SPRITE_MELTSPHERE_KV6).expect("parse fixture");
let sprite = Sprite::axis_aligned(kv, [1050.0, 1050.0, 175.0]);
let cam = oracle_sprite_front_camera();
let setup = kv6_draw_prepare(&sprite, &cam).expect("cull pass");
let state = compute_state_for_test(&setup, &sprite, &cam);
let (mut fb, mut zb) = alloc_target();
let mut target = make_target(&mut fb, &mut zb);
let mut tail = 0u32;
assert_eq!(
drawboundcubesse(
&v,
0xff,
&state,
[0.0, 0.0, 100.0, 100.0],
&mut tail,
&mut target,
),
0
);
}
#[test]
fn drawboundcubesse_culls_voxel_behind_near_plane() {
let v = Voxel {
col: 0xff,
z: 0,
vis: 0xff,
dir: 0,
};
let kv = roxlap_formats::kv6::parse(SPRITE_MELTSPHERE_KV6).expect("parse fixture");
let sprite = Sprite::axis_aligned(kv, [1050.0, 1050.0, 175.0]);
let cam = oracle_sprite_front_camera();
let setup = kv6_draw_prepare(&sprite, &cam).expect("cull pass");
let state = compute_state_for_test(&setup, &sprite, &cam);
let r0 = [0.0, 0.0, -1000.0, -1000.0];
let (mut fb, mut zb) = alloc_target();
let mut target = make_target(&mut fb, &mut zb);
let mut tail = 0u32;
assert_eq!(
drawboundcubesse(&v, 0xff, &state, r0, &mut tail, &mut target),
0
);
}
#[test]
fn iterate_no_voxels_when_culled() {
let cam = oracle_sprite_front_camera();
let s = Sprite::axis_aligned(cube_kv6(), [1020.0 - 500.0, 1050.0, 175.0]);
assert!(kv6_draw_prepare(&s, &cam).is_none());
}
#[test]
fn draw_sprite_writes_pixels_for_oracle_meltsphere() {
let kv = roxlap_formats::kv6::parse(SPRITE_MELTSPHERE_KV6).expect("parse fixture");
let sprite = Sprite::axis_aligned(kv, [1050.0, 1050.0, 175.0]);
let cam = oracle_sprite_front_camera();
let (mut fb, mut zb) = alloc_target();
let mut target = make_target(&mut fb, &mut zb);
let lighting = SpriteLighting::default_oracle();
let written = draw_sprite(&mut target, &cam, &oracle_settings(), &lighting, &sprite);
assert!(written > 0, "expected some pixels to be written");
assert!(
fb.iter().any(|&p| p != 0),
"expected at least one non-zero framebuffer entry"
);
assert!(
zb.iter().any(|&z| z.is_finite()),
"expected at least one finite zbuffer entry"
);
}
#[test]
fn draw_sprite_returns_zero_for_culled_sprite() {
let cam = oracle_sprite_front_camera();
let s = Sprite::axis_aligned(cube_kv6(), [1020.0 - 500.0, 1050.0, 175.0]);
let (mut fb, mut zb) = alloc_target();
let mut target = make_target(&mut fb, &mut zb);
let lighting = SpriteLighting::default_oracle();
assert_eq!(
draw_sprite(&mut target, &cam, &oracle_settings(), &lighting, &s),
0
);
assert!(fb.iter().all(|&p| p == 0));
}
#[test]
fn update_reflects_nolighta_lanes_match() {
let s = Sprite::axis_aligned(empty_kv6(), [1050.0, 1050.0, 175.0]);
let lighting = SpriteLighting::default_oracle();
let (cm, ca) = update_reflects(&s, &lighting);
assert_eq!(ca, 0, "kv6coladd must be zero (no fog)");
for (k, e) in cm.iter().enumerate() {
let l0 = (e & 0xffff) as u16;
let l1 = ((e >> 16) & 0xffff) as u16;
let l2 = ((e >> 32) & 0xffff) as u16;
let l3 = ((e >> 48) & 0xffff) as u16;
assert_eq!(l0, l1, "kv6colmul[{k}] lane0 != lane1");
assert_eq!(l0, l2, "kv6colmul[{k}] lane0 != lane2");
assert_eq!(l0, l3, "kv6colmul[{k}] lane0 != lane3");
}
}
#[test]
fn update_reflects_nolightb_lanes_diverge_for_tinted_kv6col() {
let s = Sprite::axis_aligned(empty_kv6(), [1050.0, 1050.0, 175.0]);
let lighting = SpriteLighting {
kv6col: 0x0040_8040, lightmode: 0,
lights: &[],
};
let (cm, _) = update_reflects(&s, &lighting);
let mut saw_divergence = false;
for e in cm.iter() {
let l0 = (e & 0xffff) as u16;
let l1 = ((e >> 16) & 0xffff) as u16;
let l2 = ((e >> 32) & 0xffff) as u16;
if l0 != l1 || l0 != l2 {
saw_divergence = true;
break;
}
}
assert!(
saw_divergence,
"non-grey kv6col must produce per-channel divergence in some kv6colmul slot"
);
}
#[test]
fn update_reflects_lightmode2_produces_directional_shading() {
let s = Sprite::axis_aligned(empty_kv6(), [100.0, 100.0, 100.0]);
let lights = [LightSrc {
pos: [110.0, 100.0, 100.0],
r2: 100.0,
sc: 16.0,
}];
let lighting = SpriteLighting {
kv6col: DEFAULT_KV6COL,
lightmode: 2,
lights: &lights,
};
let (cm, _) = update_reflects(&s, &lighting);
let mut min_w = u16::MAX;
let mut max_w = 0u16;
for e in cm.iter() {
let l0 = (e & 0xffff) as u16;
min_w = min_w.min(l0);
max_w = max_w.max(l0);
}
assert!(
max_w > min_w + 16,
"lightmode-2 should produce directional shading: min={min_w} max={max_w}"
);
}
#[test]
fn update_reflects_lightmode2_no_lights_falls_back_to_ambient() {
let s = Sprite::axis_aligned(empty_kv6(), [100.0, 100.0, 100.0]);
let lighting = SpriteLighting {
kv6col: DEFAULT_KV6COL,
lightmode: 2,
lights: &[],
};
let (cm, _) = update_reflects(&s, &lighting);
let any_nonzero = cm.iter().any(|&e| e != 0);
assert!(
any_nonzero,
"lightmode-2 with no lights should still emit ambient shading"
);
}
}