#![warn(missing_docs)]
use crate::config::Tile;
use fidget_core::{
eval::Function,
render::{ImageSize, RenderHandle, ThreadPool, TileSizes, VoxelSize},
shape::{Shape, ShapeVars},
};
use nalgebra::Point2;
use rayon::prelude::*;
use zerocopy::{FromBytes, Immutable, IntoBytes};
mod config;
mod render2d;
mod render3d;
pub mod effects;
pub use config::{ImageRenderConfig, VoxelRenderConfig};
pub use render2d::DistancePixel;
use render2d::render as render2d;
use render3d::render as render3d;
pub(crate) struct TileSizesRef<'a>(&'a [usize]);
impl<'a> std::ops::Index<usize> for TileSizesRef<'a> {
type Output = usize;
fn index(&self, i: usize) -> &Self::Output {
&self.0[i]
}
}
impl TileSizesRef<'_> {
fn new(tiles: &TileSizes, max_size: usize) -> TileSizesRef<'_> {
let i = tiles
.iter()
.position(|t| *t < max_size)
.unwrap_or(tiles.len())
.saturating_sub(1);
TileSizesRef(&tiles[i..])
}
pub fn last(&self) -> usize {
*self.0.last().unwrap()
}
pub fn get(&self, i: usize) -> Option<usize> {
self.0.get(i).copied()
}
#[inline]
pub(crate) fn pixel_offset(&self, pos: Point2<usize>) -> usize {
let x = pos.x % self.0[0];
let y = pos.y % self.0[0];
x + y * self.0[0]
}
}
pub(crate) fn render_tiles<'a, F: Function, W: RenderWorker<'a, F, T>, T>(
shape: Shape<F, T>,
vars: &ShapeVars<f32>,
config: &'a W::Config,
) -> Option<Vec<(Tile<2>, W::Output)>>
where
W::Config: Send + Sync,
T: Sync,
{
use rayon::prelude::*;
let tile_sizes = config.tile_sizes();
let mut tiles = vec![];
let t = tile_sizes[0];
let width = config.width() as usize;
let height = config.height() as usize;
for i in 0..width.div_ceil(t) {
for j in 0..height.div_ceil(t) {
tiles.push(Tile::new(Point2::new(
i * tile_sizes[0],
j * tile_sizes[0],
)));
}
}
let mut rh = RenderHandle::new(shape);
let _ = rh.i_tape(&mut vec![]); let init = || {
let rh = rh.clone();
let worker = W::new(config);
(worker, rh)
};
match config.threads() {
None => {
let mut worker = W::new(config);
tiles
.into_iter()
.map(|tile| {
if config.is_cancelled() {
Err(())
} else {
let pixels = worker.render_tile(&mut rh, vars, tile);
Ok((tile, pixels))
}
})
.collect::<Result<Vec<_>, ()>>()
.ok()
}
Some(p) => p.run(|| {
tiles
.into_par_iter()
.map_init(init, |(w, rh), tile| {
if config.is_cancelled() {
Err(())
} else {
let pixels = w.render_tile(rh, vars, tile);
Ok((tile, pixels))
}
})
.collect::<Result<Vec<_>, ()>>()
.ok()
}),
}
}
pub(crate) trait RenderConfig {
fn width(&self) -> u32;
fn height(&self) -> u32;
fn tile_sizes(&self) -> TileSizesRef<'_>;
fn threads(&self) -> Option<&ThreadPool>;
fn is_cancelled(&self) -> bool;
}
pub(crate) trait RenderWorker<'a, F: Function, T> {
type Config: RenderConfig;
type Output: Send;
fn new(cfg: &'a Self::Config) -> Self;
fn render_tile(
&mut self,
shape: &mut RenderHandle<F, T>,
vars: &ShapeVars<f32>,
tile: config::Tile<2>,
) -> Self::Output;
}
#[derive(Clone)]
pub struct Image<P, S = ImageSize> {
data: Vec<P>,
size: S,
}
pub trait ImageSizeLike {
fn width(&self) -> u32;
fn height(&self) -> u32;
}
impl ImageSizeLike for ImageSize {
fn width(&self) -> u32 {
self.width()
}
fn height(&self) -> u32 {
self.height()
}
}
impl ImageSizeLike for VoxelSize {
fn width(&self) -> u32 {
self.width()
}
fn height(&self) -> u32 {
self.height()
}
}
impl<P: Send, S: ImageSizeLike + Sync> Image<P, S> {
pub fn apply_effect<F: Fn(usize, usize) -> P + Send + Sync>(
&mut self,
f: F,
threads: Option<&ThreadPool>,
) {
let r = |(y, row): (usize, &mut [P])| {
for (x, v) in row.iter_mut().enumerate() {
*v = f(x, y);
}
};
if let Some(threads) = threads {
threads.run(|| {
self.data
.par_chunks_mut(self.size.width() as usize)
.enumerate()
.for_each(r)
})
} else {
self.data
.chunks_mut(self.size.width() as usize)
.enumerate()
.for_each(r)
}
}
}
impl<P, S: Default> Default for Image<P, S> {
fn default() -> Self {
Image {
data: vec![],
size: S::default(),
}
}
}
impl<P: Default + Clone, S: ImageSizeLike> Image<P, S> {
pub fn new(size: S) -> Self {
Self {
data: vec![
P::default();
size.width() as usize * size.height() as usize
],
size,
}
}
}
impl<P, S: Clone> Image<P, S> {
pub fn size(&self) -> S {
self.size.clone()
}
pub fn map<T, F: Fn(&P) -> T>(&self, f: F) -> Image<T, S> {
let data = self.data.iter().map(f).collect();
Image {
data,
size: self.size.clone(),
}
}
pub fn take(self) -> (Vec<P>, S) {
(self.data, self.size)
}
}
impl<P, S: ImageSizeLike> Image<P, S> {
pub fn width(&self) -> usize {
self.size.width() as usize
}
pub fn height(&self) -> usize {
self.size.height() as usize
}
fn decode_position(&self, pos: (usize, usize)) -> usize {
let (row, col) = pos;
assert!(
row < self.height(),
"row ({row}) must be less than image height ({})",
self.height()
);
assert!(
col < self.width(),
"column ({col}) must be less than image width ({})",
self.width()
);
row * self.width() + col
}
}
impl<P, S> Image<P, S> {
pub fn iter(&self) -> impl Iterator<Item = &P> + '_ {
self.data.iter()
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
}
impl<'a, P: 'a, S> IntoIterator for &'a Image<P, S> {
type Item = &'a P;
type IntoIter = std::slice::Iter<'a, P>;
fn into_iter(self) -> Self::IntoIter {
self.data.iter()
}
}
impl<P, S> IntoIterator for Image<P, S> {
type Item = P;
type IntoIter = std::vec::IntoIter<P>;
fn into_iter(self) -> Self::IntoIter {
self.data.into_iter()
}
}
impl<P, S> std::ops::Index<usize> for Image<P, S> {
type Output = P;
fn index(&self, index: usize) -> &Self::Output {
&self.data[index]
}
}
impl<P, S> std::ops::IndexMut<usize> for Image<P, S> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.data[index]
}
}
macro_rules! define_image_index {
($ty:ty) => {
impl<P, S> std::ops::Index<$ty> for Image<P, S> {
type Output = [P];
fn index(&self, index: $ty) -> &Self::Output {
&self.data[index]
}
}
impl<P, S> std::ops::IndexMut<$ty> for Image<P, S> {
fn index_mut(&mut self, index: $ty) -> &mut Self::Output {
&mut self.data[index]
}
}
};
}
define_image_index!(std::ops::Range<usize>);
define_image_index!(std::ops::RangeTo<usize>);
define_image_index!(std::ops::RangeFrom<usize>);
define_image_index!(std::ops::RangeInclusive<usize>);
define_image_index!(std::ops::RangeToInclusive<usize>);
define_image_index!(std::ops::RangeFull);
impl<P, S: ImageSizeLike> std::ops::Index<(usize, usize)> for Image<P, S> {
type Output = P;
fn index(&self, pos: (usize, usize)) -> &Self::Output {
let index = self.decode_position(pos);
&self.data[index]
}
}
impl<P, S: ImageSizeLike> std::ops::IndexMut<(usize, usize)> for Image<P, S> {
fn index_mut(&mut self, pos: (usize, usize)) -> &mut Self::Output {
let index = self.decode_position(pos);
&mut self.data[index]
}
}
#[repr(C)]
#[derive(Debug, Default, Copy, Clone, IntoBytes, FromBytes, Immutable)]
pub struct GeometryPixel {
pub depth: f32,
pub normal: [f32; 3],
}
impl GeometryPixel {
pub fn to_color(&self) -> [u8; 3] {
let [dx, dy, dz] = self.normal;
let s = (dx.powi(2) + dy.powi(2) + dz.powi(2)).sqrt();
if s != 0.0 {
let scale = u8::MAX as f32 / s;
[
(dx.abs() * scale) as u8,
(dy.abs() * scale) as u8,
(dz.abs() * scale) as u8,
]
} else {
[0; 3]
}
}
}
pub type GeometryBuffer = Image<GeometryPixel, VoxelSize>;
impl<P: Default + Copy + Clone> Image<P, VoxelSize> {
pub fn depth(&self) -> usize {
self.size.depth() as usize
}
}
pub type ColorImage = Image<[u8; 3]>;