mandelbrot/
mandelbrot.rs

1#![no_std]
2
3extern crate alloc;
4#[macro_use]
5extern crate playdate_rs;
6
7use core::ops::{Add, Mul};
8
9use alloc::format;
10use playdate_rs::display::{DISPLAY_HEIGHT, DISPLAY_WIDTH};
11use playdate_rs::graphics::Color;
12use playdate_rs::math::Vec2;
13use playdate_rs::system::Buttons;
14use playdate_rs::{app, println, App, PLAYDATE};
15
16#[derive(Debug, Clone, Copy, PartialEq)]
17struct Complex {
18    re: f32,
19    im: f32,
20}
21
22impl Complex {
23    fn new(re: f32, im: f32) -> Self {
24        Self { re, im }
25    }
26
27    fn from_point(point: Vec2<i32>, center: Complex, scale: f32) -> Self {
28        let bounds = (DISPLAY_WIDTH as f32, DISPLAY_HEIGHT as f32);
29        let c1 = Complex::new(
30            center.re - bounds.0 / 2.0 * scale,
31            center.im + bounds.1 / 2.0 * scale,
32        );
33        let c2 = Complex::new(
34            center.re + bounds.0 / 2.0 * scale,
35            center.im - bounds.1 / 2.0 * scale,
36        );
37        let upper_left = Complex::new(c1.re.min(c2.re), c1.im.max(c2.im));
38        let lower_right = Complex::new(c1.re.max(c2.re), c1.im.min(c2.im));
39        assert!(lower_right.re > upper_left.re);
40        assert!(upper_left.im > lower_right.im);
41        let (width, height) = (
42            lower_right.re - upper_left.re,
43            upper_left.im - lower_right.im,
44        );
45        Complex {
46            re: upper_left.re + point.x as f32 * width / bounds.0,
47            im: upper_left.im - point.y as f32 * height / bounds.1,
48        }
49    }
50
51    fn norm_sqr(&self) -> f32 {
52        self.re * self.re + self.im * self.im
53    }
54}
55
56impl Add<Complex> for Complex {
57    type Output = Self;
58    fn add(self, rhs: Self) -> Self::Output {
59        Self::new(self.re + rhs.re, self.im + rhs.im)
60    }
61}
62
63impl Mul<Complex> for Complex {
64    type Output = Self;
65    fn mul(self, rhs: Complex) -> Self::Output {
66        Self::new(
67            self.re * rhs.re - self.im * rhs.im,
68            self.re * rhs.im + self.im * rhs.re,
69        )
70    }
71}
72
73fn f(c: Complex, max_iter: i32) -> bool {
74    let mut z = Complex::new(0.0, 0.0);
75    for _ in 0..max_iter {
76        if z.norm_sqr() > 4.0 {
77            return true;
78        }
79        z = z * z + c;
80    }
81    false
82}
83
84#[app]
85pub struct Mandelbrot {
86    center: Complex,
87    scale: f32,
88}
89
90impl Mandelbrot {
91    fn get_iter(&self) -> i32 {
92        match self.scale {
93            s if s > 0.01 => 16,
94            s if s > 0.001 => 32,
95            s if s > 0.0001 => 64,
96            s if s > 0.00001 => 96,
97            _ => 128,
98        }
99    }
100
101    fn draw_frame(&self) {
102        PLAYDATE.graphics.clear(Color::White);
103        let iter = self.get_iter();
104        for y in 0..DISPLAY_HEIGHT {
105            for x in 0..DISPLAY_WIDTH {
106                let pos = vec2![x as i32, y as i32];
107                let v = f(Complex::from_point(pos, self.center, self.scale), iter);
108                PLAYDATE
109                    .graphics
110                    .draw_pixel(pos, if v { Color::Black } else { Color::White });
111            }
112        }
113    }
114
115    fn draw_meta(&self) {
116        let text_area_width = 130;
117        let row_height = 14;
118        let text_area_height = row_height * 3;
119        let top_left_x = DISPLAY_WIDTH as i32 - text_area_width;
120        PLAYDATE.graphics.draw_rect(
121            rect! {
122                x: top_left_x - 3, y: DISPLAY_HEIGHT as i32 - text_area_height - 3,
123                w: text_area_width + 3, h: text_area_height + 3,
124            },
125            Color::White,
126        );
127        PLAYDATE.graphics.fill_rect(
128            rect! {
129                x: top_left_x - 2, y: DISPLAY_HEIGHT as i32 - text_area_height - 2,
130                w: text_area_width + 2, h: text_area_height + 2,
131            },
132            Color::Black,
133        );
134        PLAYDATE.graphics.fill_rect(
135            rect! {
136                x: top_left_x, y: DISPLAY_HEIGHT as i32 - text_area_height,
137                w: text_area_width , h: text_area_height,
138            },
139            Color::White,
140        );
141        PLAYDATE.graphics.draw_text(
142            format!("<{:.4}, {:.4}i>", self.center.re, self.center.im,),
143            vec2![top_left_x + 2, DISPLAY_HEIGHT as i32 - row_height * 3],
144        );
145        PLAYDATE.graphics.draw_text(
146            format!("SCALE: {:.8}", 1.0 / (self.scale * 100.0)),
147            vec2![top_left_x + 2, DISPLAY_HEIGHT as i32 - row_height * 2],
148        );
149        PLAYDATE.graphics.draw_text(
150            format!("ITER: {:}", self.get_iter()),
151            vec2![top_left_x + 2, DISPLAY_HEIGHT as i32 - row_height],
152        );
153    }
154}
155
156impl App for Mandelbrot {
157    fn new() -> Self {
158        println!("Hello, Mandelbrot!");
159        Self {
160            center: Complex::new(-0.5, 0.0),
161            scale: 0.01,
162        }
163    }
164
165    fn init(&mut self) {
166        let font = PLAYDATE
167            .graphics
168            .load_font("/System/Fonts/Roobert-10-Bold.pft")
169            .unwrap();
170        PLAYDATE.graphics.set_font(&font);
171        self.draw_frame();
172    }
173
174    fn update(&mut self, _delta: f32) {
175        let button_state = PLAYDATE.system.get_button_state();
176        let prev_scale = self.scale;
177        let prev_center = self.center;
178        // Scale
179        if button_state.current.contains(Buttons::A) {
180            self.scale /= 1.1;
181        } else if button_state.current.contains(Buttons::B) {
182            self.scale *= 1.1
183        }
184        if !PLAYDATE.system.is_crank_docked() {
185            let crank = PLAYDATE.system.get_crank_change();
186            if crank > 0.0 {
187                self.scale /= 1.05;
188            } else if crank < 0.0 {
189                self.scale *= 1.05;
190            }
191        }
192        // Move
193        if button_state.current.contains(Buttons::Up) {
194            self.center.im += 10.0 * self.scale
195        } else if button_state.current.contains(Buttons::Down) {
196            self.center.im -= 10.0 * self.scale
197        }
198        if button_state.current.contains(Buttons::Left) {
199            self.center.re -= 10.0 * self.scale
200        } else if button_state.current.contains(Buttons::Right) {
201            self.center.re += 10.0 * self.scale
202        }
203        if prev_center != self.center || prev_scale != self.scale {
204            self.draw_frame();
205        }
206        // Draw metadata
207        self.draw_meta();
208        // Draw FPS
209        PLAYDATE.system.draw_fps(vec2![0, 0]);
210    }
211}