commit 2496f181e300b3db4cd2bb3d9d915ede3cef7ffe Author: Quaternions Date: Tue Jan 30 17:41:16 2024 -0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e7f31ab --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,478 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "blake3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glam" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3" + +[[package]] +name = "lazy-regex" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d12be4595afdf58bd19e4a9f4e24187da2a66700786ff660a418e9059937a4c" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44bcd58e6c97a7fcbaffcdc95728b393b8d98933bfadad49ed4097845b57ef0b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lz4" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1" +dependencies = [ + "libc", + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0f7f43585c34e4fdd7497d746bc32e14458cf11c69341cc0587b1d825dde42" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce97fecd27bc49296e5e20518b5a1bb54a14f7d5fe6228bc9686ee2a74915cc8" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rbx_binary" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6314dd6bf5c21d0598cdb53cf5d241aa643ba41da8b8abf7402b4a35096f03f6" +dependencies = [ + "log", + "lz4", + "profiling", + "rbx_dom_weak", + "rbx_reflection", + "rbx_reflection_database", + "thiserror", +] + +[[package]] +name = "rbx_dom_weak" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b67b56bac99849c2e3c57547b036927f71c57cf7f4d900d04e3e4ee774ec316" +dependencies = [ + "rbx_types", + "serde", +] + +[[package]] +name = "rbx_loader" +version = "0.1.0" +dependencies = [ + "glam", + "lazy-regex", + "rbx_binary", + "rbx_dom_weak", + "rbx_reflection_database", + "rbx_xml", + "strafesnet_common", +] + +[[package]] +name = "rbx_reflection" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41509c991b53a7276a746a795eae2b9204f398164920f61976995b47fe1722" +dependencies = [ + "rbx_types", + "serde", + "thiserror", +] + +[[package]] +name = "rbx_reflection_database" +version = "0.2.10+roblox-607" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e20c06fa41f7aadc79005c8354f592b2c2f4d0c61e1080ed5718dafc30aea0" +dependencies = [ + "lazy_static", + "rbx_reflection", + "rmp-serde", + "serde", +] + +[[package]] +name = "rbx_types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca23bfd469d067d81ef14f65fe09aeddc25abcf576a889d1a7664fe021cf18c" +dependencies = [ + "base64", + "bitflags", + "blake3", + "lazy_static", + "rand", + "serde", + "thiserror", +] + +[[package]] +name = "rbx_xml" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c03f95500961c32340791d1fabd4587f6873bdbff077ecca6ae32db7960dea" +dependencies = [ + "base64", + "log", + "rbx_dom_weak", + "rbx_reflection", + "rbx_reflection_database", + "xml-rs", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rmp" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strafesnet_common" +version = "0.1.0" +source = "git+https://git.itzana.me/StrafesNET/common?rev=434ca29aef7e3015c9ca1ed45de8fef42e33fdfb#434ca29aef7e3015c9ca1ed45de8fef42e33fdfb" +dependencies = [ + "glam", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "xml-rs" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c419ba1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rbx_loader" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +glam = "0.25.0" +lazy-regex = "3.1.0" +rbx_binary = "0.7.4" +rbx_dom_weak = "2.7.0" +rbx_reflection_database = "0.2.10" +rbx_xml = "0.13.3" +strafesnet_common = { git = "https://git.itzana.me/StrafesNET/common", rev = "434ca29aef7e3015c9ca1ed45de8fef42e33fdfb" } diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..a7e77cb --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..468cd79 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ad62509 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +StrafesNET Roblox Loader +======================== + +## Convert Roblox files into StrafesNET data structures + +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..57a6919 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +mod primitives; +pub mod rbx; \ No newline at end of file diff --git a/src/primitives.rs b/src/primitives.rs new file mode 100644 index 0000000..3c98259 --- /dev/null +++ b/src/primitives.rs @@ -0,0 +1,493 @@ +use strafesnet_common::model::{Color4,TextureCoordinate,IndexedModel,IndexedPolygon,IndexedGroup,IndexedVertex}; +use strafesnet_common::integer::Planar64Vec3; + +#[derive(Debug)] +pub enum Primitives{ + Sphere, + Cube, + Cylinder, + Wedge, + CornerWedge, +} +#[derive(Hash,PartialEq,Eq)] +pub enum CubeFace{ + Right, + Top, + Back, + Left, + Bottom, + Front, +} +const CUBE_DEFAULT_TEXTURE_COORDS:[TextureCoordinate;4]=[ + TextureCoordinate::new(0.0,0.0), + TextureCoordinate::new(1.0,0.0), + TextureCoordinate::new(1.0,1.0), + TextureCoordinate::new(0.0,1.0), +]; +const CUBE_DEFAULT_VERTICES:[Planar64Vec3;8]=[ + Planar64Vec3::int(-1,-1, 1),//0 left bottom back + Planar64Vec3::int( 1,-1, 1),//1 right bottom back + Planar64Vec3::int( 1, 1, 1),//2 right top back + Planar64Vec3::int(-1, 1, 1),//3 left top back + Planar64Vec3::int(-1, 1,-1),//4 left top front + Planar64Vec3::int( 1, 1,-1),//5 right top front + Planar64Vec3::int( 1,-1,-1),//6 right bottom front + Planar64Vec3::int(-1,-1,-1),//7 left bottom front +]; +const CUBE_DEFAULT_NORMALS:[Planar64Vec3;6]=[ + Planar64Vec3::int( 1, 0, 0),//CubeFace::Right + Planar64Vec3::int( 0, 1, 0),//CubeFace::Top + Planar64Vec3::int( 0, 0, 1),//CubeFace::Back + Planar64Vec3::int(-1, 0, 0),//CubeFace::Left + Planar64Vec3::int( 0,-1, 0),//CubeFace::Bottom + Planar64Vec3::int( 0, 0,-1),//CubeFace::Front +]; +const CUBE_DEFAULT_POLYS:[[[u32;3];4];6]=[ + // right (1, 0, 0) + [ + [6,2,0],//[vertex,tex,norm] + [5,1,0], + [2,0,0], + [1,3,0], + ], + // top (0, 1, 0) + [ + [5,3,1], + [4,2,1], + [3,1,1], + [2,0,1], + ], + // back (0, 0, 1) + [ + [0,3,2], + [1,2,2], + [2,1,2], + [3,0,2], + ], + // left (-1, 0, 0) + [ + [0,2,3], + [3,1,3], + [4,0,3], + [7,3,3], + ], + // bottom (0,-1, 0) + [ + [1,1,4], + [0,0,4], + [7,3,4], + [6,2,4], + ], + // front (0, 0,-1) + [ + [4,1,5], + [5,0,5], + [6,3,5], + [7,2,5], + ], +]; + +#[derive(Hash,PartialEq,Eq)] +pub enum WedgeFace{ + Right, + TopFront, + Back, + Left, + Bottom, +} +const WEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[ + Planar64Vec3::int( 1, 0, 0),//Wedge::Right + Planar64Vec3::int( 0, 1,-1),//Wedge::TopFront + Planar64Vec3::int( 0, 0, 1),//Wedge::Back + Planar64Vec3::int(-1, 0, 0),//Wedge::Left + Planar64Vec3::int( 0,-1, 0),//Wedge::Bottom +]; +/* +local cornerWedgeVerticies = { + Vector3.new(-1/2,-1/2,-1/2),7 + Vector3.new(-1/2,-1/2, 1/2),0 + Vector3.new( 1/2,-1/2,-1/2),6 + Vector3.new( 1/2,-1/2, 1/2),1 + Vector3.new( 1/2, 1/2,-1/2),5 +} +*/ +#[derive(Hash,PartialEq,Eq)] +pub enum CornerWedgeFace{ + Right, + TopBack, + TopLeft, + Bottom, + Front, +} +const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[ + Planar64Vec3::int( 1, 0, 0),//CornerWedge::Right + Planar64Vec3::int( 0, 1, 1),//CornerWedge::BackTop + Planar64Vec3::int(-1, 1, 0),//CornerWedge::LeftTop + Planar64Vec3::int( 0,-1, 0),//CornerWedge::Bottom + Planar64Vec3::int( 0, 0,-1),//CornerWedge::Front +]; +pub fn unit_sphere()->crate::model::IndexedModel{ + unit_cube() +} +#[derive(Default)] +pub struct CubeFaceDescription([Option;6]); +impl CubeFaceDescription{ + pub fn insert(&mut self,index:CubeFace,value:FaceDescription){ + self.0[index as usize]=Some(value); + } + pub fn pairs(self)->std::iter::FilterMap,6>>,impl FnMut((usize,Option))->Option<(usize,FaceDescription)>>{ + self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) + } +} +pub fn unit_cube()->crate::model::IndexedModel{ + let mut t=CubeFaceDescription::default(); + t.insert(CubeFace::Right,FaceDescription::default()); + t.insert(CubeFace::Top,FaceDescription::default()); + t.insert(CubeFace::Back,FaceDescription::default()); + t.insert(CubeFace::Left,FaceDescription::default()); + t.insert(CubeFace::Bottom,FaceDescription::default()); + t.insert(CubeFace::Front,FaceDescription::default()); + generate_partial_unit_cube(t) +} +pub fn unit_cylinder()->crate::model::IndexedModel{ + unit_cube() +} +#[derive(Default)] +pub struct WedgeFaceDescription([Option;5]); +impl WedgeFaceDescription{ + pub fn insert(&mut self,index:WedgeFace,value:FaceDescription){ + self.0[index as usize]=Some(value); + } + pub fn pairs(self)->std::iter::FilterMap,5>>,impl FnMut((usize,Option))->Option<(usize,FaceDescription)>>{ + self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) + } +} +pub fn unit_wedge()->crate::model::IndexedModel{ + let mut t=WedgeFaceDescription::default(); + t.insert(WedgeFace::Right,FaceDescription::default()); + t.insert(WedgeFace::TopFront,FaceDescription::default()); + t.insert(WedgeFace::Back,FaceDescription::default()); + t.insert(WedgeFace::Left,FaceDescription::default()); + t.insert(WedgeFace::Bottom,FaceDescription::default()); + generate_partial_unit_wedge(t) +} +#[derive(Default)] +pub struct CornerWedgeFaceDescription([Option;5]); +impl CornerWedgeFaceDescription{ + pub fn insert(&mut self,index:CornerWedgeFace,value:FaceDescription){ + self.0[index as usize]=Some(value); + } + pub fn pairs(self)->std::iter::FilterMap,5>>,impl FnMut((usize,Option))->Option<(usize,FaceDescription)>>{ + self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u))) + } +} +pub fn unit_cornerwedge()->crate::model::IndexedModel{ + let mut t=CornerWedgeFaceDescription::default(); + t.insert(CornerWedgeFace::Right,FaceDescription::default()); + t.insert(CornerWedgeFace::TopBack,FaceDescription::default()); + t.insert(CornerWedgeFace::TopLeft,FaceDescription::default()); + t.insert(CornerWedgeFace::Bottom,FaceDescription::default()); + t.insert(CornerWedgeFace::Front,FaceDescription::default()); + generate_partial_unit_cornerwedge(t) +} + +#[derive(Clone)] +pub struct FaceDescription{ + pub texture:Option, + pub transform:glam::Affine2, + pub color:Color4, +} +impl std::default::Default for FaceDescription{ + fn default()->Self { + Self{ + texture:None, + transform:glam::Affine2::IDENTITY, + color:Color4::new(1.0,1.0,1.0,0.0),//zero alpha to hide the default texture + } + } +} +//TODO: it's probably better to use a shared vertex buffer between all primitives and use indexed rendering instead of generating a unique vertex buffer for each primitive. +//implementation: put all roblox primitives into one model.groups <- this won't work but I forget why +pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->crate::model::IndexedModel{ + let mut generated_pos=Vec::new(); + let mut generated_tex=Vec::new(); + let mut generated_normal=Vec::new(); + let mut generated_color=Vec::new(); + let mut generated_vertices=Vec::new(); + let mut groups=Vec::new(); + let mut transforms=Vec::new(); + //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. + for (face_id,face_description) in face_descriptions.pairs(){ + //assume that scanning short lists is faster than hashing. + let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ + transform_index + }else{ + //create new transform_index + let transform_index=transforms.len(); + transforms.push(face_description.transform); + for tex in CUBE_DEFAULT_TEXTURE_COORDS{ + generated_tex.push(face_description.transform.transform_point2(tex)); + } + transform_index + } as u32; + let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){ + color_index + }else{ + //create new color_index + let color_index=generated_color.len(); + generated_color.push(face_description.color); + color_index + } as u32; + //always push normal + let normal_index=generated_normal.len() as u32; + generated_normal.push(CUBE_DEFAULT_NORMALS[face_id]); + //push vertices as they are needed + groups.push(IndexedGroup{ + texture:face_description.texture, + polys:vec![IndexedPolygon{ + vertices:CUBE_DEFAULT_POLYS[face_id].map(|tup|{ + let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; + let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ + pos_index + }else{ + //create new pos_index + let pos_index=generated_pos.len(); + generated_pos.push(pos); + pos_index + } as u32; + //always push vertex + let vertex=IndexedVertex{ + pos:pos_index, + tex:tup[1]+4*transform_index, + normal:normal_index, + color:color_index, + }; + let vert_index=generated_vertices.len(); + generated_vertices.push(vertex); + vert_index as u32 + }).to_vec(), + }], + }); + } + IndexedModel{ + unique_pos:generated_pos, + unique_tex:generated_tex, + unique_normal:generated_normal, + unique_color:generated_color, + unique_vertices:generated_vertices, + groups, + instances:Vec::new(), + } +} +//don't think too hard about the copy paste because this is all going into the map tool eventually... +pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crate::model::IndexedModel{ + let wedge_default_polys=vec![ + // right (1, 0, 0) + vec![ + [6,2,0],//[vertex,tex,norm] + [2,0,0], + [1,3,0], + ], + // FrontTop (0, 1, -1) + vec![ + [3,1,1], + [2,0,1], + [6,3,1], + [7,2,1], + ], + // back (0, 0, 1) + vec![ + [0,3,2], + [1,2,2], + [2,1,2], + [3,0,2], + ], + // left (-1, 0, 0) + vec![ + [0,2,3], + [3,1,3], + [7,3,3], + ], + // bottom (0,-1, 0) + vec![ + [1,1,4], + [0,0,4], + [7,3,4], + [6,2,4], + ], + ]; + let mut generated_pos=Vec::new(); + let mut generated_tex=Vec::new(); + let mut generated_normal=Vec::new(); + let mut generated_color=Vec::new(); + let mut generated_vertices=Vec::new(); + let mut groups=Vec::new(); + let mut transforms=Vec::new(); + //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. + for (face_id,face_description) in face_descriptions.pairs(){ + //assume that scanning short lists is faster than hashing. + let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ + transform_index + }else{ + //create new transform_index + let transform_index=transforms.len(); + transforms.push(face_description.transform); + for tex in CUBE_DEFAULT_TEXTURE_COORDS{ + generated_tex.push(face_description.transform.transform_point2(tex)); + } + transform_index + } as u32; + let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){ + color_index + }else{ + //create new color_index + let color_index=generated_color.len(); + generated_color.push(face_description.color); + color_index + } as u32; + //always push normal + let normal_index=generated_normal.len() as u32; + generated_normal.push(WEDGE_DEFAULT_NORMALS[face_id]); + //push vertices as they are needed + groups.push(IndexedGroup{ + texture:face_description.texture, + polys:vec![IndexedPolygon{ + vertices:wedge_default_polys[face_id].iter().map(|tup|{ + let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; + let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ + pos_index + }else{ + //create new pos_index + let pos_index=generated_pos.len(); + generated_pos.push(pos); + pos_index + } as u32; + //always push vertex + let vertex=IndexedVertex{ + pos:pos_index, + tex:tup[1]+4*transform_index, + normal:normal_index, + color:color_index, + }; + let vert_index=generated_vertices.len(); + generated_vertices.push(vertex); + vert_index as u32 + }).collect(), + }], + }); + } + IndexedModel{ + unique_pos:generated_pos, + unique_tex:generated_tex, + unique_normal:generated_normal, + unique_color:generated_color, + unique_vertices:generated_vertices, + groups, + instances:Vec::new(), + } +} + +pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->crate::model::IndexedModel{ + let cornerwedge_default_polys=vec![ + // right (1, 0, 0) + vec![ + [6,2,0],//[vertex,tex,norm] + [5,1,0], + [1,3,0], + ], + // BackTop (0, 1, 1) + vec![ + [5,3,1], + [0,1,1], + [1,0,1], + ], + // LeftTop (-1, 1, 0) + vec![ + [5,3,2], + [7,2,2], + [0,1,2], + ], + // bottom (0,-1, 0) + vec![ + [1,1,3], + [0,0,3], + [7,3,3], + [6,2,3], + ], + // front (0, 0,-1) + vec![ + [5,0,4], + [6,3,4], + [7,2,4], + ], + ]; + let mut generated_pos=Vec::new(); + let mut generated_tex=Vec::new(); + let mut generated_normal=Vec::new(); + let mut generated_color=Vec::new(); + let mut generated_vertices=Vec::new(); + let mut groups=Vec::new(); + let mut transforms=Vec::new(); + //note that on a cube every vertex is guaranteed to be unique, so there's no need to hash them against existing vertices. + for (face_id,face_description) in face_descriptions.pairs(){ + //assume that scanning short lists is faster than hashing. + let transform_index=if let Some(transform_index)=transforms.iter().position(|&transform|transform==face_description.transform){ + transform_index + }else{ + //create new transform_index + let transform_index=transforms.len(); + transforms.push(face_description.transform); + for tex in CUBE_DEFAULT_TEXTURE_COORDS{ + generated_tex.push(face_description.transform.transform_point2(tex)); + } + transform_index + } as u32; + let color_index=if let Some(color_index)=generated_color.iter().position(|&color|color==face_description.color){ + color_index + }else{ + //create new color_index + let color_index=generated_color.len(); + generated_color.push(face_description.color); + color_index + } as u32; + //always push normal + let normal_index=generated_normal.len() as u32; + generated_normal.push(CORNERWEDGE_DEFAULT_NORMALS[face_id]); + //push vertices as they are needed + groups.push(IndexedGroup{ + texture:face_description.texture, + polys:vec![IndexedPolygon{ + vertices:cornerwedge_default_polys[face_id].iter().map(|tup|{ + let pos=CUBE_DEFAULT_VERTICES[tup[0] as usize]; + let pos_index=if let Some(pos_index)=generated_pos.iter().position(|&p|p==pos){ + pos_index + }else{ + //create new pos_index + let pos_index=generated_pos.len(); + generated_pos.push(pos); + pos_index + } as u32; + //always push vertex + let vertex=IndexedVertex{ + pos:pos_index, + tex:tup[1]+4*transform_index, + normal:normal_index, + color:color_index, + }; + let vert_index=generated_vertices.len(); + generated_vertices.push(vertex); + vert_index as u32 + }).collect(), + }], + }); + } + IndexedModel{ + unique_pos:generated_pos, + unique_tex:generated_tex, + unique_normal:generated_normal, + unique_color:generated_color, + unique_vertices:generated_vertices, + groups, + instances:Vec::new(), + } +} diff --git a/src/rbx.rs b/src/rbx.rs new file mode 100644 index 0000000..8a76a87 --- /dev/null +++ b/src/rbx.rs @@ -0,0 +1,524 @@ +use crate::primitives; +use strafesnet_common::gameplay_attributes; +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)->model::CollisionAttributes{ + let mut general=model::GameMechanicAttributes::default(); + let mut intersecting=model::IntersectingAttributes::default(); + let mut contacting=model::ContactingAttributes::default(); + let mut force_can_collide=can_collide; + match name{ + "Water"=>{ + force_can_collide=false; + //TODO: read stupid CustomPhysicalProperties + intersecting.water=Some(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(model::GameMechanicAccelerator{acceleration:velocity}); + }, + // "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(model::GameMechanicStageElement{ + // mode_id:0, + // stage_id:0, + // force:false, + // behaviour:model::StageElementBehaviour::Unordered + // })), + "SetVelocity"=>general.trajectory=Some(model::GameMechanicSetTrajectory::Velocity(velocity)), + "MapFinish"=>{force_can_collide=false;general.zone=Some(model::GameMechanicZone{mode_id:0,behaviour:model::ZoneBehaviour::Finish})}, + "MapAnticheat"=>{force_can_collide=false;general.zone=Some(model::GameMechanicZone{mode_id:0,behaviour:model::ZoneBehaviour::Anitcheat})}, + "Platform"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(model::GameMechanicStageElement{ + mode_id:0, + stage_id:0, + force:false, + behaviour: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(model::TeleportBehaviour::StageElement(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"=>model::StageElementBehaviour::SpawnAt, + //cancollide false so you don't hit the side + //NOT a decoration + "Trigger"=>{force_can_collide=false;model::StageElementBehaviour::Trigger}, + "Teleport"=>{force_can_collide=false;model::StageElementBehaviour::Teleport}, + "Platform"=>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(model::TeleportBehaviour::StageElement(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"=>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(model::GameMechanicZone{mode_id:captures[2].parse::().unwrap(),behaviour:model::ZoneBehaviour::Finish}), + "Anticheat"=>general.zone=Some(model::GameMechanicZone{mode_id:captures[2].parse::().unwrap(),behaviour: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(model::TeleportBehaviour::Wormhole(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(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(model::GameMechanicBooster::Velocity(velocity)); + } + match force_can_collide{ + true=>{ + match name{ + "Bounce"=>contacting.contact_behaviour=Some(model::ContactingBehaviour::Elastic(u32::MAX)), + "Surf"=>contacting.contact_behaviour=Some(model::ContactingBehaviour::Surf), + "Ladder"=>contacting.contact_behaviour=Some(model::ContactingBehaviour::Ladder(model::ContactingLadder{sticky:true})), + _=>(), + } + model::CollisionAttributes::Contact{contacting,general} + }, + false=>if force_intersecting + ||general.any() + ||intersecting.any() + { + model::CollisionAttributes::Intersect{intersecting,general} + }else{ + 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) -> 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(model::TempIndexedAttributes::Start(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(model::TempIndexedAttributes::Start(model::TempAttrStart{mode_id:captures[2].parse::().unwrap()})), + "Spawn"|"ForceSpawn"=>Some(model::TempIndexedAttributes::Spawn(model::TempAttrSpawn{mode_id:0,stage_id:captures[2].parse::().unwrap()})), + "WormholeOut"=>Some(model::TempIndexedAttributes::Wormhole(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(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, + }); + } + } + } + model::IndexedModelInstances{ + textures:asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(), + models:indexed_models, + spawn_point, + modes:Vec::new(), + } +}