33 Commits

Author SHA1 Message Date
baea9e297c sad api 2024-09-18 18:48:39 -07:00
aa361bce77 turn run_scripts into member function 2024-09-18 15:31:09 -07:00
555b9773e6 not fallible, print errors 2024-09-17 18:28:40 -07:00
bdda966497 wip 2024-09-17 18:27:56 -07:00
b9f9b2ba7e update deps 2024-09-16 18:26:07 -07:00
deabb1769a v0.3.4 update common 2024-08-09 14:34:12 -07:00
418450e7d3 update common 2024-08-09 14:34:01 -07:00
f8f44b7b45 update deps 2024-08-09 14:33:50 -07:00
5ee149b66e update deps 2024-08-07 18:41:04 -07:00
5193d0be71 update deps 2024-07-29 16:32:16 -07:00
24b28f28dd update deps 2024-07-22 13:49:41 -07:00
77d120597d update rbx_mesh 2024-03-29 02:54:38 -07:00
44b2ae1c10 use strafesnet registry 2024-03-29 02:54:38 -07:00
12b04a97d6 update rbx deps 2024-03-29 01:44:42 -07:00
127534632e use rbx_mesh crate 2024-03-13 13:56:52 -07:00
9aca2575a8 v0.3.0 roblox mesh support 2024-03-13 13:38:52 -07:00
1f2ad779f1 roblox mesh converter + loading 2024-03-13 13:37:31 -07:00
775d1972dc update common 2024-03-13 13:36:38 -07:00
c835183abd add rbx_mesh dep 2024-03-13 13:36:25 -07:00
fab60f19ee misc 2024-03-09 10:47:39 -08:00
5ed15e783d update common 2024-03-02 13:10:13 -08:00
cc7713ca73 jump_limit changes 2024-02-21 02:34:17 -08:00
261e42c33d unused import 2024-02-15 00:46:43 -08:00
e0739fa792 use utf8 patched version 2024-02-15 00:43:55 -08:00
34017ea72c v0.2.0 new loading function signatures 2024-02-14 23:40:25 -08:00
bcaa28cf6a implement new loading api 2024-02-14 23:38:29 -08:00
0b630576d4 wrapped types + implement error trait 2024-02-13 21:28:06 -08:00
1c33646765 pass as ref 2024-02-13 21:28:06 -08:00
3b038687b6 rust master 2024-02-13 06:58:39 -08:00
048e408390 prefer this code 2024-02-13 05:43:37 -08:00
ed0dcdd051 not todo 2024-02-13 05:40:48 -08:00
0f3bd4420e use unreachable instead of panic 2024-02-13 05:38:35 -08:00
93bc4dc0fb data structure rewrite, implement texture_loader 2024-02-13 03:12:07 -08:00
7 changed files with 1225 additions and 415 deletions

2
.cargo/config.toml Normal file

@ -0,0 +1,2 @@
[registries.strafesnet]
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"

323
Cargo.lock generated

