Skip to content
Snippets Groups Projects
s5_fractals.rs 6.99 KiB
Newer Older
mod circle;
mod mandelbrot;
mod rectangle;

Finn Bear's avatar
Finn Bear committed
use crate::color::set_alpha;
use crate::component::code::{pseudocode, Code};
Finn Bear's avatar
Finn Bear committed
use crate::component::grid::Grid;
use crate::egui::{Color32, Context, Frame, Ui};
Finn Bear's avatar
Finn Bear committed
use crate::fade_in::{fade_in_manual, fade_out_manual};
Finn Bear's avatar
Finn Bear committed
use crate::governor::Governor;
Finn Bear's avatar
Finn Bear committed
use crate::slide::s4_automata::randomize_grid;
use crate::slide::s5_fractals::circle::circle;
use crate::slide::s5_fractals::mandelbrot::mandelbrot;
use crate::slide::s5_fractals::rectangle::rectangle;
Finn Bear's avatar
Finn Bear committed
use crate::slide::Slide;
Finn Bear's avatar
Finn Bear committed
use eframe::egui::style::Margin;

Finn Bear's avatar
Finn Bear committed
/// Mandelbrot, etc.
Finn Bear's avatar
Finn Bear committed
pub struct Fractals {
Finn Bear's avatar
Finn Bear committed
    /// Where we are in the transitions.
    state: FractalsState,
Finn Bear's avatar
Finn Bear committed
    /// For showing more pixels.
    governor: Governor,
Finn Bear's avatar
Finn Bear committed
    /// Pseudocode display.
Finn Bear's avatar
Finn Bear committed
    code: Code,
Finn Bear's avatar
Finn Bear committed
    /// Persistent grid state.
Finn Bear's avatar
Finn Bear committed
    grid: Grid,
}

