From 12e6760fb591eac07ec9b87d24391302bc88b7e5 Mon Sep 17 00:00:00 2001 From: Nicolas Klier Date: Tue, 7 May 2024 21:39:27 +0200 Subject: [PATCH] Initial commit --- .gitignore | 5 +++ .vscode/launch.json | 45 +++++++++++++++++++ Cargo.toml | 17 +++++++ src/cell.rs | 23 ++++++++++ src/main.rs | 88 ++++++++++++++++++++++++++++++++++++ src/stage.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 284 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 Cargo.toml create mode 100644 src/cell.rs create mode 100644 src/main.rs create mode 100644 src/stage.rs diff --git a/.gitignore b/.gitignore index 3ca43ae..193d30e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,8 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + + +# Added by cargo + +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b5c20e3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'DayNight'", + "cargo": { + "args": [ + "build", + "--bin=DayNight", + "--package=DayNight" + ], + "filter": { + "name": "DayNight", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'DayNight'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=DayNight", + "--package=DayNight" + ], + "filter": { + "name": "DayNight", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..199b80b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "DayNight" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +pixels = "0.13.0" +env_logger = "0.10" +log = "0.4" +winit = "0.28" +winit_input_helper = "0.14" +randomize = "5.0.0" +getrandom = "0.2.15" +byteorder = "1" +noise = "0.9" \ No newline at end of file diff --git a/src/cell.rs b/src/cell.rs new file mode 100644 index 0000000..e094c27 --- /dev/null +++ b/src/cell.rs @@ -0,0 +1,23 @@ +#[derive(Clone, Copy, Debug)] +pub enum CellVariance { + VOID = 0, + DAY = 1, + NIGHT = 2 +} + +impl Default for CellVariance { + fn default() -> Self { + Self::VOID + } +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Cell { + pub variance: CellVariance +} + +impl Cell { + pub fn new(variance: CellVariance) -> Self { + Self { variance } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8200d57 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,88 @@ +mod cell; +mod stage; + +use pixels::{Error, Pixels, SurfaceTexture}; +use stage::Stage; +use winit::{ + dpi::LogicalSize, + event::{Event, VirtualKeyCode}, + event_loop::{ControlFlow, EventLoop}, + window::{self, WindowBuilder}, +}; +use winit_input_helper::WinitInputHelper; + +const WIDTH: u32 = 256; +const HEIGHT: u32 = 256; +const SCALE_FACTOR: f64 = 4.0; + +fn main() -> Result<(), Error> { + env_logger::init(); + + let event_loop = EventLoop::new(); + let mut input = WinitInputHelper::new(); + + let window = { + let size: LogicalSize = LogicalSize::new(WIDTH as f64, HEIGHT as f64); + let scaled_size = + LogicalSize::new(WIDTH as f64 * SCALE_FACTOR, HEIGHT as f64 * SCALE_FACTOR); + + WindowBuilder::new() + .with_title("Day and Night") + .with_inner_size(scaled_size) + .with_min_inner_size(size) + .build(&event_loop) + .unwrap() + }; + + let mut pixels = { + let window_size = window.inner_size(); + let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); + Pixels::new(WIDTH, HEIGHT, surface_texture)? + }; + + let mut stage = Stage::new(WIDTH as usize, HEIGHT as usize); + stage.scatter(stage::ScatterTypes::RANDOM); + + let mut paused = false; + + let mut draw_state: Option = None; + + event_loop.run(move |event, _, control_flow| { + if let Event::RedrawRequested(_) = event { + stage.draw(pixels.frame_mut()); + + if let Err(err) = pixels.render() { + debug_assert!(true, "Oops, I did it again."); + + *control_flow = ControlFlow::Exit; + return; + } + } + + if input.update(&event) { + if input.key_pressed(VirtualKeyCode::Escape) || input.close_requested() { + *control_flow = ControlFlow::Exit; + return; + } + + if input.key_pressed(VirtualKeyCode::R) { + stage.scatter(stage::ScatterTypes::RANDOM); + } + + // Resize the window + if let Some(size) = input.window_resized() { + if let Err(err) = pixels.resize_surface(size.width, size.height) { + debug_assert!(true, "Resize failed!"); + *control_flow = ControlFlow::Exit; + return; + } + } + + if !paused || input.key_pressed_os(VirtualKeyCode::Space) { + stage.update(); + } + + window.request_redraw(); + } + }); +} diff --git a/src/stage.rs b/src/stage.rs new file mode 100644 index 0000000..3fd9267 --- /dev/null +++ b/src/stage.rs @@ -0,0 +1,106 @@ +use noise::core::value; +use noise::core::value::value_2d; +use noise::permutationtable::PermutationTable; +use noise::utils::NoiseMapBuilder; +use noise::utils::PlaneMapBuilder; +use noise::BasicMulti; +use noise::Fbm; +use noise::Perlin; +use noise::Seedable; +use randomize::Gen32; +use randomize::PCG32; + +use crate::cell::Cell; +use crate::cell::CellVariance; +pub struct Stage { + cells: Vec, + + /// Used for temporary work item. Replaces `cells` once done. + cells_dup: Vec, + width: usize, + height: usize, +} + +pub enum ScatterTypes { + RANDOM = 0, +} + +impl Stage { + pub fn new(width: usize, height: usize) -> Self { + assert!(width != 0 && height != 0); + let size = width.checked_mul(height).expect("Given width is too big."); + + Self { + cells: vec![Cell::default(); size], + cells_dup: vec![Cell::default(); size], + width, + height, + } + } + + /// Generate a pseudorandom seed for the game's PRNG. + fn generate_seed() -> (u64, u64) { + use byteorder::{ByteOrder, NativeEndian}; + use getrandom::getrandom; + + let mut seed = [0_u8; 16]; + + getrandom(&mut seed).expect("failed to getrandom"); + + ( + NativeEndian::read_u64(&seed[0..8]), + NativeEndian::read_u64(&seed[8..16]), + ) + } + + pub fn scatter(&mut self, scatter: ScatterTypes) { + match scatter { + ScatterTypes::RANDOM => { + let seed = Self::generate_seed(); + let mut rng: randomize::PCG32 = PCG32::new(seed.0, seed.1); + + let basicmulti = BasicMulti::::new(rng.next_u32()); + + let plane = PlaneMapBuilder::new(basicmulti) + .set_size(self.width, self.height) + .set_x_bounds(-5.0, 5.0) + .set_y_bounds(-5.0, 5.0) + .build(); + + for y in 0..self.height { + for x in 0..self.width { + let mut variance = CellVariance::VOID; + + let value = plane.get_value(x, y); + if value <= 0.1 { + variance = CellVariance::DAY; + } else { + variance = CellVariance::NIGHT; + } + + let cell = self.cells.get_mut(x + y * self.width).unwrap(); + *cell = Cell::new(variance); + } + } + } + } + + self.update(); + } + + pub fn draw(&self, screen: &mut [u8]) { + debug_assert_eq!(screen.len(), 4 * self.cells.len()); + for (c, pix) in self.cells.iter().zip(screen.chunks_exact_mut(4)) { + let color = match c.variance { + CellVariance::VOID => [0x0, 0x0, 0x0, 0xFF], + CellVariance::DAY => [0xD3, 0xE3, 0xD3, 0xFF], + CellVariance::NIGHT => [0x10, 0x41, 0x4F, 0xFF], + }; + pix.copy_from_slice(&color); + } + } + + pub fn update(&mut self) { + std::mem::swap(&mut self.cells_dup, &mut self.cells); + } +}