field.rs

2.9 kB · rust · 103 lines

1use crate::board::{Board, Frame};2use crate::ink::Ramp;34// FIELDS56fn range(values: &[f64]) -> (f64, f64) {7    let lo = values.iter().copied().fold(f64::MAX, f64::min);8    let hi = values.iter().copied().fold(f64::MIN, f64::max);9    if hi - lo < 1e-12 {10        (lo, lo + 1.0)11    } else {12        (lo, hi)13    }14}1516/// Paints a scalar field into the frame, nearest sampled, normalised to its own range.17pub fn draw(18    board: &mut Board,19    frame: Frame,20    width: usize,21    height: usize,22    values: &[f64],23    ramp: &Ramp,24) {25    draw_range(board, frame, width, height, values, range(values), ramp);26}2728/// Paints a scalar field into the frame, nearest sampled, normalised to the given range.29pub fn draw_range(30    board: &mut Board,31    frame: Frame,32    width: usize,33    height: usize,34    values: &[f64],35    span: (f64, f64),36    ramp: &Ramp,37) {38    if width == 0 || height == 0 || values.len() < width * height {39        return;40    }41    let (lo, hi) = span;42    let reach = if (hi - lo).abs() < 1e-12 {43        1.044    } else {45        hi - lo46    };47    let x0 = frame.x.ceil().max(0.0) as usize;48    let y0 = frame.y.ceil().max(0.0) as usize;49    let x1 = ((frame.x + frame.w).floor().max(0.0) as usize).min(board.width);50    let y1 = ((frame.y + frame.h).floor().max(0.0) as usize).min(board.height);51    for py in y0..y1 {52        for px in x0..x1 {53            let u = (px as f64 + 0.5 - frame.x) / frame.w;54            let v = (py as f64 + 0.5 - frame.y) / frame.h;55            let col = ((u * width as f64) as usize).min(width - 1);56            let row = ((v * height as f64) as usize).min(height - 1);57            let t = (values[row * width + col] - lo) / reach;58            board.blend(px, py, ramp.at(t), 1.0);59        }60    }61}6263/// Paints a function over the unit square into the frame, sampled on a resolution by resolution grid.64pub fn sample(65    board: &mut Board,66    frame: Frame,67    resolution: usize,68    f: impl Fn(f64, f64) -> f64,69    ramp: &Ramp,70) {71    if resolution == 0 {72        return;73    }74    let mut values = Vec::with_capacity(resolution * resolution);75    for row in 0..resolution {76        for col in 0..resolution {77            let u = (col as f64 + 0.5) / resolution as f64;78            let v = (row as f64 + 0.5) / resolution as f64;79            values.push(f(u, v));80        }81    }82    draw(board, frame, resolution, resolution, &values, ramp);83}8485#[cfg(test)]86mod tests {87    use super::*;88    use crate::ink;89    #[test]90    fn a_flat_field_paints_the_ramps_low_end_everywhere() {91        let mut board = Board::new(64, 64, ink::ground());92        let frame = Frame::new(0.0, 0.0, 64.0, 64.0);93        sample(94            &mut board,95            frame,96            8,97            |_, _| 1.0,98            &Ramp::tone(ink::blue(), ink::yellow()),99        );100        let blue = ink::blue();101        assert_eq!(board.pixels[0], [blue.r, blue.g, blue.b, 255]);102    }103}