240 lines
7.7 KiB
Rust
240 lines
7.7 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};
|
|
use std::time::Instant;
|
|
|
|
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 z = scale.zoom;
|
|
let h = scale.horizontal;
|
|
let v = scale.vertical;
|
|
let limit = scale.iteration_limit;
|
|
|
|
let x = ((-2.5 * z) + h, (1.0 * z) + h);
|
|
let y = ((-1.0 * z) - v, (1.0 * z) - v);
|
|
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,
|
|
},
|
|
..Default::default()
|
|
},
|
|
TextSection {
|
|
value: "".to_string(),
|
|
style: TextStyle {
|
|
font: ui_font.clone(),
|
|
font_size: 40.0,
|
|
color: white,
|
|
},
|
|
..Default::default()
|
|
},
|
|
TextSection {
|
|
value: "\nFrametime: ".to_string(),
|
|
style: TextStyle {
|
|
font: ui_font.clone(),
|
|
font_size: 40.0,
|
|
color: white,
|
|
},
|
|
..Default::default()
|
|
},
|
|
TextSection {
|
|
value: "".to_string(),
|
|
style: TextStyle {
|
|
font: ui_font.clone(),
|
|
font_size: 40.0,
|
|
color: white,
|
|
},
|
|
..Default::default()
|
|
},
|
|
TextSection {
|
|
value: "ms".to_string(),
|
|
style: TextStyle {
|
|
font: ui_font.clone(),
|
|
font_size: 40.0,
|
|
color: white,
|
|
},
|
|
..Default::default()
|
|
},
|
|
],
|
|
..Default::default()
|
|
},
|
|
style: Style {
|
|
..Default::default()
|
|
},
|
|
|
|
..Default::default()
|
|
})
|
|
.insert(PerformanceInfo);
|
|
}
|