@ -4,30 +4,36 @@ version = 3
[[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 = "arrayref"
version = "0.3.7"
name = "array-init"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc"
[[package]]
name = "arrayref"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
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 = "autocfg"
version = "1.1.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "base64"
@ -35,6 +41,30 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "binrw"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f36b7cb3ab9ff6a2858650d8dc360e783a5d14dc29594db48c56a3c233cc265"
dependencies = [
"array-init",
"binrw_derive",
"bytemuck",
]
[[package]]
name = "binrw_derive"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20ea7a8c5c8eeffffac6d54d172444e15beffac6f817fac714460a9a9aa88da3"
dependencies = [
"either",
"owo-colors",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -42,10 +72,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "blake3"
version = "1.5.0"
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "blake3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
dependencies = [
"arrayref",
"arrayvec",
@ -54,6 +90,12 @@ dependencies = [
"constant_time_eq",
]
[[package]]
name = "bytemuck"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae"
[[package]]
name = "byteorder"
version = "1.5.0"
@ -62,11 +104,11 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.83"
version = "1.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800"
dependencies = [
"libc",
"shlex",
]
[[package]]
@ -77,15 +119,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[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 = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[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",
@ -94,15 +142,26 @@ dependencies = [
[[package]]
name = "glam"
version = "0.25.0"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "151665d9be52f9bb40fc7966565d39666f2d1e69233571b71b87791c7e0528b3"
checksum = "779ae4bf7e8421cf91c0b3b64e7e8b40b862fba4d393f59150042de7c4965a94"
[[package]]
name = "id"
version = "0.1.0"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "2337e7a6c273082b672e377e159d7a168fb51438461b7c4033c79a515dd7a25a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
]
[[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",
@ -111,49 +170,48 @@ 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",
"syn 2.0.77",
]
[[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.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[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 = "lz4"
version = "1.24.0"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9e2dd86df36ce760a60f6ff6ad526f7ba1f14ba0356f8254fb6905e6494df1"
checksum = "a231296ca742e418c43660cb68e082486ff2538e8db432bc818580f3965025ed"
dependencies = [
"libc",
"lz4-sys",
]
[[package]]
name = "lz4-sys"
version = "1.9.4"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900"
checksum = "fcb44a01837a858d47e5a630d2ccf304c8efcc4b83b8f9f75b7a9ee4fcc6e57d"
dependencies = [
"cc",
"libc",
@ -161,15 +219,15 @@ 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 = "num-traits"
version = "0.2.17"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
@ -181,50 +239,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "paste"
version = "1.0.14"
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[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.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0f7f43585c34e4fdd7497d746bc32e14458cf11c69341cc0587b1d825dde42"
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
dependencies = [
"profiling-procmacros",
]
[[package]]
name = "profiling-procmacros"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce97fecd27bc49296e5e20518b5a1bb54a14f7d5fe6228bc9686ee2a74915cc8"
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
dependencies = [
"quote",
"syn",
"syn 2.0.77",
]
[[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",
]
@ -262,8 +329,8 @@ dependencies = [
[[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",
@ -276,19 +343,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",
@ -297,9 +374,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",
@ -309,12 +386,12 @@ 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",
"bitflags 1.3.2",
"blake3",
"lazy_static",
"rand",
@ -325,8 +402,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",
@ -338,9 +415,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.3"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -350,9 +427,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.5"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
@ -361,15 +438,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.2"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[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",
"num-traits",
@ -378,9 +455,9 @@ 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",
"rmp",
@ -389,40 +466,52 @@ 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",
"syn 2.0.77",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "strafesnet_common"
version = "0.1.0"
source = "git+https://git.itzana.me/StrafesNET/common?rev=5ee826d9487b5e2bea4b3cf99a68ce9a95d72f72#5ee826d9487b5e2bea4b3cf99a68ce9a95d72f72"
version = "0.4.1"
source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
checksum = "9a2621612e675a8f804abbbbe3b60caeafe58a2422cccbe453268d6f457df4f3"
dependencies = [
"arrayvec",
"bitflags 2.6.0",
"glam",
"id",
]
[[package]]
name = "strafesnet_rbx_loader"
version = "0.1.0"
version = "0.3.4"
dependencies = [
"bytemuck",
"glam",
"lazy-regex",
"rbx_binary",
"rbx_dom_weak",
"rbx_mesh",
"rbx_reflection_database",
"rbx_xml",
"strafesnet_common",
@ -430,9 +519,20 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.48"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
dependencies = [
"proc-macro2",
"quote",
@ -441,29 +541,29 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.56"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.56"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.77",
]
[[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 = "wasi"
@ -473,6 +573,27 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[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.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.77",
]

@ -1,15 +1,21 @@
[package]
name = "strafesnet_rbx_loader"
version = "0.1.0"
version = "0.3.4"
edition = "2021"
repository = "https://git.itzana.me/StrafesNET/rbx_loader"
license = "MIT OR Apache-2.0"
description = "Convert Roblox place and model files to StrafesNET data structures."
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
glam = "0.25.0"
bytemuck = "1.14.3"
glam = "0.28.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 = "5ee826d9487b5e2bea4b3cf99a68ce9a95d72f72" }
rbx_binary = { version = "0.7.4", registry = "strafesnet" }
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
rbx_mesh = "0.1.2"
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
rbx_xml = { version = "0.13.3", registry = "strafesnet" }
strafesnet_common = { version = "0.4.0", registry = "strafesnet" }

@ -1,2 +1,71 @@
use std::io::Read;
mod rbx;
mod mesh;
mod primitives;
pub mod rbx;
pub mod data{
pub struct RobloxMeshBytes(Vec<u8>);
impl RobloxMeshBytes{
pub fn new(bytes:Vec<u8>)->Self{
Self(bytes)
}
pub(crate) fn cursor(self)->std::io::Cursor<Vec<u8>>{
std::io::Cursor::new(self.0)
}
}
}
pub struct Dom(rbx_dom_weak::WeakDom);
impl Dom{
pub fn run_scripts(self)->Self{
let runner=roblox_emulator::runner::Runner::new().unwrap();
let mut context=roblox_emulator::context::Context::new(self.0);
for script in context.scripts(){
let (modified_context,script_error)=runner.run_script(script,context);
context=modified_context;
if let Err(e)=script_error{
e.print();
}
}
Self(context.into_inner())
}
}
#[derive(Debug)]
pub enum ReadError{
RbxBinary(rbx_binary::DecodeError),
RbxXml(rbx_xml::DecodeError),
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 fn read<R:Read>(input:R)->Result<Dom,ReadError>{
let mut buf=std::io::BufReader::new(input);
let peek=std::io::BufRead::fill_buf(&mut buf).map_err(ReadError::Io)?;
match &peek[0..8]{
b"<roblox!"=>rbx_binary::from_reader(buf).map(Dom).map_err(ReadError::RbxBinary),
b"<roblox "=>rbx_xml::from_reader_default(buf).map(Dom).map_err(ReadError::RbxXml),
_=>Err(ReadError::UnknownFileFormat),
}
}
//ConvertError
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
dom:&Dom,
acquire_render_config_id:AcquireRenderConfigId,
acquire_mesh_id:AcquireMeshId
)->rbx::PartialMap1
where
AcquireRenderConfigId:FnMut(Option<&str>)->strafesnet_common::model::RenderConfigId,
AcquireMeshId:FnMut(&str)->strafesnet_common::model::MeshId,
{
rbx::convert(&dom.0,acquire_render_config_id,acquire_mesh_id)
}

210
src/mesh.rs Normal file

@ -0,0 +1,210 @@
use std::collections::HashMap;
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
use strafesnet_common::{integer::Planar64Vec3,model::{self, ColorId, IndexedVertex, NormalId, PolygonGroup, PolygonList, PositionId, TextureCoordinateId, VertexId}};
#[derive(Debug)]
pub enum Error{
Planar64Vec3(strafesnet_common::integer::Planar64TryFromFloatError),
RbxMesh(rbx_mesh::mesh::Error)
}
impl std::fmt::Display for Error{
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
write!(f,"{self:?}")
}
}
impl std::error::Error for Error{}
fn ingest_vertices2<
AcquirePosId,
AcquireTexId,
AcquireNormalId,
AcquireColorId,
AcquireVertexId,
>(
vertices:Vec<Vertex2>,
acquire_pos_id:&mut AcquirePosId,
acquire_tex_id:&mut AcquireTexId,
acquire_normal_id:&mut AcquireNormalId,
acquire_color_id:&mut AcquireColorId,
acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
AcquireColorId:FnMut([f32;4])->ColorId,
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
rbx_mesh::mesh::VertexId2(vertex_id as u32),
acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id(vertex.tex),
normal:acquire_normal_id(vertex.norm)?,
color:acquire_color_id(vertex.color.map(|f|f as f32/255.0f32))
}),
))).collect::<Result<_,_>>()?)
}
fn ingest_vertices_truncated2<
AcquirePosId,
AcquireTexId,
AcquireNormalId,
AcquireVertexId,
>(
vertices:Vec<Vertex2Truncated>,
acquire_pos_id:&mut AcquirePosId,
acquire_tex_id:&mut AcquireTexId,
acquire_normal_id:&mut AcquireNormalId,
static_color_id:ColorId,//pick one color and fill everything with it
acquire_vertex_id:&mut AcquireVertexId,
)->Result<HashMap<rbx_mesh::mesh::VertexId2,VertexId>,Error>
where
AcquirePosId:FnMut([f32;3])->Result<PositionId,Error>,
AcquireTexId:FnMut([f32;2])->TextureCoordinateId,
AcquireNormalId:FnMut([f32;3])->Result<NormalId,Error>,
AcquireVertexId:FnMut(IndexedVertex)->VertexId,
{
//this monster is collecting a map of old_vertices_index -> unique_vertices_index
//while also doing the inserting unique entries into lists simultaneously
Ok(vertices.into_iter().enumerate().map(|(vertex_id,vertex)|Ok((
rbx_mesh::mesh::VertexId2(vertex_id as u32),
acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id(vertex.tex),
normal:acquire_normal_id(vertex.norm)?,
color:static_color_id
}),
))).collect::<Result<_,_>>()?)
}
fn ingest_faces2_lods3(
polygon_groups:&mut Vec<PolygonGroup>,
vertex_id_map:&HashMap<rbx_mesh::mesh::VertexId2,VertexId>,
faces:&Vec<rbx_mesh::mesh::Face2>,
lods:&Vec<rbx_mesh::mesh::Lod3>
){
//faces have to be split into polygon groups based on lod
polygon_groups.extend(lods.windows(2).map(|lod_pair|
PolygonGroup::PolygonList(PolygonList::new(faces[lod_pair[0].0 as usize..lod_pair[1].0 as usize].iter().map(|face|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
).collect()))
))
}
pub fn convert(roblox_mesh_bytes:crate::data::RobloxMeshBytes)->Result<model::Mesh,Error>{
//generate that mesh boi
let mut unique_pos=Vec::new();
let mut pos_id_from=HashMap::new();
let mut unique_tex=Vec::new();
let mut tex_id_from=HashMap::new();
let mut unique_normal=Vec::new();
let mut normal_id_from=HashMap::new();
let mut unique_color=Vec::new();
let mut color_id_from=HashMap::new();
let mut unique_vertices=Vec::new();
let mut vertex_id_from=HashMap::new();
let mut polygon_groups=Vec::new();
let mut acquire_pos_id=|pos|{
let p=Planar64Vec3::try_from(pos).map_err(Error::Planar64Vec3)?;
Ok(PositionId::new(*pos_id_from.entry(p).or_insert_with(||{
let pos_id=unique_pos.len();
unique_pos.push(p);
pos_id
}) as u32))
};
let mut acquire_tex_id=|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(glam::Vec2::from_array(tex));
tex_id
}) as u32)
};
let mut acquire_normal_id=|normal|{
let n=Planar64Vec3::try_from(normal).map_err(Error::Planar64Vec3)?;
Ok(NormalId::new(*normal_id_from.entry(n).or_insert_with(||{
let normal_id=unique_normal.len();
unique_normal.push(n);
normal_id
}) as u32))
};
let mut acquire_color_id=|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(glam::Vec4::from_array(color));
color_id
}) as u32)
};
let mut acquire_vertex_id=|vertex:IndexedVertex|{
VertexId::new(*vertex_id_from.entry(vertex.clone()).or_insert_with(||{
let vertex_id=unique_vertices.len();
unique_vertices.push(vertex);
vertex_id
}) as u32)
};
match rbx_mesh::read_versioned(roblox_mesh_bytes.cursor()).map_err(Error::RbxMesh)?{
rbx_mesh::mesh::VersionedMesh::Version1(mesh)=>{
let color_id=acquire_color_id([1.0f32;4]);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.vertices.chunks_exact(3).map(|trip|{
let mut ingest_vertex1=|vertex:&rbx_mesh::mesh::Vertex1|Ok(acquire_vertex_id(IndexedVertex{
pos:acquire_pos_id(vertex.pos)?,
tex:acquire_tex_id([vertex.tex[0],vertex.tex[1]]),
normal:acquire_normal_id(vertex.norm)?,
color:color_id,
}));
Ok(vec![ingest_vertex1(&trip[0])?,ingest_vertex1(&trip[1])?,ingest_vertex1(&trip[2])?])
}).collect::<Result<_,_>>()?)));
},
rbx_mesh::mesh::VersionedMesh::Version2(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
//pick white and make all the vertices white
let color_id=acquire_color_id([1.0f32;4]);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
}?;
//one big happy group for all the faces
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(mesh.faces.into_iter().map(|face|
vec![vertex_id_map[&face.0],vertex_id_map[&face.1],vertex_id_map[&face.2]]
).collect())));
},
rbx_mesh::mesh::VersionedMesh::Version3(mesh)=>{
let vertex_id_map=match mesh.header.sizeof_vertex{
rbx_mesh::mesh::SizeOfVertex2::Truncated=>{
let color_id=acquire_color_id([1.0f32;4]);
ingest_vertices_truncated2(mesh.vertices_truncated,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,color_id,&mut acquire_vertex_id)
},
rbx_mesh::mesh::SizeOfVertex2::Full=>ingest_vertices2(mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id),
}?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::VersionedMesh::Version4(mesh)=>{
let vertex_id_map=ingest_vertices2(
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
)?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
rbx_mesh::mesh::VersionedMesh::Version5(mesh)=>{
let vertex_id_map=ingest_vertices2(
mesh.vertices,&mut acquire_pos_id,&mut acquire_tex_id,&mut acquire_normal_id,&mut acquire_color_id,&mut acquire_vertex_id
)?;
ingest_faces2_lods3(&mut polygon_groups,&vertex_id_map,&mesh.faces,&mesh.lods);
},
}
Ok(model::Mesh{
unique_pos,
unique_normal,
unique_tex,
unique_color,
unique_vertices,
polygon_groups,
//these should probably be moved to the model...
graphics_groups:Vec::new(),
physics_groups:Vec::new(),
})
}

