aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock891
-rw-r--r--Cargo.toml11
-rw-r--r--LICENSE13
-rw-r--r--README.md19
-rw-r--r--res/font.bmpbin0 -> 49290 bytes
-rw-r--r--res/map.txt24
-rw-r--r--res/textures/barrel.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/bluestone.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/colorstone.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/eagle.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/greenlight.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/greystone.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/mossy.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/pillar.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/purplestone.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/redbrick.bmpbin0 -> 12426 bytes
-rw-r--r--res/textures/wood.bmpbin0 -> 12426 bytes
-rw-r--r--src/camera.rs96
-rw-r--r--src/framebuffer.rs234
-rw-r--r--src/main.rs261
-rw-r--r--src/map.rs60
-rw-r--r--src/texture.rs81
-rw-r--r--src/util.rs45
-rw-r--r--src/vec2.rs52
25 files changed, 1788 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..cd8ad3a
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,891 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "auto_ops"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7460f7dd8e100147b82a63afca1a20eb6c231ee36b90ba7272e14951cb58af59"
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bindgen"
+version = "0.56.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
+dependencies = [
+ "bitflags",
+ "cexpr",
+ "clang-sys",
+ "clap",
+ "env_logger",
+ "lazy_static",
+ "lazycell",
+ "log",
+ "peeking_take_while",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "rustc-hash",
+ "shlex",
+ "which",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "bumpalo"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
+
+[[package]]
+name = "cc"
+version = "1.0.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
+
+[[package]]
+name = "cexpr"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27"
+dependencies = [
+ "nom 5.1.2",
+]
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clang-sys"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c"
+dependencies = [
+ "glob",
+ "libc",
+ "libloading",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "cmake"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "downcast-rs"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17392a012ea30ef05a610aa97dfb49496e71c9f676b27879922ea5bdf60d9d3f"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d34cfa13a63ae058bfa601fe9e313bbdb3746427c1459185464ce0fcf62e1e8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "redox_syscall",
+ "winapi",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
+dependencies = [
+ "cfg-if 1.0.0",
+ "crc32fast",
+ "libc",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "js-sys"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "lazycell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+
+[[package]]
+name = "libc"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
+
+[[package]]
+name = "libloading"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
+dependencies = [
+ "cfg-if 1.0.0",
+ "winapi",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "maybe-uninit"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
+
+[[package]]
+name = "memchr"
+version = "2.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+
+[[package]]
+name = "minifb"
+version = "0.19.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b6e41119d1667465608d36488fa5dcd228057a26c156e25f17f492f38435124"
+dependencies = [
+ "cc",
+ "orbclient",
+ "raw-window-handle",
+ "tempfile",
+ "wayland-client",
+ "wayland-cursor",
+ "wayland-protocols",
+ "winapi",
+ "x11-dl",
+ "xkb",
+ "xkbcommon-sys",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
+dependencies = [
+ "adler",
+ "autocfg",
+]
+
+[[package]]
+name = "nix"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
+dependencies = [
+ "bitflags",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+]
+
+[[package]]
+name = "nom"
+version = "5.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
+dependencies = [
+ "memchr",
+ "version_check",
+]
+
+[[package]]
+name = "nom"
+version = "6.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
+dependencies = [
+ "memchr",
+ "version_check",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
+
+[[package]]
+name = "orbclient"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c976c5018e7f1db4359616d8b31ef8ae7d9649b11803c0b38fff67fd2999fc8"
+dependencies = [
+ "libc",
+ "raw-window-handle",
+ "redox_syscall",
+ "sdl2",
+ "sdl2-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "peeking_take_while"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "raw-window-handle"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "sdl2"
+version = "0.34.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "505d7a6ef5f96289a6ec50fc8b65ec75f5571f0aa94fa6ea230f6b228fa05d57"
+dependencies = [
+ "bitflags",
+ "lazy_static",
+ "libc",
+ "raw-window-handle",
+ "sdl2-sys",
+]
+
+[[package]]
+name = "sdl2-sys"
+version = "0.34.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cb164f53dbcad111de976bbf1f3083d3fcdeda88da9cfa281c70822720ee3da"
+dependencies = [
+ "cfg-if 0.1.10",
+ "cmake",
+ "flate2",
+ "libc",
+ "tar",
+ "unidiff",
+ "version-compare",
+]
+
+[[package]]
+name = "shlex"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2"
+
+[[package]]
+name = "smallvec"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
+
+[[package]]
+name = "soft-raycasting-demo"
+version = "0.1.0"
+dependencies = [
+ "auto_ops",
+ "minifb",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "syn"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fe99c6bd8b1cc636890bcc071842de909d902c81ac7dab53ba33c421ab8ffb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0bcfbd6a598361fda270d82469fff3d65089dc33e175c9a131f7b4cd395f228"
+dependencies = [
+ "filetime",
+ "libc",
+ "xattr",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
+
+[[package]]
+name = "unidiff"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8a62719acf1933bfdbeb73a657ecd9ecece70b405125267dd549e2e2edc232c"
+dependencies = [
+ "encoding_rs",
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version-compare"
+version = "0.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d63556a25bae6ea31b52e640d7c41d1ab27faba4ccb600013837a3d0b3994ca1"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
+
+[[package]]
+name = "wayland-client"
+version = "0.28.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ca44d86554b85cf449f1557edc6cc7da935cc748c8e4bf1c507cbd43bae02c"
+dependencies = [
+ "bitflags",
+ "downcast-rs",
+ "libc",
+ "nix",
+ "wayland-commons",
+ "wayland-scanner",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-commons"
+version = "0.28.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bd75ae380325dbcff2707f0cd9869827ea1d2d6d534cff076858d3f0460fd5a"
+dependencies = [
+ "nix",
+ "once_cell",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-cursor"
+version = "0.28.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b37e5455ec72f5de555ec39b5c3704036ac07c2ecd50d0bffe02d5fe2d4e65ab"
+dependencies = [
+ "nix",
+ "wayland-client",
+ "xcursor",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.28.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95df3317872bcf9eec096c864b69aa4769a1d5d6291a5b513f8ba0af0efbd52c"
+dependencies = [
+ "bitflags",
+ "wayland-client",
+ "wayland-commons",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.28.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "389d680d7bd67512dc9c37f39560224327038deb0f0e8d33f870900441b68720"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "xml-rs",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.28.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2907bd297eef464a95ba9349ea771611771aa285b932526c633dc94d5400a8e2"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "which"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "x11-dl"
+version = "2.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bf981e3a5b3301209754218f962052d4d9ee97e478f4d26d4a6eced34c1fef8"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "maybe-uninit",
+ "pkg-config",
+]
+
+[[package]]
+name = "xattr"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "xcursor"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a9a231574ae78801646617cefd13bfe94be907c0e4fa979cfd8b770aa3c5d08"
+dependencies = [
+ "nom 6.1.2",
+]
+
+[[package]]
+name = "xkb"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aec02bc5de902aa579f3d2f2c522edaf40fa42963cbaffe645b058ddcc68fdb2"
+dependencies = [
+ "bitflags",
+ "libc",
+ "xkbcommon-sys",
+]
+
+[[package]]
+name = "xkbcommon-sys"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59a001b79d45b0b4541c228a501177f2b35db976bf7ee3f7fce8fa2381554ab5"
+dependencies = [
+ "bindgen",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "xml-rs"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..5016e0e
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "soft-raycasting-demo"
+version = "0.1.0"
+authors = ["matthew"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+auto_ops = "0.3"
+minifb = "0.19.3"
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..456c488
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2e07e71
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+# soft-raycasting-demo
+
+A simple (and slightly messy) software ray casting renderer, written in Rust.
+It uses [rust_minifb](https://github.com/emoon/rust_minifb) to create a window within which to render and capture input.
+Here's some of it's features:
+
+* A cell based layout system.
+* Affine texture mapped walls, floors and ceilings, with each cell able to have unique textures.
+* A simple font renderer, used for an FPS display.
+* Primitive BMP parsing for fonts and textures.
+* Simple lighting effect based on the alignment of a cell's walls.
+* Sprite rendering.
+* Adjustable camera height.
+* A 2D Z-buffer.
+* Thin wall support, including transparency.
+* Per-tile fog (but not volumetric).
+
+Based on [Lode Vandevenne's graphics tutorials](https://lodev.org/cgtutor/).
+All code is WTFPL licensed, and the assets in res/textures are the property of ID Software.
diff --git a/res/font.bmp b/res/font.bmp
new file mode 100644
index 0000000..a2368f0
--- /dev/null
+++ b/res/font.bmp
Binary files differ
diff --git a/res/map.txt b/res/map.txt
new file mode 100644
index 0000000..5e92541
--- /dev/null
+++ b/res/map.txt
@@ -0,0 +1,24 @@
+4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,7,7,7,7,7,7,7,7,
+4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,7,
+4,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,
+4,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,
+4,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,7,
+4,0,4,0,0,0,0,5,5,5,5,5,5,5,5,5,7,7,0,7,7,7,7,7,
+4,0,5,0,0,0,0,5,0,5,0,5,0,5,0,5,7,0,0,0,7,7,7,1,
+4,0,6,0,0,0,0,5,0,0,0,0,0,0,0,5,7,0,0,0,0,0,0,8,
+4,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,1,
+4,0,8,0,0,0,0,5,0,0,0,0,0,0,0,5,7,0,0,0,0,0,0,8,
+4,0,0,0,0,0,0,5,0,0,0,0,0,0,0,5,7,0,0,0,7,7,7,1,
+4,0,0,0,0,0,0,5,5,5,5,0,5,5,5,5,7,7,7,7,7,7,7,1,
+6,6,6,6,6,6,6,6,6,6,6,0,6,6,6,6,6,6,6,6,6,6,6,6,
+8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,
+6,6,6,6,6,6,0,6,6,6,6,0,6,6,6,6,6,6,6,6,6,6,6,6,
+4,4,4,4,4,4,0,4,4,4,6,0,6,2,2,2,2,2,2,2,3,3,3,3,
+4,0,0,0,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,0,0,0,2,
+4,0,0,0,0,0,0,0,0,0,0,0,6,2,0,0,5,0,0,2,0,0,0,2,
+4,0,0,0,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,2,0,2,2,
+4,0,6,0,6,0,0,0,0,4,6,0,0,0,0,0,5,0,0,0,0,0,0,2,
+4,0,0,5,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,2,0,2,2,
+4,0,6,0,6,0,0,0,0,4,6,0,6,2,0,0,5,0,0,2,0,0,0,2,
+4,0,0,0,0,0,0,0,0,4,6,0,6,2,0,0,0,0,0,2,0,0,0,2,
+4,4,4,4,4,4,4,4,4,4,1,1,1,2,2,2,2,2,2,3,3,3,3,3
diff --git a/res/textures/barrel.bmp b/res/textures/barrel.bmp
new file mode 100644
index 0000000..7410861
--- /dev/null
+++ b/res/textures/barrel.bmp
Binary files differ
diff --git a/res/textures/bluestone.bmp b/res/textures/bluestone.bmp
new file mode 100644
index 0000000..4eaa14c
--- /dev/null
+++ b/res/textures/bluestone.bmp
Binary files differ
diff --git a/res/textures/colorstone.bmp b/res/textures/colorstone.bmp
new file mode 100644
index 0000000..f9cc4e4
--- /dev/null
+++ b/res/textures/colorstone.bmp
Binary files differ
diff --git a/res/textures/eagle.bmp b/res/textures/eagle.bmp
new file mode 100644
index 0000000..1d7f659
--- /dev/null
+++ b/res/textures/eagle.bmp
Binary files differ
diff --git a/res/textures/greenlight.bmp b/res/textures/greenlight.bmp
new file mode 100644
index 0000000..5ef3c11
--- /dev/null
+++ b/res/textures/greenlight.bmp
Binary files differ
diff --git a/res/textures/greystone.bmp b/res/textures/greystone.bmp
new file mode 100644
index 0000000..c0248a4
--- /dev/null
+++ b/res/textures/greystone.bmp
Binary files differ
diff --git a/res/textures/mossy.bmp b/res/textures/mossy.bmp
new file mode 100644
index 0000000..534bfcb
--- /dev/null
+++ b/res/textures/mossy.bmp
Binary files differ
diff --git a/res/textures/pillar.bmp b/res/textures/pillar.bmp
new file mode 100644
index 0000000..77ed575
--- /dev/null
+++ b/res/textures/pillar.bmp
Binary files differ
diff --git a/res/textures/purplestone.bmp b/res/textures/purplestone.bmp
new file mode 100644
index 0000000..5cf6a23
--- /dev/null
+++ b/res/textures/purplestone.bmp
Binary files differ
diff --git a/res/textures/redbrick.bmp b/res/textures/redbrick.bmp
new file mode 100644
index 0000000..2d64460
--- /dev/null
+++ b/res/textures/redbrick.bmp
Binary files differ
diff --git a/res/textures/wood.bmp b/res/textures/wood.bmp
new file mode 100644
index 0000000..d2e545e
--- /dev/null
+++ b/res/textures/wood.bmp
Binary files differ
diff --git a/src/camera.rs b/src/camera.rs
new file mode 100644
index 0000000..b158dee
--- /dev/null
+++ b/src/camera.rs
@@ -0,0 +1,96 @@
+use crate::map::{Map, MapCell};
+use crate::util::{Side, Step};
+use crate::vec2::Vec2;
+
+use minifb::{Key, Window};
+
+pub struct Intersection {
+ pub side: Side,
+ pub step: Vec2<Step>,
+ pub map_coordinates: Vec2<usize>,
+ pub wall_offset: Vec2<f64>,
+}
+
+pub struct Ray {
+ pub direction: Vec2<f64>,
+ pub intersections: Vec<Intersection>,
+}
+
+pub struct Camera {
+ pub position: Vec2<f64>,
+ pub direction: Vec2<f64>,
+ pub plane: Vec2<f64>,
+ pub height: f64,
+}
+
+impl Camera {
+ pub fn get_ray(&self, x: usize, screen_width: usize) -> Ray {
+ let camera_x: f64 = 2.0 * (x as f64) / (screen_width as f64) - 1.0;
+ Ray {
+ direction: &self.direction + &self.plane * camera_x,
+ intersections: Vec::new(),
+ }
+ }
+
+ pub fn update_position_with_keys(&mut self, delta: f64, window: &Window, world: &Map) {
+ let move_speed = delta * 5.0;
+ let rot_speed = delta * 3.0;
+
+ if window.is_key_down(Key::W) {
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position + &self.direction * move_speed).as_usize())
+ {
+ self.position += &self.direction * move_speed;
+ }
+ }
+ if window.is_key_down(Key::S) {
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position - &self.direction * move_speed).as_usize())
+ {
+ self.position -= &self.direction * move_speed;
+ }
+ }
+ if window.is_key_down(Key::A) {
+ let mut direction = self.direction.clone();
+ direction.rotate(-std::f64::consts::PI / 2.0);
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position - &direction * move_speed).as_usize())
+ {
+ self.position -= &direction * (move_speed / 1.5);
+ }
+ }
+ if window.is_key_down(Key::D) {
+ let mut direction = self.direction.clone();
+ direction.rotate(std::f64::consts::PI / 2.0);
+ if let Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) = world.at(&(&self.position - &direction * move_speed).as_usize())
+ {
+ self.position -= &direction * (move_speed / 1.5);
+ }
+ }
+ if window.is_key_down(Key::Left) {
+ self.direction.rotate(rot_speed);
+ self.plane.rotate(rot_speed);
+ }
+ if window.is_key_down(Key::Right) {
+ self.direction.rotate(-rot_speed);
+ self.plane.rotate(-rot_speed);
+ }
+ }
+}
diff --git a/src/framebuffer.rs b/src/framebuffer.rs
new file mode 100644
index 0000000..5a250d7
--- /dev/null
+++ b/src/framebuffer.rs
@@ -0,0 +1,234 @@
+use crate::texture::Font;
+use crate::map::{MapCell, Map};
+use crate::camera::{Ray, Camera};
+use crate::util::{Side, Sprite};
+use crate::vec2::Vec2;
+
+pub struct Framebuffer {
+ pub height: usize,
+ pub width: usize,
+ pub pixels: Vec<u32>,
+ pub z_buffer: Vec<f64>,
+}
+
+impl Framebuffer {
+ pub fn new(height: usize, width: usize) -> Framebuffer {
+ Framebuffer {
+ height,
+ width,
+ pixels: vec![0; height * width],
+ z_buffer: vec![f64::INFINITY; height * width],
+ }
+ }
+
+ pub fn clear(&mut self) {
+ for pixel in &mut self.pixels {
+ *pixel = 0;
+ }
+ }
+
+ pub fn clear_z_buffer(&mut self) {
+ for pixel in &mut self.z_buffer {
+ *pixel = f64::INFINITY;
+ }
+ }
+
+ pub fn draw_vertical_line(&mut self, x: usize, start: usize, stop: usize, color: u32) {
+ for row in start..stop {
+ self.pixels[row * self.width + x] = color;
+ }
+ }
+
+ pub fn write_ascii_string(&mut self, x: usize, y: usize, string: &Vec<u8>, font: &Font, color: u32) {
+ for char_index in 0..string.len() {
+ for glyph_x in 0..font.glyph_size {
+ for glyph_y in 0..font.glyph_size {
+ if font.glyphs[(font.glyph_size - 1 - glyph_y) * (font.charset_length * font.glyph_size) + glyph_x + font.glyph_size * (string[char_index] as usize)] {
+ self.pixels[((y + glyph_y) * self.width) + x + (char_index * font.glyph_size) + glyph_x] = color;
+ }
+ }
+ }
+ }
+ }
+
+ pub fn set_pixel(&mut self, x: usize, y: usize, color: u32) {
+ if let Some(pixel) = self.pixels.get_mut(y * self.width + x) {
+ *pixel = color;
+ }
+ }
+
+ pub fn draw_wall(&mut self, camera: &Camera, x: usize, perp_wall_dist: f64, cell: &MapCell, side: &Side, ray: &Ray, world: &Map) {
+ let line_height = (self.height as f64 / perp_wall_dist) as i32;
+ if line_height < 0 {
+ self.draw_vertical_line(x, 0, self.height, 0x00FF0000);
+ return;
+ }
+ let draw_start = ((-line_height / 2 + (self.height as i32) / 2) - 1 + ((camera.height / perp_wall_dist) as i32)).max(0);
+ let draw_end = ((line_height / 2 + (self.height as i32) / 2) + 1 + ((camera.height / perp_wall_dist) as i32)).min(self.height as i32);
+
+ match cell {
+ MapCell::Wall { texture } | MapCell::ThinWall { texture, orientation: _, offset_into_cell: _, ceiling_texture: _, floor_texture: _ } => {
+ let wall_x = match &side {
+ Side::X => camera.position.y + perp_wall_dist * ray.direction.y,
+ Side::Y=> camera.position.x + perp_wall_dist * ray.direction.x,
+ }.fract();
+
+ let mut tex_x = (wall_x * (texture.width as f64)) as usize;
+ if let Side::X = side {
+ if ray.direction.x > 0.0 {
+ tex_x = texture.width - tex_x - 1;
+ }
+ } else {
+ if ray.direction.y < 0.0 {
+ tex_x = texture.width - tex_x - 1;
+ }
+ }
+ let step = (texture.height as f64) / (line_height as f64);
+ let mut tex_position = ((draw_start as f64) - (camera.height / perp_wall_dist) - (self.height as f64) / 2.0 + (line_height as f64) / 2.0) * step;
+ for y in draw_start..draw_end {
+ let tex_y = (tex_position as usize) & (texture.height - 1);
+ tex_position += step;
+ if perp_wall_dist < self.z_buffer[y as usize * self.width + x as usize] {
+ let mut color = texture.data[texture.height * tex_y + tex_x];
+ if (color & 0x00FFFFFF) != 0 {
+ if let Side::Y = side {
+ color = (color >> 1) & 8355711;
+ }
+ if let Some(MapCell::Empty { ceiling_texture: _, floor_texture: _, fog, fog_color }) = world.at(&camera.position.as_usize()) {
+ let fog_prop = (perp_wall_dist * fog).min(1.0);
+ if fog_prop > 0.0 {
+ let mut color_bytes = color.to_le_bytes();
+ let fog_bytes = fog_color.to_le_bytes();
+ color_bytes[0] = (fog_bytes[0] as f64 * fog_prop + color_bytes[0] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[1] = (fog_bytes[1] as f64 * fog_prop + color_bytes[1] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[2] = (fog_bytes[2] as f64 * fog_prop + color_bytes[2] as f64 * (1.0 - fog_prop)) as u8;
+ color = u32::from_le_bytes(color_bytes);
+ }
+ }
+ self.set_pixel(x, y as usize, color);
+ self.z_buffer[y as usize * self.width + x as usize] = perp_wall_dist;
+ }
+ }
+ }
+ },
+ MapCell::Empty { ceiling_texture: _, floor_texture: _, fog: _, fog_color: _ } => {},
+ }
+ }
+
+ pub fn draw_sprites(&mut self, camera: &Camera, sprites: &mut Vec<Sprite>, world: &Map) {
+ for sprite in sprites.into_iter() {
+ sprite.distance_from_camera = (&camera.position - &sprite.position).length();
+ }
+ for sprite in sprites {
+ let rel_position = &sprite.position - &camera.position;
+ let inverse_det = 1.0 / (camera.plane.x * camera.direction.y - camera.direction.x * camera.plane.y);
+ let transform = Vec2 {
+ x: inverse_det * (camera.direction.y * rel_position.x - camera.direction.x * rel_position.y),
+ y: inverse_det * (-camera.plane.y * rel_position.x + camera.plane.x * rel_position.y)
+ };
+ if transform.y == 0.0 {
+ continue;
+ }
+ let vertical_offset = ((sprite.vertical_offset / transform.y) + (camera.height / transform.y)) as i32;
+ let sprite_screen_x = ((self.width as f64 / 2.0) * (1.0 + transform.x / transform.y)) as i32;
+ let sprite_height = (((self.height as f64 / transform.y) as i32).abs() as f64 * sprite.scale_factor.y) as i32;
+ let sprite_width = (((self.height as f64 / transform.y) as i32).abs() as f64 * sprite.scale_factor.x) as i32;
+ let draw_start = Vec2 {
+ x: ((-sprite_width / 2) + sprite_screen_x).max(0),
+ y: ((-sprite_height / 2 + (self.height as i32) / 2) + vertical_offset).max(0),
+ };
+ let draw_end = Vec2 {
+ x: ((sprite_width / 2) + sprite_screen_x).min(self.width as i32),
+ y: ((sprite_height / 2 + (self.height as i32) / 2) + vertical_offset).min(self.height as i32),
+ };
+ for column in draw_start.x..draw_end.x {
+ let tex_x = (256 * (column - (-sprite_width / 2 + (sprite_screen_x))) * sprite.texture.width as i32 / sprite_width) as i32 / 256;
+ if transform.y > 0.0 && column >= 0 && column < self.width as i32 {
+ for y in draw_start.y..draw_end.y {
+ let d = (y - vertical_offset) * 256 - self.height as i32 * 128 + sprite_height * 128;
+ let tex_y = ((d * sprite.texture.height as i32) / sprite_height) / 256;
+ if (tex_x as usize) < sprite.texture.width && (tex_y as usize) < sprite.texture.height {
+ let mut color = sprite.texture.data[sprite.texture.width * tex_y as usize + tex_x as usize];
+ if (color & 0x00FFFFFF) != 0 {
+ if let Some(&depth) = self.z_buffer.get(y as usize * self.width + column as usize) {
+ if sprite.distance_from_camera < depth {
+ if let Some(MapCell::Empty { ceiling_texture: _, floor_texture: _, fog, fog_color }) = world.at(&camera.position.as_usize()) {
+ let fog_prop = (sprite.distance_from_camera * fog).min(1.0);
+ if fog_prop > 0.0 {
+ let mut color_bytes = color.to_le_bytes();
+ let fog_bytes = fog_color.to_le_bytes();
+ color_bytes[0] = (fog_bytes[0] as f64 * fog_prop + color_bytes[0] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[1] = (fog_bytes[1] as f64 * fog_prop + color_bytes[1] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[2] = (fog_bytes[2] as f64 * fog_prop + color_bytes[2] as f64 * (1.0 - fog_prop)) as u8;
+ color = u32::from_le_bytes(color_bytes);
+ }
+ }
+ self.set_pixel(column as usize, y as usize, color);
+ self.z_buffer[y as usize * self.width + column as usize] = sprite.distance_from_camera;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pub fn draw_floor_and_ceiling(&mut self, camera: &Camera, world: &Map) {
+ for y in 0..self.height {
+ let is_floor = y > self.height / 2;
+ let ray_dir_0 = &camera.direction - &camera.plane;
+ let ray_dir_1 = &camera.direction + &camera.plane;
+
+ let current_position = if is_floor {
+ (y as i32) - (self.height as i32) / 2
+ } else {
+ (self.height as i32) / 2 - (y as i32)
+ };
+ let camera_z = if is_floor {
+ 0.5 * self.height as f64 + camera.height
+ } else {
+ 0.5 * self.height as f64 - camera.height
+ };
+ let row_distance = camera_z / current_position as f64;
+
+ let floor_step = row_distance * (&ray_dir_1 - &ray_dir_0) / self.width as f64;
+ let mut floor = &camera.position + row_distance * &ray_dir_0;
+
+ for x in 0..self.width {
+ let cell = floor.as_usize();
+ floor += &floor_step;
+ match world.at(&cell) {
+ Some(MapCell::Empty { ceiling_texture, floor_texture, fog: _, fog_color: _ }) | Some(MapCell::ThinWall { texture: _, orientation: _, offset_into_cell: _, ceiling_texture, floor_texture }) => {
+ let texture_coords = Vec2 {
+ // this is also the ceiling size, but we only check the floor size
+ x: ((floor_texture.width as f64 * (floor.x - cell.x as f64)) as usize & (floor_texture.width - 1)) as i32,
+ y: ((floor_texture.height as f64 * (floor.y - cell.y as f64)) as usize & (floor_texture.height - 1)) as i32,
+ };
+
+ let mut color = if is_floor {
+ (floor_texture.data[(floor_texture.width as i32 * texture_coords.y + texture_coords.x) as usize] >> 1) & 8355711
+ } else {
+ (ceiling_texture.data[(ceiling_texture.width as i32 * texture_coords.y + texture_coords.x) as usize] >> 1) & 8355711
+ };
+ if let Some(MapCell::Empty { ceiling_texture: _, floor_texture: _, fog, fog_color }) = world.at(&camera.position.as_usize()) {
+ let fog_prop = ((&floor - &camera.position).length() * fog).min(1.0);
+ if fog_prop > 0.0 {
+ let mut color_bytes = color.to_le_bytes();
+ let fog_bytes = fog_color.to_le_bytes();
+ color_bytes[0] = (fog_bytes[0] as f64 * fog_prop + color_bytes[0] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[1] = (fog_bytes[1] as f64 * fog_prop + color_bytes[1] as f64 * (1.0 - fog_prop)) as u8;
+ color_bytes[2] = (fog_bytes[2] as f64 * fog_prop + color_bytes[2] as f64 * (1.0 - fog_prop)) as u8;
+ color = u32::from_le_bytes(color_bytes);
+ }
+ }
+ self.pixels[y * self.width + x] = color;
+ },
+ Some(MapCell::Wall { texture: _ }) => {},
+ None => {},
+ }
+ }
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..ff2ca7a
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,261 @@
+mod camera;
+mod framebuffer;
+mod map;
+mod texture;
+mod util;
+mod vec2;
+
+use minifb::{Window, WindowOptions};
+
+use camera::{Camera, Intersection};
+use framebuffer::Framebuffer;
+use map::{Map, MapCell};
+use texture::{Font, Texture};
+use util::{Orientation, Side, Sprite, Step};
+use vec2::Vec2;
+
+use std::rc::Rc;
+use std::time::Instant;
+
+fn main() {
+ let mut framebuffer = Framebuffer::new(600, 800);
+ let mut camera = Camera {
+ position: Vec2 { x: 3.0, y: 12.0 },
+ direction: Vec2 { x: -1.0, y: 0.0 },
+ plane: Vec2 {
+ x: 0.0,
+ y: (framebuffer.width as f64 / framebuffer.height as f64) / 2.0,
+ },
+ height: 0.0,
+ };
+
+ let mut time = Instant::now();
+ let mut old_time: Instant;
+
+ let font = Font::load_from_bmp(&include_bytes!("../res/font.bmp").to_vec(), 8);
+ let textures: Vec<Rc<Texture>> = vec![
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/eagle.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/redbrick.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/purplestone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/greystone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/bluestone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/mossy.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/wood.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/colorstone.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/barrel.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/pillar.bmp").to_vec(),
+ )),
+ Rc::new(Texture::load_from_bmp(
+ &include_bytes!("../res/textures/greenlight.bmp").to_vec(),
+ )),
+ ];
+
+ let world = Map::new(&textures);
+
+ let mut sprites = vec![
+ Sprite {
+ position: Vec2 { x: 3.0, y: 8.0 },
+ texture: textures[8].clone(),
+ scale_factor: Vec2 { x: 1.0, y: 1.0 },
+ vertical_offset: 0.0,
+ distance_from_camera: 0.0,
+ },
+ Sprite {
+ position: Vec2 { x: 3.0, y: 6.0 },
+ texture: textures[8].clone(),
+ scale_factor: Vec2 { x: 1.5, y: 1.5 },
+ vertical_offset: -150.0,
+ distance_from_camera: 0.0,
+ },
+ ];
+
+ let mut window = Window::new(
+ "Raycasting Demo",
+ framebuffer.width,
+ framebuffer.height,
+ WindowOptions::default(),
+ )
+ .unwrap();
+ window.limit_update_rate(Some(std::time::Duration::from_micros(16600)));
+
+ while window.is_open() {
+ framebuffer.draw_floor_and_ceiling(&camera, &world);
+ for x in 0..framebuffer.width {
+ let mut side_dist = Vec2::<f64>::new();
+ let mut ray = camera.get_ray(x, framebuffer.width);
+ let mut map = camera.position.as_usize();
+ let delta_dist = Vec2 {
+ x: (1.0 / ray.direction.x).abs(),
+ y: (1.0 / ray.direction.y).abs(),
+ };
+ let mut side: Side;
+ let step = Vec2 {
+ x: Step::from(ray.direction.x < 0.0),
+ y: Step::from(ray.direction.y < 0.0),
+ };
+ if ray.direction.x < 0.0 {
+ side_dist.x = (camera.position.x - (map.x as f64)) * delta_dist.x;
+ } else {
+ side_dist.x = ((map.x as f64) + 1.0 - camera.position.x) * delta_dist.x;
+ }
+ if ray.direction.y < 0.0 {
+ side_dist.y = (camera.position.y - (map.y as f64)) * delta_dist.y;
+ } else {
+ side_dist.y = ((map.y as f64) + 1.0 - camera.position.y) * delta_dist.y;
+ }
+
+ loop {
+ if side_dist.x < side_dist.y {
+ side_dist.x += delta_dist.x;
+ match step.x {
+ Step::Left => map.x -= 1,
+ Step::Right => map.x += 1,
+ }
+ side = Side::X;
+ } else {
+ side_dist.y += delta_dist.y;
+ match step.y {
+ Step::Left => map.y -= 1,
+ Step::Right => map.y += 1,
+ }
+ side = Side::Y;
+ }
+ match world.at(&map) {
+ Some(MapCell::Wall { texture }) => {
+ ray.intersections.push(Intersection {
+ side: side.clone(),
+ step: step.clone(),
+ map_coordinates: map.clone(),
+ wall_offset: Vec2 { x: 0.0, y: 0.0 },
+ });
+ if !texture.has_transparency {
+ break;
+ }
+ }
+ Some(MapCell::ThinWall {
+ texture,
+ orientation,
+ offset_into_cell,
+ ceiling_texture: _,
+ floor_texture: _,
+ }) => match orientation {
+ Orientation::XAxis => {
+ if side_dist.x - (delta_dist.x / (1.0 / offset_into_cell)) > side_dist.y
+ {
+ continue;
+ } else {
+ ray.intersections.push(Intersection {
+ side: Side::X,
+ step: step.clone(),
+ map_coordinates: map.clone(),
+ wall_offset: Vec2 {
+ x: offset_into_cell * step.x.value() as f64,
+ y: 0.0,
+ },
+ });
+ if !texture.has_transparency {
+ break;
+ }
+ }
+ }
+ Orientation::YAxis => {
+ if side_dist.y - (delta_dist.y / (1.0 / offset_into_cell)) > side_dist.x
+ {
+ continue;
+ } else {
+ ray.intersections.push(Intersection {
+ side: Side::Y,
+ step: step.clone(),
+ map_coordinates: map.clone(),
+ wall_offset: Vec2 {
+ x: 0.0,
+ y: offset_into_cell * step.y.value() as f64,
+ },
+ });
+ if !texture.has_transparency {
+ break;
+ }
+ }
+ }
+ },
+ Some(MapCell::Empty {
+ ceiling_texture: _,
+ floor_texture: _,
+ fog: _,
+ fog_color: _,
+ }) => continue,
+ None => break,
+ }
+ }
+ for intersection in &ray.intersections {
+ let perp_wall_dist = match &intersection.side {
+ Side::X => {
+ ((intersection.map_coordinates.x as f64) - camera.position.x
+ + intersection.wall_offset.x
+ + ((1 - intersection.step.x.value()) as f64) / 2.0)
+ / ray.direction.x
+ }
+ Side::Y => {
+ ((intersection.map_coordinates.y as f64) - camera.position.y
+ + intersection.wall_offset.y
+ + ((1 - intersection.step.y.value()) as f64) / 2.0)
+ / ray.direction.y
+ }
+ };
+ if let Some(cell) = world.at(&intersection.map_coordinates) {
+ framebuffer.draw_wall(
+ &camera,
+ x,
+ perp_wall_dist,
+ cell,
+ &intersection.side,
+ &ray,
+ &world,
+ );
+ }
+ }
+ }
+ framebuffer.draw_sprites(&camera, &mut sprites, &world);
+ old_time = time;
+ time = Instant::now();
+ let frame_time = (time - old_time).as_secs_f64();
+ framebuffer.write_ascii_string(
+ 0,
+ 0,
+ &format!("{:.3}", (1.0 / frame_time)).into_bytes(),
+ &font,
+ 0x00FFFFFF,
+ );
+ framebuffer.write_ascii_string(
+ 0,
+ font.glyph_size,
+ &format!("x: {:.3}, y: {:.3}", camera.position.x, camera.position.y).into_bytes(),
+ &font,
+ 0x00FFFFFF,
+ );
+ camera.update_position_with_keys(frame_time, &window, &world);
+ window
+ .update_with_buffer(&framebuffer.pixels, framebuffer.width, framebuffer.height)
+ .unwrap();
+ framebuffer.clear_z_buffer();
+ }
+}
diff --git a/src/map.rs b/src/map.rs
new file mode 100644
index 0000000..b4ac57f
--- /dev/null
+++ b/src/map.rs
@@ -0,0 +1,60 @@
+use std::rc::Rc;
+
+use crate::vec2::Vec2;
+use crate::util::Orientation;
+use crate::texture::Texture;
+
+pub enum MapCell {
+ Empty {
+ ceiling_texture: Rc<Texture>,
+ floor_texture: Rc<Texture>,
+ fog: f64,
+ fog_color: u32,
+ },
+ Wall {
+ texture: Rc<Texture>,
+ },
+ ThinWall {
+ texture: Rc<Texture>,
+ orientation: Orientation,
+ offset_into_cell: f64,
+ ceiling_texture: Rc<Texture>,
+ floor_texture: Rc<Texture>,
+ },
+}
+
+pub struct Map {
+ width: usize,
+ height: usize,
+ cells: Vec<MapCell>,
+}
+
+impl Map {
+ pub fn new(texture_atlas: &Vec<Rc<Texture>>) -> Map {
+ let layout = include_str!("../res/map.txt").replace("\n", "");
+ let mut cells = Vec::with_capacity(layout.len());
+ for cell in layout.split(',') {
+ match cell {
+ "0" => cells.push(MapCell::Empty { ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone(), fog: 0.08, fog_color: 0x00000000 }),
+ _ => cells.push(MapCell::Wall { texture: texture_atlas[cell.parse::<usize>().unwrap() - 1].clone() }),
+ }
+ }
+ cells[5 * 24 + 9] = MapCell::ThinWall { texture: texture_atlas[6].clone(), orientation: Orientation::XAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ cells[5 * 24 + 10] = MapCell::ThinWall { texture: texture_atlas[5].clone(), orientation: Orientation::XAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ cells[5 * 24 + 11] = MapCell::ThinWall { texture: texture_atlas[4].clone(), orientation: Orientation::XAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ cells[3 * 24 + 2] = MapCell::ThinWall { texture: texture_atlas[9].clone(), orientation: Orientation::YAxis, offset_into_cell: 0.5, ceiling_texture: texture_atlas[6].clone(), floor_texture: texture_atlas[3].clone() };
+ Map {
+ width: 24,
+ height: 24,
+ cells,
+ }
+ }
+
+ pub fn at(&self, position: &Vec2<usize>) -> Option<&MapCell> {
+ if position.x < self.height && position.y < self.width {
+ self.cells.get(position.x * self.width + position.y)
+ } else {
+ None
+ }
+ }
+}
diff --git a/src/texture.rs b/src/texture.rs
new file mode 100644
index 0000000..2751b7a
--- /dev/null
+++ b/src/texture.rs
@@ -0,0 +1,81 @@
+pub struct Font {
+ pub charset_length: usize,
+ pub glyph_size: usize,
+ pub glyphs: Vec<bool>,
+}
+
+impl Font {
+ pub fn load_from_bmp(bmp_data: &Vec<u8>, glyph_size: usize) -> Font {
+ let data_position = u32::from_le_bytes([
+ bmp_data[0x0A],
+ bmp_data[0x0B],
+ bmp_data[0x0C],
+ bmp_data[0x0D],
+ ]);
+ let mut glyphs = Vec::new();
+ // step_by 3 assuming 24-bit depth
+ for byte_index in ((data_position as usize)..bmp_data.len()).step_by(3) {
+ glyphs.push(bmp_data[byte_index] == 0xFF);
+ }
+ Font {
+ charset_length: 256,
+ glyph_size,
+ glyphs,
+ }
+ }
+}
+
+pub struct Texture {
+ pub width: usize,
+ pub height: usize,
+ pub has_transparency: bool,
+ pub data: Vec<u32>,
+}
+
+impl Texture {
+ pub fn load_from_bmp(bmp_data: &Vec<u8>) -> Texture {
+ let data_position = u32::from_le_bytes([
+ bmp_data[0x0A],
+ bmp_data[0x0B],
+ bmp_data[0x0C],
+ bmp_data[0x0D],
+ ]);
+ // assuming windows BITMAPINFOHEADER, these are i32
+ let width = i32::from_le_bytes([
+ bmp_data[0x12],
+ bmp_data[0x13],
+ bmp_data[0x14],
+ bmp_data[0x15],
+ ]) as usize;
+ let height = i32::from_le_bytes([
+ bmp_data[0x16],
+ bmp_data[0x17],
+ bmp_data[0x18],
+ bmp_data[0x19],
+ ]) as usize;
+ let mut has_transparency = false;
+ let mut data = Vec::with_capacity(width * height);
+ // step_by 3 assuming 24-bit depth
+ for byte_index in ((data_position as usize)..bmp_data.len()).step_by(3) {
+ if bmp_data[byte_index] == 0x00
+ && bmp_data[byte_index + 1] == 0x00
+ && bmp_data[byte_index + 2] == 0x00
+ {
+ has_transparency = true;
+ }
+ data.push(u32::from_le_bytes([
+ bmp_data[byte_index],
+ bmp_data[byte_index + 1],
+ bmp_data[byte_index + 2],
+ 0x00,
+ ]));
+ }
+ data.reverse();
+ Texture {
+ width,
+ height,
+ has_transparency,
+ data,
+ }
+ }
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..dcd2b72
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,45 @@
+use std::rc::Rc;
+
+use crate::vec2::Vec2;
+use crate::texture::Texture;
+
+#[derive(Clone)]
+pub enum Side {
+ X,
+ Y,
+}
+
+pub enum Orientation {
+ XAxis,
+ YAxis,
+}
+
+#[derive(Clone)]
+pub enum Step {
+ Left,
+ Right,
+}
+
+impl Step {
+ pub fn value(&self) -> i32 {
+ match self {
+ Step::Left => -1,
+ Step::Right => 1,
+ }
+ }
+
+ pub fn from(value: bool) -> Step {
+ match value {
+ true => Step::Left,
+ false => Step::Right,
+ }
+ }
+}
+
+pub struct Sprite {
+ pub position: Vec2<f64>,
+ pub texture: Rc<Texture>,
+ pub vertical_offset: f64,
+ pub scale_factor: Vec2<f64>,
+ pub distance_from_camera: f64,
+}
diff --git a/src/vec2.rs b/src/vec2.rs
new file mode 100644
index 0000000..077433a
--- /dev/null
+++ b/src/vec2.rs
@@ -0,0 +1,52 @@
+use auto_ops::{impl_op_ex, impl_op_ex_commutative};
+
+#[derive(Clone)]
+pub struct Vec2<T> {
+ pub x: T,
+ pub y: T,
+}
+
+impl Vec2<f64> {
+ pub fn new() -> Vec2<f64> {
+ Vec2 { x: 0.0, y: 0.0 }
+ }
+
+ pub fn rotate(&mut self, radians: f64) {
+ *self = Vec2 {
+ x: self.x * radians.cos() - self.y * radians.sin(),
+ y: self.x * radians.sin() + self.y * radians.cos(),
+ };
+ }
+
+ pub fn length_squared(&self) -> f64 {
+ self.x * self.x + self.y * self.y
+ }
+
+ pub fn length(&self) -> f64 {
+ self.length_squared().sqrt()
+ }
+
+ pub fn as_usize(&self) -> Vec2<usize> {
+ Vec2 {
+ x: self.x as usize,
+ y: self.y as usize,
+ }
+ }
+}
+
+impl_op_ex!(+ |lhs: &Vec2<f64>, rhs: &Vec2<f64>| -> Vec2<f64> { Vec2 { x: lhs.x + rhs.x, y: lhs.y + rhs.y } });
+impl_op_ex!(-|lhs: &Vec2<f64>, rhs: &Vec2<f64>| -> Vec2<f64> {
+ Vec2 {
+ x: lhs.x - rhs.x,
+ y: lhs.y - rhs.y,
+ }
+});
+impl_op_ex!(+= |lhs: &mut Vec2<f64>, rhs: &Vec2<f64>| { *lhs = Vec2 { x: lhs.x + rhs.x, y: lhs.y + rhs.y } });
+impl_op_ex!(-= |lhs: &mut Vec2<f64>, rhs: &Vec2<f64>| { *lhs = Vec2 { x: lhs.x - rhs.x, y: lhs.y - rhs.y } });
+impl_op_ex_commutative!(*|lhs: &Vec2<f64>, rhs: &f64| -> Vec2<f64> {
+ Vec2 {
+ x: lhs.x * rhs,
+ y: lhs.y * rhs,
+ }
+});
+impl_op_ex!(/ |lhs: &Vec2<f64>, rhs: &f64| -> Vec2<f64> { lhs * (1.0 / rhs) });