iso.rs

3.1 kB · rust · 107 lines

1use crate::board::{Board, Frame};2use mrlycore::Color;3use mrlymath::three::faces::quads;4use mrlymath::three::Cell3d;56// PROJECTION78const RATIO: f64 = 0.866_025_403_784_438_6;910/// Projects a point of cube space to the isometric plane: x runs right-down, y left-down, z up.11pub fn project(x: f64, y: f64, z: f64) -> (f64, f64) {12    ((x - y) * RATIO, (x + y) * 0.5 - z)13}1415type Face = ([(f64, f64); 4], usize, f64);1617fn fit(faces: &[Face], frame: Frame) -> impl Fn((f64, f64)) -> (f64, f64) {18    let mut lo = (f64::MAX, f64::MAX);19    let mut hi = (f64::MIN, f64::MIN);20    for (quad, _, _) in faces {21        for p in quad {22            lo.0 = lo.0.min(p.0);23            lo.1 = lo.1.min(p.1);24            hi.0 = hi.0.max(p.0);25            hi.1 = hi.1.max(p.1);26        }27    }28    let (span_x, span_y) = ((hi.0 - lo.0).max(1e-9), (hi.1 - lo.1).max(1e-9));29    let scale = (frame.w / span_x).min(frame.h / span_y);30    let (ox, oy) = (31        frame.x + (frame.w - span_x * scale) / 2.0,32        frame.y + (frame.h - span_y * scale) / 2.0,33    );34    move |p: (f64, f64)| (ox + (p.0 - lo.0) * scale, oy + (p.1 - lo.1) * scale)35}3637// DRAWING3839/// Draws a cube's exposed faces in isometric, fitted to the frame and painted back to front.40///41/// The shades are the tones of the top, the left and the right face in that order, and the42/// edge, when given, is the hairline stroked around every face. Faces turned away from the43/// viewer are dropped before the painter's sort.44pub fn draw(45    board: &mut Board,46    frame: Frame,47    cell: &Cell3d,48    shade: [Color; 3],49    edge: Option<Color>,50) {51    let mut faces = Vec::new();52    for quad in quads(cell) {53        let (nx, ny, nz) = (54            quad.normal.x as f64,55            quad.normal.y as f64,56            quad.normal.z as f64,57        );58        if nx + ny + nz <= 0.0 {59            continue;60        }61        let tone = if nz > 0.0 {62            063        } else if ny > 0.0 {64            165        } else {66            267        };68        let corner = |i: usize| {69            let v = quad.verts[i];70            project(v.x as f64, v.y as f64, v.z as f64)71        };72        let depth = quad73            .verts74            .iter()75            .map(|v| (v.x + v.y + v.z) as f64)76            .sum::<f64>()77            / 4.0;78        faces.push(([corner(0), corner(1), corner(2), corner(3)], tone, depth));79    }80    if faces.is_empty() {81        return;82    }83    faces.sort_by(|a, b| a.2.total_cmp(&b.2));84    let place = fit(&faces, frame);85    let thick = (frame.w.min(frame.h) / 400.0).max(0.6);86    for (quad, tone, _) in &faces {87        let pts: Vec<(f64, f64)> = quad.iter().map(|p| place(*p)).collect();88        board.polygon(&pts, shade[*tone]);89        if let Some(color) = edge {90            let mut loop_pts = pts.clone();91            loop_pts.push(pts[0]);92            board.polyline(&loop_pts, thick, color);93        }94    }95}9697#[cfg(test)]98mod tests {99    use super::*;100    #[test]101    fn the_projection_lifts_z_straight_up_the_screen() {102        let ground = project(0.0, 0.0, 0.0);103        let above = project(0.0, 0.0, 1.0);104        assert!((above.0 - ground.0).abs() < 1e-12);105        assert!(above.1 < ground.1);106    }107}