use crate::{
ColorRGB, Complex, FractalConfig, FractalDescriptor, MAX_ITERATIONS, MIN_ITERATIONS, Monitor,
NEON_PALETTES, RelaxedEscape, RelaxedViewportConfig, get_random_integer,
optimize_relaxed_viewport,
};
const ZOOM_RANGE: [f64; 2] = [1.2, 3.2];
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NovaPreset {
pub power: u32,
pub r: Complex,
pub c: Complex,
pub name: &'static str,
}
pub struct NovaGenerator {
pub preset: NovaPreset,
pub config: FractalConfig,
}
impl Default for NovaGenerator {
fn default() -> Self {
Self {
preset: NovaPreset {
power: 3,
r: Complex::new(1.0, 0.0),
c: Complex::new(0.1, 0.15),
name: "Liquid Mercury Flow",
},
config: FractalConfig {
scan_iterations: get_random_integer::<_, u32>(
MIN_ITERATIONS / 12,
MAX_ITERATIONS / 12,
)
.max(50),
color_palette: NEON_PALETTES[0],
zoom: 1.8,
rotation: Complex::one(),
},
}
}
}
impl FractalDescriptor for NovaGenerator {
#[inline(always)]
fn config(&self) -> &FractalConfig {
&self.config
}
#[inline(always)]
fn center(&self) -> Complex {
Complex::zero()
}
#[inline(always)]
fn is_julia(&self) -> bool {
true
}
#[inline(always)]
fn render_pixel(&self, z_init: Complex, _scale: f64, max_radius: f64) -> (ColorRGB, f64, f64) {
let (i, diff_norm, z_final) = compute_nova_escape(
z_init,
self.preset.power,
self.preset.r,
self.preset.c,
self.config.scan_iterations,
);
let edge_fade = z_init.circular_fade(max_radius, 0.40);
let escape_state = RelaxedEscape {
iterations: i,
max_iterations: self.config.scan_iterations,
diff_norm,
z_final,
};
escape_state.color(self.config.color_palette, edge_fade, (1e-5_f64).ln(), true)
}
fn info_text(&self) -> String {
format!(
"fractal [{}]\n\
f(z) = z^{} - 1 = 0, where c = {:6.3} {:+5.3}i (iter = {:4}, zoom = {:.2}), color: {}",
self.preset.name,
self.preset.power,
self.preset.c.re,
self.preset.c.im,
self.config.scan_iterations,
self.config.zoom,
self.config.color_palette
)
}
}
impl NovaGenerator {
pub fn random(monitor: &Monitor) -> Self {
let presets = [
NovaPreset {
power: 3,
r: Complex::new(1.0, 0.0),
c: Complex::new(0.10, 0.15),
name: "Liquid Mercury Flow",
},
NovaPreset {
power: 3,
r: Complex::new(1.0, 0.0),
c: Complex::new(-0.20, 0.45),
name: "Cosmic Plasma Flare",
},
NovaPreset {
power: 4,
r: Complex::new(1.0, 0.0),
c: Complex::new(0.22, 0.10),
name: "Ornate Coral Filigree",
},
NovaPreset {
power: 3,
r: Complex::new(0.9, 0.0),
c: Complex::new(-0.35, 0.25),
name: "Nebulous Dust Whispers",
},
NovaPreset {
power: 4,
r: Complex::new(1.0, 0.0),
c: Complex::new(-0.10, 0.35),
name: "Gilded Lace Tapestry",
},
NovaPreset {
power: 5,
r: Complex::new(1.0, 0.0),
c: Complex::new(-0.05, 0.55),
name: "Glacial Frost Lattice",
},
NovaPreset {
power: 3,
r: Complex::new(1.15, 0.0),
c: Complex::new(0.0, 0.12),
name: "Spiritual Mandala Ripple",
},
NovaPreset {
power: 4,
r: Complex::new(0.8, 0.0),
c: Complex::new(0.30, -0.20),
name: "Bioluminescent Spore Nest",
},
NovaPreset {
power: 3,
r: Complex::new(1.0, 0.0),
c: Complex::new(0.18, -0.40),
name: "Abyssal Trench Vines",
},
NovaPreset {
power: 6,
r: Complex::new(1.0, 0.0),
c: Complex::new(-0.15, 0.15),
name: "Hyperdimensional Loom",
},
NovaPreset {
power: 3,
r: Complex::new(1.0, 0.15),
c: Complex::new(-0.15, 0.35),
name: "Gothic Cathedral Rose",
},
NovaPreset {
power: 5,
r: Complex::new(0.85, 0.25),
c: Complex::new(0.25, 0.05),
name: "Quantum Foam Fluctuation",
},
NovaPreset {
power: 4,
r: Complex::new(1.2, -0.10),
c: Complex::new(-0.28, -0.28),
name: "Stellar Nucleosynthesis",
},
NovaPreset {
power: 6,
r: Complex::new(0.95, 0.05),
c: Complex::new(0.05, 0.42),
name: "Emerald Moss Labyrinth",
},
NovaPreset {
power: 7,
r: Complex::new(1.0, 0.0),
c: Complex::new(-0.08, 0.38),
name: "Bismuth Crystal Citadel",
},
NovaPreset {
power: 3,
r: Complex::new(0.75, -0.30),
c: Complex::new(0.32, 0.18),
name: "Astral Jellyfish Canopy",
},
NovaPreset {
power: 4,
r: Complex::new(1.1, 0.15),
c: Complex::new(-0.45, 0.10),
name: "Solar Prominence Loops",
},
NovaPreset {
power: 5,
r: Complex::new(0.9, -0.20),
c: Complex::new(-0.12, -0.32),
name: "Aetheric Ley Line Matrix",
},
NovaPreset {
power: 8,
r: Complex::new(1.05, 0.10),
c: Complex::new(0.15, 0.15),
name: "Phytoplankton Radiance",
},
NovaPreset {
power: 6,
r: Complex::new(0.8, 0.40),
c: Complex::new(-0.22, 0.22),
name: "Chronos Vortex Gear",
},
NovaPreset {
power: 5,
r: Complex::new(1.0, 0.3),
c: Complex::new(-0.18, 0.12),
name: "Aeon Temple Portico",
},
NovaPreset {
power: 8,
r: Complex::new(0.9, -0.15),
c: Complex::new(0.20, 0.35),
name: "Hyperborean Crown",
},
NovaPreset {
power: 3,
r: Complex::new(1.1, 0.45),
c: Complex::new(-0.33, -0.05),
name: "Abyssal Nautilus Shell",
},
NovaPreset {
power: 4,
r: Complex::new(0.7, 0.5),
c: Complex::new(0.15, -0.55),
name: "Spectral Dragon Spine",
},
NovaPreset {
power: 3,
r: Complex::new(1.3, -0.2),
c: Complex::new(0.25, 0.25),
name: "Opalescent Silk Ribbons",
},
NovaPreset {
power: 5,
r: Complex::new(1.0, -0.4),
c: Complex::new(-0.42, 0.18),
name: "Phoenix Heart Nebula",
},
NovaPreset {
power: 7,
r: Complex::new(0.85, 0.1),
c: Complex::new(0.30, -0.30),
name: "Crystalline Geode Valley",
},
NovaPreset {
power: 6,
r: Complex::new(1.15, -0.3),
c: Complex::new(-0.02, 0.48),
name: "Eldritch Eye Lattice",
},
NovaPreset {
power: 4,
r: Complex::new(0.9, 0.35),
c: Complex::new(-0.25, 0.30),
name: "Prismatic Quantum Lattice",
},
NovaPreset {
power: 9,
r: Complex::new(1.0, 0.25),
c: Complex::new(0.08, -0.28),
name: "Void Weaver Spindle",
},
];
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 nova = Self {
preset: selected_preset,
config: FractalConfig {
scan_iterations: get_random_integer(40, 80),
color_palette,
zoom: 1.8,
rotation: Complex::from_polar(1.0, radians),
},
};
nova.optimize_fit(monitor);
nova
}
pub fn optimize_fit(&mut self, monitor: &Monitor) {
let width = monitor.resolution.width as u32;
let height = monitor.resolution.height as u32;
let scan_iterations = self.config.scan_iterations;
let power = self.preset.power;
let r = self.preset.r;
let c = self.preset.c;
let config = RelaxedViewportConfig {
width,
height,
search_limit: 1.6,
steps: 64,
zoom_range: ZOOM_RANGE,
rand_range: [0.90, 1.35],
fallback_range: [1.30, 2.80],
};
let (zoom, rotation) = optimize_relaxed_viewport(config, self.config.rotation, |z| {
let (i, _, _) = compute_nova_escape(z, power, r, c, scan_iterations);
i > 6 && i < scan_iterations - 2
});
self.config.zoom = zoom;
self.config.rotation = rotation;
}
}
#[inline(always)]
fn compute_nova_escape(
z_init: Complex,
power: u32,
r: Complex,
c: 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 {
let z_norm_sq = z.abs_sq();
if !(1e-6..=100.0).contains(&z_norm_sq) {
break;
}
let step = r * z.newton_step_term(power);
let z_next = z - step + c;
let diff = z_next - z;
diff_norm = diff.abs_sq();
if diff_norm < 1e-5 {
z = z_next;
break;
}
z = z_next;
i += 1;
}
(i, diff_norm, z)
}
#[cfg(test)]
mod tests_nova {
use super::*;
#[test]
fn test_nova_generation_random() {
let monitor = Monitor::default();
let nova = NovaGenerator::random(&monitor);
assert!(nova.config.zoom > 0.0);
assert!(nova.preset.power > 0);
}
}