mandelbrot/src/main.rs

234 lines
7.5 KiB
Rust

use bevy::diagnostic::{Diagnostics, FrameTimeDiagnosticsPlugin};
use bevy::prelude::*;
use bevy::render::texture::{Extent3d, TextureDimension, TextureFormat};
use mandelbrot::{Bounds, Mandelbrot, TEXTURE_HEIGHT, TEXTURE_WIDTH};
const MAX_ITERATION_COUNT: u32 = 1024;
fn main() {
let window_settings = WindowDescriptor {
width: TEXTURE_WIDTH as f32,
height: TEXTURE_HEIGHT as f32,
title: "Mandelbrot".to_string(),
vsync: false,
resizable: false,
..Default::default()
};
App::build()
.insert_resource(window_settings)
.add_plugins(DefaultPlugins)
.add_plugin(FrameTimeDiagnosticsPlugin)
.add_startup_system(setup.system())
.insert_resource(TextureOptions::default())
.add_system(performance_info_system.system())
.add_system(mandelbrot_render_system.system())
.add_system(user_input_system.system())
.run();
}
struct PerformanceInfo;
struct TextureOptions {
zoom: f64,
horizontal: f64,
vertical: f64,
step: f64,
iteration_limit: u32,
}
impl Default for TextureOptions {
fn default() -> Self {
Self {
zoom: 1.0,
horizontal: 0.0,
vertical: 0.0,
step: 0.01,
iteration_limit: 64,
}
}
}
fn user_input_system(input: Res<Input<KeyCode>>, mut scale: ResMut<TextureOptions>) {
// Zoom: E to Zoom In, Q to Zoom Out
// Horizontal Movement: D to move right, A to move left
// Vertical Movement: W to move up, S to move Down
// R to increase the steps taken by zoom, horizontal movement, and vertical movement
// T to increase the Iteration Limit, G to decrease the iteration limit
let zoom = input.pressed(KeyCode::Q) as i8 - input.pressed(KeyCode::E) as i8;
let horizontal = input.pressed(KeyCode::D) as i8 - input.pressed(KeyCode::A) as i8;
let vertical = input.pressed(KeyCode::W) as i8 - input.pressed(KeyCode::S) as i8;
let step_change = input.pressed(KeyCode::R) as i8 - input.pressed(KeyCode::F) as i8;
let limit_change = input.just_pressed(KeyCode::T) as i8 - input.just_pressed(KeyCode::G) as i8;
scale.step += (scale.step / 10.0) * step_change as f64;
scale.vertical += scale.step * vertical as f64;
scale.horizontal += scale.step * horizontal as f64;
scale.zoom += scale.step * zoom as f64;
if limit_change == 1 {
scale.iteration_limit *= 2;
if scale.iteration_limit > MAX_ITERATION_COUNT {
scale.iteration_limit = MAX_ITERATION_COUNT;
}
} else if limit_change == -1 {
scale.iteration_limit /= 2;
if scale.iteration_limit < 32 {
scale.iteration_limit = 32;
}
}
if scale.zoom < 0.0 {
// We can't go below 0
scale.zoom -= scale.step * zoom as f64;
// slow down our step function
scale.step /= 10.0;
}
}
fn performance_info_system(
// time: Res<Time>,
diag: Res<Diagnostics>,
mut query: Query<&mut Text, With<PerformanceInfo>>,
) {
for mut text in query.iter_mut() {
let mut fps = 0.00;
let mut frametime = 0.00;
if let Some(fps_diag) = diag.get(FrameTimeDiagnosticsPlugin::FPS) {
if let Some(average) = fps_diag.average() {
fps = average;
}
}
if let Some(frametime_diag) = diag.get(FrameTimeDiagnosticsPlugin::FRAME_TIME) {
if let Some(average) = frametime_diag.average() {
frametime = average;
}
}
// FPS Number
text.sections[1].value = format!("{:.1}", fps);
// Frametime Number
text.sections[3].value = format!("{:.3}", frametime * 1000.0);
}
}
fn mandelbrot_render_system(
materials: Res<Assets<ColorMaterial>>,
mut textures: ResMut<Assets<Texture>>,
scale: Res<TextureOptions>,
mut query: Query<(&mut Mandelbrot, &Handle<ColorMaterial>)>,
) {
for (mut fractal, handle) in query.iter_mut() {
if let Some(material) = materials.get(handle) {
if let Some(texture_handle) = &material.texture {
if let Some(texture) = textures.get_mut(texture_handle) {
let zoom = scale.zoom;
let horiz = scale.horizontal;
let vert = scale.vertical;
let limit = scale.iteration_limit;
let x = ((-2.5 * zoom) + horiz, (1.0 * zoom) + horiz);
let y = ((-1.0 * zoom) - vert, (1.0 * zoom) - vert);
let bounds = Bounds::new(x, y);
texture
.data
.copy_from_slice(fractal.scaled_image(bounds, limit));
}
}
}
}
}
fn setup(
mut commands: Commands,
mut textures: ResMut<Assets<Texture>>,
mut materials: ResMut<Assets<ColorMaterial>>,
asset_server: Res<AssetServer>,
) {
let ui_font = asset_server.load("fonts/vcr_osd_mono.ttf");
let white = Color::rgb(1.0, 1.0, 1.0);
let mut fractal = Mandelbrot::default();
let texture_size = Extent3d::new(TEXTURE_WIDTH as u32, TEXTURE_HEIGHT as u32, 1);
let texture_data = fractal.image().to_vec();
let texture_handle = textures.add(Texture::new(
texture_size,
TextureDimension::D2,
texture_data,
TextureFormat::Rgba8UnormSrgb,
));
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
commands.spawn_bundle(UiCameraBundle::default());
commands
.spawn_bundle(SpriteBundle {
material: materials.add(texture_handle.into()),
..Default::default()
})
.insert(fractal);
commands
.spawn_bundle(TextBundle {
text: Text {
sections: vec![
TextSection {
value: "FPS: ".to_string(),
style: TextStyle {
font: ui_font.clone(),
font_size: 40.0,
color: white,
},
},
TextSection {
value: "".to_string(),
style: TextStyle {
font: ui_font.clone(),
font_size: 40.0,
color: white,
},
},
TextSection {
value: "\nFrametime: ".to_string(),
style: TextStyle {
font: ui_font.clone(),
font_size: 40.0,
color: white,
},
},
TextSection {
value: "".to_string(),
style: TextStyle {
font: ui_font.clone(),
font_size: 40.0,
color: white,
},
},
TextSection {
value: "ms".to_string(),
style: TextStyle {
font: ui_font,
font_size: 40.0,
color: white,
},
},
],
..Default::default()
},
style: Style {
..Default::default()
},
..Default::default()
})
.insert(PerformanceInfo);
}