# 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"
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]]
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"
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"]
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
../scripts/bin2hex.py $^ > $@
emu:
- cargo run
+ cargo run --release
clean:
cargo clean
| 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
-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",
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)
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()
}
--- /dev/null
+[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
--- /dev/null
+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,
+ ],
+}
+"#
+ );
+ }
+}
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" {
#[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 {}
use crate::{
fixed::Fix64,
+ screen::RgbColor,
sin_cos::sin_cos_pi,
vec::Vec3D,
world::{Block, World},
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]
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();
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;
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;
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),
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());
}
}
}
-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);
.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;
use crate::{
fixed::Fix64,
- screen::{Color, Screen},
+ screen::{PackedColor, RgbColor, Screen},
take_once::TakeOnce,
vec::Vec3D,
};
#[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 }
}
}
}
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,
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;
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;
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),
+ );
}
}
}