embedded-3dgfx 0.7.1

3D graphics rendering for embedded systems (fork of embedded-gfx by Kezii)
Documentation
//! Fixed-point 16.16 edge stepping and row-width selection.

use embedded_graphics_core::prelude::Point;

/// Maximum rasterized row width for the selected `row_width_*` feature.
///
/// The `row_width_*` features are meant to be mutually exclusive. When a build
/// enables several, the widest wins so mixed `--features` runs stay
/// deterministic instead of failing to define this constant; enable the
/// `strict-features` feature to reject the mix outright.
pub const MAX_ROW_WIDTH: usize = resolve_row_width();

const fn resolve_row_width() -> usize {
    if cfg!(feature = "row_width_320") {
        320
    } else if cfg!(feature = "row_width_240") {
        240
    } else if cfg!(feature = "row_width_160") {
        160
    } else if cfg!(feature = "row_width_96") {
        96
    } else {
        100
    }
}

// Fixed-point 16.16 edge stepper — integer-only replacement for f32 invslope.
pub const FP_SHIFT: i64 = 16;

#[inline(always)]
pub fn fixed_to_i32(value: i64) -> i32 {
    if value >= 0 {
        (value >> FP_SHIFT) as i32
    } else {
        -((-value) >> FP_SHIFT) as i32
    }
}

pub struct EdgeStepper {
    pub x: i64,
    pub step: i64,
}

impl EdgeStepper {
    pub fn new(start: Point, end: Point, y: i32) -> Self {
        let dy = (end.y - start.y) as i64;
        let (step, x) = if dy != 0 {
            let s = (((end.x - start.x) as i64) << FP_SHIFT) / dy;
            let x = ((start.x as i64) << FP_SHIFT) + s * (y - start.y) as i64;
            (s, x)
        } else {
            (0, (start.x as i64) << FP_SHIFT)
        };
        Self { x, step }
    }

    #[inline(always)]
    pub fn current_x(&self) -> i32 {
        fixed_to_i32(self.x)
    }

    #[inline(always)]
    pub fn advance(&mut self) {
        self.x += self.step;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn row_width_resolves_to_a_known_value() {
        assert!(matches!(MAX_ROW_WIDTH, 96 | 100 | 160 | 240 | 320));
    }

    /// The `row_width_*` features are additive in cargo, so a mixed build must
    /// still resolve deterministically: the widest wins.
    #[cfg(feature = "row_width_320")]
    #[test]
    fn row_width_mix_picks_widest() {
        assert_eq!(MAX_ROW_WIDTH, 320);
    }

    #[test]
    fn test_fixed_to_i32() {
        assert_eq!(fixed_to_i32(0), 0);
        assert_eq!(fixed_to_i32(1 << FP_SHIFT), 1);
        assert_eq!(fixed_to_i32(42 << FP_SHIFT), 42);
        assert_eq!(fixed_to_i32(-(5 << FP_SHIFT)), -5);
    }

    #[test]
    fn test_edge_stepper_vertical() {
        let p1 = Point::new(10, 0);
        let p2 = Point::new(10, 20);
        let mut stepper = EdgeStepper::new(p1, p2, 0);
        assert_eq!(stepper.current_x(), 10);
        stepper.advance();
        assert_eq!(stepper.current_x(), 10);
    }

    #[test]
    fn test_edge_stepper_diagonal() {
        let p1 = Point::new(0, 0);
        let p2 = Point::new(20, 20);
        let mut stepper = EdgeStepper::new(p1, p2, 0);
        assert_eq!(stepper.current_x(), 0);
        stepper.advance();
        assert_eq!(stepper.current_x(), 1);
        stepper.advance();
        assert_eq!(stepper.current_x(), 2);
    }

    #[test]
    fn test_edge_stepper_horizontal_zero_dy() {
        let p1 = Point::new(5, 10);
        let p2 = Point::new(25, 10);
        let stepper = EdgeStepper::new(p1, p2, 10);
        assert_eq!(stepper.current_x(), 5);
        assert_eq!(stepper.step, 0);
    }
}