From fbdba31b3688a4931de792cc0f38ddc7ba61b512 Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sat, 15 Jul 2023 11:22:31 -0700 Subject: [PATCH 1/8] `wgpu` beginnings --- Cargo.lock | 425 ++++++++++------------------------------------- Cargo.toml | 7 +- assets/map.png | Bin 45162 -> 0 bytes src/main.rs | 159 +++--------------- src/raycaster.rs | 347 -------------------------------------- src/vector.rs | 134 --------------- 6 files changed, 118 insertions(+), 954 deletions(-) delete mode 100644 assets/map.png delete mode 100644 src/raycaster.rs delete mode 100644 src/vector.rs diff --git a/Cargo.lock b/Cargo.lock index 81c851e..e7e1683 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + [[package]] name = "android-activity" version = "0.4.2" @@ -134,12 +143,6 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - [[package]] name = "bitflags" version = "1.3.2" @@ -189,12 +192,6 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - [[package]] name = "calloop" version = "0.10.6" @@ -240,12 +237,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "com-rs" version = "0.2.1" @@ -301,55 +292,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset 0.9.0", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "d3d12" version = "0.6.0" @@ -383,10 +325,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] -name = "either" -version = "1.8.1" +name = "env_logger" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] [[package]] name = "equivalent" @@ -395,19 +344,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" [[package]] -name = "exr" -version = "1.7.0" +name = "errno" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", ] [[package]] @@ -429,19 +383,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin", -] - [[package]] name = "foreign-types" version = "0.3.2" @@ -457,18 +398,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - [[package]] name = "getrandom" version = "0.2.10" @@ -476,20 +405,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", -] - -[[package]] -name = "gif" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" -dependencies = [ - "color_quant", - "weezl", ] [[package]] @@ -562,15 +479,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "half" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -614,23 +522,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] -name = "image" -version = "0.24.6" +name = "humantime" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "exr", - "gif", - "jpeg-decoder", - "num-rational", - "num-traits", - "png", - "qoi", - "tiff", -] +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" @@ -664,6 +559,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "jni-sys" version = "0.3.0" @@ -679,15 +585,6 @@ dependencies = [ "libc", ] -[[package]] -name = "jpeg-decoder" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" -dependencies = [ - "rayon", -] - [[package]] name = "js-sys" version = "0.3.64" @@ -714,12 +611,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - [[package]] name = "libc" version = "0.2.147" @@ -746,6 +637,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + [[package]] name = "lock_api" version = "0.4.10" @@ -795,15 +692,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - [[package]] name = "metal" version = "0.24.0" @@ -866,15 +754,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - [[package]] name = "ndk" version = "0.7.0" @@ -913,7 +792,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -926,7 +805,7 @@ dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "memoffset 0.6.5", + "memoffset", ] [[package]] @@ -939,27 +818,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -969,16 +827,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" version = "0.5.11" @@ -1128,40 +976,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" -[[package]] -name = "pin-project" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.25", -] - -[[package]] -name = "pixels" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ba8189b31db4f12fbf0d4a8eab2d7d7343a504a8d8a7ea4b14ffb2e6129136a" -dependencies = [ - "bytemuck", - "pollster", - "raw-window-handle", - "thiserror", - "ultraviolet", - "wgpu", -] - [[package]] name = "pkg-config" version = "0.3.27" @@ -1181,12 +995,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "pollster" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1212,15 +1020,6 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "332cd62e95873ea4f41f3dfd6bbbfc5b52aec892d7e8d534197c4720a0bbbab2" -[[package]] -name = "qoi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" -dependencies = [ - "bytemuck", -] - [[package]] name = "quote" version = "1.0.29" @@ -1243,36 +1042,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] -name = "rayon" -version = "1.7.0" +name = "redox_syscall" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "either", - "rayon-core", + "bitflags 1.3.2", ] [[package]] -name = "rayon-core" -version = "1.11.0" +name = "regex" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", ] [[package]] -name = "redox_syscall" -version = "0.3.5" +name = "regex-automata" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" dependencies = [ - "bitflags 1.3.2", + "aho-corasick", + "memchr", + "regex-syntax", ] +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + [[package]] name = "renderdoc-sys" version = "1.0.0" @@ -1292,12 +1098,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "safe_arch" -version = "0.7.0" +name = "rustix" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62a7484307bd40f8f7ccbacccac730108f2cae119a3b11c74485b48aa9ea650f" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" dependencies = [ - "bytemuck", + "bitflags 2.3.3", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1365,15 +1175,6 @@ dependencies = [ "wayland-protocols", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - [[package]] name = "spirv" version = "0.2.0+1.5.4" @@ -1447,17 +1248,6 @@ dependencies = [ "syn 2.0.25", ] -[[package]] -name = "tiff" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7449334f9ff2baf290d55d73983a7d6fa15e01198faef72af07e2a8db851e471" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - [[package]] name = "tiny-skia" version = "0.8.4" @@ -1510,21 +1300,13 @@ checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33" name = "twoderaycaster" version = "0.1.0" dependencies = [ - "image", - "pixels", + "env_logger", + "log", + "wgpu", "winit", "winit_input_helper", ] -[[package]] -name = "ultraviolet" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0b28b9a6ce66d47e3c5666aa738c5ec5223fcdd4c263f3edc98ab6fef618b3" -dependencies = [ - "wide", -] - [[package]] name = "unicode-ident" version = "1.0.10" @@ -1710,12 +1492,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "weezl" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" - [[package]] name = "wgpu" version = "0.16.2" @@ -1816,16 +1592,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "wide" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40018623e2dba2602a9790faba8d33f2ebdebf4b86561b83928db735f8784728" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "widestring" version = "1.0.2" @@ -2082,12 +1848,3 @@ name = "xml-rs" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a56c84a8ccd4258aed21c92f70c0f6dea75356b6892ae27c24139da456f9336" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] diff --git a/Cargo.toml b/Cargo.toml index 3b09aae..52db7a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -image = "0.24.6" -pixels = "0.13.0" -winit = "0.28.6" +winit = "0.28" +env_logger = "0.10" +log = "0.4" +wgpu = "0.16" winit_input_helper = "0.14.1" diff --git a/assets/map.png b/assets/map.png deleted file mode 100644 index 7b43fb36c0546ab2cbc66e442b868446b7eedb23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45162 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 E0nG5YZU6uP diff --git a/src/main.rs b/src/main.rs index 2b2c348..06237cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,46 +1,25 @@ -use pixels::Error; use winit::{ event::{DeviceEvent, Event, VirtualKeyCode, WindowEvent}, event_loop::EventLoop, }; use winit_input_helper::WinitInputHelper; -mod raycaster; -mod vector; mod window; pub const WIDTH: u32 = 3840; pub const HEIGHT: u32 = 2160; pub const SCALEFACTOR: u32 = 2; -pub static mut ACCELERATION: f64 = 0.1; - -fn main() -> Result<(), Error> { +fn main() -> Result<(), String> { let mut input = WinitInputHelper::new(); let event_loop = EventLoop::new(); - let mut gw = window::GameWindow::new("2D Raycaster", &event_loop)?; - let mut raycaster = raycaster::RayCaster::new(60.); - let mut map_toggle = false; + let mut gw = window::GameWindow::new("3D World", &event_loop)?; event_loop.run(move |event, _, control_flow| { match event { Event::RedrawRequested(_) => { - let now = std::time::Instant::now(); - // println!("Redraw requested"); - let frame = gw.pixels.frame_mut(); - - // Clear the frame - for pixel in frame.chunks_exact_mut(4) { - pixel.copy_from_slice(&[0, 0, 0, 0]); // Set every pixel to black - } - raycaster.update_player(); - - raycaster.draw(frame, map_toggle).unwrap(); - gw.pixels.render().unwrap(); - let elapsed = now.elapsed().as_millis(); - println!("FPS: {}", 1000 / elapsed) } Event::WindowEvent { @@ -59,128 +38,36 @@ fn main() -> Result<(), Error> { gw.resize((size.width, size.height)); } - Event::DeviceEvent { - event: DeviceEvent::MouseMotion { delta }, - .. - } => raycaster.change_direction(raycaster::Direction::Mouse(delta.0, delta.1)), + // Event::DeviceEvent { + // event: DeviceEvent::MouseMotion { delta }, + // .. + // } => raycaster.change_direction(raycaster::Direction::Mouse(delta.0, delta.1)), _ => {} } - if input.update(&event) { - if input.held_shift() { - unsafe { - ACCELERATION = 0.5; - } - } else { - unsafe { - ACCELERATION = 0.1; - } - } + // if input.update(&event) { + // if input.key_held(VirtualKeyCode::W) { + // raycaster.change_direction(raycaster::Direction::Up) + // } - if input.key_held(VirtualKeyCode::W) { - raycaster.change_direction(raycaster::Direction::Up) - } + // if input.key_held(VirtualKeyCode::S) { + // raycaster.change_direction(raycaster::Direction::Down) + // } - if input.key_held(VirtualKeyCode::S) { - raycaster.change_direction(raycaster::Direction::Down) - } + // if input.key_held(VirtualKeyCode::A) { + // raycaster.change_direction(raycaster::Direction::Left) + // } - if input.key_held(VirtualKeyCode::A) { - raycaster.change_direction(raycaster::Direction::Left) - } + // if input.key_held(VirtualKeyCode::D) { + // raycaster.change_direction(raycaster::Direction::Right) + // } - if input.key_held(VirtualKeyCode::D) { - raycaster.change_direction(raycaster::Direction::Right) - } - - if input.key_pressed(VirtualKeyCode::M) { - map_toggle = !map_toggle; - } - } + // if input.key_pressed(VirtualKeyCode::M) { + // map_toggle = !map_toggle; + // } + // } gw.window.request_redraw(); }); } - -fn verline(frame: &mut [u8], x: usize, y1: usize, y2: usize, rgba: [u8; 4], scale: usize) { - for y in (y1 * scale)..=(y2 * scale) { - set_pixel(frame, x, y, rgba, scale); - } -} - -pub fn line( - frame: &mut [u8], - x1: isize, - y1: isize, - x2: isize, - y2: isize, - color: [u8; 4], - scale: usize, -) { - if x1 == x2 { - verline(frame, x1 as usize, y1 as usize, y2 as usize, color, scale); - return; - } - let dx = isize::abs(x2 - x1) * scale as isize; - let sx = if x1 < x2 { 1 } else { -1 }; - let dy = -isize::abs(y2 - y1) * scale as isize; - let sy = if y1 < y2 { 1 } else { -1 }; - let mut err = dx + dy; - let mut x = x1 * scale as isize; - let mut y = y1 * scale as isize; - - loop { - set_pixel(frame, x as usize, y as usize, color, scale); - - if x == x2 * scale as isize && y == y2 * scale as isize { - break; - } - - let e2 = 2 * err; - - if e2 >= dy { - err += dy; - x += sx * scale as isize; - } - - if e2 <= dx { - err += dx; - y += sy * scale as isize; - } - } -} - -fn filled_rectangle( - frame: &mut [u8], - x1: usize, - y1: usize, - x2: usize, - y2: usize, - color: [u8; 4], - scale: usize, -) { - for x in (x1 * scale)..=(x2 * scale) { - for y in (y1 * scale)..=(y2 * scale) { - if x >= WIDTH as usize || y >= HEIGHT as usize { - continue; - } - set_pixel(frame, x, y, color, scale); - } - } -} - -pub fn set_pixel(frame: &mut [u8], x: usize, y: usize, color: [u8; 4], scale: usize) { - for i in 0..scale { - for j in 0..scale { - let xi = x * scale + i; - let yj = y * scale + j; - if xi < WIDTH as usize && yj < HEIGHT as usize { - let index = (yj * WIDTH as usize + xi) * 4; - if index + 4 <= frame.len() { - frame[index..index + 4].copy_from_slice(&color); - } - } - } - } -} diff --git a/src/raycaster.rs b/src/raycaster.rs deleted file mode 100644 index a927f75..0000000 --- a/src/raycaster.rs +++ /dev/null @@ -1,347 +0,0 @@ -use crate::{line, set_pixel, vector::Vector, verline, ACCELERATION, HEIGHT, WIDTH}; - -pub struct RayCaster { - player: Player, - map: Vec>, - fov: f64, -} - -struct Ray { - dir: Vector, - hit: bool, -} - -struct Player { - pub pos: Vector, - pub dir: Vector, - pub vel: Vector, -} - -pub enum Direction { - Up, - Down, - Left, - Right, - Mouse(f64, f64), -} - -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -struct MapCell { - pub color: [u8; 4], - pub solid: MapCellType, - pub height: f64, -} - -#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] -enum MapCellType { - Empty, - Wall, -} - -impl MapCell { - pub fn new(color: [u8; 4], solid: MapCellType, height: f64) -> Self { - Self { - color, - solid, - height, - } - } - - pub fn empty() -> Self { - Self { - color: [0, 0, 0, 0], - solid: MapCellType::Empty, - height: 0.0, - } - } -} - -impl RayCaster { - pub fn new(fov: f64) -> Self { - Self { - player: Player { - pos: Vector { x: 22.0, y: 12.0 }, - dir: Vector { x: -1.0, y: 0.0 }, - vel: Vector { x: 0., y: 0. }, - }, - - map: generate_map(), - - // map: [ - // [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,2,2,2,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1], - // [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,3,0,0,0,3,0,0,0,1], - // [1,0,0,0,0,0,2,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,2,2,0,2,2,0,0,0,0,3,0,3,0,3,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,4,0,0,0,0,5,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,4,0,4,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,4,0,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,4,4,4,4,4,4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], - // [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1] - // ], - fov, - } - } - - pub fn draw(&self, frame: &mut [u8], map_toggle: bool) -> Result<(), String> { - if map_toggle { - // map - for y in 0..self.map.len() { - for x in 0..self.map[y].len() { - let cell = self.map[y][x]; - - set_pixel(frame, x, y, cell.color, 1); - // filled_rectangle(frame, x, y, x+1, y+2, color, PIXELSIZE) - } - } - - set_pixel( - frame, - self.player.pos.x as usize, - self.player.pos.y as usize, - [25, 0, 255, 255], - 1, - ); - line( - frame, - self.player.pos.x as isize, - self.player.pos.y as isize, - (self.player.pos.x + self.player.dir.x * 10.) as isize, - (self.player.pos.y + self.player.dir.y * 10.) as isize, - [255, 0, 0, 255], - 1, - ); - - // orthogonal line - line( - frame, - self.player.pos.x as isize, - self.player.pos.y as isize, - (self.player.pos.x + self.player.dir.orthogonal(Direction::Left).x * 10.) as isize, - (self.player.pos.y + self.player.dir.orthogonal(Direction::Left).y * 10.) as isize, - [0, 255, 0, 255], - 1, - ); - line( - frame, - self.player.pos.x as isize, - self.player.pos.y as isize, - (self.player.pos.x + self.player.dir.orthogonal(Direction::Right).x * 10.) as isize, - (self.player.pos.y + self.player.dir.orthogonal(Direction::Right).y * 10.) as isize, - [0, 0, 255, 255], - 1, - ); - return Ok(()); - } - - // raycasting - let half_fov: f64 = self.fov / 2.; - const NUMRAYS: f64 = WIDTH as f64; - for i in 0..NUMRAYS as usize { - let angle = (self.fov / NUMRAYS * i as f64 - half_fov) * 1f64.to_radians(); - let mut ray = Ray { - dir: self.player.dir.rotate(angle), - hit: false, - }; - - // map_pos is the current map cell we are in - let mut map_pos: Vector = Vector::new( - self.player.pos.x.floor() as i32, - self.player.pos.y.floor() as i32, - ); - - // delta of ray to next map cell - let delta_dist = Vector { - x: (1.0 / ray.dir.x).abs(), - y: (1.0 / ray.dir.y).abs(), - }; - - // step direction for map_pos - let step = Vector { - x: if ray.dir.x < 0. { -1. } else { 1. }, - y: if ray.dir.y < 0. { -1. } else { 1. }, - }; - - // ray distance from side of map cell (helps with determining direction to inc) - let mut side_dist: Vector = Vector { - x: if ray.dir.x < 0. { - // top left edge of map cell - (self.player.pos.x - map_pos.x as f64) * delta_dist.x - } else { - // top right edge of map cell - (map_pos.x as f64 + 1. - self.player.pos.x) * delta_dist.x - }, - - y: if ray.dir.y < 0. { - // top left edge of map cell - (self.player.pos.y - map_pos.y as f64) * delta_dist.y - } else { - // top right edge of map cell - (map_pos.y as f64 + 1. - self.player.pos.y) * delta_dist.y - }, - }; - - // DDA - let mut side = 0; - while !ray.hit { - if side_dist.x < side_dist.y { - side_dist.x += delta_dist.x; - map_pos.x += step.x as i32; - side = 0; - } else { - side_dist.y += delta_dist.y; - map_pos.y += step.y as i32; - side = 1; - } - - if self.map[map_pos.y as usize][map_pos.x as usize].solid != MapCellType::Empty { - ray.hit = true; - } - } - - let mut cell = self.map[map_pos.y as usize][map_pos.x as usize]; - - if side == 1 { - cell.color.div_assign(2) - } - - let distance: f64 = if side == 0 { - (map_pos.x as f64 - self.player.pos.x + (1. - step.x) / 2.) / ray.dir.x - } else { - (map_pos.y as f64 - self.player.pos.y + (1. - step.y) / 2.) / ray.dir.y - }; - - let correct_distance = distance * (self.player.dir.angle() - ray.dir.angle()).cos(); - - // fog - cell.color.mul_assign(1. / (1. + correct_distance * correct_distance * 0.0001)); - - let mut height = (HEIGHT as f64 / correct_distance).abs() * 15.; - if height > HEIGHT as f64 { - height = HEIGHT as f64; - } - - let column_start = HEIGHT as usize / 2 - height as usize / 2; - let column_end = HEIGHT as usize / 2 + height as usize / 2; - verline(frame, i, column_start, column_end, cell.color, 1); - } - Ok(()) - } - - pub fn update_player(&mut self) { - let new_pos_x = Vector::new(self.player.pos.x + self.player.vel.x, self.player.pos.y); - if self.is_valid_position(&new_pos_x) { - self.player.pos = new_pos_x; - } - - let new_pos_y = Vector::new(self.player.pos.x, self.player.pos.y + self.player.vel.y); - if self.is_valid_position(&new_pos_y) { - self.player.pos = new_pos_y; - } - - self.player.vel *= 0.8; - } - - fn is_valid_position(&self, pos: &Vector) -> bool { - if let Some(row) = self.map.get(pos.y as usize) { - if let Some(cell) = row.get(pos.x as usize) { - if cell.solid == MapCellType::Empty { - return true; - } - } - } - - false - } - - pub fn change_direction(&mut self, dir: Direction) { - const ROTATESPEED: f64 = 0.001; - let acceleration = unsafe { ACCELERATION }; - - match dir { - Direction::Down => { - self.player.vel.x -= self.player.dir.x * acceleration; - self.player.vel.y -= self.player.dir.y * acceleration; - } - Direction::Up => { - self.player.vel.x += self.player.dir.x * acceleration; - self.player.vel.y += self.player.dir.y * acceleration; - } - Direction::Left => { - let ortho = self.player.dir.orthogonal(Direction::Left); - self.player.vel.x -= ortho.x * acceleration; - self.player.vel.y -= ortho.y * acceleration; - } - Direction::Right => { - let ortho = self.player.dir.orthogonal(Direction::Right); - self.player.vel.x -= ortho.x * acceleration; - self.player.vel.y -= ortho.y * acceleration; - } - Direction::Mouse(dx, _) => { - self.player.dir = self.player.dir.rotate(dx * ROTATESPEED); - } - } - } -} - -trait MulAssign { - fn mul_assign(&mut self, rhs: f64); -} - -impl MulAssign for [u8; 4] { - fn mul_assign(&mut self, rhs: f64) { - self[0] = (self[0] as f64 * rhs) as u8; - self[1] = (self[1] as f64 * rhs) as u8; - self[2] = (self[2] as f64 * rhs) as u8; - self[3] = (self[3] as f64 * rhs) as u8; - } -} - -trait DivAssign { - fn div_assign(&mut self, rhs: u8); -} - -impl DivAssign for [u8; 4] { - fn div_assign(&mut self, rhs: u8) { - self[0] /= rhs; - self[1] /= rhs; - self[2] /= rhs; - self[3] /= rhs; - } -} - -fn generate_map() -> Vec> { - let img = image::open("assets/map.png").unwrap(); - let img = img.to_rgba8(); - let (width, height) = img.dimensions(); - - let mut buffer: Vec> = vec![vec![MapCell::empty(); width as usize]; height as usize]; - - for y in 0..height { - for x in 0..width { - let pixel = img.get_pixel(x, y).0; - let solid = if pixel == [0, 0, 0, 0] { - MapCellType::Empty - } else { - MapCellType::Wall - }; - buffer[y as usize][x as usize] = MapCell::new(pixel, solid, 0.); - } - } - - buffer -} \ No newline at end of file diff --git a/src/vector.rs b/src/vector.rs deleted file mode 100644 index be2d1b4..0000000 --- a/src/vector.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::raycaster::Direction; - -#[derive(Clone, Copy, PartialEq, PartialOrd)] -pub struct Vector { - pub x: T, - pub y: T, -} - -impl Vector { - pub fn new(x: T, y: T) -> Self { - Self { x, y } - } - - pub fn rotate(&self, angle: f64) -> Vector - where - T: Into + From + Copy, - { - let x = self.x.into(); - let y = self.y.into(); - - let new_x = (x * angle.cos() - y * angle.sin()).into(); - let new_y = (x * angle.sin() + y * angle.cos()).into(); - - Vector::new(new_x, new_y) - } - - pub fn angle(&self) -> f64 - where - T: Into + From + Copy, - { - let x = self.x.into(); - let y = self.y.into(); - y.atan2(x) - } - - pub fn orthogonal(&self, dir: Direction) -> Self - where - T: std::ops::Neg + Copy, - { - match dir { - Direction::Right => Vector::new(self.y, -self.x), - Direction::Left => Vector::new(-self.y, self.x), - _ => panic!("Invalid direction"), - } - } -} - -impl std::ops::Add for Vector -where - T: std::ops::Add, -{ - type Output = Self; - - fn add(self, rhs: Self) -> Self::Output { - Self { - x: self.x + rhs.x, - y: self.y + rhs.y, - } - } -} - -impl std::ops::AddAssign for Vector -where - T: std::ops::AddAssign, -{ - fn add_assign(&mut self, rhs: Self) { - self.x += rhs.x; - self.y += rhs.y; - } -} - -impl std::ops::AddAssign for Vector -where - T: std::ops::AddAssign + Copy, -{ - fn add_assign(&mut self, rhs: T) { - self.x += rhs; - self.y += rhs; - } -} - -impl std::ops::MulAssign for Vector -where - T: std::ops::MulAssign, -{ - fn mul_assign(&mut self, rhs: Self) { - self.x *= rhs.x; - self.y *= rhs.y; - } -} - -impl std::ops::SubAssign for Vector -where - T: std::ops::SubAssign, -{ - fn sub_assign(&mut self, rhs: Self) { - self.x -= rhs.x; - self.y -= rhs.y; - } -} - -impl std::ops::SubAssign for Vector -where - T: std::ops::SubAssign + Copy, -{ - fn sub_assign(&mut self, rhs: T) { - self.x -= rhs; - self.y -= rhs; - } -} - -impl std::ops::Mul for Vector -where - T: std::ops::Mul + Copy, -{ - type Output = Self; - - fn mul(self, rhs: T) -> Self::Output { - Self { - x: self.x * rhs, - y: self.y * rhs, - } - } -} - -impl std::ops::MulAssign for Vector -where - T: std::ops::MulAssign + Copy, -{ - fn mul_assign(&mut self, rhs: T) { - self.x *= rhs; - self.y *= rhs; - } -} From ad30bbb7a578a1ddc07106b455c58b7df2587968 Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sat, 15 Jul 2023 11:27:16 -0700 Subject: [PATCH 2/8] Reformating and renaming --- Cargo.lock | 50 ++++++++++++++++----------------- Cargo.toml | 2 +- src/lib.rs | 34 +++++++++++++++++++++++ src/main.rs | 76 +++------------------------------------------------ src/window.rs | 44 ----------------------------- 5 files changed, 64 insertions(+), 142 deletions(-) create mode 100644 src/lib.rs delete mode 100644 src/window.rs diff --git a/Cargo.lock b/Cargo.lock index e7e1683..efa92f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -339,9 +339,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" @@ -866,7 +866,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", ] [[package]] @@ -1210,9 +1210,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.25" +version = "2.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" dependencies = [ "proc-macro2", "quote", @@ -1245,7 +1245,18 @@ checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", +] + +[[package]] +name = "threedee" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "wgpu", + "winit", + "winit_input_helper", ] [[package]] @@ -1281,9 +1292,9 @@ checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" [[package]] name = "toml_edit" -version = "0.19.12" +version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ "indexmap 2.0.0", "toml_datetime", @@ -1296,22 +1307,11 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a464a4b34948a5f67fddd2b823c62d9d92e44be75058b99939eae6c5b6960b33" -[[package]] -name = "twoderaycaster" -version = "0.1.0" -dependencies = [ - "env_logger", - "log", - "wgpu", - "winit", - "winit_input_helper", -] - [[package]] name = "unicode-ident" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unicode-width" @@ -1364,7 +1364,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", "wasm-bindgen-shared", ] @@ -1398,7 +1398,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.25", + "syn 2.0.26", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1816,9 +1816,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.4.9" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" +checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 52db7a6..e04d442 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "twoderaycaster" +name = "threedee" version = "0.1.0" edition = "2021" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3174b58 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,34 @@ +use winit::{ + event::*, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, +}; + +pub fn run() { + env_logger::init(); + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + + event_loop.run(move |event, _, control_flow| match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + + _ => {} + }, + + _ => {} + }); +} + diff --git a/src/main.rs b/src/main.rs index 06237cc..f680f6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,73 +1,5 @@ -use winit::{ - event::{DeviceEvent, Event, VirtualKeyCode, WindowEvent}, - event_loop::EventLoop, -}; -use winit_input_helper::WinitInputHelper; +use threedee::run; -mod window; - -pub const WIDTH: u32 = 3840; -pub const HEIGHT: u32 = 2160; -pub const SCALEFACTOR: u32 = 2; - -fn main() -> Result<(), String> { - let mut input = WinitInputHelper::new(); - - let event_loop = EventLoop::new(); - let mut gw = window::GameWindow::new("3D World", &event_loop)?; - - event_loop.run(move |event, _, control_flow| { - match event { - Event::RedrawRequested(_) => { - - } - - Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - // println!("Window closed"); - *control_flow = winit::event_loop::ControlFlow::Exit; - } - - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - println!("Window resized to {:?}", size); - gw.resize((size.width, size.height)); - } - - // Event::DeviceEvent { - // event: DeviceEvent::MouseMotion { delta }, - // .. - // } => raycaster.change_direction(raycaster::Direction::Mouse(delta.0, delta.1)), - - _ => {} - } - - // if input.update(&event) { - // if input.key_held(VirtualKeyCode::W) { - // raycaster.change_direction(raycaster::Direction::Up) - // } - - // if input.key_held(VirtualKeyCode::S) { - // raycaster.change_direction(raycaster::Direction::Down) - // } - - // if input.key_held(VirtualKeyCode::A) { - // raycaster.change_direction(raycaster::Direction::Left) - // } - - // if input.key_held(VirtualKeyCode::D) { - // raycaster.change_direction(raycaster::Direction::Right) - // } - - // if input.key_pressed(VirtualKeyCode::M) { - // map_toggle = !map_toggle; - // } - // } - - gw.window.request_redraw(); - }); -} +fn main() { + run(); +} \ No newline at end of file diff --git a/src/window.rs b/src/window.rs deleted file mode 100644 index 5f67173..0000000 --- a/src/window.rs +++ /dev/null @@ -1,44 +0,0 @@ -use pixels::{Error, Pixels, SurfaceTexture, PixelsBuilder}; -use winit::{ - dpi::LogicalSize, - event_loop::EventLoop, - window::{CursorGrabMode, Window, WindowBuilder}, -}; - -use crate::{HEIGHT, WIDTH, SCALEFACTOR}; - -pub struct GameWindow { - pub window: Window, - pub pixels: Pixels, -} - -impl GameWindow { - pub fn new(title: &str, event_loop: &EventLoop<()>) -> Result { - let size = LogicalSize::new(WIDTH / SCALEFACTOR, HEIGHT / SCALEFACTOR); - let window = WindowBuilder::new() - .with_title(title) - .with_inner_size(size) - .build(event_loop) - .unwrap(); - - window - .set_cursor_grab(CursorGrabMode::Confined) - .or_else(|_e| window.set_cursor_grab(CursorGrabMode::Locked)) - .unwrap(); - window.set_cursor_visible(false); - - let surface_texture = SurfaceTexture::new(WIDTH, HEIGHT, &window); - let pixels = PixelsBuilder::new(WIDTH, HEIGHT, surface_texture) - .enable_vsync(true) - .build()?; - - Ok(Self { - window, - pixels, - }) - } - - pub fn resize(&mut self, new_size: (u32, u32)) { - self.pixels.resize_surface(new_size.0, new_size.1).unwrap(); - } -} From f7d7cb19733ab468e2147a70fa6380415469dd78 Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sat, 15 Jul 2023 11:44:51 -0700 Subject: [PATCH 3/8] Adding the boilerplate code to building a window --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/lib.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++++------ src/main.rs | 3 +- 4 files changed, 191 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index efa92f5..c76af6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -995,6 +995,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1254,6 +1260,7 @@ version = "0.1.0" dependencies = [ "env_logger", "log", + "pollster", "wgpu", "winit", "winit_input_helper", diff --git a/Cargo.toml b/Cargo.toml index e04d442..3998e01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ env_logger = "0.10" log = "0.4" wgpu = "0.16" winit_input_helper = "0.14.1" +pollster = "0.3.0" diff --git a/src/lib.rs b/src/lib.rs index 3174b58..355802b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,34 +1,195 @@ use winit::{ event::*, event_loop::{ControlFlow, EventLoop}, - window::WindowBuilder, + window::{WindowBuilder, Window}, }; -pub fn run() { +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + window: Window, +} + +impl State { + // Creating some of the wgpu types requires async code + async fn new(window: Window) -> Self { + let size = window.inner_size(); + + // The instance is a handle to our GPU + // Backends::all => Vulkan + Metal + DX12 + Browser WebGPU + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::all(), + dx12_shader_compiler: Default::default(), + }); + + // # Safety + // + // The surface needs to live as long as the window that created it. + // State owns the window so this should be safe. + let surface = unsafe { instance.create_surface(&window) }.unwrap(); + + let adapter = instance.request_adapter( + &wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }, + ).await.unwrap(); + + let (device, queue) = adapter.request_device( + &wgpu::DeviceDescriptor { + features: wgpu::Features::empty(), + // WebGL doesn't support all of wgpu's features, so if + // we're building for the web we'll have to disable some. + limits: wgpu::Limits::default(), + label: None, + }, + None, // Trace path + ).await.unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + // Shader code in this tutorial assumes an sRGB surface texture. Using a different + // one will result all the colors coming out darker. If you want to support non + // sRGB surfaces, you'll need to account for that when drawing to the frame. + let surface_format = surface_caps.formats.iter() + .copied() + .find(|f| f.is_srgb()) + .unwrap_or(surface_caps.formats[0]); + let config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: surface_format, + width: size.width, + height: size.height, + present_mode: surface_caps.present_modes[0], + alpha_mode: surface_caps.alpha_modes[0], + view_formats: vec![], + }; + surface.configure(&device, &config); + + Self { + window, + surface, + device, + queue, + config, + size, + } + } + + pub fn window(&self) -> &Window { + &self.window + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + } + + fn input(&mut self, event: &WindowEvent) -> bool { + false + } + + fn update(&mut self) { + // todo!() + } + + fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let output = self.surface.get_current_texture()?; + let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + { + let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + } + + // submit will accept anything that implements IntoIter + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + + } +} + + +pub async fn run() { env_logger::init(); let event_loop = EventLoop::new(); let window = WindowBuilder::new().build(&event_loop).unwrap(); - event_loop.run(move |event, _, control_flow| match event { - Event::WindowEvent { - ref event, - window_id, - } if window_id == window.id() => match event { - WindowEvent::CloseRequested - | WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(VirtualKeyCode::Escape), - .. - }, - .. - } => *control_flow = ControlFlow::Exit, + let mut state = State::new(window).await; + event_loop.run(move |event, _, control_flow| { + match event { + Event::RedrawRequested(window_id) if window_id == state.window().id() => { + state.update(); + match state.render() { + Ok(_) => {} + // Reconfigure the surface if lost + Err(wgpu::SurfaceError::Lost) => state.resize(state.size), + // The system is out of memory, we should probably quit + Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, + // All other errors (Outdated, Timeout) should be resolved by the next frame + Err(e) => eprintln!("{:?}", e), + } + } + Event::MainEventsCleared => { + // RedrawRequested will only trigger once, unless we manually + // request it. + state.window().request_redraw(); + } + + Event::WindowEvent { + ref event, + window_id, + } if window_id == state.window().id() => if !state.input(event) { // UPDATED! + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(physical_size) => { + state.resize(*physical_size); + } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + state.resize(**new_inner_size); + } + _ => {} + } + } _ => {} - }, - - _ => {} + } }); } diff --git a/src/main.rs b/src/main.rs index f680f6c..0ba5a3a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ use threedee::run; +use pollster; fn main() { - run(); + pollster::block_on(run()); } \ No newline at end of file From a6018072a578d3970ea64454744e59469737d7b1 Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sat, 15 Jul 2023 12:08:52 -0700 Subject: [PATCH 4/8] Render pipeline implemented --- Cargo.lock | 10 ------- Cargo.toml | 1 - src/lib.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++----- src/shader.wgsl | 19 ++++++++++++ 4 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 src/shader.wgsl diff --git a/Cargo.lock b/Cargo.lock index c76af6d..a06b5c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1263,7 +1263,6 @@ dependencies = [ "pollster", "wgpu", "winit", - "winit_input_helper", ] [[package]] @@ -1812,15 +1811,6 @@ dependencies = [ "x11-dl", ] -[[package]] -name = "winit_input_helper" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de0485e86aa2ee87d2d4c373a908c9548357bc65c5bce19fd884c8ea9eac4d7" -dependencies = [ - "winit", -] - [[package]] name = "winnow" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 3998e01..432f4d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,5 +10,4 @@ winit = "0.28" env_logger = "0.10" log = "0.4" wgpu = "0.16" -winit_input_helper = "0.14.1" pollster = "0.3.0" diff --git a/src/lib.rs b/src/lib.rs index 355802b..1bb748d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ struct State { config: wgpu::SurfaceConfiguration, size: winit::dpi::PhysicalSize, window: Window, + render_pipeline: wgpu::RenderPipeline, } impl State { @@ -69,6 +70,51 @@ impl State { }; surface.configure(&device, &config); + let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + }); + Self { window, surface, @@ -76,6 +122,7 @@ impl State { queue, config, size, + render_pipeline, } } @@ -92,7 +139,7 @@ impl State { } } - fn input(&mut self, event: &WindowEvent) -> bool { + fn input(&mut self, _event: &WindowEvent) -> bool { false } @@ -100,7 +147,7 @@ impl State { // todo!() } - fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + fn render(&mut self, color: [f64; 4]) -> Result<(), wgpu::SurfaceError> { let output = self.surface.get_current_texture()?; let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { @@ -108,23 +155,26 @@ impl State { }); { - let _render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, + r: color[0], + g: color[1], + b: color[2], + a: color[3], }), store: true, }, })], depth_stencil_attachment: None, }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.draw(0..3, 0..1); } // submit will accept anything that implements IntoIter @@ -143,12 +193,13 @@ pub async fn run() { let window = WindowBuilder::new().build(&event_loop).unwrap(); let mut state = State::new(window).await; + let mut color = [0.0, 0.0, 0.0, 1.0]; event_loop.run(move |event, _, control_flow| { match event { Event::RedrawRequested(window_id) if window_id == state.window().id() => { state.update(); - match state.render() { + match state.render(color) { Ok(_) => {} // Reconfigure the surface if lost Err(wgpu::SurfaceError::Lost) => state.resize(state.size), @@ -179,12 +230,23 @@ pub async fn run() { }, .. } => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(physical_size) => { state.resize(*physical_size); } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { state.resize(**new_inner_size); } + + WindowEvent::CursorMoved { position, .. } => { + color = [ + position.x / state.size.width as f64, + position.y / state.size.height as f64, + position.x / state.size.width as f64, + 1.0, + ]; + } _ => {} } } diff --git a/src/shader.wgsl b/src/shader.wgsl new file mode 100644 index 0000000..f5a0eae --- /dev/null +++ b/src/shader.wgsl @@ -0,0 +1,19 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, +}; + +@vertex +fn vs_main( + @builtin(vertex_index) in_vertex_index: u32, +) -> VertexOutput { + var out: VertexOutput; + let x = f32(1 - i32(in_vertex_index)) * 0.5; + let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; + out.clip_position = vec4(x, y, 0.0, 1.0); + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return vec4(0.3, 0.2, 0.1, 1.0); +} \ No newline at end of file From acaac99b5a196cf507014997a8ad8ef0a8dab244 Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sun, 16 Jul 2023 11:57:12 -0700 Subject: [PATCH 5/8] Finished challenge --- src/shader.wgsl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shader.wgsl b/src/shader.wgsl index f5a0eae..890f6e1 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,5 +1,6 @@ struct VertexOutput { @builtin(position) clip_position: vec4, + @location(1) color: vec4, }; @vertex @@ -10,10 +11,11 @@ fn vs_main( let x = f32(1 - i32(in_vertex_index)) * 0.5; let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; out.clip_position = vec4(x, y, 0.0, 1.0); + out.color = vec4(smoothstep(-0.5, 0.5, x), smoothstep(-0.5, 0.5, y), 0.0, 1.0); return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return vec4(0.3, 0.2, 0.1, 1.0); + return in.color; } \ No newline at end of file From c61864e7dac547fa7e310b4e2093fd8ca14f65a8 Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sun, 16 Jul 2023 12:06:58 -0700 Subject: [PATCH 6/8] Added vertex and index buffer --- Cargo.lock | 15 ++++++++++ Cargo.toml | 1 + src/lib.rs | 80 +++++++++++++++++++++++++++++++++++++++++++++++-- src/shader.wgsl | 17 ++++++----- 4 files changed, 104 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a06b5c9..2e3b91a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -191,6 +191,20 @@ name = "bytemuck" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.26", +] [[package]] name = "calloop" @@ -1258,6 +1272,7 @@ dependencies = [ name = "threedee" version = "0.1.0" dependencies = [ + "bytemuck", "env_logger", "log", "pollster", diff --git a/Cargo.toml b/Cargo.toml index 432f4d4..bfa60ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,4 @@ env_logger = "0.10" log = "0.4" wgpu = "0.16" pollster = "0.3.0" +bytemuck = { version = "1.13.1", features = ["derive"] } diff --git a/src/lib.rs b/src/lib.rs index 1bb748d..bebe6b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,10 @@ +use wgpu::util::DeviceExt; use winit::{ event::*, event_loop::{ControlFlow, EventLoop}, window::{WindowBuilder, Window}, }; +use bytemuck; struct State { surface: wgpu::Surface, @@ -12,6 +14,10 @@ struct State { size: winit::dpi::PhysicalSize, window: Window, render_pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + num_vertices: u32, + index_buffer: wgpu::Buffer, + num_indices: u32, } impl State { @@ -70,6 +76,7 @@ impl State { }; surface.configure(&device, &config); + // Configuring render pipeline with shader code let shader = device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -83,7 +90,9 @@ impl State { vertex: wgpu::VertexState { module: &shader, entry_point: "vs_main", - buffers: &[], + buffers: &[ + Vertex::desc(), + ], }, fragment: Some(wgpu::FragmentState { module: &shader, @@ -115,6 +124,26 @@ impl State { multiview: None, }); + // Creating vertex buffer + let vertex_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(VERTICES), + usage: wgpu::BufferUsages::VERTEX, + } + ); + let num_vertices = VERTICES.len() as u32; + + // Create index buffer for better memory use + let index_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(INDICES), + usage: wgpu::BufferUsages::INDEX, + } + ); + let num_indices = INDICES.len() as u32; + Self { window, surface, @@ -123,6 +152,10 @@ impl State { config, size, render_pipeline, + vertex_buffer, + num_vertices, + index_buffer, + num_indices, } } @@ -174,7 +207,9 @@ impl State { }); render_pass.set_pipeline(&self.render_pipeline); - render_pass.draw(0..3, 0..1); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + render_pass.draw_indexed(0..self.num_indices, 0, 0..1); } // submit will accept anything that implements IntoIter @@ -186,6 +221,47 @@ impl State { } } +#[repr(C)] +#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] +struct Vertex { + position: [f32; 3], + color: [f32; 3], +} + +impl Vertex { + fn desc() -> wgpu::VertexBufferLayout<'static> { + wgpu::VertexBufferLayout { + array_stride: std::mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::VertexStepMode::Vertex, + attributes: &[ + wgpu::VertexAttribute { + offset: 0, + shader_location: 0, + format: wgpu::VertexFormat::Float32x3, + }, + wgpu::VertexAttribute { + offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + shader_location: 1, + format: wgpu::VertexFormat::Float32x3, + } + ] + } + } +} + +const VERTICES: &[Vertex] = &[ + Vertex { position: [-0.0868241, 0.49240386, 0.0], color: [0.5, 0.0, 0.5] }, + Vertex { position: [-0.49513406, 0.06958647, 0.0], color: [0.5, 0.0, 0.5] }, + Vertex { position: [-0.21918549, -0.44939706, 0.0], color: [0.5, 0.0, 0.5] }, + Vertex { position: [0.35966998, -0.3473291, 0.0], color: [0.5, 0.0, 0.5] }, + Vertex { position: [0.44147372, 0.2347359, 0.0], color: [0.5, 0.0, 0.5] }, +]; + +const INDICES: &[u16] = &[ + 0, 1, 4, + 1, 2, 4, + 2, 3, 4, +]; pub async fn run() { env_logger::init(); diff --git a/src/shader.wgsl b/src/shader.wgsl index 890f6e1..21b7d7d 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,21 +1,24 @@ +struct VertexInput { + @location(0) position: vec3, + @location(1) color: vec3, +} + struct VertexOutput { @builtin(position) clip_position: vec4, - @location(1) color: vec4, + @location(1) color: vec3, }; @vertex fn vs_main( - @builtin(vertex_index) in_vertex_index: u32, + model: VertexInput, ) -> VertexOutput { var out: VertexOutput; - let x = f32(1 - i32(in_vertex_index)) * 0.5; - let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; - out.clip_position = vec4(x, y, 0.0, 1.0); - out.color = vec4(smoothstep(-0.5, 0.5, x), smoothstep(-0.5, 0.5, y), 0.0, 1.0); + out.color = model.color; + out.clip_position = vec4(model.position, 1.0); return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return in.color; + return vec4(in.color, 1.0); } \ No newline at end of file From b9380beb78e982d466cbb73fe4d5a3459cc79c66 Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sun, 16 Jul 2023 12:53:25 -0700 Subject: [PATCH 7/8] Updating `README.md` --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8b4f3e4..9392abd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# twodee raycaster -A very basic 2D raycaster built with Rust +# 3D Graphics Engine +Basic Graphics Engine built with `wgpu` in Rust -![Demo picture](https://github.com/manorajesh/twodeeraycaster/blob/master/images/demo.png) +![Demo picture](https://github.com/manorajesh/rusty-graphics/blob/master/images/demo.png) ## Installation ``` -git clone https://github.com/manorajesh/twodeeraycaster.git && cd twodeeraycaster +git clone https://github.com/manorajesh/rusty-graphics.git && cd rusty-graphics cargo run ``` @@ -13,4 +13,4 @@ cargo run Use the arrow keys to traverse the extremely entertaining room and watch the shadows #### Important Code -The [`draw`](https://github.com/manorajesh/twodeeraycaster/blob/cdf31fba1238801ae4804fe2ce98fec9d935985d/src/raycaster.rs#L147-L207) function is responsible for casting the rays and rendering them accordingly. +The [`draw`](https://github.com/manorajesh/rusty-graphics/blob/cdf31fba1238801ae4804fe2ce98fec9d935985d/src/raycaster.rs#L147-L207) function is responsible for casting the rays and rendering them accordingly. From 1677643831461ff6a69eb1f72b4635798a5d787a Mon Sep 17 00:00:00 2001 From: manorajesh Date: Sun, 16 Jul 2023 13:04:53 -0700 Subject: [PATCH 8/8] New picture --- README.md | 2 +- images/demo.png | Bin 33766 -> 9639 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9392abd..652d5da 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # 3D Graphics Engine Basic Graphics Engine built with `wgpu` in Rust -![Demo picture](https://github.com/manorajesh/rusty-graphics/blob/master/images/demo.png) +![Demo picture](https://github.com/manorajesh/rusty-graphics/blob/wgpu/images/demo.png) ## Installation ``` diff --git a/images/demo.png b/images/demo.png index 9b95141342e12f6012f7cb8db6f670cabf8896a6..6a52b9340294e7a4d44191e5a53d64bde43d7bf4 100644 GIT binary patch literal 9639 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 H>>m6-=TDx7 literal 33766 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

c3rFWul0sTq5T^G?3yB0B-iYZ0UM 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