use crate::kv6::Kv6;
pub const SPRITE_FLAG_NO_SHADING: u32 = 1 << 0;
pub const SPRITE_FLAG_KFA: u32 = 1 << 1;
pub const SPRITE_FLAG_INVISIBLE: u32 = 1 << 2;
pub const SPRITE_FLAG_NO_Z: u32 = 1 << 3;
pub const SPRITE_FLAG_NO_SHADOW_CAST: u32 = 1 << 4;
pub const SPRITE_FLAG_NO_SHADOW_RECEIVE: u32 = 1 << 5;
#[derive(Debug, Clone)]
pub struct Sprite {
pub kv6: Kv6,
pub p: [f32; 3],
pub s: [f32; 3],
pub h: [f32; 3],
pub f: [f32; 3],
pub flags: u32,
pub material: u8,
pub alpha_mul: u8,
pub material_map: Vec<(u32, u8)>,
}
impl Sprite {
#[must_use]
pub fn axis_aligned(kv6: Kv6, pos: [f32; 3]) -> Self {
Self {
kv6,
p: pos,
s: [1.0, 0.0, 0.0],
h: [0.0, 1.0, 0.0],
f: [0.0, 0.0, 1.0],
flags: 0,
material: 0,
alpha_mul: 255,
material_map: Vec::new(),
}
}
#[must_use]
pub fn casts_shadow(&self) -> bool {
self.flags & SPRITE_FLAG_NO_SHADOW_CAST == 0
}
#[must_use]
pub fn receives_shadow(&self) -> bool {
self.flags & SPRITE_FLAG_NO_SHADOW_RECEIVE == 0
}
#[must_use]
pub fn with_casts_shadow(mut self, casts: bool) -> Self {
if casts {
self.flags &= !SPRITE_FLAG_NO_SHADOW_CAST;
} else {
self.flags |= SPRITE_FLAG_NO_SHADOW_CAST;
}
self
}
#[must_use]
pub fn with_receives_shadow(mut self, receives: bool) -> Self {
if receives {
self.flags &= !SPRITE_FLAG_NO_SHADOW_RECEIVE;
} else {
self.flags |= SPRITE_FLAG_NO_SHADOW_RECEIVE;
}
self
}
pub fn carve_sphere_with_colfunc<S, C>(
&mut self,
centre: [i32; 3],
radius: u32,
solid: S,
colfunc: C,
) where
S: Fn(i32, i32, i32) -> bool,
C: Fn(i32, i32, i32) -> u32,
{
self.kv6
.carve_sphere_with_colfunc(centre, radius, solid, colfunc);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sprite_shadow_flags_default_on_and_toggle() {
let kv6 = Kv6::from_fn_shaded(2, 2, 2, |_, _, _| Some(0x8033_4455));
let s = Sprite::axis_aligned(kv6, [0.0; 3]);
assert!(s.casts_shadow() && s.receives_shadow());
let no_cast = s.clone().with_casts_shadow(false);
assert!(!no_cast.casts_shadow() && no_cast.receives_shadow());
assert_eq!(
no_cast.flags & SPRITE_FLAG_NO_SHADOW_CAST,
SPRITE_FLAG_NO_SHADOW_CAST
);
let no_recv = s.clone().with_receives_shadow(false);
assert!(no_recv.casts_shadow() && !no_recv.receives_shadow());
let neither = s
.clone()
.with_casts_shadow(false)
.with_receives_shadow(false);
assert!(!neither.casts_shadow() && !neither.receives_shadow());
let back = neither.with_casts_shadow(true);
assert!(back.casts_shadow() && !back.receives_shadow());
}
#[test]
fn carve_sphere_delegates_to_kv6_and_leaves_pose() {
const BASE: u32 = 0x8033_4455;
let kv6 = Kv6::from_fn_shaded(16, 16, 16, |_, _, _| Some(BASE));
let mut sprite = Sprite::axis_aligned(kv6, [10.0, 20.0, 30.0]);
let before = sprite.kv6.voxels.len();
sprite.carve_sphere_with_colfunc([8, 8, 8], 4, |_, _, _| true, |_, _, _| 0x8000_FF00);
assert_ne!(sprite.kv6.voxels.len(), before);
assert_eq!(sprite.p, [10.0, 20.0, 30.0]);
assert_eq!(sprite.s, [1.0, 0.0, 0.0]);
}
}