use indicatif::{ProgressBar, ProgressStyle};
use nalgebra::Complex;
use ndarray::Array2;
use num_traits::{Float, FloatConst, FromPrimitive, NumCast};
use rand::{
distr::{Distribution, StandardUniform, uniform::SampleUniform},
rng,
};
use rayon::prelude::*;
use std::sync::Arc;
use crate::{Attractor, Settings};
#[inline]
fn create_position_to_pixel_mapper<T: Send + Sync + Float + NumCast>(
offset: Complex<T>,
scale: T,
resolution: [usize; 2],
) -> impl Fn(&Complex<T>) -> Option<[usize; 2]> + Send + Sync {
let y_res = T::from(resolution[0]).unwrap();
let x_res = T::from(resolution[1]).unwrap();
let aspect_ratio = x_res / y_res;
let height = scale;
let width = scale * aspect_ratio;
let half_height = height / T::from(2.0).unwrap();
let half_width = width / T::from(2.0).unwrap();
let max_y = y_res - T::one();
let max_x = x_res - T::one();
move |p: &Complex<T>| {
let shifted_re = p.re - offset.re;
let shifted_im = p.im - offset.im;
let y = ((half_height - shifted_im) / height) * max_y;
let x = ((half_width - shifted_re) / width) * max_x;
(y >= T::zero() && y < y_res && x >= T::zero() && x < x_res).then(|| [y.to_usize().unwrap(), x.to_usize().unwrap()])
}
}
#[inline]
pub fn render<T>(settings: &Settings<T>) -> Array2<u32>
where
T: Float + FloatConst + SampleUniform + NumCast + FromPrimitive + Send + Sync,
StandardUniform: Distribution<T>,
{
let progress_bar = ProgressBar::new(u64::try_from(settings.num_samples).unwrap());
progress_bar.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:50.cyan/blue} {pos}/{len} samples ({percent}%) - {eta_precise} remaining")
.unwrap()
.progress_chars("\u{2588}\u{2593}\u{2592}\u{2591}"),
);
progress_bar.set_message("Rendering fractal...");
let arc_progress_bar = Arc::new(progress_bar);
let group_counts: Vec<Array2<u32>> = (0..settings.num_groups)
.into_par_iter()
.map(|_group_index| render_group(settings, &Arc::clone(&arc_progress_bar)))
.collect();
arc_progress_bar.finish_with_message("Rendering complete!");
let mut total_counts = Array2::zeros(settings.resolution);
for counts in group_counts {
total_counts += &counts;
}
total_counts
}
#[inline]
fn render_group<T>(settings: &Settings<T>, progress_bar: &Arc<ProgressBar>) -> Array2<u32>
where
T: Float + FloatConst + SampleUniform + NumCast + FromPrimitive + Send + Sync,
StandardUniform: Distribution<T>,
{
let offset = Complex::new(settings.offset[0], settings.offset[1]);
let mapper = create_position_to_pixel_mapper(offset, settings.scale, settings.resolution);
let mut counts = Array2::zeros(settings.resolution);
let mut rng = rng();
for i in 0..(settings.num_samples / settings.num_groups) {
let pos = settings.generator.sample(&mut rng);
render_path(
settings.attractor.as_ref(),
&mapper,
pos,
settings.max_iter,
settings.warmup,
&mut counts,
);
if i % 100 == 0 {
progress_bar.inc(100);
}
}
let remainder = (settings.num_samples / settings.num_groups) % 100;
if remainder > 0 {
progress_bar.inc(u64::try_from(remainder).unwrap());
}
counts
}
#[inline]
fn render_path<T>(
attractor: &(dyn Attractor<T> + Sync),
mapper: impl Fn(&Complex<T>) -> Option<[usize; 2]>,
start: Complex<T>,
max_iter: usize,
warmup: usize,
counts: &mut Array2<u32>,
) where
T: Float + NumCast + FromPrimitive,
{
let mut pos = start;
for _ in 0..warmup {
pos = attractor.iterate(pos);
}
for _ in 0..max_iter {
pos = attractor.iterate(pos);
if let Some([x, y]) = mapper(&pos) {
counts[[x, y]] += 1;
}
}
}