From: Jacob Lifshay Date: Thu, 5 Oct 2023 03:59:18 +0000 (-0700) Subject: switch to world with libre-soc logo X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=2cbf447a66ee3d6f6cc5da0d5768d3fb5c5ecd69;p=microwatt.git switch to world with libre-soc logo --- diff --git a/rust_voxels_game/Cargo.lock b/rust_voxels_game/Cargo.lock index 2852db3..229b42b 100644 --- a/rust_voxels_game/Cargo.lock +++ b/rust_voxels_game/Cargo.lock @@ -2,6 +2,37 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "libc" version = "0.2.148" @@ -9,12 +40,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" [[package]] -name = "termios" -version = "0.3.3" +name = "minetest-schematic" +version = "0.0.0" +dependencies = [ + "flate2", + "thiserror", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ - "libc", + "adler", +] + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", ] [[package]] @@ -22,5 +79,52 @@ name = "rust_voxels_game" version = "0.0.0" dependencies = [ "libc", + "minetest-schematic", "termios", ] + +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termios" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" +dependencies = [ + "libc", +] + +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/rust_voxels_game/Cargo.toml b/rust_voxels_game/Cargo.toml index c6da8e5..53f75fc 100644 --- a/rust_voxels_game/Cargo.toml +++ b/rust_voxels_game/Cargo.toml @@ -12,6 +12,9 @@ panic = "abort" termios = { version = "0.3.3", optional = true } libc = { version = "0.2", optional = true } +[build-dependencies] +minetest-schematic = { path = "minetest-schematic" } + [features] embedded = [] hosted = ["dep:termios", "dep:libc"] @@ -22,4 +25,10 @@ panic = "abort" codegen-units = 1 # better optimizations opt-level = 'z' # Optimize for size. debug = true # symbols are nice and they don't increase the size on Flash -lto = true # better optimizations \ No newline at end of file +lto = true # better optimizations + +[workspace] +members = [ + ".", + "minetest-schematic", +] \ No newline at end of file diff --git a/rust_voxels_game/Makefile b/rust_voxels_game/Makefile index 162e6ff..b01ac3c 100644 --- a/rust_voxels_game/Makefile +++ b/rust_voxels_game/Makefile @@ -45,7 +45,7 @@ rust_voxels_game.hex: rust_voxels_game.bin ../scripts/bin2hex.py $^ > $@ emu: - cargo run + cargo run --release clean: cargo clean diff --git a/rust_voxels_game/README.md b/rust_voxels_game/README.md index 9298383..ff3d845 100644 --- a/rust_voxels_game/README.md +++ b/rust_voxels_game/README.md @@ -48,7 +48,7 @@ pins going from the corner closest to the button: | 1 | UART RX | TX | | 2 | UART TX | RX | -Then, in a separate terminal that you've resized to be at least 100x76, run +Then, in a separate terminal that you've resized to be at least 100x41, run (replacing ttyUSB0 with whatever serial device the OrangeCrab is connected to): ```bash sudo tio --baudrate=1000000 /dev/ttyUSB0 diff --git a/rust_voxels_game/build.rs b/rust_voxels_game/build.rs index 16ab0de..782bece 100644 --- a/rust_voxels_game/build.rs +++ b/rust_voxels_game/build.rs @@ -1,4 +1,11 @@ -use std::{env, io, path::Path, process::Command}; +use minetest_schematic::MTS; +use std::{ + collections::HashMap, + env, fs, + io::{self, Write}, + path::{Path, PathBuf}, + process::Command, +}; const CFLAGS: &[&str] = &[ "-Os", @@ -32,11 +39,17 @@ fn uart_bauds() -> u32 { s.to_str().unwrap().parse().unwrap() } +fn make_output_path(source: impl AsRef, extension: &str) -> PathBuf { + let mut retval = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + retval.push(source.as_ref().file_name().unwrap()); + retval.set_extension(extension); + retval +} + fn gcc(source: impl AsRef) -> io::Result<()> { let source = source.as_ref(); println!("cargo:rerun-if-changed={}", source.display()); - let target = source.with_extension("o"); - let target = Path::new(target.file_name().unwrap()); + let target = make_output_path(source, "o"); println!("cargo:rustc-link-arg={}", target.display()); if !Command::new(format!("{}gcc", prefix())) .args(CFLAGS) @@ -68,12 +81,83 @@ fn embedded() -> io::Result<()> { Ok(()) } +fn generate_world(source: impl AsRef) -> io::Result<()> { + let source = source.as_ref(); + println!("cargo:rerun-if-changed={}", source.display()); + let target = make_output_path(source, "rs"); + let mts = MTS::read( + &mut io::BufReader::new(fs::File::open(source)?), + MTS::MAX_NODE_COUNT, + )?; + let color_map: HashMap<&str, Option<(u8, u8, u8)>> = HashMap::from_iter([ + ("air", None), + // colors from: + // https://github.com/minetest/minetestmapper/blob/e14f27f41268a11c34299a8b94a380b28c6b71e9/colors.txt + ("wool:black", Some((30, 30, 30))), + ("wool:blue", Some((0, 73, 146))), + ("wool:brown", Some((88, 44, 0))), + ("wool:cyan", Some((0, 132, 140))), + ("wool:dark_green", Some((33, 103, 0))), + ("wool:dark_grey", Some((60, 60, 60))), + ("wool:green", Some((93, 218, 28))), + ("wool:grey", Some((133, 133, 133))), + ("wool:magenta", Some((201, 3, 112))), + ("wool:orange", Some((214, 83, 22))), + ("wool:pink", Some((255, 133, 133))), + ("wool:red", Some((170, 18, 18))), + ("wool:violet", Some((93, 5, 169))), + ("wool:white", Some((220, 220, 220))), + ("wool:yellow", Some((254, 226, 16))), + ]); + let mut o = io::BufWriter::new(fs::File::create(target)?); + writeln!(o, "// autogenerated from {}", source.display())?; + writeln!(o, "use crate::{{world::Block, screen::RgbColor}};")?; + writeln!( + o, + "pub const SCHEMATIC: &'static [[[Block; {}]; {}]; {}] = &[", + mts.size_x, mts.size_y, mts.size_z + )?; + for z in 0..mts.size_z { + writeln!(o, " [")?; + for y in 0..mts.size_y { + writeln!(o, " [")?; + for x in 0..mts.size_x { + let node = mts.nodes[mts.pos_to_node_index(x, y, z)]; + let node_kind = &*mts.node_names[node.name_id as usize]; + let Some(color) = color_map.get(node_kind) else { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("unsupported node kind {node_kind:?}"), + )); + }; + if let Some((r, g, b)) = *color { + writeln!(o, " Block {{ color: Some(RgbColor {{ r: {r}, g: {g}, b: {b} }}.to_packed()) }},")?; + } else { + writeln!(o, " Block::default(),")?; + } + } + writeln!(o, " ],")?; + } + writeln!(o, " ],")?; + } + writeln!(o, "];")?; + drop(o.into_inner()?); + Ok(()) +} + +fn common() -> io::Result<()> { + generate_world("libre-soc-logo.mts")?; + Ok(()) +} + #[cfg(feature = "embedded")] fn main() -> io::Result<()> { + common(); embedded() } #[cfg(feature = "hosted")] -fn main() { +fn main() -> io::Result<()> { let _ = embedded; + common() } diff --git a/rust_voxels_game/libre-soc-logo.mts b/rust_voxels_game/libre-soc-logo.mts new file mode 100644 index 0000000..c6cca42 Binary files /dev/null and b/rust_voxels_game/libre-soc-logo.mts differ diff --git a/rust_voxels_game/minetest-schematic/Cargo.toml b/rust_voxels_game/minetest-schematic/Cargo.toml new file mode 100644 index 0000000..5593a81 --- /dev/null +++ b/rust_voxels_game/minetest-schematic/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "minetest-schematic" +version = "0.0.0" +edition = "2021" +publish = false +license = "LGPL-3.0+" + +[dependencies] +flate2 = "1.0.27" +thiserror = "1.0" \ No newline at end of file diff --git a/rust_voxels_game/minetest-schematic/src/lib.rs b/rust_voxels_game/minetest-schematic/src/lib.rs new file mode 100644 index 0000000..ee4f6ed --- /dev/null +++ b/rust_voxels_game/minetest-schematic/src/lib.rs @@ -0,0 +1,285 @@ +use std::{ + io::{self, ErrorKind, Read}, + mem, str, +}; +use thiserror::Error; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct MTSNode { + /// `content` in the specification, index into `MTS::node_names` + pub name_id: u16, + pub param1: u8, + pub param2: u8, +} + +impl MTSNode { + pub const fn probability(self) -> u8 { + self.param1 & 0x7F + } + pub const fn force_place(self) -> bool { + self.param1 & 0x80 != 0 + } +} + +#[derive(Debug, Clone)] +pub struct MTS { + pub size_x: u16, + pub size_y: u16, + pub size_z: u16, + pub node_names: Vec>, + pub nodes: Vec, + pub y_slice_probabilities: Vec, +} + +impl MTS { + pub const SIGNATURE: [u8; 4] = *b"MTSM"; + pub const CURRENT_VERSION: u16 = 4; + pub const MAX_NODE_COUNT: usize = isize::MAX as usize / mem::size_of::(); + pub const fn valid_size(size_x: u16, size_y: u16, size_z: u16, max_node_count: usize) -> bool { + let Some(total_nodes) = Self::try_node_count(size_x, size_y, size_z) else { + return false; + }; + total_nodes <= max_node_count + } + pub const fn try_node_count(size_x: u16, size_y: u16, size_z: u16) -> Option { + let Some(x_mul_y) = (size_x as usize).checked_mul(size_y as usize) else { + return None; + }; + x_mul_y.checked_mul(size_z as usize) + } + pub const fn node_count(size_x: u16, size_y: u16, size_z: u16) -> usize { + if let Some(retval) = Self::try_node_count(size_x, size_y, size_z) { + retval + } else { + panic!("invalid node count"); + } + } + pub fn pos_to_node_index(&self, x: u16, y: u16, z: u16) -> usize { + assert!( + Self::valid_size(self.size_x, self.size_y, self.size_z, Self::MAX_NODE_COUNT), + "size too big" + ); + assert!( + x < self.size_x && y < self.size_y && z < self.size_z, + "position out of range" + ); + let mut retval = 0usize; + retval += z as usize; + retval *= self.size_y as usize; + retval += y as usize; + retval *= self.size_x as usize; + retval += x as usize; + retval + } + fn read_bytes(reader: &mut impl io::BufRead) -> Result<[u8; N], MTSError> { + let mut buf = [0; N]; + reader.read_exact(&mut buf)?; + Ok(buf) + } + fn read_u8(reader: &mut impl io::BufRead) -> Result { + Ok(Self::read_bytes::<1>(reader)?[0]) + } + fn read_u16(reader: &mut impl io::BufRead) -> Result { + Ok(u16::from_be_bytes(Self::read_bytes(reader)?)) + } + fn read_string(reader: &mut impl io::BufRead) -> Result { + let len = Self::read_u16(reader)? as usize; + let mut buf = vec![0u8; len]; + reader.read_exact(&mut buf)?; + Ok(String::from_utf8(buf).map_err(|e| e.utf8_error())?) + } + pub fn read(reader: &mut R, max_node_count: usize) -> Result { + let max_node_count = max_node_count.min(Self::MAX_NODE_COUNT); + if Self::read_bytes(reader)? != Self::SIGNATURE { + return Err(MTSError::InvalidSignature); + } + let version = Self::read_u16(reader)?; + if version != Self::CURRENT_VERSION { + return Err(MTSError::UnsupportedVersion { version }); + } + let size_x = Self::read_u16(reader)?; + let size_y = Self::read_u16(reader)?; + let size_z = Self::read_u16(reader)?; + if !Self::valid_size(size_x, size_y, size_z, max_node_count) { + return Err(MTSError::SizeTooBig { + size_x, + size_y, + size_z, + }); + } + let mut y_slice_probabilities = vec![0; size_y as usize]; + reader.read_exact(&mut y_slice_probabilities)?; + let node_names_len = Self::read_u16(reader)?; + let mut node_names = Vec::with_capacity(node_names_len.into()); + for _ in 0..node_names_len { + node_names.push(Self::read_string(reader)?.into_boxed_str()); + } + let mut reader = flate2::bufread::ZlibDecoder::new(reader); + let node_count = Self::node_count(size_x, size_y, size_z); + let mut buf = vec![0; node_count * 2]; + reader.read_exact(&mut buf)?; + let mut nodes: Vec = vec![ + MTSNode { + name_id: 0, + param1: 0, + param2: 0 + }; + node_count + ]; + let mut buf_reader = &*buf; + for node in &mut nodes { + node.name_id = Self::read_u16(&mut buf_reader)?; + if node.name_id >= node_names_len { + return Err(MTSError::NameIdOutOfRange { + name_id: node.name_id, + node_names_len, + }); + } + } + buf.truncate(node_count); + reader.read_exact(&mut buf)?; + let mut buf_reader = &*buf; + for node in &mut nodes { + node.param1 = Self::read_u8(&mut buf_reader)?; + } + reader.read_exact(&mut buf)?; + let mut buf_reader = &*buf; + for node in &mut nodes { + node.param2 = Self::read_u8(&mut buf_reader)?; + } + match reader.read_exact(&mut [0u8]) { + Err(e) if e.kind() == ErrorKind::UnexpectedEof => {} + e => { + e?; + return Err(MTSError::TooManyBytes); + } + } + Ok(MTS { + size_x, + size_y, + size_z, + node_names, + nodes, + y_slice_probabilities, + }) + } +} + +#[derive(Debug, Error)] +pub enum MTSError { + #[error("invalid MTS signature")] + InvalidSignature, + #[error("unsupported MTS version {version}")] + UnsupportedVersion { version: u16 }, + #[error("MTS too big: ({size_x}, {size_y}, {size_z})")] + SizeTooBig { + size_x: u16, + size_y: u16, + size_z: u16, + }, + #[error("Name Id (`content` field) is out of range: {name_id} not in 0..{node_names_len}")] + NameIdOutOfRange { name_id: u16, node_names_len: u16 }, + #[error("too many bytes in decompressed schematic")] + TooManyBytes, + #[error(transparent)] + Utf8Error(#[from] str::Utf8Error), + #[error(transparent)] + IoError(#[from] io::Error), +} + +impl From for io::Error { + fn from(value: MTSError) -> Self { + match value { + MTSError::Utf8Error(e) => io::Error::new(ErrorKind::InvalidData, e), + MTSError::IoError(e) => e, + MTSError::InvalidSignature + | MTSError::UnsupportedVersion { .. } + | MTSError::SizeTooBig { .. } + | MTSError::NameIdOutOfRange { .. } + | MTSError::TooManyBytes => io::Error::new(ErrorKind::InvalidData, value), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_test() { + let bytes: &[u8] = &[ + 0x4d, 0x54, 0x53, 0x4d, 0x00, 0x04, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x7f, 0x7f, + 0x00, 0x04, 0x00, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3a, 0x73, 0x74, + 0x6f, 0x6e, 0x65, 0x00, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x3a, 0x64, + 0x69, 0x72, 0x74, 0x00, 0x03, 0x61, 0x69, 0x72, 0x00, 0x17, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x3a, 0x64, 0x69, 0x72, 0x74, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, + 0x67, 0x72, 0x61, 0x73, 0x73, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x03, 0x46, 0x20, 0x64, + 0x62, 0x60, 0x66, 0x60, 0xaa, 0x87, 0x02, 0x06, 0x28, 0x00, 0x00, 0x32, 0x71, 0x04, + 0x02, + ]; + let mts = MTS::read(&mut { bytes }, MTS::MAX_NODE_COUNT).unwrap(); + println!("{mts:#?}"); + assert_eq!( + format!("\n{mts:#?}\n"), + r#" +MTS { + size_x: 2, + size_y: 2, + size_z: 2, + node_names: [ + "default:stone", + "default:dirt", + "air", + "default:dirt_with_grass", + ], + nodes: [ + MTSNode { + name_id: 0, + param1: 127, + param2: 0, + }, + MTSNode { + name_id: 0, + param1: 127, + param2: 0, + }, + MTSNode { + name_id: 0, + param1: 127, + param2: 0, + }, + MTSNode { + name_id: 1, + param1: 127, + param2: 0, + }, + MTSNode { + name_id: 1, + param1: 127, + param2: 0, + }, + MTSNode { + name_id: 2, + param1: 127, + param2: 0, + }, + MTSNode { + name_id: 3, + param1: 127, + param2: 0, + }, + MTSNode { + name_id: 2, + param1: 127, + param2: 0, + }, + ], + y_slice_probabilities: [ + 127, + 127, + ], +} +"# + ); + } +} diff --git a/rust_voxels_game/src/console.rs b/rust_voxels_game/src/console.rs index 4f418fe..18f443c 100644 --- a/rust_voxels_game/src/console.rs +++ b/rust_voxels_game/src/console.rs @@ -1,5 +1,5 @@ use crate::take_once::{AlreadyTaken, TakeOnce}; -use core::{cell::UnsafeCell, ffi::c_int, fmt}; +use core::{ffi::c_int, fmt}; #[cfg(feature = "embedded")] extern "C" { @@ -131,6 +131,8 @@ impl Console { #[cfg(feature = "embedded")] pub(crate) unsafe fn emergency_console() -> &'static mut Console { + use core::cell::UnsafeCell; + struct EmergencyConsole(UnsafeCell); unsafe impl Sync for EmergencyConsole {} diff --git a/rust_voxels_game/src/lib.rs b/rust_voxels_game/src/lib.rs index 84cdd35..0f59e9f 100644 --- a/rust_voxels_game/src/lib.rs +++ b/rust_voxels_game/src/lib.rs @@ -2,6 +2,7 @@ use crate::{ fixed::Fix64, + screen::RgbColor, sin_cos::sin_cos_pi, vec::Vec3D, world::{Block, World}, @@ -17,6 +18,11 @@ mod sin_cos; mod take_once; mod vec; mod world; +mod shapes { + pub(crate) mod libre_soc_logo { + include!(concat!(env!("OUT_DIR"), "/libre-soc-logo.rs")); + } +} #[cfg(feature = "embedded")] #[panic_handler] @@ -39,6 +45,20 @@ fn exit(code: i32) -> ! { panic!("exited code={code}"); } +#[rustfmt::skip] +const NEW_BLOCK_COLORS: [RgbColor; 10] = [ + RgbColor { r: 0, g: 0, b: 0 }, + RgbColor { r: 0, g: 0, b: 0xFF }, + RgbColor { r: 0, g: 0xFF, b: 0 }, + RgbColor { r: 0, g: 0xFF, b: 0xFF }, + RgbColor { r: 0xFF, g: 0, b: 0 }, + RgbColor { r: 0xFF, g: 0, b: 0xFF }, + RgbColor { r: 0xFF, g: 0xFF, b: 0 }, + RgbColor { r: 0xFF, g: 0xFF, b: 0xFF }, + RgbColor { r: 0x55, g: 0x55, b: 0x55 }, + RgbColor { r: 0xAA, g: 0xAA, b: 0xAA }, +]; + #[cfg_attr(feature = "embedded", no_mangle)] pub extern "C" fn main() -> ! { let console = console::Console::take(); @@ -48,9 +68,9 @@ pub extern "C" fn main() -> ! { let mut pos = Vec3D { x: Fix64::from(0i64), y: Fix64::from(0i64), - z: Fix64::from(0i64), + z: Fix64::from(20i64), }; - let mut theta_over_pi = Fix64::from(0i64); + let mut theta_over_pi = Fix64::from(1i64); let mut phi_over_pi = Fix64::from(0i64); let mut blink_counter = 0; let blink_period = 6; @@ -77,21 +97,25 @@ pub extern "C" fn main() -> ! { let down = forward0 * sin_phi + down0 * cos_phi; let mut restore_cursor = None; let (_prev_pos, hit_pos) = world.get_hit_pos(pos, forward); - if blink_counter * 2 < blink_period { + if blink_counter * 3 < blink_period * 2 { restore_cursor = hit_pos.map(|hit_pos| { let block = world.get_mut(hit_pos).unwrap(); let old = *block; - block.color.0 = block.color.0.wrapping_add(100); - if *block == Block::default() { - block.color.0 = block.color.0.wrapping_add(1); - } + block.color = block.color.map(|_| { + if blink_counter * 3 < blink_period { + RgbColor::black().to_packed() + } else { + RgbColor::white().to_packed() + } + }); move |world: &mut World| *world.get_mut(hit_pos).unwrap() = old }); } world.render(screen, pos, forward, right, down); restore_cursor.map(|f| f(world)); screen.display(console); - writeln!(console, "Press WASD to move, IJKL to change look dir, 0-9 to place a block, - to delete a block, ESC to exit.").unwrap(); + writeln!(console, "Press WASD to move, IJKL to change look dir, F to move down, R to move up").unwrap(); + writeln!(console, "0-9 to place a block, - to delete a block, ESC to exit.").unwrap(); loop { let (prev_pos, hit_pos) = world.get_hit_pos(pos, forward); let mut new_pos = pos; @@ -99,8 +123,10 @@ pub extern "C" fn main() -> ! { break; }; match b { - b'w' | b'W' => new_pos = pos + forward * Fix64::from_rat(1, 4), - b's' | b'S' => new_pos = pos - forward * Fix64::from_rat(1, 4), + b'w' | b'W' => new_pos = pos + forward0 * Fix64::from_rat(1, 4), + b's' | b'S' => new_pos = pos - forward0 * Fix64::from_rat(1, 4), + b'f' | b'F' => new_pos = pos + down0 * Fix64::from_rat(1, 4), + b' ' | b'r' | b'R' => new_pos = pos - down0 * Fix64::from_rat(1, 4), b'd' | b'D' => new_pos = pos + right * Fix64::from_rat(1, 4), b'a' | b'A' => new_pos = pos - right * Fix64::from_rat(1, 4), b'i' | b'I' => phi_over_pi += Fix64::from_rat(1, 32), @@ -110,7 +136,8 @@ pub extern "C" fn main() -> ! { b'0'..=b'9' => { if let Some(prev_pos) = prev_pos { if prev_pos != pos.map(Fix64::floor) { - world.get_mut(prev_pos).unwrap().color.0 = 1 + b - b'0'; + world.get_mut(prev_pos).unwrap().color = + Some(NEW_BLOCK_COLORS[(b - b'0') as usize].to_packed()); } } } diff --git a/rust_voxels_game/src/screen.rs b/rust_voxels_game/src/screen.rs index 412a672..27b8ce8 100644 --- a/rust_voxels_game/src/screen.rs +++ b/rust_voxels_game/src/screen.rs @@ -1,43 +1,121 @@ -use crate::{console::Console, fixed::Fix64, take_once::TakeOnce}; -use core::fmt::Write; +use crate::{console::Console, fixed::Fix64, take_once::TakeOnce, vec::Vec3D}; +use core::{fmt::Write, num::NonZeroU8}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub struct Color(pub u8); +#[repr(transparent)] +pub struct PackedColor(NonZeroU8); -impl Color { - pub const fn default() -> Color { - Color(0) +impl PackedColor { + pub const R_STEPS: u32 = 7; + pub const G_STEPS: u32 = 9; + pub const B_STEPS: u32 = 4; + pub const R_MAX: u32 = Self::R_STEPS - 1; + pub const G_MAX: u32 = Self::G_STEPS - 1; + pub const B_MAX: u32 = Self::B_STEPS - 1; +} + +const _: () = { + assert!( + PackedColor::R_STEPS * PackedColor::G_STEPS * PackedColor::B_STEPS < u8::MAX as u32, + "not enough space" + ); +}; + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct RgbColor { + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl RgbColor { + pub const fn as_vec3d(self) -> Vec3D { + Vec3D { + x: self.r, + y: self.g, + z: self.b, + } + } + pub const fn from_vec3d(v: Vec3D) -> Self { + Self { + r: v.x, + g: v.y, + b: v.z, + } + } + pub const fn black() -> Self { + Self { r: 0, g: 0, b: 0 } + } + pub const fn white() -> Self { + Self { + r: 0xFF, + g: 0xFF, + b: 0xFF, + } + } + pub const fn to_packed(self) -> PackedColor { + let r = self.r as u32 * PackedColor::R_MAX / u8::MAX as u32; + let g = self.g as u32 * PackedColor::G_MAX / u8::MAX as u32; + let b = self.b as u32 * PackedColor::B_MAX / u8::MAX as u32; + let mut retval = r; + retval *= PackedColor::G_STEPS; + retval += g; + retval *= PackedColor::B_STEPS; + retval += b; + let Some(retval) = NonZeroU8::new((1 + retval) as u8) else { + unreachable!(); + }; + PackedColor(retval) + } + pub const fn from_packed(v: PackedColor) -> Self { + let mut v = v.0.get() as u32; + v -= 1; + let b = v % PackedColor::B_STEPS; + v /= PackedColor::B_STEPS; + let g = v % PackedColor::G_STEPS; + v /= PackedColor::G_STEPS; + let r = v % PackedColor::R_STEPS; + let r = r * u8::MAX as u32 / PackedColor::R_MAX; + let g = g * u8::MAX as u32 / PackedColor::G_MAX; + let b = b * u8::MAX as u32 / PackedColor::B_MAX; + Self { + r: r as u8, + g: g as u8, + b: b as u8, + } } } impl Console { - pub fn set_background_color(&mut self, color: Color) { - write!(self, "\x1B[48;5;{}m", color.0).unwrap(); + pub fn set_background_color(&mut self, color: RgbColor) { + let RgbColor { r, g, b } = color; + write!(self, "\x1B[48;2;{r};{g};{b}m").unwrap(); } - pub fn set_foreground_color(&mut self, color: Color) { - write!(self, "\x1B[38;5;{}m", color.0).unwrap(); + pub fn set_foreground_color(&mut self, color: RgbColor) { + let RgbColor { r, g, b } = color; + write!(self, "\x1B[38;2;{r};{g};{b}m").unwrap(); } } pub struct Screen { - pub pixels: [[Color; Self::X_SIZE]; Self::Y_SIZE], + pub pixels: [[RgbColor; Self::X_SIZE]; Self::Y_SIZE], } impl Screen { - pub const X_SIZE: usize = 80; - pub const Y_SIZE: usize = 50; + pub const X_SIZE: usize = 100; + pub const Y_SIZE: usize = 75; pub fn pixel_dimensions(&self) -> (Fix64, Fix64) { (Fix64::from(1), Fix64::from(1)) } pub fn take() -> &'static mut Screen { static SCREEN: TakeOnce = TakeOnce::new(Screen { - pixels: [[Color(0); Screen::X_SIZE]; Screen::Y_SIZE], + pixels: [[RgbColor { r: 0, g: 0, b: 0 }; Screen::X_SIZE]; Screen::Y_SIZE], }); SCREEN.take().expect("screen already taken") } pub fn display(&self, console: &mut Console) { - let mut last_bg = Color::default(); - let mut last_fg = Color::default(); + let mut last_bg = RgbColor::black(); + let mut last_fg = RgbColor::white(); write!(console, "\x1B[H").unwrap(); for y in (0..Self::Y_SIZE).step_by(2) { console.set_background_color(last_bg); @@ -48,7 +126,7 @@ impl Screen { .pixels .get(y + 1) .map(|row| row[x]) - .unwrap_or(Color::default()); + .unwrap_or(RgbColor::black()); if fg != last_fg { console.set_foreground_color(fg); last_fg = fg; diff --git a/rust_voxels_game/src/world.rs b/rust_voxels_game/src/world.rs index 7bfc135..812e77a 100644 --- a/rust_voxels_game/src/world.rs +++ b/rust_voxels_game/src/world.rs @@ -1,6 +1,6 @@ use crate::{ fixed::Fix64, - screen::{Color, Screen}, + screen::{PackedColor, RgbColor, Screen}, take_once::TakeOnce, vec::Vec3D, }; @@ -8,17 +8,15 @@ use core::ops::ControlFlow; #[derive(Copy, Clone, PartialEq, Eq)] pub struct Block { - pub color: Color, + pub color: Option, } impl Block { pub const fn is_empty(&self) -> bool { - self.color.0 == Color::default().0 + self.color.is_none() } pub const fn default() -> Self { - Block { - color: Color::default(), - } + Block { color: None } } } @@ -64,19 +62,84 @@ impl RayCastDimension { } impl World { - pub const SIZE: usize = 40; + pub const SIZE: usize = 50; pub const ARRAY_AXIS_ORIGIN: i64 = Self::SIZE as i64 / -2; pub const ARRAY_ORIGIN: Vec3D = Vec3D { x: Self::ARRAY_AXIS_ORIGIN, y: Self::ARRAY_AXIS_ORIGIN, z: Self::ARRAY_AXIS_ORIGIN, }; - const fn init_block(pos: Vec3D) -> Block { + const fn init_block_schematic( + mut pos: Vec3D, + center_at: Vec3D, + schematic: &[[[Block; X_SIZE]; Y_SIZE]; Z_SIZE], + ) -> Option { + pos.x -= center_at.x; + pos.y -= center_at.y; + pos.z -= center_at.z; + let x = pos.x + X_SIZE as i64 / 2; + let y = pos.y + Y_SIZE as i64 / 2; + let z = pos.z + Z_SIZE as i64 / 2; + if x >= 0 && x < X_SIZE as i64 && y >= 0 && y < Y_SIZE as i64 && z >= 0 && z < Z_SIZE as i64 + { + Some(schematic[z as usize][y as usize][x as usize]) + } else { + None + } + } + const fn init_block(pos: Vec3D, array_pos: Vec3D) -> Block { + if let Some(block) = Self::init_block_schematic( + pos, + Vec3D { x: 0, y: 0, z: 0 }, + crate::shapes::libre_soc_logo::SCHEMATIC, + ) { + return block; + } let mut block = Block { - color: Color((pos.x * 157 + pos.y * 246 + pos.z * 43 + 123) as u8), + color: Some( + RgbColor { + r: (pos.x * 0xFF / Self::SIZE as i64 + 128) as u8, + g: (pos.y * 0xFF / Self::SIZE as i64 + 128) as u8, + b: (pos.z * 0xFF / Self::SIZE as i64 + 128) as u8, + } + .to_packed(), + ), }; - const SPHERES: &[(Vec3D, i64, Color)] = &[ - (Vec3D { x: 0, y: 0, z: 0 }, 10 * 10, Color::default()), + if array_pos.x > 0 + && array_pos.x < Self::SIZE - 1 + && array_pos.y > 0 + && array_pos.y < Self::SIZE - 1 + && array_pos.z > 0 + && array_pos.z < Self::SIZE - 1 + { + block = Block::default(); + } + if pos.y == -10 { + let checker = (((pos.x ^ pos.y ^ pos.z) as u64 % 8) * 16 + 0x40) as u8; + block = Block { + color: Some( + RgbColor { + r: checker, + g: checker, + b: checker, + } + .to_packed(), + ), + }; + } + const SPHERES: &[(Vec3D, i64, Option)] = &[ + ( + Vec3D { x: 0, y: -5, z: 15 }, + 3 * 3, + Some( + RgbColor { + r: 0x80, + g: 0x80, + b: 0x80, + } + .to_packed(), + ), + ), ( Vec3D { x: -5, @@ -84,40 +147,57 @@ impl World { z: -5, }, 3 * 3, - Color(3), + Some( + RgbColor { + r: 0xFF, + g: 0, + b: 0, + } + .to_packed(), + ), ), ( Vec3D { x: -5, y: 5, - z: 5, + z: -15, }, 3 * 3, - Color(6), + Some( + RgbColor { + r: 0, + g: 0xFF, + b: 0, + } + .to_packed(), + ), ), ( - Vec3D { - x: 5, - y: 5, - z: -5, - }, + Vec3D { x: 5, y: 5, z: -5 }, 3 * 3, - Color(5), + Some( + RgbColor { + r: 0, + g: 0, + b: 0xFF, + } + .to_packed(), + ), ), ( Vec3D { x: 5, y: -5, - z: 5, + z: -15, }, 3 * 3, - Color(7), + Some(RgbColor::white().to_packed()), ), ]; let mut sphere_idx = 0; while sphere_idx < SPHERES.len() { let (sphere_pos, r_sq, sphere_color) = SPHERES[sphere_idx]; - if pos.sub_const(sphere_pos).abs_sq_const() <= r_sq { + if pos.sub_const(sphere_pos).abs_sq_const() < r_sq { block.color = sphere_color; } sphere_idx += 1; @@ -135,7 +215,8 @@ impl World { array_pos.z = 0; while array_pos.z < Self::SIZE { let pos = Self::from_array_pos(array_pos); - retval.blocks[array_pos.z][array_pos.y][array_pos.x] = Self::init_block(pos); + retval.blocks[array_pos.z][array_pos.y][array_pos.x] = + Self::init_block(pos, array_pos); array_pos.z += 1; } array_pos.y += 1; @@ -279,16 +360,34 @@ impl World { let right_factor = (Fix64::from(x as i64) - screen_x_center) * right_factor_inc; let down_factor = (Fix64::from(y as i64) - screen_y_center) * down_factor_inc; let dir = forward + right * right_factor + down * down_factor; - let mut color = Color::default(); - self.cast_ray(start, dir, |_pos, block| { + let mut color = None; + let mut prev_pos = None; + let mut delta = Vec3D { x: 0, y: 0, z: 0 }; + self.cast_ray(start, dir, |pos, block| { if block.is_empty() { + prev_pos = Some(pos); ControlFlow::Continue(()) } else { color = block.color; + if let Some(prev_pos) = prev_pos { + delta = pos - prev_pos; + } ControlFlow::Break(()) } }); - *pixel = color; + let color = color.map_or(RgbColor::black(), RgbColor::from_packed); + let factor = if delta.x != 0 { + Fix64::from_rat(3, 4) + } else if delta.y != 0 { + Fix64::from_rat(2, 3) + } else { + Fix64::from_int(1) + }; + *pixel = RgbColor::from_vec3d( + color + .as_vec3d() + .map(|v| (Fix64::from_int(v as i64) * factor).round() as u8), + ); } } }