use crate::effects::{
ComplexEffectsExt, MAX_ITERATIONS, MIN_ITERATIONS, RelaxedEscape, RelaxedViewportConfig,
optimize_relaxed_viewport, render_fractal_parallel,
};
use crate::{Complex, ImageEffect, NEON_PALETTES, NeonColor, get_random_integer};
use image::RgbImage;
const ZOOM_RANGE: [f64; 2] = [1.5, 3.8];
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NewtonPreset {
pub power: u32,
pub lambda: Complex,
pub name: &'static str,
}
pub struct NewtonGenerator {
pub preset: NewtonPreset,
pub scan_iterations: u32,
pub color_palette: NeonColor,
pub zoom: f64,
pub rotation: Complex,
}
impl Default for NewtonGenerator {
fn default() -> Self {
Self {
preset: NewtonPreset {
power: 3,
lambda: Complex::new(1.0, 0.3),
name: "Gothic Rose Mandala",
},
scan_iterations: get_random_integer::<_, u32>(MIN_ITERATIONS / 10, MAX_ITERATIONS / 10)
.max(60),
color_palette: NEON_PALETTES[0],
zoom: 2.0,
rotation: Complex::one(),
}
}
}
impl ImageEffect for NewtonGenerator {
fn apply(&self, rgb_img: &mut RgbImage) {
let scan_iterations = self.scan_iterations;
let power = self.preset.power;
let lambda = self.preset.lambda;
let color_palette = self.color_palette;
let (width, height) = rgb_img.dimensions();
let w_f = width as f64;
let h_f = height as f64;
let aspect_ratio = w_f.max(h_f) / w_f.min(h_f);
let max_radius = 0.98 * 0.5 * self.zoom * aspect_ratio;
render_fractal_parallel(
rgb_img,
self.zoom,
self.rotation,
Complex::new(0.0, 0.0),
true, move |z_init, _scale| {
let (i, diff_norm, z_final) =
compute_newton_escape(z_init, power, lambda, scan_iterations);
let edge_fade = z_init.circular_fade(max_radius as f32, 0.40);
let escape_state = RelaxedEscape {
iterations: i,
max_iterations: scan_iterations,
diff_norm,
z_final,
};
escape_state.color(
color_palette,
edge_fade,
(1e-6_f64).ln() as f32,
false, )
},
);
}
fn info(&self) -> String {
format!(
"fractal [{}]\n\
f(z) = z^{} - 1 = 0, where lambda = {:5.2} {} {:4.2}i (iter = {:4}, zoom = {:.2}), color: {}",
self.preset.name,
self.preset.power,
self.preset.lambda.re,
if self.preset.lambda.im >= 0.0 {
"+"
} else {
"-"
},
self.preset.lambda.im.abs(),
self.scan_iterations,
self.zoom,
self.color_palette
)
}
}
impl NewtonGenerator {
pub fn random(monitor: &crate::Monitor) -> Self {
let width = monitor.resolution.width as u32;
let height = monitor.resolution.height as u32;
let presets = [
NewtonPreset {
power: 3,
lambda: Complex::new(1.0, 0.3),
name: "Gothic Rose Mandala",
},
NewtonPreset {
power: 5,
lambda: Complex::new(0.9, 0.1),
name: "Imperial Star Compass",
},
NewtonPreset {
power: 4,
lambda: Complex::new(1.0, 0.0),
name: "Stained Glass Kaleidoscope",
},
NewtonPreset {
power: 6,
lambda: Complex::new(0.85, 0.2),
name: "Cosmic Snowflake Grid",
},
NewtonPreset {
power: 3,
lambda: Complex::new(1.35, 0.0),
name: "Spiked Crown of Thorns",
},
NewtonPreset {
power: 8,
lambda: Complex::new(0.7, 0.4),
name: "Quantum Energy Shells",
},
NewtonPreset {
power: 5,
lambda: Complex::new(1.1, 0.25),
name: "Solar Flare Compass",
},
NewtonPreset {
power: 3,
lambda: Complex::new(0.8, 0.5),
name: "Celtic Knotwork Ribbon",
},
NewtonPreset {
power: 4,
lambda: Complex::new(0.6, 0.6),
name: "Nautilus Spiral Chamber",
},
NewtonPreset {
power: 7,
lambda: Complex::new(1.0, 0.05),
name: "Hyper-Dimensional Matrix",
},
NewtonPreset {
power: 6,
lambda: Complex::new(1.15, 0.15),
name: "Aetheric Frost Flower",
},
NewtonPreset {
power: 8,
lambda: Complex::new(0.90, 0.30),
name: "Celestial Gearwork",
},
NewtonPreset {
power: 3,
lambda: Complex::new(0.75, 0.60),
name: "Byzantine Dome",
},
NewtonPreset {
power: 5,
lambda: Complex::new(1.25, -0.20),
name: "Abyssal Starfish",
},
NewtonPreset {
power: 4,
lambda: Complex::new(0.80, 0.45),
name: "Hyperborean Sigil",
},
NewtonPreset {
power: 7,
lambda: Complex::new(1.0, -0.30),
name: "Prismatic Labyrinth",
},
NewtonPreset {
power: 3,
lambda: Complex::new(0.95, 0.80),
name: "Nebula Core Spiral",
},
NewtonPreset {
power: 5,
lambda: Complex::new(0.60, 0.80),
name: "Aura Borealis Compass",
},
NewtonPreset {
power: 10,
lambda: Complex::new(0.85, 0.0),
name: "Obsidian Glass Lattices",
},
NewtonPreset {
power: 4,
lambda: Complex::new(1.40, -0.40),
name: "Bio-Polymer Filament",
},
];
let p_idx: usize = get_random_integer(0, NEON_PALETTES.len() - 1);
let color_palette = NEON_PALETTES[p_idx];
let angle_degrees: f64 = get_random_integer(0, 359);
let radians = angle_degrees.to_radians();
let preset_idx: usize = get_random_integer(0, presets.len() - 1);
let selected_preset = presets[preset_idx];
let mut newton = Self {
preset: selected_preset,
scan_iterations: get_random_integer(40, 100),
color_palette,
zoom: 2.0,
rotation: Complex::from_polar(1.0, radians),
};
newton.optimize_fit(width, height);
newton
}
pub fn optimize_fit(&mut self, width: u32, height: u32) {
let scan_iterations = self.scan_iterations;
let power = self.preset.power;
let lambda = self.preset.lambda;
let config = RelaxedViewportConfig {
width,
height,
search_limit: 1.8,
steps: 64,
zoom_range: ZOOM_RANGE,
rand_range: [0.95, 1.25],
fallback_range: [1.50, 2.50],
};
let (zoom, rotation) = optimize_relaxed_viewport(config, self.rotation, |z| {
let (i, _, _) = compute_newton_escape(z, power, lambda, scan_iterations);
i > 2 && i < scan_iterations - 2
});
self.zoom = zoom;
self.rotation = rotation;
}
}
#[inline(always)]
fn compute_newton_escape(
z_init: Complex,
power: u32,
lambda: Complex,
scan_iterations: u32,
) -> (u32, f64, Complex) {
let mut z = z_init;
let mut i = 0;
let mut diff_norm = 1.0;
while i < scan_iterations {
if z.abs_sq() < 1e-8 {
break;
}
let step = lambda * z.newton_step_term(power);
let z_next = z - step;
diff_norm = step.abs_sq();
if diff_norm < 1e-6 {
z = z_next;
break;
}
z = z_next;
i += 1;
}
(i, diff_norm, z)
}
#[cfg(test)]
mod tests_newton {
use super::*;
use crate::core::Monitor;
#[test]
fn test_newton_generation_random() {
let monitor = Monitor::default();
let newton = NewtonGenerator::random(&monitor);
assert!(newton.zoom > 0.0);
assert!(newton.scan_iterations > 0);
}
}