diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 891 | ||||
-rw-r--r-- | Cargo.toml | 11 | ||||
-rw-r--r-- | LICENSE | 13 | ||||
-rw-r--r-- | README.md | 19 | ||||
-rw-r--r-- | res/font.bmp | bin | 0 -> 49290 bytes | |||
-rw-r--r-- | res/map.txt | 24 | ||||
-rw-r--r-- | res/textures/barrel.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/bluestone.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/colorstone.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/eagle.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/greenlight.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/greystone.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/mossy.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/pillar.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/purplestone.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/redbrick.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | res/textures/wood.bmp | bin | 0 -> 12426 bytes | |||
-rw-r--r-- | src/camera.rs | 96 | ||||
-rw-r--r-- | src/framebuffer.rs | 234 | ||||
-rw-r--r-- | src/main.rs | 261 | ||||
-rw-r--r-- | src/map.rs | 60 | ||||
-rw-r--r-- | src/texture.rs | 81 | ||||
-rw-r--r-- | src/util.rs | 45 | ||||
-rw-r--r-- | src/vec2.rs | 52 |
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" @@ -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 Binary files differnew file mode 100644 index 0000000..a2368f0 --- /dev/null +++ b/res/font.bmp 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 Binary files differnew file mode 100644 index 0000000..7410861 --- /dev/null +++ b/res/textures/barrel.bmp diff --git a/res/textures/bluestone.bmp b/res/textures/bluestone.bmp Binary files differnew file mode 100644 index 0000000..4eaa14c --- /dev/null +++ b/res/textures/bluestone.bmp diff --git a/res/textures/colorstone.bmp b/res/textures/colorstone.bmp Binary files differnew file mode 100644 index 0000000..f9cc4e4 --- /dev/null +++ b/res/textures/colorstone.bmp diff --git a/res/textures/eagle.bmp b/res/textures/eagle.bmp Binary files differnew file mode 100644 index 0000000..1d7f659 --- /dev/null +++ b/res/textures/eagle.bmp diff --git a/res/textures/greenlight.bmp b/res/textures/greenlight.bmp Binary files differnew file mode 100644 index 0000000..5ef3c11 --- /dev/null +++ b/res/textures/greenlight.bmp diff --git a/res/textures/greystone.bmp b/res/textures/greystone.bmp Binary files differnew file mode 100644 index 0000000..c0248a4 --- /dev/null +++ b/res/textures/greystone.bmp diff --git a/res/textures/mossy.bmp b/res/textures/mossy.bmp Binary files differnew file mode 100644 index 0000000..534bfcb --- /dev/null +++ b/res/textures/mossy.bmp diff --git a/res/textures/pillar.bmp b/res/textures/pillar.bmp Binary files differnew file mode 100644 index 0000000..77ed575 --- /dev/null +++ b/res/textures/pillar.bmp diff --git a/res/textures/purplestone.bmp b/res/textures/purplestone.bmp Binary files differnew file mode 100644 index 0000000..5cf6a23 --- /dev/null +++ b/res/textures/purplestone.bmp diff --git a/res/textures/redbrick.bmp b/res/textures/redbrick.bmp Binary files differnew file mode 100644 index 0000000..2d64460 --- /dev/null +++ b/res/textures/redbrick.bmp diff --git a/res/textures/wood.bmp b/res/textures/wood.bmp Binary files differnew file mode 100644 index 0000000..d2e545e --- /dev/null +++ b/res/textures/wood.bmp 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) }); |