diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..8b662bc --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[registries.strafesnet] +index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 71b575d..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -/textures \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 70a6c92..07bfa78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" +checksum = "79faae4620f45232f599d9bc7b290f88247a0834162c4495ab2f02d60004adfb" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,9 +20,9 @@ checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -33,27 +33,27 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "android-activity" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39b801912a977c3fd52d80511fe1c0c8480c6f957f21ae2ce1b92ffe970cf4b9" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", - "bitflags 2.4.2", + "bitflags 2.6.0", "cc", "cesu8", "jni", @@ -62,7 +62,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror", ] @@ -99,15 +99,15 @@ checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "as-raw-xcb-connection" @@ -117,11 +117,11 @@ checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" [[package]] name = "ash" -version = "0.37.3+1.3.251" +version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.7.4", + "libloading", ] [[package]] @@ -132,9 +132,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base64" @@ -143,10 +143,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "binrw" -version = "0.13.3" +name = "beef" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173901312e9850391d4d7c1318c4e099fdc037d61870fca427429830efdb4e5f" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "binrw" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f36b7cb3ab9ff6a2858650d8dc360e783a5d14dc29594db48c56a3c233cc265" dependencies = [ "array-init", "binrw_derive", @@ -155,9 +161,9 @@ dependencies = [ [[package]] name = "binrw_derive" -version = "0.13.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb515fdd6f8d3a357c8e19b8ec59ef53880807864329b1cb1cba5c53bf76557e" +checksum = "20ea7a8c5c8eeffffac6d54d172444e15beffac6f817fac714460a9a9aa88da3" dependencies = [ "either", "owo-colors", @@ -168,18 +174,18 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "d2c54ff287cfc0a34f38a6b832ea1bd8e448a330b3e40a50859e6488bee07f22" [[package]] name = "bitflags" @@ -189,15 +195,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake3" -version = "1.5.0" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" dependencies = [ "arrayref", "arrayvec", @@ -212,30 +218,36 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" -[[package]] -name = "block-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" -dependencies = [ - "objc-sys", -] - [[package]] name = "block2" -version = "0.3.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "block-sys", "objc2", ] [[package]] -name = "bumpalo" -version = "3.14.0" +name = "bnum" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "50202def95bf36cb7d1d7a7962cea1c36a3f8ad42425e5d2b71d7acb8041b5b8" + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bv" @@ -248,22 +260,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.14.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.5.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", ] [[package]] @@ -280,17 +292,17 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "calloop" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "log", "polling", "rustix", @@ -300,9 +312,9 @@ dependencies = [ [[package]] name = "calloop-wayland-source" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", "rustix", @@ -312,12 +324,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "e8d9e0b4957f635b8d3da819d0db5603620467ecf1f692d22a8c2717ce27e6d8" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -338,6 +351,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cgmath" version = "0.18.0" @@ -391,9 +410,9 @@ dependencies = [ [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -401,24 +420,24 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "configparser" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec6d3da8e550377a85339063af6e3735f4b1d9392108da4e083a1b3b9820288" +checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "core-foundation" @@ -432,15 +451,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core-graphics" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -462,9 +481,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -477,18 +496,24 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "cursor-icon" @@ -498,12 +523,12 @@ checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" [[package]] name = "d3d12" -version = "0.19.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" +checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" dependencies = [ - "bitflags 2.4.2", - "libloading 0.8.1", + "bitflags 2.6.0", + "libloading", "winapi", ] @@ -513,7 +538,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479dfe1e6737aa9e96c6ac7b69689dc4c32da8383f2c12744739d76afa8b66c4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "byteorder 1.5.0", "enum-primitive-derive", "num-traits", @@ -531,20 +556,35 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.1", + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" +dependencies = [ + "litrs", ] [[package]] name = "downcast-rs" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "enum-primitive-derive" @@ -565,9 +605,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -579,6 +619,24 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" +[[package]] +name = "fixed_wide" +version = "0.1.1" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "d9c2cf115b3785ede870fada07e8b1aeba3378345b4ca86fe3c772ecabc05c0f" +dependencies = [ + "arrayvec", + "bnum", + "paste", + "ratio_ops", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.5.0" @@ -597,7 +655,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", ] [[package]] @@ -606,6 +664,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -618,9 +685,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -640,9 +707,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.25.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" +checksum = "c28091a37a5d09b555cb6628fd954da299b536433834f5b8e59eba78e0cbbf8a" [[package]] name = "glow" @@ -658,9 +725,9 @@ dependencies = [ [[package]] name = "glutin_wgl_sys" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +checksum = "0a4e1951bbd9434a81aa496fe59ccc2235af3820d27b85f9314e279609211e2c" dependencies = [ "gl_generator", ] @@ -671,7 +738,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "gpu-alloc-types", ] @@ -681,14 +748,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", ] [[package]] name = "gpu-allocator" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +checksum = "fdd4240fc91d3433d5e5b0fc5b67672d771850dc19bbee03c1381e19322803d7" dependencies = [ "log", "presser", @@ -699,49 +766,71 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] name = "gpu-descriptor-types" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", ] [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hassle-rs" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af2a7e73e1f34c48da31fb668a907f250794837e08faa144fd24f0b8b741e890" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "com", "libc", - "libloading 0.8.1", + "libloading", "thiserror", "widestring", "winapi", ] +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hexf-parse" version = "0.2.1" @@ -749,31 +838,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] -name = "icrate" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" +name = "id" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "2337e7a6c273082b672e377e159d7a168fb51438461b7c4033c79a515dd7a25a" dependencies = [ - "block2", - "dispatch", - "objc2", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", ] [[package]] name = "indexmap" -version = "2.2.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433de089bd45971eecf4668ee0ee8f4cec17db4f8bd8f7bc3197a6ce37aa7d9b" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -802,18 +901,18 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -825,7 +924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.1", + "libloading", "pkg-config", ] @@ -837,9 +936,9 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "lazy-regex" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c" +checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -848,46 +947,36 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "3.1.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b" +checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.48", + "syn 2.0.79", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "windows-targets 0.52.6", ] [[package]] @@ -896,22 +985,39 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "libc", "redox_syscall 0.4.1", ] +[[package]] +name = "linear_ops" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "b2e6977ac24f47086d8a7a2d4ae1c720e86dfdc8407cf5e34c18bfa01053c456" +dependencies = [ + "fixed_wide", + "paste", + "ratio_ops", +] + [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -919,25 +1025,66 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "logos" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6b6e02facda28ca5fb8dbe4b152496ba3b1bd5a4b40bb2b1b2d8ad74e0f39b" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32eb6b5f26efacd015b000bfc562186472cd9b34bdba3f6b264e2a052676d10" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "syn 2.0.79", +] + +[[package]] +name = "logos-derive" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5d0c5463c911ef55624739fc353238b4e310f0144be1f875dc42fec6bfd5ec" +dependencies = [ + "logos-codegen", +] + +[[package]] +name = "luau0-src" +version = "0.10.3+luau640" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f39d12b514a676c943990cfbe6200fedcb9c293c8c9219d29be512a6969be92" +dependencies = [ + "cc", +] [[package]] name = "lz4" -version = "1.24.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ - "libc", "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.9.4" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -973,26 +1120,26 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] [[package]] name = "metal" -version = "0.27.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "block", "core-graphics-types", "foreign-types", @@ -1002,19 +1149,69 @@ dependencies = [ ] [[package]] -name = "naga" -version = "0.19.0" +name = "miette" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8878eb410fc90853da3908aebfe61d73d26d4437ef850b70050461f939509899" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" dependencies = [ + "cfg-if", + "miette-derive", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "mlua" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7" +dependencies = [ + "bstr", + "libloading", + "mlua-sys", + "num-traits", + "once_cell", + "rustc-hash 2.0.0", +] + +[[package]] +name = "mlua-sys" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe026d6bd1583a9cf9080e189030ddaea7e6f5f0deb366a8e26f8a26c4135b8" +dependencies = [ + "cc", + "cfg-if", + "luau0-src", + "pkg-config", +] + +[[package]] +name = "naga" +version = "22.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" +dependencies = [ + "arrayvec", "bit-set", - "bitflags 2.4.2", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", "indexmap", "log", - "num-traits", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -1023,14 +1220,14 @@ dependencies = [ [[package]] name = "ndk" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", "thiserror", @@ -1052,41 +1249,44 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.17" +name = "ndk-sys" +version = "0.6.0+11769913" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", ] -[[package]] -name = "obj" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059c95245738cdc7b40078cdd51a23200252a4c0a0a6dd005136152b3f467a4a" - [[package]] name = "objc" version = "0.2.7" @@ -1094,45 +1294,219 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", - "objc_exception", ] [[package]] name = "objc-sys" -version = "0.3.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ "objc-sys", "objc2-encode", ] [[package]] -name = "objc2-encode" -version = "3.0.0" +name = "objc2-app-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] [[package]] -name = "objc_exception" -version = "0.1.2" +name = "objc2-cloud-kit" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ - "cc", + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "orbclient" @@ -1145,9 +1519,9 @@ dependencies = [ [[package]] name = "owned_ttf_parser" -version = "0.20.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7" +checksum = "490d3a563d3122bf7c911a59b0add9389e5ec0f5f0c3ac6b91ff235a0e6a7f90" dependencies = [ "ttf-parser", ] @@ -1160,9 +1534,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1170,22 +1544,47 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.7", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn 2.0.79", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "percent-encoding" @@ -1194,29 +1593,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pin-project-lite" -version = "0.2.13" +name = "pin-project" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polling" -version = "3.3.2" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545c980a3880efd47b2e262f6a4bb6daad6555cf3367aa9c4e52895f69537a41" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi", "pin-project-lite", "rustix", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1226,10 +1646,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "portable-atomic" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "presser" @@ -1239,55 +1668,55 @@ checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e" +checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b322d7d65c1ab449be3c890fcbd0db6e1092d0dd05d79dba2dd28032cebeb05" +checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd" dependencies = [ "quote", - "syn 2.0.48", + "syn 2.0.79", ] [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1328,17 +1757,23 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab" +[[package]] +name = "ratio_ops" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "01239195d6afe0509e7e3511b716c0540251dfe7ece0a9a5a27116afb766c42c" + [[package]] name = "raw-window-handle" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rbx_binary" version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6314dd6bf5c21d0598cdb53cf5d241aa643ba41da8b8abf7402b4a35096f03f6" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "18b401155b93f7151217bf51e36bdfa7bddcaf5f0d26b563c9ac3b08a3701c27" dependencies = [ "log", "lz4", @@ -1351,19 +1786,29 @@ dependencies = [ [[package]] name = "rbx_dom_weak" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b67b56bac99849c2e3c57547b036927f71c57cf7f4d900d04e3e4ee774ec316" +version = "2.9.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "2a6b916687c98aaea36f9c03e80906bfafab057bebee248628c8c04def807f43" dependencies = [ "rbx_types", "serde", ] [[package]] -name = "rbx_reflection" -version = "4.5.0" +name = "rbx_mesh" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41509c991b53a7276a746a795eae2b9204f398164920f61976995b47fe1722" +checksum = "864ead0e98afce28c960f653d6203483834890d07f87b60e2f01415530a2fe9d" +dependencies = [ + "binrw", + "lazy-regex", +] + +[[package]] +name = "rbx_reflection" +version = "4.7.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "c1b43fe592a4ce6fe54eb215fb82735efbb516d2cc045a94e3dc0234ff293620" dependencies = [ "rbx_types", "serde", @@ -1372,9 +1817,9 @@ dependencies = [ [[package]] name = "rbx_reflection_database" -version = "0.2.10+roblox-607" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e20c06fa41f7aadc79005c8354f592b2c2f4d0c61e1080ed5718dafc30aea0" +version = "0.2.12+roblox-638" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "2e772bb9e1bc0ebe65d338f876d1bb1ea22e15a8f9a82e8245028010c2fea3c9" dependencies = [ "lazy_static", "rbx_reflection", @@ -1384,9 +1829,9 @@ dependencies = [ [[package]] name = "rbx_types" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca23bfd469d067d81ef14f65fe09aeddc25abcf576a889d1a7664fe021cf18c" +version = "1.10.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "d7a390c44034fa448c53bd0983dfc2d70d8d6b2f65be4f164d4bec8b6a2a2d09" dependencies = [ "base64", "bitflags 1.3.2", @@ -1400,8 +1845,8 @@ dependencies = [ [[package]] name = "rbx_xml" version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c03f95500961c32340791d1fabd4587f6873bdbff077ecca6ae32db7960dea" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "d6d1a15f58a1e4b4f578abe6eb5e1461cb16eea82fb4a147d5995c9b79f08d1f" dependencies = [ "base64", "log", @@ -1411,15 +1856,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -1430,10 +1866,19 @@ dependencies = [ ] [[package]] -name = "regex" -version = "1.10.3" +name = "redox_syscall" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1443,9 +1888,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1454,21 +1899,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "renderdoc-sys" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "rmp" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" dependencies = [ "byteorder 1.5.0", "num-traits", @@ -1477,15 +1922,29 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.1.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" dependencies = [ "byteorder 1.5.0", "rmp", "serde", ] +[[package]] +name = "roblox_emulator" +version = "0.4.1" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "6935943e8d473c8d19b52623877bee18421743072e2675730886d13592008266" +dependencies = [ + "glam", + "mlua", + "rbx_dom_weak", + "rbx_reflection", + "rbx_reflection_database", + "rbx_types", +] + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1493,12 +1952,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] -name = "rustix" -version = "0.38.30" +name = "rustc-hash" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -1528,9 +1993,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" -version = "0.8.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b2eaf3a5b264a521b988b2e73042e742df700c4f962cde845d1541adb46550" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", @@ -1541,24 +2006,30 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "slab" version = "0.4.9" @@ -1579,17 +2050,17 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smithay-client-toolkit" -version = "0.18.0" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e3d9941fa3bacf7c2bf4b065304faa14164151254cd16ce1b1bc8fc381600f" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "calloop", "calloop-wayland-source", "cursor-icon", @@ -1610,9 +2081,9 @@ dependencies = [ [[package]] name = "smol_str" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6845563ada680337a52d43bb0b29f396f2d911616f6573012645b9e3d048a49" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" dependencies = [ "serde", ] @@ -1623,7 +2094,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", ] [[package]] @@ -1634,33 +2105,89 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strafe-client" -version = "0.9.0" +version = "0.10.5" dependencies = [ "bytemuck", "configparser", "ddsfile", "glam", - "lazy-regex", - "obj", + "id", "parking_lot", "pollster", - "rbx_binary", - "rbx_dom_weak", - "rbx_reflection_database", - "rbx_xml", + "strafesnet_bsp_loader", "strafesnet_common", - "vbsp", - "vmdl", + "strafesnet_deferred_loader", + "strafesnet_rbx_loader", + "strafesnet_snf", "wgpu", "winit", ] [[package]] -name = "strafesnet_common" -version = "0.1.0" -source = "git+https://git.itzana.me/StrafesNET/common?rev=434ca29aef7e3015c9ca1ed45de8fef42e33fdfb#434ca29aef7e3015c9ca1ed45de8fef42e33fdfb" +name = "strafesnet_bsp_loader" +version = "0.2.2" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "34f944637bc3b3ed4c430819c672174b3a3edfd51f79b6b87f4931e3714a398e" dependencies = [ "glam", + "strafesnet_common", + "vbsp", + "vmdl", +] + +[[package]] +name = "strafesnet_common" +version = "0.5.2" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "91cc1f3699bd8248da18bf5d11273264396a257b5d47b8558acb2cb4e1761219" +dependencies = [ + "arrayvec", + "bitflags 2.6.0", + "fixed_wide", + "glam", + "id", + "linear_ops", + "ratio_ops", +] + +[[package]] +name = "strafesnet_deferred_loader" +version = "0.4.1" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "cb47034893e945c640063a6c0fb09c6186dcc9f0b221b8c41f5a22070fe430f4" +dependencies = [ + "strafesnet_common", + "url", + "vbsp", +] + +[[package]] +name = "strafesnet_rbx_loader" +version = "0.5.2" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "bb852ee329d26410daee50f1e583ea8286caf6a81a42ff887b78f21477c48731" +dependencies = [ + "bytemuck", + "glam", + "lazy-regex", + "rbx_binary", + "rbx_dom_weak", + "rbx_mesh", + "rbx_reflection_database", + "rbx_xml", + "roblox_emulator", + "strafesnet_common", +] + +[[package]] +name = "strafesnet_snf" +version = "0.2.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "c6e8856d79c29bd5687b08bc1653370f7e242c84d5c06afa8629bd3e00c433bf" +dependencies = [ + "binrw", + "id", + "strafesnet_common", ] [[package]] @@ -1669,6 +2196,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "structmeta" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn 2.0.79", +] + +[[package]] +name = "structmeta-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "syn" version = "1.0.109" @@ -1682,26 +2232,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "syn_util" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754c4559b79657554e9d8a0d56e65e490c76d382b9c23108364ec4125dea23c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -1713,29 +2252,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", ] [[package]] name = "tiny-skia" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a067b809476893fce6a254cf285850ff69c847e6cfbade6a20b655b6c7e80d" +checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", "arrayvec", @@ -1747,9 +2286,9 @@ dependencies = [ [[package]] name = "tiny-skia-path" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de35e8a90052baaaf61f171680ac2f8e925a1e43ea9d2e3a00514772250e541" +checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" dependencies = [ "arrayref", "bytemuck", @@ -1757,16 +2296,31 @@ dependencies = [ ] [[package]] -name = "toml_datetime" -version = "0.6.5" +name = "tinyvec" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", @@ -1792,7 +2346,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", ] [[package]] @@ -1806,84 +2360,114 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.20.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +checksum = "5be21190ff5d38e8b4a2d3b6a3ae57f612cc39c96e83cedeaf7abc338a8bac4a" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] [[package]] name = "vbsp" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9267540dab0c93bb5201c40ba3b2d027e2717bf355a8f9bf25377b06a5b32f6" +checksum = "f14a5685e0bb386aac9b9c6046a05152a46a0bc58d53afb3fbe577f1a1c2bb05" dependencies = [ "ahash", "arrayvec", "binrw", - "bitflags 2.4.2", + "bitflags 2.6.0", "bv", "cgmath", "itertools", "lzma-rs", "num_enum", + "serde", "static_assertions", "thiserror", - "vbsp-derive", + "vdf-reader", "zip-lzma", ] [[package]] -name = "vbsp-derive" -version = "0.1.0" +name = "vdf-reader" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ade687fadf34b1b7502387fc9eb7b4032ddc9b93022d31356e9984c957abaad" +checksum = "543945fdc3d51b20e3e0f5fd845ddeca4a270e56522035cf152105bc144ffd65" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "syn_util", + "logos", + "miette", + "parse-display", + "serde", + "thiserror", ] [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vmdl" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "892922743c4c107372331efd8f67c57282590f8c18c26b4465c4b0e1e6678664" +checksum = "da2f6b8c22da8937403a5da864e7ea390f30cd40b7a077079e40279aa6a4f553" dependencies = [ "arrayvec", - "bitflags 2.4.2", + "bitflags 2.6.0", "bytemuck", "cgmath", + "half", "itertools", + "num_enum", "static_assertions", "thiserror", "tracing", @@ -1891,9 +2475,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1907,34 +2491,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -1944,9 +2529,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1954,28 +2539,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "wayland-backend" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" +checksum = "056535ced7a150d45159d3a8dc30f91a2e2d588ca0b23f70e56033622b8016f6" dependencies = [ "cc", "downcast-rs", @@ -1987,11 +2572,11 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.2" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" +checksum = "e3f45d1222915ef1fd2057220c1d9d9624b7654443ea35c3877f7a52bd0a5a2d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "rustix", "wayland-backend", "wayland-scanner", @@ -2003,16 +2588,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.1" +version = "0.31.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" +checksum = "3a94697e66e76c85923b0d28a0c251e8f0666f58fc47d316c0f4da6da75d37cb" dependencies = [ "rustix", "wayland-client", @@ -2021,11 +2606,11 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "2b5755d77ae9040bb872a25026555ce4cb0ae75fd923e90d25fba07d81057de0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -2033,11 +2618,11 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +checksum = "8a0a41a6875e585172495f7a96dfa42ca7e0213868f4f15c313f7c33221a7eff" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2046,11 +2631,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "dad87b5fd1b1d3ca2f792df8f686a2a11e3fe1077b71096f7a175ab699f89109" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -2059,9 +2644,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.1" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" +checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", "quick-xml", @@ -2070,9 +2655,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.1" +version = "0.31.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +checksum = "efa8ac0d8e8ed3e3b5c9fc92c7881406a268e11555abe36493efabe649a29e09" dependencies = [ "dlib", "log", @@ -2082,9 +2667,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -2092,9 +2677,9 @@ dependencies = [ [[package]] name = "web-time" -version = "0.2.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -2102,13 +2687,13 @@ dependencies = [ [[package]] name = "wgpu" -version = "0.19.1" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d" +checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ "arrayvec", - "cfg-if", - "cfg_aliases", + "cfg_aliases 0.1.1", + "document-features", "js-sys", "log", "naga", @@ -2127,15 +2712,15 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "0.19.0" +version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b15e451d4060ada0d99a64df44e4d590213496da7c4f245572d51071e8e30ed" +checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.4.2", - "cfg_aliases", - "codespan-reporting", + "bitflags 2.6.0", + "cfg_aliases 0.1.1", + "document-features", "indexmap", "log", "naga", @@ -2143,27 +2728,26 @@ dependencies = [ "parking_lot", "profiling", "raw-window-handle", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", - "web-sys", "wgpu-hal", "wgpu-types", ] [[package]] name = "wgpu-hal" -version = "0.19.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bb47856236bfafc0bc591a925eb036ac19cd987624a447ff353e7a7e7e6f72" +checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", "arrayvec", "ash", "bit-set", - "bitflags 2.4.2", + "bitflags 2.6.0", "block", - "cfg_aliases", + "cfg_aliases 0.1.1", "core-graphics-types", "d3d12", "glow", @@ -2175,10 +2759,11 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.1", + "libloading", "log", "metal", "naga", + "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", "parking_lot", @@ -2186,7 +2771,7 @@ dependencies = [ "range-alloc", "raw-window-handle", "renderdoc-sys", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "thiserror", "wasm-bindgen", @@ -2197,20 +2782,20 @@ dependencies = [ [[package]] name = "wgpu-types" -version = "0.19.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "895fcbeb772bfb049eb80b2d6e47f6c9af235284e9703c96fc0218a42ffd5af2" +checksum = "bc9d91f0e2c4b51434dfa6db77846f2793149d8e73f800fa2e41f52b8eac3c5d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "js-sys", "web-sys", ] [[package]] name = "widestring" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -2230,11 +2815,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2250,7 +2835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2259,7 +2844,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -2271,22 +2856,22 @@ dependencies = [ "windows-targets 0.42.2", ] -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2321,17 +2906,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2348,9 +2934,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2366,9 +2952,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2384,9 +2970,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2402,9 +2994,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2420,9 +3012,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2438,9 +3030,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2456,43 +3048,47 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.29.10" +version = "0.30.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c824f11941eeae66ec71111cc2674373c772f482b58939bb4066b642aa2ffcf" +checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.4.2", + "bitflags 2.6.0", + "block2", "bytemuck", "calloop", - "cfg_aliases", + "cfg_aliases 0.2.1", + "concurrent-queue", "core-foundation", "core-graphics", "cursor-icon", - "icrate", + "dpi", "js-sys", "libc", - "log", "memmap2", "ndk", - "ndk-sys", "objc2", - "once_cell", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", "orbclient", "percent-encoding", + "pin-project", "raw-window-handle", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "rustix", "sctk-adwaita", "smithay-client-toolkit", "smol_str", + "tracing", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", @@ -2502,7 +3098,7 @@ dependencies = [ "wayland-protocols-plasma", "web-sys", "web-time", - "windows-sys 0.48.0", + "windows-sys 0.52.0", "x11-dl", "x11rb", "xkbcommon-dl", @@ -2510,9 +3106,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.5.35" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1931d78a9c73861da0134f453bb1f790ce49b2e30eba8410b4b79bac72b46a2d" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -2530,14 +3126,14 @@ dependencies = [ [[package]] name = "x11rb" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.1", + "libloading", "once_cell", "rustix", "x11rb-protocol", @@ -2545,23 +3141,23 @@ dependencies = [ [[package]] name = "x11rb-protocol" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0ccd7b4a5345edfcd0c3535718a4e9ff7798ffc536bb5b5a0e26ff84732911" +checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" [[package]] name = "xkbcommon-dl" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6924668544c48c0133152e7eec86d644a056ca3d09275eb8d5cdb9855f9d8699" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "dlib", "log", "once_cell", @@ -2570,34 +3166,35 @@ dependencies = [ [[package]] name = "xkeysym" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder 1.5.0", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 02d986d..3a3c79b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,30 +1,36 @@ [package] name = "strafe-client" -version = "0.9.0" +version = "0.10.5" edition = "2021" +repository = "https://git.itzana.me/StrafesNET/strafe-client" +license = "Custom" +description = "StrafesNET game client for bhop and surf." +authors = ["Rhys Lloyd "] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = ["snf"] +snf = ["dep:strafesnet_snf"] +source = ["dep:strafesnet_deferred_loader", "dep:strafesnet_bsp_loader"] +roblox = ["dep:strafesnet_deferred_loader", "dep:strafesnet_rbx_loader"] [dependencies] bytemuck = { version = "1.13.1", features = ["derive"] } configparser = "3.0.2" ddsfile = "0.5.1" -glam = "0.25.0" -lazy-regex = "3.0.2" -obj = "0.10.2" +glam = "0.29.0" +id = { version = "0.1.0", registry = "strafesnet" } parking_lot = "0.12.1" pollster = "0.3.0" -rbx_binary = "0.7.1" -rbx_dom_weak = "2.5.0" -rbx_reflection_database = "0.2.7" -rbx_xml = "0.13.1" -strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "434ca29aef7e3015c9ca1ed45de8fef42e33fdfb" } -vbsp = "0.5.0" -vmdl = "0.1.1" -wgpu = "0.19.0" -winit = "0.29.2" +strafesnet_bsp_loader = { version = "0.2.1", registry = "strafesnet", optional = true } +strafesnet_common = { version = "0.5.2", registry = "strafesnet" } +strafesnet_deferred_loader = { version = "0.4.0", features = ["legacy"], registry = "strafesnet", optional = true } +strafesnet_rbx_loader = { version = "0.5.1", registry = "strafesnet", optional = true } +strafesnet_snf = { version = "0.2.0", registry = "strafesnet", optional = true } +wgpu = "22.1.0" +winit = "0.30.5" -#[profile.release] +[profile.release] #lto = true -#strip = true -#codegen-units = 1 +strip = true +codegen-units = 1 diff --git a/src/face_crawler.rs b/src/face_crawler.rs index 8224252..4fc22d9 100644 --- a/src/face_crawler.rs +++ b/src/face_crawler.rs @@ -1,32 +1,33 @@ use crate::physics::Body; -use crate::model_physics::{FEV,MeshQuery,DirectedEdge}; -use strafesnet_common::integer::{Time,Planar64}; -use strafesnet_common::zeroes::zeroes2; +use crate::model_physics::{GigaTime,FEV,MeshQuery,DirectedEdge,MinkowskiMesh,MinkowskiFace,MinkowskiDirectedEdge,MinkowskiVert}; +use strafesnet_common::integer::{Time,Fixed,Ratio}; +#[derive(Debug)] enum Transition{ Miss, - Next(FEV,Time), - Hit(F,Time), + Next(FEV,GigaTime), + Hit(F,GigaTime), } - fn next_transition(fev:&FEV,time:Time,mesh:&impl MeshQuery,body:&Body,time_limit:Time)->Transition{ +type MinkowskiFEV=FEV; +type MinkowskiTransition=Transition; + + fn next_transition(fev:&MinkowskiFEV,body_time:GigaTime,mesh:&MinkowskiMesh,body:&Body,mut best_time:GigaTime)->MinkowskiTransition{ //conflicting derivative means it crosses in the wrong direction. //if the transition time is equal to an already tested transition, do not replace the current best. - let mut best_time=time_limit; - let mut best_transtition=Transition::Miss; + let mut best_transition=MinkowskiTransition::Miss; match fev{ - &FEV::::Face(face_id)=>{ + &MinkowskiFEV::Face(face_id)=>{ //test own face collision time, ignoring roots with zero or conflicting derivative //n=face.normal d=face.dot //n.a t^2+n.v t+n.p-d==0 let (n,d)=mesh.face_nd(face_id); //TODO: use higher precision d value? //use the mesh transform translation instead of baking it into the d value. - for t in zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ + if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ + best_time=dt; + best_transition=MinkowskiTransition::Hit(face_id,dt); break; } } @@ -36,18 +37,18 @@ enum Transition{ let n=n.cross(edge_n); let verts=mesh.edge_verts(directed_edge_id.as_undirected()); //WARNING: d is moved out of the *2 block because of adding two vertices! - for t in zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))),n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Edge(directed_edge_id.as_undirected()),t); + //WARNING: precision is swept under the rug! + for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ + if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ + best_time=dt; + best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt); break; } } } //if none: }, - &FEV::::Edge(edge_id)=>{ + &MinkowskiFEV::Edge(edge_id)=>{ //test each face collision time, ignoring roots with zero or conflicting derivative let edge_n=mesh.edge_n(edge_id); let edge_verts=mesh.edge_verts(edge_id); @@ -57,11 +58,10 @@ enum Transition{ //edge_n gets parity from the order of edge_faces let n=face_n.cross(edge_n)*((i as i64)*2-1); //WARNING yada yada d *2 - for t in zeroes2(n.dot(delta_pos),n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Face(edge_face_id),t); + for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){ + if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ + best_time=dt; + best_transition=MinkowskiTransition::Next(MinkowskiFEV::Face(edge_face_id),dt); break; } } @@ -70,27 +70,27 @@ enum Transition{ for (i,&vert_id) in edge_verts.iter().enumerate(){ //vertex normal gets parity from vert index let n=edge_n*(1-2*(i as i64)); - for t in zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Vert(vert_id),t); + for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ + if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ + let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); + best_time=dt; + best_transition=MinkowskiTransition::Next(MinkowskiFEV::Vert(vert_id),dt); break; } } } //if none: }, - &FEV::::Vert(vert_id)=>{ + &MinkowskiFEV::Vert(vert_id)=>{ //test each edge collision time, ignoring roots with zero or conflicting derivative for &directed_edge_id in mesh.vert_edges(vert_id).iter(){ //edge is directed away from vertex, but we want the dot product to turn out negative let n=-mesh.directed_edge_n(directed_edge_id); - for t in zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ - let t=body.time+Time::from(t); - if time<=t&&t::Edge(directed_edge_id.as_undirected()),t); + for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){ + if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ + let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4()); + best_time=dt; + best_transition=MinkowskiTransition::Next(MinkowskiFEV::Edge(directed_edge_id.as_undirected()),dt); break; } } @@ -98,22 +98,30 @@ enum Transition{ //if none: }, } - best_transtition + best_transition } pub enum CrawlResult{ Miss(FEV), - Hit(F,Time), + Hit(F,GigaTime), } -pub fn crawl_fev(mut fev:FEV,mesh:&impl MeshQuery,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult{ - let mut time=start_time; +type MinkowskiCrawlResult=CrawlResult; +pub fn crawl_fev(mut fev:MinkowskiFEV,mesh:&MinkowskiMesh,relative_body:&Body,start_time:Time,time_limit:Time)->MinkowskiCrawlResult{ + let mut body_time={ + let r=(start_time-relative_body.time).to_ratio(); + Ratio::new(r.num.fix_4(),r.den.fix_4()) + }; + let time_limit={ + let r=(time_limit-relative_body.time).to_ratio(); + Ratio::new(r.num.fix_4(),r.den.fix_4()) + }; for _ in 0..20{ - match next_transition(&fev,time,mesh,relative_body,time_limit){ + match next_transition(&fev,body_time,mesh,relative_body,time_limit){ Transition::Miss=>return CrawlResult::Miss(fev), - Transition::Next(next_fev,next_time)=>(fev,time)=(next_fev,next_time), + Transition::Next(next_fev,next_time)=>(fev,body_time)=(next_fev,next_time), Transition::Hit(face,time)=>return CrawlResult::Hit(face,time), } } //TODO: fix all bugs - println!("Too many iterations! Using default behaviour instead of crashing..."); + //println!("Too many iterations! Using default behaviour instead of crashing..."); CrawlResult::Miss(fev) } diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..3adc397 --- /dev/null +++ b/src/file.rs @@ -0,0 +1,144 @@ +use std::io::Read; + +#[derive(Debug)] +pub enum ReadError{ + #[cfg(feature="roblox")] + Roblox(strafesnet_rbx_loader::ReadError), + #[cfg(feature="source")] + Source(strafesnet_bsp_loader::ReadError), + #[cfg(feature="snf")] + StrafesNET(strafesnet_snf::Error), + #[cfg(feature="snf")] + StrafesNETMap(strafesnet_snf::map::Error), + Io(std::io::Error), + UnknownFileFormat, +} +impl std::fmt::Display for ReadError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for ReadError{} + +pub enum DataStructure{ + #[cfg(feature="roblox")] + Roblox(strafesnet_rbx_loader::Model), + #[cfg(feature="source")] + Source(strafesnet_bsp_loader::Bsp), + #[cfg(feature="snf")] + StrafesNET(strafesnet_common::map::CompleteMap), +} + +pub fn read(input:R)->Result{ + let mut buf=std::io::BufReader::new(input); + let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?; + match &peek[0..4]{ + #[cfg(feature="roblox")] + b"Ok(DataStructure::Roblox(strafesnet_rbx_loader::read(buf).map_err(ReadError::Roblox)?)), + #[cfg(feature="source")] + b"VBSP"=>Ok(DataStructure::Source(strafesnet_bsp_loader::read(buf).map_err(ReadError::Source)?)), + #[cfg(feature="snf")] + b"SNFM"=>Ok(DataStructure::StrafesNET( + strafesnet_snf::read_map(buf).map_err(ReadError::StrafesNET)? + .into_complete_map().map_err(ReadError::StrafesNETMap)? + )), + _=>Err(ReadError::UnknownFileFormat), + } +} + +#[derive(Debug)] +pub enum LoadError{ + ReadError(ReadError), + File(std::io::Error), + Io(std::io::Error), +} +impl std::fmt::Display for LoadError{ + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{self:?}") + } +} +impl std::error::Error for LoadError{} + +pub fn load>(path:P)->Result{ + //blocking because it's simpler... + let file=std::fs::File::open(path).map_err(LoadError::File)?; + match read(file).map_err(LoadError::ReadError)?{ + #[cfg(feature="snf")] + DataStructure::StrafesNET(map)=>Ok(map), + #[cfg(feature="roblox")] + DataStructure::Roblox(model)=>{ + let mut place=model.into_place(); + place.run_scripts(); + + let mut loader=strafesnet_deferred_loader::roblox_legacy(); + + let (texture_loader,mesh_loader)=loader.get_inner_mut(); + + let map_step1=strafesnet_rbx_loader::convert( + &place, + |name|texture_loader.acquire_render_config_id(name), + |name|mesh_loader.acquire_mesh_id(name), + ); + + let meshpart_meshes=mesh_loader.load_meshes().map_err(LoadError::Io)?; + + let map_step2=map_step1.add_meshpart_meshes_and_calculate_attributes( + meshpart_meshes.into_iter().map(|(mesh_id,loader_model)| + (mesh_id,strafesnet_rbx_loader::data::RobloxMeshBytes::new(loader_model.get())) + ) + ); + + let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume(); + + let map=map_step2.add_render_configs_and_textures( + render_configs.into_iter(), + textures.into_iter().map(|(texture_id,texture)| + (texture_id,match texture{ + strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data, + }) + ) + ); + + Ok(map) + }, + #[cfg(feature="source")] + DataStructure::Source(bsp)=>{ + let mut loader=strafesnet_deferred_loader::source_legacy(); + + let (texture_loader,mesh_loader)=loader.get_inner_mut(); + + let map_step1=strafesnet_bsp_loader::convert( + &bsp, + |name|texture_loader.acquire_render_config_id(name), + |name|mesh_loader.acquire_mesh_id(name), + ); + + let prop_meshes=mesh_loader.load_meshes(bsp.as_ref()); + + let map_step2=map_step1.add_prop_meshes( + //the type conflagulator 9000 + prop_meshes.into_iter().map(|(mesh_id,loader_model)| + (mesh_id,strafesnet_bsp_loader::data::ModelData{ + mdl:strafesnet_bsp_loader::data::MdlData::new(loader_model.mdl.get()), + vtx:strafesnet_bsp_loader::data::VtxData::new(loader_model.vtx.get()), + vvd:strafesnet_bsp_loader::data::VvdData::new(loader_model.vvd.get()), + }) + ), + |name|texture_loader.acquire_render_config_id(name), + ); + + let (textures,render_configs)=loader.into_render_configs().map_err(LoadError::Io)?.consume(); + + let map=map_step2.add_render_configs_and_textures( + render_configs.into_iter(), + textures.into_iter().map(|(texture_id,texture)| + (texture_id,match texture{ + strafesnet_deferred_loader::texture::Texture::ImageDDS(data)=>data, + }) + ), + ); + + Ok(map) + }, + } +} diff --git a/src/graphics.rs b/src/graphics.rs index e83ee9e..6a13b11 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,92 +1,87 @@ use std::borrow::Cow; +use std::collections::{HashSet,HashMap}; +use strafesnet_common::map; use strafesnet_common::integer; +use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId}; use wgpu::{util::DeviceExt,AstcBlock,AstcChannel}; -use crate::model_graphics::{GraphicsVertex,GraphicsModelColor4,GraphicsModelInstance,GraphicsModelSingleTexture,IndexedGraphicsModelSingleTexture,IndexedGroupFixedTexture}; +use crate::model_graphics::{self,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex}; -#[derive(Clone)] -pub struct GraphicsModelUpdate{ - transform:Option, - color:Option, +struct Indices{ + count:u32, + buf:wgpu::Buffer, + format:wgpu::IndexFormat, } - -struct Entity{ - index_count:u32, - index_buf:wgpu::Buffer, -} -fn create_entities(device:&wgpu::Device,entities:&Vec>)->Vec{ - entities.iter().map(|indices|{ - let index_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ - label:Some("Index"), - contents:bytemuck::cast_slice(indices), - usage:wgpu::BufferUsages::INDEX, - }); - Entity{ - index_buf, - index_count:indices.len() as u32, +impl Indices{ + fn new(device:&wgpu::Device,indices:&Vec,format:wgpu::IndexFormat)->Self{ + Self{ + buf:device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ + label:Some("Index"), + contents:bytemuck::cast_slice(indices), + usage:wgpu::BufferUsages::INDEX, + }), + count:indices.len() as u32, + format, } - }).collect() + } } - struct GraphicsModel{ - entities:Vec, - model_buf:wgpu::Buffer, + indices:Indices, vertex_buf:wgpu::Buffer, bind_group:wgpu::BindGroup, - index_format:wgpu::IndexFormat, - instances:Vec, + instance_count:u32, } -pub struct GraphicsSamplers{ - repeat: wgpu::Sampler, +struct GraphicsSamplers{ + repeat:wgpu::Sampler, } -pub struct GraphicsBindGroupLayouts{ - model: wgpu::BindGroupLayout, +struct GraphicsBindGroupLayouts{ + model:wgpu::BindGroupLayout, } -pub struct GraphicsBindGroups { - camera: wgpu::BindGroup, - skybox_texture: wgpu::BindGroup, +struct GraphicsBindGroups{ + camera:wgpu::BindGroup, + skybox_texture:wgpu::BindGroup, } -pub struct GraphicsPipelines{ - skybox: wgpu::RenderPipeline, - model: wgpu::RenderPipeline, +struct GraphicsPipelines{ + skybox:wgpu::RenderPipeline, + model:wgpu::RenderPipeline, } -pub struct GraphicsCamera{ - screen_size: glam::UVec2, - fov: glam::Vec2,//slope +struct GraphicsCamera{ + screen_size:glam::UVec2, + fov:glam::Vec2,//slope //camera angles and such are extrapolated and passed in every time } #[inline] -fn perspective_rh(fov_x_slope: f32, fov_y_slope: f32, z_near: f32, z_far: f32) -> glam::Mat4 { +fn perspective_rh(fov_x_slope:f32,fov_y_slope:f32,z_near:f32,z_far:f32)->glam::Mat4{ //glam_assert!(z_near > 0.0 && z_far > 0.0); - let r = z_far / (z_near - z_far); + let r=z_far/(z_near-z_far); glam::Mat4::from_cols( - glam::Vec4::new(1.0/fov_x_slope, 0.0, 0.0, 0.0), - glam::Vec4::new(0.0, 1.0/fov_y_slope, 0.0, 0.0), - glam::Vec4::new(0.0, 0.0, r, -1.0), - glam::Vec4::new(0.0, 0.0, r * z_near, 0.0), + glam::Vec4::new(1.0/fov_x_slope,0.0,0.0,0.0), + glam::Vec4::new(0.0,1.0/fov_y_slope,0.0,0.0), + glam::Vec4::new(0.0,0.0,r,-1.0), + glam::Vec4::new(0.0,0.0,r*z_near,0.0), ) } impl GraphicsCamera{ pub fn proj(&self)->glam::Mat4{ - perspective_rh(self.fov.x, self.fov.y, 0.5, 2000.0) + perspective_rh(self.fov.x,self.fov.y,0.4,4000.0) } pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{ //f32 good enough for view matrix - glam::Mat4::from_translation(pos) * glam::Mat4::from_euler(glam::EulerRot::YXZ, angles.x, angles.y, 0f32) + glam::Mat4::from_translation(pos)*glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32) } - pub fn to_uniform_data(&self,(pos,angles): (glam::Vec3,glam::Vec2)) -> [f32; 16 * 4] { + pub fn to_uniform_data(&self,pos:glam::Vec3,angles:glam::Vec2)->[f32;16*4]{ let proj=self.proj(); - let proj_inv = proj.inverse(); + let proj_inv=proj.inverse(); let view_inv=self.world(pos,angles); let view=view_inv.inverse(); - let mut raw = [0f32; 16 * 4]; + let mut raw=[0f32; 16 * 4]; raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]); raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]); raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]); @@ -103,38 +98,44 @@ impl std::default::Default for GraphicsCamera{ } } +pub struct FrameState{ + pub body:crate::physics::Body, + pub camera:crate::physics::PhysicsCamera, + pub time:integer::Time, +} + pub struct GraphicsState{ - pipelines: GraphicsPipelines, - bind_groups: GraphicsBindGroups, - bind_group_layouts: GraphicsBindGroupLayouts, - samplers: GraphicsSamplers, + pipelines:GraphicsPipelines, + bind_groups:GraphicsBindGroups, + bind_group_layouts:GraphicsBindGroupLayouts, + samplers:GraphicsSamplers, camera:GraphicsCamera, - camera_buf: wgpu::Buffer, - temp_squid_texture_view: wgpu::TextureView, - models: Vec, - depth_view: wgpu::TextureView, - staging_belt: wgpu::util::StagingBelt, + camera_buf:wgpu::Buffer, + temp_squid_texture_view:wgpu::TextureView, + models:Vec, + depth_view:wgpu::TextureView, + staging_belt:wgpu::util::StagingBelt, } impl GraphicsState{ - const DEPTH_FORMAT: wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus; + const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus; fn create_depth_texture( - config: &wgpu::SurfaceConfiguration, - device: &wgpu::Device, - ) -> wgpu::TextureView { - let depth_texture=device.create_texture(&wgpu::TextureDescriptor { - size: wgpu::Extent3d { - width: config.width, - height: config.height, - depth_or_array_layers: 1, + config:&wgpu::SurfaceConfiguration, + device:&wgpu::Device, + )->wgpu::TextureView{ + let depth_texture=device.create_texture(&wgpu::TextureDescriptor{ + size:wgpu::Extent3d{ + width:config.width, + height:config.height, + depth_or_array_layers:1, }, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: Self::DEPTH_FORMAT, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - label: None, - view_formats: &[], + mip_level_count:1, + sample_count:1, + dimension:wgpu::TextureDimension::D2, + format:Self::DEPTH_FORMAT, + usage:wgpu::TextureUsages::RENDER_ATTACHMENT, + label:None, + view_formats:&[], }); depth_texture.create_view(&wgpu::TextureViewDescriptor::default()) @@ -145,181 +146,180 @@ impl GraphicsState{ pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){ self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2(); } - pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,indexed_models:crate::model::IndexedModelInstances){ + pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){ //generate texture view per texture - - //idk how to do this gooder lol - let mut double_map=std::collections::HashMap::::new(); - let mut texture_loading_threads=Vec::new(); - let num_textures=indexed_models.textures.len(); - for (i,texture_id) in indexed_models.textures.into_iter().enumerate(){ - let path=std::path::PathBuf::from(format!("textures/{}.dds",texture_id)); - if let Ok(mut file) = std::fs::File::open(path.clone()){ - double_map.insert(i as u32, texture_loading_threads.len() as u32); - texture_loading_threads.push((texture_id,std::thread::spawn(move ||{ - ddsfile::Dds::read(&mut file).unwrap() - }))); - }else{ - //println!("missing texture path={:?}",path); - } - } - - let texture_views:Vec=texture_loading_threads.into_iter().map(|(texture_id,thread)|{ - let image=thread.join().unwrap(); + let texture_views:HashMap=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{ + let texture_id=model::TextureId::new(texture_id as u32); + let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){ + Ok(image)=>image, + Err(e)=>{ + println!("Error loading texture: {e}"); + return None; + }, + }; let (mut width,mut height)=(image.get_width(),image.get_height()); let format=match image.header10.unwrap().dxgi_format{ - ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB => wgpu::TextureFormat::Rgba8UnormSrgb, - ddsfile::DxgiFormat::BC7_UNorm_sRGB => { - //floor(w,4), should be ceil(w,4) + ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB=>wgpu::TextureFormat::Rgba8UnormSrgb, + ddsfile::DxgiFormat::BC7_UNorm_sRGB =>{ + //floor(w,4),should be ceil(w,4) width=width/4*4; height=height/4*4; wgpu::TextureFormat::Bc7RgbaUnormSrgb }, - other=>panic!("unsupported format {:?}",other), + other=>{ + println!("unsupported texture format{:?}",other); + return None; + }, }; - let size = wgpu::Extent3d { + let size=wgpu::Extent3d{ width, height, - depth_or_array_layers: 1, + depth_or_array_layers:1, }; - let layer_size = wgpu::Extent3d { - depth_or_array_layers: 1, + let layer_size=wgpu::Extent3d{ + depth_or_array_layers:1, ..size }; - let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); + let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); - let texture = device.create_texture_with_data( + let texture=device.create_texture_with_data( queue, - &wgpu::TextureDescriptor { + &wgpu::TextureDescriptor{ size, - mip_level_count: max_mips, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, + mip_level_count:max_mips, + sample_count:1, + dimension:wgpu::TextureDimension::D2, format, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some(format!("Texture{}",texture_id).as_str()), - view_formats: &[], + usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, + label:Some(format!("Texture{}",texture_id.get()).as_str()), + view_formats:&[], }, wgpu::util::TextureDataOrder::LayerMajor, &image.data, ); - texture.create_view(&wgpu::TextureViewDescriptor { - label: Some(format!("Texture{} View",texture_id).as_str()), - dimension: Some(wgpu::TextureViewDimension::D2), + Some((texture_id,texture.create_view(&wgpu::TextureViewDescriptor{ + label:Some(format!("Texture{} View",texture_id.get()).as_str()), + dimension:Some(wgpu::TextureViewDimension::D2), ..wgpu::TextureViewDescriptor::default() - }) + }))) }).collect(); + let num_textures=texture_views.len(); //split groups with different textures into separate models - //the models received here are supposed to be tightly packed, i.e. no code needs to check if two models are using the same groups. - let indexed_models_len=indexed_models.models.len(); - let mut unique_texture_models=Vec::with_capacity(indexed_models_len); - for model in indexed_models.models.into_iter(){ - //convert ModelInstance into GraphicsModelInstance - let instances:Vec=model.instances.into_iter().filter_map(|instance|{ - if instance.color.w==0.0&&!model.groups.iter().any(|g|g.texture.is_some()){ - None - }else{ - Some(GraphicsModelInstance{ - transform: instance.transform.into(), - normal_transform: Into::::into(instance.transform.matrix3).inverse().transpose(), - color:GraphicsModelColor4::from(instance.color), - }) + //the models received here are supposed to be tightly packed,i.e. no code needs to check if two models are using the same groups. + let indexed_models_len=map.models.len(); + //models split into graphics_group.RenderConfigId + let mut owned_mesh_id_from_mesh_id_render_config_id:HashMap>=HashMap::new(); + let mut unique_render_config_models:Vec=Vec::with_capacity(indexed_models_len); + for model in &map.models{ + //wow + let instance=GraphicsModelOwned{ + transform:model.transform.into(), + normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(), + color:GraphicsModelColor4::new(model.color), + }; + //get or create owned mesh map + let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id + .entry(model.mesh).or_insert_with(||{ + let mut owned_mesh_map=HashMap::new(); + //add mesh if renderid never before seen for this model + //add instance + //convert Model into GraphicsModelOwned + //check each group, if it's using a new render config then make a new clone of the model + if let Some(mesh)=map.meshes.get(model.mesh.get() as usize){ + for graphics_group in mesh.graphics_groups.iter(){ + //get or create owned mesh + let owned_mesh_id=owned_mesh_map + .entry(graphics_group.render).or_insert_with(||{ + //create + let owned_mesh_id=IndexedGraphicsMeshOwnedRenderConfigId::new(unique_render_config_models.len() as u32); + unique_render_config_models.push(IndexedGraphicsMeshOwnedRenderConfig{ + unique_pos:mesh.unique_pos.iter().map(|v|v.to_array().map(Into::into)).collect(), + unique_tex:mesh.unique_tex.iter().map(|v|*v.as_ref()).collect(), + unique_normal:mesh.unique_normal.iter().map(|v|v.to_array().map(Into::into)).collect(), + unique_color:mesh.unique_color.iter().map(|v|*v.as_ref()).collect(), + unique_vertices:mesh.unique_vertices.clone(), + render_config:graphics_group.render, + polys:model::PolygonGroup::PolygonList(model::PolygonList::new(Vec::new())), + instances:Vec::new(), + }); + owned_mesh_id + }); + let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap(); + match &mut owned_mesh.polys{ + model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend( + graphics_group.groups.iter().flat_map(|polygon_group_id|{ + mesh.polygon_groups[polygon_group_id.get() as usize].polys() + }) + .map(|vertex_id_slice| + vertex_id_slice.to_vec() + ) + ), + } + } } - }).collect(); - //skip pushing a model if all instances are invisible - if instances.len()==0{ - continue; - } - //check each group, if it's using a new texture then make a new clone of the model - let id=unique_texture_models.len(); - let mut unique_textures=Vec::new(); - for group in model.groups.into_iter(){ - //ignore zero copy optimization for now - let texture_index=if let Some(texture_index)=unique_textures.iter().position(|&texture|texture==group.texture){ - texture_index - }else{ - //create new texture_index - let texture_index=unique_textures.len(); - unique_textures.push(group.texture); - unique_texture_models.push(IndexedGraphicsModelSingleTexture{ - unique_pos:model.unique_pos.iter().map(|&v|*Into::::into(v).as_ref()).collect(), - unique_tex:model.unique_tex.iter().map(|v|*v.as_ref()).collect(), - unique_normal:model.unique_normal.iter().map(|&v|*Into::::into(v).as_ref()).collect(), - unique_color:model.unique_color.iter().map(|v|*v.as_ref()).collect(), - unique_vertices:model.unique_vertices.clone(), - texture:group.texture, - groups:Vec::new(), - instances:instances.clone(), - }); - texture_index - }; - unique_texture_models[id+texture_index].groups.push(IndexedGroupFixedTexture{ - polys:group.polys, - }); + owned_mesh_map + }); + for owned_mesh_id in owned_mesh_map.values(){ + let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap(); + let render_config=&map.render_configs[owned_mesh.render_config.get() as usize]; + if model.color.w==0.0&&render_config.texture.is_none(){ + continue; + } + owned_mesh.instances.push(instance.clone()); } } - //check every model to see if it's using the same (texture,color) but has few instances, if it is combine it into one model - //1. collect unique instances of texture and color, note model id - //2. for each model id, check if removing it from the pool decreases both the model count and instance count by more than one + //check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model + //1. collect unique instances of texture and color,note model id + //2. for each model id,check if removing it from the pool decreases both the model count and instance count by more than one //3. transpose all models that stay in the set - //best plan: benchmark set_bind_group, set_vertex_buffer, set_index_buffer and draw_indexed + //best plan:benchmark set_bind_group,set_vertex_buffer,set_index_buffer and draw_indexed //check if the estimated render performance is better by transposing multiple model instances into one model instance - //for now: just deduplicate single models... + //for now:just deduplicate single models... let mut deduplicated_models=Vec::with_capacity(indexed_models_len);//use indexed_models_len because the list will likely get smaller instead of bigger - let mut unique_texture_color=std::collections::HashMap::new();//texture->color->vec![(model_id,instance_id)] - for (model_id,model) in unique_texture_models.iter().enumerate(){ - //for now: filter out models with more than one instance + let mut unique_texture_color=HashMap::new();//texture->color->vec![(model_id,instance_id)] + for (model_id,model) in unique_render_config_models.iter().enumerate(){ + //for now:filter out models with more than one instance if 1=model.unique_pos.iter().map(|untransformed_pos|{ + let map_pos_id:Vec=model.unique_pos.iter().map(|untransformed_pos|{ let pos=instance.transform.transform_point3(glam::Vec3::from_array(untransformed_pos.clone())).to_array(); - let h=pos.map(|v|bytemuck::cast::(v)); - (if let Some(&pos_id)=pos_id_from.get(&h){ - pos_id - }else{ + let h=bytemuck::cast::<[f32;3],[u32;3]>(pos); + PositionId::new(*pos_id_from.entry(h).or_insert_with(||{ let pos_id=unique_pos.len(); - unique_pos.push(pos.clone()); - pos_id_from.insert(h,pos_id); + unique_pos.push(pos); pos_id - }) as u32 + }) as u32) }).collect(); - let map_tex_id:Vec=model.unique_tex.iter().map(|tex|{ - let h=tex.map(|v|bytemuck::cast::(v)); - (if let Some(&tex_id)=tex_id_from.get(&h){ - tex_id - }else{ + let map_tex_id:Vec=model.unique_tex.iter().map(|&tex|{ + let h=bytemuck::cast::<[f32;2],[u32;2]>(tex); + TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{ let tex_id=unique_tex.len(); - unique_tex.push(tex.clone()); - tex_id_from.insert(h,tex_id); + unique_tex.push(tex); tex_id - }) as u32 + }) as u32) }).collect(); - let map_normal_id:Vec=model.unique_normal.iter().map(|untransformed_normal|{ + let map_normal_id:Vec=model.unique_normal.iter().map(|untransformed_normal|{ let normal=(instance.normal_transform*glam::Vec3::from_array(untransformed_normal.clone())).to_array(); - let h=normal.map(|v|bytemuck::cast::(v)); - (if let Some(&normal_id)=normal_id_from.get(&h){ - normal_id - }else{ + let h=bytemuck::cast::<[f32;3],[u32;3]>(normal); + NormalId::new(*normal_id_from.entry(h).or_insert_with(||{ let normal_id=unique_normal.len(); - unique_normal.push(normal.clone()); - normal_id_from.insert(h,normal_id); + unique_normal.push(normal); normal_id - }) as u32 + }) as u32) }).collect(); - let map_color_id:Vec=model.unique_color.iter().map(|color|{ - let h=color.map(|v|bytemuck::cast::(v)); - (if let Some(&color_id)=color_id_from.get(&h){ - color_id - }else{ + let map_color_id:Vec=model.unique_color.iter().map(|&color|{ + let h=bytemuck::cast::<[f32;4],[u32;4]>(color); + ColorId::new(*color_id_from.entry(h).or_insert_with(||{ let color_id=unique_color.len(); - unique_color.push(color.clone()); - color_id_from.insert(h,color_id); + unique_color.push(color); color_id - }) as u32 + }) as u32) }).collect(); //map the indexed vertices onto new indices //creating the vertex map is slightly different because the vertices are directly hashable - let map_vertex_id:Vec=model.unique_vertices.iter().map(|unmapped_vertex|{ - let vertex=crate::model::IndexedVertex{ - pos:map_pos_id[unmapped_vertex.pos as usize], - tex:map_tex_id[unmapped_vertex.tex as usize], - normal:map_normal_id[unmapped_vertex.normal as usize], - color:map_color_id[unmapped_vertex.color as usize], + let map_vertex_id:Vec=model.unique_vertices.iter().map(|unmapped_vertex|{ + let vertex=model::IndexedVertex{ + pos:map_pos_id[unmapped_vertex.pos.get() as usize], + tex:map_tex_id[unmapped_vertex.tex.get() as usize], + normal:map_normal_id[unmapped_vertex.normal.get() as usize], + color:map_color_id[unmapped_vertex.color.get() as usize], }; - (if let Some(&vertex_id)=vertex_id_from.get(&vertex){ - vertex_id - }else{ + VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{ let vertex_id=unique_vertices.len(); - unique_vertices.push(vertex.clone()); - vertex_id_from.insert(vertex,vertex_id); + unique_vertices.push(vertex); vertex_id - }) as u32 + }) as u32) }).collect(); - for group in &model.groups{ - for poly in &group.polys{ - polys.push(crate::model::IndexedPolygon{vertices:poly.vertices.iter().map(|&vertex_id|map_vertex_id[vertex_id as usize]).collect()}); - } - } + polys.extend(model.polys.polys().map(|poly| + poly.iter().map(|vertex_id| + map_vertex_id[vertex_id.get() as usize] + ).collect() + )); } //push model into dedup - deduplicated_models.push(IndexedGraphicsModelSingleTexture{ + deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{ unique_pos, unique_tex, unique_normal, unique_color, unique_vertices, - texture, - groups:vec![IndexedGroupFixedTexture{ - polys - }], - instances:vec![GraphicsModelInstance{ + render_config, + polys:model::PolygonGroup::PolygonList(model::PolygonList::new(polys)), + instances:vec![GraphicsModelOwned{ transform:glam::Mat4::IDENTITY, normal_transform:glam::Mat3::IDENTITY, color @@ -421,7 +404,7 @@ impl GraphicsState{ } } //fill untouched models - for (model_id,model) in unique_texture_models.into_iter().enumerate(){ + for (model_id,model) in unique_render_config_models.into_iter().enumerate(){ if !selected_model_instances.contains(&model_id){ deduplicated_models.push(model); } @@ -429,45 +412,44 @@ impl GraphicsState{ //de-index models let deduplicated_models_len=deduplicated_models.len(); - let models:Vec=deduplicated_models.into_iter().map(|model|{ - let mut vertices = Vec::new(); - let mut index_from_vertex = std::collections::HashMap::new();//:: + let models:Vec=deduplicated_models.into_iter().map(|model|{ + let mut vertices=Vec::new(); + let mut index_from_vertex=HashMap::new();//:: //this mut be combined in a more complex way if the models use different render patterns per group - let mut indices = Vec::new(); - for group in model.groups { - for poly in group.polys { - for end_index in 2..poly.vertices.len() { - for &index in &[0, end_index - 1, end_index] { - let vertex_index = poly.vertices[index]; - if let Some(&i)=index_from_vertex.get(&vertex_index){ - indices.push(i); - }else{ - let i=vertices.len(); - let vertex=&model.unique_vertices[vertex_index as usize]; - vertices.push(GraphicsVertex{ - pos: model.unique_pos[vertex.pos as usize], - tex: model.unique_tex[vertex.tex as usize], - normal: model.unique_normal[vertex.normal as usize], - color:model.unique_color[vertex.color as usize], - }); - index_from_vertex.insert(vertex_index,i); - indices.push(i); - } - } - } - } + let mut indices=Vec::new(); + for poly in model.polys.polys(){ + let mut poly_vertices=poly.iter() + .map(|&vertex_index|*index_from_vertex.entry(vertex_index).or_insert_with(||{ + let i=vertices.len(); + let vertex=&model.unique_vertices[vertex_index.get() as usize]; + vertices.push(GraphicsVertex{ + pos:model.unique_pos[vertex.pos.get() as usize], + tex:model.unique_tex[vertex.tex.get() as usize], + normal:model.unique_normal[vertex.normal.get() as usize], + color:model.unique_color[vertex.color.get() as usize], + }); + i + })); + + let a=poly_vertices.next().unwrap(); + let mut b=poly_vertices.next().unwrap(); + + poly_vertices.for_each(|c|{ + indices.extend([a,b,c]); + b=c; + }); } - GraphicsModelSingleTexture{ + GraphicsMeshOwnedRenderConfig{ instances:model.instances, - entities:if (u32::MAX as usize){ - match double_map.get(&texture_id){ - Some(&mapped_texture_id)=>&texture_views[mapped_texture_id as usize], - None=>&self.temp_squid_texture_view, - } - }, - None=>&self.temp_squid_texture_view, - }; - let model_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.bind_group_layouts.model, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: model_buf.as_entire_binding(), + let render_config=&map.render_configs[model.render_config.get() as usize]; + let texture_view=render_config.texture.and_then(|texture_id| + texture_views.get(&texture_id) + ).unwrap_or(&self.temp_squid_texture_view); + let bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ + layout:&self.bind_group_layouts.model, + entries:&[ + wgpu::BindGroupEntry{ + binding:0, + resource:model_buf.as_entire_binding(), }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::TextureView(texture_view), + wgpu::BindGroupEntry{ + binding:1, + resource:wgpu::BindingResource::TextureView(texture_view), }, - wgpu::BindGroupEntry { - binding: 2, - resource: wgpu::BindingResource::Sampler(&self.samplers.repeat), + wgpu::BindGroupEntry{ + binding:2, + resource:wgpu::BindingResource::Sampler(&self.samplers.repeat), }, ], - label: Some(format!("Model{} Bind Group",model_count).as_str()), + label:Some(format!("Model{} Bind Group",model_count).as_str()), }); - let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex"), - contents: bytemuck::cast_slice(&model.vertices), - usage: wgpu::BufferUsages::VERTEX, + let vertex_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ + label:Some("Vertex"), + contents:bytemuck::cast_slice(&model.vertices), + usage:wgpu::BufferUsages::VERTEX, }); //all of these are being moved here self.models.push(GraphicsModel{ - instances:instances_chunk.to_vec(), + instance_count:instances_chunk.len() as u32, vertex_buf, - index_format:match &model.entities{ - crate::model_graphics::Entities::U32(_)=>wgpu::IndexFormat::Uint32, - crate::model_graphics::Entities::U16(_)=>wgpu::IndexFormat::Uint16, + indices:match &model.indices{ + model_graphics::Indices::U32(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint32), + model_graphics::Indices::U16(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint16), }, - entities:match &model.entities{ - crate::model_graphics::Entities::U32(entities)=>create_entities(device,entities), - crate::model_graphics::Entities::U16(entities)=>create_entities(device,entities), - }, - bind_group: model_bind_group, - model_buf, + bind_group, }); } } @@ -539,8 +513,8 @@ impl GraphicsState{ println!("Textures Loaded={}",texture_views.len()); println!("Indexed Models={}",indexed_models_len); println!("Deduplicated Models={}",deduplicated_models_len); - println!("Graphics Objects: {}",self.models.len()); - println!("Graphics Instances: {}",instance_count); + println!("Graphics Objects:{}",self.models.len()); + println!("Graphics Instances:{}",instance_count); } pub fn new( @@ -548,325 +522,331 @@ impl GraphicsState{ queue:&wgpu::Queue, config:&wgpu::SurfaceConfiguration, )->Self{ - let camera_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, + let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ + label:None, + entries:&[ + wgpu::BindGroupLayoutEntry{ + binding:0, + visibility:wgpu::ShaderStages::VERTEX, + ty:wgpu::BindingType::Buffer{ + ty:wgpu::BufferBindingType::Uniform, + has_dynamic_offset:false, + min_binding_size:None, }, - count: None, + count:None, }, ], }); - let skybox_texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Skybox Texture Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - multisampled: false, - view_dimension: wgpu::TextureViewDimension::Cube, + let skybox_texture_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ + label:Some("Skybox Texture Bind Group Layout"), + entries:&[ + wgpu::BindGroupLayoutEntry{ + binding:0, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Texture{ + sample_type:wgpu::TextureSampleType::Float{filterable:true}, + multisampled:false, + view_dimension:wgpu::TextureViewDimension::Cube, }, - count: None, + count:None, }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, + wgpu::BindGroupLayoutEntry{ + binding:1, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count:None, }, ], }); - let model_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Model Bind Group Layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, + let model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{ + label:Some("Model Bind Group Layout"), + entries:&[ + wgpu::BindGroupLayoutEntry{ + binding:0, + visibility:wgpu::ShaderStages::VERTEX, + ty:wgpu::BindingType::Buffer{ + ty:wgpu::BufferBindingType::Uniform, + has_dynamic_offset:false, + min_binding_size:None, }, - count: None, + count:None, }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - multisampled: false, - view_dimension: wgpu::TextureViewDimension::D2, + wgpu::BindGroupLayoutEntry{ + binding:1, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Texture{ + sample_type:wgpu::TextureSampleType::Float{filterable:true}, + multisampled:false, + view_dimension:wgpu::TextureViewDimension::D2, }, - count: None, + count:None, }, - wgpu::BindGroupLayoutEntry { - binding: 2, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), - count: None, + wgpu::BindGroupLayoutEntry{ + binding:2, + visibility:wgpu::ShaderStages::FRAGMENT, + ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + count:None, }, ], }); - let clamp_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("Clamp Sampler"), - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, + let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ + label:Some("Clamp Sampler"), + address_mode_u:wgpu::AddressMode::ClampToEdge, + address_mode_v:wgpu::AddressMode::ClampToEdge, + address_mode_w:wgpu::AddressMode::ClampToEdge, + mag_filter:wgpu::FilterMode::Linear, + min_filter:wgpu::FilterMode::Linear, + mipmap_filter:wgpu::FilterMode::Linear, ..Default::default() }); - let repeat_sampler = device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("Repeat Sampler"), - address_mode_u: wgpu::AddressMode::Repeat, - address_mode_v: wgpu::AddressMode::Repeat, - address_mode_w: wgpu::AddressMode::Repeat, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - mipmap_filter: wgpu::FilterMode::Linear, + let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{ + label:Some("Repeat Sampler"), + address_mode_u:wgpu::AddressMode::Repeat, + address_mode_v:wgpu::AddressMode::Repeat, + address_mode_w:wgpu::AddressMode::Repeat, + mag_filter:wgpu::FilterMode::Linear, + min_filter:wgpu::FilterMode::Linear, + mipmap_filter:wgpu::FilterMode::Linear, anisotropy_clamp:16, ..Default::default() }); // Create the render pipeline - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: None, - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), + let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{ + label:None, + source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))), }); //load textures - let device_features = device.features(); + let device_features=device.features(); let skybox_texture_view={ - let skybox_format = if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC) { + let skybox_format=if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC){ println!("Using ASTC"); - wgpu::TextureFormat::Astc { - block: AstcBlock::B4x4, - channel: AstcChannel::UnormSrgb, + wgpu::TextureFormat::Astc{ + block:AstcBlock::B4x4, + channel:AstcChannel::UnormSrgb, } - } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2) { + }else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2){ println!("Using ETC2"); wgpu::TextureFormat::Etc2Rgb8UnormSrgb - } else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC) { + }else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC){ println!("Using BC"); wgpu::TextureFormat::Bc1RgbaUnormSrgb - } else { + }else{ println!("Using plain"); wgpu::TextureFormat::Bgra8UnormSrgb }; - let bytes = match skybox_format { - wgpu::TextureFormat::Astc { - block: AstcBlock::B4x4, - channel: AstcChannel::UnormSrgb, - } => &include_bytes!("../images/astc.dds")[..], - wgpu::TextureFormat::Etc2Rgb8UnormSrgb => &include_bytes!("../images/etc2.dds")[..], - wgpu::TextureFormat::Bc1RgbaUnormSrgb => &include_bytes!("../images/bc1.dds")[..], - wgpu::TextureFormat::Bgra8UnormSrgb => &include_bytes!("../images/bgra.dds")[..], - _ => unreachable!(), + let bytes=match skybox_format{ + wgpu::TextureFormat::Astc{ + block:AstcBlock::B4x4, + channel:AstcChannel::UnormSrgb, + }=>&include_bytes!("../images/astc.dds")[..], + wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../images/etc2.dds")[..], + wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../images/bc1.dds")[..], + wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../images/bgra.dds")[..], + _=>unreachable!(), }; - let skybox_image = ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); + let skybox_image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); - let size = wgpu::Extent3d { - width: skybox_image.get_width(), - height: skybox_image.get_height(), - depth_or_array_layers: 6, + let size=wgpu::Extent3d{ + width:skybox_image.get_width(), + height:skybox_image.get_height(), + depth_or_array_layers:6, }; - let layer_size = wgpu::Extent3d { - depth_or_array_layers: 1, + let layer_size=wgpu::Extent3d{ + depth_or_array_layers:1, ..size }; - let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); + let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); - let skybox_texture = device.create_texture_with_data( + let skybox_texture=device.create_texture_with_data( queue, - &wgpu::TextureDescriptor { + &wgpu::TextureDescriptor{ size, - mip_level_count: max_mips, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: skybox_format, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some("Skybox Texture"), - view_formats: &[], + mip_level_count:max_mips, + sample_count:1, + dimension:wgpu::TextureDimension::D2, + format:skybox_format, + usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, + label:Some("Skybox Texture"), + view_formats:&[], }, wgpu::util::TextureDataOrder::LayerMajor, &skybox_image.data, ); - skybox_texture.create_view(&wgpu::TextureViewDescriptor { - label: Some("Skybox Texture View"), - dimension: Some(wgpu::TextureViewDimension::Cube), + skybox_texture.create_view(&wgpu::TextureViewDescriptor{ + label:Some("Skybox Texture View"), + dimension:Some(wgpu::TextureViewDimension::Cube), ..wgpu::TextureViewDescriptor::default() }) }; //squid let squid_texture_view={ - let bytes = include_bytes!("../images/squid.dds"); + let bytes=include_bytes!("../images/squid.dds"); - let image = ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); + let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap(); - let size = wgpu::Extent3d { - width: image.get_width(), - height: image.get_height(), - depth_or_array_layers: 1, + let size=wgpu::Extent3d{ + width:image.get_width(), + height:image.get_height(), + depth_or_array_layers:1, }; - let layer_size = wgpu::Extent3d { - depth_or_array_layers: 1, + let layer_size=wgpu::Extent3d{ + depth_or_array_layers:1, ..size }; - let max_mips = layer_size.max_mips(wgpu::TextureDimension::D2); + let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2); - let texture = device.create_texture_with_data( + let texture=device.create_texture_with_data( queue, - &wgpu::TextureDescriptor { + &wgpu::TextureDescriptor{ size, - mip_level_count: max_mips, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bc7RgbaUnorm, - usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, - label: Some("Squid Texture"), - view_formats: &[], + mip_level_count:max_mips, + sample_count:1, + dimension:wgpu::TextureDimension::D2, + format:wgpu::TextureFormat::Bc7RgbaUnorm, + usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST, + label:Some("Squid Texture"), + view_formats:&[], }, wgpu::util::TextureDataOrder::LayerMajor, &image.data, ); - texture.create_view(&wgpu::TextureViewDescriptor { - label: Some("Squid Texture View"), - dimension: Some(wgpu::TextureViewDimension::D2), + texture.create_view(&wgpu::TextureViewDescriptor{ + label:Some("Squid Texture View"), + dimension:Some(wgpu::TextureViewDimension::D2), ..wgpu::TextureViewDescriptor::default() }) }; - let model_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[ + let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ + label:None, + bind_group_layouts:&[ &camera_bind_group_layout, &skybox_texture_bind_group_layout, &model_bind_group_layout, ], - push_constant_ranges: &[], + push_constant_ranges:&[], }); - let sky_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: None, - bind_group_layouts: &[ + let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{ + label:None, + bind_group_layouts:&[ &camera_bind_group_layout, &skybox_texture_bind_group_layout, ], - push_constant_ranges: &[], + push_constant_ranges:&[], }); // Create the render pipelines - let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Sky Pipeline"), - layout: Some(&sky_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_sky", - buffers: &[], + let sky_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ + label:Some("Sky Pipeline"), + layout:Some(&sky_pipeline_layout), + vertex:wgpu::VertexState{ + module:&shader, + entry_point:"vs_sky", + buffers:&[], + compilation_options:wgpu::PipelineCompilationOptions::default(), }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_sky", - targets: &[Some(config.view_formats[0].into())], + fragment:Some(wgpu::FragmentState{ + module:&shader, + entry_point:"fs_sky", + targets:&[Some(config.view_formats[0].into())], + compilation_options:wgpu::PipelineCompilationOptions::default(), }), - primitive: wgpu::PrimitiveState { - front_face: wgpu::FrontFace::Cw, + primitive:wgpu::PrimitiveState{ + front_face:wgpu::FrontFace::Cw, ..Default::default() }, - depth_stencil: Some(wgpu::DepthStencilState { - format: Self::DEPTH_FORMAT, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::LessEqual, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), + depth_stencil:Some(wgpu::DepthStencilState{ + format:Self::DEPTH_FORMAT, + depth_write_enabled:false, + depth_compare:wgpu::CompareFunction::LessEqual, + stencil:wgpu::StencilState::default(), + bias:wgpu::DepthBiasState::default(), }), - multisample: wgpu::MultisampleState::default(), - multiview: None, + multisample:wgpu::MultisampleState::default(), + multiview:None, + cache:None, }); - let model_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Model Pipeline"), - layout: Some(&model_pipeline_layout), - vertex: wgpu::VertexState { - module: &shader, - entry_point: "vs_entity_texture", - buffers: &[wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as wgpu::BufferAddress, - step_mode: wgpu::VertexStepMode::Vertex, - attributes: &wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2, 2 => Float32x3, 3 => Float32x4], + let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{ + label:Some("Model Pipeline"), + layout:Some(&model_pipeline_layout), + vertex:wgpu::VertexState{ + module:&shader, + entry_point:"vs_entity_texture", + buffers:&[wgpu::VertexBufferLayout{ + array_stride:std::mem::size_of::() as wgpu::BufferAddress, + step_mode:wgpu::VertexStepMode::Vertex, + attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4], }], + compilation_options:wgpu::PipelineCompilationOptions::default(), }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: "fs_entity_texture", - targets: &[Some(config.view_formats[0].into())], + fragment:Some(wgpu::FragmentState{ + module:&shader, + entry_point:"fs_entity_texture", + targets:&[Some(config.view_formats[0].into())], + compilation_options:wgpu::PipelineCompilationOptions::default(), }), - primitive: wgpu::PrimitiveState { - front_face: wgpu::FrontFace::Cw, + primitive:wgpu::PrimitiveState{ + front_face:wgpu::FrontFace::Cw, cull_mode:Some(wgpu::Face::Front), ..Default::default() }, - depth_stencil: Some(wgpu::DepthStencilState { - format: Self::DEPTH_FORMAT, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::LessEqual, - stencil: wgpu::StencilState::default(), - bias: wgpu::DepthBiasState::default(), + depth_stencil:Some(wgpu::DepthStencilState{ + format:Self::DEPTH_FORMAT, + depth_write_enabled:true, + depth_compare:wgpu::CompareFunction::LessEqual, + stencil:wgpu::StencilState::default(), + bias:wgpu::DepthBiasState::default(), }), - multisample: wgpu::MultisampleState::default(), - multiview: None, + multisample:wgpu::MultisampleState::default(), + multiview:None, + cache:None, }); let camera=GraphicsCamera::default(); - let camera_uniforms = camera.to_uniform_data(crate::physics::PhysicsOutputState::default().extrapolate(glam::IVec2::ZERO,integer::Time::ZERO)); - let camera_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Camera"), - contents: bytemuck::cast_slice(&camera_uniforms), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO); + let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{ + label:Some("Camera"), + contents:bytemuck::cast_slice(&camera_uniforms), + usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST, }); - let camera_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &camera_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: camera_buf.as_entire_binding(), + let camera_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ + layout:&camera_bind_group_layout, + entries:&[ + wgpu::BindGroupEntry{ + binding:0, + resource:camera_buf.as_entire_binding(), }, ], - label: Some("Camera"), + label:Some("Camera"), }); - let skybox_texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &skybox_texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&skybox_texture_view), + let skybox_texture_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{ + layout:&skybox_texture_bind_group_layout, + entries:&[ + wgpu::BindGroupEntry{ + binding:0, + resource:wgpu::BindingResource::TextureView(&skybox_texture_view), }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&clamp_sampler), + wgpu::BindGroupEntry{ + binding:1, + resource:wgpu::BindingResource::Sampler(&clamp_sampler), }, ], - label: Some("Sky Texture"), + label:Some("Sky Texture"), }); - let depth_view = Self::create_depth_texture(config, device); + let depth_view=Self::create_depth_texture(config,device); Self{ pipelines:GraphicsPipelines{ @@ -879,12 +859,12 @@ impl GraphicsState{ }, camera, camera_buf, - models: Vec::new(), + models:Vec::new(), depth_view, - staging_belt: wgpu::util::StagingBelt::new(0x100), - bind_group_layouts: GraphicsBindGroupLayouts { model: model_bind_group_layout }, - samplers: GraphicsSamplers { repeat: repeat_sampler }, - temp_squid_texture_view: squid_texture_view, + staging_belt:wgpu::util::StagingBelt::new(0x100), + bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout}, + samplers:GraphicsSamplers{repeat:repeat_sampler}, + temp_squid_texture_view:squid_texture_view, } } pub fn resize( @@ -892,7 +872,7 @@ impl GraphicsState{ device:&wgpu::Device, config:&wgpu::SurfaceConfiguration, user_settings:&crate::settings::UserSettings, - ) { + ){ self.depth_view=Self::create_depth_texture(config,device); self.camera.screen_size=glam::uvec2(config.width,config.height); self.load_user_settings(user_settings); @@ -902,17 +882,17 @@ impl GraphicsState{ view:&wgpu::TextureView, device:&wgpu::Device, queue:&wgpu::Queue, - physics_output:crate::physics::PhysicsOutputState, - predicted_time:integer::Time, - mouse_pos:glam::IVec2, - ) { - //TODO: use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input + frame_state:FrameState, + ){ + //TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input - let mut encoder = - device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None}); // update rotation - let camera_uniforms = self.camera.to_uniform_data(physics_output.extrapolate(mouse_pos,predicted_time)); + let camera_uniforms=self.camera.to_uniform_data( + frame_state.body.extrapolated_position(frame_state.time).map(Into::::into).to_array().into(), + frame_state.camera.simulate_move_angles(glam::IVec2::ZERO) + ); self.staging_belt .write_buffer( &mut encoder, @@ -924,8 +904,8 @@ impl GraphicsState{ .copy_from_slice(bytemuck::cast_slice(&camera_uniforms)); //This code only needs to run when the uniforms change /* - for model in self.models.iter() { - let model_uniforms = get_instances_buffer_data(&model.instances); + for model in self.models.iter(){ + let model_uniforms=get_instances_buffer_data(&model.instances); self.staging_belt .write_buffer( &mut encoder, @@ -940,49 +920,47 @@ impl GraphicsState{ self.staging_belt.finish(); { - let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[Some(wgpu::RenderPassColorAttachment { + let mut rpass=encoder.begin_render_pass(&wgpu::RenderPassDescriptor{ + label:None, + color_attachments:&[Some(wgpu::RenderPassColorAttachment{ 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, + 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:wgpu::StoreOp::Store, }, })], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), + depth_stencil_attachment:Some(wgpu::RenderPassDepthStencilAttachment{ + view:&self.depth_view, + depth_ops:Some(wgpu::Operations{ + load:wgpu::LoadOp::Clear(1.0), store:wgpu::StoreOp::Discard, }), - stencil_ops: None, + stencil_ops:None, }), timestamp_writes:Default::default(), occlusion_query_set:Default::default(), }); - rpass.set_bind_group(0, &self.bind_groups.camera, &[]); - rpass.set_bind_group(1, &self.bind_groups.skybox_texture, &[]); + rpass.set_bind_group(0,&self.bind_groups.camera,&[]); + rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]); rpass.set_pipeline(&self.pipelines.model); - for model in self.models.iter() { - rpass.set_bind_group(2, &model.bind_group, &[]); - rpass.set_vertex_buffer(0, model.vertex_buf.slice(..)); - - for entity in model.entities.iter(){ - rpass.set_index_buffer(entity.index_buf.slice(..),model.index_format); - rpass.draw_indexed(0..entity.index_count,0,0..model.instances.len() as u32); - } + for model in &self.models{ + rpass.set_bind_group(2,&model.bind_group,&[]); + rpass.set_vertex_buffer(0,model.vertex_buf.slice(..)); + rpass.set_index_buffer(model.indices.buf.slice(..),model.indices.format); + //TODO: loop over triangle strips + rpass.draw_indexed(0..model.indices.count,0,0..model.instance_count); } rpass.set_pipeline(&self.pipelines.skybox); - rpass.draw(0..3, 0..1); + rpass.draw(0..3,0..1); } queue.submit(std::iter::once(encoder.finish())); @@ -992,10 +970,9 @@ impl GraphicsState{ } const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::(); const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4; -fn get_instances_buffer_data(instances:&[GraphicsModelInstance]) -> Vec { - let mut raw = Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len()); - for (i,mi) in instances.iter().enumerate(){ - let mut v = raw.split_off(MODEL_BUFFER_SIZE*i); +fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec{ + let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len()); + for mi in instances{ //model transform raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]); //normal transform @@ -1007,7 +984,6 @@ fn get_instances_buffer_data(instances:&[GraphicsModelInstance]) -> Vec { raw.extend_from_slice(&[0.0]); //color raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get())); - raw.append(&mut v); } raw } diff --git a/src/graphics_worker.rs b/src/graphics_worker.rs index 378269c..9950785 100644 --- a/src/graphics_worker.rs +++ b/src/graphics_worker.rs @@ -1,11 +1,8 @@ -use strafesnet_common::integer; - pub enum Instruction{ - Render(crate::physics::PhysicsOutputState,integer::Time,glam::IVec2), + Render(crate::graphics::FrameState), //UpdateModel(crate::graphics::GraphicsModelUpdate), Resize(winit::dpi::PhysicalSize,crate::settings::UserSettings), - GenerateModels(crate::model::IndexedModelInstances), - ClearModels, + ChangeMap(strafesnet_common::map::CompleteMap), } //Ideally the graphics thread worker description is: @@ -18,36 +15,32 @@ WorkerDescription{ //up to three frames in flight, dropping new frame requests when all three are busy, and dropping output frames when one renders out of order pub fn new<'a>( - mut graphics:crate::graphics::GraphicsState, - mut config:wgpu::SurfaceConfiguration, - surface:wgpu::Surface<'a>, - device:wgpu::Device, - queue:wgpu::Queue, - )->crate::compat_worker::INWorker<'a,Instruction>{ + mut graphics:crate::graphics::GraphicsState, + mut config:wgpu::SurfaceConfiguration, + surface:wgpu::Surface<'a>, + device:wgpu::Device, + queue:wgpu::Queue, +)->crate::compat_worker::INWorker<'a,Instruction>{ let mut resize=None; crate::compat_worker::INWorker::new(move |ins:Instruction|{ match ins{ - Instruction::GenerateModels(indexed_model_instances)=>{ - graphics.generate_models(&device,&queue,indexed_model_instances); - }, - Instruction::ClearModels=>{ + Instruction::ChangeMap(map)=>{ graphics.clear(); + graphics.generate_models(&device,&queue,&map); }, Instruction::Resize(size,user_settings)=>{ resize=Some((size,user_settings)); } - Instruction::Render(physics_output,predicted_time,mouse_pos)=>{ - if let Some((size,user_settings))=&resize{ + Instruction::Render(frame_state)=>{ + if let Some((size,user_settings))=resize.take(){ println!("Resizing to {:?}",size); let t0=std::time::Instant::now(); config.width=size.width.max(1); config.height=size.height.max(1); surface.configure(&device,&config); - graphics.resize(&device,&config,user_settings); + graphics.resize(&device,&config,&user_settings); println!("Resize took {:?}",t0.elapsed()); } - //clear every time w/e - resize=None; //this has to go deeper somehow let frame=match surface.get_current_texture(){ Ok(frame)=>frame, @@ -63,10 +56,10 @@ pub fn new<'a>( ..wgpu::TextureViewDescriptor::default() }); - graphics.render(&view,&device,&queue,physics_output,predicted_time,mouse_pos); + graphics.render(&view,&device,&queue,frame_state); frame.present(); } } }) -} \ No newline at end of file +} diff --git a/src/load_bsp.rs b/src/load_bsp.rs deleted file mode 100644 index 840886c..0000000 --- a/src/load_bsp.rs +++ /dev/null @@ -1,232 +0,0 @@ -use strafesnet_common::integer; - -const VALVE_SCALE:f32=1.0/16.0; -fn valve_transform(v:[f32;3])->integer::Planar64Vec3{ - integer::Planar64Vec3::try_from([v[0]*VALVE_SCALE,v[2]*VALVE_SCALE,-v[1]*VALVE_SCALE]).unwrap() -} -pub fn generate_indexed_models(input:&mut R)->Result{ - let mut s=Vec::new(); - - match input.read_to_end(&mut s){ - Ok(_)=>(), - Err(e)=>println!("load_bsp::generate_indexed_models read_to_end failed: {:?}",e), - } - - match vbsp::Bsp::read(s.as_slice()){ - Ok(bsp)=>{ - let mut spawn_point=integer::Planar64Vec3::ZERO; - - let mut name_from_texture_id=Vec::new(); - let mut texture_id_from_name=std::collections::HashMap::new(); - - let mut models=bsp.models().map(|world_model|{ - //non-deduplicated - let mut spam_pos=Vec::new(); - let mut spam_tex=Vec::new(); - let mut spam_normal=Vec::new(); - let mut spam_vertices=Vec::new(); - let groups=world_model.faces() - .filter(|face| face.is_visible())//TODO: look at this - .map(|face|{ - let face_texture=face.texture(); - let face_texture_data=face_texture.texture_data(); - let (texture_u,texture_v)=(glam::Vec3A::from_slice(&face_texture.texture_transforms_u[0..3]),glam::Vec3A::from_slice(&face_texture.texture_transforms_v[0..3])); - let texture_offset=glam::vec2(face_texture.texture_transforms_u[3],face_texture.texture_transforms_v[3]); - let texture_size=glam::vec2(face_texture_data.width as f32,face_texture_data.height as f32); - - //texture - let texture_id=if let Some(&texture_id)=texture_id_from_name.get(face_texture_data.name()){ - texture_id - }else{ - let texture_id=name_from_texture_id.len() as u32; - texture_id_from_name.insert(face_texture_data.name().to_string(),texture_id); - name_from_texture_id.push(face_texture_data.name().to_string()); - texture_id - }; - - //normal - let normal=face.normal(); - let normal_idx=spam_normal.len() as u32; - spam_normal.push(valve_transform(<[f32;3]>::from(normal))); - let mut vertices:Vec=face.vertex_positions().map(|vertex_pos|{ - let vertex_xyz=<[f32;3]>::from(vertex_pos); - let pos=glam::Vec3A::from_array(vertex_xyz); - let pos_idx=spam_pos.len(); - spam_pos.push(valve_transform(vertex_xyz)); - - //calculate texture coordinates - let tex=(glam::vec2(pos.dot(texture_u),pos.dot(texture_v))+texture_offset)/texture_size; - let tex_idx=spam_tex.len() as u32; - spam_tex.push(tex); - - let i=spam_vertices.len() as u32; - spam_vertices.push(crate::model::IndexedVertex{ - pos: pos_idx as u32, - tex: tex_idx as u32, - normal: normal_idx, - color: 0, - }); - i - }).collect(); - vertices.reverse(); - crate::model::IndexedGroup{ - texture:Some(texture_id), - polys:vec![crate::model::IndexedPolygon{vertices}], - } - }).collect(); - crate::model::IndexedModel{ - unique_pos:spam_pos, - unique_tex:spam_tex, - unique_normal:spam_normal, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - groups, - instances:vec![crate::model::ModelInstance{ - attributes:crate::model::CollisionAttributes::Decoration, - transform:integer::Planar64Affine3::new( - integer::Planar64Mat3::default(), - valve_transform(<[f32;3]>::from(world_model.origin)) - ), - ..Default::default() - }], - } - }).collect(); - - //dedupe prop models - let mut model_dedupe=std::collections::HashSet::new(); - for prop in bsp.static_props(){ - model_dedupe.insert(prop.model()); - } - - //generate unique meshes - let mut model_map=std::collections::HashMap::with_capacity(model_dedupe.len()); - let mut prop_models=Vec::new(); - for model_name in model_dedupe{ - let model_name_lower=model_name.to_lowercase(); - //.mdl, .vvd, .dx90.vtx - let mut path=std::path::PathBuf::from(model_name_lower.as_str()); - let file_name=std::path::PathBuf::from(path.file_stem().unwrap()); - path.pop(); - path.push(file_name); - let mut vvd_path=path.clone(); - let mut vtx_path=path.clone(); - vvd_path.set_extension("vvd"); - vtx_path.set_extension("dx90.vtx"); - match (bsp.pack.get(model_name_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){ - (Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{ - match (vmdl::mdl::Mdl::read(mdl_file.as_ref()),vmdl::vvd::Vvd::read(vvd_file.as_ref()),vmdl::vtx::Vtx::read(vtx_file.as_ref())){ - (Ok(mdl),Ok(vvd),Ok(vtx))=>{ - let model=vmdl::Model::from_parts(mdl,vtx,vvd); - let texture_paths=model.texture_directories(); - if texture_paths.len()!=1{ - println!("WARNING: multiple texture paths"); - } - let skin=model.skin_tables().nth(0).unwrap(); - - let mut spam_pos=Vec::with_capacity(model.vertices().len()); - let mut spam_normal=Vec::with_capacity(model.vertices().len()); - let mut spam_tex=Vec::with_capacity(model.vertices().len()); - let mut spam_vertices=Vec::with_capacity(model.vertices().len()); - for (i,vertex) in model.vertices().iter().enumerate(){ - spam_pos.push(valve_transform(<[f32;3]>::from(vertex.position))); - spam_normal.push(valve_transform(<[f32;3]>::from(vertex.normal))); - spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates)); - spam_vertices.push(crate::model::IndexedVertex{ - pos:i as u32, - tex:i as u32, - normal:i as u32, - color:0, - }); - } - - let model_id=prop_models.len(); - model_map.insert(model_name,model_id); - prop_models.push(crate::model::IndexedModel{ - unique_pos:spam_pos, - unique_normal:spam_normal, - unique_tex:spam_tex, - unique_color:vec![glam::Vec4::ONE], - unique_vertices:spam_vertices, - groups:model.meshes().map(|mesh|{ - let texture=if let (Some(texture_path),Some(texture_name))=(texture_paths.get(0),skin.texture(mesh.material_index())){ - let mut path=std::path::PathBuf::from(texture_path.as_str()); - path.push(texture_name); - let texture_location=path.as_os_str().to_str().unwrap(); - let texture_id=if let Some(&texture_id)=texture_id_from_name.get(texture_location){ - texture_id - }else{ - println!("texture! {}",texture_location); - let texture_id=name_from_texture_id.len() as u32; - texture_id_from_name.insert(texture_location.to_string(),texture_id); - name_from_texture_id.push(texture_location.to_string()); - texture_id - }; - Some(texture_id) - }else{ - None - }; - - crate::model::IndexedGroup{ - texture, - polys:{ - //looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function - mesh.vertex_strip_indices().map(|strip|{ - strip.collect::>().chunks(3).map(|tri|{ - crate::model::IndexedPolygon{vertices:vec![tri[0] as u32,tri[1] as u32,tri[2] as u32]} - }).collect::>() - }).flatten().collect() - }, - } - }).collect(), - instances:Vec::new(), - }); - }, - _=>println!("model_name={} error",model_name), - } - }, - _=>println!("no model name={}",model_name), - } - } - - //generate model instances - for prop in bsp.static_props(){ - let placement=prop.as_prop_placement(); - if let Some(&model_index)=model_map.get(placement.model){ - prop_models[model_index].instances.push(crate::model::ModelInstance{ - transform:integer::Planar64Affine3::new( - integer::Planar64Mat3::try_from( - glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale)) - //TODO: figure this out - *glam::Mat3A::from_quat(glam::Quat::from_xyzw( - placement.rotation.v.x,//b - placement.rotation.v.y,//c - placement.rotation.v.z,//d - placement.rotation.s,//a - )) - ).unwrap(), - valve_transform(<[f32;3]>::from(placement.origin)), - ), - attributes:crate::model::CollisionAttributes::Decoration, - ..Default::default() - }); - }else{ - //println!("model not found {}",placement.model); - } - } - - //actually add the prop models - prop_models.append(&mut models); - - Ok(crate::model::IndexedModelInstances{ - textures:name_from_texture_id, - models:prop_models, - spawn_point, - modes:Vec::new(), - }) - }, - Err(e)=>{ - println!("rotten {:?}",e); - Err(e) - }, - } -} diff --git a/src/load_roblox.rs b/src/load_roblox.rs deleted file mode 100644 index 211208d..0000000 --- a/src/load_roblox.rs +++ /dev/null @@ -1,523 +0,0 @@ -use crate::primitives; -use strafesnet_common::integer::{Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3}; - -fn class_is_a(class: &str, superclass: &str) -> bool { - if class==superclass { - return true - } - let class_descriptor=rbx_reflection_database::get().classes.get(class); - if let Some(descriptor) = &class_descriptor { - if let Some(class_super) = &descriptor.superclass { - return class_is_a(&class_super, superclass) - } - } - return false -} -fn recursive_collect_superclass(objects: &mut std::vec::Vec,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){ - let mut stack=vec![instance]; - while let Some(item)=stack.pop(){ - for &referent in item.children(){ - if let Some(c)=dom.get_by_ref(referent){ - if class_is_a(c.class.as_str(),superclass){ - objects.push(c.referent());//copy ref - } - stack.push(c); - } - } - } -} -fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_weak::types::Vector3)->Planar64Affine3{ - Planar64Affine3::new( - Planar64Mat3::from_cols( - Planar64Vec3::try_from([cf.orientation.x.x,cf.orientation.y.x,cf.orientation.z.x]).unwrap() - *Planar64::try_from(size.x/2.0).unwrap(), - Planar64Vec3::try_from([cf.orientation.x.y,cf.orientation.y.y,cf.orientation.z.y]).unwrap() - *Planar64::try_from(size.y/2.0).unwrap(), - Planar64Vec3::try_from([cf.orientation.x.z,cf.orientation.y.z,cf.orientation.z.z]).unwrap() - *Planar64::try_from(size.z/2.0).unwrap(), - ), - Planar64Vec3::try_from([cf.position.x,cf.position.y,cf.position.z]).unwrap() - ) -} -fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_intersecting:bool)->crate::model::CollisionAttributes{ - let mut general=crate::model::GameMechanicAttributes::default(); - let mut intersecting=crate::model::IntersectingAttributes::default(); - let mut contacting=crate::model::ContactingAttributes::default(); - let mut force_can_collide=can_collide; - match name{ - "Water"=>{ - force_can_collide=false; - //TODO: read stupid CustomPhysicalProperties - intersecting.water=Some(crate::model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity}); - }, - "Accelerator"=>{ - //although the new game supports collidable accelerators, this is a roblox compatability map loader - force_can_collide=false; - general.accelerator=Some(crate::model::GameMechanicAccelerator{acceleration:velocity}); - }, - // "UnorderedCheckpoint"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ - // mode_id:0, - // stage_id:0, - // force:false, - // behaviour:crate::model::StageElementBehaviour::Unordered - // })), - "SetVelocity"=>general.trajectory=Some(crate::model::GameMechanicSetTrajectory::Velocity(velocity)), - "MapFinish"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Finish})}, - "MapAnticheat"=>{force_can_collide=false;general.zone=Some(crate::model::GameMechanicZone{mode_id:0,behaviour:crate::model::ZoneBehaviour::Anitcheat})}, - "Platform"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ - mode_id:0, - stage_id:0, - force:false, - behaviour:crate::model::StageElementBehaviour::Platform, - })), - other=>{ - if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$") - .captures(other){ - general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ - mode_id:0, - stage_id:captures[3].parse::().unwrap(), - force:match captures.get(1){ - Some(m)=>m.as_str()=="Force", - None=>false, - }, - behaviour:match &captures[2]{ - "Spawn"|"SpawnAt"=>crate::model::StageElementBehaviour::SpawnAt, - //cancollide false so you don't hit the side - //NOT a decoration - "Trigger"=>{force_can_collide=false;crate::model::StageElementBehaviour::Trigger}, - "Teleport"=>{force_can_collide=false;crate::model::StageElementBehaviour::Teleport}, - "Platform"=>crate::model::StageElementBehaviour::Platform, - _=>panic!("regex1[2] messed up bad"), - } - })); - }else if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Jump)(\d+)$") - .captures(other){ - general.teleport_behaviour=Some(crate::model::TeleportBehaviour::StageElement(crate::model::GameMechanicStageElement{ - mode_id:0, - stage_id:0, - force:match captures.get(1){ - Some(m)=>m.as_str()=="Force", - None=>false, - }, - behaviour:match &captures[2]{ - "Jump"=>crate::model::StageElementBehaviour::JumpLimit(captures[3].parse::().unwrap()), - _=>panic!("regex4[1] messed up bad"), - } - })); - }else if let Some(captures)=lazy_regex::regex!(r"^Bonus(Finish|Anticheat)(\d+)$") - .captures(other){ - force_can_collide=false; - match &captures[1]{ - "Finish"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:captures[2].parse::().unwrap(),behaviour:crate::model::ZoneBehaviour::Finish}), - "Anticheat"=>general.zone=Some(crate::model::GameMechanicZone{mode_id:captures[2].parse::().unwrap(),behaviour:crate::model::ZoneBehaviour::Anitcheat}), - _=>panic!("regex2[1] messed up bad"), - } - }else if let Some(captures)=lazy_regex::regex!(r"^(WormholeIn)(\d+)$") - .captures(other){ - force_can_collide=false; - match &captures[1]{ - "WormholeIn"=>general.teleport_behaviour=Some(crate::model::TeleportBehaviour::Wormhole(crate::model::GameMechanicWormhole{destination_model_id:captures[2].parse::().unwrap()})), - _=>panic!("regex3[1] messed up bad"), - } - } - // else if let Some(captures)=lazy_regex::regex!(r"^(OrderedCheckpoint)(\d+)$") - // .captures(other){ - // match &captures[1]{ - // "OrderedCheckpoint"=>general.checkpoint=Some(crate::model::GameMechanicCheckpoint::Ordered{mode_id:0,checkpoint_id:captures[2].parse::().unwrap()}), - // _=>panic!("regex3[1] messed up bad"), - // } - // } - } - } - //need some way to skip this - if velocity!=Planar64Vec3::ZERO{ - general.booster=Some(crate::model::GameMechanicBooster::Velocity(velocity)); - } - match force_can_collide{ - true=>{ - match name{ - "Bounce"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Elastic(u32::MAX)), - "Surf"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Surf), - "Ladder"=>contacting.contact_behaviour=Some(crate::model::ContactingBehaviour::Ladder(crate::model::ContactingLadder{sticky:true})), - _=>(), - } - crate::model::CollisionAttributes::Contact{contacting,general} - }, - false=>if force_intersecting - ||general.any() - ||intersecting.any() - { - crate::model::CollisionAttributes::Intersect{intersecting,general} - }else{ - crate::model::CollisionAttributes::Decoration - }, - } -} - -struct RobloxAssetId(u64); -struct RobloxAssetIdParseErr; -impl std::str::FromStr for RobloxAssetId { - type Err=RobloxAssetIdParseErr; - fn from_str(s: &str) -> Result{ - let regman=lazy_regex::regex!(r"(\d+)$"); - if let Some(captures) = regman.captures(s) { - if captures.len()==2{//captures[0] is all captures concatenated, and then each individual capture - if let Ok(id) = captures[0].parse::() { - return Ok(Self(id)); - } - } - } - Err(RobloxAssetIdParseErr) - } -} -#[derive(Clone,Copy,PartialEq)] -struct RobloxTextureTransform{ - offset_u:f32, - offset_v:f32, - scale_u:f32, - scale_v:f32, -} -impl std::cmp::Eq for RobloxTextureTransform{}//???? -impl std::default::Default for RobloxTextureTransform{ - fn default() -> Self { - Self{offset_u:0.0,offset_v:0.0,scale_u:1.0,scale_v:1.0} - } -} -impl std::hash::Hash for RobloxTextureTransform { - fn hash(&self, state: &mut H) { - self.offset_u.to_ne_bytes().hash(state); - self.offset_v.to_ne_bytes().hash(state); - self.scale_u.to_ne_bytes().hash(state); - self.scale_v.to_ne_bytes().hash(state); - } -} -#[derive(Clone,PartialEq)] -struct RobloxFaceTextureDescription{ - texture:u32, - color:glam::Vec4, - transform:RobloxTextureTransform, -} -impl std::cmp::Eq for RobloxFaceTextureDescription{}//???? -impl std::hash::Hash for RobloxFaceTextureDescription { - fn hash(&self, state: &mut H) { - self.texture.hash(state); - self.transform.hash(state); - for &el in self.color.as_ref().iter() { - el.to_ne_bytes().hash(state); - } - } -} -impl RobloxFaceTextureDescription{ - fn to_face_description(&self)->primitives::FaceDescription{ - primitives::FaceDescription{ - texture:Some(self.texture), - transform:glam::Affine2::from_translation( - glam::vec2(self.transform.offset_u,self.transform.offset_v) - ) - *glam::Affine2::from_scale( - glam::vec2(self.transform.scale_u,self.transform.scale_v) - ), - color:self.color, - } - } -} -type RobloxPartDescription=[Option;6]; -type RobloxWedgeDescription=[Option;5]; -type RobloxCornerWedgeDescription=[Option;5]; -#[derive(Clone,Eq,Hash,PartialEq)] -enum RobloxBasePartDescription{ - Sphere(RobloxPartDescription), - Part(RobloxPartDescription), - Cylinder(RobloxPartDescription), - Wedge(RobloxWedgeDescription), - CornerWedge(RobloxCornerWedgeDescription), -} -pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> crate::model::IndexedModelInstances{ - //IndexedModelInstances includes textures - let mut spawn_point=Planar64Vec3::ZERO; - - let mut indexed_models=Vec::new(); - let mut model_id_from_description=std::collections::HashMap::::new(); - - let mut texture_id_from_asset_id=std::collections::HashMap::::new(); - let mut asset_id_from_texture_id=Vec::new(); - - let mut object_refs=Vec::new(); - let mut temp_objects=Vec::new(); - recursive_collect_superclass(&mut object_refs, &dom, dom.root(),"BasePart"); - for object_ref in object_refs { - if let Some(object)=dom.get_by_ref(object_ref){ - if let ( - Some(rbx_dom_weak::types::Variant::CFrame(cf)), - Some(rbx_dom_weak::types::Variant::Vector3(size)), - Some(rbx_dom_weak::types::Variant::Vector3(velocity)), - Some(rbx_dom_weak::types::Variant::Float32(transparency)), - Some(rbx_dom_weak::types::Variant::Color3uint8(color3)), - Some(rbx_dom_weak::types::Variant::Bool(can_collide)), - ) = ( - object.properties.get("CFrame"), - object.properties.get("Size"), - object.properties.get("Velocity"), - object.properties.get("Transparency"), - object.properties.get("Color"), - object.properties.get("CanCollide"), - ) - { - let model_transform=planar64_affine3_from_roblox(cf,size); - - if model_transform.matrix3.determinant()==Planar64::ZERO{ - let mut parent_ref=object.parent(); - let mut full_path=object.name.clone(); - while let Some(parent)=dom.get_by_ref(parent_ref){ - full_path=format!("{}.{}",parent.name,full_path); - parent_ref=parent.parent(); - } - println!("Zero determinant CFrame at location {}",full_path); - println!("matrix3:{}",model_transform.matrix3); - continue; - } - - //push TempIndexedAttributes - let mut force_intersecting=false; - let mut temp_indexing_attributes=Vec::new(); - if let Some(attr)=match &object.name[..]{ - "MapStart"=>{ - spawn_point=model_transform.transform_point3(Planar64Vec3::ZERO)+Planar64Vec3::Y*5/2; - Some(crate::model::TempIndexedAttributes::Start(crate::model::TempAttrStart{mode_id:0})) - }, - other=>{ - let regman=lazy_regex::regex!(r"^(BonusStart|Spawn|ForceSpawn|WormholeOut)(\d+)$"); - if let Some(captures) = regman.captures(other) { - match &captures[1]{ - "BonusStart"=>Some(crate::model::TempIndexedAttributes::Start(crate::model::TempAttrStart{mode_id:captures[2].parse::().unwrap()})), - "Spawn"|"ForceSpawn"=>Some(crate::model::TempIndexedAttributes::Spawn(crate::model::TempAttrSpawn{mode_id:0,stage_id:captures[2].parse::().unwrap()})), - "WormholeOut"=>Some(crate::model::TempIndexedAttributes::Wormhole(crate::model::TempAttrWormhole{wormhole_id:captures[2].parse::().unwrap()})), - _=>None, - } - }else{ - None - } - } - }{ - force_intersecting=true; - temp_indexing_attributes.push(attr); - } - - //TODO: also detect "CylinderMesh" etc here - let shape=match &object.class[..]{ - "Part"=>{ - if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){ - match shape.to_u32(){ - 0=>primitives::Primitives::Sphere, - 1=>primitives::Primitives::Cube, - 2=>primitives::Primitives::Cylinder, - 3=>primitives::Primitives::Wedge, - 4=>primitives::Primitives::CornerWedge, - _=>panic!("Funky roblox PartType={};",shape.to_u32()), - } - }else{ - panic!("Part has no Shape!"); - } - }, - "TrussPart"=>primitives::Primitives::Cube, - "WedgePart"=>primitives::Primitives::Wedge, - "CornerWedgePart"=>primitives::Primitives::CornerWedge, - _=>{ - println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class); - primitives::Primitives::Cube - } - }; - - //use the biggest one and cut it down later... - let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None]; - temp_objects.clear(); - recursive_collect_superclass(&mut temp_objects, &dom, object,"Decal"); - for &decal_ref in &temp_objects{ - if let Some(decal)=dom.get_by_ref(decal_ref){ - if let ( - Some(rbx_dom_weak::types::Variant::Content(content)), - Some(rbx_dom_weak::types::Variant::Enum(normalid)), - Some(rbx_dom_weak::types::Variant::Color3(decal_color3)), - Some(rbx_dom_weak::types::Variant::Float32(decal_transparency)), - ) = ( - decal.properties.get("Texture"), - decal.properties.get("Face"), - decal.properties.get("Color3"), - decal.properties.get("Transparency"), - ) { - if let Ok(asset_id)=content.clone().into_string().parse::(){ - let texture_id=if let Some(&texture_id)=texture_id_from_asset_id.get(&asset_id.0){ - texture_id - }else{ - let texture_id=asset_id_from_texture_id.len() as u32; - texture_id_from_asset_id.insert(asset_id.0,texture_id); - asset_id_from_texture_id.push(asset_id.0); - texture_id - }; - let normal_id=normalid.to_u32(); - if normal_id<6{ - let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{ - //generate tranform - if let ( - Some(rbx_dom_weak::types::Variant::Float32(ox)), - Some(rbx_dom_weak::types::Variant::Float32(oy)), - Some(rbx_dom_weak::types::Variant::Float32(sx)), - Some(rbx_dom_weak::types::Variant::Float32(sy)), - ) = ( - decal.properties.get("OffsetStudsU"), - decal.properties.get("OffsetStudsV"), - decal.properties.get("StudsPerTileU"), - decal.properties.get("StudsPerTileV"), - ) - { - let (size_u,size_v)=match normal_id{ - 0=>(size.z,size.y),//right - 1=>(size.x,size.z),//top - 2=>(size.x,size.y),//back - 3=>(size.z,size.y),//left - 4=>(size.x,size.z),//bottom - 5=>(size.x,size.y),//front - _=>panic!("unreachable"), - }; - ( - glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency), - RobloxTextureTransform{ - offset_u:*ox/(*sx),offset_v:*oy/(*sy), - scale_u:size_u/(*sx),scale_v:size_v/(*sy), - } - ) - }else{ - (glam::Vec4::ONE,RobloxTextureTransform::default()) - } - }else{ - (glam::Vec4::ONE,RobloxTextureTransform::default()) - }; - part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{ - texture:texture_id, - color:roblox_texture_color, - transform:roblox_texture_transform, - }); - }else{ - println!("NormalId={} unsupported for shape={:?}",normal_id,shape); - } - } - } - } - } - //obscure rust syntax "slice pattern" - let [ - f0,//Cube::Right - f1,//Cube::Top - f2,//Cube::Back - f3,//Cube::Left - f4,//Cube::Bottom - f5,//Cube::Front - ]=part_texture_description; - let basepart_texture_description=match shape{ - primitives::Primitives::Sphere=>RobloxBasePartDescription::Sphere([f0,f1,f2,f3,f4,f5]), - primitives::Primitives::Cube=>RobloxBasePartDescription::Part([f0,f1,f2,f3,f4,f5]), - primitives::Primitives::Cylinder=>RobloxBasePartDescription::Cylinder([f0,f1,f2,f3,f4,f5]), - //use front face texture first and use top face texture as a fallback - primitives::Primitives::Wedge=>RobloxBasePartDescription::Wedge([ - f0,//Cube::Right->Wedge::Right - if f5.is_some(){f5}else{f1},//Cube::Front|Cube::Top->Wedge::TopFront - f2,//Cube::Back->Wedge::Back - f3,//Cube::Left->Wedge::Left - f4,//Cube::Bottom->Wedge::Bottom - ]), - //TODO: fix Left+Back texture coordinates to match roblox when not overwridden by Top - primitives::Primitives::CornerWedge=>RobloxBasePartDescription::CornerWedge([ - f0,//Cube::Right->CornerWedge::Right - if f2.is_some(){f2}else{f1.clone()},//Cube::Back|Cube::Top->CornerWedge::TopBack - if f3.is_some(){f3}else{f1},//Cube::Left|Cube::Top->CornerWedge::TopLeft - f4,//Cube::Bottom->CornerWedge::Bottom - f5,//Cube::Front->CornerWedge::Front - ]), - }; - //make new model if unit cube has not been created before - let model_id=if let Some(&model_id)=model_id_from_description.get(&basepart_texture_description){ - //push to existing texture model - model_id - }else{ - let model_id=indexed_models.len(); - model_id_from_description.insert(basepart_texture_description.clone(),model_id);//borrow checker going crazy - indexed_models.push(match basepart_texture_description{ - RobloxBasePartDescription::Sphere(part_texture_description) - |RobloxBasePartDescription::Cylinder(part_texture_description) - |RobloxBasePartDescription::Part(part_texture_description)=>{ - let mut cube_face_description=primitives::CubeFaceDescription::default(); - for (face_id,roblox_face_description) in part_texture_description.iter().enumerate(){ - cube_face_description.insert( - match face_id{ - 0=>primitives::CubeFace::Right, - 1=>primitives::CubeFace::Top, - 2=>primitives::CubeFace::Back, - 3=>primitives::CubeFace::Left, - 4=>primitives::CubeFace::Bottom, - 5=>primitives::CubeFace::Front, - _=>panic!("unreachable"), - }, - match roblox_face_description{ - Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), - None=>primitives::FaceDescription::default(), - }); - } - primitives::generate_partial_unit_cube(cube_face_description) - }, - RobloxBasePartDescription::Wedge(wedge_texture_description)=>{ - let mut wedge_face_description=primitives::WedgeFaceDescription::default(); - for (face_id,roblox_face_description) in wedge_texture_description.iter().enumerate(){ - wedge_face_description.insert( - match face_id{ - 0=>primitives::WedgeFace::Right, - 1=>primitives::WedgeFace::TopFront, - 2=>primitives::WedgeFace::Back, - 3=>primitives::WedgeFace::Left, - 4=>primitives::WedgeFace::Bottom, - _=>panic!("unreachable"), - }, - match roblox_face_description{ - Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), - None=>primitives::FaceDescription::default(), - }); - } - primitives::generate_partial_unit_wedge(wedge_face_description) - }, - RobloxBasePartDescription::CornerWedge(cornerwedge_texture_description)=>{ - let mut cornerwedge_face_description=primitives::CornerWedgeFaceDescription::default(); - for (face_id,roblox_face_description) in cornerwedge_texture_description.iter().enumerate(){ - cornerwedge_face_description.insert( - match face_id{ - 0=>primitives::CornerWedgeFace::Right, - 1=>primitives::CornerWedgeFace::TopBack, - 2=>primitives::CornerWedgeFace::TopLeft, - 3=>primitives::CornerWedgeFace::Bottom, - 4=>primitives::CornerWedgeFace::Front, - _=>panic!("unreachable"), - }, - match roblox_face_description{ - Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(), - None=>primitives::FaceDescription::default(), - }); - } - primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description) - }, - }); - model_id - }; - indexed_models[model_id].instances.push(crate::model::ModelInstance { - transform:model_transform, - color:glam::vec4(color3.r as f32/255f32, color3.g as f32/255f32, color3.b as f32/255f32, 1.0-*transparency), - attributes:get_attributes(&object.name,*can_collide,Planar64Vec3::try_from([velocity.x,velocity.y,velocity.z]).unwrap(),force_intersecting), - temp_indexing:temp_indexing_attributes, - }); - } - } - } - crate::model::IndexedModelInstances{ - textures:asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(), - models:indexed_models, - spawn_point, - modes:Vec::new(), - } -} diff --git a/src/main.rs b/src/main.rs index 3f4300b..337ba79 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,10 @@ -use strafesnet_common::integer; - -mod model; +mod file; mod setup; mod window; mod worker; mod physics; mod graphics; mod settings; -mod primitives; -mod load_bsp; -mod load_roblox; mod face_crawler; mod compat_worker; mod model_physics; @@ -17,102 +12,6 @@ mod model_graphics; mod physics_worker; mod graphics_worker; -fn load_file(path: std::path::PathBuf)->Option{ - println!("Loading file: {:?}", &path); - //oh boy! let's load the map! - if let Ok(file)=std::fs::File::open(path){ - let mut input = std::io::BufReader::new(file); - let mut first_8=[0u8;8]; - //.rbxm roblox binary = "{ - match match &first_8[4..8]{ - b"lox!"=>rbx_binary::from_reader(input).map_err(|e|format!("{:?}",e)), - b"lox "=>rbx_xml::from_reader(input,rbx_xml::DecodeOptions::default()).map_err(|e|format!("{:?}",e)), - other=>Err(format!("Unknown Roblox file type {:?}",other)), - }{ - Ok(dom)=>Some(load_roblox::generate_indexed_models(dom)), - Err(e)=>{ - println!("Error loading roblox file:{:?}",e); - None - }, - } - }, - b"VBSP"=>load_bsp::generate_indexed_models(&mut input).ok(), - //b"SNFM"=>Some(sniffer::generate_indexed_models(input)), - //b"SNFB"=>Some(sniffer::load_bot(input)), - other=>{ - println!("loser file {:?}",other); - None - }, - } - }else{ - println!("Failed to read first 8 bytes and seek back to beginning of file."); - None - } - }else{ - println!("Could not open file"); - None - } -} - -pub fn default_models()->model::IndexedModelInstances{ - let mut indexed_models = Vec::new(); - indexed_models.push(primitives::unit_sphere()); - indexed_models.push(primitives::unit_cylinder()); - indexed_models.push(primitives::unit_cube()); - println!("models.len = {:?}", indexed_models.len()); - //quad monkeys - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(10.,5.,10.))).unwrap(), - ..Default::default() - }); - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(20.,5.,10.))).unwrap(), - color:glam::vec4(1.0,0.0,0.0,1.0), - ..Default::default() - }); - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(10.,5.,20.))).unwrap(), - color:glam::vec4(0.0,1.0,0.0,1.0), - ..Default::default() - }); - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(20.,5.,20.))).unwrap(), - color:glam::vec4(0.0,0.0,1.0,1.0), - ..Default::default() - }); - //decorative monkey - indexed_models[0].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(15.,10.,15.))).unwrap(), - color:glam::vec4(0.5,0.5,0.5,0.5), - attributes:model::CollisionAttributes::Decoration, - ..Default::default() - }); - //teapot - indexed_models[1].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_scale_rotation_translation(glam::vec3(0.5, 1.0, 0.2),glam::quat(-0.22248298016985793,-0.839457167990537,-0.05603504040830783,-0.49261857546227916),glam::vec3(-10.,7.,10.))).unwrap(), - ..Default::default() - }); - //ground - indexed_models[2].instances.push(model::ModelInstance{ - transform:integer::Planar64Affine3::try_from(glam::Affine3A::from_translation(glam::vec3(0.,0.,0.))*glam::Affine3A::from_scale(glam::vec3(160.0, 1.0, 160.0))).unwrap(), - ..Default::default() - }); - model::IndexedModelInstances{ - textures:Vec::new(), - models:indexed_models, - spawn_point:integer::Planar64Vec3::Y*50, - modes:Vec::new(), - } -} - fn main(){ setup::setup_and_start(format!("Strafe Client v{}",env!("CARGO_PKG_VERSION"))); } diff --git a/src/model.rs b/src/model.rs deleted file mode 100644 index ddfc3d9..0000000 --- a/src/model.rs +++ /dev/null @@ -1,321 +0,0 @@ -use strafesnet_common::integer::{Time,Planar64,Planar64Vec3,Planar64Affine3}; -pub type TextureCoordinate=glam::Vec2; -pub type Color4=glam::Vec4; -#[derive(Clone,Hash,PartialEq,Eq)] -pub struct IndexedVertex{ - pub pos:u32, - pub tex:u32, - pub normal:u32, - pub color:u32, -} -pub struct IndexedPolygon{ - pub vertices:Vec, -} -pub struct IndexedGroup{ - pub texture:Option,//RenderPattern? material/texture/shader/flat color - pub polys:Vec, -} -pub struct IndexedModel{ - pub unique_pos:Vec, - pub unique_normal:Vec, - pub unique_tex:Vec, - pub unique_color:Vec, - pub unique_vertices:Vec, - pub groups: Vec, - pub instances:Vec, -} -pub struct ModelInstance{ - //pub id:u64,//this does not actually help with map fixes resimulating bots, they must always be resimulated - pub transform:Planar64Affine3, - pub color:Color4,//transparency is in here - pub attributes:CollisionAttributes, - pub temp_indexing:Vec, -} -impl std::default::Default for ModelInstance{ - fn default() -> Self { - Self{ - color:Color4::ONE, - transform:Default::default(), - attributes:Default::default(), - temp_indexing:Default::default(), - } - } -} -pub struct IndexedModelInstances{ - pub textures:Vec,//RenderPattern - pub models:Vec, - //may make this into an object later. - pub modes:Vec, - pub spawn_point:Planar64Vec3, -} -//stage description referencing flattened ids is spooky, but the map loading is meant to be deterministic. -pub struct ModeDescription{ - //TODO: put "default" style modifiers in mode - //pub style:StyleModifiers, - pub start:usize,//start=model_id - pub spawns:Vec,//spawns[spawn_id]=model_id - pub spawn_from_stage_id:std::collections::HashMap::, - pub ordered_checkpoint_from_checkpoint_id:std::collections::HashMap::, -} -impl ModeDescription{ - pub fn get_spawn_model_id(&self,stage_id:u32)->Option<&usize>{ - self.spawns.get(*self.spawn_from_stage_id.get(&stage_id)?) - } -} -//I don't want this code to exist! -#[derive(Clone)] -pub struct TempAttrStart{ - pub mode_id:u32, -} -#[derive(Clone)] -pub struct TempAttrSpawn{ - pub mode_id:u32, - pub stage_id:u32, -} -#[derive(Clone)] -pub struct TempAttrWormhole{ - pub wormhole_id:u32, -} -pub enum TempIndexedAttributes{ - Start(TempAttrStart), - Spawn(TempAttrSpawn), - Wormhole(TempAttrWormhole), -} - -//you have this effect while in contact -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct ContactingLadder{ - pub sticky:bool -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum ContactingBehaviour{ - Surf, - Cling,//usable as a zipline, or other weird and wonderful things - Ladder(ContactingLadder), - Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32 -} -//you have this effect while intersecting -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct IntersectingWater{ - pub viscosity:Planar64, - pub density:Planar64, - pub velocity:Planar64Vec3, -} -//All models can be given these attributes -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicAccelerator{ - pub acceleration:Planar64Vec3 -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum GameMechanicBooster{ - Affine(Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more - Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity - Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum TrajectoryChoice{ - HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time - LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum GameMechanicSetTrajectory{ - //Speed-type SetTrajectory - AirTime(Time),//air time (relative to gravity direction) is invariant across mass and gravity changes - Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes - DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions - //Velocity-type SetTrajectory - TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time - target_point:Planar64Vec3, - time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way - }, - TargetPointSpeed{//launch at a fixed speed and land at a target point - target_point:Planar64Vec3, - speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead - trajectory_choice:TrajectoryChoice, - }, - Velocity(Planar64Vec3),//SetVelocity -} -impl GameMechanicSetTrajectory{ - fn is_velocity(&self)->bool{ - match self{ - GameMechanicSetTrajectory::AirTime(_) - |GameMechanicSetTrajectory::Height(_) - |GameMechanicSetTrajectory::DotVelocity{direction:_,dot:_}=>false, - GameMechanicSetTrajectory::TargetPointTime{target_point:_,time:_} - |GameMechanicSetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_} - |GameMechanicSetTrajectory::Velocity(_)=>true, - } - } -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum ZoneBehaviour{ - //Start is indexed - //Checkpoints are indexed - Finish, - Anitcheat, -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicZone{ - pub mode_id:u32, - pub behaviour:ZoneBehaviour, -} -// enum TrapCondition{ -// FasterThan(Planar64), -// SlowerThan(Planar64), -// InRange(Planar64,Planar64), -// OutsideRange(Planar64,Planar64), -// } -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum StageElementBehaviour{ - //Spawn,//The behaviour of stepping on a spawn setting the spawnid - SpawnAt,//must be standing on top to get effect. except cancollide false - Trigger, - Teleport, - Platform, - //Checkpoint acts like a trigger if you haven't hit all the checkpoints yet. - //Note that all stage elements act like this for the next stage. - Checkpoint, - //OrderedCheckpoint. You must pass through all of these in ascending order. - //If you hit them out of order it acts like a trigger. - //Do not support backtracking at all for now. - Ordered{ - checkpoint_id:u32, - }, - //UnorderedCheckpoint. You must pass through all of these in any order. - Unordered, - //If you get reset by a jump limit - JumpLimit(u32), - //Speedtrap(TrapCondition),//Acts as a trigger with a speed condition -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicStageElement{ - pub mode_id:u32, - pub stage_id:u32,//which spawn to send to - pub force:bool,//allow setting to lower spawn id i.e. 7->3 - pub behaviour:StageElementBehaviour -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicWormhole{ - //destination does not need to be another wormhole - //this defines a one way portal to a destination model transform - //two of these can create a two way wormhole - pub destination_model_id:u32, - //(position,angles)*=origin.transform.inverse()*destination.transform -} -#[derive(Clone,Hash,Eq,PartialEq)] -pub enum TeleportBehaviour{ - StageElement(GameMechanicStageElement), - Wormhole(GameMechanicWormhole), -} -//attributes listed in order of handling -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct GameMechanicAttributes{ - pub zone:Option, - pub booster:Option, - pub trajectory:Option, - pub teleport_behaviour:Option, - pub accelerator:Option, -} -impl GameMechanicAttributes{ - pub fn any(&self)->bool{ - self.zone.is_some() - ||self.booster.is_some() - ||self.trajectory.is_some() - ||self.teleport_behaviour.is_some() - ||self.accelerator.is_some() - } - pub fn is_wrcp(&self,current_mode_id:u32)->bool{ - self.trajectory.as_ref().map_or(false,|t|t.is_velocity()) - &&match &self.teleport_behaviour{ - Some(TeleportBehaviour::StageElement( - GameMechanicStageElement{ - mode_id, - stage_id:_, - force:true, - behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport - } - ))=>current_mode_id==*mode_id, - _=>false, - } - } -} -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct ContactingAttributes{ - //friction? - pub contact_behaviour:Option, -} -impl ContactingAttributes{ - pub fn any(&self)->bool{ - self.contact_behaviour.is_some() - } -} -#[derive(Default,Clone,Hash,Eq,PartialEq)] -pub struct IntersectingAttributes{ - pub water:Option, -} -impl IntersectingAttributes{ - pub fn any(&self)->bool{ - self.water.is_some() - } -} -//Spawn(u32) NO! spawns are indexed in the map header instead of marked with attibutes -pub enum CollisionAttributes{ - Decoration,//visual only - Contact{//track whether you are contacting the object - contacting:ContactingAttributes, - general:GameMechanicAttributes, - }, - Intersect{//track whether you are intersecting the object - intersecting:IntersectingAttributes, - general:GameMechanicAttributes, - }, -} -impl std::default::Default for CollisionAttributes{ - fn default() -> Self { - Self::Contact{ - contacting:ContactingAttributes::default(), - general:GameMechanicAttributes::default() - } - } -} - -pub fn generate_indexed_model_list_from_obj(data:obj::ObjData,color:Color4)->Vec{ - let mut unique_vertex_index = std::collections::HashMap::::new(); - return data.objects.iter().map(|object|{ - unique_vertex_index.clear(); - let mut unique_vertices = Vec::new(); - let groups = object.groups.iter().map(|group|{ - IndexedGroup{ - texture:None, - polys:group.polys.iter().map(|poly|{ - IndexedPolygon{ - vertices:poly.0.iter().map(|&tup|{ - if let Some(&i)=unique_vertex_index.get(&tup){ - i - }else{ - let i=unique_vertices.len() as u32; - unique_vertices.push(IndexedVertex{ - pos: tup.0 as u32, - tex: tup.1.unwrap() as u32, - normal: tup.2.unwrap() as u32, - color: 0, - }); - unique_vertex_index.insert(tup,i); - i - } - }).collect() - } - }).collect() - } - }).collect(); - IndexedModel{ - unique_pos: data.position.iter().map(|&v|Planar64Vec3::try_from(v).unwrap()).collect(), - unique_tex: data.texture.iter().map(|&v|TextureCoordinate::from_array(v)).collect(), - unique_normal: data.normal.iter().map(|&v|Planar64Vec3::try_from(v).unwrap()).collect(), - unique_color: vec![color], - unique_vertices, - groups, - instances:Vec::new(), - } - }).collect() -} diff --git a/src/model_graphics.rs b/src/model_graphics.rs index 8e871e7..2468cda 100644 --- a/src/model_graphics.rs +++ b/src/model_graphics.rs @@ -1,50 +1,39 @@ -use bytemuck::{Pod, Zeroable}; -use crate::model::{IndexedVertex,IndexedPolygon}; -#[derive(Clone, Copy, Pod, Zeroable)] +use bytemuck::{Pod,Zeroable}; +use strafesnet_common::model::{IndexedVertex,PolygonGroup,RenderConfigId}; +#[derive(Clone,Copy,Pod,Zeroable)] #[repr(C)] -pub struct GraphicsVertex { - pub pos: [f32; 3], - pub tex: [f32; 2], - pub normal: [f32; 3], - pub color: [f32; 4], +pub struct GraphicsVertex{ + pub pos:[f32;3], + pub tex:[f32;2], + pub normal:[f32;3], + pub color:[f32;4], } -pub struct IndexedGroupFixedTexture{ - pub polys:Vec, -} -pub struct IndexedGraphicsModelSingleTexture{ - pub unique_pos:Vec<[f32; 3]>, - pub unique_tex:Vec<[f32; 2]>, - pub unique_normal:Vec<[f32; 3]>, - pub unique_color:Vec<[f32; 4]>, +#[derive(Clone,Copy,id::Id)] +pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32); +pub struct IndexedGraphicsMeshOwnedRenderConfig{ + pub unique_pos:Vec<[f32;3]>, + pub unique_tex:Vec<[f32;2]>, + pub unique_normal:Vec<[f32;3]>, + pub unique_color:Vec<[f32;4]>, pub unique_vertices:Vec, - pub texture:Option,//RenderPattern? material/texture/shader/flat color - pub groups: Vec, - pub instances:Vec, + pub render_config:RenderConfigId, + pub polys:PolygonGroup, + pub instances:Vec, } -pub enum Entities{ - U32(Vec>), - U16(Vec>), +pub enum Indices{ + U32(Vec), + U16(Vec), } -pub struct GraphicsModelSingleTexture{ - pub instances:Vec, +pub struct GraphicsMeshOwnedRenderConfig{ pub vertices:Vec, - pub entities:Entities, - pub texture:Option, + pub indices:Indices, + pub render_config:RenderConfigId, + pub instances:Vec, } -#[derive(Clone,PartialEq)] +#[derive(Clone,Copy,PartialEq,id::Id)] pub struct GraphicsModelColor4(glam::Vec4); -impl GraphicsModelColor4{ - pub const fn get(&self)->glam::Vec4{ - self.0 - } -} -impl From for GraphicsModelColor4{ - fn from(value:glam::Vec4)->Self{ - Self(value) - } -} impl std::hash::Hash for GraphicsModelColor4{ - fn hash(&self,state:&mut H) { + fn hash(&self,state:&mut H) { for &f in self.0.as_ref(){ bytemuck::cast::(f).hash(state); } @@ -52,7 +41,7 @@ impl std::hash::Hash for GraphicsModelColor4{ } impl Eq for GraphicsModelColor4{} #[derive(Clone)] -pub struct GraphicsModelInstance{ +pub struct GraphicsModelOwned{ pub transform:glam::Mat4, pub normal_transform:glam::Mat3, pub color:GraphicsModelColor4, diff --git a/src/model_physics.rs b/src/model_physics.rs index 7e31ac0..5789b9a 100644 --- a/src/model_physics.rs +++ b/src/model_physics.rs @@ -1,23 +1,15 @@ use std::borrow::{Borrow,Cow}; -use strafesnet_common::zeroes; -use strafesnet_common::integer::{self,Planar64,Planar64Vec3}; +use std::collections::{HashSet,HashMap}; +use strafesnet_common::integer::vec3::Vector3; +use strafesnet_common::model::{self,MeshId,PolygonIter}; +use strafesnet_common::integer::{self,vec3,Fixed,Planar64,Planar64Vec3,Ratio}; -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct VertId(usize); -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct EdgeId(usize); pub trait UndirectedEdge{ type DirectedEdge:Copy+DirectedEdge; fn as_directed(&self,parity:bool)->Self::DirectedEdge; } -impl UndirectedEdge for EdgeId{ - type DirectedEdge=DirectedEdgeId; - fn as_directed(&self,parity:bool)->DirectedEdgeId{ - DirectedEdgeId(self.0|((parity as usize)<<(usize::BITS-1))) - } -} pub trait DirectedEdge{ - type UndirectedEdge:Copy+UndirectedEdge; + type UndirectedEdge:Copy+std::fmt::Debug+UndirectedEdge; fn as_undirected(&self)->Self::UndirectedEdge; fn parity(&self)->bool; //this is stupid but may work fine @@ -25,22 +17,40 @@ pub trait DirectedEdge{ self.as_undirected().as_directed(!self.parity()) } } + +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct MeshVertId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct MeshFaceId(u32); + +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshVertId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshEdgeId(u32); /// DirectedEdgeId refers to an EdgeId when undirected. -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct DirectedEdgeId(usize); -impl DirectedEdge for DirectedEdgeId{ - type UndirectedEdge=EdgeId; - fn as_undirected(&self)->EdgeId{ - EdgeId(self.0&!(1<<(usize::BITS-1))) - } - fn parity(&self)->bool{ - self.0&(1<<(usize::BITS-1))!=0 +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshDirectedEdgeId(u32); +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct SubmeshFaceId(u32); + +impl UndirectedEdge for SubmeshEdgeId{ + type DirectedEdge=SubmeshDirectedEdgeId; + fn as_directed(&self,parity:bool)->SubmeshDirectedEdgeId{ + SubmeshDirectedEdgeId(self.0|((parity as u32)<<(u32::BITS-1))) + } +} +impl DirectedEdge for SubmeshDirectedEdgeId{ + type UndirectedEdge=SubmeshEdgeId; + fn as_undirected(&self)->SubmeshEdgeId{ + SubmeshEdgeId(self.0&!(1<<(u32::BITS-1))) + } + fn parity(&self)->bool{ + self.0&(1<<(u32::BITS-1))!=0 } } -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] -pub struct FaceId(usize); //Vertex <-> Edge <-> Face -> Collide +#[derive(Debug)] pub enum FEV{ Face(F), Edge(E::UndirectedEdge), @@ -48,12 +58,16 @@ pub enum FEV{ } //use Unit32 #[repr(C)] for map files +#[derive(Clone,Hash,Eq,PartialEq)] struct Face{ normal:Planar64Vec3, dot:Planar64, } struct Vert(Planar64Vec3); pub trait MeshQuery{ + // Vertex must be Planar64Vec3 because it represents an actual position + type Normal; + type Offset; fn edge_n(&self,edge_id:EDGE::UndirectedEdge)->Planar64Vec3{ let verts=self.edge_verts(edge_id); self.vert(verts[1].clone())-self.vert(verts[0].clone()) @@ -63,7 +77,7 @@ pub trait MeshQuery{ (self.vert(verts[1].clone())-self.vert(verts[0].clone()))*((directed_edge_id.parity() as i64)*2-1) } fn vert(&self,vert_id:VERT)->Planar64Vec3; - fn face_nd(&self,face_id:FACE)->(Planar64Vec3,Planar64); + fn face_nd(&self,face_id:FACE)->(Self::Normal,Self::Offset); fn face_edges(&self,face_id:FACE)->Cow>; fn edge_faces(&self,edge_id:EDGE::UndirectedEdge)->Cow<[FACE;2]>; fn edge_verts(&self,edge_id:EDGE::UndirectedEdge)->Cow<[VERT;2]>; @@ -71,34 +85,170 @@ pub trait MeshQuery{ fn vert_faces(&self,vert_id:VERT)->Cow>; } struct FaceRefs{ - edges:Vec, + edges:Vec, //verts:Vec, } struct EdgeRefs{ - faces:[FaceId;2],//left, right - verts:[VertId;2],//bottom, top + faces:[SubmeshFaceId;2],//left, right + verts:[SubmeshVertId;2],//bottom, top } struct VertRefs{ - faces:Vec, - edges:Vec, + faces:Vec, + edges:Vec, } -pub struct PhysicsMesh{ - faces:Vec, - verts:Vec, +pub struct PhysicsMeshData{ + //this contains all real and virtual faces used in both the complete mesh and convex submeshes + //faces are sorted such that all faces that belong to the complete mesh appear first, and then + //all remaining faces are virtual to operate internal logic of the face crawler + //and cannot be part of a physics collision + //virtual faces are only used in convex submeshes. + faces:Vec,//MeshFaceId indexes this list + verts:Vec,//MeshVertId indexes this list +} +pub struct PhysicsMeshTopology{ + //mapping of local ids to PhysicsMeshData ids + faces:Vec,//SubmeshFaceId indexes this list + verts:Vec,//SubmeshVertId indexes this list + //all ids here are local to this object face_topology:Vec, edge_topology:Vec, vert_topology:Vec, } +#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct PhysicsMeshId(u32); +impl Into for PhysicsMeshId{ + fn into(self)->MeshId{ + MeshId::new(self.0) + } +} +impl From for PhysicsMeshId{ + fn from(value:MeshId)->Self{ + Self::new(value.get()) + } +} +#[derive(Debug,Default,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +pub struct PhysicsSubmeshId(u32); +pub struct PhysicsMesh{ + data:PhysicsMeshData, + complete_mesh:PhysicsMeshTopology, + //Most objects in roblox maps are already convex, so the list length is 0 + //as soon as the mesh is divided into 2 submeshes, the list length jumps to 2. + //length 1 is unnecessary since the complete mesh would be a duplicate of the only submesh, but would still function properly + submeshes:Vec, +} +impl PhysicsMesh{ + pub fn unit_cube()->Self{ + //go go gadget debug print mesh + let data=PhysicsMeshData{ + faces:vec![ + Face{normal:vec3::raw_xyz( 4294967296, 0, 0),dot:Planar64::raw(4294967296)}, + Face{normal:vec3::raw_xyz( 0, 4294967296, 0),dot:Planar64::raw(4294967296)}, + Face{normal:vec3::raw_xyz( 0, 0, 4294967296),dot:Planar64::raw(4294967296)}, + Face{normal:vec3::raw_xyz(-4294967296, 0, 0),dot:Planar64::raw(4294967296)}, + Face{normal:vec3::raw_xyz( 0,-4294967296, 0),dot:Planar64::raw(4294967296)}, + Face{normal:vec3::raw_xyz( 0, 0,-4294967296),dot:Planar64::raw(4294967296)} + ], + verts:vec![ + Vert(vec3::raw_xyz( 4294967296,-4294967296,-4294967296)), + Vert(vec3::raw_xyz( 4294967296, 4294967296,-4294967296)), + Vert(vec3::raw_xyz( 4294967296, 4294967296, 4294967296)), + Vert(vec3::raw_xyz( 4294967296,-4294967296, 4294967296)), + Vert(vec3::raw_xyz(-4294967296, 4294967296,-4294967296)), + Vert(vec3::raw_xyz(-4294967296, 4294967296, 4294967296)), + Vert(vec3::raw_xyz(-4294967296,-4294967296, 4294967296)), + Vert(vec3::raw_xyz(-4294967296,-4294967296,-4294967296)) + ] + }; + let mesh_topology=PhysicsMeshTopology{ + faces:(0..data.faces.len() as u32).map(MeshFaceId::new).collect(), + verts:(0..data.verts.len() as u32).map(MeshVertId::new).collect(), + face_topology:vec![ + FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775808u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775809u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775810u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(3)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775812u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775813u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId(1)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId((9223372036854775814u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775816u64-(1<<63)+(1<<31)) as u32)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId((9223372036854775817u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(10)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId((9223372036854775815u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775818u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId((9223372036854775811u64-(1<<63)+(1<<31)) as u32)]}, + FaceRefs{edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId((9223372036854775819u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(9)]} + ], + edge_topology:vec![ + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(5)],verts:[SubmeshVertId(0),SubmeshVertId(1)]}, + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(1)],verts:[SubmeshVertId(1),SubmeshVertId(2)]}, + EdgeRefs{faces:[SubmeshFaceId(0),SubmeshFaceId(2)],verts:[SubmeshVertId(2),SubmeshVertId(3)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(0)],verts:[SubmeshVertId(0),SubmeshVertId(3)]}, + EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(5)],verts:[SubmeshVertId(1),SubmeshVertId(4)]}, + EdgeRefs{faces:[SubmeshFaceId(1),SubmeshFaceId(3)],verts:[SubmeshVertId(4),SubmeshVertId(5)]}, + EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(1)],verts:[SubmeshVertId(2),SubmeshVertId(5)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(2)],verts:[SubmeshVertId(3),SubmeshVertId(6)]}, + EdgeRefs{faces:[SubmeshFaceId(2),SubmeshFaceId(3)],verts:[SubmeshVertId(5),SubmeshVertId(6)]}, + EdgeRefs{faces:[SubmeshFaceId(3),SubmeshFaceId(5)],verts:[SubmeshVertId(4),SubmeshVertId(7)]}, + EdgeRefs{faces:[SubmeshFaceId(4),SubmeshFaceId(3)],verts:[SubmeshVertId(6),SubmeshVertId(7)]}, + EdgeRefs{faces:[SubmeshFaceId(5),SubmeshFaceId(4)],verts:[SubmeshVertId(0),SubmeshVertId(7)]} + ], + vert_topology:vec![ + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(4),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId((9223372036854775811u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775819u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775808u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId((9223372036854775812u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId(0),SubmeshDirectedEdgeId((9223372036854775809u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(1),SubmeshDirectedEdgeId((9223372036854775810u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775814u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(0),SubmeshFaceId(2),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(2),SubmeshDirectedEdgeId(3),SubmeshDirectedEdgeId((9223372036854775815u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(3),SubmeshFaceId(5),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(4),SubmeshDirectedEdgeId((9223372036854775817u64-(1<<63)+(1<<31)) as u32),SubmeshDirectedEdgeId((9223372036854775813u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(1)],edges:vec![SubmeshDirectedEdgeId(5),SubmeshDirectedEdgeId(6),SubmeshDirectedEdgeId((9223372036854775816u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(2),SubmeshFaceId(3),SubmeshFaceId(4)],edges:vec![SubmeshDirectedEdgeId(7),SubmeshDirectedEdgeId(8),SubmeshDirectedEdgeId((9223372036854775818u64-(1<<63)+(1<<31)) as u32)]}, + VertRefs{faces:vec![SubmeshFaceId(4),SubmeshFaceId(3),SubmeshFaceId(5)],edges:vec![SubmeshDirectedEdgeId(10),SubmeshDirectedEdgeId(11),SubmeshDirectedEdgeId(9)]} + ] + }; + Self{ + data, + complete_mesh:mesh_topology, + submeshes:Vec::new(), + } + } + pub fn unit_cylinder()->Self{ + Self::unit_cube() + } + #[inline] + pub const fn complete_mesh(&self)->&PhysicsMeshTopology{ + &self.complete_mesh + } + #[inline] + pub const fn complete_mesh_view(&self)->PhysicsMeshView{ + PhysicsMeshView{ + data:&self.data, + topology:self.complete_mesh(), + } + } + #[inline] + pub fn submeshes(&self)->&[PhysicsMeshTopology]{ + //the complete mesh is already a convex mesh when len()==0, len()==1 is invalid but will still work + if self.submeshes.len()==0{ + std::slice::from_ref(&self.complete_mesh) + }else{ + &self.submeshes.as_slice() + } + } + #[inline] + pub fn submesh_view(&self,submesh_id:PhysicsSubmeshId)->PhysicsMeshView{ + PhysicsMeshView{ + data:&self.data, + topology:&self.submeshes()[submesh_id.get() as usize], + } + } + pub fn submesh_views(&self)->impl Iterator{ + self.submeshes().iter().map(|topology|PhysicsMeshView{ + data:&self.data, + topology, + }) + } +} +//mesh builder code #[derive(Default,Clone)] struct VertRefGuy{ - edges:std::collections::HashSet, - faces:std::collections::HashSet, + edges:HashSet, + faces:HashSet, } #[derive(Clone,Hash,Eq,PartialEq)] -struct EdgeRefVerts([VertId;2]); +struct EdgeRefVerts([SubmeshVertId;2]); impl EdgeRefVerts{ - fn new(v0:VertId,v1:VertId)->(Self,bool){ + const fn new(v0:SubmeshVertId,v1:SubmeshVertId)->(Self,bool){ (if v0.0Self{ - Self([FaceId(0);2]) + const fn new()->Self{ + Self([SubmeshFaceId(0);2]) } - fn push(&mut self,i:usize,face_id:FaceId){ + fn push(&mut self,i:usize,face_id:SubmeshFaceId){ self.0[i]=face_id; } } -struct FaceRefEdges(Vec); +struct FaceRefEdges(Vec); #[derive(Default)] struct EdgePool{ edge_guys:Vec<(EdgeRefVerts,EdgeRefFaces)>, - edge_id_from_guy:std::collections::HashMap, + edge_id_from_guy:HashMap, } impl EdgePool{ - fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,EdgeId){ + fn push(&mut self,edge_ref_verts:EdgeRefVerts)->(&mut EdgeRefFaces,SubmeshEdgeId){ let edge_id=if let Some(&edge_id)=self.edge_id_from_guy.get(&edge_ref_verts){ edge_id }else{ - let edge_id=self.edge_guys.len(); + let edge_id=SubmeshEdgeId::new(self.edge_guys.len() as u32); self.edge_guys.push((edge_ref_verts.clone(),EdgeRefFaces::new())); self.edge_id_from_guy.insert(edge_ref_verts,edge_id); edge_id }; - (&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id)}.1,EdgeId(edge_id)) - } -} -impl From<&crate::model::IndexedModel> for PhysicsMesh{ - fn from(indexed_model:&crate::model::IndexedModel)->Self{ - assert!(indexed_model.unique_pos.len()!=0,"Mesh cannot have 0 vertices"); - let verts=indexed_model.unique_pos.iter().map(|v|Vert(v.clone())).collect(); - let mut vert_ref_guys=vec![VertRefGuy::default();indexed_model.unique_pos.len()]; - let mut edge_pool=EdgePool::default(); - let mut face_i=0; - let mut faces=Vec::new(); - let mut face_ref_guys=Vec::new(); - for group in indexed_model.groups.iter(){for poly in group.polys.iter(){ - let face_id=FaceId(face_i); - //one face per poly - let mut normal=Planar64Vec3::ZERO; - let len=poly.vertices.len(); - let face_edges=poly.vertices.iter().enumerate().map(|(i,&vert_id)|{ - let vert0_id=indexed_model.unique_vertices[vert_id as usize].pos as usize; - let vert1_id=indexed_model.unique_vertices[poly.vertices[(i+1)%len] as usize].pos as usize; - //https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method) - let v0=indexed_model.unique_pos[vert0_id]; - let v1=indexed_model.unique_pos[vert1_id]; - normal+=Planar64Vec3::new( - (v0.y()-v1.y())*(v0.z()+v1.z()), - (v0.z()-v1.z())*(v0.x()+v1.x()), - (v0.x()-v1.x())*(v0.y()+v1.y()), - ); - //get/create edge and push face into it - let (edge_ref_verts,is_sorted)=EdgeRefVerts::new(VertId(vert0_id),VertId(vert1_id)); - let (edge_ref_faces,edge_id)=edge_pool.push(edge_ref_verts); - //polygon vertices as assumed to be listed clockwise - //populate the edge face on the left or right depending on how the edge vertices got sorted - edge_ref_faces.push(!is_sorted as usize,face_id); - //index edges & face into vertices - { - let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(vert0_id)}; - vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); - vert_ref_guy.faces.insert(face_id); - unsafe{vert_ref_guys.get_unchecked_mut(vert1_id)}.edges.insert(edge_id.as_directed(!is_sorted)); - } - //return directed_edge_id - edge_id.as_directed(is_sorted) - }).collect(); - //choose precision loss randomly idk - normal=normal/len as i64; - let mut dot=Planar64::ZERO; - for &v in poly.vertices.iter(){ - dot+=normal.dot(indexed_model.unique_pos[indexed_model.unique_vertices[v as usize].pos as usize]); - } - faces.push(Face{normal,dot:dot/len as i64}); - face_ref_guys.push(FaceRefEdges(face_edges)); - face_i+=1; - }} - //conceivably faces, edges, and vertices exist now - Self{ - faces, - verts, - face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{ - FaceRefs{edges:face_ref_guy.0} - }).collect(), - edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)| - EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0} - ).collect(), - vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy| - VertRefs{ - edges:vert_ref_guy.edges.into_iter().collect(), - faces:vert_ref_guy.faces.into_iter().collect(), - } - ).collect(), - } + (&mut unsafe{self.edge_guys.get_unchecked_mut(edge_id.get() as usize)}.1,edge_id) } } -impl PhysicsMesh{ - pub fn verts<'a>(&'a self)->impl Iterator+'a{ - self.verts.iter().map(|Vert(pos)|*pos) +#[derive(Debug)] +pub enum PhysicsMeshError{ + ZeroVertices, + NoPhysicsGroups, +} +impl std::fmt::Display for PhysicsMeshError{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f,"{self:?}") } } -impl MeshQuery for PhysicsMesh{ - fn face_nd(&self,face_id:FaceId)->(Planar64Vec3,Planar64){ - (self.faces[face_id.0].normal,self.faces[face_id.0].dot) +impl std::error::Error for PhysicsMeshError{} + +impl TryFrom<&model::Mesh> for PhysicsMesh{ + type Error=PhysicsMeshError; + fn try_from(mesh:&model::Mesh)->Result{ + if mesh.unique_pos.len()==0{ + return Err(PhysicsMeshError::ZeroVertices); + } + let verts=mesh.unique_pos.iter().copied().map(Vert).collect(); + //TODO: fix submeshes + //flat map mesh.physics_groups[$1].groups.polys()[$2] as face_id + //lower face_id points to upper face_id + //the same face is not allowed to be in multiple polygon groups + let mut faces=Vec::new(); + let mut face_id_from_face=HashMap::new(); + let mut mesh_topologies:Vec=mesh.physics_groups.iter().map(|physics_group|{ + //construct submesh + let mut submesh_faces=Vec::new();//these contain a map from submeshId->meshId + let mut submesh_verts=Vec::new(); + let mut submesh_vert_id_from_mesh_vert_id=HashMap::::new(); + //lazy closure + let mut get_submesh_vert_id=|vert_id:MeshVertId|{ + if let Some(&submesh_vert_id)=submesh_vert_id_from_mesh_vert_id.get(&vert_id){ + submesh_vert_id + }else{ + let submesh_vert_id=SubmeshVertId::new(submesh_verts.len() as u32); + submesh_verts.push(vert_id); + submesh_vert_id_from_mesh_vert_id.insert(vert_id,submesh_vert_id); + submesh_vert_id + } + }; + let mut edge_pool=EdgePool::default(); + let mut vert_ref_guys=vec![VertRefGuy::default();mesh.unique_pos.len()]; + let mut face_ref_guys=Vec::new(); + for polygon_group_id in &physics_group.groups{ + let polygon_group=&mesh.polygon_groups[polygon_group_id.get() as usize]; + for poly_vertices in polygon_group.polys(){ + let submesh_face_id=SubmeshFaceId::new(submesh_faces.len() as u32); + //one face per poly + let mut normal=Vector3::new([Fixed::ZERO,Fixed::ZERO,Fixed::ZERO]); + let len=poly_vertices.len(); + let face_edges=poly_vertices.into_iter().enumerate().map(|(i,vert_id)|{ + let vert0_id=MeshVertId::new(mesh.unique_vertices[vert_id.get() as usize].pos.get() as u32); + let vert1_id=MeshVertId::new(mesh.unique_vertices[poly_vertices[(i+1)%len].get() as usize].pos.get() as u32); + //index submesh verts + let submesh_vert0_id=get_submesh_vert_id(vert0_id); + let submesh_vert1_id=get_submesh_vert_id(vert1_id); + //https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal (Newell's Method) + let v0=mesh.unique_pos[vert0_id.get() as usize]; + let v1=mesh.unique_pos[vert1_id.get() as usize]; + normal+=Vector3::new([ + (v0.y-v1.y)*(v0.z+v1.z), + (v0.z-v1.z)*(v0.x+v1.x), + (v0.x-v1.x)*(v0.y+v1.y), + ]); + //get/create edge and push face into it + let (edge_ref_verts,is_sorted)=EdgeRefVerts::new(submesh_vert0_id,submesh_vert1_id); + let (edge_ref_faces,edge_id)=edge_pool.push(edge_ref_verts); + //polygon vertices as assumed to be listed clockwise + //populate the edge face on the left or right depending on how the edge vertices got sorted + edge_ref_faces.push(!is_sorted as usize,submesh_face_id); + //index edges & face into vertices + { + let vert_ref_guy=unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert0_id.get() as usize)}; + vert_ref_guy.edges.insert(edge_id.as_directed(is_sorted)); + vert_ref_guy.faces.insert(submesh_face_id); + unsafe{vert_ref_guys.get_unchecked_mut(submesh_vert1_id.get() as usize)}.edges.insert(edge_id.as_directed(!is_sorted)); + } + //return directed_edge_id + edge_id.as_directed(is_sorted) + }).collect(); + let mut dot=Fixed::ZERO; + // find the average dot + for &v in poly_vertices{ + dot+=normal.dot(mesh.unique_pos[mesh.unique_vertices[v.get() as usize].pos.get() as usize]); + } + //assume face hash is stable, and there are no flush faces... + let face=Face{ + normal:(normal/len as i64).divide().fix_1(), + dot:(dot/(len*len) as i64).fix_1(), + }; + let face_id=match face_id_from_face.get(&face){ + Some(&face_id)=>face_id, + None=>{ + let face_id=MeshFaceId::new(faces.len() as u32); + face_id_from_face.insert(face.clone(),face_id); + faces.push(face); + face_id + } + }; + submesh_faces.push(face_id); + face_ref_guys.push(FaceRefEdges(face_edges)); + } + } + PhysicsMeshTopology{ + faces:submesh_faces, + verts:submesh_verts, + face_topology:face_ref_guys.into_iter().map(|face_ref_guy|{ + FaceRefs{edges:face_ref_guy.0} + }).collect(), + edge_topology:edge_pool.edge_guys.into_iter().map(|(edge_ref_verts,edge_ref_faces)| + EdgeRefs{faces:edge_ref_faces.0,verts:edge_ref_verts.0} + ).collect(), + vert_topology:vert_ref_guys.into_iter().map(|vert_ref_guy| + VertRefs{ + edges:vert_ref_guy.edges.into_iter().collect(), + faces:vert_ref_guy.faces.into_iter().collect(), + } + ).collect(), + } + }).collect(); + Ok(Self{ + data:PhysicsMeshData{ + faces, + verts, + }, + complete_mesh:mesh_topologies.pop().ok_or(PhysicsMeshError::NoPhysicsGroups)?, + submeshes:mesh_topologies, + }) + } +} + +pub struct PhysicsMeshView<'a>{ + data:&'a PhysicsMeshData, + topology:&'a PhysicsMeshTopology, +} +impl MeshQuery for PhysicsMeshView<'_>{ + type Normal=Planar64Vec3; + type Offset=Planar64; + fn face_nd(&self,face_id:SubmeshFaceId)->(Planar64Vec3,Planar64){ + let face_idx=self.topology.faces[face_id.get() as usize].get() as usize; + (self.data.faces[face_idx].normal,self.data.faces[face_idx].dot) } //ideally I never calculate the vertex position, but I have to for the graphical meshes... - fn vert(&self,vert_id:VertId)->Planar64Vec3{ - self.verts[vert_id.0].0 + fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ + let vert_idx=self.topology.verts[vert_id.get() as usize].get() as usize; + self.data.verts[vert_idx].0 } - fn face_edges(&self,face_id:FaceId)->Cow>{ - Cow::Borrowed(&self.face_topology[face_id.0].edges) + fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ + Cow::Borrowed(&self.topology.face_topology[face_id.get() as usize].edges) } - fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ - Cow::Borrowed(&self.edge_topology[edge_id.0].faces) + fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].faces) } - fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ - Cow::Borrowed(&self.edge_topology[edge_id.0].verts) + fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ + Cow::Borrowed(&self.topology.edge_topology[edge_id.get() as usize].verts) } - fn vert_edges(&self,vert_id:VertId)->Cow>{ - Cow::Borrowed(&self.vert_topology[vert_id.0].edges) + fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].edges) } - fn vert_faces(&self,vert_id:VertId)->Cow>{ - Cow::Borrowed(&self.vert_topology[vert_id.0].faces) + fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ + Cow::Borrowed(&self.topology.vert_topology[vert_id.get() as usize].faces) + } +} + +pub struct PhysicsMeshTransform{ + pub vertex:integer::Planar64Affine3, + pub normal:integer::mat3::Matrix3>, + pub det:Fixed<3,96>, +} +impl PhysicsMeshTransform{ + pub fn new(transform:integer::Planar64Affine3)->Self{ + Self{ + normal:transform.matrix3.adjugate().transpose(), + det:transform.matrix3.det(), + vertex:transform, + } } } pub struct TransformedMesh<'a>{ - mesh:&'a PhysicsMesh, - transform:&'a integer::Planar64Affine3, - normal_transform:&'a integer::Planar64Mat3, - transform_det:Planar64, + view:PhysicsMeshView<'a>, + transform:&'a PhysicsMeshTransform, } impl TransformedMesh<'_>{ - pub fn new<'a>( - mesh:&'a PhysicsMesh, - transform:&'a integer::Planar64Affine3, - normal_transform:&'a integer::Planar64Mat3, - transform_det:Planar64, - )->TransformedMesh<'a>{ + pub const fn new<'a>( + view:PhysicsMeshView<'a>, + transform:&'a PhysicsMeshTransform, + )->TransformedMesh<'a>{ TransformedMesh{ - mesh, + view, transform, - normal_transform, - transform_det, } } - fn farthest_vert(&self,dir:Planar64Vec3)->VertId{ - let mut best_dot=Planar64::MIN; - let mut best_vert=VertId(0); - for (i,vert) in self.mesh.verts.iter().enumerate(){ - let p=self.transform.transform_point3(vert.0); - let d=dir.dot(p); - if best_dot(&'a self)->impl Iterator>>+'a{ + self.view.data.verts.iter().map(|&Vert(pos)|self.transform.vertex.transform_point3(pos)) + } + fn farthest_vert(&self,dir:Planar64Vec3)->SubmeshVertId{ + //this happens to be well-defined. there are no virtual virtices + SubmeshVertId::new( + self.view.topology.verts.iter() + .enumerate() + .max_by_key(|(_,&vert_id)| + dir.dot(self.transform.vertex.transform_point3(self.view.data.verts[vert_id.get() as usize].0)) + ) + //assume there is more than zero vertices. + .unwrap().0 as u32 + ) } } -impl MeshQuery for TransformedMesh<'_>{ - fn face_nd(&self,face_id:FaceId)->(Planar64Vec3,Planar64){ - let (n,d)=self.mesh.face_nd(face_id); - let transformed_n=*self.normal_transform*n; - let transformed_d=d+transformed_n.dot(self.transform.translation)/self.transform_det; - (transformed_n/self.transform_det,transformed_d) +impl MeshQuery for TransformedMesh<'_>{ + type Normal=Vector3>; + type Offset=Fixed<4,128>; + fn face_nd(&self,face_id:SubmeshFaceId)->(Self::Normal,Self::Offset){ + let (n,d)=self.view.face_nd(face_id); + let transformed_n=self.transform.normal*n; + let transformed_d=d*self.transform.det+transformed_n.dot(self.transform.vertex.translation); + (transformed_n,transformed_d) } - fn vert(&self,vert_id:VertId)->Planar64Vec3{ - self.transform.transform_point3(self.mesh.vert(vert_id)) + fn vert(&self,vert_id:SubmeshVertId)->Planar64Vec3{ + self.transform.vertex.transform_point3(self.view.vert(vert_id)).fix_1() } #[inline] - fn face_edges(&self,face_id:FaceId)->Cow>{ - self.mesh.face_edges(face_id) + fn face_edges(&self,face_id:SubmeshFaceId)->Cow>{ + self.view.face_edges(face_id) } #[inline] - fn edge_faces(&self,edge_id:EdgeId)->Cow<[FaceId;2]>{ - self.mesh.edge_faces(edge_id) + fn edge_faces(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshFaceId;2]>{ + self.view.edge_faces(edge_id) } #[inline] - fn edge_verts(&self,edge_id:EdgeId)->Cow<[VertId;2]>{ - self.mesh.edge_verts(edge_id) + fn edge_verts(&self,edge_id:SubmeshEdgeId)->Cow<[SubmeshVertId;2]>{ + self.view.edge_verts(edge_id) } #[inline] - fn vert_edges(&self,vert_id:VertId)->Cow>{ - self.mesh.vert_edges(vert_id) + fn vert_edges(&self,vert_id:SubmeshVertId)->Cow>{ + self.view.vert_edges(vert_id) } #[inline] - fn vert_faces(&self,vert_id:VertId)->Cow>{ - self.mesh.vert_faces(vert_id) + fn vert_faces(&self,vert_id:SubmeshVertId)->Cow>{ + self.view.vert_faces(vert_id) } } @@ -305,14 +533,14 @@ impl MeshQuery for TransformedMesh<'_>{ //(face,vertex) //(edge,edge) //(vertex,face) -#[derive(Clone,Copy)] +#[derive(Clone,Copy,Debug)] pub enum MinkowskiVert{ - VertVert(VertId,VertId), + VertVert(SubmeshVertId,SubmeshVertId), } -#[derive(Clone,Copy)] +#[derive(Clone,Copy,Debug)] pub enum MinkowskiEdge{ - VertEdge(VertId,EdgeId), - EdgeVert(EdgeId,VertId), + VertEdge(SubmeshVertId,SubmeshEdgeId), + EdgeVert(SubmeshEdgeId,SubmeshVertId), //EdgeEdge when edges are parallel } impl UndirectedEdge for MinkowskiEdge{ @@ -324,10 +552,10 @@ impl UndirectedEdge for MinkowskiEdge{ } } } -#[derive(Clone,Copy)] +#[derive(Clone,Copy,Debug)] pub enum MinkowskiDirectedEdge{ - VertEdge(VertId,DirectedEdgeId), - EdgeVert(DirectedEdgeId,VertId), + VertEdge(SubmeshVertId,SubmeshDirectedEdgeId), + EdgeVert(SubmeshDirectedEdgeId,SubmeshVertId), //EdgeEdge when edges are parallel } impl DirectedEdge for MinkowskiDirectedEdge{ @@ -345,22 +573,23 @@ impl DirectedEdge for MinkowskiDirectedEdge{ } } } -#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +#[derive(Clone,Copy,Debug,Hash,Eq,PartialEq)] pub enum MinkowskiFace{ - VertFace(VertId,FaceId), - EdgeEdge(EdgeId,EdgeId,bool), - FaceVert(FaceId,VertId), + VertFace(SubmeshVertId,SubmeshFaceId), + EdgeEdge(SubmeshEdgeId,SubmeshEdgeId,bool), + FaceVert(SubmeshFaceId,SubmeshVertId), //EdgeFace //FaceEdge //FaceFace } pub struct MinkowskiMesh<'a>{ - mesh0:&'a TransformedMesh<'a>, - mesh1:&'a TransformedMesh<'a>, + mesh0:TransformedMesh<'a>, + mesh1:TransformedMesh<'a>, } //infinity fev algorithm state transition +#[derive(Debug)] enum Transition{ Done,//found closest vert, no edges are better Vert(MinkowskiVert),//transition to vert @@ -370,8 +599,10 @@ enum EV{ Edge(MinkowskiEdge), } +pub type GigaTime=Ratio,Fixed<4,128>>; + impl MinkowskiMesh<'_>{ - pub fn minkowski_sum<'a>(mesh0:&'a TransformedMesh,mesh1:&'a TransformedMesh)->MinkowskiMesh<'a>{ + pub fn minkowski_sum<'a>(mesh0:TransformedMesh<'a>,mesh1:TransformedMesh<'a>)->MinkowskiMesh<'a>{ MinkowskiMesh{ mesh0, mesh1, @@ -380,7 +611,7 @@ impl MinkowskiMesh<'_>{ fn farthest_vert(&self,dir:Planar64Vec3)->MinkowskiVert{ MinkowskiVert::VertVert(self.mesh0.farthest_vert(dir),self.mesh1.farthest_vert(-dir)) } - fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{ + fn next_transition_vert(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->Transition{ let mut best_transition=Transition::Done; for &directed_edge_id in self.vert_edges(vert_id).iter(){ let edge_n=self.directed_edge_n(directed_edge_id); @@ -390,7 +621,7 @@ impl MinkowskiMesh<'_>{ let test_vert_id=edge_verts[directed_edge_id.parity() as usize]; //test if it's closer let diff=point-self.vert(test_vert_id); - if zeroes::zeroes1(edge_n.dot(diff),edge_n.dot(infinity_dir)).len()==0{ + if edge_n.dot(infinity_dir).is_zero(){ let distance_squared=diff.dot(diff); if distance_squared<*best_distance_squared{ best_transition=Transition::Vert(test_vert_id); @@ -400,21 +631,21 @@ impl MinkowskiMesh<'_>{ } best_transition } - fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Planar64,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ + fn final_ev(&self,vert_id:MinkowskiVert,best_distance_squared:&mut Fixed<2,64>,infinity_dir:Planar64Vec3,point:Planar64Vec3)->EV{ let mut best_transition=EV::Vert(vert_id); let diff=point-self.vert(vert_id); for &directed_edge_id in self.vert_edges(vert_id).iter(){ let edge_n=self.directed_edge_n(directed_edge_id); //is boundary uncrossable by a crawl from infinity //check if time of collision is outside Time::MIN..Time::MAX - let d=edge_n.dot(diff); - if zeroes::zeroes1(d,edge_n.dot(infinity_dir)).len()==0{ + if edge_n.dot(infinity_dir).is_zero(){ + let d=edge_n.dot(diff); //test the edge let edge_nn=edge_n.dot(edge_n); - if Planar64::ZERO<=d&&d<=edge_nn{ + if !d.is_negative()&&d<=edge_nn{ let distance_squared={ let c=diff.cross(edge_n); - c.dot(c)/edge_nn + (c.dot(c)/edge_nn).divide().fix_2() }; if distance_squared<=*best_distance_squared{ best_transition=EV::Edge(directed_edge_id.as_undirected()); @@ -460,7 +691,7 @@ impl MinkowskiMesh<'_>{ let boundary_d=boundary_n.dot(delta_pos); //check if time of collision is outside Time::MIN..Time::MAX //infinity_dir can always be treated as a velocity - if (boundary_d)<=Planar64::ZERO&&zeroes::zeroes1(boundary_d,boundary_n.dot(infinity_dir)*2).len()==0{ + if !boundary_d.is_positive()&&boundary_n.dot(infinity_dir).is_zero(){ //both faces cannot pass this condition, return early if one does. return FEV::::Face(face_id); } @@ -474,15 +705,16 @@ impl MinkowskiMesh<'_>{ let infinity_fev=self.infinity_fev(-dir,infinity_body.position); //a line is simpler to solve than a parabola infinity_body.velocity=dir; - infinity_body.acceleration=Planar64Vec3::ZERO; + infinity_body.acceleration=vec3::ZERO; //crawl in from negative infinity along a tangent line to get the closest fev - match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN,infinity_body.time){ + // TODO: change crawl_fev args to delta time? Optional values? + match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){ crate::face_crawler::CrawlResult::Miss(fev)=>Some(fev), crate::face_crawler::CrawlResult::Hit(_,_)=>None, } }) } - pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,integer::Time)>{ + pub fn predict_collision_in(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{ self.closest_fev_not_inside(relative_body.clone()).map_or(None,|fev|{ //continue forwards along the body parabola match crate::face_crawler::crawl_fev(fev,self,relative_body,relative_body.time,time_limit){ @@ -491,7 +723,7 @@ impl MinkowskiMesh<'_>{ } }) } - pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,integer::Time)>{ + pub fn predict_collision_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time)->Option<(MinkowskiFace,GigaTime)>{ //create an extrapolated body at time_limit let infinity_body=crate::physics::Body::new( relative_body.extrapolated_position(time_limit), @@ -507,10 +739,13 @@ impl MinkowskiMesh<'_>{ } }) } - pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,integer::Time)>{ + pub fn predict_collision_face_out(&self,relative_body:&crate::physics::Body,time_limit:integer::Time,contact_face_id:MinkowskiFace)->Option<(MinkowskiEdge,GigaTime)>{ //no algorithm needed, there is only one state and two cases (Edge,None) //determine when it passes an edge ("sliding off" case) - let mut best_time=time_limit; + let mut best_time={ + let r=(time_limit-relative_body.time).to_ratio(); + Ratio::new(r.num.fix_4(),r.den.fix_4()) + }; let mut best_edge=None; let face_n=self.face_nd(contact_face_id).0; for &directed_edge_id in self.face_edges(contact_face_id).iter(){ @@ -520,10 +755,10 @@ impl MinkowskiMesh<'_>{ let verts=self.edge_verts(directed_edge_id.as_undirected()); let d=n.dot(self.vert(verts[0])+self.vert(verts[1])); //WARNING! d outside of *2 - for t in zeroes::zeroes2((n.dot(relative_body.position))*2-d,n.dot(relative_body.velocity)*2,n.dot(relative_body.acceleration)){ - let t=relative_body.time+integer::Time::from(t); - if relative_body.time::zeroes2(((n.dot(relative_body.position))*2-d).fix_4(),n.dot(relative_body.velocity).fix_4()*2,n.dot(relative_body.acceleration).fix_4()){ + if Ratio::new(Planar64::ZERO,Planar64::EPSILON).le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(relative_body.extrapolated_velocity_ratio_dt(dt)).is_negative(){ + best_time=dt; best_edge=Some(directed_edge_id); break; } @@ -531,9 +766,28 @@ impl MinkowskiMesh<'_>{ } best_edge.map(|e|(e.as_undirected(),best_time)) } + fn infinity_in(&self,infinity_body:crate::physics::Body)->Option<(MinkowskiFace,GigaTime)>{ + let infinity_fev=self.infinity_fev(-infinity_body.velocity,infinity_body.position); + match crate::face_crawler::crawl_fev(infinity_fev,self,&infinity_body,integer::Time::MIN/4,infinity_body.time){ + crate::face_crawler::CrawlResult::Miss(_)=>None, + crate::face_crawler::CrawlResult::Hit(face,time)=>Some((face,time)), + } + } + pub fn is_point_in_mesh(&self,point:Planar64Vec3)->bool{ + let infinity_body=crate::physics::Body::new(point,vec3::Y,vec3::ZERO,integer::Time::ZERO); + //movement must escape the mesh forwards and backwards in time, + //otherwise the point is not inside the mesh + self.infinity_in(infinity_body) + .is_some_and(|_| + self.infinity_in(-infinity_body) + .is_some() + ) + } } impl MeshQuery for MinkowskiMesh<'_>{ - fn face_nd(&self,face_id:MinkowskiFace)->(Planar64Vec3,Planar64){ + type Normal=Vector3>; + type Offset=Fixed<4,128>; + fn face_nd(&self,face_id:MinkowskiFace)->(Self::Normal,Self::Offset){ match face_id{ MinkowskiFace::VertFace(v0,f1)=>{ let (n,d)=self.mesh1.face_nd(f1); @@ -547,7 +801,7 @@ impl MeshQuery for MinkowskiM let n=edge0_n.cross(edge1_n); let e0d=n.dot(self.mesh0.vert(e0v0)+self.mesh0.vert(e0v1)); let e1d=n.dot(self.mesh1.vert(e1v0)+self.mesh1.vert(e1v1)); - (n*(parity as i64*4-2),(e0d-e1d)*(parity as i64*2-1)) + ((n*(parity as i64*4-2)).fix_3(),((e0d-e1d)*(parity as i64*2-1)).fix_4()) }, MinkowskiFace::FaceVert(f0,v1)=>{ let (n,d)=self.mesh0.face_nd(f0); @@ -596,17 +850,18 @@ impl MeshQuery for MinkowskiM let &[e1f0,e1f1]=self.mesh1.edge_faces(e1).borrow(); Cow::Owned([(e1f1,false),(e1f0,true)].map(|(edge_face_id1,face_parity)|{ let mut best_edge=None; - let mut best_d=Planar64::ZERO; + let mut best_d:Ratio,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE); let edge_face1_n=self.mesh1.face_nd(edge_face_id1).0; let edge_face1_nn=edge_face1_n.dot(edge_face1_n); for &directed_edge_id0 in v0e.iter(){ let edge0_n=self.mesh0.directed_edge_n(directed_edge_id0); //must be behind other face. let d=edge_face1_n.dot(edge0_n); - if d for MinkowskiM let &[e0f0,e0f1]=self.mesh0.edge_faces(e0).borrow(); Cow::Owned([(e0f0,true),(e0f1,false)].map(|(edge_face_id0,face_parity)|{ let mut best_edge=None; - let mut best_d=Planar64::ZERO; + let mut best_d:Ratio,Fixed<8,256>>=Ratio::new(Fixed::ZERO,Fixed::ONE); let edge_face0_n=self.mesh0.face_nd(edge_face_id0).0; let edge_face0_nn=edge_face0_n.dot(edge_face0_n); for &directed_edge_id1 in v1e.iter(){ let edge1_n=self.mesh1.directed_edge_n(directed_edge_id1); let d=edge_face0_n.dot(edge1_n); - if d for MinkowskiM //detect shared volume when the other mesh is mirrored along a test edge dir let v0f=self.mesh0.vert_faces(v0); let v1f=self.mesh1.vert_faces(v1); - let v0f_n:Vec=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect(); - let v1f_n:Vec=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect(); + let v0f_n:Vec<_>=v0f.iter().map(|&face_id|self.mesh0.face_nd(face_id).0).collect(); + let v1f_n:Vec<_>=v1f.iter().map(|&face_id|self.mesh1.face_nd(face_id).0).collect(); let the_len=v0f.len()+v1f.len(); for &directed_edge_id in self.mesh0.vert_edges(v0).iter(){ let n=self.mesh0.directed_edge_n(directed_edge_id); let nn=n.dot(n); + // TODO: there's gotta be a better way to do this //make a set of faces let mut face_normals=Vec::with_capacity(the_len); //add mesh0 faces as-is face_normals.clone_from(&v0f_n); for face_n in &v1f_n{ //add reflected mesh1 faces - face_normals.push(*face_n-n*(face_n.dot(n)*2/nn)); + face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3()); } if is_empty_volume(face_normals){ edges.push(MinkowskiDirectedEdge::EdgeVert(directed_edge_id,v1)); @@ -693,7 +949,7 @@ impl MeshQuery for MinkowskiM let mut face_normals=Vec::with_capacity(the_len); face_normals.clone_from(&v1f_n); for face_n in &v0f_n{ - face_normals.push(*face_n-n*(face_n.dot(n)*2/nn)); + face_normals.push(*face_n-(n*face_n.dot(n)*2/nn).divide().fix_3()); } if is_empty_volume(face_normals){ edges.push(MinkowskiDirectedEdge::VertEdge(v0,directed_edge_id)); @@ -708,7 +964,7 @@ impl MeshQuery for MinkowskiM } } -fn is_empty_volume(normals:Vec)->bool{ +fn is_empty_volume(normals:Vec>>)->bool{ let len=normals.len(); for i in 0..len-1{ for j in i+1..len{ @@ -716,9 +972,10 @@ fn is_empty_volume(normals:Vec)->bool{ let mut d_comp=None; for k in 0..len{ if k!=i&&k!=j{ - let d=n.dot(normals[k]); + let d=n.dot(normals[k]).is_negative(); if let Some(comp)=&d_comp{ - if *comp*d)->bool{ #[test] fn test_is_empty_volume(){ - assert!(!is_empty_volume([Planar64Vec3::X,Planar64Vec3::Y,Planar64Vec3::Z].to_vec())); - assert!(is_empty_volume([Planar64Vec3::X,Planar64Vec3::Y,Planar64Vec3::Z,Planar64Vec3::NEG_X].to_vec())); + assert!(!is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3()].to_vec())); + assert!(is_empty_volume([vec3::X.fix_3(),vec3::Y.fix_3(),vec3::Z.fix_3(),vec3::NEG_X.fix_3()].to_vec())); } #[test] fn build_me_a_cube(){ - let unit_cube=crate::primitives::unit_cube(); - let mesh=PhysicsMesh::from(&unit_cube); + let mesh=PhysicsMesh::unit_cube(); //println!("mesh={:?}",mesh); -} \ No newline at end of file +} diff --git a/src/physics.rs b/src/physics.rs index cccb4f3..956136f 100644 --- a/src/physics.rs +++ b/src/physics.rs @@ -1,44 +1,42 @@ -use crate::model_physics::{PhysicsMesh,TransformedMesh,MeshQuery}; +use std::collections::{HashMap,HashSet}; +use crate::model_physics::{self,PhysicsMesh,PhysicsMeshTransform,TransformedMesh,MeshQuery,PhysicsMeshId,PhysicsSubmeshId}; use strafesnet_common::bvh; +use strafesnet_common::map; +use strafesnet_common::run; use strafesnet_common::aabb; +use strafesnet_common::model::{MeshId,ModelId}; +use strafesnet_common::mouse::MouseState; +use strafesnet_common::gameplay_attributes::{self,CollisionAttributesId}; +use strafesnet_common::gameplay_modes::{self,StageId}; +use strafesnet_common::gameplay_style::{self,StyleModifiers}; +use strafesnet_common::controls_bitflag::Controls; use strafesnet_common::instruction::{self,InstructionEmitter,InstructionConsumer,TimedInstruction}; -use strafesnet_common::integer::{self,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64,Ratio64Vec2}; +use strafesnet_common::integer::{self,vec3,mat3,Time,Planar64,Planar64Vec3,Planar64Mat3,Angle32,Ratio64Vec2}; +use gameplay::ModeState; +//external influence +//this is how you influence the physics from outside +use strafesnet_common::physics::Instruction as PhysicsInputInstruction; + +//internal influence +//when the physics asks itself what happens next, this is how it's represented #[derive(Debug)] -pub enum PhysicsInstruction { - CollisionStart(Collision), - CollisionEnd(Collision), +enum PhysicsInternalInstruction{ + CollisionStart(Collision,model_physics::GigaTime), + CollisionEnd(Collision,model_physics::GigaTime), StrafeTick, ReachWalkTargetVelocity, // Water, - // Spawn( - // Option, - // bool,//true = Trigger; false = teleport - // bool,//true = Force - // ) - //InputInstructions conditionally activate RefreshWalkTarget (by doing what SetWalkTargetVelocity used to do and then flagging it) - Input(PhysicsInputInstruction), } #[derive(Debug)] -pub enum PhysicsInputInstruction { - ReplaceMouse(MouseState,MouseState), - SetNextMouse(MouseState), - SetMoveRight(bool), - SetMoveUp(bool), - SetMoveBack(bool), - SetMoveLeft(bool), - SetMoveDown(bool), - SetMoveForward(bool), - SetJump(bool), - SetZoom(bool), - Reset, - Idle, - //Idle: there were no input events, but the simulation is safe to advance to this timestep - //for interpolation / networking / playback reasons, most playback heads will always want - //to be 1 instruction ahead to generate the next state for interpolation. +enum PhysicsInstruction{ + Internal(PhysicsInternalInstruction), + //InputInstructions conditionally activate RefreshWalkTarget + //(by doing what SetWalkTargetVelocity used to do and then flagging it) + Input(PhysicsInputInstruction), } -#[derive(Clone,Hash,Default)] +#[derive(Clone,Copy,Debug,Hash)] pub struct Body{ pub position:Planar64Vec3,//I64 where 2^32 = 1 u pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s @@ -57,189 +55,212 @@ impl std::ops::Neg for Body{ } } -//hey dumbass just use a delta -#[derive(Clone,Debug)] -pub struct MouseState { - pub pos: glam::IVec2, - pub time:Time, +#[derive(Clone,Debug,Default)] +pub struct InputState{ + mouse:MouseState, + next_mouse:MouseState, + controls:strafesnet_common::controls_bitflag::Controls, } -impl Default for MouseState{ - fn default() -> Self { - Self { - time:Time::ZERO, - pos:glam::IVec2::ZERO, - } +impl InputState{ + pub const fn get_next_mouse(&self)->&MouseState{ + &self.next_mouse } -} -impl MouseState { - pub fn lerp(&self,target:&MouseState,time:Time)->glam::IVec2 { - let m0=self.pos.as_i64vec2(); - let m1=target.pos.as_i64vec2(); + fn set_next_mouse(&mut self,next_mouse:MouseState){ + //I like your functions magic language + self.mouse=std::mem::replace(&mut self.next_mouse,next_mouse); + //equivalently: + //(self.next_mouse,self.mouse)=(next_mouse,self.next_mouse.clone()); + } + fn replace_mouse(&mut self,mouse:MouseState,next_mouse:MouseState){ + (self.next_mouse,self.mouse)=(next_mouse,mouse); + } + fn set_control(&mut self,control:Controls,state:bool){ + self.controls.set(control,state) + } + fn time_delta(&self)->Time{ + self.next_mouse.time-self.mouse.time + } + fn mouse_delta(&self)->glam::IVec2{ + self.next_mouse.pos-self.mouse.pos + } + fn lerp_delta(&self,time:Time)->glam::IVec2{ //these are deltas - let t1t=(target.time-time).nanos(); - let tt0=(time-self.time).nanos(); - let dt=(target.time-self.time).nanos(); - ((m0*t1t+m1*tt0)/dt).as_ivec2() + let dm=self.mouse_delta().as_i64vec2(); + let t=(time-self.mouse.time).nanos(); + let dt=self.time_delta().nanos(); + ((dm*t)/dt).as_ivec2() } } - +#[derive(Clone,Debug)] enum JumpDirection{ Exactly(Planar64Vec3), FromContactNormal, } -enum WalkEnum{ +impl JumpDirection{ + fn direction(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ + match self{ + JumpDirection::FromContactNormal=>contact_normal(models,hitbox_mesh,contact), + &JumpDirection::Exactly(dir)=>dir, + } + } +} +#[derive(Clone,Debug)] +enum TransientAcceleration{ Reached, - Transient(WalkTarget), + Reachable{ + acceleration:Planar64Vec3, + time:Time, + }, + //walk target will never be reached + Unreachable{ + acceleration:Planar64Vec3, + } } -struct WalkTarget{ - velocity:Planar64Vec3, - time:Time, -} -struct WalkState{ +#[derive(Clone,Debug)] +struct ContactMoveState{ jump_direction:JumpDirection, contact:ContactCollision, - state:WalkEnum, + target:TransientAcceleration, } -impl WalkEnum{ - //args going crazy - //(walk_enum,body.acceleration)=with_target_velocity(); - fn with_target_velocity(body:&Body,style:&StyleModifiers,velocity:Planar64Vec3,normal:&Planar64Vec3,speed:Planar64,normal_accel:Planar64)->(WalkEnum,Planar64Vec3){ - let mut target_diff=velocity-body.velocity; - //remove normal component - target_diff-=normal.clone()*(normal.dot(target_diff)/normal.dot(normal.clone())); - if target_diff==Planar64Vec3::ZERO{ - (WalkEnum::Reached,Planar64Vec3::ZERO) +impl TransientAcceleration{ + fn with_target_diff(target_diff:Planar64Vec3,accel:Planar64,time:Time)->Self{ + if target_diff==vec3::ZERO{ + TransientAcceleration::Reached }else{ //normal friction acceleration is clippedAcceleration.dot(normal)*friction - let diff_len=target_diff.length(); - let friction=if diff_lenSelf{ + let target_diff=target_velocity-body.velocity; + //precalculate accel + let accel=walk_settings.accel(target_diff,gravity); + Self::with_target_diff(target_diff,accel,body.time) + } + fn ladder(ladder_settings:&gameplay_style::LadderSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3)->Self{ + let target_diff=target_velocity-body.velocity; + let accel=ladder_settings.accel(target_diff,gravity); + Self::with_target_diff(target_diff,accel,body.time) + } + fn acceleration(&self)->Planar64Vec3{ + match self{ + TransientAcceleration::Reached=>vec3::ZERO, + &TransientAcceleration::Reachable{acceleration,time:_}=>acceleration, + &TransientAcceleration::Unreachable{acceleration}=>acceleration, } } } -impl WalkState{ - fn ground(body:&Body,style:&StyleModifiers,gravity:Planar64Vec3,velocity:Planar64Vec3,contact:ContactCollision,normal:&Planar64Vec3)->(Self,Planar64Vec3){ - let (walk_enum,a)=WalkEnum::with_target_velocity(body,style,velocity,&Planar64Vec3::Y,style.walk_speed,-normal.dot(gravity)); - (Self{ - state:walk_enum, +impl ContactMoveState{ + fn ground(walk_settings:&gameplay_style::WalkSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3,contact:ContactCollision)->Self{ + Self{ + target:TransientAcceleration::ground(walk_settings,body,gravity,target_velocity), contact, - jump_direction:JumpDirection::Exactly(Planar64Vec3::Y), - },a) + jump_direction:JumpDirection::Exactly(vec3::Y), + } } - fn ladder(body:&Body,style:&StyleModifiers,gravity:Planar64Vec3,velocity:Planar64Vec3,contact:ContactCollision,normal:&Planar64Vec3)->(Self,Planar64Vec3){ - let (walk_enum,a)=WalkEnum::with_target_velocity(body,style,velocity,normal,style.ladder_speed,style.ladder_accel); - (Self{ - state:walk_enum, + fn ladder(ladder_settings:&gameplay_style::LadderSettings,body:&Body,gravity:Planar64Vec3,target_velocity:Planar64Vec3,contact:ContactCollision)->Self{ + Self{//,style,velocity,normal,style.ladder_speed,style.ladder_accel + target:TransientAcceleration::ladder(ladder_settings,body,gravity,target_velocity), contact, jump_direction:JumpDirection::FromContactNormal, - },a) - } -} - -struct Modes{ - modes:Vec, - mode_from_mode_id:std::collections::HashMap::, -} -impl Modes{ - fn clear(&mut self){ - self.modes.clear(); - self.mode_from_mode_id.clear(); - } - fn get_mode(&self,mode_id:u32)->Option<&crate::model::ModeDescription>{ - self.modes.get(*self.mode_from_mode_id.get(&mode_id)?) - } - fn insert(&mut self,temp_map_mode_id:u32,mode:crate::model::ModeDescription){ - let mode_id=self.modes.len(); - self.mode_from_mode_id.insert(temp_map_mode_id,mode_id); - self.modes.push(mode); - } -} -impl Default for Modes{ - fn default() -> Self { - Self{ - modes:Vec::new(), - mode_from_mode_id:std::collections::HashMap::new(), } } } +fn ground_things(walk_settings:&gameplay_style::WalkSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){ + let normal=contact_normal(models,hitbox_mesh,contact); + let gravity=touching.base_acceleration(models,style,camera,input_state); + let control_dir=style.get_y_control_dir(camera,input_state.controls); + let mut target_velocity=walk_settings.get_walk_target_velocity(control_dir,normal); + touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity); + (gravity,target_velocity) +} +fn ladder_things(ladder_settings:&gameplay_style::LadderSettings,contact:&ContactCollision,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->(Planar64Vec3,Planar64Vec3){ + let normal=contact_normal(models,hitbox_mesh,contact); + let gravity=touching.base_acceleration(models,style,camera,input_state); + let control_dir=style.get_y_control_dir(camera,input_state.controls); + let mut target_velocity=ladder_settings.get_ladder_target_velocity(control_dir,normal); + touching.constrain_velocity(models,hitbox_mesh,&mut target_velocity); + (gravity,target_velocity) +} #[derive(Default)] struct PhysicsModels{ - meshes:Vec, - models:Vec, - //separate models into Contacting and Intersecting? - //wrap model id with ContactingModelId and IntersectingModelId - //attributes can be split into contacting and intersecting (this also saves a bit of memory) - //can go even further and deduplicate General attributes separately, reconstructing it when queried - attributes:Vec, - model_id_from_wormhole_id:std::collections::HashMap::, + meshes:HashMap, + contact_models:HashMap, + intersect_models:HashMap, + contact_attributes:HashMap, + intersect_attributes:HashMap, } impl PhysicsModels{ fn clear(&mut self){ self.meshes.clear(); - self.models.clear(); - self.attributes.clear(); - self.model_id_from_wormhole_id.clear(); + self.contact_models.clear(); + self.intersect_models.clear(); + self.contact_attributes.clear(); + self.intersect_attributes.clear(); } - fn aabb_list(&self)->Vec{ - self.models.iter().map(|model|{ - let mut aabb=aabb::Aabb::default(); - for pos in self.meshes[model.mesh_id].verts(){ - aabb.grow(model.transform.transform_point3(pos)); - } - aabb - }).collect() - } - //TODO: "statically" verify the maps don't refer to any nonexistant data, if they do delete the references. - //then I can make these getter functions unchecked. - fn mesh(&self,model_id:usize)->TransformedMesh{ + fn mesh(&self,convex_mesh_id:ConvexMeshId)->TransformedMesh{ + let (mesh_id,transform)=match convex_mesh_id.model_id{ + PhysicsModelId::Contact(model_id)=>{ + let model=&self.contact_models[&model_id]; + (&model.mesh_id,&model.transform) + }, + PhysicsModelId::Intersect(model_id)=>{ + let model=&self.intersect_models[&model_id]; + (&model.mesh_id,&model.transform) + }, + }; TransformedMesh::new( - &self.meshes[self.models[model_id].mesh_id], - &self.models[model_id].transform, - &self.models[model_id].normal_transform, - self.models[model_id].transform_det, + self.meshes[mesh_id].submesh_view(convex_mesh_id.submesh_id), + transform ) } - fn model(&self,model_id:usize)->&PhysicsModel{ - &self.models[model_id] + //it's a bit weird to have three functions, but it's always going to be one of these + fn contact_mesh(&self,contact:&ContactCollision)->TransformedMesh{ + let model=&self.contact_models[&contact.model_id]; + TransformedMesh::new( + self.meshes[&model.mesh_id].submesh_view(contact.submesh_id), + &model.transform + ) } - fn attr(&self,model_id:usize)->&PhysicsCollisionAttributes{ - &self.attributes[self.models[model_id].attr_id] + fn intersect_mesh(&self,intersect:&IntersectCollision)->TransformedMesh{ + let model=&self.intersect_models[&intersect.model_id]; + TransformedMesh::new( + self.meshes[&model.mesh_id].submesh_view(intersect.submesh_id), + &model.transform + ) } - fn get_wormhole_model(&self,wormhole_id:u32)->Option<&PhysicsModel>{ - self.models.get(*self.model_id_from_wormhole_id.get(&wormhole_id)?) + fn get_model_transform(&self,model_id:ModelId)->Option<&PhysicsMeshTransform>{ + //ModelId can possibly be a decoration + self.contact_models.get(&ContactModelId::new(model_id.get())).map_or_else( + ||self.intersect_models.get(&IntersectModelId::new(model_id.get())) + .map(|model|&model.transform), + |model|Some(&model.transform) + ) } - fn push_mesh(&mut self,mesh:PhysicsMesh){ - self.meshes.push(mesh); + fn contact_model(&self,model_id:ContactModelId)->&ContactModel{ + &self.contact_models[&model_id] } - fn push_model(&mut self,model:PhysicsModel)->usize{ - let model_id=self.models.len(); - self.models.push(model); - model_id + fn intersect_model(&self,model_id:IntersectModelId)->&IntersectModel{ + &self.intersect_models[&model_id] } - fn push_attr(&mut self,attr:PhysicsCollisionAttributes)->usize{ - let attr_id=self.attributes.len(); - self.attributes.push(attr); - attr_id + fn contact_attr(&self,model_id:ContactModelId)->&gameplay_attributes::ContactAttributes{ + &self.contact_attributes[&self.contact_models[&model_id].attr_id] + } + fn intersect_attr(&self,model_id:IntersectModelId)->&gameplay_attributes::IntersectAttributes{ + &self.intersect_attributes[&self.intersect_models[&model_id].attr_id] } } -#[derive(Clone)] +#[derive(Clone,Copy,Debug)] pub struct PhysicsCamera{ //punch: Planar64Vec3, //punch_velocity: Planar64Vec3, sensitivity:Ratio64Vec2,//dots to Angle32 ratios - mouse:MouseState,//last seen absolute mouse pos clamped_mouse_pos:glam::IVec2,//angles are calculated from this cumulative value - angle_pitch_lower_limit:Angle32, - angle_pitch_upper_limit:Angle32, //angle limits could be an enum + struct that defines whether it's limited and selects clamp or wrap depending // enum AngleLimit{ // Unlimited, @@ -249,34 +270,49 @@ pub struct PhysicsCamera{ //yaw_limit:AngleLimit, } -impl PhysicsCamera { - pub fn move_mouse(&mut self,mouse_pos:glam::IVec2){ - let mut unclamped_mouse_pos=self.clamped_mouse_pos+mouse_pos-self.mouse.pos; +impl PhysicsCamera{ + const ANGLE_PITCH_LOWER_LIMIT:Angle32=Angle32::NEG_FRAC_PI_2; + const ANGLE_PITCH_UPPER_LIMIT:Angle32=Angle32::FRAC_PI_2; + pub fn move_mouse(&mut self,mouse_delta:glam::IVec2){ + let mut unclamped_mouse_pos=self.clamped_mouse_pos+mouse_delta; unclamped_mouse_pos.y=unclamped_mouse_pos.y.clamp( - self.sensitivity.y.rhs_div_int(self.angle_pitch_lower_limit.get() as i64) as i32, - self.sensitivity.y.rhs_div_int(self.angle_pitch_upper_limit.get() as i64) as i32, + self.sensitivity.y.rhs_div_int(Self::ANGLE_PITCH_LOWER_LIMIT.get() as i64) as i32, + self.sensitivity.y.rhs_div_int(Self::ANGLE_PITCH_UPPER_LIMIT.get() as i64) as i32, ); self.clamped_mouse_pos=unclamped_mouse_pos; } - pub fn simulate_move_angles(&self,mouse_pos:glam::IVec2)->glam::Vec2 { - let a=-self.sensitivity.mul_int((mouse_pos-self.mouse.pos+self.clamped_mouse_pos).as_i64vec2()); + pub fn simulate_move_angles(&self,mouse_delta:glam::IVec2)->glam::Vec2 { + let a=-self.sensitivity.mul_int((self.clamped_mouse_pos+mouse_delta).as_i64vec2()); let ax=Angle32::wrap_from_i64(a.x); let ay=Angle32::clamp_from_i64(a.y) //clamp to actual vertical cam limit - .clamp(self.angle_pitch_lower_limit,self.angle_pitch_upper_limit); + .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); return glam::vec2(ax.into(),ay.into()); } - fn simulate_move_rotation(&self,mouse_pos:glam::IVec2)->Planar64Mat3{ - let a=-self.sensitivity.mul_int((mouse_pos-self.mouse.pos+self.clamped_mouse_pos).as_i64vec2()); + #[inline] + fn get_rotation(&self,mouse_pos:glam::IVec2)->Planar64Mat3{ + let a=-self.sensitivity.mul_int(mouse_pos.as_i64vec2()); let ax=Angle32::wrap_from_i64(a.x); let ay=Angle32::clamp_from_i64(a.y) //clamp to actual vertical cam limit - .clamp(self.angle_pitch_lower_limit,self.angle_pitch_upper_limit); - Planar64Mat3::from_rotation_yx(ax,ay) + .clamp(Self::ANGLE_PITCH_LOWER_LIMIT,Self::ANGLE_PITCH_UPPER_LIMIT); + mat3::from_rotation_yx(ax,ay) } - fn simulate_move_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{ - let ax=-self.sensitivity.x.mul_int((mouse_pos_x-self.mouse.pos.x+self.clamped_mouse_pos.x) as i64); - Planar64Mat3::from_rotation_y(Angle32::wrap_from_i64(ax)) + fn rotation(&self)->Planar64Mat3{ + self.get_rotation(self.clamped_mouse_pos) + } + fn simulate_move_rotation(&self,mouse_delta:glam::IVec2)->Planar64Mat3{ + self.get_rotation(self.clamped_mouse_pos+mouse_delta) + } + fn get_rotation_y(&self,mouse_pos_x:i32)->Planar64Mat3{ + let ax=-self.sensitivity.x.mul_int(mouse_pos_x as i64); + mat3::from_rotation_y(Angle32::wrap_from_i64(ax)) + } + fn rotation_y(&self)->Planar64Mat3{ + self.get_rotation_y(self.clamped_mouse_pos.x) + } + fn simulate_move_rotation_y(&self,mouse_delta_x:i32)->Planar64Mat3{ + self.get_rotation_y(self.clamped_mouse_pos.x+mouse_delta_x) } } @@ -284,532 +320,396 @@ impl std::default::Default for PhysicsCamera{ fn default()->Self{ Self{ sensitivity:Ratio64Vec2::ONE*200_000, - mouse:MouseState::default(),//t=0 does not cause divide by zero because it's immediately replaced clamped_mouse_pos:glam::IVec2::ZERO, - angle_pitch_lower_limit:-Angle32::FRAC_PI_2, - angle_pitch_upper_limit:Angle32::FRAC_PI_2, } } } - -pub struct GameMechanicsState{ - stage_id:u32, - jump_counts:std::collections::HashMap,//model_id -> jump count - next_ordered_checkpoint_id:u32,//which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0) - unordered_checkpoints:std::collections::HashSet,//hashset of UnorderedCheckpoint model ids -} -impl std::default::Default for GameMechanicsState{ - fn default()->Self{ - Self{ - stage_id:0, - next_ordered_checkpoint_id:0, - unordered_checkpoints:std::collections::HashSet::new(), - jump_counts:std::collections::HashMap::new(), +mod gameplay{ + use super::{gameplay_modes,HashSet,HashMap,ModelId}; + #[derive(Clone,Debug)] + pub struct ModeState{ + mode_id:gameplay_modes::ModeId, + stage_id:gameplay_modes::StageId, + next_ordered_checkpoint_id:gameplay_modes::CheckpointId,//which OrderedCheckpoint model_id you must pass next (if 0 you haven't passed OrderedCheckpoint0) + unordered_checkpoints:HashSet, + jump_counts:HashMap,//model_id -> jump count + } + impl ModeState{ + pub const fn get_mode_id(&self)->gameplay_modes::ModeId{ + self.mode_id + } + pub const fn get_stage_id(&self)->gameplay_modes::StageId{ + self.stage_id + } + pub const fn get_next_ordered_checkpoint_id(&self)->gameplay_modes::CheckpointId{ + self.next_ordered_checkpoint_id + } + pub fn get_jump_count(&self,model_id:ModelId)->Option{ + self.jump_counts.get(&model_id).copied() + } + pub const fn ordered_checkpoint_count(&self)->u32{ + self.next_ordered_checkpoint_id.get() + } + pub fn unordered_checkpoint_count(&self)->u32{ + self.unordered_checkpoints.len() as u32 + } + pub fn set_mode_id(&mut self,mode_id:gameplay_modes::ModeId){ + self.clear(); + self.mode_id=mode_id; + } + pub fn set_stage_id(&mut self,stage_id:gameplay_modes::StageId){ + self.clear_checkpoints(); + self.stage_id=stage_id; + } + pub fn accumulate_ordered_checkpoint(&mut self,stage:&gameplay_modes::Stage,model_id:ModelId){ + if stage.is_next_ordered_checkpoint(self.get_next_ordered_checkpoint_id(),model_id){ + self.next_ordered_checkpoint_id=gameplay_modes::CheckpointId::new(self.next_ordered_checkpoint_id.get()+1); + } + } + pub fn accumulate_unordered_checkpoint(&mut self,stage:&gameplay_modes::Stage,model_id:ModelId){ + if stage.is_unordered_checkpoint(model_id){ + self.unordered_checkpoints.insert(model_id); + } + } + pub fn clear(&mut self){ + self.clear_jump_counts(); + self.clear_checkpoints(); + } + pub fn clear_jump_counts(&mut self){ + self.jump_counts.clear(); + } + pub fn clear_checkpoints(&mut self){ + self.next_ordered_checkpoint_id=gameplay_modes::CheckpointId::FIRST; + self.unordered_checkpoints.clear(); + } + } + impl std::default::Default for ModeState{ + fn default()->Self{ + Self{ + mode_id:gameplay_modes::ModeId::MAIN, + stage_id:gameplay_modes::StageId::FIRST, + next_ordered_checkpoint_id:gameplay_modes::CheckpointId::FIRST, + unordered_checkpoints:HashSet::new(), + jump_counts:HashMap::new(), + } } } } - +#[derive(Clone,Debug)] struct WorldState{} -enum JumpCalculation{ - Capped,//roblox - Energy,//new - Linear,//source -} - -enum JumpImpulse{ - FromTime(Time),//jump time is invariant across mass and gravity changes - FromHeight(Planar64),//jump height is invariant across mass and gravity changes - FromDeltaV(Planar64),//jump velocity is invariant across mass and gravity changes - FromEnergy(Planar64),// :) -} -//Jumping acts on dot(walks_state.normal,body.velocity) -//Capped means it increases the dot to the cap -//Energy means it adds energy -//Linear means it linearly adds on - -enum EnableStrafe{ - Always, - MaskAny(u32),//hsw, shsw - MaskAll(u32), - //Function(Boxbool>), -} - -struct StrafeSettings{ - enable:EnableStrafe, - air_accel_limit:Option, - tick_rate:Ratio64, -} - -struct Hitbox{ +struct HitboxMesh{ halfsize:Planar64Vec3, mesh:PhysicsMesh, - transform:integer::Planar64Affine3, - normal_transform:Planar64Mat3, - transform_det:Planar64, + transform:PhysicsMeshTransform, } -impl Hitbox{ +impl HitboxMesh{ fn new(mesh:PhysicsMesh,transform:integer::Planar64Affine3)->Self{ //calculate extents let mut aabb=aabb::Aabb::default(); - for vert in mesh.verts(){ - aabb.grow(transform.transform_point3(vert)); + let transform=PhysicsMeshTransform::new(transform); + let transformed_mesh=TransformedMesh::new(mesh.complete_mesh_view(),&transform); + for vert in transformed_mesh.verts(){ + aabb.grow(vert.fix_1()); } Self{ - halfsize:aabb.size()/2, + halfsize:aabb.size()>>1, mesh, transform, - normal_transform:transform.matrix3.inverse_times_det().transpose(), - transform_det:transform.matrix3.determinant(), } } - fn from_mesh_scale(mesh:PhysicsMesh,scale:Planar64Vec3)->Self{ - let matrix3=Planar64Mat3::from_diagonal(scale); - Self{ - halfsize:scale, - mesh, - normal_transform:matrix3.inverse_times_det().transpose(), - transform:integer::Planar64Affine3::new(matrix3,Planar64Vec3::ZERO), - transform_det:matrix3.determinant(),//scale.x*scale.y*scale.z but whatever - } - } - fn from_mesh_scale_offset(mesh:PhysicsMesh,scale:Planar64Vec3,offset:Planar64Vec3)->Self{ - let matrix3=Planar64Mat3::from_diagonal(scale); - Self{ - halfsize:scale, - mesh, - normal_transform:matrix3.inverse_times_det().transpose(), - transform:integer::Planar64Affine3::new(matrix3,offset), - transform_det:matrix3.determinant(), - } - } - fn roblox()->Self{ - Self::from_mesh_scale(PhysicsMesh::from(&crate::primitives::unit_cylinder()),Planar64Vec3::int(2,5,2)/2) - } - fn source()->Self{ - Self::from_mesh_scale(PhysicsMesh::from(&crate::primitives::unit_cube()),Planar64Vec3::raw(33<<28,73<<28,33<<28)/2) - } #[inline] - fn transformed_mesh(&self)->TransformedMesh{ - TransformedMesh::new(&self.mesh,&self.transform,&self.normal_transform,self.transform_det) + const fn transformed_mesh(&self)->TransformedMesh{ + TransformedMesh::new(self.mesh.complete_mesh_view(),&self.transform) } } -struct StyleModifiers{ - controls_used:u32,//controls which are allowed to pass into gameplay - controls_mask:u32,//controls which are masked from control state (e.g. jump in scroll style) - strafe:Option, - jump_impulse:JumpImpulse, - jump_calculation:JumpCalculation, - static_friction:Planar64, - kinetic_friction:Planar64, - walk_speed:Planar64, - walk_accel:Planar64, - ladder_speed:Planar64, - ladder_accel:Planar64, - ladder_dot:Planar64, - swim_speed:Planar64, - mass:Planar64, - mv:Planar64, - surf_slope:Option, - rocket_force:Option, - gravity:Planar64Vec3, - hitbox:Hitbox, - camera_offset:Planar64Vec3, +trait StyleHelper{ + fn get_control(&self,control:Controls,controls:Controls)->bool; + fn get_control_dir(&self,controls:Controls)->Planar64Vec3; + fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3; + fn get_propulsion_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3; + fn calculate_mesh(&self)->HitboxMesh; } -impl std::default::Default for StyleModifiers{ - fn default()->Self{ - Self::roblox_bhop() - } -} -impl StyleModifiers{ - const CONTROL_MOVEFORWARD:u32=0b00000001; - const CONTROL_MOVEBACK:u32=0b00000010; - const CONTROL_MOVERIGHT:u32=0b00000100; - const CONTROL_MOVELEFT:u32=0b00001000; - const CONTROL_MOVEUP:u32=0b00010000; - const CONTROL_MOVEDOWN:u32=0b00100000; - const CONTROL_JUMP:u32=0b01000000; - const CONTROL_ZOOM:u32=0b10000000; - - const RIGHT_DIR:Planar64Vec3=Planar64Vec3::X; - const UP_DIR:Planar64Vec3=Planar64Vec3::Y; - const FORWARD_DIR:Planar64Vec3=Planar64Vec3::NEG_Z; - - fn neo()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:None, - tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromEnergy(Planar64::int(512)), - jump_calculation:JumpCalculation::Energy, - gravity:Planar64Vec3::int(0,-80,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(3), - rocket_force:None, - walk_speed:Planar64::int(16), - walk_accel:Planar64::int(80), - ladder_speed:Planar64::int(16), - ladder_accel:Planar64::int(160), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(7)/8), - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } +impl StyleHelper for StyleModifiers{ + fn get_control(&self,control:Controls,controls:Controls)->bool{ + controls.intersection(self.controls_mask).contains(control) } - fn roblox_bhop()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:None, - tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), - jump_calculation:JumpCalculation::Capped, - gravity:Planar64Vec3::int(0,-100,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(27)/10, - rocket_force:None, - walk_speed:Planar64::int(18), - walk_accel:Planar64::int(90), - ladder_speed:Planar64::int(18), - ladder_accel:Planar64::int(180), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } - } - fn roblox_surf()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:None, - tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), - jump_calculation:JumpCalculation::Capped, - gravity:Planar64Vec3::int(0,-50,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(27)/10, - rocket_force:None, - walk_speed:Planar64::int(18), - walk_accel:Planar64::int(90), - ladder_speed:Planar64::int(18), - ladder_accel:Planar64::int(180), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } - } - - fn source_bhop()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:Some(Planar64::raw(150<<28)*100), - tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)), - jump_calculation:JumpCalculation::Linear, - gravity:Planar64Vec3::raw(0,-800<<28,0), - static_friction:Planar64::int(2),//? - kinetic_friction:Planar64::int(3),//? - mass:Planar64::int(1), - mv:Planar64::raw(30<<28), - rocket_force:None, - walk_speed:Planar64::int(18),//? - walk_accel:Planar64::int(90),//? - ladder_speed:Planar64::int(18),//? - ladder_accel:Planar64::int(180),//? - ladder_dot:(Planar64::int(1)/2).sqrt(),//? - swim_speed:Planar64::int(12),//? - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::source(), - camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), - } - } - fn source_surf()->Self{ - Self{ - controls_used:!0, - controls_mask:!0,//&!(Self::CONTROL_MOVEUP|Self::CONTROL_MOVEDOWN), - strafe:Some(StrafeSettings{ - enable:EnableStrafe::Always, - air_accel_limit:Some(Planar64::raw(150<<28)*66), - tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(), - }), - jump_impulse:JumpImpulse::FromHeight(Planar64::raw(52<<28)), - jump_calculation:JumpCalculation::Linear, - gravity:Planar64Vec3::raw(0,-800<<28,0), - static_friction:Planar64::int(2),//? - kinetic_friction:Planar64::int(3),//? - mass:Planar64::int(1), - mv:Planar64::raw(30<<28), - rocket_force:None, - walk_speed:Planar64::int(18),//? - walk_accel:Planar64::int(90),//? - ladder_speed:Planar64::int(18),//? - ladder_accel:Planar64::int(180),//? - ladder_dot:(Planar64::int(1)/2).sqrt(),//? - swim_speed:Planar64::int(12),//? - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::source(), - camera_offset:Planar64Vec3::raw(0,(64<<28)-(73<<27),0), - } - } - fn roblox_rocket()->Self{ - Self{ - controls_used:!0, - controls_mask:!0, - strafe:None, - jump_impulse:JumpImpulse::FromTime(Time::from_micros(715_588)), - jump_calculation:JumpCalculation::Capped, - gravity:Planar64Vec3::int(0,-100,0), - static_friction:Planar64::int(2), - kinetic_friction:Planar64::int(3),//unrealistic: kinetic friction is typically lower than static - mass:Planar64::int(1), - mv:Planar64::int(27)/10, - rocket_force:Some(Planar64::int(200)), - walk_speed:Planar64::int(18), - walk_accel:Planar64::int(90), - ladder_speed:Planar64::int(18), - ladder_accel:Planar64::int(180), - ladder_dot:(Planar64::int(1)/2).sqrt(), - swim_speed:Planar64::int(12), - surf_slope:Some(Planar64::raw(3787805118)),// normal.y=0.75 - hitbox:Hitbox::roblox(), - camera_offset:Planar64Vec3::int(0,2,0),//4.5-2.5=2 - } - } - - fn get_control(&self,control:u32,controls:u32)->bool{ - controls&self.controls_mask&control==control - } - - fn allow_strafe(&self,controls:u32)->bool{ - //disable strafing according to strafe settings - match &self.strafe{ - Some(StrafeSettings{enable:EnableStrafe::Always,air_accel_limit:_,tick_rate:_})=>true, - &Some(StrafeSettings{enable:EnableStrafe::MaskAny(mask),air_accel_limit:_,tick_rate:_})=>mask&controls!=0, - &Some(StrafeSettings{enable:EnableStrafe::MaskAll(mask),air_accel_limit:_,tick_rate:_})=>mask&controls==mask, - None=>false, - } - } - - fn get_control_dir(&self,controls:u32)->Planar64Vec3{ + fn get_control_dir(&self,controls:Controls)->Planar64Vec3{ //don't get fancy just do it - let mut control_dir:Planar64Vec3 = Planar64Vec3::ZERO; + let mut control_dir:Planar64Vec3=vec3::ZERO; //Apply mask after held check so you can require non-allowed keys to be held for some reason - let controls=controls&self.controls_mask; - if controls & Self::CONTROL_MOVEFORWARD == Self::CONTROL_MOVEFORWARD { + let controls=controls.intersection(self.controls_mask); + if controls.contains(Controls::MoveForward){ control_dir+=Self::FORWARD_DIR; } - if controls & Self::CONTROL_MOVEBACK == Self::CONTROL_MOVEBACK { + if controls.contains(Controls::MoveBackward){ control_dir-=Self::FORWARD_DIR; } - if controls & Self::CONTROL_MOVELEFT == Self::CONTROL_MOVELEFT { + if controls.contains(Controls::MoveLeft){ control_dir-=Self::RIGHT_DIR; } - if controls & Self::CONTROL_MOVERIGHT == Self::CONTROL_MOVERIGHT { + if controls.contains(Controls::MoveRight){ control_dir+=Self::RIGHT_DIR; } - if controls & Self::CONTROL_MOVEUP == Self::CONTROL_MOVEUP { + if controls.contains(Controls::MoveUp){ control_dir+=Self::UP_DIR; } - if controls & Self::CONTROL_MOVEDOWN == Self::CONTROL_MOVEDOWN { + if controls.contains(Controls::MoveDown){ control_dir-=Self::UP_DIR; } return control_dir } - //fn get_jump_time(&self)->Planar64 - //fn get_jump_height(&self)->Planar64 - //fn get_jump_energy(&self)->Planar64 - fn get_jump_deltav(&self)->Planar64{ - match &self.jump_impulse{ - &JumpImpulse::FromTime(time)=>self.gravity.length()*(time/2), - &JumpImpulse::FromHeight(height)=>(self.gravity.length()*height*2).sqrt(), - &JumpImpulse::FromDeltaV(deltav)=>deltav, - &JumpImpulse::FromEnergy(energy)=>(energy*2/self.mass).sqrt(), - } + fn get_y_control_dir(&self,camera:&PhysicsCamera,controls:Controls)->Planar64Vec3{ + (camera.rotation_y()*self.get_control_dir(controls)).fix_1() } - fn get_walk_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time,normal:&Planar64Vec3)->Planar64Vec3{ - let mut control_dir=self.get_control_dir(controls); - if control_dir==Planar64Vec3::ZERO{ - return control_dir; - } - let camera_mat=camera.simulate_move_rotation_y(camera.mouse.lerp(&next_mouse,time).x); - control_dir=camera_mat*control_dir; - let n=normal.length(); - let m=control_dir.length(); - let d=normal.dot(control_dir)/m; - if dPlanar64Vec3{ + //don't interpolate this! discrete mouse movement, constant acceleration + (camera.rotation()*self.get_control_dir(controls)).fix_1() } - fn get_ladder_target_velocity(&self,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time,normal:&Planar64Vec3)->Planar64Vec3{ - let mut control_dir=self.get_control_dir(controls); - if control_dir==Planar64Vec3::ZERO{ - return control_dir; - } - let camera_mat=camera.simulate_move_rotation(camera.mouse.lerp(&next_mouse,time)); - control_dir=camera_mat*control_dir; - let n=normal.length(); - let m=control_dir.length(); - let mut d=normal.dot(control_dir)/m; - if d< -self.ladder_dot*n{ - control_dir=Planar64Vec3::Y*m; - d=normal.y(); - }else if self.ladder_dot*nPlanar64Vec3{ - let camera_mat=camera.simulate_move_rotation(camera.mouse.lerp(&next_mouse,time)); - camera_mat*self.get_control_dir(controls) - } - #[inline] - fn mesh(&self)->TransformedMesh{ - self.hitbox.transformed_mesh() + fn calculate_mesh(&self)->HitboxMesh{ + let mesh=match self.hitbox.mesh{ + gameplay_style::HitboxMesh::Box=>PhysicsMesh::unit_cube(), + gameplay_style::HitboxMesh::Cylinder=>PhysicsMesh::unit_cylinder(), + }; + let transform=integer::Planar64Affine3::new( + mat3::from_diagonal(self.hitbox.halfsize), + vec3::ZERO + ); + HitboxMesh::new(mesh,transform) } } - +#[derive(Clone,Debug)] enum MoveState{ Air, - Walk(WalkState), + Walk(ContactMoveState), + Ladder(ContactMoveState), Water, - Ladder(WalkState), + Fly, } - -pub struct PhysicsState{ - time:Time, - body:Body, - world:WorldState,//currently there is only one state the world can be in - game:GameMechanicsState, - style:StyleModifiers, - touching:TouchingState, - //camera must exist in state because wormholes modify the camera, also camera punch - camera:PhysicsCamera, - pub next_mouse:MouseState,//Where is the mouse headed next - controls:u32, - move_state:MoveState, - models:PhysicsModels, - bvh:bvh::BvhNode, - - modes:Modes, - //the spawn point is where you spawn when you load into the map. - //This is not the same as Reset which teleports you to Spawn0 - spawn_point:Planar64Vec3, -} -#[derive(Clone,Default)] -pub struct PhysicsOutputState{ - body:Body, - camera:PhysicsCamera, - camera_offset:Planar64Vec3, -} -impl PhysicsOutputState{ - pub fn extrapolate(&self,mouse_pos:glam::IVec2,time:Time)->(glam::Vec3,glam::Vec2){ - ((self.body.extrapolated_position(time)+self.camera_offset).into(),self.camera.simulate_move_angles(mouse_pos)) +impl MoveState{ + //call this after state.move_state is changed + fn apply_enum(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + match self{ + MoveState::Fly=>body.acceleration=vec3::ZERO, + MoveState::Air=>{ + //calculate base acceleration + let a=touching.base_acceleration(models,style,camera,input_state); + //set_acceleration clips according to contacts + set_acceleration(body,touching,models,hitbox_mesh,a); + }, + _=>(), + } + } + //function to coerce &mut self into &self + fn apply_to_body(&self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + match self{ + MoveState::Air=>(), + MoveState::Water=>(), + MoveState::Fly=>{ + //set velocity according to current control state + let v=style.get_propulsion_control_dir(camera,input_state.controls)*80; + //set_velocity clips velocity according to current touching state + set_velocity(body,touching,models,hitbox_mesh,v); + }, + MoveState::Walk(walk_state) + |MoveState::Ladder(walk_state) + =>{ + //accelerate towards walk target or do nothing + let a=walk_state.target.acceleration(); + set_acceleration(body,touching,models,hitbox_mesh,a); + }, + } + } + /// changes the move state + fn apply_input(&mut self,body:&Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + match self{ + MoveState::Fly + |MoveState::Air + |MoveState::Water=>(), + MoveState::Walk(ContactMoveState{target,contact,jump_direction:_})=>{ + if let Some(walk_settings)=&style.walk{ + let (gravity,target_velocity)=ground_things(walk_settings,contact,touching,models,hitbox_mesh,style,camera,input_state); + *target=TransientAcceleration::ground(walk_settings,body,gravity,target_velocity); + }else{ + panic!("ContactMoveState exists in style which does not allow walking!"); + } + }, + MoveState::Ladder(ContactMoveState{target,contact,jump_direction:_})=>{ + if let Some(ladder_settings)=&style.ladder{ + let (gravity,target_velocity)=ladder_things(ladder_settings,contact,touching,models,hitbox_mesh,style,camera,input_state); + *target=TransientAcceleration::ladder(ladder_settings,body,gravity,target_velocity); + }else{ + panic!("ContactMoveState exists in style which does not allow walking!"); + } + }, + } + } + fn get_walk_state(&self)->Option<&ContactMoveState>{ + match self{ + MoveState::Walk(walk_state) + |MoveState::Ladder(walk_state) + =>Some(walk_state), + MoveState::Air + |MoveState::Water + |MoveState::Fly + =>None, + } + } + fn next_move_instruction(&self,strafe:&Option,time:Time)->Option>{ + //check if you have a valid walk state and create an instruction + match self{ + MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.target{ + &TransientAcceleration::Reachable{acceleration:_,time}=>Some(TimedInstruction{ + time, + instruction:PhysicsInternalInstruction::ReachWalkTargetVelocity + }), + TransientAcceleration::Unreachable{acceleration:_} + |TransientAcceleration::Reached + =>None, + } + MoveState::Air=>strafe.as_ref().map(|strafe|{ + TimedInstruction{ + time:strafe.next_tick(time), + //only poll the physics if there is a before and after mouse event + instruction:PhysicsInternalInstruction::StrafeTick + } + }), + MoveState::Water=>None,//TODO + MoveState::Fly=>None, + } + } + //lmao idk this is convenient + fn apply_enum_and_input_and_body(&mut self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + self.apply_enum(body,touching,models,hitbox_mesh,style,camera,input_state); + self.apply_input(body,touching,models,hitbox_mesh,style,camera,input_state); + self.apply_to_body(body,touching,models,hitbox_mesh,style,camera,input_state); + } + fn apply_enum_and_body(&mut self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + self.apply_enum(body,touching,models,hitbox_mesh,style,camera,input_state); + self.apply_to_body(body,touching,models,hitbox_mesh,style,camera,input_state); + } + fn apply_input_and_body(&mut self,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + self.apply_input(body,touching,models,hitbox_mesh,style,camera,input_state); + self.apply_to_body(body,touching,models,hitbox_mesh,style,camera,input_state); + } + fn set_move_state(&mut self,move_state:MoveState,body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + *self=move_state; + //this function call reads the above state that was just set + self.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state); + } + fn cull_velocity(&mut self,velocity:Planar64Vec3,body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState){ + //TODO: be more precise about contacts + if set_velocity_cull(body,touching,models,hitbox_mesh,velocity){ + //TODO do better + match self.get_walk_state(){ + //did you stop touching the thing you were walking on? + Some(walk_state)=>if !touching.contacts.contains(&walk_state.contact){ + self.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state); + }, + None=>self.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state), + } + } } } #[derive(Clone,Hash,Eq,PartialEq)] enum PhysicsCollisionAttributes{ - Contact{//track whether you are contacting the object - contacting:crate::model::ContactingAttributes, - general:crate::model::GameMechanicAttributes, - }, - Intersect{//track whether you are intersecting the object - intersecting:crate::model::IntersectingAttributes, - general:crate::model::GameMechanicAttributes, - }, + Contact(gameplay_attributes::ContactAttributes), + Intersect(gameplay_attributes::IntersectAttributes), } struct NonPhysicsError; -impl TryFrom<&crate::model::CollisionAttributes> for PhysicsCollisionAttributes{ +impl TryFrom<&gameplay_attributes::CollisionAttributes> for PhysicsCollisionAttributes{ type Error=NonPhysicsError; - fn try_from(value:&crate::model::CollisionAttributes)->Result{ + fn try_from(value:&gameplay_attributes::CollisionAttributes)->Result{ match value{ - crate::model::CollisionAttributes::Decoration=>Err(NonPhysicsError), - crate::model::CollisionAttributes::Contact{contacting,general}=>Ok(Self::Contact{contacting:contacting.clone(),general:general.clone()}), - crate::model::CollisionAttributes::Intersect{intersecting,general}=>Ok(Self::Intersect{intersecting:intersecting.clone(),general:general.clone()}), + gameplay_attributes::CollisionAttributes::Decoration=>Err(NonPhysicsError), + gameplay_attributes::CollisionAttributes::Contact(attr)=>Ok(Self::Contact(attr.clone())), + gameplay_attributes::CollisionAttributes::Intersect(attr)=>Ok(Self::Intersect(attr.clone())), } } } - -pub struct PhysicsModel{ - //A model is a thing that has a hitbox. can be represented by a list of TreyMesh-es - //in this iteration, all it needs is extents. - mesh_id:usize, - attr_id:usize, - transform:integer::Planar64Affine3, - normal_transform:integer::Planar64Mat3, - transform_det:Planar64, -} - -impl PhysicsModel{ - pub fn new(mesh_id:usize,attr_id:usize,transform:integer::Planar64Affine3)->Self{ - Self{ - mesh_id, - attr_id, - transform, - normal_transform:transform.matrix3.inverse_times_det().transpose(), - transform_det:transform.matrix3.determinant(), - } +#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct ContactAttributesId(u32); +impl Into for ContactAttributesId{ + fn into(self)->CollisionAttributesId{ + CollisionAttributesId::new(self.0) } } +impl From for ContactAttributesId{ + fn from(value:CollisionAttributesId)->Self{ + Self::new(value.get()) + } +} +#[derive(Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct IntersectAttributesId(u32); +impl Into for IntersectAttributesId{ + fn into(self)->CollisionAttributesId{ + CollisionAttributesId::new(self.0) + } +} +impl From for IntersectAttributesId{ + fn from(value:CollisionAttributesId)->Self{ + Self::new(value.get()) + } +} +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct ContactModelId(u32); +impl Into for ContactModelId{ + fn into(self)->ModelId{ + ModelId::new(self.get()) + } +} +#[derive(Debug,Clone,Copy,Hash,id::Id,Eq,PartialEq)] +struct IntersectModelId(u32); +impl Into for IntersectModelId{ + fn into(self)->ModelId{ + ModelId::new(self.get()) + } +} +#[derive(Debug,Clone,Copy,Hash,Eq,PartialEq)] +enum PhysicsModelId{ + Contact(ContactModelId), + Intersect(IntersectModelId), +} +impl Into for PhysicsModelId{ + fn into(self)->ModelId{ + ModelId::new(match self{ + PhysicsModelId::Contact(model_id)=>model_id.get(), + PhysicsModelId::Intersect(model_id)=>model_id.get(), + }) + } +} +//unique physics meshes indexed by this +#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] +struct ConvexMeshId{ + model_id:PhysicsModelId, + submesh_id:PhysicsSubmeshId, +} +struct ContactModel{ + mesh_id:PhysicsMeshId, + attr_id:ContactAttributesId, + transform:PhysicsMeshTransform, +} +struct IntersectModel{ + mesh_id:PhysicsMeshId, + attr_id:IntersectAttributesId, + transform:PhysicsMeshTransform, +} -#[derive(Debug,Clone,Eq,Hash,PartialEq)] +#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] struct ContactCollision{ - face_id:crate::model_physics::MinkowskiFace, - model_id:usize,//using id to avoid lifetimes + face_id:model_physics::MinkowskiFace, + model_id:ContactModelId, + submesh_id:PhysicsSubmeshId, } -#[derive(Debug,Clone,Eq,Hash,PartialEq)] +#[derive(Debug,Clone,Copy,Eq,Hash,PartialEq)] struct IntersectCollision{ - model_id:usize, + model_id:IntersectModelId, + submesh_id:PhysicsSubmeshId, } #[derive(Debug,Clone,Eq,Hash,PartialEq)] enum Collision{ @@ -817,23 +717,17 @@ enum Collision{ Intersect(IntersectCollision), } impl Collision{ - fn model_id(&self)->usize{ - match self{ - &Collision::Contact(ContactCollision{model_id,face_id:_}) - |&Collision::Intersect(IntersectCollision{model_id})=>model_id, - } - } - fn face_id(&self)->Option{ - match self{ - &Collision::Contact(ContactCollision{model_id:_,face_id})=>Some(face_id), - &Collision::Intersect(IntersectCollision{model_id:_})=>None, + const fn new(convex_mesh_id:ConvexMeshId,face_id:model_physics::MinkowskiFace)->Self{ + match convex_mesh_id.model_id{ + PhysicsModelId::Contact(model_id)=>Collision::Contact(ContactCollision{model_id,submesh_id:convex_mesh_id.submesh_id,face_id}), + PhysicsModelId::Intersect(model_id)=>Collision::Intersect(IntersectCollision{model_id,submesh_id:convex_mesh_id.submesh_id}), } } } -#[derive(Default)] +#[derive(Clone,Debug,Default)] struct TouchingState{ - contacts:std::collections::HashSet::, - intersects:std::collections::HashSet::, + contacts:HashSet::, + intersects:HashSet::, } impl TouchingState{ fn clear(&mut self){ @@ -852,124 +746,71 @@ impl TouchingState{ Collision::Intersect(collision)=>self.intersects.remove(collision), } } - fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->Planar64Vec3{ + fn base_acceleration(&self,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,input_state:&InputState)->Planar64Vec3{ let mut a=style.gravity; - if let Some(rocket_force)=style.rocket_force{ - a+=style.get_propulsion_control_dir(camera,controls,next_mouse,time)*rocket_force; + if let Some(rocket_settings)=&style.rocket{ + a+=rocket_settings.acceleration(style.get_propulsion_control_dir(camera,input_state.controls)); } //add accelerators for contact in &self.contacts{ - match models.attr(contact.model_id){ - PhysicsCollisionAttributes::Contact{contacting,general}=>{ - match &general.accelerator{ - Some(accelerator)=>a+=accelerator.acceleration, - None=>(), - } - }, - _=>panic!("impossible touching state"), + if let Some(accelerator)=&models.contact_attr(contact.model_id).general.accelerator{ + a+=accelerator.acceleration; } } for intersect in &self.intersects{ - match models.attr(intersect.model_id){ - PhysicsCollisionAttributes::Intersect{intersecting,general}=>{ - match &general.accelerator{ - Some(accelerator)=>a+=accelerator.acceleration, - None=>(), - } - }, - _=>panic!("impossible touching state"), + if let Some(accelerator)=&models.intersect_attr(intersect.model_id).general.accelerator{ + a+=accelerator.acceleration; } } - //add water../? + //TODO: add water a } - fn constrain_velocity(&self,models:&PhysicsModels,style_mesh:&TransformedMesh,velocity:&mut Planar64Vec3){ + fn constrain_velocity(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,velocity:&mut Planar64Vec3){ //TODO: trey push solve for contact in &self.contacts{ - let n=contact_normal(models,style_mesh,contact); - let d=n.dot128(*velocity); - if d<0{ - *velocity-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64); + let n=contact_normal(models,hitbox_mesh,contact); + let d=n.dot(*velocity); + if d.is_negative(){ + *velocity-=(n*d/n.length_squared()).divide().fix_1(); } } } - fn constrain_acceleration(&self,models:&PhysicsModels,style_mesh:&TransformedMesh,acceleration:&mut Planar64Vec3){ + fn constrain_acceleration(&self,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,acceleration:&mut Planar64Vec3){ //TODO: trey push solve for contact in &self.contacts{ - let n=contact_normal(models,style_mesh,contact); - let d=n.dot128(*acceleration); - if d<0{ - *acceleration-=n*Planar64::raw(((d<<32)/n.dot128(n)) as i64); + let n=contact_normal(models,hitbox_mesh,contact); + let d=n.dot(*acceleration); + if d.is_negative(){ + *acceleration-=(n*d/n.length_squared()).divide().fix_1(); } } } - fn get_move_state(&self,body:&Body,models:&PhysicsModels,style:&StyleModifiers,camera:&PhysicsCamera,controls:u32,next_mouse:&MouseState,time:Time)->(MoveState,Planar64Vec3){ - //check current move conditions and use heuristics to determine - //which ladder to climb on, which ground to walk on, etc - //collect move state affecting objects from contacts (accelerator,water,ladder,ground) - let style_mesh=style.mesh(); - let gravity=self.base_acceleration(models,style,camera,controls,next_mouse,time); - let mut move_state=MoveState::Air; - let mut a=gravity; - for contact in &self.contacts{ - match models.attr(contact.model_id){ - PhysicsCollisionAttributes::Contact{contacting,general}=>{ - let normal=contact_normal(models,&style_mesh,contact); - match &contacting.contact_behaviour{ - Some(crate::model::ContactingBehaviour::Ladder(_))=>{ - //ladder walkstate - let mut target_velocity=style.get_ladder_target_velocity(camera,controls,next_mouse,time,&normal); - self.constrain_velocity(models,&style_mesh,&mut target_velocity); - let (walk_state,mut acceleration)=WalkState::ladder(body,style,gravity,target_velocity,contact.clone(),&normal); - move_state=MoveState::Ladder(walk_state); - self.constrain_acceleration(models,&style_mesh,&mut acceleration); - a=acceleration; - }, - None=>if style.surf_slope.map_or(true,|s|normal.walkable(s,Planar64Vec3::Y)){ - //check ground - let mut target_velocity=style.get_walk_target_velocity(camera,controls,next_mouse,time,&normal); - self.constrain_velocity(models,&style_mesh,&mut target_velocity); - let (walk_state,mut acceleration)=WalkState::ground(body,style,gravity,target_velocity,contact.clone(),&normal); - move_state=MoveState::Walk(walk_state); - self.constrain_acceleration(models,&style_mesh,&mut acceleration); - a=acceleration; - }, - _=>(), - } - }, - _=>panic!("impossible touching state"), - } - } - for intersect in &self.intersects{ - //water - } - self.constrain_acceleration(models,&style_mesh,&mut a); - (move_state,a) - } - fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,style_mesh:&TransformedMesh,body:&Body,time:Time){ - let relative_body=VirtualBody::relative(&Body::default(),body).body(time); + fn predict_collision_end(&self,collector:&mut instruction::InstructionCollector,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,body:&Body,time:Time){ + let relative_body=VirtualBody::relative(&Body::ZERO,body).body(time); for contact in &self.contacts{ //detect face slide off - let model_mesh=models.mesh(contact.model_id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); - collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(face,time)|{ + let model_mesh=models.contact_mesh(contact); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); + collector.collect(minkowski.predict_collision_face_out(&relative_body,collector.time(),contact.face_id).map(|(_face,time)|{ TimedInstruction{ - time, - instruction:PhysicsInstruction::CollisionEnd( - Collision::Contact(ContactCollision{model_id:contact.model_id,face_id:contact.face_id}) + time:relative_body.time+time.into(), + instruction:PhysicsInternalInstruction::CollisionEnd( + Collision::Contact(*contact), + time ), } })); } for intersect in &self.intersects{ //detect model collision in reverse - let model_mesh=models.mesh(intersect.model_id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); - collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(face,time)|{ + let model_mesh=models.intersect_mesh(intersect); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); + collector.collect(minkowski.predict_collision_out(&relative_body,collector.time()).map(|(_face,time)|{ TimedInstruction{ - time, - instruction:PhysicsInstruction::CollisionEnd( - Collision::Intersect(IntersectCollision{model_id:intersect.model_id}) + time:relative_body.time+time.into(), + instruction:PhysicsInternalInstruction::CollisionEnd( + Collision::Intersect(*intersect), + time ), } })); @@ -978,7 +819,8 @@ impl TouchingState{ } impl Body{ - pub fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{ + pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO); + pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time)->Self{ Self{ position, velocity, @@ -988,20 +830,63 @@ impl Body{ } pub fn extrapolated_position(&self,time:Time)->Planar64Vec3{ let dt=time-self.time; - self.position+self.velocity*dt+self.acceleration*(dt*dt/2) + self.position + +(self.velocity*dt).map(|elem|elem.divide().fix_1()) + +self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1()) } pub fn extrapolated_velocity(&self,time:Time)->Planar64Vec3{ let dt=time-self.time; - self.velocity+self.acceleration*dt + self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1()) } pub fn advance_time(&mut self,time:Time){ self.position=self.extrapolated_position(time); self.velocity=self.extrapolated_velocity(time); self.time=time; } + pub fn extrapolated_position_ratio_dt(&self,dt:integer::Ratio)->Planar64Vec3 + where + // Why? + // All of this can be removed with const generics because the type can be specified as + // Ratio,Fixed> + // which is known to implement all the necessary traits + Num:Copy, + Den:Copy+core::ops::Mul, + D1:Copy, + Num:core::ops::Mul, + Planar64:core::ops::Mul, + N1:core::ops::Add, + Num:core::ops::Mul, + Den:core::ops::Mul, + D2:Copy, + Planar64:core::ops::Mul, + N4:integer::Divide, + T1:integer::Fix, + { + // a*dt^2/2 + v*dt + p + // (a*dt/2+v)*dt+p + (self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem)) + .map(|elem|elem.divide().fix())+self.position + } + pub fn extrapolated_velocity_ratio_dt(&self,dt:integer::Ratio)->Planar64Vec3 + where + Num:Copy, + Den:Copy, + Num:core::ops::Mul, + Planar64:core::ops::Mul, + N1:integer::Divide, + T1:integer::Fix, + { + // a*dt + v + self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity + } + pub fn advance_time_ratio_dt(&mut self,dt:model_physics::GigaTime){ + self.position=self.extrapolated_position_ratio_dt(dt); + self.velocity=self.extrapolated_velocity_ratio_dt(dt); + self.time+=dt.into(); + } pub fn infinity_dir(&self)->Option{ - if self.velocity==Planar64Vec3::ZERO{ - if self.acceleration==Planar64Vec3::ZERO{ + if self.velocity==vec3::ZERO{ + if self.acceleration==vec3::ZERO{ None }else{ Some(self.acceleration) @@ -1015,22 +900,22 @@ impl Body{ aabb.grow(self.extrapolated_position(t1)); //v+a*t==0 //goober code - if self.acceleration.x()!=Planar64::ZERO{ - let t=Time::from(-self.velocity.x()/self.acceleration.x()); - if t0{ body1:&'a Body, } impl VirtualBody<'_>{ - fn relative<'a>(body0:&'a Body,body1:&'a Body)->VirtualBody<'a>{ + const fn relative<'a>(body0:&'a Body,body1:&'a Body)->VirtualBody<'a>{ //(p0,v0,a0,t0) //(p1,v1,a1,t1) VirtualBody{ @@ -1069,147 +954,85 @@ impl VirtualBody<'_>{ } } +#[derive(Clone,Debug)] +pub struct PhysicsState{ + time:Time, + body:Body, + _world:WorldState,//currently there is only one state the world can be in + touching:TouchingState, + //camera must exist in state because wormholes modify the camera, also camera punch + camera:PhysicsCamera, + //input_state + input_state:InputState, + //style + style:StyleModifiers,//mode style with custom style updates applied + //gameplay_state + mode_state:ModeState, + move_state:MoveState, + //run is non optional: when you spawn in a run is created + //the run cannot be finished unless you start it by visiting + //a start zone. If you change mode, a new run is created. + run:run::Run, +} +//random collection of contextual data that doesn't belong in PhysicsState +pub struct PhysicsData{ + //permanent map data + bvh:bvh::BvhNode, + //transient map/environment data (open world loads/unloads parts of this data) + models:PhysicsModels, + //semi-transient data + modes:gameplay_modes::Modes, + //cached calculations + hitbox_mesh:HitboxMesh, +} impl Default for PhysicsState{ fn default()->Self{ Self{ - spawn_point:Planar64Vec3::int(0,50,0), - body:Body::new(Planar64Vec3::int(0,50,0),Planar64Vec3::int(0,0,0),Planar64Vec3::int(0,-100,0),Time::ZERO), + body:Body::new(vec3::int(0,50,0),vec3::int(0,0,0),vec3::int(0,-100,0),Time::ZERO), time:Time::ZERO, style:StyleModifiers::default(), touching:TouchingState::default(), - models:PhysicsModels::default(), - bvh:bvh::BvhNode::default(), - move_state: MoveState::Air, + move_state:MoveState::Air, camera:PhysicsCamera::default(), - next_mouse:MouseState::default(), - controls:0, - world:WorldState{}, - game:GameMechanicsState::default(), - modes:Modes::default(), + input_state:InputState::default(), + _world:WorldState{}, + mode_state:ModeState::default(), + run:run::Run::new(), + } + } +} +impl Default for PhysicsData{ + fn default()->Self{ + Self{ + bvh:bvh::BvhNode::default(), + models:Default::default(), + modes:Default::default(), + hitbox_mesh:StyleModifiers::default().calculate_mesh(), } } } -impl PhysicsState { - pub fn clear(&mut self){ - self.models.clear(); - self.modes.clear(); +impl PhysicsState{ + fn clear(&mut self){ self.touching.clear(); - self.bvh=bvh::BvhNode::default(); } - - pub fn output(&self)->PhysicsOutputState{ - PhysicsOutputState{ - body:self.body.clone(), - camera:self.camera.clone(), - camera_offset:self.style.camera_offset.clone(), - } + fn reset_to_default(&mut self){ + let mut new_state=Self::default(); + new_state.camera.sensitivity=self.camera.sensitivity; + *self=new_state; } - - pub fn spawn(&mut self,spawn_point:Planar64Vec3){ - self.game.stage_id=0; - self.spawn_point=spawn_point; - self.process_instruction(instruction::TimedInstruction{ - time:self.time, - instruction: PhysicsInstruction::Input(PhysicsInputInstruction::Reset), - }); + fn next_move_instruction(&self)->Option>{ + self.move_state.next_move_instruction(&self.style.strafe,self.time) } - - pub fn generate_models(&mut self,indexed_models:&crate::model::IndexedModelInstances){ - let mut starts=Vec::new(); - let mut spawns=Vec::new(); - let mut attr_hash=std::collections::HashMap::new(); - for model in &indexed_models.models{ - let mesh_id=self.models.meshes.len(); - let mut make_mesh=false; - for model_instance in &model.instances{ - if let Ok(physics_attributes)=PhysicsCollisionAttributes::try_from(&model_instance.attributes){ - let attr_id=if let Some(&attr_id)=attr_hash.get(&physics_attributes){ - attr_id - }else{ - let attr_id=self.models.push_attr(physics_attributes.clone()); - attr_hash.insert(physics_attributes,attr_id); - attr_id - }; - let model_physics=PhysicsModel::new(mesh_id,attr_id,model_instance.transform); - make_mesh=true; - let model_id=self.models.push_model(model_physics); - for attr in &model_instance.temp_indexing{ - match attr{ - crate::model::TempIndexedAttributes::Start(s)=>starts.push((model_id,s.clone())), - crate::model::TempIndexedAttributes::Spawn(s)=>spawns.push((model_id,s.clone())), - crate::model::TempIndexedAttributes::Wormhole(s)=>{self.models.model_id_from_wormhole_id.insert(s.wormhole_id,model_id);}, - } - } - } - } - if make_mesh{ - self.models.push_mesh(PhysicsMesh::from(model)); - } - } - self.bvh=bvh::generate_bvh(self.models.aabb_list()); - //I don't wanna write structs for temporary structures - //this code builds ModeDescriptions from the unsorted lists at the top of the function - starts.sort_by_key(|tup|tup.1.mode_id); - let mut mode_id_from_map_mode_id=std::collections::HashMap::new(); - let mut modedatas:Vec<(usize,Vec<(u32,usize)>,u32)>=starts.into_iter().enumerate().map(|(i,(model_id,s))|{ - mode_id_from_map_mode_id.insert(s.mode_id,i); - (model_id,Vec::new(),s.mode_id) - }).collect(); - for (model_id,s) in spawns{ - if let Some(mode_id)=mode_id_from_map_mode_id.get(&s.mode_id){ - if let Some(modedata)=modedatas.get_mut(*mode_id){ - modedata.1.push((s.stage_id,model_id)); - } - } - } - for mut tup in modedatas.into_iter(){ - tup.1.sort_by_key(|tup|tup.0); - let mut eshmep1=std::collections::HashMap::new(); - let mut eshmep2=std::collections::HashMap::new(); - self.modes.insert(tup.2,crate::model::ModeDescription{ - start:tup.0, - spawns:tup.1.into_iter().enumerate().map(|(i,tup)|{eshmep1.insert(tup.0,i);tup.1}).collect(), - spawn_from_stage_id:eshmep1, - ordered_checkpoint_from_checkpoint_id:eshmep2, - }); - } - println!("Physics Objects: {}",self.models.models.len()); + fn cull_velocity(&mut self,data:&PhysicsData,velocity:Planar64Vec3){ + self.move_state.cull_velocity(velocity,&mut self.body,&mut self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state); } - - pub fn load_user_settings(&mut self,user_settings:&crate::settings::UserSettings){ - self.camera.sensitivity=user_settings.calculate_sensitivity(); + fn set_move_state(&mut self,data:&PhysicsData,move_state:MoveState){ + self.move_state.set_move_state(move_state,&mut self.body,&self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state); } - - //tickless gaming - pub fn run(&mut self, time_limit:Time){ - //prepare is ommitted - everything is done via instructions. - while let Some(instruction) = self.next_instruction(time_limit) {//collect - //process - self.process_instruction(instruction); - //write hash lol - } + fn apply_input_and_body(&mut self,data:&PhysicsData){ + self.move_state.apply_input_and_body(&mut self.body,&self.touching,&data.models,&data.hitbox_mesh,&self.style,&self.camera,&self.input_state); } - - pub fn advance_time(&mut self, time: Time){ - self.body.advance_time(time); - self.time=time; - } - - fn set_control(&mut self,control:u32,state:bool){ - self.controls=if state{self.controls|control}else{self.controls&!control}; - } - - fn next_strafe_instruction(&self)->Option>{ - self.style.strafe.as_ref().map(|strafe|{ - TimedInstruction{ - time:Time::from_nanos(strafe.tick_rate.rhs_div_int(strafe.tick_rate.mul_int(self.time.nanos())+1)), - //only poll the physics if there is a before and after mouse event - instruction:PhysicsInstruction::StrafeTick - } - }) - } - //state mutated on collision: //Accelerator //stair step-up @@ -1240,653 +1063,1122 @@ impl PhysicsState { // instruction:PhysicsInstruction::Water // }); // } +} - fn refresh_walk_target(&mut self)->Planar64Vec3{ - match &mut self.move_state{ - MoveState::Air|MoveState::Water=>self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time), - MoveState::Walk(WalkState{state,contact,jump_direction:_})=>{ - let style_mesh=self.style.mesh(); - let n=contact_normal(&self.models,&style_mesh,contact); - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut a; - let mut v=self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&n); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); - let normal_accel=-n.dot(gravity)/n.length(); - (*state,a)=WalkEnum::with_target_velocity(&self.body,&self.style,v,&n,self.style.walk_speed,normal_accel); - a - }, - MoveState::Ladder(WalkState{state,contact,jump_direction:_})=>{ - let style_mesh=self.style.mesh(); - let n=contact_normal(&self.models,&style_mesh,contact); - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut a; - let mut v=self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&n); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); - (*state,a)=WalkEnum::with_target_velocity(&self.body,&self.style,v,&n,self.style.ladder_speed,self.style.ladder_accel); - a - }, +#[derive(Default)] +pub struct PhysicsContext{ + state:PhysicsState,//this captures the entire state of the physics. + data:PhysicsData,//data currently loaded into memory which is needded for physics to run, but is not part of the state. +} +//the physics consumes the generic PhysicsInstruction, but can only emit the more narrow PhysicsInternalInstruction +impl instruction::InstructionConsumer for PhysicsContext{ + fn process_instruction(&mut self,ins:TimedInstruction){ + atomic_state_update(&mut self.state,&self.data,ins) + } +} +impl instruction::InstructionEmitter for PhysicsContext{ + //this little next instruction function can cache its return value and invalidate the cached value by watching the State. + fn next_instruction(&self,time_limit:Time)->Option>{ + next_instruction_internal(&self.state,&self.data,time_limit) + } +} +impl PhysicsContext{ + pub fn camera_body(&self)->Body{ + Body{ + position:self.state.body.position+self.state.style.camera_offset, + ..self.state.body } } - fn next_move_instruction(&self)->Option>{ - //check if you have a valid walk state and create an instruction - match &self.move_state{ - MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>match &walk_state.state{ - WalkEnum::Transient(walk_target)=>Some(TimedInstruction{ - time:walk_target.time, - instruction:PhysicsInstruction::ReachWalkTargetVelocity - }), - WalkEnum::Reached=>None, - } - MoveState::Air=>self.next_strafe_instruction(), - MoveState::Water=>None,//TODO + pub const fn camera(&self)->PhysicsCamera{ + self.state.camera + } + pub const fn get_next_mouse(&self)->&MouseState{ + self.state.input_state.get_next_mouse() + } + /// use with caution, this is the only non-instruction way to mess with physics + pub fn generate_models(&mut self,map:&map::CompleteMap){ + self.state.clear(); + let mut modes=map.modes.clone(); + for mode in &mut modes.modes{ + mode.denormalize_data(); } + let mut used_contact_attributes=Vec::new(); + let mut used_intersect_attributes=Vec::new(); + + //temporary type for type safety lol + #[derive(Clone,Copy,Hash,Eq,PartialEq)] + enum PhysicsAttributesId{ + Contact(ContactAttributesId), + Intersect(IntersectAttributesId), + } + + let mut contact_models=HashMap::new(); + let mut intersect_models=HashMap::new(); + + let mut physics_attr_id_from_model_attr_id=HashMap::::new(); + let mut used_meshes=Vec::new(); + let mut physics_mesh_id_from_model_mesh_id=HashMap::::new(); + for (model_id,model) in map.models.iter().enumerate(){ + //TODO: use .entry().or_insert_with(||{ + let attr_id=if let Some(&attr_id)=physics_attr_id_from_model_attr_id.get(&model.attributes){ + attr_id + }else{ + //check if it's real + match map.attributes.get(model.attributes.get() as usize).and_then(|m_attr|{ + PhysicsCollisionAttributes::try_from(m_attr).map_or(None,|p_attr|{ + let attr_id=match p_attr{ + PhysicsCollisionAttributes::Contact(attr)=>{ + let attr_id=ContactAttributesId::new(used_contact_attributes.len() as u32); + used_contact_attributes.push(attr); + PhysicsAttributesId::Contact(attr_id) + }, + PhysicsCollisionAttributes::Intersect(attr)=>{ + let attr_id=IntersectAttributesId::new(used_intersect_attributes.len() as u32); + used_intersect_attributes.push(attr); + PhysicsAttributesId::Intersect(attr_id) + }, + }; + physics_attr_id_from_model_attr_id.insert(model.attributes,attr_id); + Some(attr_id) + }) + }){ + Some(attr_id)=>attr_id, + None=>continue, + } + }; + let mesh_id=if let Some(&mesh_id)=physics_mesh_id_from_model_mesh_id.get(&model.mesh){ + mesh_id + }else{ + match map.meshes.get(model.mesh.get() as usize).and_then(|mesh|{ + match PhysicsMesh::try_from(mesh){ + Ok(physics_mesh)=>{ + let mesh_id=PhysicsMeshId::new(used_meshes.len() as u32); + used_meshes.push(physics_mesh); + physics_mesh_id_from_model_mesh_id.insert(model.mesh,mesh_id); + Some(mesh_id) + }, + Err(e)=>{ + println!("Failed to build PhysicsMesh: {e}"); + None + } + } + }){ + Some(mesh_id)=>mesh_id, + None=>continue, + } + }; + let transform=PhysicsMeshTransform::new(model.transform); + match attr_id{ + PhysicsAttributesId::Contact(attr_id)=>{ + contact_models.insert(ContactModelId::new(model_id as u32),ContactModel{ + mesh_id, + attr_id, + transform, + }); + }, + PhysicsAttributesId::Intersect(attr_id)=>{ + intersect_models.insert(IntersectModelId::new(model_id as u32),IntersectModel{ + mesh_id, + attr_id, + transform, + }); + }, + } + } + let meshes:HashMap=used_meshes.into_iter() + .enumerate() + .map(|(mesh_id,mesh)| + (PhysicsMeshId::new(mesh_id as u32),mesh) + ).collect(); + let convex_mesh_aabb_list= + //map the two lists into a single type so they can be processed with one closure + contact_models.iter().map(|(&model_id,model)| + (PhysicsModelId::Contact(model_id),&model.mesh_id,&model.transform) + ).chain(intersect_models.iter().map(|(&model_id,model)| + (PhysicsModelId::Intersect(model_id),&model.mesh_id,&model.transform) + )) + .flat_map(|(model_id,mesh_id,transform)|{ + meshes[mesh_id].submesh_views() + .enumerate().map(move|(submesh_id,view)|{ + let mut aabb=aabb::Aabb::default(); + let transformed_mesh=TransformedMesh::new(view,transform); + for v in transformed_mesh.verts(){ + aabb.grow(v.fix_1()); + } + (ConvexMeshId{ + model_id, + submesh_id:PhysicsSubmeshId::new(submesh_id as u32), + },aabb) + }) + }).collect(); + let bvh=bvh::generate_bvh(convex_mesh_aabb_list); + let model_count=contact_models.len()+intersect_models.len(); + let models=PhysicsModels{ + meshes, + contact_models, + intersect_models, + contact_attributes:used_contact_attributes.into_iter() + .enumerate() + .map(|(attr_id,attr)| + (ContactAttributesId::new(attr_id as u32),attr) + ).collect(), + intersect_attributes:used_intersect_attributes.into_iter() + .enumerate() + .map(|(attr_id,attr)| + (IntersectAttributesId::new(attr_id as u32),attr) + ).collect(), + }; + self.data.bvh=bvh; + self.data.models=models; + self.data.modes=modes; + //hitbox_mesh is unchanged + println!("Physics Objects: {}",model_count); + } + + //tickless gaming + fn run_internal_exhaustive(&mut self,time_limit:Time){ + //prepare is ommitted - everything is done via instructions. + while let Some(instruction)=self.next_instruction(time_limit){//collect + //process + self.process_instruction(TimedInstruction{ + time:instruction.time, + instruction:PhysicsInstruction::Internal(instruction.instruction), + }); + //write hash lol + } + } + pub fn run_input_instruction(&mut self,instruction:TimedInstruction){ + self.run_internal_exhaustive(instruction.time); + self.process_instruction(TimedInstruction{ + time:instruction.time, + instruction:PhysicsInstruction::Input(instruction.instruction), + }); } } -impl instruction::InstructionEmitter for PhysicsState{ - //this little next instruction function can cache its return value and invalidate the cached value by watching the State. - fn next_instruction(&self,time_limit:Time)->Option>{ + //this is the one who asks + fn next_instruction_internal(state:&PhysicsState,data:&PhysicsData,time_limit:Time)->Option>{ //JUST POLLING!!! NO MUTATION let mut collector = instruction::InstructionCollector::new(time_limit); - collector.collect(self.next_move_instruction()); + collector.collect(state.next_move_instruction()); - let style_mesh=self.style.mesh(); //check for collision ends - self.touching.predict_collision_end(&mut collector,&self.models,&style_mesh,&self.body,self.time); + state.touching.predict_collision_end(&mut collector,&data.models,&data.hitbox_mesh,&state.body,state.time); //check for collision starts let mut aabb=aabb::Aabb::default(); - self.body.grow_aabb(&mut aabb,self.time,collector.time()); - aabb.inflate(self.style.hitbox.halfsize); - //common body - let relative_body=VirtualBody::relative(&Body::default(),&self.body).body(self.time); - self.bvh.the_tester(&aabb,&mut |id|{ + state.body.grow_aabb(&mut aabb,state.time,collector.time()); + aabb.inflate(data.hitbox_mesh.halfsize); + //relative to moving platforms + //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time); + let relative_body=&state.body; + data.bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ //no checks are needed because of the time limits. - let model_mesh=self.models.mesh(id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,&style_mesh); - collector.collect(minkowski.predict_collision_in(&relative_body,collector.time()) + let model_mesh=data.models.mesh(convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,data.hitbox_mesh.transformed_mesh()); + collector.collect(minkowski.predict_collision_in(relative_body,collector.time()) //temp (?) code to avoid collision loops - .map_or(None,|(face,time)|if time==self.time{None}else{Some((face,time))}) - .map(|(face,time)|{ - TimedInstruction{time,instruction:PhysicsInstruction::CollisionStart(match self.models.attr(id){ - PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>Collision::Contact(ContactCollision{model_id:id,face_id:face}), - PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>Collision::Intersect(IntersectCollision{model_id:id}), - })} - })); + .map_or(None,|(face,dt)|{ + let time=relative_body.time+dt.into(); + if time<=state.time{None}else{Some((time,face,dt))}}) + .map(|(time,face,dt)| + TimedInstruction{ + time, + instruction:PhysicsInternalInstruction::CollisionStart( + Collision::new(convex_mesh_id,face), + dt + ) + } + ) + ); }); collector.instruction() } + + +fn contact_normal(models:&PhysicsModels,hitbox_mesh:&HitboxMesh,contact:&ContactCollision)->Planar64Vec3{ + let model_mesh=models.contact_mesh(contact); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); + // TODO: normalize to i64::MAX>>1 + minkowski.face_nd(contact.face_id).0.fix_1() } -fn get_walk_state(move_state:&MoveState)->Option<&WalkState>{ - match move_state{ - MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>Some(walk_state), - MoveState::Air|MoveState::Water=>None, +fn recalculate_touching( + move_state:&mut MoveState, + body:&mut Body, + touching:&mut TouchingState, + run:&mut run::Run, + mode_state:&mut ModeState, + mode:Option<&gameplay_modes::Mode>, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + bvh:&bvh::BvhNode, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + time:Time, +){ + //collision_end all existing contacts + //I would have preferred while let Some(contact)=contacts.pop() + //but there is no such method + while let Some(&contact)=touching.contacts.iter().next(){ + collision_end_contact(move_state,body,touching,models,hitbox_mesh,style,camera,input_state,models.contact_attr(contact.model_id),contact) } + while let Some(&intersect)=touching.intersects.iter().next(){ + collision_end_intersect(touching,mode,run,models.intersect_attr(intersect.model_id),intersect,time); + } + //find all models in the teleport region + let mut aabb=aabb::Aabb::default(); + aabb.grow(body.position); + aabb.inflate(hitbox_mesh.halfsize); + //relative to moving platforms + //let relative_body=&VirtualBody::relative(&Body::default(),&state.body).body(state.time); + bvh.the_tester(&aabb,&mut |&convex_mesh_id|{ + //no checks are needed because of the time limits. + let model_mesh=models.mesh(convex_mesh_id); + let minkowski=model_physics::MinkowskiMesh::minkowski_sum(model_mesh,hitbox_mesh.transformed_mesh()); + if minkowski.is_point_in_mesh(body.position){ + match convex_mesh_id.model_id{ + //being inside of contact objects is an invalid physics state + //but the physics isn't advanced enough to do anything about it yet + //TODO: PushSolve and search for the closest valid position + PhysicsModelId::Contact(_)=>(), + PhysicsModelId::Intersect(model_id)=> + collision_start_intersect(move_state,body,mode_state,touching,mode,run,models,hitbox_mesh,bvh,style,camera,input_state, + models.intersect_attr(model_id), + IntersectCollision{ + model_id, + submesh_id:convex_mesh_id.submesh_id, + }, + time, + ), + } + } + }); } - -fn jumped_velocity(models:&PhysicsModels,style:&StyleModifiers,walk_state:&WalkState,v:&mut Planar64Vec3){ - let jump_dir=match &walk_state.jump_direction{ - JumpDirection::FromContactNormal=>contact_normal(models,&style.mesh(),&walk_state.contact), - &JumpDirection::Exactly(dir)=>dir, - }; - *v=*v+jump_dir*(style.get_jump_deltav()/jump_dir.length()); -} - -fn contact_normal(models:&PhysicsModels,style_mesh:&TransformedMesh,contact:&ContactCollision)->Planar64Vec3{ - let model_mesh=models.mesh(contact.model_id); - let minkowski=crate::model_physics::MinkowskiMesh::minkowski_sum(&model_mesh,style_mesh); - minkowski.face_nd(contact.face_id).0 -} - -fn set_position(body:&mut Body,touching:&mut TouchingState,point:Planar64Vec3)->Planar64Vec3{ +fn set_position( + point:Planar64Vec3, + move_state:&mut MoveState, + body:&mut Body, + touching:&mut TouchingState, + run:&mut run::Run, + mode_state:&mut ModeState, + mode:Option<&gameplay_modes::Mode>, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + bvh:&bvh::BvhNode, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + time:Time, +)->Planar64Vec3{ //test intersections at new position //hovering above the surface 0 units is not intersecting. you will fall into it just fine body.position=point; - //manual clear //for c in contacts{process_instruction(CollisionEnd(c))} - touching.clear(); - //TODO: calculate contacts and determine the actual state - //touching.recalculate(body); + //calculate contacts and determine the actual state + recalculate_touching(move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); point } -fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,v:Planar64Vec3)->bool{ +fn set_velocity_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,v:Planar64Vec3)->bool{ //This is not correct but is better than what I have let mut culled=false; touching.contacts.retain(|contact|{ - let n=contact_normal(models,style_mesh,contact); - let r=n.dot(v)<=Planar64::ZERO; - if !r{ + let n=contact_normal(models,hitbox_mesh,contact); + let r=n.dot(v).is_positive(); + if r{ culled=true; println!("set_velocity_cull contact={:?}",contact); } - r + !r }); - set_velocity(body,touching,models,style_mesh,v); + set_velocity(body,touching,models,hitbox_mesh,v); culled } -fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,mut v:Planar64Vec3)->Planar64Vec3{ - touching.constrain_velocity(models,style_mesh,&mut v); +fn set_velocity(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut v:Planar64Vec3){ + touching.constrain_velocity(models,hitbox_mesh,&mut v); body.velocity=v; - v } -fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,a:Planar64Vec3)->bool{ +fn set_acceleration_cull(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,a:Planar64Vec3)->bool{ //This is not correct but is better than what I have let mut culled=false; touching.contacts.retain(|contact|{ - let n=contact_normal(models,style_mesh,contact); - let r=n.dot(a)<=Planar64::ZERO; - if !r{ + let n=contact_normal(models,hitbox_mesh,contact); + let r=n.dot(a).is_positive(); + if r{ culled=true; println!("set_acceleration_cull contact={:?}",contact); } - r + !r }); - set_acceleration(body,touching,models,style_mesh,a); + set_acceleration(body,touching,models,hitbox_mesh,a); culled } -fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,style_mesh:&TransformedMesh,mut a:Planar64Vec3)->Planar64Vec3{ - touching.constrain_acceleration(models,style_mesh,&mut a); +fn set_acceleration(body:&mut Body,touching:&TouchingState,models:&PhysicsModels,hitbox_mesh:&HitboxMesh,mut a:Planar64Vec3){ + touching.constrain_acceleration(models,hitbox_mesh,&mut a); body.acceleration=a; - a } -fn teleport(body:&mut Body,touching:&mut TouchingState,models:&PhysicsModels,style:&StyleModifiers,point:Planar64Vec3)->MoveState{ - set_position(body,touching,point); - set_acceleration(body,touching,models,&style.mesh(),style.gravity); - MoveState::Air +fn teleport( + point:Planar64Vec3, + move_state:&mut MoveState, + body:&mut Body, + touching:&mut TouchingState, + run:&mut run::Run, + mode_state:&mut ModeState, + mode:Option<&gameplay_modes::Mode>, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + bvh:&bvh::BvhNode, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + time:Time, +){ + set_position(point,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); + set_acceleration(body,touching,models,hitbox_mesh,style.gravity); } -fn teleport_to_spawn(body:&mut Body,touching:&mut TouchingState,style:&StyleModifiers,mode:&crate::model::ModeDescription,models:&PhysicsModels,stage_id:u32)->Option{ - let model=models.model(*mode.get_spawn_model_id(stage_id)? as usize); - let point=model.transform.transform_point3(Planar64Vec3::Y)+Planar64Vec3::Y*(style.hitbox.halfsize.y()+Planar64::ONE/16); - Some(teleport(body,touching,models,style,point)) +enum TeleportToSpawnError{ + NoModel, +} +fn teleport_to_spawn( + stage:&gameplay_modes::Stage, + move_state:&mut MoveState, + body:&mut Body, + touching:&mut TouchingState, + run:&mut run::Run, + mode_state:&mut ModeState, + mode:&gameplay_modes::Mode, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + bvh:&bvh::BvhNode, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + time:Time, +)->Result<(),TeleportToSpawnError>{ + const EPSILON:Planar64=Planar64::raw((1<<32)/16); + let transform=models.get_model_transform(stage.spawn()).ok_or(TeleportToSpawnError::NoModel)?; + //TODO: transform.vertex.matrix3.col(1)+transform.vertex.translation + let point=transform.vertex.transform_point3(vec3::Y).fix_1()+Planar64Vec3::new([Planar64::ZERO,style.hitbox.halfsize.y+EPSILON,Planar64::ZERO]); + teleport(point,move_state,body,touching,run,mode_state,Some(mode),models,hitbox_mesh,bvh,style,camera,input_state,time); + Ok(()) } -fn run_teleport_behaviour(teleport_behaviour:&Option,game:&mut GameMechanicsState,models:&PhysicsModels,modes:&Modes,style:&StyleModifiers,touching:&mut TouchingState,body:&mut Body,model_id:usize)->Option{ +fn run_teleport_behaviour( + model_id:ModelId, + wormhole:Option<&gameplay_attributes::Wormhole>, + mode:Option<&gameplay_modes::Mode>, + move_state:&mut MoveState, + body:&mut Body, + touching:&mut TouchingState, + run:&mut run::Run, + mode_state:&mut ModeState, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + bvh:&bvh::BvhNode, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + time:Time, +){ //TODO: jump count and checkpoints are always reset on teleport. //Map makers are expected to use tools to prevent //multi-boosting on JumpLimit boosters such as spawning into a SetVelocity - match teleport_behaviour{ - Some(crate::model::TeleportBehaviour::StageElement(stage_element))=>{ - if stage_element.force||game.stage_idNone, - crate::model::StageElementBehaviour::Trigger - |crate::model::StageElementBehaviour::Teleport=>{ - //I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird - teleport_to_spawn(body,touching,style,modes.get_mode(stage_element.mode_id)?,models,game.stage_id) - }, - crate::model::StageElementBehaviour::Platform=>None, - &crate::model::StageElementBehaviour::Checkpoint=>{ - // let mode=modes.get_mode(stage_element.mode_id)?; - // if mode.ordered_checkpoint_id.map_or(true,|id|id{ - if checkpoint_idif !stage.is_empty(){ + mode_state.set_stage_id(stage_id); + let _=teleport_to_spawn(stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); + return; + }, + None=>{ + //no such stage! set to last existing stage and break loop + mode_state.set_stage_id(StageId::new(stage_id.get()-1)); + loop_unbroken=false; + break; + }, + } + }; + //notably you do not get teleported for touching ordered checkpoints in the wrong order within the same stage. + if loop_unbroken{ + mode_state.set_stage_id(stage_element.stage_id()); + } + }else if stage_element.force(){ + //forced stage_element will set the stage_id even if the stage has already been passed + mode_state.set_stage_id(stage_element.stage_id()); + } + match stage_element.behaviour(){ + gameplay_modes::StageElementBehaviour::SpawnAt=>(), + gameplay_modes::StageElementBehaviour::Trigger + |gameplay_modes::StageElementBehaviour::Teleport=>if let Some(mode_state_stage)=mode.get_stage(mode_state.get_stage_id()){ + //I guess this is correct behaviour when trying to teleport to a non-existent spawn but it's still weird + let _=teleport_to_spawn(mode_state_stage,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); + return; }, - crate::model::StageElementBehaviour::Unordered=>{ - //count model id in accumulated unordered checkpoints - game.unordered_checkpoints.insert(model_id); - None - }, - &crate::model::StageElementBehaviour::JumpLimit(jump_limit)=>{ - //let count=game.jump_counts.get(&model.id); - //TODO - None + gameplay_modes::StageElementBehaviour::Platform=>(), + gameplay_modes::StageElementBehaviour::Check=>(),//this is to run the checkpoint check behaviour without any other side effects + gameplay_modes::StageElementBehaviour::Checkpoint=>{ + //each of these checks if the model is actually a valid respective checkpoint object + //accumulate sequential ordered checkpoints + mode_state.accumulate_ordered_checkpoint(&stage,model_id); + //insert model id in accumulated unordered checkpoints + mode_state.accumulate_unordered_checkpoint(&stage,model_id); }, } - }, - Some(crate::model::TeleportBehaviour::Wormhole(wormhole))=>{ - let origin_model=models.model(model_id); - let destination_model=models.get_wormhole_model(wormhole.destination_model_id)?; - //ignore the transform for now - Some(teleport(body,touching,models,style,body.position-origin_model.transform.translation+destination_model.transform.translation)) } - None=>None, + } + } + if let Some(&gameplay_attributes::Wormhole{destination_model})=wormhole{ + if let (Some(origin),Some(destination))=(models.get_model_transform(model_id),models.get_model_transform(destination_model)){ + let point=body.position-origin.vertex.translation+destination.vertex.translation; + //TODO: camera angles + teleport(point,move_state,body,touching,run,mode_state,mode,models,hitbox_mesh,bvh,style,camera,input_state,time); + } } } -impl instruction::InstructionConsumer for PhysicsState { - fn process_instruction(&mut self, ins:TimedInstruction) { +fn collision_start_contact( + move_state:&mut MoveState, + body:&mut Body, + mode_state:&mut ModeState, + touching:&mut TouchingState, + run:&mut run::Run, + mode:Option<&gameplay_modes::Mode>, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + bvh:&bvh::BvhNode, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + attr:&gameplay_attributes::ContactAttributes, + contact:ContactCollision, + time:Time, +){ + let incident_velocity=body.velocity; + //add to touching + touching.insert(Collision::Contact(contact)); + //clip v + set_velocity(body,touching,models,hitbox_mesh,incident_velocity); + match &attr.contacting.contact_behaviour{ + Some(gameplay_attributes::ContactingBehaviour::Surf)=>println!("I'm surfing!"), + Some(gameplay_attributes::ContactingBehaviour::Cling)=>println!("Unimplemented!"), + &Some(gameplay_attributes::ContactingBehaviour::Elastic(elasticity))=>{ + let reflected_velocity=body.velocity+((body.velocity-incident_velocity)*Planar64::raw(elasticity as i64+1)).fix_1(); + set_velocity(body,touching,models,hitbox_mesh,reflected_velocity); + }, + Some(gameplay_attributes::ContactingBehaviour::Ladder(contacting_ladder))=> + if let Some(ladder_settings)=&style.ladder{ + if contacting_ladder.sticky{ + //kill v + //actually you could do this with a booster attribute :thinking: + //it's a little bit different because maybe you want to chain ladders together + set_velocity(body,touching,models,hitbox_mesh,vec3::ZERO);//model.velocity + } + //ladder walkstate + let (gravity,target_velocity)=ladder_things(ladder_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state); + let walk_state=ContactMoveState::ladder(ladder_settings,body,gravity,target_velocity,contact); + move_state.set_move_state(MoveState::Ladder(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state); + }, + Some(gameplay_attributes::ContactingBehaviour::NoJump)=>todo!("nyi"), + None=>if let Some(walk_settings)=&style.walk{ + if walk_settings.is_slope_walkable(contact_normal(models,hitbox_mesh,&contact),vec3::Y){ + //ground + let (gravity,target_velocity)=ground_things(walk_settings,&contact,touching,models,hitbox_mesh,style,camera,input_state); + let walk_state=ContactMoveState::ground(walk_settings,body,gravity,target_velocity,contact); + move_state.set_move_state(MoveState::Walk(walk_state),body,touching,models,hitbox_mesh,style,camera,input_state); + } + }, + } + //I love making functions with 10 arguments to dodge the borrow checker + run_teleport_behaviour(contact.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time); + if style.get_control(Controls::Jump,input_state.controls){ + if let (Some(jump_settings),Some(walk_state))=(&style.jump,move_state.get_walk_state()){ + let jump_dir=walk_state.jump_direction.direction(models,hitbox_mesh,&walk_state.contact); + let jumped_velocity=jump_settings.jumped_velocity(style,jump_dir,body.velocity,attr.general.booster.as_ref()); + move_state.cull_velocity(jumped_velocity,body,touching,models,hitbox_mesh,style,camera,input_state); + } + } + match &attr.general.trajectory{ + Some(trajectory)=>{ + match trajectory{ + gameplay_attributes::SetTrajectory::AirTime(_)=>todo!(), + gameplay_attributes::SetTrajectory::Height(_)=>todo!(), + gameplay_attributes::SetTrajectory::TargetPointTime { target_point: _, time: _ }=>todo!(), + gameplay_attributes::SetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ }=>todo!(), + &gameplay_attributes::SetTrajectory::Velocity(velocity)=>{ + move_state.cull_velocity(velocity,body,touching,models,hitbox_mesh,style,camera,input_state); + }, + gameplay_attributes::SetTrajectory::DotVelocity { direction: _, dot: _ }=>todo!(), + } + }, + None=>(), + } + //doing enum to set the acceleration when surfing + //doing input_and_body to refresh the walk state if you hit a wall while accelerating + move_state.apply_enum_and_input_and_body(body,touching,models,hitbox_mesh,style,camera,input_state); +} + +fn collision_start_intersect( + move_state:&mut MoveState, + body:&mut Body, + mode_state:&mut ModeState, + touching:&mut TouchingState, + mode:Option<&gameplay_modes::Mode>, + run:&mut run::Run, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + bvh:&bvh::BvhNode, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + attr:&gameplay_attributes::IntersectAttributes, + intersect:IntersectCollision, + time:Time, +){ + //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop + touching.insert(Collision::Intersect(intersect)); + //insta booster! + if let Some(booster)=&attr.general.booster{ + move_state.cull_velocity(booster.boost(body.velocity),body,touching,models,hitbox_mesh,style,camera,input_state); + } + if let Some(mode)=mode{ + let zone=mode.get_zone(intersect.model_id.into()); + match zone{ + Some(gameplay_modes::Zone::Start)=>{ + println!("@@@@ Starting new run!"); + *run=run::Run::new(); + }, + Some(gameplay_modes::Zone::Finish)=>{ + match run.finish(time){ + Ok(())=>println!("@@@@ Finished run time={}",run.time(time)), + Err(e)=>println!("@@@@ Run Finish error:{e:?}"), + } + }, + Some(gameplay_modes::Zone::Anticheat)=>run.flag(run::FlagReason::Anticheat), + None=>(), + } + } + run_teleport_behaviour(intersect.model_id.into(),attr.general.wormhole.as_ref(),mode,move_state,body,touching,run,mode_state,models,hitbox_mesh,bvh,style,camera,input_state,time); +} + +fn collision_end_contact( + move_state:&mut MoveState, + body:&mut Body, + touching:&mut TouchingState, + models:&PhysicsModels, + hitbox_mesh:&HitboxMesh, + style:&StyleModifiers, + camera:&PhysicsCamera, + input_state:&InputState, + _attr:&gameplay_attributes::ContactAttributes, + contact:ContactCollision, +){ + touching.remove(&Collision::Contact(contact));//remove contact before calling contact_constrain_acceleration + //check ground + //TODO do better + //this is inner code from move_state.cull_velocity + match move_state.get_walk_state(){ + //did you stop touching the thing you were walking on? + Some(walk_state)=>if walk_state.contact==contact{ + move_state.set_move_state(MoveState::Air,body,touching,models,hitbox_mesh,style,camera,input_state); + }, + None=>move_state.apply_enum_and_body(body,touching,models,hitbox_mesh,style,camera,input_state), + } +} +fn collision_end_intersect( + touching:&mut TouchingState, + mode:Option<&gameplay_modes::Mode>, + run:&mut run::Run, + _attr:&gameplay_attributes::IntersectAttributes, + intersect:IntersectCollision, + time:Time, +){ + touching.remove(&Collision::Intersect(intersect)); + if let Some(mode)=mode{ + let zone=mode.get_zone(intersect.model_id.into()); + match zone{ + Some(gameplay_modes::Zone::Start)=>{ + match run.start(time){ + Ok(())=>println!("@@@@ Started run"), + Err(e)=>println!("@@@@ Run Start error:{e:?}"), + } + }, + _=>(), + } + } +} +fn atomic_internal_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ + state.time=ins.time; + let (should_advance_body,goober_time)=match ins.instruction{ + PhysicsInternalInstruction::CollisionStart(_,dt) + |PhysicsInternalInstruction::CollisionEnd(_,dt)=>(true,Some(dt)), + PhysicsInternalInstruction::StrafeTick + |PhysicsInternalInstruction::ReachWalkTargetVelocity=>(true,None), + }; + if should_advance_body{ + match goober_time{ + Some(dt)=>state.body.advance_time_ratio_dt(dt), + None=>state.body.advance_time(state.time), + } + } + match ins.instruction{ + PhysicsInternalInstruction::CollisionStart(collision,_)=>{ + let mode=data.modes.get_mode(state.mode_state.get_mode_id()); + match collision{ + Collision::Contact(contact)=>collision_start_contact( + &mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching,&mut state.run, + mode, + &data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state, + data.models.contact_attr(contact.model_id), + contact, + state.time, + ), + Collision::Intersect(intersect)=>collision_start_intersect( + &mut state.move_state,&mut state.body,&mut state.mode_state,&mut state.touching, + mode, + &mut state.run,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state, + data.models.intersect_attr(intersect.model_id), + intersect, + state.time, + ), + } + }, + PhysicsInternalInstruction::CollisionEnd(collision,_)=>match collision{ + Collision::Contact(contact)=>collision_end_contact( + &mut state.move_state,&mut state.body,&mut state.touching,&data.models,&data.hitbox_mesh,&state.style,&state.camera,&state.input_state, + data.models.contact_attr(contact.model_id), + contact + ), + Collision::Intersect(intersect)=>collision_end_intersect( + &mut state.touching, + data.modes.get_mode(state.mode_state.get_mode_id()), + &mut state.run, + data.models.intersect_attr(intersect.model_id), + intersect, + state.time + ), + }, + PhysicsInternalInstruction::StrafeTick=>{ + //TODO make this less huge + if let Some(strafe_settings)=&state.style.strafe{ + let controls=state.input_state.controls; + if strafe_settings.activates(controls){ + let masked_controls=strafe_settings.mask(controls); + let control_dir=state.style.get_control_dir(masked_controls); + if control_dir!=vec3::ZERO{ + let camera_mat=state.camera.simulate_move_rotation_y(state.input_state.lerp_delta(state.time).x); + if let Some(ticked_velocity)=strafe_settings.tick_velocity(state.body.velocity,(camera_mat*control_dir).with_length(Planar64::ONE).divide().fix_1()){ + //this is wrong but will work ig + //need to note which push planes activate in push solve and keep those + state.cull_velocity(data,ticked_velocity); + } + } + } + } + } + PhysicsInternalInstruction::ReachWalkTargetVelocity=>{ + match &mut state.move_state{ + MoveState::Air + |MoveState::Water + |MoveState::Fly + =>println!("ReachWalkTargetVelocity fired for non-walking MoveState"), + MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{ + match &walk_state.target{ + //you are not supposed to reach a walk target which is already reached! + TransientAcceleration::Reached=>unreachable!(), + TransientAcceleration::Reachable{acceleration:_,time:_}=>{ + //velocity is already handled by advance_time + //we know that the acceleration is precisely zero because the walk target is known to be reachable + //which means that gravity can be fully cancelled + //ignore moving platforms for now + set_acceleration(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); + walk_state.target=TransientAcceleration::Reached; + }, + //you are not supposed to reach an unreachable walk target! + TransientAcceleration::Unreachable{acceleration:_}=>unreachable!(), + } + } + } + }, + } + } + +fn atomic_input_instruction(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ + state.time=ins.time; + let should_advance_body=match ins.instruction{ + //the body may as well be a quantum wave function + //as far as these instruction are concerned (they don't care where it is) + PhysicsInputInstruction::SetSensitivity(..) + |PhysicsInputInstruction::Reset + |PhysicsInputInstruction::Restart + |PhysicsInputInstruction::Spawn(..) + |PhysicsInputInstruction::SetZoom(..) + |PhysicsInputInstruction::Idle=>false, + //these controls only update the body if you are on the ground + PhysicsInputInstruction::SetNextMouse(..) + |PhysicsInputInstruction::ReplaceMouse(..) + |PhysicsInputInstruction::SetMoveForward(..) + |PhysicsInputInstruction::SetMoveLeft(..) + |PhysicsInputInstruction::SetMoveBack(..) + |PhysicsInputInstruction::SetMoveRight(..) + |PhysicsInputInstruction::SetMoveUp(..) + |PhysicsInputInstruction::SetMoveDown(..) + |PhysicsInputInstruction::SetJump(..)=>{ + match &state.move_state{ + MoveState::Fly + |MoveState::Water + |MoveState::Walk(_) + |MoveState::Ladder(_)=>true, + MoveState::Air=>false, + } + }, + //the body must be updated unconditionally + PhysicsInputInstruction::PracticeFly=>true, + }; + if should_advance_body{ + state.body.advance_time(state.time); + } + //TODO: UNTAB + let mut b_refresh_walk_target=true; + match ins.instruction{ + PhysicsInputInstruction::SetSensitivity(sensitivity)=>state.camera.sensitivity=sensitivity, + PhysicsInputInstruction::SetNextMouse(m)=>{ + state.camera.move_mouse(state.input_state.mouse_delta()); + state.input_state.set_next_mouse(m); + }, + PhysicsInputInstruction::ReplaceMouse(m0,m1)=>{ + state.camera.move_mouse(m0.pos-state.input_state.mouse.pos); + state.input_state.replace_mouse(m0,m1); + }, + PhysicsInputInstruction::SetMoveForward(s)=>state.input_state.set_control(Controls::MoveForward,s), + PhysicsInputInstruction::SetMoveLeft(s)=>state.input_state.set_control(Controls::MoveLeft,s), + PhysicsInputInstruction::SetMoveBack(s)=>state.input_state.set_control(Controls::MoveBackward,s), + PhysicsInputInstruction::SetMoveRight(s)=>state.input_state.set_control(Controls::MoveRight,s), + PhysicsInputInstruction::SetMoveUp(s)=>state.input_state.set_control(Controls::MoveUp,s), + PhysicsInputInstruction::SetMoveDown(s)=>state.input_state.set_control(Controls::MoveDown,s), + PhysicsInputInstruction::SetJump(s)=>{ + state.input_state.set_control(Controls::Jump,s); + if let Some(walk_state)=state.move_state.get_walk_state(){ + if let Some(jump_settings)=&state.style.jump{ + let jump_dir=walk_state.jump_direction.direction(&data.models,&data.hitbox_mesh,&walk_state.contact); + let booster_option=data.models.contact_attr(walk_state.contact.model_id).general.booster.as_ref(); + let jumped_velocity=jump_settings.jumped_velocity(&state.style,jump_dir,state.body.velocity,booster_option); + state.cull_velocity(&data,jumped_velocity); + } + } + b_refresh_walk_target=false; + }, + PhysicsInputInstruction::SetZoom(s)=>{ + state.input_state.set_control(Controls::Zoom,s); + b_refresh_walk_target=false; + }, + PhysicsInputInstruction::Reset=>{ + //totally reset physics state + state.reset_to_default(); + b_refresh_walk_target=false; + }, + PhysicsInputInstruction::Restart=>{ + //teleport to start zone + let mode=data.modes.get_mode(state.mode_state.get_mode_id()); + let spawn_point=mode.and_then(|mode| + //TODO: spawn at the bottom of the start zone plus the hitbox size + //TODO: set camera andles to face the same way as the start zone + data.models.get_model_transform(mode.get_start().into()).map(|transform| + transform.vertex.translation + ) + ).unwrap_or(vec3::ZERO); + set_position(spawn_point,&mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state,mode,&data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time); + set_velocity(&mut state.body,&state.touching,&data.models,&data.hitbox_mesh,vec3::ZERO); + state.set_move_state(data,MoveState::Air); + b_refresh_walk_target=false; + } + PhysicsInputInstruction::Spawn(mode_id,stage_id)=>{ + //spawn at a particular stage + if let Some(mode)=data.modes.get_mode(mode_id){ + if let Some(stage)=mode.get_stage(stage_id){ + let _=teleport_to_spawn( + stage, + &mut state.move_state,&mut state.body,&mut state.touching,&mut state.run,&mut state.mode_state, + mode, + &data.models,&data.hitbox_mesh,&data.bvh,&state.style,&state.camera,&state.input_state,state.time + ); + } + } + b_refresh_walk_target=false; + }, + PhysicsInputInstruction::PracticeFly=>{ + match &state.move_state{ + MoveState::Fly=>{ + state.set_move_state(data,MoveState::Air); + }, + _=>{ + state.set_move_state(data,MoveState::Fly); + }, + } + b_refresh_walk_target=false; + }, + PhysicsInputInstruction::Idle=>{ + //literally idle! + b_refresh_walk_target=false; + }, + } + if b_refresh_walk_target{ + state.apply_input_and_body(data); + state.cull_velocity(data,state.body.velocity); + //also check if accelerating away from surface + } +} + + fn atomic_state_update(state:&mut PhysicsState,data:&PhysicsData,ins:TimedInstruction){ match &ins.instruction{ PhysicsInstruction::Input(PhysicsInputInstruction::Idle) |PhysicsInstruction::Input(PhysicsInputInstruction::SetNextMouse(_)) |PhysicsInstruction::Input(PhysicsInputInstruction::ReplaceMouse(_,_)) - |PhysicsInstruction::StrafeTick=>(), + |PhysicsInstruction::Internal(PhysicsInternalInstruction::StrafeTick) + |PhysicsInstruction::Internal(PhysicsInternalInstruction::ReachWalkTargetVelocity)=>(), _=>println!("{}|{:?}",ins.time,ins.instruction), } - //selectively update body - match &ins.instruction{ - PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>self.time=ins.time,//idle simply updates time - PhysicsInstruction::Input(_) - |PhysicsInstruction::ReachWalkTargetVelocity - |PhysicsInstruction::CollisionStart(_) - |PhysicsInstruction::CollisionEnd(_) - |PhysicsInstruction::StrafeTick=>self.advance_time(ins.time), + if ins.time{ - let style_mesh=self.style.mesh(); - let model_id=c.model_id(); - match (self.models.attr(model_id),&c){ - (PhysicsCollisionAttributes::Contact{contacting,general},Collision::Contact(contact))=>{ - let mut v=self.body.velocity; - let normal=contact_normal(&self.models,&style_mesh,contact); - match &contacting.contact_behaviour{ - Some(crate::model::ContactingBehaviour::Surf)=>println!("I'm surfing!"), - Some(crate::model::ContactingBehaviour::Cling)=>println!("Unimplemented!"), - &Some(crate::model::ContactingBehaviour::Elastic(elasticity))=>{ - //velocity and normal are facing opposite directions so this is inherently negative. - let d=normal.dot(v)*(Planar64::ONE+Planar64::raw(elasticity as i64+1)); - v+=normal*(d/normal.dot(normal)); - }, - Some(crate::model::ContactingBehaviour::Ladder(contacting_ladder))=>{ - if contacting_ladder.sticky{ - //kill v - //actually you could do this with a booster attribute :thinking: - v=Planar64Vec3::ZERO;//model.velocity - } - //ladder walkstate - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut target_velocity=self.style.get_ladder_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&normal); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut target_velocity); - let (walk_state,a)=WalkState::ladder(&self.body,&self.style,gravity,target_velocity,contact.clone(),&normal); - self.move_state=MoveState::Ladder(walk_state); - set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); - } - None=>if self.style.surf_slope.map_or(true,|s|contact_normal(&self.models,&style_mesh,contact).walkable(s,Planar64Vec3::Y)){ - //ground - let gravity=self.touching.base_acceleration(&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - let mut target_velocity=self.style.get_walk_target_velocity(&self.camera,self.controls,&self.next_mouse,self.time,&normal); - self.touching.constrain_velocity(&self.models,&style_mesh,&mut target_velocity); - let (walk_state,a)=WalkState::ground(&self.body,&self.style,gravity,target_velocity,contact.clone(),&normal); - self.move_state=MoveState::Walk(walk_state); - set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); - }, - } - //check ground - self.touching.insert(c); - //I love making functions with 10 arguments to dodge the borrow checker - run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model_id); - //flatten v - self.touching.constrain_velocity(&self.models,&style_mesh,&mut v); - match &general.booster{ - Some(booster)=>{ - //DELETE THIS when boosters get converted to height machines - match booster{ - &crate::model::GameMechanicBooster::Affine(transform)=>v=transform.transform_point3(v), - &crate::model::GameMechanicBooster::Velocity(velocity)=>v+=velocity, - &crate::model::GameMechanicBooster::Energy{direction: _,energy: _}=>todo!(), - } - }, - None=>(), - } - let calc_move=if self.style.get_control(StyleModifiers::CONTROL_JUMP,self.controls){ - if let Some(walk_state)=get_walk_state(&self.move_state){ - jumped_velocity(&self.models,&self.style,walk_state,&mut v); - set_velocity_cull(&mut self.body,&mut self.touching,&self.models,&style_mesh,v) - }else{false} - }else{false}; - match &general.trajectory{ - Some(trajectory)=>{ - match trajectory{ - crate::model::GameMechanicSetTrajectory::AirTime(_) => todo!(), - crate::model::GameMechanicSetTrajectory::Height(_) => todo!(), - crate::model::GameMechanicSetTrajectory::TargetPointTime { target_point: _, time: _ } => todo!(), - crate::model::GameMechanicSetTrajectory::TargetPointSpeed { target_point: _, speed: _, trajectory_choice: _ } => todo!(), - &crate::model::GameMechanicSetTrajectory::Velocity(velocity)=>v=velocity, - crate::model::GameMechanicSetTrajectory::DotVelocity { direction: _, dot: _ } => todo!(), - } - }, - None=>(), - } - set_velocity(&mut self.body,&self.touching,&self.models,&style_mesh,v); - //not sure if or is correct here - if calc_move||Planar64::ZERO{ - //I think that setting the velocity to 0 was preventing surface contacts from entering an infinite loop - self.touching.insert(c); - run_teleport_behaviour(&general.teleport_behaviour,&mut self.game,&self.models,&self.modes,&self.style,&mut self.touching,&mut self.body,model_id); - }, - _=>panic!("invalid pair"), - } - }, - PhysicsInstruction::CollisionEnd(c) => { - match self.models.attr(c.model_id()){ - PhysicsCollisionAttributes::Contact{contacting:_,general:_}=>{ - self.touching.remove(&c);//remove contact before calling contact_constrain_acceleration - //check ground - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - }, - PhysicsCollisionAttributes::Intersect{intersecting:_,general:_}=>{ - self.touching.remove(&c); - }, - } - }, - PhysicsInstruction::StrafeTick => { - let control_dir=self.style.get_control_dir(self.controls); - if control_dir!=Planar64Vec3::ZERO{ - let camera_mat=self.camera.simulate_move_rotation_y(self.camera.mouse.lerp(&self.next_mouse,self.time).x); - let control_dir=camera_mat*control_dir; - //normalize but careful for zero - let d=self.body.velocity.dot(control_dir); - if d { - match &mut self.move_state{ - MoveState::Air|MoveState::Water=>(), - MoveState::Walk(walk_state)|MoveState::Ladder(walk_state)=>{ - match &mut walk_state.state{ - WalkEnum::Reached=>(), - WalkEnum::Transient(walk_target)=>{ - let style_mesh=self.style.mesh(); - //precisely set velocity - let a=Planar64Vec3::ZERO;//ignore gravity for now. - set_acceleration(&mut self.body,&self.touching,&self.models,&style_mesh,a); - let v=walk_target.velocity; - set_velocity(&mut self.body,&self.touching,&self.models,&style_mesh,v); - walk_state.state=WalkEnum::Reached; - }, - } - } - } - }, - PhysicsInstruction::Input(input_instruction) => { - let mut refresh_walk_target=true; - match input_instruction{ - PhysicsInputInstruction::SetNextMouse(m) => { - self.camera.move_mouse(self.next_mouse.pos); - (self.camera.mouse,self.next_mouse)=(self.next_mouse.clone(),m); - }, - PhysicsInputInstruction::ReplaceMouse(m0,m1) => { - self.camera.move_mouse(m0.pos); - (self.camera.mouse,self.next_mouse)=(m0,m1); - }, - PhysicsInputInstruction::SetMoveForward(s) => self.set_control(StyleModifiers::CONTROL_MOVEFORWARD,s), - PhysicsInputInstruction::SetMoveLeft(s) => self.set_control(StyleModifiers::CONTROL_MOVELEFT,s), - PhysicsInputInstruction::SetMoveBack(s) => self.set_control(StyleModifiers::CONTROL_MOVEBACK,s), - PhysicsInputInstruction::SetMoveRight(s) => self.set_control(StyleModifiers::CONTROL_MOVERIGHT,s), - PhysicsInputInstruction::SetMoveUp(s) => self.set_control(StyleModifiers::CONTROL_MOVEUP,s), - PhysicsInputInstruction::SetMoveDown(s) => self.set_control(StyleModifiers::CONTROL_MOVEDOWN,s), - PhysicsInputInstruction::SetJump(s) => { - self.set_control(StyleModifiers::CONTROL_JUMP,s); - if let Some(walk_state)=get_walk_state(&self.move_state){ - let mut v=self.body.velocity; - jumped_velocity(&self.models,&self.style,walk_state,&mut v); - if set_velocity_cull(&mut self.body,&mut self.touching,&self.models,&self.style.mesh(),v){ - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - } - } - refresh_walk_target=false; - }, - PhysicsInputInstruction::SetZoom(s) => { - self.set_control(StyleModifiers::CONTROL_ZOOM,s); - refresh_walk_target=false; - }, - PhysicsInputInstruction::Reset => { - //it matters which of these runs first, but I have not thought it through yet as it doesn't matter yet - set_position(&mut self.body,&mut self.touching,self.spawn_point); - set_velocity(&mut self.body,&self.touching,&self.models,&self.style.mesh(),Planar64Vec3::ZERO); - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - refresh_walk_target=false; - }, - PhysicsInputInstruction::Idle => {refresh_walk_target=false;},//literally idle! - } - if refresh_walk_target{ - let a=self.refresh_walk_target(); - if set_acceleration_cull(&mut self.body,&mut self.touching,&self.models,&self.style.mesh(),a){ - (self.move_state,self.body.acceleration)=self.touching.get_move_state(&self.body,&self.models,&self.style,&self.camera,self.controls,&self.next_mouse,self.time); - } - } - }, + PhysicsInstruction::Input(PhysicsInputInstruction::Idle)=>(), + PhysicsInstruction::Internal(instruction)=>atomic_internal_instruction(state,data,TimedInstruction{time:ins.time,instruction}), + PhysicsInstruction::Input(instruction)=>atomic_input_instruction(state,data,TimedInstruction{time:ins.time,instruction}), } } -} -#[allow(dead_code)] -fn test_collision_axis_aligned(relative_body:Body,expected_collision_time:Option