Skip to content
Snippets Groups Projects
Commit 07f64793 authored by Finn Bear's avatar Finn Bear
Browse files

Improve fractals slide.

parent 1e61685f
No related branches found
No related tags found
No related merge requests found
...@@ -92,7 +92,9 @@ pub fn pseudocode(code: &str) -> String { ...@@ -92,7 +92,9 @@ pub fn pseudocode(code: &str) -> String {
.replace("let ", "") .replace("let ", "")
.replace("mut ", "") .replace("mut ", "")
.replace(": f32", "") .replace(": f32", "")
.replace(" as f32", "")
.replace(" -> bool", "") .replace(" -> bool", "")
.replace(" -> f32", "")
.replace("println!", "println") .replace("println!", "println")
.replace(";\n", "\n") .replace(";\n", "\n")
} }
mod circle; mod circle;
mod julia_gradient;
mod mandelbrot; mod mandelbrot;
mod mandelbrot_gradient;
mod rectangle; mod rectangle;
use crate::color::set_alpha; use crate::color::set_alpha;
...@@ -11,11 +13,14 @@ use crate::fade_in::{fade_in_manual, fade_out_manual}; ...@@ -11,11 +13,14 @@ use crate::fade_in::{fade_in_manual, fade_out_manual};
use crate::governor::Governor; use crate::governor::Governor;
use crate::slide::s4_automata::randomize_grid; use crate::slide::s4_automata::randomize_grid;
use crate::slide::s6_fractals::circle::circle; use crate::slide::s6_fractals::circle::circle;
use crate::slide::s6_fractals::julia_gradient::julia as julia_gradient;
use crate::slide::s6_fractals::mandelbrot::mandelbrot; use crate::slide::s6_fractals::mandelbrot::mandelbrot;
use crate::slide::s6_fractals::mandelbrot_gradient::mandelbrot as mandelbrot_gradient;
use crate::slide::s6_fractals::rectangle::rectangle; use crate::slide::s6_fractals::rectangle::rectangle;
use crate::slide::Slide; use crate::slide::Slide;
use eframe::egui::style::Margin; use eframe::egui::style::Margin;
use eframe::egui::{Align2, Vec2}; use eframe::egui::{Align2, Vec2};
use std::ops::{Bound, RangeBounds};
/// Mandelbrot, etc. /// Mandelbrot, etc.
pub struct Fractals { pub struct Fractals {
...@@ -61,6 +66,16 @@ enum FractalsState { ...@@ -61,6 +66,16 @@ enum FractalsState {
Mandelbrot { Mandelbrot {
/// How many pixels of the grid we've filled in. /// How many pixels of the grid we've filled in.
pixels: usize, pixels: usize,
/// Whether fractal traversal mode is enabled.
traversal: bool,
},
MandelbrotGradient {
/// How many pixels of the grid we've filled in.
pixels: usize,
},
JuliaGradient {
/// How many pixels of the grid we've filled in.
pixels: usize,
}, },
} }
...@@ -97,7 +112,7 @@ impl Default for Fractals { ...@@ -97,7 +112,7 @@ impl Default for Fractals {
impl Slide for Fractals { impl Slide for Fractals {
fn transition(&mut self, ctx: &Context) -> bool { fn transition(&mut self, ctx: &Context) -> bool {
// Increment state by one. // Increment state by one.
match self.state { match &mut self.state {
FractalsState::Before => { FractalsState::Before => {
self.state = FractalsState::Grid { self.state = FractalsState::Grid {
fade_start: ctx.input().time, fade_start: ctx.input().time,
...@@ -119,8 +134,23 @@ impl Slide for Fractals { ...@@ -119,8 +134,23 @@ impl Slide for Fractals {
} }
FractalsState::Axes { .. } => self.state = FractalsState::Rectangle { pixels: 0 }, FractalsState::Axes { .. } => self.state = FractalsState::Rectangle { pixels: 0 },
FractalsState::Rectangle { .. } => self.state = FractalsState::Circle { pixels: 0 }, FractalsState::Rectangle { .. } => self.state = FractalsState::Circle { pixels: 0 },
FractalsState::Circle { .. } => self.state = FractalsState::Mandelbrot { pixels: 0 }, FractalsState::Circle { .. } => {
FractalsState::Mandelbrot { .. } => return true, self.state = FractalsState::Mandelbrot {
pixels: 0,
traversal: false,
}
}
FractalsState::Mandelbrot { traversal, .. } => {
if !*traversal {
*traversal = true;
} else {
self.state = FractalsState::MandelbrotGradient { pixels: 0 };
}
}
FractalsState::MandelbrotGradient { .. } => {
self.state = FractalsState::JuliaGradient { pixels: 0 }
}
FractalsState::JuliaGradient { .. } => return true,
} }
false false
} }
...@@ -140,7 +170,27 @@ impl Slide for Fractals { ...@@ -140,7 +170,27 @@ impl Slide for Fractals {
// //
// Note that we use indirection via a reference but not a heap-allocated [`Box`]. We can get // 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. // away with that since the function doesn't leave our stack frame.
let mut algo: Option<&dyn Fn(f32, f32) -> bool> = None; let mut algo: Option<Box<dyn Fn(f32, f32) -> Color32>> = None;
// Don't necessarily need to understand what is going on here.
fn wrap_bool_algo<A: Fn(f32, f32) -> bool>(a: A) -> impl Fn(f32, f32) -> Color32 {
move |x: f32, y: f32| {
if a(x, y) {
Color32::BLACK
} else {
Color32::WHITE
}
}
}
// Again, don't necessarily need to understand what is going on here.
fn wrap_f32_algo<A: Fn(f32, f32) -> f32>(a: A) -> impl Fn(f32, f32) -> Color32 {
move |x: f32, y: f32| {
let c = (a(x, y) * 255.0) as u8;
// Slight bluish tint.
Color32::from_rgb((c as u16 * 8 / 10) as u8, (c as u16 * 9 / 10) as u8, c)
}
}
// Limit to this many pixels. If [`None`], will render all pixels. // Limit to this many pixels. If [`None`], will render all pixels.
// We use a mutable reference so we can increment in one place. // We use a mutable reference so we can increment in one place.
...@@ -173,57 +223,90 @@ impl Slide for Fractals { ...@@ -173,57 +223,90 @@ impl Slide for Fractals {
} }
FractalsState::Rectangle { pixels } => { FractalsState::Rectangle { pixels } => {
self.code.code = pseudocode(include_str!("s6_fractals/rectangle.rs")); self.code.code = pseudocode(include_str!("s6_fractals/rectangle.rs"));
algo = Some(&rectangle); algo = Some(Box::new(wrap_bool_algo(rectangle)));
limit_pixels = Some(pixels) limit_pixels = Some(pixels)
} }
FractalsState::Circle { pixels } => { FractalsState::Circle { pixels } => {
self.code.code = pseudocode(include_str!("s6_fractals/circle.rs")); self.code.code = pseudocode(include_str!("s6_fractals/circle.rs"));
algo = Some(&circle); algo = Some(Box::new(wrap_bool_algo(circle)));
limit_pixels = Some(pixels); limit_pixels = Some(pixels);
} }
FractalsState::Mandelbrot { pixels } => { FractalsState::Mandelbrot {
pixels,
traversal: _,
} => {
self.code.code = pseudocode(include_str!("s6_fractals/mandelbrot.rs")); self.code.code = pseudocode(include_str!("s6_fractals/mandelbrot.rs"));
algo = Some(&mandelbrot); algo = Some(Box::new(wrap_bool_algo(mandelbrot)));
limit_pixels = Some(pixels);
}
FractalsState::MandelbrotGradient { pixels } => {
// Higher resolution grid.
self.grid.size_cells = 256;
self.code.code = pseudocode(include_str!("s6_fractals/mandelbrot_gradient.rs"));
algo = Some(Box::new(wrap_f32_algo(mandelbrot_gradient)));
limit_pixels = Some(pixels);
}
FractalsState::JuliaGradient { pixels } => {
self.code.code = pseudocode(include_str!("s6_fractals/julia_gradient.rs"));
algo = Some(Box::new(wrap_f32_algo(julia_gradient)));
limit_pixels = Some(pixels); limit_pixels = Some(pixels);
} }
} }
// Which pixels the algorithm should render.
let mut render_range: (Bound<usize>, Bound<usize>) = (Bound::Unbounded, Bound::Unbounded);
// Which pixels should be set to transparent.
let mut clear_range: (Bound<usize>, Bound<usize>) = (Bound::Unbounded, Bound::Unbounded);
// Whether to skip rendering and clearing entirely.
let mut skip = false;
// Double mutable borrow to avoid moving limit_pixels. // Double mutable borrow to avoid moving limit_pixels.
if let Some(limit_pixels) = &mut limit_pixels { if let Some(limit_pixels) = &mut limit_pixels {
if **limit_pixels < self.grid.size_cells.pow(2) if **limit_pixels < self.grid.size_cells.pow(2)
&& self.governor.ready(ui.ctx().input().time, 0.02) && self.governor.ready(ui.ctx().input().time, 0.02)
{ {
**limit_pixels += self.grid.size_cells.pow(2) * 33 / 32 / 32; let new_limit_pixels = **limit_pixels + self.grid.size_cells.pow(2) * 33 / 32 / 32;
// This is the range we just added, and therefore must render.
render_range = (
Bound::Included(**limit_pixels),
Bound::Excluded(new_limit_pixels),
);
// This is all the pixels after (that may be obsolete).
clear_range = (Bound::Included(new_limit_pixels), Bound::Unbounded);
**limit_pixels = new_limit_pixels;
} else {
// Waiting on timer, don't touch the pixels.
skip = true;
} }
} }
// Paint the grid. // Paint the grid.
if let Some(algo) = algo { if !skip {
// Effective limit for pixels. if let Some(algo) = algo {
let effective_limit = limit_pixels.map(|l| *l).unwrap_or(usize::MAX); // Store the resolution before mutably borrowing self.grid.
let size_cells = self.grid.size_cells;
// Store the resolution before mutably borrowing self.grid.
let size_cells = self.grid.size_cells;
for (i, (gx, gy, c)) in self.grid.iter_mut().enumerate() { for (i, (gx, gy, c)) in self.grid.iter_mut().enumerate() {
if i >= effective_limit { if render_range.contains(&i) {
*c = Color32::TRANSPARENT; // 0 to 1.
continue; let nx = (gx as f32 + 0.5) / size_cells as f32;
} let ny = (gy as f32 + 0.5) / size_cells as f32;
// 0 to 1. // -4 to 4.
let nx = (gx as f32 + 0.5) / size_cells as f32; let x = nx * 4.0 - 2.0;
let ny = (gy as f32 + 0.5) / size_cells as f32; let y = ny * 4.0 - 2.0;
// -4 to 4. *c = algo(x, y);
let x = nx * 4.0 - 2.0; } else if clear_range.contains(&i) {
let y = ny * 4.0 - 2.0; *c = Color32::TRANSPARENT;
}
*c = if algo(x, y) { }
Color32::BLACK
} else {
Color32::WHITE
};
} }
} }
......
#[rustfmt::skip]
pub fn julia(mut x: f32, mut y: f32) -> f32 {
let max = 200.0;
let mut i = 0.0;
while x * x + y * y
<= 4.0 && i < max {
let x_tmp = x * x
- y * y - 0.2278;
y = 2.0 * x * y - 0.65;
x = x_tmp;
i += 1.0;
}
return i / max;
}
#[rustfmt::skip]
pub fn mandelbrot(x: f32, y: f32) -> f32 {
let max = 100.0;
let mut x1 = 0.0;
let mut y1 = 0.0;
let mut i = 0.0;
while x1 * x1 + y1 * y1
<= 4.0 && i < max {
let x_tmp = x1 * x1
- y1 * y1 + x;
y1 = 2.0 * x1 * y1 + y;
x1 = x_tmp;
i += 1.0;
}
return i / max;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment