use chromatic::{Colour, ColourMap, LabAlpha};
use geodesic::prelude::*;
use indicatif::ParallelProgressIterator;
use nalgebra::{Point3, Unit};
use ndarray::{Array2, s};
use ndarray_stats::QuantileExt;
use photo::Image;
use rayon::prelude::*;
type Precision = f32;
const COLOURS: [&str; 2] = ["#000000FF", "#FFFFFFFF"];
fn main() -> Result<(), Box<dyn std::error::Error>> {
let assets = SerializedAssets::<Precision>::load("./inputs/assets.json")?.build()?;
let scene = SerializedScene::<Precision>::load("./inputs/scene.json")?.build(&assets)?;
let camera = SerializedCamera::load("./inputs/camera.json")?.build()?;
let resolution = camera.resolution();
let sun = Point3::new(10.0, -5.0, 20.0);
let total_pixels = resolution[0] * resolution[1];
let pixel_coords: Vec<(usize, usize)> = (0..resolution[0])
.flat_map(|row| (0..resolution[1]).map(move |col| (row, col)))
.collect();
let light_values: Vec<((usize, usize), Precision)> = pixel_coords
.into_par_iter()
.progress_count(total_pixels as u64)
.map(|(row, col)| -> Result<((usize, usize), Precision), GeodesicError> {
let ray = camera.generate_ray([row, col])?;
let light_value = if let Some(hit) = scene.intersect(&ray)? {
let ambient = 0.1;
let hit_position = ray.origin + ray.direction.scale(hit.distance - 0.01);
let light_dir = Unit::new_normalize(sun - hit_position);
let diffuse = (hit.geometric_normal.dot(&light_dir)).max(0.0);
let shadow_ray = Ray::new(hit_position, light_dir);
let shadow = if scene.intersect_any(&shadow_ray, 100.0)? {
0.0 } else {
1.0 };
ambient + (diffuse * (1.0 - ambient) * shadow)
} else {
0.0 };
Ok(((row, col), light_value))
})
.collect::<Result<Vec<_>, GeodesicError>>()?;
let mut light = Array2::<Precision>::zeros(*resolution);
for ((row, col), value) in light_values {
light[[row, col]] = value;
}
let light = downsample_average(&light, 16);
println!("Processed all {} pixels", total_pixels);
let cmap = ColourMap::new_uniform(&COLOURS.iter().map(|&c| LabAlpha::from_hex(c).unwrap()).collect::<Vec<_>>());
let min_light = light.min().unwrap();
let max_light = light.max().unwrap();
let mut range = max_light - min_light;
if range.is_nan() || range == 0.0 {
range = 1.0;
}
println!("Min light: {}, Max light: {}", min_light, max_light);
let img = light.mapv(|d| (d - min_light) / range).mapv(|d| cmap.sample(d));
img.save("./output/image.png")?;
Ok(())
}
fn downsample_average(arr: &Array2<Precision>, factor: usize) -> Array2<Precision> {
let (rows, cols) = arr.dim();
let new_rows = rows / factor;
let new_cols = cols / factor;
Array2::from_shape_fn((new_rows, new_cols), |(i, j)| {
let window = arr.slice(s![i * factor..(i + 1) * factor, j * factor..(j + 1) * factor]);
window.mean().unwrap_or(0.0)
})
}