wallswitch 0.60.11

randomly selects wallpapers for multiple monitors
Documentation
//! Nova Julia liquid fractal generator overlay.
//!
//! This module implements the Nova Julia fractal, a formula combining
//! Newton-Raphson root-finding loops with a dynamic Julia-like perturbation constant.
//! It produces fluid, organic, plume-like structures resembling liquid metal, glowing
//! plasma currents, and detailed biological lattices.

use crate::{
    ColorRGB, Complex, FractalConfig, FractalDescriptor, MAX_ITERATIONS, MIN_ITERATIONS, Monitor,
    NEON_PALETTES, RelaxedEscape, RelaxedViewportConfig, get_random_integer,
    optimize_relaxed_viewport,
};

/// Valid operational range for randomized zoom viewport allocation.
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,
}

/// A procedural generator for rendering Nova Julia fractals onto desktop backgrounds.
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);
    }
}