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}