@ -1,4 +1,4 @@
use strafesnet_common::model::{Color4,TextureCoordinate,IndexedModel,IndexedPolygon,IndexedGroup,IndexedVertex};
use strafesnet_common::model::{Color4,TextureCoordinate,Mesh,IndexedGraphicsGroup,IndexedPhysicsGroup,IndexedVertex,PolygonGroupId,PolygonGroup,PolygonList,IndexedVertexList,PositionId,TextureCoordinateId,NormalId,ColorId,VertexId,RenderConfigId};
use strafesnet_common::integer::Planar64Vec3;
#[derive(Debug)]
@ -126,8 +126,8 @@ const CORNERWEDGE_DEFAULT_NORMALS:[Planar64Vec3;5]=[
Planar64Vec3::int( 0,-1, 0),//CornerWedge::Bottom
Planar64Vec3::int( 0, 0,-1),//CornerWedge::Front
];
pub fn unit_sphere()->crate::model::IndexedModel{
unit_cube()
pub fn unit_sphere(render:RenderConfigId)->Mesh{
unit_cube(render)
}
#[derive(Default)]
pub struct CubeFaceDescription([Option<FaceDescription>;6]);
@ -139,18 +139,19 @@ impl CubeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
}
}
pub fn unit_cube()->crate::model::IndexedModel{
pub fn unit_cube(render:RenderConfigId)->Mesh{
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());
t.insert(CubeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Top,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Back,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Left,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Bottom,FaceDescription::new_with_render_id(render));
t.insert(CubeFace::Front,FaceDescription::new_with_render_id(render));
generate_partial_unit_cube(t)
}
pub fn unit_cylinder()->crate::model::IndexedModel{
unit_cube()
pub fn unit_cylinder(render:RenderConfigId)->Mesh{
//lmao
unit_cube(render)
}
#[derive(Default)]
pub struct WedgeFaceDescription([Option<FaceDescription>;5]);
@ -162,13 +163,13 @@ impl WedgeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
}
}
pub fn unit_wedge()->crate::model::IndexedModel{
pub fn unit_wedge(render:RenderConfigId)->Mesh{
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());
t.insert(WedgeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::TopFront,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::Back,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::Left,FaceDescription::new_with_render_id(render));
t.insert(WedgeFace::Bottom,FaceDescription::new_with_render_id(render));
generate_partial_unit_wedge(t)
}
#[derive(Default)]
@ -181,40 +182,40 @@ impl CornerWedgeFaceDescription{
self.0.into_iter().enumerate().filter_map(|v|v.1.map(|u|(v.0,u)))
}
}
pub fn unit_cornerwedge()->crate::model::IndexedModel{
pub fn unit_cornerwedge(render:RenderConfigId)->Mesh{
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());
t.insert(CornerWedgeFace::Right,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::TopBack,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::TopLeft,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::Bottom,FaceDescription::new_with_render_id(render));
t.insert(CornerWedgeFace::Front,FaceDescription::new_with_render_id(render));
generate_partial_unit_cornerwedge(t)
}
#[derive(Clone)]
pub struct FaceDescription{
pub texture:Option<u32>,
pub render:RenderConfigId,
pub transform:glam::Affine2,
pub color:Color4,
}
impl std::default::Default for FaceDescription{
fn default()->Self {
impl FaceDescription{
pub fn new_with_render_id(render:RenderConfigId)->Self {
Self{
texture:None,
render,
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{
pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->Mesh{
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 polygon_groups=Vec::new();
let mut graphics_groups=Vec::new();
let mut physics_group=IndexedPhysicsGroup::default();
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(){
@ -225,9 +226,9 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->crate:
//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));
}
generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
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){
@ -242,46 +243,50 @@ pub fn generate_partial_unit_cube(face_descriptions:CubeFaceDescription)->crate:
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(),
}],
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
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:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
let vert_index=generated_vertices.len();
generated_vertices.push(vertex);
VertexId::new(vert_index as u32)
}).to_vec(),
])));
graphics_groups.push(IndexedGraphicsGroup{
render:face_description.render,
groups:vec![group_id],
});
physics_group.groups.push(group_id);
}
IndexedModel{
Mesh{
unique_pos:generated_pos,
unique_tex:generated_tex,
unique_normal:generated_normal,
unique_color:generated_color,
unique_vertices:generated_vertices,
groups,
instances:Vec::new(),
polygon_groups,
graphics_groups,
physics_groups:vec![physics_group],
}
}
//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![
pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->Mesh{
let wedge_default_polys=[
// right (1, 0, 0)
vec![
[6,2,0],//[vertex,tex,norm]
@ -321,7 +326,9 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crat
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 polygon_groups=Vec::new();
let mut graphics_groups=Vec::new();
let mut physics_group=IndexedPhysicsGroup::default();
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(){
@ -332,9 +339,9 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crat
//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));
}
generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
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){
@ -349,46 +356,50 @@ pub fn generate_partial_unit_wedge(face_descriptions:WedgeFaceDescription)->crat
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(),
}],
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
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:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
let vert_index=generated_vertices.len();
generated_vertices.push(vertex);
VertexId::new(vert_index as u32)
}).collect()
])));
graphics_groups.push(IndexedGraphicsGroup{
render:face_description.render,
groups:vec![group_id],
});
physics_group.groups.push(group_id);
}
IndexedModel{
Mesh{
unique_pos:generated_pos,
unique_tex:generated_tex,
unique_normal:generated_normal,
unique_color:generated_color,
unique_vertices:generated_vertices,
groups,
instances:Vec::new(),
polygon_groups,
graphics_groups,
physics_groups:vec![physics_group],
}
}
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->crate::model::IndexedModel{
let cornerwedge_default_polys=vec![
pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescription)->Mesh{
let cornerwedge_default_polys=[
// right (1, 0, 0)
vec![
[6,2,0],//[vertex,tex,norm]
@ -426,7 +437,9 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
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 polygon_groups=Vec::new();
let mut graphics_groups=Vec::new();
let mut physics_group=IndexedPhysicsGroup::default();
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(){
@ -437,9 +450,9 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
//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));
}
generated_tex.extend(CUBE_DEFAULT_TEXTURE_COORDS.map(|tex|
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){
@ -454,40 +467,44 @@ pub fn generate_partial_unit_cornerwedge(face_descriptions:CornerWedgeFaceDescri
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(),
}],
let group_id=PolygonGroupId::new(polygon_groups.len() as u32);
polygon_groups.push(PolygonGroup::PolygonList(PolygonList::new(vec![
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:PositionId::new(pos_index),
tex:TextureCoordinateId::new(tup[1]+4*transform_index),
normal:NormalId::new(normal_index),
color:ColorId::new(color_index),
};
let vert_index=generated_vertices.len();
generated_vertices.push(vertex);
VertexId::new(vert_index as u32)
}).collect(),
])));
graphics_groups.push(IndexedGraphicsGroup{
render:face_description.render,
groups:vec![group_id],
});
physics_group.groups.push(group_id);
}
IndexedModel{
Mesh{
unique_pos:generated_pos,
unique_tex:generated_tex,
unique_normal:generated_normal,
unique_color:generated_color,
unique_vertices:generated_vertices,
groups,
instances:Vec::new(),
polygon_groups,
graphics_groups,
physics_groups:vec![physics_group],
}
}

@ -1,6 +1,13 @@
use std::collections::HashMap;
use crate::primitives;
use strafesnet_common::gameplay_attributes;
use strafesnet_common::map;
use strafesnet_common::model;
use strafesnet_common::gameplay_modes;
use strafesnet_common::gameplay_style;
use strafesnet_common::gameplay_attributes as attr;
use strafesnet_common::integer::{Planar64,Planar64Vec3,Planar64Mat3,Planar64Affine3};
use strafesnet_common::model::RenderConfigId;
use strafesnet_common::updatable::Updatable;
fn class_is_a(class: &str, superclass: &str) -> bool {
if class==superclass {
@ -12,7 +19,7 @@ fn class_is_a(class: &str, superclass: &str) -> bool {
return class_is_a(&class_super, superclass)
}
}
return false
false
}
fn recursive_collect_superclass(objects: &mut std::vec::Vec<rbx_dom_weak::types::Ref>,dom: &rbx_dom_weak::WeakDom, instance: &rbx_dom_weak::Instance, superclass: &str){
let mut stack=vec![instance];
@ -40,91 +47,270 @@ fn planar64_affine3_from_roblox(cf:&rbx_dom_weak::types::CFrame,size:&rbx_dom_we
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();
struct ModeBuilder{
mode:gameplay_modes::Mode,
final_stage_id_from_builder_stage_id:HashMap<gameplay_modes::StageId,gameplay_modes::StageId>,
}
#[derive(Default)]
struct ModesBuilder{
modes:HashMap<gameplay_modes::ModeId,gameplay_modes::Mode>,
stages:HashMap<gameplay_modes::ModeId,HashMap<gameplay_modes::StageId,gameplay_modes::Stage>>,
mode_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::ModeUpdate)>,
stage_updates:Vec<(gameplay_modes::ModeId,gameplay_modes::StageId,gameplay_modes::StageUpdate)>,
}
impl ModesBuilder{
fn build(mut self)->gameplay_modes::Modes{
//collect modes and stages into contiguous arrays
let mut unique_modes:Vec<(gameplay_modes::ModeId,gameplay_modes::Mode)>
=self.modes.into_iter().collect();
unique_modes.sort_by(|a,b|a.0.cmp(&b.0));
let (mut modes,final_mode_id_from_builder_mode_id):(Vec<ModeBuilder>,HashMap<gameplay_modes::ModeId,gameplay_modes::ModeId>)
=unique_modes.into_iter().enumerate()
.map(|(final_mode_id,(builder_mode_id,mut mode))|{
(
ModeBuilder{
final_stage_id_from_builder_stage_id:self.stages.remove(&builder_mode_id).map_or_else(||HashMap::new(),|stages|{
let mut unique_stages:Vec<(gameplay_modes::StageId,gameplay_modes::Stage)>
=stages.into_iter().collect();
unique_stages.sort_by(|a,b|a.0.cmp(&b.0));
unique_stages.into_iter().enumerate()
.map(|(final_stage_id,(builder_stage_id,stage))|{
mode.push_stage(stage);
(builder_stage_id,gameplay_modes::StageId::new(final_stage_id as u32))
}).collect()
}),
mode,
},
(
builder_mode_id,
gameplay_modes::ModeId::new(final_mode_id as u32)
)
)
}).unzip();
//TODO: failure messages or errors or something
//push stage updates
for (builder_mode_id,builder_stage_id,stage_update) in self.stage_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
if let Some(&final_stage_id)=mode.final_stage_id_from_builder_stage_id.get(&builder_stage_id){
if let Some(stage)=mode.mode.get_stage_mut(final_stage_id){
stage.update(stage_update);
}
}
}
}
}
//push mode updates
for (builder_mode_id,mut mode_update) in self.mode_updates{
if let Some(final_mode_id)=final_mode_id_from_builder_mode_id.get(&builder_mode_id){
if let Some(mode)=modes.get_mut(final_mode_id.get() as usize){
//map stage id on stage elements
mode_update.map_stage_element_ids(|stage_id|
//walk down one stage id at a time until a stage is found
//TODO use better logic like BTreeMap::upper_bound instead of walking
// final_stage_id_from_builder_stage_id.upper_bound(Bound::Included(&stage_id))
// .value().copied().unwrap_or(gameplay_modes::StageId::FIRST)
(0..=stage_id.get()).rev().find_map(|builder_stage_id|
//map the stage element to that stage
mode.final_stage_id_from_builder_stage_id.get(&gameplay_modes::StageId::new(builder_stage_id)).copied()
).unwrap_or(gameplay_modes::StageId::FIRST)
);
mode.mode.update(mode_update);
}
}
}
gameplay_modes::Modes::new(modes.into_iter().map(|mode_builder|mode_builder.mode).collect())
}
fn insert_mode(&mut self,mode_id:gameplay_modes::ModeId,mode:gameplay_modes::Mode){
assert!(self.modes.insert(mode_id,mode).is_none(),"Cannot replace existing mode");
}
fn insert_stage(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage:gameplay_modes::Stage){
assert!(self.stages.entry(mode_id).or_insert(HashMap::new()).insert(stage_id,stage).is_none(),"Cannot replace existing stage");
}
fn push_mode_update(&mut self,mode_id:gameplay_modes::ModeId,mode_update:gameplay_modes::ModeUpdate){
self.mode_updates.push((mode_id,mode_update));
}
fn push_stage_update(&mut self,mode_id:gameplay_modes::ModeId,stage_id:gameplay_modes::StageId,stage_update:gameplay_modes::StageUpdate){
self.stage_updates.push((mode_id,stage_id,stage_update));
}
}
fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,model_id:model::ModelId,modes_builder:&mut ModesBuilder,wormhole_in_model_to_id:&mut HashMap<model::ModelId,u32>,wormhole_id_to_out_model:&mut HashMap<u32,model::ModelId>)->attr::CollisionAttributes{
let mut general=attr::GeneralAttributes::default();
let mut intersecting=attr::IntersectingAttributes::default();
let mut contacting=attr::ContactingAttributes::default();
let mut force_can_collide=can_collide;
let mut force_intersecting=false;
match name{
"Water"=>{
force_can_collide=false;
//TODO: read stupid CustomPhysicalProperties
intersecting.water=Some(model::IntersectingWater{density:Planar64::ONE,viscosity:Planar64::ONE/10,velocity});
intersecting.water=Some(attr::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});
general.accelerator=Some(attr::Accelerator{acceleration:velocity});
},
// "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(model::GameMechanicStageElement{
// "UnorderedCheckpoint"=>general.teleport_behaviour=Some(model::TeleportBehaviour::StageElement(attr::StageElement{
// 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,
})),
"SetVelocity"=>general.trajectory=Some(attr::SetTrajectory::Velocity(velocity)),
"MapStart"=>{
force_can_collide=false;
force_intersecting=true;
modes_builder.insert_mode(
gameplay_modes::ModeId::MAIN,
gameplay_modes::Mode::empty(
gameplay_style::StyleModifiers::roblox_bhop(),
model_id
)
);
},
"MapFinish"=>{
force_can_collide=false;
force_intersecting=true;
modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::zone(
model_id,
gameplay_modes::Zone::Finish,
),
);
},
"MapAnticheat"=>{
force_can_collide=false;
force_intersecting=true;
modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::zone(
model_id,
gameplay_modes::Zone::Anticheat,
),
);
},
"Platform"=>{
modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::element(
model_id,
gameplay_modes::StageElement::new(gameplay_modes::StageId::FIRST,false,gameplay_modes::StageElementBehaviour::Platform,None),//roblox does not know which stage the platform belongs to
),
);
},
other=>{
if let Some(captures)=lazy_regex::regex!(r"^(Force)?(Spawn|SpawnAt|Trigger|Teleport|Platform)(\d+)$")
let regman=lazy_regex::regex!(r"^(BonusStart|WormholeOut)(\d+)$");
if let Some(captures)=regman.captures(other){
match &captures[1]{
"BonusStart"=>{
force_can_collide=false;
force_intersecting=true;
modes_builder.insert_mode(
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
gameplay_modes::Mode::empty(
gameplay_style::StyleModifiers::roblox_bhop(),
model_id
)
);
},
"WormholeOut"=>{
//the PhysicsModelId has to exist for it to be teleported to!
force_intersecting=true;
//this object is not special in strafe client, but the roblox mapping needs to be converted to model id
assert!(wormhole_id_to_out_model.insert(captures[2].parse::<u32>().unwrap(),model_id).is_none(),"Cannot have multiple WormholeOut with same id");
},
_=>(),
}
}else 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::<u32>().unwrap(),
force:match captures.get(1){
force_intersecting=true;
let stage_id=gameplay_modes::StageId::new(captures[3].parse::<u32>().unwrap());
let stage_element=gameplay_modes::StageElement::new(
//stage_id:
stage_id,
//force:
match captures.get(1){
Some(m)=>m.as_str()=="Force",
None=>false,
},
behaviour:match &captures[2]{
"Spawn"|"SpawnAt"=>model::StageElementBehaviour::SpawnAt,
//behaviour:
match &captures[2]{
"Spawn"=>{
modes_builder.insert_stage(
gameplay_modes::ModeId::MAIN,
stage_id,
gameplay_modes::Stage::empty(model_id),
);
//TODO: let denormalize handle this
gameplay_modes::StageElementBehaviour::SpawnAt
},
"SpawnAt"=>gameplay_modes::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,
"Trigger"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Trigger},
"Teleport"=>{force_can_collide=false;gameplay_modes::StageElementBehaviour::Teleport},
"Platform"=>gameplay_modes::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::<u32>().unwrap()),
_=>panic!("regex4[1] messed up bad"),
}
}));
None
);
modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::element(
model_id,
stage_element,
),
);
}else if let Some(captures)=lazy_regex::regex!(r"^(Jump|WormholeIn)(\d+)$")
.captures(other){
match &captures[1]{
"Jump"=>modes_builder.push_mode_update(
gameplay_modes::ModeId::MAIN,
gameplay_modes::ModeUpdate::element(
model_id,
//jump_limit:
gameplay_modes::StageElement::new(
gameplay_modes::StageId::FIRST,
false,
gameplay_modes::StageElementBehaviour::Check,
Some(captures[2].parse::<u8>().unwrap())
)
),
),
"WormholeIn"=>{
force_can_collide=false;
force_intersecting=true;
assert!(wormhole_in_model_to_id.insert(model_id,captures[2].parse::<u32>().unwrap()).is_none(),"Impossible");
},
_=>panic!("regex2[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::<u32>().unwrap(),behaviour:model::ZoneBehaviour::Finish}),
"Anticheat"=>general.zone=Some(model::GameMechanicZone{mode_id:captures[2].parse::<u32>().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::<u32>().unwrap()})),
_=>panic!("regex3[1] messed up bad"),
}
force_intersecting=true;
modes_builder.push_mode_update(
gameplay_modes::ModeId::new(captures[2].parse::<u32>().unwrap()),
gameplay_modes::ModeUpdate::zone(
model_id,
//zone:
match &captures[1]{
"Finish"=>gameplay_modes::Zone::Finish,
"Anticheat"=>gameplay_modes::Zone::Anticheat,
_=>panic!("regex3[1] messed up bad"),
},
),
);
}
// else if let Some(captures)=lazy_regex::regex!(r"^(OrderedCheckpoint)(\d+)$")
// else if let Some(captures)=lazy_regex::regex!(r"^Stage(\d+)OrderedCheckpoint(\d+)$")
// .captures(other){
// match &captures[1]{
// "OrderedCheckpoint"=>general.checkpoint=Some(model::GameMechanicCheckpoint::Ordered{mode_id:0,checkpoint_id:captures[2].parse::<u32>().unwrap()}),
// "OrderedCheckpoint"=>modes_builder.push_stage_update(
// gameplay_modes::ModeId::MAIN,
// gameplay_modes::StageId::new(0),
// gameplay_modes::StageUpdate::ordered_checkpoint(captures[2].parse::<u32>().unwrap()),
// ),
// _=>panic!("regex3[1] messed up bad"),
// }
// }
@ -132,45 +318,29 @@ fn get_attributes(name:&str,can_collide:bool,velocity:Planar64Vec3,force_interse
}
//need some way to skip this
if velocity!=Planar64Vec3::ZERO{
general.booster=Some(model::GameMechanicBooster::Velocity(velocity));
general.booster=Some(attr::Booster::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})),
"Bounce"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Elastic(u32::MAX)),
"Surf"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Surf),
"Ladder"=>contacting.contact_behaviour=Some(attr::ContactingBehaviour::Ladder(attr::ContactingLadder{sticky:true})),
_=>(),
}
model::CollisionAttributes::Contact{contacting,general}
attr::CollisionAttributes::Contact(attr::ContactAttributes{contacting,general})
},
false=>if force_intersecting
||general.any()
||intersecting.any()
{
model::CollisionAttributes::Intersect{intersecting,general}
attr::CollisionAttributes::Intersect(attr::IntersectAttributes{intersecting,general})
}else{
model::CollisionAttributes::Decoration
attr::CollisionAttributes::Decoration
},
}
}
struct RobloxAssetId(u64);
struct RobloxAssetIdParseErr;
impl std::str::FromStr for RobloxAssetId {
type Err=RobloxAssetIdParseErr;
fn from_str(s: &str) -> Result<Self, Self::Err>{
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::<u64>() {
return Ok(Self(id));
}
}
}
Err(RobloxAssetIdParseErr)
}
}
#[derive(Clone,Copy,PartialEq)]
struct RobloxTextureTransform{
offset_u:f32,
@ -180,12 +350,12 @@ struct RobloxTextureTransform{
}
impl std::cmp::Eq for RobloxTextureTransform{}//????
impl std::default::Default for RobloxTextureTransform{
fn default() -> Self {
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<H: std::hash::Hasher>(&self, state: &mut H) {
impl std::hash::Hash for RobloxTextureTransform{
fn hash<H:std::hash::Hasher>(&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);
@ -194,16 +364,16 @@ impl std::hash::Hash for RobloxTextureTransform {
}
#[derive(Clone,PartialEq)]
struct RobloxFaceTextureDescription{
texture:u32,
render:RenderConfigId,
color:glam::Vec4,
transform:RobloxTextureTransform,
}
impl std::cmp::Eq for RobloxFaceTextureDescription{}//????
impl std::hash::Hash for RobloxFaceTextureDescription {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.texture.hash(state);
impl std::hash::Hash for RobloxFaceTextureDescription{
fn hash<H:std::hash::Hasher>(&self,state:&mut H){
self.render.hash(state);
self.transform.hash(state);
for &el in self.color.as_ref().iter() {
for &el in self.color.as_ref().iter(){
el.to_ne_bytes().hash(state);
}
}
@ -211,7 +381,7 @@ impl std::hash::Hash for RobloxFaceTextureDescription {
impl RobloxFaceTextureDescription{
fn to_face_description(&self)->primitives::FaceDescription{
primitives::FaceDescription{
texture:Some(self.texture),
render:self.render,
transform:glam::Affine2::from_translation(
glam::vec2(self.transform.offset_u,self.transform.offset_v)
)
@ -233,15 +403,52 @@ enum RobloxBasePartDescription{
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;
enum Shape{
Primitive(primitives::Primitives),
MeshPart,
}
enum MeshAvailability{
Immediate,
Deferred(RenderConfigId),
}
struct DeferredModelDeferredAttributes{
render:RenderConfigId,
model:ModelDeferredAttributes,
}
struct ModelDeferredAttributes{
mesh:model::MeshId,
deferred_attributes:GetAttributesArgs,
color:model::Color4,//transparency is in here
transform:Planar64Affine3,
}
struct ModelOwnedAttributes{
mesh:model::MeshId,
attributes:attr::CollisionAttributes,
color:model::Color4,//transparency is in here
transform:Planar64Affine3,
}
struct GetAttributesArgs{
name:Box<str>,
can_collide:bool,
velocity:Planar64Vec3,
}
pub fn convert<AcquireRenderConfigId,AcquireMeshId>(
dom:&rbx_dom_weak::WeakDom,
mut acquire_render_config_id:AcquireRenderConfigId,
mut acquire_mesh_id:AcquireMeshId,
)->PartialMap1
where
AcquireRenderConfigId:FnMut(Option<&str>)->model::RenderConfigId,
AcquireMeshId:FnMut(&str)->model::MeshId,
{
let mut indexed_models=Vec::new();
let mut model_id_from_description=std::collections::HashMap::<RobloxBasePartDescription,usize>::new();
let mut deferred_models_deferred_attributes=Vec::new();
let mut primitive_models_deferred_attributes=Vec::new();
let mut primitive_meshes=Vec::new();
let mut mesh_id_from_description=HashMap::new();
let mut texture_id_from_asset_id=std::collections::HashMap::<u64,u32>::new();
let mut asset_id_from_texture_id=Vec::new();
//just going to leave it like this for now instead of reworking the data structures for this whole thing
let textureless_render_group=acquire_render_config_id(None);
let mut object_refs=Vec::new();
let mut temp_objects=Vec::new();
@ -278,57 +485,36 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
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::<u32>().unwrap()})),
"Spawn"|"ForceSpawn"=>Some(model::TempIndexedAttributes::Spawn(model::TempAttrSpawn{mode_id:0,stage_id:captures[2].parse::<u32>().unwrap()})),
"WormholeOut"=>Some(model::TempIndexedAttributes::Wormhole(model::TempAttrWormhole{wormhole_id:captures[2].parse::<u32>().unwrap()})),
_=>None,
}
}else{
None
}
}
}{
force_intersecting=true;
temp_indexing_attributes.push(attr);
}
//at this point a new model is going to be generated for sure.
let model_id=model::ModelId::new(primitive_models_deferred_attributes.len() as u32);
//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!");
}
let shape=match object.class.as_str(){
"Part"=>if let Some(rbx_dom_weak::types::Variant::Enum(shape))=object.properties.get("Shape"){
Shape::Primitive(match shape.to_u32(){
0=>primitives::Primitives::Sphere,
1=>primitives::Primitives::Cube,
2=>primitives::Primitives::Cylinder,
3=>primitives::Primitives::Wedge,
4=>primitives::Primitives::CornerWedge,
other=>panic!("Funky roblox PartType={};",other),
})
}else{
panic!("Part has no Shape!");
},
"TrussPart"=>primitives::Primitives::Cube,
"WedgePart"=>primitives::Primitives::Wedge,
"CornerWedgePart"=>primitives::Primitives::CornerWedge,
"TrussPart"=>Shape::Primitive(primitives::Primitives::Cube),
"WedgePart"=>Shape::Primitive(primitives::Primitives::Wedge),
"CornerWedgePart"=>Shape::Primitive(primitives::Primitives::CornerWedge),
"MeshPart"=>Shape::MeshPart,
_=>{
println!("Unsupported BasePart ClassName={}; defaulting to cube",object.class);
primitives::Primitives::Cube
Shape::Primitive(primitives::Primitives::Cube)
}
};
let (availability,mesh_id)=match shape{
Shape::Primitive(primitive_shape)=>{
//TODO: TAB TAB
//use the biggest one and cut it down later...
let mut part_texture_description:RobloxPartDescription=[None,None,None,None,None,None];
temp_objects.clear();
@ -346,15 +532,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
decal.properties.get("Color3"),
decal.properties.get("Transparency"),
) {
if let Ok(asset_id)=content.clone().into_string().parse::<RobloxAssetId>(){
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 render_id=acquire_render_config_id(Some(content.as_ref()));
let normal_id=normalid.to_u32();
if normal_id<6{
let (roblox_texture_color,roblox_texture_transform)=if decal.class=="Texture"{
@ -378,7 +556,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
3=>(size.z,size.y),//left
4=>(size.x,size.z),//bottom
5=>(size.x,size.y),//front
_=>panic!("unreachable"),
_=>unreachable!(),
};
(
glam::vec4(decal_color3.r,decal_color3.g,decal_color3.b,1.0-*decal_transparency),
@ -394,14 +572,13 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
(glam::Vec4::ONE,RobloxTextureTransform::default())
};
part_texture_description[normal_id as usize]=Some(RobloxFaceTextureDescription{
texture:texture_id,
render:render_id,
color:roblox_texture_color,
transform:roblox_texture_transform,
});
}else{
println!("NormalId={} unsupported for shape={:?}",normal_id,shape);
println!("NormalId={} unsupported for shape={:?}",normal_id,primitive_shape);
}
}
}
}
}
@ -414,7 +591,7 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
f4,//Cube::Bottom
f5,//Cube::Front
]=part_texture_description;
let basepart_texture_description=match shape{
let basepart_description=match primitive_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]),
@ -436,13 +613,13 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
]),
};
//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){
let mesh_id=if let Some(&mesh_id)=mesh_id_from_description.get(&basepart_description){
//push to existing texture model
model_id
mesh_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{
let mesh_id=model::MeshId::new(primitive_meshes.len() as u32);
mesh_id_from_description.insert(basepart_description.clone(),mesh_id);//borrow checker going crazy
let mesh=match basepart_description{
RobloxBasePartDescription::Sphere(part_texture_description)
|RobloxBasePartDescription::Cylinder(part_texture_description)
|RobloxBasePartDescription::Part(part_texture_description)=>{
@ -456,11 +633,11 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
3=>primitives::CubeFace::Left,
4=>primitives::CubeFace::Bottom,
5=>primitives::CubeFace::Front,
_=>panic!("unreachable"),
_=>unreachable!(),
},
match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::default(),
None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
});
}
primitives::generate_partial_unit_cube(cube_face_description)
@ -475,11 +652,11 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
2=>primitives::WedgeFace::Back,
3=>primitives::WedgeFace::Left,
4=>primitives::WedgeFace::Bottom,
_=>panic!("unreachable"),
_=>unreachable!(),
},
match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::default(),
None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
});
}
primitives::generate_partial_unit_wedge(wedge_face_description)
@ -494,31 +671,239 @@ pub fn generate_indexed_models(dom:rbx_dom_weak::WeakDom) -> model::IndexedModel
2=>primitives::CornerWedgeFace::TopLeft,
3=>primitives::CornerWedgeFace::Bottom,
4=>primitives::CornerWedgeFace::Front,
_=>panic!("unreachable"),
_=>unreachable!(),
},
match roblox_face_description{
Some(roblox_texture_transform)=>roblox_texture_transform.to_face_description(),
None=>primitives::FaceDescription::default(),
None=>primitives::FaceDescription::new_with_render_id(textureless_render_group),
});
}
primitives::generate_partial_unit_cornerwedge(cornerwedge_face_description)
},
});
model_id
};
primitive_meshes.push(mesh);
mesh_id
};
indexed_models[model_id].instances.push(model::ModelInstance {
(MeshAvailability::Immediate,mesh_id)
},
Shape::MeshPart=>if let (
Some(rbx_dom_weak::types::Variant::Content(mesh_asset_id)),
Some(rbx_dom_weak::types::Variant::Content(texture_asset_id)),
)=(
object.properties.get("MeshId"),
object.properties.get("TextureID"),
){
(
MeshAvailability::Deferred(acquire_render_config_id(Some(texture_asset_id.as_ref()))),
acquire_mesh_id(mesh_asset_id.as_ref()),
)
}else{
panic!("Mesh has no Mesh or Texture");
},
};
let model_deferred_attributes=ModelDeferredAttributes{
mesh:mesh_id,
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,
});
deferred_attributes:GetAttributesArgs{
name:object.name.as_str().into(),
can_collide:*can_collide,
velocity:Planar64Vec3::try_from([velocity.x,velocity.y,velocity.z]).unwrap(),
},
};
match availability{
MeshAvailability::Immediate=>primitive_models_deferred_attributes.push(model_deferred_attributes),
MeshAvailability::Deferred(render)=>deferred_models_deferred_attributes.push(DeferredModelDeferredAttributes{
render,
model:model_deferred_attributes
}),
}
}
}
}
model::IndexedModelInstances{
textures:asset_id_from_texture_id.iter().map(|t|t.to_string()).collect(),
models:indexed_models,
spawn_point,
modes:Vec::new(),
PartialMap1{
primitive_meshes,
primitive_models_deferred_attributes,
deferred_models_deferred_attributes,
}
}
struct MeshWithAabb{
mesh:model::Mesh,
aabb:strafesnet_common::aabb::Aabb,
}
pub struct PartialMap1{
primitive_meshes:Vec<model::Mesh>,
primitive_models_deferred_attributes:Vec<ModelDeferredAttributes>,
deferred_models_deferred_attributes:Vec<DeferredModelDeferredAttributes>,
}
impl PartialMap1{
pub fn add_meshpart_meshes_and_calculate_attributes(
mut self,
meshpart_meshes:impl IntoIterator<Item=(model::MeshId,crate::data::RobloxMeshBytes)>,
)->PartialMap2{
//calculate attributes
let mut modes_builder=ModesBuilder::default();
let mut unique_attributes=Vec::new();
let mut attributes_id_from_attributes=HashMap::new();
let mut wormhole_in_model_to_id=HashMap::new();
let mut wormhole_id_to_out_model=HashMap::new();
//decode roblox meshes
//generate mesh_id_map based on meshes that failed to load
let loaded_meshes:HashMap<model::MeshId,MeshWithAabb>=
meshpart_meshes.into_iter().flat_map(|(old_mesh_id,roblox_mesh_bytes)|
match crate::mesh::convert(roblox_mesh_bytes){
Ok(mesh)=>{
let mut aabb=strafesnet_common::aabb::Aabb::default();
for &pos in &mesh.unique_pos{
aabb.grow(pos);
}
Some((old_mesh_id,MeshWithAabb{
mesh,
aabb,
}))
},
Err(e)=>{
println!("Error converting mesh: {e:?}");
None
},
}
).collect();
let mut mesh_id_from_render_config_id=HashMap::new();
//ignore meshes that fail to load completely for now
let mut acquire_mesh_id_from_render_config_id=|old_mesh_id,render|{
loaded_meshes.get(&old_mesh_id).map(|mesh_with_aabb|(
*mesh_id_from_render_config_id.entry(old_mesh_id).or_insert_with(||HashMap::new())
.entry(render).or_insert_with(||{
let mesh_id=model::MeshId::new(self.primitive_meshes.len() as u32);
let mut mesh_clone=mesh_with_aabb.mesh.clone();
//add a render group lool
mesh_clone.graphics_groups.push(model::IndexedGraphicsGroup{
render,
//the lowest lod is highest quality
groups:vec![model::PolygonGroupId::new(0)]
});
self.primitive_meshes.push(mesh_clone);
mesh_id
}),
&mesh_with_aabb.aabb,
))
};
//now that the meshes are loaded, these models can be generated
let models_owned_attributes:Vec<ModelOwnedAttributes>=
self.deferred_models_deferred_attributes.into_iter().flat_map(|deferred_model_deferred_attributes|{
//meshes need to be cloned from loaded_meshes with a new id when they are used with a new render_id
//insert into primitive_meshes
let (mesh,aabb)=acquire_mesh_id_from_render_config_id(
deferred_model_deferred_attributes.model.mesh,
deferred_model_deferred_attributes.render
)?;
let size=aabb.size();
Some(ModelDeferredAttributes{
mesh,
deferred_attributes:deferred_model_deferred_attributes.model.deferred_attributes,
color:deferred_model_deferred_attributes.model.color,
transform:Planar64Affine3::new(
Planar64Mat3::from_cols(
deferred_model_deferred_attributes.model.transform.matrix3.x_axis*2/size.x(),
deferred_model_deferred_attributes.model.transform.matrix3.y_axis*2/size.y(),
deferred_model_deferred_attributes.model.transform.matrix3.z_axis*2/size.z()
),
deferred_model_deferred_attributes.model.transform.translation
),
})
}).chain(self.primitive_models_deferred_attributes.into_iter())
.enumerate().map(|(model_id,model_deferred_attributes)|{
let model_id=model::ModelId::new(model_id as u32);
ModelOwnedAttributes{
mesh:model_deferred_attributes.mesh,
attributes:get_attributes(
&model_deferred_attributes.deferred_attributes.name,
model_deferred_attributes.deferred_attributes.can_collide,
model_deferred_attributes.deferred_attributes.velocity,
model_id,
&mut modes_builder,
&mut wormhole_in_model_to_id,
&mut wormhole_id_to_out_model,
),
color:model_deferred_attributes.color,
transform:model_deferred_attributes.transform,
}
}).collect();
let models=models_owned_attributes.into_iter().enumerate().map(|(model_id,mut model_owned_attributes)|{
//TODO: TAB
let model_id=model::ModelId::new(model_id as u32);
//update attributes with wormhole id
//TODO: errors/prints
if let Some(wormhole_id)=wormhole_in_model_to_id.get(&model_id){
if let Some(&wormhole_out_model_id)=wormhole_id_to_out_model.get(wormhole_id){
match &mut model_owned_attributes.attributes{
attr::CollisionAttributes::Contact(attr::ContactAttributes{contacting:_,general})
|attr::CollisionAttributes::Intersect(attr::IntersectAttributes{intersecting:_,general})
=>general.wormhole=Some(attr::Wormhole{destination_model:wormhole_out_model_id}),
attr::CollisionAttributes::Decoration=>println!("Not a wormhole"),
}
}
}
//index the attributes
let attributes_id=if let Some(&attributes_id)=attributes_id_from_attributes.get(&model_owned_attributes.attributes){
attributes_id
}else{
let attributes_id=attr::CollisionAttributesId::new(unique_attributes.len() as u32);
attributes_id_from_attributes.insert(model_owned_attributes.attributes.clone(),attributes_id);
unique_attributes.push(model_owned_attributes.attributes);
attributes_id
};
model::Model{
mesh:model_owned_attributes.mesh,
transform:model_owned_attributes.transform,
color:model_owned_attributes.color,
attributes:attributes_id,
}
}).collect();
PartialMap2{
meshes:self.primitive_meshes,
models,
modes:modes_builder.build(),
attributes:unique_attributes,
}
}
}
pub struct PartialMap2{
meshes:Vec<model::Mesh>,
models:Vec<model::Model>,
modes:gameplay_modes::Modes,
attributes:Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
}
impl PartialMap2{
pub fn add_render_configs_and_textures(
self,
render_configs:impl IntoIterator<Item=(model::RenderConfigId,model::RenderConfig)>,
textures:impl IntoIterator<Item=(model::TextureId,Vec<u8>)>,
)->map::CompleteMap{
let (textures,texture_id_map):(Vec<Vec<u8>>,HashMap<model::TextureId,model::TextureId>)
=textures.into_iter().enumerate().map(|(new_texture_id,(old_texture_id,texture))|{
(texture,(old_texture_id,model::TextureId::new(new_texture_id as u32)))
}).unzip();
let render_configs=render_configs.into_iter().map(|(render_config_id,mut render_config)|{
//this may generate duplicate no-texture render configs but idc
render_config.texture=render_config.texture.and_then(|texture_id|
texture_id_map.get(&texture_id).copied()
);
render_config
}).collect();
map::CompleteMap{
modes:self.modes,
attributes:self.attributes,
meshes:self.meshes,
models:self.models,
//the roblox legacy texture thing always works
textures,
render_configs,
}
}
}