use serde::{Deserialize, Serialize};
use display_json::DisplayAsJsonPretty;
use num::cast;
use num::integer::div_rem;
use crate::util::coloring::colors::{Color, RGB};
use crate::util::coloring::ColorMap1d;
use crate::util::frame::Frame;
use crate::util::viewport::Viewport;
use crate::util::{ComplexNumber, ProgressPrinter};
#[derive(Serialize, Deserialize, DisplayAsJsonPretty)]
pub struct JuliaSet {
width: u32,
height: u32,
center: ComplexNumber,
zoom: f64,
iter: u32,
rotation: Option<u16>,
color_map: ColorMap1d,
c: Option<ComplexNumber>,
}
impl JuliaSet {
#[must_use]
#[allow(clippy::too_many_arguments)]
pub fn new(
width: u32,
height: u32,
center: ComplexNumber,
zoom: f64,
iter: u32,
rotation: Option<u16>,
color_map: ColorMap1d,
c: Option<ComplexNumber>,
) -> Self {
Self {
width,
height,
center,
zoom,
iter,
rotation,
color_map,
c,
}
}
#[must_use]
pub fn creator(self) -> Creator {
Creator::new(self)
}
}
pub struct Creator {
args: JuliaSet,
viewport: Viewport,
}
impl Creator {
#[must_use]
pub fn new(args: JuliaSet) -> Self {
let viewport = Self::viewport(&args);
Self { args, viewport }
}
#[must_use]
pub fn create(&self) -> Frame<Color> {
let mut frame =
Frame::filled_default(self.args.width, self.args.height);
let pp = u64::from(self.args.width * self.args.height);
let pp = ProgressPrinter::new(pp, 2500);
frame.par_for_each_mut(|(i, pixel)| {
let (im, re) = div_rem(i, self.args.width.try_into().unwrap());
let mut z = self.viewport.rotated_point(re, im);
let c = if let Some(c) = &self.args.c {
c.into()
} else {
z
};
let mut z_sqr = z.norm_sqr();
let mut j = 0;
while j < self.args.iter && z_sqr <= 4.0 {
z = z.powi(2) + c;
z_sqr = z.norm_sqr();
j += 1;
}
let color = if j == self.args.iter {
1.
} else {
let mu = z_sqr.sqrt().log2().log2();
(f64::from(j + 1) - mu) / f64::from(self.args.iter)
};
*pixel = RGB::new(
cast(color * 255.).unwrap(),
cast(color * 255.).unwrap(),
cast(color * 255.).unwrap(),
)
.as_color();
pp.increment();
});
frame
}
fn viewport(args: &JuliaSet) -> Viewport {
let w = f64::from(args.width);
let h = f64::from(args.height);
let aspect_ratio = w / h;
let vp_width = aspect_ratio / args.zoom;
let vp_height = 1. / args.zoom;
let grid_delta_x = vp_width / w;
let grid_delta_y = vp_height / h;
Viewport::from_center(
args.center.into(),
vp_width,
vp_height,
grid_delta_x,
grid_delta_y,
args.rotation.unwrap_or(0),
)
}
}