#[derive(Default)]
#[allow(unused)]
enum FractalsState {
Finn Bear's avatar
Finn Bear committed
    /// Nothing on screen (except header).
    #[default]
    Before,
Finn Bear's avatar
Finn Bear committed
    /// Fading in grid.
    Grid {
Finn Bear's avatar
Finn Bear committed
        /// When we started fading in the grid.
        fade_start: f64,
    },
Finn Bear's avatar
Finn Bear committed
    /// Erasing the automata cells.
    Erase {
        /// When we started fading out the dots.
        fade_start: f64,
    },
    Rectangle {
Finn Bear's avatar
Finn Bear committed
        /// How many pixels of the grid we've filled in.
        pixels: usize,
    },
    Circle {
Finn Bear's avatar
Finn Bear committed
        /// How many pixels of the grid we've filled in.
        pixels: usize,
    },
    Mandelbrot {
Finn Bear's avatar
Finn Bear committed
        /// How many pixels of the grid we've filled in.
        pixels: usize,
Finn Bear's avatar
Finn Bear committed
impl Default for Fractals {
    fn default() -> Self {
        const HORIZONTAL_OFFSET: f32 = 275.0;

Finn Bear's avatar
Finn Bear committed
        // Code goes on the left.
Finn Bear's avatar
Finn Bear committed
        let mut code = Code::default();
        code.name = "fractal_code";
        code.offset_x = -HORIZONTAL_OFFSET;
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
        // Grid goes on the right.
        let mut grid = Grid::default();
        grid.name = "fractal_grid";
        grid.offset_x = HORIZONTAL_OFFSET;
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
        grid.size_cells = 128;
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
        randomize_grid(&mut grid);

Finn Bear's avatar
Finn Bear committed
        // Grid cell stroke aliases too much at this resolution.
        grid.stroke_width = 0.0;

        Self {
            state: FractalsState::default(),
Finn Bear's avatar
Finn Bear committed
            governor: Governor::default(),
Finn Bear's avatar
Finn Bear committed
}

impl Slide for Fractals {
    fn transition(&mut self, ctx: &Context) -> bool {
        // Increment state by one.
        match self.state {
            FractalsState::Before => {
                self.state = FractalsState::Grid {
                    fade_start: ctx.input().time,
                }
            }
Finn Bear's avatar
Finn Bear committed
            FractalsState::Grid { .. } => {
                // Make sure we finished fading in.
                self.code.alpha = 1.0;
                self.grid.alpha = 1.0;

Finn Bear's avatar
Finn Bear committed
                self.state = FractalsState::Erase {
                    fade_start: ctx.input().time,
                };
Finn Bear's avatar
Finn Bear committed
            }
Finn Bear's avatar
Finn Bear committed
            FractalsState::Erase { .. } => self.state = FractalsState::Rectangle { pixels: 0 },
Finn Bear's avatar
Finn Bear committed
            FractalsState::Rectangle { .. } => self.state = FractalsState::Circle { pixels: 0 },
            FractalsState::Circle { .. } => self.state = FractalsState::Mandelbrot { pixels: 0 },
            FractalsState::Mandelbrot { .. } => return true,
        }
        false
    }
Finn Bear's avatar
Finn Bear committed

    fn show(&mut self, ui: &mut Ui) {
        Frame::none().margin(Margin::same(20.0)).show(ui, |ui| {
            ui.vertical_centered(|ui| {
                ui.heading("Fractals");
            });
        });
Finn Bear's avatar
Finn Bear committed

        ui.ctx().request_repaint();

Finn Bear's avatar
Finn Bear committed
        // The function that will be used to color the grid.
Finn Bear's avatar
Finn Bear committed
        //
        // It is dynamically typed (so that multiple distinct functions can be used).
        //
        // Note that we use indirection via a reference but not a heap-allocated [`Box`]. We can get
        // away with that since the function doesn't leave our stack frame.
Finn Bear's avatar
Finn Bear committed
        let algo: Option<&dyn Fn(f32, f32) -> bool>;
Finn Bear's avatar
Finn Bear committed
        // Limit to this many pixels. If [`None`], will render all pixels.
        // We use a mutable reference so we can increment in one place.
        let mut limit_pixels: Option<&mut usize> = None;

        match &mut self.state {
            FractalsState::Before => {
Finn Bear's avatar
Finn Bear committed
                // Don't proceed to render the code/grid.
Finn Bear's avatar
Finn Bear committed
            &mut FractalsState::Grid { fade_start } => {
Finn Bear's avatar
Finn Bear committed
                // Fade in the code and grid.
Finn Bear's avatar
Finn Bear committed
                fade_in_manual(ui, fade_start, |_, alpha| {
                    self.code.alpha = alpha;
                    self.grid.alpha = alpha;
                });
                algo = None;
            }
            &mut FractalsState::Erase { fade_start } => {
                fade_out_manual(ui, fade_start, |_, alpha| {
                    // Fade out the automata cells.
                    for (_, _, cell) in self.grid.iter_mut() {
                        if cell.a() > 0 {
                            *cell = set_alpha(Color32::BLACK, alpha);
                        }
                    }
                });
                algo = None;
Finn Bear's avatar
Finn Bear committed
            FractalsState::Rectangle { pixels } => {
                self.code.code = pseudocode(include_str!("s5_fractals/rectangle.rs"));
Finn Bear's avatar
Finn Bear committed
                algo = Some(&rectangle);
Finn Bear's avatar
Finn Bear committed
                limit_pixels = Some(pixels)
Finn Bear's avatar
Finn Bear committed
            FractalsState::Circle { pixels } => {
                self.code.code = pseudocode(include_str!("s5_fractals/circle.rs"));
Finn Bear's avatar
Finn Bear committed
                algo = Some(&circle);
Finn Bear's avatar
Finn Bear committed
                limit_pixels = Some(pixels);
Finn Bear's avatar
Finn Bear committed
            FractalsState::Mandelbrot { pixels } => {
                self.code.code = pseudocode(include_str!("s5_fractals/mandelbrot.rs"));
Finn Bear's avatar
Finn Bear committed
                algo = Some(&mandelbrot);
Finn Bear's avatar
Finn Bear committed
                limit_pixels = Some(pixels);
            }
        }

        // Double mutable borrow to avoid moving limit_pixels.
        if let Some(limit_pixels) = &mut limit_pixels {
            if **limit_pixels < self.grid.size_cells.pow(2)
                && self.governor.ready(ui.ctx().input().time, 0.02)
            {
                **limit_pixels += self.grid.size_cells.pow(2) * 33 / 32 / 32;
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
        // Paint the grid.
        if let Some(algo) = algo {
            // Effective limit for pixels.
            let effective_limit = limit_pixels.map(|l| *l).unwrap_or(usize::MAX);
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
            // Store the resolution before mutably borrowing self.grid.
            let size_cells = self.grid.size_cells;
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
            for (i, (gx, gy, c)) in self.grid.iter_mut().enumerate() {
                if i >= effective_limit {
                    *c = Color32::TRANSPARENT;
                    continue;
                }
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
                // 0 to 1.
                let nx = (gx as f32 + 0.5) / size_cells as f32;
                let ny = (gy as f32 + 0.5) / size_cells as f32;
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
                // -4 to 4.
                let x = nx * 4.0 - 2.0;
                let y = ny * 4.0 - 2.0;
Finn Bear's avatar
Finn Bear committed

Finn Bear's avatar
Finn Bear committed
                *c = if algo(x, y) {
                    Color32::BLACK
                } else {
                    Color32::WHITE
                };
            }
Finn Bear's avatar
Finn Bear committed
        }

        self.code.show(ui.ctx());
        self.grid.show(ui.ctx());
Finn Bear's avatar
Finn Bear committed
    }
}