use crate::pipeline::effects::{DepthBias, DepthInterpolationMode, DitherConfig, FogConfig};
use crate::pipeline::shade::retro::palette::PaletteMode;
use crate::pipeline::shade::retro::sky::SkyConfig;
use crate::pipeline::shade::retro::stipple::StippleMode;
use crate::pipeline::shade::retro::texture_lod::TextureMapping;
use crate::pipeline::shade::retro::tint::ScreenTint;
use nalgebra::Point2;
#[derive(Debug, Clone, Copy)]
pub struct RasterState<'a> {
pub width: usize,
pub height: usize,
pub fog: Option<&'a FogConfig>,
pub dither: Option<&'a DitherConfig>,
pub screen_tint: Option<ScreenTint>,
pub palette_mode: PaletteMode,
pub stipple_mode: StippleMode,
pub texture_mapping: TextureMapping,
pub depth_bias: Option<DepthBias>,
pub depth_mode: DepthInterpolationMode,
pub sky: Option<SkyConfig>,
pub camera_dir: [f32; 3],
}
impl Default for RasterState<'_> {
fn default() -> Self {
Self {
width: 0,
height: 0,
fog: None,
dither: None,
screen_tint: None,
palette_mode: PaletteMode::Off,
stipple_mode: StippleMode::Off,
texture_mapping: TextureMapping::PerspectiveCorrect,
depth_bias: None,
depth_mode: DepthInterpolationMode::Exact,
sky: None,
camera_dir: [0.0, 0.0, -1.0],
}
}
}
impl<'a> RasterState<'a> {
pub const fn new(width: usize, height: usize) -> Self {
Self {
width,
height,
fog: None,
dither: None,
screen_tint: None,
palette_mode: PaletteMode::Off,
stipple_mode: StippleMode::Off,
texture_mapping: TextureMapping::PerspectiveCorrect,
depth_bias: None,
depth_mode: DepthInterpolationMode::Exact,
sky: None,
camera_dir: [0.0, 0.0, -1.0],
}
}
pub fn from_zbuffer(width: usize, zbuffer_len: usize) -> Self {
let height = if width == 0 { 0 } else { zbuffer_len / width };
Self::new(width, height)
}
#[must_use]
pub const fn with_fog(mut self, fog: Option<&'a FogConfig>) -> Self {
self.fog = fog;
self
}
#[must_use]
pub const fn with_dither(mut self, dither: Option<&'a DitherConfig>) -> Self {
self.dither = dither;
self
}
#[must_use]
pub const fn with_screen_tint(mut self, tint: Option<ScreenTint>) -> Self {
self.screen_tint = tint;
self
}
#[must_use]
pub const fn with_palette_mode(mut self, mode: PaletteMode) -> Self {
self.palette_mode = mode;
self
}
#[must_use]
pub const fn with_stipple_mode(mut self, mode: StippleMode) -> Self {
self.stipple_mode = mode;
self
}
#[must_use]
pub const fn with_texture_mapping(mut self, mapping: TextureMapping) -> Self {
self.texture_mapping = mapping;
self
}
#[must_use]
pub const fn with_depth_bias(mut self, bias: Option<DepthBias>) -> Self {
self.depth_bias = bias;
self
}
#[must_use]
pub const fn with_depth_mode(mut self, mode: DepthInterpolationMode) -> Self {
self.depth_mode = mode;
self
}
#[must_use]
pub const fn with_sky(mut self, sky: Option<SkyConfig>) -> Self {
self.sky = sky;
self
}
#[must_use]
pub const fn with_camera_dir(mut self, camera_dir: [f32; 3]) -> Self {
self.camera_dir = camera_dir;
self
}
}
#[inline]
pub(crate) fn ascending_y_order(points: &[Point2<i32>; 3]) -> [usize; 3] {
let mut order = [0usize, 1, 2];
let y = |i: usize| points[i].y;
if y(order[0]) > y(order[1]) {
order.swap(0, 1);
}
if y(order[0]) > y(order[2]) {
order.swap(0, 2);
}
if y(order[1]) > y(order[2]) {
order.swap(1, 2);
}
order
}
#[inline]
pub(crate) fn apply_order<T: Copy>(arr: &mut [T; 3], order: [usize; 3]) {
let src = *arr;
arr[0] = src[order[0]];
arr[1] = src[order[1]];
arr[2] = src[order[2]];
}
#[inline]
pub(crate) fn tri_fully_off_screen(points: &[Point2<i32>; 3], width: usize, height: usize) -> bool {
let [p1, p2, p3] = *points;
let w = width as i32;
let h = height as i32;
(p1.x < 0 && p2.x < 0 && p3.x < 0)
|| (p1.x >= w && p2.x >= w && p3.x >= w)
|| (p1.y < 0 && p2.y < 0 && p3.y < 0)
|| (p1.y >= h && p2.y >= h && p3.y >= h)
}
#[cfg(test)]
mod tests {
use super::*;
fn pts(y0: i32, y1: i32, y2: i32) -> [Point2<i32>; 3] {
[Point2::new(0, y0), Point2::new(1, y1), Point2::new(2, y2)]
}
#[test]
fn default_state_disables_effects() {
let s = RasterState::new(8, 4);
assert_eq!((s.width, s.height), (8, 4));
assert!(s.fog.is_none());
assert!(s.dither.is_none());
assert!(s.screen_tint.is_none());
assert_eq!(s.stipple_mode, StippleMode::Off);
assert_eq!(s.palette_mode, PaletteMode::Off);
assert!(s.sky.is_none());
assert_eq!(s.camera_dir, [0.0, 0.0, -1.0]);
}
#[test]
fn from_zbuffer_derives_height() {
let s = RasterState::from_zbuffer(16, 64);
assert_eq!((s.width, s.height), (16, 4));
assert_eq!(RasterState::from_zbuffer(0, 64).height, 0);
}
#[test]
fn ascending_y_order_sorts_and_permutes_payload() {
let points = pts(30, 10, 20);
let order = ascending_y_order(&points);
assert_eq!(order, [1, 2, 0]);
let mut colors = [10u8, 20, 30];
apply_order(&mut colors, order);
assert_eq!(colors, [20, 30, 10]);
}
#[test]
fn ascending_y_order_matches_legacy_swap_network() {
let points = pts(5, 5, 1);
let order = ascending_y_order(&points);
assert_eq!(order, [2, 1, 0]);
let mut ys: [i32; 3] = [5, 5, 1];
apply_order(&mut ys, order);
assert_eq!(ys, [1, 5, 5]);
}
#[test]
fn cull_rejects_only_fully_outside_triangles() {
assert!(tri_fully_off_screen(&pts(-5, -4, -3), 16, 16));
assert!(!tri_fully_off_screen(&pts(-5, -4, 3), 16, 16));
assert!(tri_fully_off_screen(&pts(20, 21, 22), 16, 16));
}
}