switch to world with libre-soc logo
authorJacob Lifshay <programmerjake@gmail.com>
Thu, 5 Oct 2023 03:59:18 +0000 (20:59 -0700)
committerJacob Lifshay <programmerjake@gmail.com>
Thu, 5 Oct 2023 04:16:23 +0000 (21:16 -0700)
12 files changed:
rust_voxels_game/Cargo.lock
rust_voxels_game/Cargo.toml
rust_voxels_game/Makefile
rust_voxels_game/README.md
rust_voxels_game/build.rs
rust_voxels_game/libre-soc-logo.mts [new file with mode: 0644]
rust_voxels_game/minetest-schematic/Cargo.toml [new file with mode: 0644]
rust_voxels_game/minetest-schematic/src/lib.rs [new file with mode: 0644]
rust_voxels_game/src/console.rs
rust_voxels_game/src/lib.rs
rust_voxels_game/src/screen.rs
rust_voxels_game/src/world.rs

index 2852db34b275a28974cd9c360b2601e0d372476f..229b42bcf29b4c30b3e23add8067e1690a0221c0 100644 (file)
@@ -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"
index c6da8e58e8c1583ba033bee9a9915e31a410a3b8..53f75fcf65bfde4bf9f277216fe018816f7df5c6 100644 (file)
@@ -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
index 162e6fff0a0d5f42c3feaa50b35e32a5393d9bba..b01ac3cd6eb0dcadc71a2772b51e0a5020aea54f 100644 (file)
@@ -45,7 +45,7 @@ rust_voxels_game.hex: rust_voxels_game.bin
        ../scripts/bin2hex.py $^ > $@
 
 emu:
-       cargo run
+       cargo run --release
 
 clean:
        cargo clean
index 9298383931171a3a2af5e9f9261954db7344e5cc..ff3d8451a557c97d3a2829aad151cfd981f80ac2 100644 (file)
@@ -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
index 16ab0def2b6977243f0611b6ea21146450d1e9fa..782bece72f08022cea3fac59c6dc276b975a77af 100644 (file)
@@ -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<Path>, 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<Path>) -> 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<Path>) -> 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 (file)
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 (file)
index 0000000..5593a81
--- /dev/null
@@ -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 (file)
index 0000000..ee4f6ed
--- /dev/null
@@ -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<Box<str>>,
+    pub nodes: Vec<MTSNode>,
+    pub y_slice_probabilities: Vec<u8>,
+}
+
+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::<MTSNode>();
+    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<usize> {
+        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<const N: usize>(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<u8, MTSError> {
+        Ok(Self::read_bytes::<1>(reader)?[0])
+    }
+    fn read_u16(reader: &mut impl io::BufRead) -> Result<u16, MTSError> {
+        Ok(u16::from_be_bytes(Self::read_bytes(reader)?))
+    }
+    fn read_string(reader: &mut impl io::BufRead) -> Result<String, MTSError> {
+        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<R: io::BufRead>(reader: &mut R, max_node_count: usize) -> Result<MTS, MTSError> {
+        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<MTSNode> = 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<MTSError> 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,
+    ],
+}
+"#
+        );
+    }
+}
index 4f418fed376adb8b4e21a84f8f70e775596fe300..18f443cb68ea04ef729ee9609ad81a7bb97aa6f1 100644 (file)
@@ -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<Console>);
 
         unsafe impl Sync for EmergencyConsole {}
index 84cdd354797ade985572051b40de00d3bfec39f4..0f59e9fa887f2d0b87a38c0afea3c1937a8383b5 100644 (file)
@@ -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());
                         }
                     }
                 }
index 412a672a1c091df0ecad42d470d22969144482fd..27b8ce8f1f5208599cc8e0c6d013cc30d3ce9389 100644 (file)
-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<u8> {
+        Vec3D {
+            x: self.r,
+            y: self.g,
+            z: self.b,
+        }
+    }
+    pub const fn from_vec3d(v: Vec3D<u8>) -> 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<Screen> = 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;
index 7bfc1357ada15dc9273d67efe1d6797276fbb531..812e77aeae48ac7ef74768c871382e47a87815df 100644 (file)
@@ -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<PackedColor>,
 }
 
 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<i64> = Vec3D {
         x: Self::ARRAY_AXIS_ORIGIN,
         y: Self::ARRAY_AXIS_ORIGIN,
         z: Self::ARRAY_AXIS_ORIGIN,
     };
-    const fn init_block(pos: Vec3D<i64>) -> Block {
+    const fn init_block_schematic<const X_SIZE: usize, const Y_SIZE: usize, const Z_SIZE: usize>(
+        mut pos: Vec3D<i64>,
+        center_at: Vec3D<i64>,
+        schematic: &[[[Block; X_SIZE]; Y_SIZE]; Z_SIZE],
+    ) -> Option<Block> {
+        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<i64>, array_pos: Vec3D<usize>) -> 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>, 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>, i64, Option<PackedColor>)] = &[
+            (
+                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),
+                );
             }
         }
     }