use fidget::{
Context,
eval::{Function, MathFunction},
gui::View2,
raster::ImageRenderConfig,
render::ImageSize,
shape::{Shape, ShapeVars},
var::Var,
};
use nalgebra::Point2;
const HI: &str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../models/hi.vm"));
const QUARTER: &str =
include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../models/quarter.vm"));
#[derive(Default)]
struct Cfg {
vars: ShapeVars<f32>,
view: View2,
wide: bool,
}
impl Cfg {
fn test<F: Function>(&self, shape: Shape<F>, expected: &'static str) {
self.test_with_mat(shape, nalgebra::Matrix3::identity(), expected);
}
fn test_with_mat<F: Function>(
&self,
shape: Shape<F>,
world_to_model: nalgebra::Matrix3<f32>,
expected: &'static str,
) {
let width = if self.wide { 64 } else { 32 };
let cfg = ImageRenderConfig {
image_size: ImageSize::new(width, 32),
world_to_model: world_to_model * self.view.world_to_model(),
..Default::default()
};
let out = cfg
.run_with_vars(shape, &self.vars)
.expect("rendering should not be cancelled");
let mut img_str = String::new();
for (i, b) in out.iter().enumerate() {
if i % width as usize == 0 {
img_str += "\n ";
}
img_str.push(if b.inside() { '#' } else { '.' });
}
if img_str != expected {
println!("image mismatch detected!");
println!("Expected:\n{expected}\nGot:\n{img_str}");
println!("Diff:");
for (a, b) in img_str.chars().zip(expected.chars()) {
print!("{}", if a != b { '!' } else { a });
}
panic!("image mismatch");
}
}
}
fn check_hi<F: Function + MathFunction>() {
let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
let shape = Shape::<F>::new(&ctx, root).unwrap();
const EXPECTED: &str = "
.................#..............
.................#..............
.................#..............
.................#..........##..
.................#..........##..
.................#..............
.................#..............
.................######.....##..
.................###..##....##..
.................##....##...##..
.................#......#...##..
.................#......#...##..
.................#......#...##..
.................#......#...##..
.................#......#...##..
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................";
Cfg::default().test(shape, EXPECTED);
}
fn check_hi_wide<F: Function + MathFunction>() {
let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
let shape = Shape::<F>::new(&ctx, root).unwrap();
const EXPECTED: &str = "
.................................#..............................
.................................#..............................
.................................#..............................
.................................#..........##..................
.................................#..........##..................
.................................#..............................
.................................#..............................
.................................######.....##..................
.................................###..##....##..................
.................................##....##...##..................
.................................#......#...##..................
.................................#......#...##..................
.................................#......#...##..................
.................................#......#...##..................
.................................#......#...##..................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................
................................................................";
Cfg {
wide: true,
..Default::default()
}
.test(shape, EXPECTED);
}
fn check_hi_transformed<F: Function + MathFunction>() {
let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
let shape = Shape::<F>::new(&ctx, root).unwrap();
let mut mat = nalgebra::Matrix3::<f32>::identity();
mat.prepend_translation_mut(&nalgebra::Vector2::new(0.5, 0.5));
mat.prepend_scaling_mut(0.5);
const EXPECTED: &str = "
.###............................
.###............................
.###............................
.###............................
.###............................
.###............................
.###............................
.###....................###.....
.###...................#####....
.###...................#####....
.###...................####.....
.###............................
.###............................
.###............................
.###..######............###.....
.#############..........###.....
.###############........###.....
.######....#####........###.....
.#####.......####.......###.....
.####.........###.......###.....
.###..........####......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
................................";
Cfg::default().test_with_mat(shape, mat, EXPECTED);
}
fn check_hi_bounded<F: Function + MathFunction>() {
let (ctx, root) = Context::from_text(HI.as_bytes()).unwrap();
let shape = Shape::<F>::new(&ctx, root).unwrap();
const EXPECTED: &str = "
.###............................
.###............................
.###............................
.###............................
.###............................
.###............................
.###............................
.###....................###.....
.###...................#####....
.###...................#####....
.###...................####.....
.###............................
.###............................
.###............................
.###..######............###.....
.#############..........###.....
.###############........###.....
.######....#####........###.....
.#####.......####.......###.....
.####.........###.......###.....
.###..........####......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
.###...........###......###.....
................................";
let view =
View2::from_center_and_scale(nalgebra::Vector2::new(0.5, 0.5), 0.5);
Cfg {
view,
..Default::default()
}
.test(shape, EXPECTED);
}
fn check_quarter<F: Function + MathFunction>() {
let (ctx, root) = Context::from_text(QUARTER.as_bytes()).unwrap();
let shape = Shape::<F>::new(&ctx, root).unwrap();
const EXPECTED: &str = "
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
................................
.....###########................
.....###########................
......##########................
......##########................
......##########................
.......#########................
........########................
.........#######................
..........######................
...........#####................
..............##................
................................
................................
................................
................................
................................";
Cfg::default().test(shape, EXPECTED);
}
fn check_circle_var<F: Function + MathFunction>() {
let mut ctx = Context::new();
let x = ctx.x();
let y = ctx.y();
let x2 = ctx.square(x).unwrap();
let y2 = ctx.square(y).unwrap();
let r2 = ctx.add(x2, y2).unwrap();
let r = ctx.sqrt(r2).unwrap();
let v = Var::new();
let c = ctx.var(v);
let root = ctx.sub(r, c).unwrap();
let shape = Shape::<F>::new(&ctx, root).unwrap();
const EXPECTED_075: &str = "
................................
................................
................................
................................
............#########...........
..........#############.........
.........###############........
........#################.......
.......###################......
......#####################.....
......#####################.....
.....#######################....
.....#######################....
.....#######################....
.....#######################....
.....#######################....
.....#######################....
.....#######################....
.....#######################....
.....#######################....
......#####################.....
......#####################.....
.......###################......
........#################.......
.........###############........
..........#############.........
............#########...........
................................
................................
................................
................................
................................";
let mut vars = ShapeVars::new();
vars.insert(v.index().unwrap(), 0.75);
Cfg {
vars,
..Default::default()
}
.test(shape.clone(), EXPECTED_075);
const EXPECTED_05: &str = "
................................
................................
................................
................................
................................
................................
................................
................................
.............#######............
...........###########..........
..........#############.........
..........#############.........
.........###############........
.........###############........
.........###############........
.........###############........
.........###############........
.........###############........
.........###############........
..........#############.........
..........#############.........
...........###########..........
.............#######............
................................
................................
................................
................................
................................
................................
................................
................................
................................";
let mut vars = ShapeVars::new();
vars.insert(v.index().unwrap(), 0.5);
Cfg {
vars,
..Default::default()
}
.test(shape, EXPECTED_05);
}
fn check_neg_infinity<F: Function + MathFunction>() {
let mut ctx = Context::new();
let root = ctx.constant(-f64::INFINITY);
let shape = Shape::<F>::new(&ctx, root).unwrap();
let cfg = ImageRenderConfig {
image_size: ImageSize::new(256, 256),
pixel_perfect: true,
threads: None,
..Default::default()
};
let out = cfg.run(shape).unwrap();
assert!(out.into_iter().all(|i| i.inside()));
}
macro_rules! render_tests {
($i:ident, $ty:ty) => {
mod $i {
#[test]
fn render_hi() {
super::check_hi::<$ty>();
}
#[test]
fn render_hi_wide() {
super::check_hi_wide::<$ty>();
}
#[test]
fn render_hi_transformed() {
super::check_hi_transformed::<$ty>();
}
#[test]
fn render_hi_bounded() {
super::check_hi_bounded::<$ty>();
}
#[test]
fn render_quarter() {
super::check_quarter::<$ty>();
}
#[test]
fn render_circle_var() {
super::check_circle_var::<$ty>();
}
#[test]
fn render_neg_infinity() {
super::check_neg_infinity::<$ty>();
}
}
};
}
render_tests!(vm, fidget::vm::VmFunction);
render_tests!(vm3, fidget::vm::GenericVmFunction<3>);
#[cfg(feature = "jit")]
render_tests!(jit, fidget::jit::JitFunction);
#[test]
fn test_camera_render_config() {
let config = ImageRenderConfig {
image_size: ImageSize::from(512),
world_to_model: View2::from_center_and_scale(
nalgebra::Vector2::new(0.5, 0.5),
0.5,
)
.world_to_model(),
..Default::default()
};
let mat = config.mat();
assert_eq!(
mat.transform_point(&Point2::new(0.0, -1.0)),
Point2::new(0.0, 1.0)
);
assert_eq!(
mat.transform_point(&Point2::new(512.0, -1.0)),
Point2::new(1.0, 1.0)
);
assert_eq!(
mat.transform_point(&Point2::new(512.0, 511.0)),
Point2::new(1.0, 0.0)
);
let config = ImageRenderConfig {
image_size: ImageSize::from(512),
world_to_model: View2::from_center_and_scale(
nalgebra::Vector2::new(0.5, 0.5),
0.25,
)
.world_to_model(),
..Default::default()
};
let mat = config.mat();
assert_eq!(
mat.transform_point(&Point2::new(0.0, -1.0)),
Point2::new(0.25, 0.75)
);
assert_eq!(
mat.transform_point(&Point2::new(512.0, -1.0)),
Point2::new(0.75, 0.75)
);
assert_eq!(
mat.transform_point(&Point2::new(512.0, 511.0)),
Point2::new(0.75, 0.25)
);
}