forked from StrafesNET/strafe-project
Compare commits
706 Commits
wgpu-0.20
...
Unquatific
Author | SHA1 | Date | |
---|---|---|---|
88dcfe40ed | |||
08f419f931 | |||
6066e82fd2 | |||
ca8035cdfc | |||
ff5d954cfb | |||
a967f31004 | |||
8ad5d28e51 | |||
ab05893813 | |||
2f7597146e | |||
004e0d3776 | |||
120d8197b7 | |||
36ba73a892 | |||
86cf7e74b1 | |||
24787fede5 | |||
3797408bc8 | |||
47c9b77b00 | |||
479e657251 | |||
63fbc94287 | |||
1318ae20ca | |||
851d9c935d | |||
d0a190861c | |||
4dca7fc369 | |||
62dfe23539 | |||
3991cb5064 | |||
1dc2556d85 | |||
4f21985290 | |||
ccce54c1a3 | |||
02bb2d797c | |||
eb30566a82 | |||
f8880593e6 | |||
cbdc389cc9 | |||
ff011f9f0c | |||
2bd6868882 | |||
0a515f423d | |||
f73f45128e | |||
c968377901 | |||
34ae17be5d | |||
5fb440584b | |||
5ad5229a62 | |||
18d941d5bd | |||
dfdce476ca | |||
e04a4bdf15 | |||
19bb5c021e | |||
0f8a7fbbbf | |||
002ebbb52e | |||
1d46d29f97 | |||
0ee333cb71 | |||
406dd16627 | |||
031ffb3e50 | |||
be7ba54f7e | |||
831cbfef58 | |||
25e90b69b9 | |||
6f958e3eda | |||
4b3aadfc04 | |||
5dfe37487e | |||
c1188aec1b | |||
263b82cfa7 | |||
784858848e | |||
ef4e699598 | |||
9395d58a2c | |||
f8c623cd88 | |||
0dc2d1f89e | |||
f51c554492 | |||
3797d226e4 | |||
1e7814488e | |||
fd2aa19949 | |||
24701dc5fe | |||
d7d24cc64c | |||
bd38c6f1e4 | |||
6f0749f827 | |||
a6eb5e184c | |||
210832c737 | |||
da169ade70 | |||
bffc5bb8f1 | |||
f8f659e8ce | |||
135103364d | |||
368ce7da9e | |||
13621022e1 | |||
03e9d989f8 | |||
5ddb3dacfd | |||
8950bcbf02 | |||
9c1807ec76 | |||
752ad6bd95 | |||
9898c53de8 | |||
4ce6fb2155 | |||
c5fb915a6d | |||
b672c1360d | |||
25577211b2 | |||
fe91de5668 | |||
6dfa46bc67 | |||
3ad9d45452 | |||
9a03ac199a | |||
aa1db9551e | |||
692697f82b | |||
5e45e952d9 | |||
09dd442948 | |||
726ffeca21 | |||
082de122b1 | |||
bf0c4f74c3 | |||
97e2010826 | |||
de363bc271 | |||
849b0813b9 | |||
fac1f318d7 | |||
a23e8fc36f | |||
58edaf3291 | |||
d8aacb9ed2 | |||
35d60cde16 | |||
69d43d8bea | |||
b2beef4726 | |||
a9a4ed2c46 | |||
4988eeed65 | |||
ff9c122470 | |||
eccf7243c4 | |||
b457da64a1 | |||
ed3f9f9b30 | |||
136b360590 | |||
7b8f091ab3 | |||
3a716373db | |||
a67aa71fb0 | |||
ef78778708 | |||
f4dc80713f | |||
9030646f39 | |||
ba9918f2d5 | |||
acbb1c8478 | |||
96c8b1035a | |||
67223efa26 | |||
0148476648 | |||
cb5b842276 | |||
e4114ab2cc | |||
9b077d6db7 | |||
7980770b8d | |||
7e09494146 | |||
d496a2dac5 | |||
e110365325 | |||
c0323d12df | |||
7774fe9eb4 | |||
3fabb6dbae | |||
7d67e762b1 | |||
7cc0fd59c8 | |||
db8e22c34d | |||
a88debe9f1 | |||
317400548d | |||
bded9fccdf | |||
14cc0776a6 | |||
d002216bd9 | |||
c12ad7df0f | |||
7a3184efd5 | |||
35c19abaf1 | |||
d5a3b797da | |||
00a5d71b2b | |||
d272ac242b | |||
e67f0f0889 | |||
40655cdf44 | |||
6c12bc2bf6 | |||
5a1df16bd9 | |||
9d219004f4 | |||
6ec0d61b3d | |||
748f544a4b | |||
7403888348 | |||
c06ba65662 | |||
fb38f1076d | |||
f5e9287798 | |||
6b66009c44 | |||
a4e2137463 | |||
a8d439f343 | |||
a648526384 | |||
a274b6d232 | |||
218a7fbf0f | |||
64e44846aa | |||
4cd0114567 | |||
991e01d530 | |||
c2b04a046c | |||
fe5caa7ad3 | |||
f25954e1a8 | |||
65d2b71de9 | |||
81f6a405cf | |||
522f9f6668 | |||
3bb496f87b | |||
9d56773343 | |||
ac48fb8a0d | |||
5fd51c2085 | |||
dcad5fc8ec | |||
ea76d137fd | |||
315fa962f2 | |||
d02df2f83e | |||
ed78807a9f | |||
0d05540a6b | |||
9b3dde66bd | |||
a65eef3609 | |||
b96b26bb2d | |||
fabd53423e | |||
7ada66ffbf | |||
72caf29ad8 | |||
e2bd9ba692 | |||
c4a2778af1 | |||
46bb2bac4e | |||
383df8637f | |||
1a6831cf8b | |||
39a7c31d32 | |||
b419f2a321 | |||
92e333fcbe | |||
c6b4cc29b8 | |||
9a7ebb0f0a | |||
e525a80b91 | |||
6a3f0ca9aa | |||
4a939e74d1 | |||
72874d450f | |||
7780e1a3f1 | |||
0e4ea2e722 | |||
438d0ec6ec | |||
94e23b7f0f | |||
e6cd239dcb | |||
8d97ffba92 | |||
e46f4fb900 | |||
2b58204cb9 | |||
b91f061797 | |||
102ea607ab | |||
ba357ee99b | |||
ccae1a45e1 | |||
a30893dcdf | |||
8aa5aa2c5a | |||
a1626b464e | |||
0ea8551781 | |||
b0df1fb3bd | |||
66d9279445 | |||
b765f4cb21 | |||
1e299b7b9c | |||
652bf5a0d3 | |||
4f65c35407 | |||
90609f4ad4 | |||
ff76f3c865 | |||
ededfd9e55 | |||
e2c74b8dfe | |||
701897b00d | |||
930ac1c331 | |||
aa0bee9f69 | |||
f3d4d8dbda | |||
f0d0c925dc | |||
7bd456ee8c | |||
acfdc1bd5e | |||
261e0a474e | |||
a394778a8e | |||
2dddda685a | |||
129998e628 | |||
cef0ce4c09 | |||
d5854f7e92 | |||
f84bd5651e | |||
546a4aa8c7 | |||
94cd23fe4b | |||
c7c17243fb | |||
1585e2f321 | |||
4f13a1e69d | |||
a4e2ed7e2f | |||
1a0bb8f8c4 | |||
362b5709ff | |||
bc773f7d45 | |||
934475b959 | |||
665d528b87 | |||
865d7a7886 | |||
031cd6e771 | |||
2ccc5bb17d | |||
4eb6c7c057 | |||
b9f9b2ba7e | |||
6dbe96fca2 | |||
655a6da251 | |||
a100f182e1 | |||
0734122e75 | |||
4e284311e1 | |||
0cd28e402e | |||
260ed0fd5c | |||
ec82745c6d | |||
10e56fb0b9 | |||
5646bd3b5a | |||
6cb639317c | |||
db5c37c2fb | |||
a73a32f2ad | |||
44ac6fe4be | |||
1a24de3cd9 | |||
9f77531995 | |||
7b78338c76 | |||
021d7f9d1f | |||
338669b60f | |||
085d9185a9 | |||
1fd7a6eafd | |||
91b96e4b5d | |||
fc65d0f1f4 | |||
4eaf8794f6 | |||
fa8614891d | |||
c20a0a4a89 | |||
e66a245c78 | |||
eb7eb30814 | |||
57c3f2dd9e | |||
b772647137 | |||
dd2140d1d2 | |||
6cbd3446e5 | |||
b6d260bf2c | |||
53bb1522eb | |||
206e219467 | |||
8ee6204a42 | |||
803f1bea9e | |||
62419e94e1 | |||
d3c4d530b6 | |||
898407a0e9 | |||
66186c7354 | |||
36c769346c | |||
5f2bec75f5 | |||
7a9aaf9fe0 | |||
9ad90cea2e | |||
f2fec0b3b9 | |||
dae72d73d5 | |||
4a1eff40da | |||
d5bd82761a | |||
5cad8637cd | |||
607706ee2a | |||
2312ee27b7 | |||
4d2aa0b2c8 | |||
34450d6a13 | |||
1a6ece1312 | |||
e95f675e91 | |||
504ff37c47 | |||
41cdd03b1b | |||
e375173625 | |||
488a6b6496 | |||
5cdd2c3ee1 | |||
a0da6873c1 | |||
345d5737a2 | |||
f4d28dd3c3 | |||
c362081003 | |||
990a923463 | |||
56b781fcb8 | |||
e026f6efed | |||
e475da5fb4 | |||
c3026c67e9 | |||
103697fbdd | |||
cf17460b77 | |||
823a05c101 | |||
e5f95b97ce | |||
176eb762e3 | |||
15bd78c0e1 | |||
0f9d0c8c39 | |||
eefbdafc16 | |||
b0ecfeb294 | |||
48a8271b99 | |||
1604888254 | |||
4017f33447 | |||
f0527714db | |||
27d96f9b19 | |||
a5094fe873 | |||
1bd45283a9 | |||
a6dc0c37ba | |||
83434a89c7 | |||
b14c84bdad | |||
e98744871b | |||
c26ce93fc8 | |||
c856509759 | |||
5cb98ee29f | |||
bc29cd9848 | |||
502ab7f33f | |||
e0dba8840e | |||
4d13b4ada7 | |||
2a2e729f59 | |||
63cf94499b | |||
83a39468d5 | |||
9aba811cd0 | |||
e413409f9f | |||
e6a28fbb70 | |||
88acec5659 | |||
0f0d7f7a9a | |||
263f0d35d4 | |||
d713b96ad3 | |||
20285f0f98 | |||
f103c247b8 | |||
8e1807b4b7 | |||
f531e8d8ee | |||
78f860c672 | |||
0924518922 | |||
46d89619bd | |||
540749e4f1 | |||
3c5f01da89 | |||
70a79a8d25 | |||
0483c9eb27 | |||
61aad93f8d | |||
cd1aa26293 | |||
b656371142 | |||
9266edbf92 | |||
6335b1da47 | |||
8d5fc1ae48 | |||
67c30b8535 | |||
95651d7091 | |||
91b378aa43 | |||
ac7d9f5c3b | |||
b45d93a7dc | |||
6549305c9f | |||
6ea9eff844 | |||
e684fb421e | |||
8ba76c7a00 | |||
3d3eb966a4 | |||
491de52f17 | |||
69da2c52a4 | |||
9f6dffafda | |||
446de71c30 | |||
d47eaa423e | |||
e604ce83e9 | |||
ac250e9d84 | |||
617952c1e3 | |||
9f9e8c793b | |||
1f6594468d | |||
cc3cb35309 | |||
a923a6b5d1 | |||
68d1c23cfa | |||
8aa7da6be7 | |||
0be0dd5c6f | |||
f4ab9403a4 | |||
67ac4cf7ff | |||
002d3d9eac | |||
e1368962c1 | |||
4ae391e9fd | |||
c43fab2f18 | |||
20a317612e | |||
60753490c6 | |||
79ab26f118 | |||
7a5406e769 | |||
009aa0c296 | |||
2d87e4b5cc | |||
4ce5c045a8 | |||
047451d247 | |||
a097e3945f | |||
d48b03889d | |||
a96cae9e80 | |||
ef13fbe5f0 | |||
fa2b9ca515 | |||
ac5ef8f9be | |||
dda8ebefc4 | |||
f12ab4a3c3 | |||
da5decb2f7 | |||
8206e16952 | |||
f32bd4a667 | |||
5175a2ea18 | |||
98349915ec | |||
43b8190148 | |||
aec20424b8 | |||
62e0d8e2b6 | |||
c6cc1da4be | |||
b836bc2a11 | |||
3ccc19f768 | |||
d38d103399 | |||
114b2725ea | |||
e7eac03f04 | |||
75ca27f232 | |||
01de555888 | |||
78bc7aa8a1 | |||
3d3ffbf79a | |||
8b4608bda3 | |||
17a6671ead | |||
24c88e2b5b | |||
c4462cf340 | |||
3b7fbe4038 | |||
73e8afbc77 | |||
47f6e75de9 | |||
5f8104d531 | |||
276602e5f3 | |||
713b68adef | |||
79011171cb | |||
cd58e20fc2 | |||
fbdabf449a | |||
0a95f492ba | |||
27dba8a90d | |||
2b8bb0b705 | |||
b2a84e3be1 | |||
0468484788 | |||
b9dc97053f | |||
40ed173b60 | |||
fd5a813357 | |||
50726199b9 | |||
0676007430 | |||
97c49c9351 | |||
10689784be | |||
2eff5dda9e | |||
93b04f4f1f | |||
c616e82c47 | |||
d3f84c2dbd | |||
5e45753756 | |||
2a0afdcc60 | |||
cddf8b6f84 | |||
deabb1769a | |||
418450e7d3 | |||
f8f44b7b45 | |||
eb022b128c | |||
46ceab5526 | |||
4eb8ff1928 | |||
6a198ad746 | |||
88b87b1859 | |||
aa79d9a54e | |||
d95549070b | |||
af1f1aad14 | |||
cfee6f119f | |||
b9e34f53c3 | |||
394f1f1dc2 | |||
05ec7ea5d8 | |||
e1897acca3 | |||
5ee149b66e | |||
8e24d5af14 | |||
a31bb6c095 | |||
8012a459bf | |||
3255b687e5 | |||
22216846e3 | |||
eebde9b54a | |||
7d1058164b | |||
415be69ba8 | |||
62e9397c98 | |||
7996df532e | |||
617cae300f | |||
4b2de6f88c | |||
b4b85b7da4 | |||
3a98eaff7c | |||
4b5b7dc2fb | |||
9563290454 | |||
a2d9e97467 | |||
62a6af9261 | |||
a819f93fa9 | |||
8a13640c55 | |||
85ba12ff92 | |||
4f492d73b0 | |||
0375844f4f | |||
0dd41ae2f1 | |||
a8d54fdd7c | |||
34ac541260 | |||
099788b746 | |||
305017c6c8 | |||
e47d152af2 | |||
755adeaefd | |||
fed810334b | |||
0ff4de3d38 | |||
e04e754abb | |||
5c1f69628d | |||
0c0365746e | |||
0c4a34c9cb | |||
1318fd2856 | |||
44031c8b83 | |||
d6470ee81b | |||
a7c7088c1f | |||
dd6acbfc2f | |||
3fea6ec5a2 | |||
38df41e043 | |||
171cd8f2e4 | |||
914b5da897 | |||
52e92bfa5a | |||
938500e39b | |||
d036a33501 | |||
05c8db4750 | |||
d82dfc2bc2 | |||
c59f40892a | |||
4863605af7 | |||
685e74575a | |||
d1aca4519b | |||
4a4ede36ed | |||
a0afea050b | |||
e3130661f4 | |||
a1ee34c7a3 | |||
5193d0be71 | |||
31725dae0c | |||
6ce2e64961 | |||
5d25400942 | |||
a00b6a63c9 | |||
931cef52b1 | |||
3e8c5167dc | |||
2262c6feac | |||
fa7e7d58bf | |||
0705e879db | |||
1401c7ad13 | |||
d3e114c7b6 | |||
e1cdddc892 | |||
4abeefa7e8 | |||
7b826e11be | |||
47aef14ff3 | |||
33cc2e1a45 | |||
f337c71c5b | |||
ba00fed9b8 | |||
3434cd394a | |||
b6935b4f5f | |||
f0035bcee9 | |||
7e91c85822 | |||
33aa8d5d9c | |||
6cd5234c91 | |||
80fc0e13db | |||
6ee4c8014a | |||
e0e635e083 | |||
0215df9f96 | |||
ec5bb74d46 | |||
9982a52af5 | |||
24b28f28dd | |||
db992afec5 | |||
582009baed | |||
34939db8ef | |||
b61088195a | |||
19ab098be0 | |||
bf1fad9fc4 | |||
62b5ba2b33 | |||
f2a7b44884 | |||
c054b6aab3 | |||
7caec210ce | |||
5fdcf9047c | |||
3756af9638 | |||
8424fea634 | |||
874984fd98 | |||
0e4489b360 | |||
324e0eb69b | |||
e391e9bc27 | |||
77d120597d | |||
44b2ae1c10 | |||
12b04a97d6 | |||
46c73b80c6 | |||
127534632e | |||
9aca2575a8 | |||
1f2ad779f1 | |||
775d1972dc | |||
c835183abd | |||
e2473f0537 | |||
acaeaccf68 | |||
a9f3e61f2b | |||
790c72a1b8 | |||
e37d8540c2 | |||
db142d125b | |||
fab60f19ee | |||
5ed15e783d | |||
a1aafed513 | |||
a051f442df | |||
4f166e0d5c | |||
ef922135e6 | |||
9aec0e1b52 | |||
b065f83faf | |||
39b202176f | |||
3979eec34c | |||
0a50ee3cbd | |||
b73da3e552 | |||
cc7713ca73 | |||
2273f881b2 | |||
c0a85cd719 | |||
3cab9afa32 | |||
f91de2d669 | |||
261e42c33d | |||
e0739fa792 | |||
45b2af405b | |||
c03cd0e905 | |||
5a0893cbb9 | |||
a2eac57282 | |||
34017ea72c | |||
bcaa28cf6a | |||
093a54c527 | |||
45dc9e4bd3 | |||
f2635be0fb | |||
f3174cd191 | |||
f58cf4ec2a | |||
6a8f34098d | |||
c1ac2eb1d8 | |||
b8283d921f | |||
32a28880db | |||
38efed0d04 | |||
2c7ced7151 | |||
0b630576d4 | |||
1c33646765 | |||
3b038687b6 | |||
048e408390 | |||
ed0dcdd051 | |||
0f3bd4420e | |||
93bc4dc0fb | |||
c182e46001 | |||
47cdea0c8a | |||
6043c23c19 | |||
274cca6568 | |||
8c9af0e205 | |||
2f70b11abd | |||
aba7fbdb8a | |||
dbb05bd4ef | |||
856386e9bf | |||
3c43b6c6a6 | |||
99351848b7 | |||
0c439bf2e6 | |||
f38d990a54 | |||
0532b47fec | |||
40e7717aec | |||
3e737282dc | |||
708c0c48ef | |||
20bbb6924e | |||
5ee826d948 | |||
03d1550f60 | |||
79b8120106 | |||
0d2c27f60f | |||
4f56b9b6bd | |||
2496f181e3 | |||
ce6f8074a9 | |||
434ca29aef | |||
dea408daee | |||
7b263ae30e | |||
d5af08730b | |||
14e7e4df29 | |||
040b607792 | |||
5ea8a2db5f | |||
2d420d7676 | |||
6f22aaeec4 | |||
134e3fc79a | |||
b2673f1732 | |||
ec60086109 | |||
fc1c9e843f | |||
a9504a192b | |||
fe2b9921b4 |
.gitignoreCONTRIBUTING.mdCargo.lockCargo.tomlREADME.md
lib
README.md
logo.pngbsp_loader
common
deferred_loader
fixed_wide
linear_ops
ratio_ops
rbx_loader
roblox_emulator
snf
src
compat_worker.rsface_crawler.rsfile.rsgraphics.rsgraphics_worker.rsmodel_graphics.rsmodel_physics.rsphysics.rsphysics_worker.rssettings.rssetup.rswindow.rsworker.rs
strafe-client
tools
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
/target
|
||||
/textures
|
@ -1 +0,0 @@
|
||||
By contributing code to the [StrafesNET project](https://git.itzana.me/StrafesNET/strafe-client), you agree to license your contribution under the [License](LICENSE).
|
1276
Cargo.lock
generated
1276
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
45
Cargo.toml
45
Cargo.toml
@ -1,30 +1,19 @@
|
||||
[package]
|
||||
name = "strafe-client"
|
||||
version = "0.9.5"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-client"
|
||||
license = "Custom"
|
||||
description = "StrafesNET game client for bhop and surf."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
[workspace]
|
||||
members = [
|
||||
"lib/bsp_loader",
|
||||
"lib/common",
|
||||
"lib/deferred_loader",
|
||||
"lib/fixed_wide",
|
||||
"lib/linear_ops",
|
||||
"lib/ratio_ops",
|
||||
"lib/rbx_loader",
|
||||
"lib/roblox_emulator",
|
||||
"lib/snf",
|
||||
"strafe-client",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||
configparser = "3.0.2"
|
||||
ddsfile = "0.5.1"
|
||||
glam = "0.25.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
||||
parking_lot = "0.12.1"
|
||||
pollster = "0.3.0"
|
||||
strafesnet_bsp_loader = { version = "0.1.1", registry = "strafesnet" }
|
||||
strafesnet_common = { version = "0.1.2", registry = "strafesnet" }
|
||||
strafesnet_deferred_loader = { version = "0.3.0", features = ["legacy"], registry = "strafesnet" }
|
||||
strafesnet_rbx_loader = { version = "0.3.0", registry = "strafesnet" }
|
||||
wgpu = "22.0.0"
|
||||
winit = "0.30.4"
|
||||
|
||||
#[profile.release]
|
||||
[profile.release]
|
||||
#lto = true
|
||||
#strip = true
|
||||
#codegen-units = 1
|
||||
strip = true
|
||||
codegen-units = 1
|
||||
|
15
README.md
15
README.md
@ -1,10 +1,13 @@
|
||||
<img align="right" width="25%" src="strafe.png">
|
||||
<img align="right" width="25%" src="logo.png">
|
||||
|
||||
# Strafe Client
|
||||
In development client for jumping on squares (and riding on triangles)
|
||||
# Strafe Project
|
||||
Monorepo for working on projects related to strafe client.
|
||||
|
||||
## How to build and run
|
||||
1. Have rust and git installed
|
||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-client`
|
||||
3. `cd strafe-client`
|
||||
4. `cargo run --release`
|
||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
||||
3. `cd strafe-project`
|
||||
4. `cargo run --release --bin strafe-client`
|
||||
|
||||
## Licenses
|
||||
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client has a sole proprietor license.
|
||||
|
19
lib/README.md
Normal file
19
lib/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
Vectors: Fixed Size, Fixed Point, Wide
|
||||
======================================
|
||||
|
||||
## These exist separately in the Rust ecosystem, but not together.
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
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.
|
||||
</sub>
|
1
lib/bsp_loader/.gitignore
vendored
Normal file
1
lib/bsp_loader/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
16
lib/bsp_loader/Cargo.toml
Normal file
16
lib/bsp_loader/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "strafesnet_bsp_loader"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Convert Valve BSP 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.29.0"
|
||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||
vbsp = "0.6.0"
|
||||
vmdl = "0.2.0"
|
176
lib/bsp_loader/LICENSE-APACHE
Normal file
176
lib/bsp_loader/LICENSE-APACHE
Normal file
@ -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
|
23
lib/bsp_loader/LICENSE-MIT
Normal file
23
lib/bsp_loader/LICENSE-MIT
Normal file
@ -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.
|
19
lib/bsp_loader/README.md
Normal file
19
lib/bsp_loader/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
StrafesNET BSP Loader
|
||||
=====================
|
||||
|
||||
## Convert Valve BSP files into StrafesNET data structures
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
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.
|
||||
</sub>
|
393
lib/bsp_loader/src/bsp.rs
Normal file
393
lib/bsp_loader/src/bsp.rs
Normal file
@ -0,0 +1,393 @@
|
||||
use strafesnet_common::{gameplay_attributes, integer, map, model};
|
||||
|
||||
const VALVE_SCALE: f32 = 1.0 / 16.0;
|
||||
fn valve_transform([x, y, z]: [f32; 3]) -> integer::Planar64Vec3 {
|
||||
integer::vec3::try_from_f32_array([x * VALVE_SCALE, z * VALVE_SCALE, -y * VALVE_SCALE]).unwrap()
|
||||
}
|
||||
pub fn convert_bsp<AcquireRenderConfigId, AcquireMeshId>(
|
||||
bsp: &vbsp::Bsp,
|
||||
mut acquire_render_config_id: AcquireRenderConfigId,
|
||||
mut acquire_mesh_id: AcquireMeshId,
|
||||
) -> PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId,
|
||||
AcquireMeshId: FnMut(&str) -> model::MeshId,
|
||||
{
|
||||
//figure out real attributes later
|
||||
let mut unique_attributes = Vec::new();
|
||||
unique_attributes.push(gameplay_attributes::CollisionAttributes::Decoration);
|
||||
const TEMP_TOUCH_ME_ATTRIBUTE: gameplay_attributes::CollisionAttributesId =
|
||||
gameplay_attributes::CollisionAttributesId::new(0);
|
||||
|
||||
let mut prop_mesh_count = 0;
|
||||
//declare all prop models to Loader
|
||||
let prop_models = bsp
|
||||
.static_props()
|
||||
.map(|prop| {
|
||||
//get or create mesh_id
|
||||
let mesh_id = acquire_mesh_id(prop.model());
|
||||
//not the most failsafe code but this is just for the map tool lmao
|
||||
if prop_mesh_count == mesh_id.get() {
|
||||
prop_mesh_count += 1;
|
||||
};
|
||||
let placement = prop.as_prop_placement();
|
||||
model::Model {
|
||||
mesh: mesh_id,
|
||||
attributes: TEMP_TOUCH_ME_ATTRIBUTE,
|
||||
transform: integer::Planar64Affine3::new(
|
||||
integer::mat3::try_from_f32_array_2d(
|
||||
(glam::Mat3A::from_diagonal(glam::Vec3::splat(placement.scale))
|
||||
//TODO: figure this out
|
||||
*glam::Mat3A::from_quat(glam::Quat::from_array(placement.rotation.into())))
|
||||
.to_cols_array_2d(),
|
||||
)
|
||||
.unwrap(),
|
||||
valve_transform(placement.origin.into()),
|
||||
),
|
||||
color: glam::Vec4::ONE,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
//TODO: make the main map one single mesh with a bunch of different physics groups and graphics groups
|
||||
|
||||
//the generated MeshIds in here will collide with the Loader Mesh Ids
|
||||
//but I can't think of a good workaround other than just remapping one later.
|
||||
let world_meshes: Vec<model::Mesh> = bsp
|
||||
.models()
|
||||
.map(|world_model| {
|
||||
//non-deduplicated
|
||||
let mut spam_pos = Vec::new();
|
||||
let mut spam_tex = Vec::new();
|
||||
let mut spam_normal = Vec::new();
|
||||
let mut spam_vertices = Vec::new();
|
||||
let mut graphics_groups = Vec::new();
|
||||
let mut physics_group = model::IndexedPhysicsGroup::default();
|
||||
let polygon_groups = world_model
|
||||
.faces()
|
||||
.enumerate()
|
||||
.map(|(polygon_group_id, face)| {
|
||||
let polygon_group_id = model::PolygonGroupId::new(polygon_group_id as u32);
|
||||
let face_texture = face.texture();
|
||||
let face_texture_data = face_texture.texture_data();
|
||||
//this would be better as a 4x2 matrix
|
||||
let texture_transform_u =
|
||||
glam::Vec4::from_array(face_texture.texture_transforms_u)
|
||||
/ (face_texture_data.width as f32);
|
||||
let texture_transform_v =
|
||||
glam::Vec4::from_array(face_texture.texture_transforms_v)
|
||||
/ (face_texture_data.height as f32);
|
||||
|
||||
//this automatically figures out what the texture is trying to do and creates
|
||||
//a render config for it, and then returns the id to that render config
|
||||
let render_id = acquire_render_config_id(Some(face_texture_data.name()));
|
||||
|
||||
//normal
|
||||
let normal = face.normal();
|
||||
let normal_idx = spam_normal.len() as u32;
|
||||
spam_normal.push(valve_transform(normal.into()));
|
||||
let mut polygon_iter = face.vertex_positions().map(|vertex_position| {
|
||||
//world_model.origin seems to always be 0,0,0
|
||||
let vertex_xyz = (world_model.origin + vertex_position).into();
|
||||
let pos_idx = spam_pos.len();
|
||||
spam_pos.push(valve_transform(vertex_xyz));
|
||||
|
||||
//calculate texture coordinates
|
||||
let pos = glam::Vec3::from_array(vertex_xyz).extend(1.0);
|
||||
let tex =
|
||||
glam::vec2(texture_transform_u.dot(pos), texture_transform_v.dot(pos));
|
||||
let tex_idx = spam_tex.len() as u32;
|
||||
spam_tex.push(tex);
|
||||
|
||||
let vertex_id = model::VertexId::new(spam_vertices.len() as u32);
|
||||
spam_vertices.push(model::IndexedVertex {
|
||||
pos: model::PositionId::new(pos_idx as u32),
|
||||
tex: model::TextureCoordinateId::new(tex_idx as u32),
|
||||
normal: model::NormalId::new(normal_idx),
|
||||
color: model::ColorId::new(0),
|
||||
});
|
||||
vertex_id
|
||||
});
|
||||
let polygon_list = std::iter::from_fn(move || {
|
||||
match (
|
||||
polygon_iter.next(),
|
||||
polygon_iter.next(),
|
||||
polygon_iter.next(),
|
||||
) {
|
||||
(Some(v1), Some(v2), Some(v3)) => Some(vec![v1, v2, v3]),
|
||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if face.is_visible() {
|
||||
//TODO: deduplicate graphics groups by render id
|
||||
graphics_groups.push(model::IndexedGraphicsGroup {
|
||||
render: render_id,
|
||||
groups: vec![polygon_group_id],
|
||||
})
|
||||
}
|
||||
physics_group.groups.push(polygon_group_id);
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(polygon_list))
|
||||
})
|
||||
.collect();
|
||||
model::Mesh {
|
||||
unique_pos: spam_pos,
|
||||
unique_tex: spam_tex,
|
||||
unique_normal: spam_normal,
|
||||
unique_color: vec![glam::Vec4::ONE],
|
||||
unique_vertices: spam_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups: vec![physics_group],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let world_models:Vec<model::Model>=
|
||||
//one instance of the main world mesh
|
||||
std::iter::once((
|
||||
//world_model
|
||||
model::MeshId::new(0),
|
||||
//model_origin
|
||||
vbsp::Vector::from([0.0,0.0,0.0]),
|
||||
//model_color
|
||||
vbsp::Color{r:255,g:255,b:255},
|
||||
)).chain(
|
||||
//entities sprinkle instances of the other meshes around
|
||||
bsp.entities.iter()
|
||||
.flat_map(|ent|ent.parse())//ignore entity parsing errors
|
||||
.filter_map(|ent|match ent{
|
||||
vbsp::Entity::Brush(brush)=>Some(brush),
|
||||
vbsp::Entity::BrushIllusionary(brush)=>Some(brush),
|
||||
vbsp::Entity::BrushWall(brush)=>Some(brush),
|
||||
vbsp::Entity::BrushWallToggle(brush)=>Some(brush),
|
||||
_=>None,
|
||||
}).flat_map(|brush|
|
||||
//The first character of brush.model is '*'
|
||||
brush.model[1..].parse().map(|mesh_id|//ignore parse int errors
|
||||
(model::MeshId::new(mesh_id),brush.origin,brush.color)
|
||||
)
|
||||
)
|
||||
).map(|(mesh_id,model_origin,vbsp::Color{r,g,b})|{
|
||||
model::Model{
|
||||
mesh:mesh_id,
|
||||
attributes:TEMP_TOUCH_ME_ATTRIBUTE,
|
||||
transform:integer::Planar64Affine3::new(
|
||||
integer::mat3::identity(),
|
||||
valve_transform(model_origin.into())
|
||||
),
|
||||
color:(glam::Vec3::from_array([r as f32,g as f32,b as f32])/255.0).extend(1.0),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
PartialMap1 {
|
||||
attributes: unique_attributes,
|
||||
world_meshes,
|
||||
prop_models,
|
||||
world_models,
|
||||
modes: strafesnet_common::gameplay_modes::Modes::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
//partially constructed map types
|
||||
pub struct PartialMap1 {
|
||||
attributes: Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_models: Vec<model::Model>,
|
||||
world_meshes: Vec<model::Mesh>,
|
||||
world_models: Vec<model::Model>,
|
||||
modes: strafesnet_common::gameplay_modes::Modes,
|
||||
}
|
||||
impl PartialMap1 {
|
||||
pub fn add_prop_meshes<AcquireRenderConfigId>(
|
||||
self,
|
||||
prop_meshes: impl IntoIterator<Item = (model::MeshId, crate::data::ModelData)>,
|
||||
mut acquire_render_config_id: AcquireRenderConfigId,
|
||||
) -> PartialMap2
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId,
|
||||
{
|
||||
PartialMap2 {
|
||||
attributes: self.attributes,
|
||||
prop_meshes: prop_meshes.into_iter().filter_map(|(mesh_id,model_data)|
|
||||
//this will generate new render ids and texture ids
|
||||
match convert_mesh(model_data,&mut acquire_render_config_id){
|
||||
Ok(mesh)=>Some((mesh_id,mesh)),
|
||||
Err(e)=>{
|
||||
println!("error converting mesh: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
).collect(),
|
||||
prop_models: self.prop_models,
|
||||
world_meshes: self.world_meshes,
|
||||
world_models: self.world_models,
|
||||
modes: self.modes,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct PartialMap2 {
|
||||
attributes: Vec<strafesnet_common::gameplay_attributes::CollisionAttributes>,
|
||||
prop_meshes: Vec<(model::MeshId, model::Mesh)>,
|
||||
prop_models: Vec<model::Model>,
|
||||
world_meshes: Vec<model::Mesh>,
|
||||
world_models: Vec<model::Model>,
|
||||
modes: strafesnet_common::gameplay_modes::Modes,
|
||||
}
|
||||
impl PartialMap2 {
|
||||
pub fn add_render_configs_and_textures(
|
||||
mut self,
|
||||
render_configs: impl IntoIterator<Item = (model::RenderConfigId, model::RenderConfig)>,
|
||||
textures: impl IntoIterator<Item = (model::TextureId, Vec<u8>)>,
|
||||
) -> map::CompleteMap {
|
||||
//merge mesh and model lists, flatten and remap all ids
|
||||
let mesh_id_offset = self.world_meshes.len();
|
||||
println!("prop_meshes.len()={}", self.prop_meshes.len());
|
||||
let (mut prop_meshes, prop_mesh_id_map): (
|
||||
Vec<model::Mesh>,
|
||||
std::collections::HashMap<model::MeshId, model::MeshId>,
|
||||
) = self
|
||||
.prop_meshes
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(new_mesh_id, (old_mesh_id, mesh))| {
|
||||
(
|
||||
mesh,
|
||||
(
|
||||
old_mesh_id,
|
||||
model::MeshId::new((mesh_id_offset + new_mesh_id) as u32),
|
||||
),
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
self.world_meshes.append(&mut prop_meshes);
|
||||
//there is no modes or runtime behaviour with references to the model ids currently
|
||||
//so just relentlessly cull them if the mesh is missing
|
||||
self.world_models
|
||||
.extend(self.prop_models.into_iter().filter_map(|mut model| {
|
||||
prop_mesh_id_map.get(&model.mesh).map(|&new_mesh_id| {
|
||||
model.mesh = new_mesh_id;
|
||||
model
|
||||
})
|
||||
}));
|
||||
//let mut models=Vec::new();
|
||||
let (textures, texture_id_map): (
|
||||
Vec<Vec<u8>>,
|
||||
std::collections::HashMap<model::TextureId, model::TextureId>,
|
||||
) = textures
|
||||
.into_iter()
|
||||
//.filter_map(f) cull unused textures
|
||||
.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.world_meshes,
|
||||
models: self.world_models,
|
||||
textures,
|
||||
render_configs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_mesh<AcquireRenderConfigId>(
|
||||
model_data: crate::data::ModelData,
|
||||
acquire_render_config_id: &mut AcquireRenderConfigId,
|
||||
) -> Result<model::Mesh, vmdl::ModelError>
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> model::RenderConfigId,
|
||||
{
|
||||
let model = model_data.read_model()?;
|
||||
let texture_paths = model.texture_directories();
|
||||
if texture_paths.len() != 1 {
|
||||
println!("WARNING: multiple texture paths");
|
||||
}
|
||||
let skin = model.skin_tables().nth(0).unwrap();
|
||||
|
||||
let mut spam_pos = Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_normal = Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_tex = Vec::with_capacity(model.vertices().len());
|
||||
let mut spam_vertices = Vec::with_capacity(model.vertices().len());
|
||||
for (i, vertex) in model.vertices().iter().enumerate() {
|
||||
spam_pos.push(valve_transform(vertex.position.into()));
|
||||
spam_normal.push(valve_transform(vertex.normal.into()));
|
||||
spam_tex.push(glam::Vec2::from_array(vertex.texture_coordinates));
|
||||
spam_vertices.push(model::IndexedVertex {
|
||||
pos: model::PositionId::new(i as u32),
|
||||
tex: model::TextureCoordinateId::new(i as u32),
|
||||
normal: model::NormalId::new(i as u32),
|
||||
color: model::ColorId::new(0),
|
||||
});
|
||||
}
|
||||
let mut graphics_groups = Vec::new();
|
||||
let mut physics_groups = Vec::new();
|
||||
let polygon_groups = model
|
||||
.meshes()
|
||||
.enumerate()
|
||||
.map(|(polygon_group_id, mesh)| {
|
||||
let polygon_group_id = model::PolygonGroupId::new(polygon_group_id as u32);
|
||||
|
||||
let render_id = if let (Some(texture_path), Some(texture_name)) =
|
||||
(texture_paths.get(0), skin.texture(mesh.material_index()))
|
||||
{
|
||||
let mut path = std::path::PathBuf::from(texture_path.as_str());
|
||||
path.push(texture_name);
|
||||
acquire_render_config_id(path.as_os_str().to_str())
|
||||
} else {
|
||||
acquire_render_config_id(None)
|
||||
};
|
||||
|
||||
graphics_groups.push(model::IndexedGraphicsGroup {
|
||||
render: render_id,
|
||||
groups: vec![polygon_group_id],
|
||||
});
|
||||
physics_groups.push(model::IndexedPhysicsGroup {
|
||||
groups: vec![polygon_group_id],
|
||||
});
|
||||
model::PolygonGroup::PolygonList(model::PolygonList::new(
|
||||
//looking at the code, it would seem that the strips are pre-deindexed into triangle lists when calling this function
|
||||
mesh.vertex_strip_indices()
|
||||
.flat_map(|mut strip| {
|
||||
std::iter::from_fn(move || {
|
||||
match (strip.next(), strip.next(), strip.next()) {
|
||||
(Some(v1), Some(v2), Some(v3)) => Some(
|
||||
[v1, v2, v3]
|
||||
.map(|vertex_id| model::VertexId::new(vertex_id as u32))
|
||||
.to_vec(),
|
||||
),
|
||||
//ignore extra vertices, not sure what to do in this case, failing the whole conversion could be appropriate
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
Ok(model::Mesh {
|
||||
unique_pos: spam_pos,
|
||||
unique_normal: spam_normal,
|
||||
unique_tex: spam_tex,
|
||||
unique_color: vec![glam::Vec4::ONE],
|
||||
unique_vertices: spam_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups,
|
||||
})
|
||||
}
|
60
lib/bsp_loader/src/data.rs
Normal file
60
lib/bsp_loader/src/data.rs
Normal file
@ -0,0 +1,60 @@
|
||||
pub struct Bsp(vbsp::Bsp);
|
||||
impl Bsp {
|
||||
pub const fn new(value: vbsp::Bsp) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<vbsp::Bsp> for Bsp {
|
||||
fn as_ref(&self) -> &vbsp::Bsp {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MdlData(Vec<u8>);
|
||||
impl MdlData {
|
||||
pub const fn new(value: Vec<u8>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for MdlData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
pub struct VtxData(Vec<u8>);
|
||||
impl VtxData {
|
||||
pub const fn new(value: Vec<u8>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for VtxData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
pub struct VvdData(Vec<u8>);
|
||||
impl VvdData {
|
||||
pub const fn new(value: Vec<u8>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
impl AsRef<[u8]> for VvdData {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModelData {
|
||||
pub mdl: MdlData,
|
||||
pub vtx: VtxData,
|
||||
pub vvd: VvdData,
|
||||
}
|
||||
impl ModelData {
|
||||
pub fn read_model(&self) -> Result<vmdl::Model, vmdl::ModelError> {
|
||||
Ok(vmdl::Model::from_parts(
|
||||
vmdl::mdl::Mdl::read(self.mdl.as_ref())?,
|
||||
vmdl::vtx::Vtx::read(self.vtx.as_ref())?,
|
||||
vmdl::vvd::Vvd::read(self.vvd.as_ref())?,
|
||||
))
|
||||
}
|
||||
}
|
39
lib/bsp_loader/src/lib.rs
Normal file
39
lib/bsp_loader/src/lib.rs
Normal file
@ -0,0 +1,39 @@
|
||||
mod bsp;
|
||||
pub mod data;
|
||||
|
||||
pub use data::Bsp;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReadError {
|
||||
Bsp(vbsp::BspError),
|
||||
Io(std::io::Error),
|
||||
}
|
||||
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: std::io::Read>(mut input: R) -> Result<Bsp, ReadError> {
|
||||
let mut s = Vec::new();
|
||||
|
||||
//TODO: mmap
|
||||
input.read_to_end(&mut s).map_err(ReadError::Io)?;
|
||||
|
||||
vbsp::Bsp::read(s.as_slice())
|
||||
.map(Bsp::new)
|
||||
.map_err(ReadError::Bsp)
|
||||
}
|
||||
|
||||
pub fn convert<AcquireRenderConfigId, AcquireMeshId>(
|
||||
bsp: &Bsp,
|
||||
acquire_render_config_id: AcquireRenderConfigId,
|
||||
acquire_mesh_id: AcquireMeshId,
|
||||
) -> bsp::PartialMap1
|
||||
where
|
||||
AcquireRenderConfigId: FnMut(Option<&str>) -> strafesnet_common::model::RenderConfigId,
|
||||
AcquireMeshId: FnMut(&str) -> strafesnet_common::model::MeshId,
|
||||
{
|
||||
bsp::convert_bsp(bsp.as_ref(), acquire_render_config_id, acquire_mesh_id)
|
||||
}
|
1
lib/common/.gitignore
vendored
Normal file
1
lib/common/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
19
lib/common/Cargo.toml
Normal file
19
lib/common/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "strafesnet_common"
|
||||
version = "0.5.2"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Common types and helpers for Strafe Client associated projects."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.4"
|
||||
bitflags = "2.6.0"
|
||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] }
|
||||
linear_ops = { path = "../linear_ops", registry = "strafesnet", features = ["deferred-division","named-fields"] }
|
||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet" }
|
||||
glam = "0.29.0"
|
||||
id = { version = "0.1.0", registry = "strafesnet" }
|
176
lib/common/LICENSE-APACHE
Normal file
176
lib/common/LICENSE-APACHE
Normal file
@ -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
|
23
lib/common/LICENSE-MIT
Normal file
23
lib/common/LICENSE-MIT
Normal file
@ -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.
|
19
lib/common/README.md
Normal file
19
lib/common/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
StrafesNET Common Library
|
||||
=========================
|
||||
|
||||
## Common types used in the StrafesNET ecosystem
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
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.
|
||||
</sub>
|
59
lib/common/src/aabb.rs
Normal file
59
lib/common/src/aabb.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use crate::integer::{vec3, Planar64Vec3};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Aabb {
|
||||
min: Planar64Vec3,
|
||||
max: Planar64Vec3,
|
||||
}
|
||||
|
||||
impl Default for Aabb {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
min: vec3::MAX,
|
||||
max: vec3::MIN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Aabb {
|
||||
pub const fn new(min: Planar64Vec3, max: Planar64Vec3) -> Self {
|
||||
Self { min, max }
|
||||
}
|
||||
pub const fn max(&self) -> Planar64Vec3 {
|
||||
self.max
|
||||
}
|
||||
pub const fn min(&self) -> Planar64Vec3 {
|
||||
self.min
|
||||
}
|
||||
pub fn grow(&mut self, point: Planar64Vec3) {
|
||||
self.min = self.min.min(point);
|
||||
self.max = self.max.max(point);
|
||||
}
|
||||
pub fn join(&mut self, aabb: &Aabb) {
|
||||
self.min = self.min.min(aabb.min);
|
||||
self.max = self.max.max(aabb.max);
|
||||
}
|
||||
pub fn inflate(&mut self, hs: Planar64Vec3) {
|
||||
self.min -= hs;
|
||||
self.max += hs;
|
||||
}
|
||||
pub fn intersects(&self, aabb: &Aabb) -> bool {
|
||||
let bvec = self.min.lt(aabb.max) & aabb.min.lt(self.max);
|
||||
bvec.all()
|
||||
}
|
||||
pub fn size(&self) -> Planar64Vec3 {
|
||||
self.max - self.min
|
||||
}
|
||||
pub fn center(&self) -> Planar64Vec3 {
|
||||
self.min + (self.max - self.min) >> 1
|
||||
}
|
||||
//probably use floats for area & volume because we don't care about precision
|
||||
// pub fn area_weight(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y+d.y*d.z+d.z*d.x
|
||||
// }
|
||||
// pub fn volume(&self)->f32{
|
||||
// let d=self.max-self.min;
|
||||
// d.x*d.y*d.z
|
||||
// }
|
||||
}
|
226
lib/common/src/bvh.rs
Normal file
226
lib/common/src/bvh.rs
Normal file
@ -0,0 +1,226 @@
|
||||
use crate::aabb::Aabb;
|
||||
|
||||
//da algaritum
|
||||
//lista boxens
|
||||
//sort by {minx,maxx,miny,maxy,minz,maxz} (6 lists)
|
||||
//find the sets that minimizes the sum of surface areas
|
||||
//splitting is done when the minimum split sum of surface areas is larger than the node's own surface area
|
||||
|
||||
//start with bisection into octrees because a bad bvh is still 1000x better than no bvh
|
||||
//sort the centerpoints on each axis (3 lists)
|
||||
//bv is put into octant based on whether it is upper or lower in each list
|
||||
|
||||
pub enum RecursiveContent<R, T> {
|
||||
Branch(Vec<R>),
|
||||
Leaf(T),
|
||||
}
|
||||
impl<R, T> Default for RecursiveContent<R, T> {
|
||||
fn default() -> Self {
|
||||
Self::Branch(Vec::new())
|
||||
}
|
||||
}
|
||||
pub struct BvhNode<T> {
|
||||
content: RecursiveContent<BvhNode<T>, T>,
|
||||
aabb: Aabb,
|
||||
}
|
||||
impl<T> Default for BvhNode<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: Default::default(),
|
||||
aabb: Aabb::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct BvhWeightNode<W, T> {
|
||||
content: RecursiveContent<BvhWeightNode<W, T>, T>,
|
||||
weight: W,
|
||||
aabb: Aabb,
|
||||
}
|
||||
|
||||
impl<T> BvhNode<T> {
|
||||
pub fn the_tester<F: FnMut(&T)>(&self, aabb: &Aabb, f: &mut F) {
|
||||
match &self.content {
|
||||
RecursiveContent::Leaf(model) => f(model),
|
||||
RecursiveContent::Branch(children) => {
|
||||
for child in children {
|
||||
//this test could be moved outside the match statement
|
||||
//but that would test the root node aabb
|
||||
//you're probably not going to spend a lot of time outside the map,
|
||||
//so the test is extra work for nothing
|
||||
if aabb.intersects(&child.aabb) {
|
||||
child.the_tester(aabb, f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn into_visitor<F: FnMut(T)>(self, f: &mut F) {
|
||||
match self.content {
|
||||
RecursiveContent::Leaf(model) => f(model),
|
||||
RecursiveContent::Branch(children) => {
|
||||
for child in children {
|
||||
child.into_visitor(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn weigh_contents<W: Copy + std::iter::Sum<W>, F: Fn(&T) -> W>(
|
||||
self,
|
||||
f: &F,
|
||||
) -> BvhWeightNode<W, T> {
|
||||
match self.content {
|
||||
RecursiveContent::Leaf(model) => BvhWeightNode {
|
||||
weight: f(&model),
|
||||
content: RecursiveContent::Leaf(model),
|
||||
aabb: self.aabb,
|
||||
},
|
||||
RecursiveContent::Branch(children) => {
|
||||
let branch: Vec<BvhWeightNode<W, T>> = children
|
||||
.into_iter()
|
||||
.map(|child| child.weigh_contents(f))
|
||||
.collect();
|
||||
BvhWeightNode {
|
||||
weight: branch.iter().map(|node| node.weight).sum(),
|
||||
content: RecursiveContent::Branch(branch),
|
||||
aabb: self.aabb,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W, T> BvhWeightNode<W, T> {
|
||||
pub const fn weight(&self) -> &W {
|
||||
&self.weight
|
||||
}
|
||||
pub const fn aabb(&self) -> &Aabb {
|
||||
&self.aabb
|
||||
}
|
||||
pub fn into_content(self) -> RecursiveContent<BvhWeightNode<W, T>, T> {
|
||||
self.content
|
||||
}
|
||||
pub fn into_visitor<F: FnMut(T)>(self, f: &mut F) {
|
||||
match self.content {
|
||||
RecursiveContent::Leaf(model) => f(model),
|
||||
RecursiveContent::Branch(children) => {
|
||||
for child in children {
|
||||
child.into_visitor(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_bvh<T>(boxen: Vec<(T, Aabb)>) -> BvhNode<T> {
|
||||
generate_bvh_node(boxen, false)
|
||||
}
|
||||
|
||||
fn generate_bvh_node<T>(boxen: Vec<(T, Aabb)>, force: bool) -> BvhNode<T> {
|
||||
let n = boxen.len();
|
||||
if force || n < 20 {
|
||||
let mut aabb = Aabb::default();
|
||||
let nodes = boxen
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
aabb.join(&b.1);
|
||||
BvhNode {
|
||||
content: RecursiveContent::Leaf(b.0),
|
||||
aabb: b.1,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
BvhNode {
|
||||
content: RecursiveContent::Branch(nodes),
|
||||
aabb,
|
||||
}
|
||||
} else {
|
||||
let mut sort_x = Vec::with_capacity(n);
|
||||
let mut sort_y = Vec::with_capacity(n);
|
||||
let mut sort_z = Vec::with_capacity(n);
|
||||
for (i, (_, aabb)) in boxen.iter().enumerate() {
|
||||
let center = aabb.center();
|
||||
sort_x.push((i, center.x));
|
||||
sort_y.push((i, center.y));
|
||||
sort_z.push((i, center.z));
|
||||
}
|
||||
sort_x.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1));
|
||||
sort_y.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1));
|
||||
sort_z.sort_by(|tup0, tup1| tup0.1.cmp(&tup1.1));
|
||||
let h = n / 2;
|
||||
let median_x = sort_x[h].1;
|
||||
let median_y = sort_y[h].1;
|
||||
let median_z = sort_z[h].1;
|
||||
//locate a run of values equal to the median
|
||||
//partition point gives the first index for which the predicate evaluates to false
|
||||
let first_index_eq_median_x = sort_x.partition_point(|&(_, x)| x < median_x);
|
||||
let first_index_eq_median_y = sort_y.partition_point(|&(_, y)| y < median_y);
|
||||
let first_index_eq_median_z = sort_z.partition_point(|&(_, z)| z < median_z);
|
||||
let first_index_gt_median_x = sort_x.partition_point(|&(_, x)| x <= median_x);
|
||||
let first_index_gt_median_y = sort_y.partition_point(|&(_, y)| y <= median_y);
|
||||
let first_index_gt_median_z = sort_z.partition_point(|&(_, z)| z <= median_z);
|
||||
//pick which side median value copies go into such that both sides are as balanced as possible based on distance from n/2
|
||||
let partition_point_x =
|
||||
if n.abs_diff(2 * first_index_eq_median_x) < n.abs_diff(2 * first_index_gt_median_x) {
|
||||
first_index_eq_median_x
|
||||
} else {
|
||||
first_index_gt_median_x
|
||||
};
|
||||
let partition_point_y =
|
||||
if n.abs_diff(2 * first_index_eq_median_y) < n.abs_diff(2 * first_index_gt_median_y) {
|
||||
first_index_eq_median_y
|
||||
} else {
|
||||
first_index_gt_median_y
|
||||
};
|
||||
let partition_point_z =
|
||||
if n.abs_diff(2 * first_index_eq_median_z) < n.abs_diff(2 * first_index_gt_median_z) {
|
||||
first_index_eq_median_z
|
||||
} else {
|
||||
first_index_gt_median_z
|
||||
};
|
||||
//this ids which octant the boxen is put in
|
||||
let mut octant = vec![0; n];
|
||||
for &(i, _) in &sort_x[partition_point_x..] {
|
||||
octant[i] += 1 << 0;
|
||||
}
|
||||
for &(i, _) in &sort_y[partition_point_y..] {
|
||||
octant[i] += 1 << 1;
|
||||
}
|
||||
for &(i, _) in &sort_z[partition_point_z..] {
|
||||
octant[i] += 1 << 2;
|
||||
}
|
||||
//generate lists for unique octant values
|
||||
let mut list_list = Vec::with_capacity(8);
|
||||
let mut octant_list = Vec::with_capacity(8);
|
||||
for (i, (data, aabb)) in boxen.into_iter().enumerate() {
|
||||
let octant_id = octant[i];
|
||||
let list_id = if let Some(list_id) = octant_list.iter().position(|&id| id == octant_id)
|
||||
{
|
||||
list_id
|
||||
} else {
|
||||
let list_id = list_list.len();
|
||||
octant_list.push(octant_id);
|
||||
list_list.push(Vec::new());
|
||||
list_id
|
||||
};
|
||||
list_list[list_id].push((data, aabb));
|
||||
}
|
||||
let mut aabb = Aabb::default();
|
||||
if list_list.len() == 1 {
|
||||
generate_bvh_node(list_list.remove(0), true)
|
||||
} else {
|
||||
BvhNode {
|
||||
content: RecursiveContent::Branch(
|
||||
list_list
|
||||
.into_iter()
|
||||
.map(|b| {
|
||||
let node = generate_bvh_node(b, false);
|
||||
aabb.join(&node.aabb);
|
||||
node
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
aabb,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
lib/common/src/controls_bitflag.rs
Normal file
25
lib/common/src/controls_bitflag.rs
Normal file
@ -0,0 +1,25 @@
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone,Copy,Debug,Default)]
|
||||
pub struct Controls:u32{
|
||||
const MoveForward=1<<0;
|
||||
const MoveLeft=1<<1;
|
||||
const MoveBackward=1<<2;
|
||||
const MoveRight=1<<3;
|
||||
const MoveUp=1<<4;
|
||||
const MoveDown=1<<5;
|
||||
const LookUp=1<<6;
|
||||
const LookLeft=1<<7;
|
||||
const LookDown=1<<8;
|
||||
const LookRight=1<<9;
|
||||
const Jump=1<<10;
|
||||
const Crouch=1<<11;
|
||||
const Sprint=1<<12;
|
||||
const Zoom=1<<13;
|
||||
const Use=1<<14;//Interact with object
|
||||
const PrimaryAction=1<<15;//LBM/Shoot/Melee
|
||||
const SecondaryAction=1<<16;//RMB/ADS/Block
|
||||
|
||||
const WASD=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).bits();
|
||||
const WASDQE=Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown).bits();
|
||||
}
|
||||
}
|
192
lib/common/src/gameplay_attributes.rs
Normal file
192
lib/common/src/gameplay_attributes.rs
Normal file
@ -0,0 +1,192 @@
|
||||
use crate::integer::{AbsoluteTime, Planar64, Planar64Vec3};
|
||||
use crate::model;
|
||||
|
||||
//you have this effect while in contact
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct ContactingLadder {
|
||||
pub sticky: bool,
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum ContactingBehaviour {
|
||||
Surf,
|
||||
Ladder(ContactingLadder),
|
||||
NoJump,
|
||||
Cling, //usable as a zipline, or other weird and wonderful things
|
||||
Elastic(u32), //[1/2^32,1] 0=None (elasticity+1)/2^32
|
||||
}
|
||||
//you have this effect while intersecting
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct IntersectingWater {
|
||||
pub viscosity: Planar64,
|
||||
pub density: Planar64,
|
||||
pub velocity: Planar64Vec3,
|
||||
}
|
||||
//All models can be given these attributes
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Accelerator {
|
||||
pub acceleration: Planar64Vec3,
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum Booster {
|
||||
//Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more
|
||||
Velocity(Planar64Vec3), //straight up boost velocity adds to your current velocity
|
||||
Energy {
|
||||
direction: Planar64Vec3,
|
||||
energy: Planar64,
|
||||
}, //increase energy in direction
|
||||
AirTime(AbsoluteTime), //increase airtime, invariant across mass and gravity changes
|
||||
Height(Planar64), //increase height, invariant across mass and gravity changes
|
||||
}
|
||||
impl Booster {
|
||||
pub fn boost(&self, velocity: Planar64Vec3) -> Planar64Vec3 {
|
||||
match self {
|
||||
&Booster::Velocity(boost_velocity) => velocity + boost_velocity,
|
||||
&Booster::Energy { .. } => {
|
||||
todo!()
|
||||
//let d=direction.dot(velocity);
|
||||
//TODO: think about negative
|
||||
//velocity+direction.with_length((d*d+energy).sqrt()-d)
|
||||
}
|
||||
Booster::AirTime(_) => todo!(),
|
||||
Booster::Height(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum TrajectoryChoice {
|
||||
HighArcLongDuration, //underhand lob at target: less horizontal speed and more air time
|
||||
LowArcShortDuration, //overhand throw at target: more horizontal speed and less air time
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum SetTrajectory {
|
||||
//Speed-type SetTrajectory
|
||||
AirTime(AbsoluteTime), //air time (relative to gravity direction) is invariant across mass and gravity changes
|
||||
Height(Planar64), //boost height (relative to gravity direction) is invariant across mass and gravity changes
|
||||
DotVelocity {
|
||||
direction: Planar64Vec3,
|
||||
dot: Planar64,
|
||||
}, //set your velocity in a specific direction without touching other directions
|
||||
//Velocity-type SetTrajectory
|
||||
TargetPointTime {
|
||||
//launch on a trajectory that will land at a target point in a set amount of time
|
||||
target_point: Planar64Vec3,
|
||||
time: AbsoluteTime, //short time = fast and direct, long time = launch high in the air, negative time = wrong way
|
||||
},
|
||||
TargetPointSpeed {
|
||||
//launch at a fixed speed and land at a target point
|
||||
target_point: Planar64Vec3,
|
||||
speed: Planar64, //if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead
|
||||
trajectory_choice: TrajectoryChoice,
|
||||
},
|
||||
Velocity(Planar64Vec3), //SetVelocity
|
||||
}
|
||||
impl SetTrajectory {
|
||||
pub const fn is_absolute(&self) -> bool {
|
||||
match self {
|
||||
SetTrajectory::AirTime(_)
|
||||
| SetTrajectory::Height(_)
|
||||
| SetTrajectory::DotVelocity {
|
||||
direction: _,
|
||||
dot: _,
|
||||
} => false,
|
||||
SetTrajectory::TargetPointTime {
|
||||
target_point: _,
|
||||
time: _,
|
||||
}
|
||||
| SetTrajectory::TargetPointSpeed {
|
||||
target_point: _,
|
||||
speed: _,
|
||||
trajectory_choice: _,
|
||||
}
|
||||
| SetTrajectory::Velocity(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
// enum TrapCondition{
|
||||
// FasterThan(Planar64),
|
||||
// SlowerThan(Planar64),
|
||||
// InRange(Planar64,Planar64),
|
||||
// OutsideRange(Planar64,Planar64),
|
||||
// }
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Wormhole {
|
||||
//destination does not need to be another wormhole
|
||||
//this defines a one way portal to a destination model transform
|
||||
//two of these can create a two way wormhole
|
||||
pub destination_model: model::ModelId,
|
||||
//(position,angles)*=origin.transform.inverse()*destination.transform
|
||||
}
|
||||
//attributes listed in order of handling
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct GeneralAttributes {
|
||||
pub booster: Option<Booster>,
|
||||
pub trajectory: Option<SetTrajectory>,
|
||||
pub wormhole: Option<Wormhole>,
|
||||
pub accelerator: Option<Accelerator>,
|
||||
}
|
||||
impl GeneralAttributes {
|
||||
pub const fn any(&self) -> bool {
|
||||
self.booster.is_some()
|
||||
|| self.trajectory.is_some()
|
||||
|| self.wormhole.is_some()
|
||||
|| self.accelerator.is_some()
|
||||
}
|
||||
pub fn is_wrcp(&self) -> bool {
|
||||
self.trajectory.as_ref().map_or(false, |t| t.is_absolute())
|
||||
/*
|
||||
&&match &self.teleport_behaviour{
|
||||
Some(TeleportBehaviour::StageElement(
|
||||
StageElement{
|
||||
mode_id,
|
||||
stage_id:_,
|
||||
force:true,
|
||||
behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport
|
||||
}
|
||||
))=>current_mode_id==*mode_id,
|
||||
_=>false,
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct ContactingAttributes {
|
||||
//friction?
|
||||
pub contact_behaviour: Option<ContactingBehaviour>,
|
||||
}
|
||||
impl ContactingAttributes {
|
||||
pub const fn any(&self) -> bool {
|
||||
self.contact_behaviour.is_some()
|
||||
}
|
||||
}
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct IntersectingAttributes {
|
||||
pub water: Option<IntersectingWater>,
|
||||
}
|
||||
impl IntersectingAttributes {
|
||||
pub const fn any(&self) -> bool {
|
||||
self.water.is_some()
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, id::Id, Hash, Eq, PartialEq)]
|
||||
pub struct CollisionAttributesId(u32);
|
||||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct ContactAttributes {
|
||||
pub contacting: ContactingAttributes,
|
||||
pub general: GeneralAttributes,
|
||||
}
|
||||
#[derive(Clone, Default, Hash, Eq, PartialEq)]
|
||||
pub struct IntersectAttributes {
|
||||
pub intersecting: IntersectingAttributes,
|
||||
pub general: GeneralAttributes,
|
||||
}
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub enum CollisionAttributes {
|
||||
Decoration, //visual only
|
||||
Contact(ContactAttributes), //track whether you are contacting the object
|
||||
Intersect(IntersectAttributes), //track whether you are intersecting the object
|
||||
}
|
||||
impl CollisionAttributes {
|
||||
pub fn contact_default() -> Self {
|
||||
Self::Contact(ContactAttributes::default())
|
||||
}
|
||||
}
|
358
lib/common/src/gameplay_modes.rs
Normal file
358
lib/common/src/gameplay_modes.rs
Normal file
@ -0,0 +1,358 @@
|
||||
use crate::gameplay_style;
|
||||
use crate::model::ModelId;
|
||||
use crate::updatable::Updatable;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StageElement {
|
||||
stage_id: StageId, //which stage spawn to send to
|
||||
force: bool, //allow setting to lower spawn id i.e. 7->3
|
||||
behaviour: StageElementBehaviour,
|
||||
jump_limit: Option<u8>,
|
||||
}
|
||||
impl StageElement {
|
||||
#[inline]
|
||||
pub const fn new(
|
||||
stage_id: StageId,
|
||||
force: bool,
|
||||
behaviour: StageElementBehaviour,
|
||||
jump_limit: Option<u8>,
|
||||
) -> Self {
|
||||
Self {
|
||||
stage_id,
|
||||
force,
|
||||
behaviour,
|
||||
jump_limit,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn stage_id(&self) -> StageId {
|
||||
self.stage_id
|
||||
}
|
||||
#[inline]
|
||||
pub const fn force(&self) -> bool {
|
||||
self.force
|
||||
}
|
||||
#[inline]
|
||||
pub const fn behaviour(&self) -> StageElementBehaviour {
|
||||
self.behaviour
|
||||
}
|
||||
#[inline]
|
||||
pub const fn jump_limit(&self) -> Option<u8> {
|
||||
self.jump_limit
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
pub enum StageElementBehaviour {
|
||||
SpawnAt, //must be standing on top to get effect. except cancollide false
|
||||
Trigger,
|
||||
Teleport,
|
||||
Platform,
|
||||
//Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet.
|
||||
//Note that all stage elements act like this, this is just the isolated behaviour.
|
||||
Check,
|
||||
Checkpoint, //this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both.
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)]
|
||||
pub struct CheckpointId(u32);
|
||||
impl CheckpointId {
|
||||
pub const FIRST: Self = Self(0);
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct StageId(u32);
|
||||
impl StageId {
|
||||
pub const FIRST: Self = Self(0);
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Stage {
|
||||
spawn: ModelId,
|
||||
//open world support lol
|
||||
ordered_checkpoints_count: u32,
|
||||
unordered_checkpoints_count: u32,
|
||||
//currently loaded checkpoint models
|
||||
ordered_checkpoints: HashMap<CheckpointId, ModelId>,
|
||||
unordered_checkpoints: HashSet<ModelId>,
|
||||
}
|
||||
impl Stage {
|
||||
pub fn new(
|
||||
spawn: ModelId,
|
||||
ordered_checkpoints_count: u32,
|
||||
unordered_checkpoints_count: u32,
|
||||
ordered_checkpoints: HashMap<CheckpointId, ModelId>,
|
||||
unordered_checkpoints: HashSet<ModelId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
spawn,
|
||||
ordered_checkpoints_count,
|
||||
unordered_checkpoints_count,
|
||||
ordered_checkpoints,
|
||||
unordered_checkpoints,
|
||||
}
|
||||
}
|
||||
pub fn empty(spawn: ModelId) -> Self {
|
||||
Self {
|
||||
spawn,
|
||||
ordered_checkpoints_count: 0,
|
||||
unordered_checkpoints_count: 0,
|
||||
ordered_checkpoints: HashMap::new(),
|
||||
unordered_checkpoints: HashSet::new(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn spawn(&self) -> ModelId {
|
||||
self.spawn
|
||||
}
|
||||
#[inline]
|
||||
pub const fn ordered_checkpoints_count(&self) -> u32 {
|
||||
self.ordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub const fn unordered_checkpoints_count(&self) -> u32 {
|
||||
self.unordered_checkpoints_count
|
||||
}
|
||||
pub fn into_inner(self) -> (HashMap<CheckpointId, ModelId>, HashSet<ModelId>) {
|
||||
(self.ordered_checkpoints, self.unordered_checkpoints)
|
||||
}
|
||||
/// Returns true if the stage has no checkpoints.
|
||||
#[inline]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.is_complete(0, 0)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn is_complete(
|
||||
&self,
|
||||
ordered_checkpoints_count: u32,
|
||||
unordered_checkpoints_count: u32,
|
||||
) -> bool {
|
||||
self.ordered_checkpoints_count == ordered_checkpoints_count
|
||||
&& self.unordered_checkpoints_count == unordered_checkpoints_count
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_next_ordered_checkpoint(
|
||||
&self,
|
||||
next_ordered_checkpoint_id: CheckpointId,
|
||||
model_id: ModelId,
|
||||
) -> bool {
|
||||
self.ordered_checkpoints
|
||||
.get(&next_ordered_checkpoint_id)
|
||||
.is_some_and(|&next_checkpoint| model_id == next_checkpoint)
|
||||
}
|
||||
#[inline]
|
||||
pub fn is_unordered_checkpoint(&self, model_id: ModelId) -> bool {
|
||||
self.unordered_checkpoints.contains(&model_id)
|
||||
}
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct StageUpdate {
|
||||
//other behaviour models of this stage can have
|
||||
ordered_checkpoints: HashMap<CheckpointId, ModelId>,
|
||||
unordered_checkpoints: HashSet<ModelId>,
|
||||
}
|
||||
impl Updatable<StageUpdate> for Stage {
|
||||
fn update(&mut self, update: StageUpdate) {
|
||||
self.ordered_checkpoints.extend(update.ordered_checkpoints);
|
||||
self.unordered_checkpoints
|
||||
.extend(update.unordered_checkpoints);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
pub enum Zone {
|
||||
Start,
|
||||
Finish,
|
||||
Anticheat,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct ModeId(u32);
|
||||
impl ModeId {
|
||||
pub const MAIN: Self = Self(0);
|
||||
pub const BONUS: Self = Self(1);
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct Mode {
|
||||
style: gameplay_style::StyleModifiers,
|
||||
start: ModelId, //when you press reset you go here
|
||||
zones: HashMap<ModelId, Zone>,
|
||||
stages: Vec<Stage>, //when you load the map you go to stages[0].spawn
|
||||
//mutually exlusive stage element behaviour
|
||||
elements: HashMap<ModelId, StageElement>,
|
||||
}
|
||||
impl Mode {
|
||||
pub fn new(
|
||||
style: gameplay_style::StyleModifiers,
|
||||
start: ModelId,
|
||||
zones: HashMap<ModelId, Zone>,
|
||||
stages: Vec<Stage>,
|
||||
elements: HashMap<ModelId, StageElement>,
|
||||
) -> Self {
|
||||
Self {
|
||||
style,
|
||||
start,
|
||||
zones,
|
||||
stages,
|
||||
elements,
|
||||
}
|
||||
}
|
||||
pub fn empty(style: gameplay_style::StyleModifiers, start: ModelId) -> Self {
|
||||
Self {
|
||||
style,
|
||||
start,
|
||||
zones: HashMap::new(),
|
||||
stages: Vec::new(),
|
||||
elements: HashMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_inner(
|
||||
self,
|
||||
) -> (
|
||||
gameplay_style::StyleModifiers,
|
||||
ModelId,
|
||||
HashMap<ModelId, Zone>,
|
||||
Vec<Stage>,
|
||||
HashMap<ModelId, StageElement>,
|
||||
) {
|
||||
(
|
||||
self.style,
|
||||
self.start,
|
||||
self.zones,
|
||||
self.stages,
|
||||
self.elements,
|
||||
)
|
||||
}
|
||||
pub const fn get_start(&self) -> ModelId {
|
||||
self.start
|
||||
}
|
||||
pub const fn get_style(&self) -> &gameplay_style::StyleModifiers {
|
||||
&self.style
|
||||
}
|
||||
pub fn push_stage(&mut self, stage: Stage) {
|
||||
self.stages.push(stage)
|
||||
}
|
||||
pub fn get_stage_mut(&mut self, stage: StageId) -> Option<&mut Stage> {
|
||||
self.stages.get_mut(stage.0 as usize)
|
||||
}
|
||||
pub fn get_spawn_model_id(&self, stage: StageId) -> Option<ModelId> {
|
||||
self.stages.get(stage.0 as usize).map(|s| s.spawn)
|
||||
}
|
||||
pub fn get_zone(&self, model_id: ModelId) -> Option<&Zone> {
|
||||
self.zones.get(&model_id)
|
||||
}
|
||||
pub fn get_stage(&self, stage_id: StageId) -> Option<&Stage> {
|
||||
self.stages.get(stage_id.0 as usize)
|
||||
}
|
||||
pub fn get_element(&self, model_id: ModelId) -> Option<&StageElement> {
|
||||
self.elements.get(&model_id)
|
||||
}
|
||||
//TODO: put this in the SNF
|
||||
pub fn denormalize_data(&mut self) {
|
||||
//expand and index normalized data
|
||||
self.zones.insert(self.start, Zone::Start);
|
||||
for (stage_id, stage) in self.stages.iter().enumerate() {
|
||||
self.elements.insert(
|
||||
stage.spawn,
|
||||
StageElement {
|
||||
stage_id: StageId(stage_id as u32),
|
||||
force: false,
|
||||
behaviour: StageElementBehaviour::SpawnAt,
|
||||
jump_limit: None,
|
||||
},
|
||||
);
|
||||
for (_, &model) in &stage.ordered_checkpoints {
|
||||
self.elements.insert(
|
||||
model,
|
||||
StageElement {
|
||||
stage_id: StageId(stage_id as u32),
|
||||
force: false,
|
||||
behaviour: StageElementBehaviour::Checkpoint,
|
||||
jump_limit: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
for &model in &stage.unordered_checkpoints {
|
||||
self.elements.insert(
|
||||
model,
|
||||
StageElement {
|
||||
stage_id: StageId(stage_id as u32),
|
||||
force: false,
|
||||
behaviour: StageElementBehaviour::Checkpoint,
|
||||
jump_limit: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//this would be nice as a macro
|
||||
#[derive(Default)]
|
||||
pub struct ModeUpdate {
|
||||
zones: HashMap<ModelId, Zone>,
|
||||
stages: HashMap<StageId, StageUpdate>,
|
||||
//mutually exlusive stage element behaviour
|
||||
elements: HashMap<ModelId, StageElement>,
|
||||
}
|
||||
impl Updatable<ModeUpdate> for Mode {
|
||||
fn update(&mut self, update: ModeUpdate) {
|
||||
self.zones.extend(update.zones);
|
||||
for (stage, stage_update) in update.stages {
|
||||
if let Some(stage) = self.stages.get_mut(stage.0 as usize) {
|
||||
stage.update(stage_update);
|
||||
}
|
||||
}
|
||||
self.elements.extend(update.elements);
|
||||
}
|
||||
}
|
||||
impl ModeUpdate {
|
||||
pub fn zone(model_id: ModelId, zone: Zone) -> Self {
|
||||
let mut mu = Self::default();
|
||||
mu.zones.insert(model_id, zone);
|
||||
mu
|
||||
}
|
||||
pub fn stage(stage_id: StageId, stage_update: StageUpdate) -> Self {
|
||||
let mut mu = Self::default();
|
||||
mu.stages.insert(stage_id, stage_update);
|
||||
mu
|
||||
}
|
||||
pub fn element(model_id: ModelId, element: StageElement) -> Self {
|
||||
let mut mu = Self::default();
|
||||
mu.elements.insert(model_id, element);
|
||||
mu
|
||||
}
|
||||
pub fn map_stage_element_ids<F: Fn(StageId) -> StageId>(&mut self, f: F) {
|
||||
for (_, stage_element) in self.elements.iter_mut() {
|
||||
stage_element.stage_id = f(stage_element.stage_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct Modes {
|
||||
pub modes: Vec<Mode>,
|
||||
}
|
||||
impl Modes {
|
||||
pub const fn new(modes: Vec<Mode>) -> Self {
|
||||
Self { modes }
|
||||
}
|
||||
pub fn into_inner(self) -> Vec<Mode> {
|
||||
self.modes
|
||||
}
|
||||
pub fn push_mode(&mut self, mode: Mode) {
|
||||
self.modes.push(mode)
|
||||
}
|
||||
pub fn get_mode(&self, mode: ModeId) -> Option<&Mode> {
|
||||
self.modes.get(mode.0 as usize)
|
||||
}
|
||||
}
|
||||
pub struct ModesUpdate {
|
||||
modes: HashMap<ModeId, ModeUpdate>,
|
||||
}
|
||||
impl Updatable<ModesUpdate> for Modes {
|
||||
fn update(&mut self, update: ModesUpdate) {
|
||||
for (mode, mode_update) in update.modes {
|
||||
if let Some(mode) = self.modes.get_mut(mode.0 as usize) {
|
||||
mode.update(mode_update);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
644
lib/common/src/gameplay_style.rs
Normal file
644
lib/common/src/gameplay_style.rs
Normal file
@ -0,0 +1,644 @@
|
||||
const VALVE_SCALE: Planar64 = Planar64::raw(1 << 28); // 1/16
|
||||
|
||||
use crate::controls_bitflag::Controls;
|
||||
use crate::integer::{int, vec3::int as int3, AbsoluteTime, Planar64, Planar64Vec3, Ratio64};
|
||||
use crate::physics::Time as PhysicsTime;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StyleModifiers {
|
||||
//controls which are allowed to pass into gameplay (usually all)
|
||||
pub controls_mask: Controls,
|
||||
//controls which are masked from control state (e.g. !jump in scroll style)
|
||||
pub controls_mask_state: Controls,
|
||||
//strafing
|
||||
pub strafe: Option<StrafeSettings>,
|
||||
//player gets a controllable rocket force
|
||||
pub rocket: Option<PropulsionSettings>,
|
||||
//flying
|
||||
//pub move_type:MoveType::Fly(FlySettings)
|
||||
//MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity)
|
||||
//jumping is allowed
|
||||
pub jump: Option<JumpSettings>,
|
||||
//standing & walking is allowed
|
||||
pub walk: Option<WalkSettings>,
|
||||
//laddering is allowed
|
||||
pub ladder: Option<LadderSettings>,
|
||||
//water propulsion
|
||||
pub swim: Option<PropulsionSettings>,
|
||||
//maximum slope before sloped surfaces become frictionless
|
||||
pub gravity: Planar64Vec3,
|
||||
//hitbox
|
||||
pub hitbox: Hitbox,
|
||||
//camera location relative to the center (0,0,0) of the hitbox
|
||||
pub camera_offset: Planar64Vec3,
|
||||
//unused
|
||||
pub mass: Planar64,
|
||||
}
|
||||
impl std::default::Default for StyleModifiers {
|
||||
fn default() -> Self {
|
||||
Self::roblox_bhop()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum JumpCalculation {
|
||||
Max, //Roblox: jumped_speed=max(velocity.boost(),velocity.jump())
|
||||
BoostThenJump, //jumped_speed=velocity.boost().jump()
|
||||
JumpThenBoost, //jumped_speed=velocity.jump().boost()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum JumpImpulse {
|
||||
Time(AbsoluteTime), //jump time is invariant across mass and gravity changes
|
||||
Height(Planar64), //jump height is invariant across mass and gravity changes
|
||||
Linear(Planar64), //jump velocity is invariant across mass and gravity changes
|
||||
Energy(Planar64), // :)
|
||||
}
|
||||
//Jumping acts on dot(walks_state.normal,body.velocity)
|
||||
//Energy means it adds energy
|
||||
//Linear means it linearly adds on
|
||||
impl JumpImpulse {
|
||||
pub fn jump(
|
||||
&self,
|
||||
velocity: Planar64Vec3,
|
||||
jump_dir: Planar64Vec3,
|
||||
gravity: &Planar64Vec3,
|
||||
mass: Planar64,
|
||||
) -> Planar64Vec3 {
|
||||
match self {
|
||||
&JumpImpulse::Time(time) => velocity - (*gravity * time).map(|t| t.divide().fix_1()),
|
||||
&JumpImpulse::Height(height) => {
|
||||
//height==-v.y*v.y/(2*g.y);
|
||||
//use energy to determine max height
|
||||
let gg = gravity.length_squared();
|
||||
let g = gg.sqrt().fix_1();
|
||||
let v_g = gravity.dot(velocity);
|
||||
//do it backwards
|
||||
let radicand = v_g * v_g + (g * height * 2).fix_4();
|
||||
velocity
|
||||
- (*gravity * (radicand.sqrt().fix_2() + v_g) / gg)
|
||||
.divide()
|
||||
.fix_1()
|
||||
}
|
||||
&JumpImpulse::Linear(jump_speed) => {
|
||||
velocity + (jump_dir * jump_speed / jump_dir.length()).divide().fix_1()
|
||||
}
|
||||
&JumpImpulse::Energy(energy) => {
|
||||
//calculate energy
|
||||
//let e=gravity.dot(velocity);
|
||||
//add
|
||||
//you get the idea
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: remove this and implement JumpCalculation properly
|
||||
pub fn get_jump_deltav(&self, gravity: &Planar64Vec3, mass: Planar64) -> Planar64 {
|
||||
//gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction
|
||||
match self {
|
||||
&JumpImpulse::Time(time) => (gravity.length().fix_1() * time / 2).divide().fix_1(),
|
||||
&JumpImpulse::Height(height) => (gravity.length() * height * 2).sqrt().fix_1(),
|
||||
&JumpImpulse::Linear(deltav) => deltav,
|
||||
&JumpImpulse::Energy(energy) => (energy.sqrt() * 2 / mass.sqrt()).divide().fix_1(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct JumpSettings {
|
||||
//information used to calculate jump power
|
||||
pub impulse: JumpImpulse,
|
||||
//information used to calculate jump behaviour
|
||||
pub calculation: JumpCalculation,
|
||||
//limit the minimum jump power when combined with downwards momentum
|
||||
//This is true in both roblox and source
|
||||
pub limit_minimum: bool,
|
||||
}
|
||||
impl JumpSettings {
|
||||
pub fn jumped_velocity(
|
||||
&self,
|
||||
style: &StyleModifiers,
|
||||
jump_dir: Planar64Vec3,
|
||||
rel_velocity: Planar64Vec3,
|
||||
booster: Option<&crate::gameplay_attributes::Booster>,
|
||||
) -> Planar64Vec3 {
|
||||
let jump_speed = self.impulse.get_jump_deltav(&style.gravity, style.mass);
|
||||
match (self.limit_minimum, &self.calculation) {
|
||||
(true, JumpCalculation::Max) => {
|
||||
//the roblox calculation
|
||||
let boost_vel = match booster {
|
||||
Some(booster) => booster.boost(rel_velocity),
|
||||
None => rel_velocity,
|
||||
};
|
||||
let j = boost_vel.dot(jump_dir);
|
||||
let js = jump_speed.fix_2();
|
||||
if j < js {
|
||||
//weak booster: just do a regular jump
|
||||
boost_vel + jump_dir.with_length(js - j).divide().fix_1()
|
||||
} else {
|
||||
//activate booster normally, jump does nothing
|
||||
boost_vel
|
||||
}
|
||||
}
|
||||
(true, _) => {
|
||||
//the source calculation (?)
|
||||
let boost_vel = match booster {
|
||||
Some(booster) => booster.boost(rel_velocity),
|
||||
None => rel_velocity,
|
||||
};
|
||||
let j = boost_vel.dot(jump_dir);
|
||||
let js = jump_speed.fix_2();
|
||||
if j < js {
|
||||
//speed in direction of jump cannot be lower than amount
|
||||
boost_vel + jump_dir.with_length(js - j).divide().fix_1()
|
||||
} else {
|
||||
//boost and jump add together
|
||||
boost_vel + jump_dir.with_length(js).divide().fix_1()
|
||||
}
|
||||
}
|
||||
(false, JumpCalculation::Max) => {
|
||||
//??? calculation
|
||||
//max(boost_vel,jump_vel)
|
||||
let boost_vel = match booster {
|
||||
Some(booster) => booster.boost(rel_velocity),
|
||||
None => rel_velocity,
|
||||
};
|
||||
let boost_dot = boost_vel.dot(jump_dir);
|
||||
let js = jump_speed.fix_2();
|
||||
if boost_dot < js {
|
||||
//weak boost is extended to jump speed
|
||||
boost_vel + jump_dir.with_length(js - boost_dot).divide().fix_1()
|
||||
} else {
|
||||
//activate booster normally, jump does nothing
|
||||
boost_vel
|
||||
}
|
||||
}
|
||||
//the strafe client calculation
|
||||
(false, _) => {
|
||||
let boost_vel = match booster {
|
||||
Some(booster) => booster.boost(rel_velocity),
|
||||
None => rel_velocity,
|
||||
};
|
||||
boost_vel + jump_dir.with_length(jump_speed).divide().fix_1()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ControlsActivation {
|
||||
//allowed keys
|
||||
pub controls_mask: Controls,
|
||||
//allow strafing only if any of the masked controls are held, eg W|S for shsw
|
||||
pub controls_intersects: Controls,
|
||||
//allow strafing only if all of the masked controls are held, eg W for hsw, w-only
|
||||
pub controls_contains: Controls,
|
||||
//Function(Box<dyn Fn(u32)->bool>),
|
||||
}
|
||||
impl ControlsActivation {
|
||||
pub const fn mask(&self, controls: Controls) -> Controls {
|
||||
controls.intersection(self.controls_mask)
|
||||
}
|
||||
pub const fn activates(&self, controls: Controls) -> bool {
|
||||
(self.controls_intersects.is_empty() || controls.intersects(self.controls_intersects))
|
||||
&& controls.contains(self.controls_contains)
|
||||
}
|
||||
pub const fn full_3d() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::WASDQE,
|
||||
controls_intersects: Controls::WASDQE,
|
||||
controls_contains: Controls::empty(),
|
||||
}
|
||||
}
|
||||
//classical styles
|
||||
//Normal
|
||||
pub const fn full_2d() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::WASD,
|
||||
controls_intersects: Controls::WASD,
|
||||
controls_contains: Controls::empty(),
|
||||
}
|
||||
}
|
||||
//Sideways
|
||||
pub const fn sideways() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::MoveForward.union(Controls::MoveBackward),
|
||||
controls_intersects: Controls::MoveForward.union(Controls::MoveBackward),
|
||||
controls_contains: Controls::empty(),
|
||||
}
|
||||
}
|
||||
//Half-Sideways
|
||||
pub const fn half_sideways() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::MoveForward
|
||||
.union(Controls::MoveLeft)
|
||||
.union(Controls::MoveRight),
|
||||
controls_intersects: Controls::MoveLeft.union(Controls::MoveRight),
|
||||
controls_contains: Controls::MoveForward,
|
||||
}
|
||||
}
|
||||
//Surf Half-Sideways
|
||||
pub const fn surf_half_sideways() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::MoveForward
|
||||
.union(Controls::MoveBackward)
|
||||
.union(Controls::MoveLeft)
|
||||
.union(Controls::MoveRight),
|
||||
controls_intersects: Controls::MoveForward.union(Controls::MoveBackward),
|
||||
controls_contains: Controls::empty(),
|
||||
}
|
||||
}
|
||||
//W-Only
|
||||
pub const fn w_only() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::MoveForward,
|
||||
controls_intersects: Controls::empty(),
|
||||
controls_contains: Controls::MoveForward,
|
||||
}
|
||||
}
|
||||
//A-Only
|
||||
pub const fn a_only() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::MoveLeft,
|
||||
controls_intersects: Controls::empty(),
|
||||
controls_contains: Controls::MoveLeft,
|
||||
}
|
||||
}
|
||||
//Backwards
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StrafeSettings {
|
||||
pub enable: ControlsActivation,
|
||||
pub mv: Planar64,
|
||||
pub air_accel_limit: Option<Planar64>,
|
||||
pub tick_rate: Ratio64,
|
||||
}
|
||||
impl StrafeSettings {
|
||||
pub fn tick_velocity(
|
||||
&self,
|
||||
velocity: Planar64Vec3,
|
||||
control_dir: Planar64Vec3,
|
||||
) -> Option<Planar64Vec3> {
|
||||
let d = velocity.dot(control_dir);
|
||||
let mv = self.mv.fix_2();
|
||||
match d < mv {
|
||||
true => Some(
|
||||
velocity
|
||||
+ (control_dir
|
||||
* self
|
||||
.air_accel_limit
|
||||
.map_or(mv - d, |limit| limit.fix_2().min(mv - d)))
|
||||
.fix_1(),
|
||||
),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
pub fn next_tick(&self, time: PhysicsTime) -> PhysicsTime {
|
||||
PhysicsTime::from_nanos(
|
||||
self.tick_rate
|
||||
.rhs_div_int(self.tick_rate.mul_int(time.nanos()) + 1),
|
||||
)
|
||||
}
|
||||
pub const fn activates(&self, controls: Controls) -> bool {
|
||||
self.enable.activates(controls)
|
||||
}
|
||||
pub const fn mask(&self, controls: Controls) -> Controls {
|
||||
self.enable.mask(controls)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PropulsionSettings {
|
||||
pub magnitude: Planar64,
|
||||
}
|
||||
impl PropulsionSettings {
|
||||
pub fn acceleration(&self, control_dir: Planar64Vec3) -> Planar64Vec3 {
|
||||
(control_dir * self.magnitude).fix_1()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AccelerateSettings {
|
||||
pub accel: Planar64,
|
||||
pub topspeed: Planar64,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WalkSettings {
|
||||
pub accelerate: AccelerateSettings,
|
||||
pub static_friction: Planar64,
|
||||
pub kinetic_friction: Planar64,
|
||||
//if a surf slope angle does not exist, then everything is slippery and walking is impossible
|
||||
pub surf_dot: Planar64, //surf_dot<n.dot(up)/n.length()
|
||||
}
|
||||
impl WalkSettings {
|
||||
pub fn accel(&self, target_diff: Planar64Vec3, gravity: Planar64Vec3) -> Planar64 {
|
||||
//TODO: fallible walk accel
|
||||
let diff_len = target_diff.length().fix_1();
|
||||
let friction = if diff_len < self.accelerate.topspeed {
|
||||
self.static_friction
|
||||
} else {
|
||||
self.kinetic_friction
|
||||
};
|
||||
self.accelerate.accel.min((-gravity.y * friction).fix_1())
|
||||
}
|
||||
pub fn get_walk_target_velocity(
|
||||
&self,
|
||||
control_dir: Planar64Vec3,
|
||||
normal: Planar64Vec3,
|
||||
) -> Planar64Vec3 {
|
||||
if control_dir == crate::integer::vec3::ZERO {
|
||||
return control_dir;
|
||||
}
|
||||
let nn = normal.length_squared();
|
||||
let mm = control_dir.length_squared();
|
||||
let nnmm = nn * mm;
|
||||
let d = normal.dot(control_dir);
|
||||
let dd = d * d;
|
||||
if dd < nnmm {
|
||||
let cr = normal.cross(control_dir);
|
||||
if cr == crate::integer::vec3::ZERO_2 {
|
||||
crate::integer::vec3::ZERO
|
||||
} else {
|
||||
(cr.cross(normal) * self.accelerate.topspeed / ((nn * (nnmm - dd)).sqrt()))
|
||||
.divide()
|
||||
.fix_1()
|
||||
}
|
||||
} else {
|
||||
crate::integer::vec3::ZERO
|
||||
}
|
||||
}
|
||||
pub fn is_slope_walkable(&self, normal: Planar64Vec3, up: Planar64Vec3) -> bool {
|
||||
//normal is not guaranteed to be unit length
|
||||
let ny = normal.dot(up);
|
||||
let h = normal.length().fix_1();
|
||||
//remember this is a normal vector
|
||||
ny.is_positive() && h * self.surf_dot < ny
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LadderSettings {
|
||||
pub accelerate: AccelerateSettings,
|
||||
//how close to pushing directly into/out of the ladder normal
|
||||
//does your input need to be to redirect straight up/down the ladder
|
||||
pub dot: Planar64,
|
||||
}
|
||||
impl LadderSettings {
|
||||
pub const fn accel(&self, target_diff: Planar64Vec3, gravity: Planar64Vec3) -> Planar64 {
|
||||
//TODO: fallible ladder accel
|
||||
self.accelerate.accel
|
||||
}
|
||||
pub fn get_ladder_target_velocity(
|
||||
&self,
|
||||
mut control_dir: Planar64Vec3,
|
||||
normal: Planar64Vec3,
|
||||
) -> Planar64Vec3 {
|
||||
if control_dir == crate::integer::vec3::ZERO {
|
||||
return control_dir;
|
||||
}
|
||||
let nn = normal.length_squared();
|
||||
let mm = control_dir.length_squared();
|
||||
let nnmm = nn * mm;
|
||||
let d = normal.dot(control_dir);
|
||||
let mut dd = d * d;
|
||||
if (self.dot * self.dot * nnmm).fix_4() < dd {
|
||||
if d.is_negative() {
|
||||
control_dir = Planar64Vec3::new([Planar64::ZERO, mm.fix_1(), Planar64::ZERO]);
|
||||
} else {
|
||||
control_dir = Planar64Vec3::new([Planar64::ZERO, -mm.fix_1(), Planar64::ZERO]);
|
||||
}
|
||||
dd = (normal.y * normal.y).fix_4();
|
||||
}
|
||||
//n=d if you are standing on top of a ladder and press E.
|
||||
//two fixes:
|
||||
//- ladder movement is not allowed on walkable surfaces
|
||||
//- fix the underlying issue
|
||||
if dd < nnmm {
|
||||
let cr = normal.cross(control_dir);
|
||||
if cr == crate::integer::vec3::ZERO_2 {
|
||||
crate::integer::vec3::ZERO
|
||||
} else {
|
||||
(cr.cross(normal) * self.accelerate.topspeed / ((nn * (nnmm - dd)).sqrt()))
|
||||
.divide()
|
||||
.fix_1()
|
||||
}
|
||||
} else {
|
||||
crate::integer::vec3::ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum HitboxMesh {
|
||||
Box, //source
|
||||
Cylinder, //roblox
|
||||
//Sphere,//roblox old physics
|
||||
//Point,
|
||||
//Line,
|
||||
//DualCone,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Hitbox {
|
||||
pub halfsize: Planar64Vec3,
|
||||
pub mesh: HitboxMesh,
|
||||
}
|
||||
impl Hitbox {
|
||||
pub fn roblox() -> Self {
|
||||
Self {
|
||||
halfsize: int3(2, 5, 2) >> 1,
|
||||
mesh: HitboxMesh::Cylinder,
|
||||
}
|
||||
}
|
||||
pub fn source() -> Self {
|
||||
Self {
|
||||
halfsize: ((int3(33, 73, 33) >> 1) * VALVE_SCALE).fix_1(),
|
||||
mesh: HitboxMesh::Box,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleModifiers {
|
||||
pub const RIGHT_DIR: Planar64Vec3 = crate::integer::vec3::X;
|
||||
pub const UP_DIR: Planar64Vec3 = crate::integer::vec3::Y;
|
||||
pub const FORWARD_DIR: Planar64Vec3 = crate::integer::vec3::NEG_Z;
|
||||
|
||||
pub fn neo() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::all(),
|
||||
controls_mask_state: Controls::all(),
|
||||
strafe: Some(StrafeSettings {
|
||||
enable: ControlsActivation::full_2d(),
|
||||
air_accel_limit: None,
|
||||
mv: int(3),
|
||||
tick_rate: Ratio64::new(64, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump: Some(JumpSettings {
|
||||
impulse: JumpImpulse::Energy(int(512)),
|
||||
calculation: JumpCalculation::JumpThenBoost,
|
||||
limit_minimum: false,
|
||||
}),
|
||||
gravity: int3(0, -80, 0),
|
||||
mass: int(1),
|
||||
rocket: None,
|
||||
walk: Some(WalkSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(16),
|
||||
accel: int(80),
|
||||
},
|
||||
static_friction: int(2),
|
||||
kinetic_friction: int(3), //unrealistic: kinetic friction is typically lower than static
|
||||
surf_dot: int(3) / 4,
|
||||
}),
|
||||
ladder: Some(LadderSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(16),
|
||||
accel: int(160),
|
||||
},
|
||||
dot: (int(1) / 2).sqrt(),
|
||||
}),
|
||||
swim: Some(PropulsionSettings { magnitude: int(12) }),
|
||||
hitbox: Hitbox::roblox(),
|
||||
camera_offset: int3(0, 2, 0), //4.5-2.5=2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn roblox_bhop() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::all(),
|
||||
controls_mask_state: Controls::all(),
|
||||
strafe: Some(StrafeSettings {
|
||||
enable: ControlsActivation::full_2d(),
|
||||
air_accel_limit: None,
|
||||
mv: int(27) / 10,
|
||||
tick_rate: Ratio64::new(100, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump: Some(JumpSettings {
|
||||
impulse: JumpImpulse::Time(AbsoluteTime::from_micros(715_588)),
|
||||
calculation: JumpCalculation::Max,
|
||||
limit_minimum: true,
|
||||
}),
|
||||
gravity: int3(0, -100, 0),
|
||||
mass: int(1),
|
||||
rocket: None,
|
||||
walk: Some(WalkSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(18),
|
||||
accel: int(90),
|
||||
},
|
||||
static_friction: int(2),
|
||||
kinetic_friction: int(3), //unrealistic: kinetic friction is typically lower than static
|
||||
surf_dot: int(3) / 4, // normal.y=0.75
|
||||
}),
|
||||
ladder: Some(LadderSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(18),
|
||||
accel: int(180),
|
||||
},
|
||||
dot: (int(1) / 2).sqrt(),
|
||||
}),
|
||||
swim: Some(PropulsionSettings { magnitude: int(12) }),
|
||||
hitbox: Hitbox::roblox(),
|
||||
camera_offset: int3(0, 2, 0), //4.5-2.5=2
|
||||
}
|
||||
}
|
||||
pub fn roblox_surf() -> Self {
|
||||
Self {
|
||||
gravity: int3(0, -50, 0),
|
||||
..Self::roblox_bhop()
|
||||
}
|
||||
}
|
||||
pub fn roblox_rocket() -> Self {
|
||||
Self {
|
||||
strafe: None,
|
||||
rocket: Some(PropulsionSettings {
|
||||
magnitude: int(200),
|
||||
}),
|
||||
..Self::roblox_bhop()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn source_bhop() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::all() - Controls::MoveUp - Controls::MoveDown,
|
||||
controls_mask_state: Controls::all(),
|
||||
strafe: Some(StrafeSettings {
|
||||
enable: ControlsActivation::full_2d(),
|
||||
air_accel_limit: Some(Planar64::raw(150 << 28) * 100),
|
||||
mv: (Planar64::raw(30) * VALVE_SCALE).fix_1(),
|
||||
tick_rate: Ratio64::new(100, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump: Some(JumpSettings {
|
||||
impulse: JumpImpulse::Height((int(52) * VALVE_SCALE).fix_1()),
|
||||
calculation: JumpCalculation::JumpThenBoost,
|
||||
limit_minimum: true,
|
||||
}),
|
||||
gravity: (int3(0, -800, 0) * VALVE_SCALE).fix_1(),
|
||||
mass: int(1),
|
||||
rocket: None,
|
||||
walk: Some(WalkSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(18), //?
|
||||
accel: int(90), //?
|
||||
},
|
||||
static_friction: int(2), //?
|
||||
kinetic_friction: int(3), //?
|
||||
surf_dot: int(3) / 4, // normal.y=0.75
|
||||
}),
|
||||
ladder: Some(LadderSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(18), //?
|
||||
accel: int(180), //?
|
||||
},
|
||||
dot: (int(1) / 2).sqrt(), //?
|
||||
}),
|
||||
swim: Some(PropulsionSettings {
|
||||
magnitude: int(12), //?
|
||||
}),
|
||||
hitbox: Hitbox::source(),
|
||||
camera_offset: ((int3(0, 64, 0) - (int3(0, 73, 0) >> 1)) * VALVE_SCALE).fix_1(),
|
||||
}
|
||||
}
|
||||
pub fn source_surf() -> Self {
|
||||
Self {
|
||||
controls_mask: Controls::all() - Controls::MoveUp - Controls::MoveDown,
|
||||
controls_mask_state: Controls::all(),
|
||||
strafe: Some(StrafeSettings {
|
||||
enable: ControlsActivation::full_2d(),
|
||||
air_accel_limit: Some((int(150) * 66 * VALVE_SCALE).fix_1()),
|
||||
mv: (int(30) * VALVE_SCALE).fix_1(),
|
||||
tick_rate: Ratio64::new(66, AbsoluteTime::ONE_SECOND.get() as u64).unwrap(),
|
||||
}),
|
||||
jump: Some(JumpSettings {
|
||||
impulse: JumpImpulse::Height((int(52) * VALVE_SCALE).fix_1()),
|
||||
calculation: JumpCalculation::JumpThenBoost,
|
||||
limit_minimum: true,
|
||||
}),
|
||||
gravity: (int3(0, -800, 0) * VALVE_SCALE).fix_1(),
|
||||
mass: int(1),
|
||||
rocket: None,
|
||||
walk: Some(WalkSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(18), //?
|
||||
accel: int(90), //?
|
||||
},
|
||||
static_friction: int(2), //?
|
||||
kinetic_friction: int(3), //?
|
||||
surf_dot: int(3) / 4, // normal.y=0.75
|
||||
}),
|
||||
ladder: Some(LadderSettings {
|
||||
accelerate: AccelerateSettings {
|
||||
topspeed: int(18), //?
|
||||
accel: int(180), //?
|
||||
},
|
||||
dot: (int(1) / 2).sqrt(), //?
|
||||
}),
|
||||
swim: Some(PropulsionSettings {
|
||||
magnitude: int(12), //?
|
||||
}),
|
||||
hitbox: Hitbox::source(),
|
||||
camera_offset: ((int3(0, 64, 0) - (int3(0, 73, 0) >> 1)) * VALVE_SCALE).fix_1(),
|
||||
}
|
||||
}
|
||||
}
|
66
lib/common/src/instruction.rs
Normal file
66
lib/common/src/instruction.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TimedInstruction<I, T> {
|
||||
pub time: Time<T>,
|
||||
pub instruction: I,
|
||||
}
|
||||
|
||||
pub trait InstructionEmitter {
|
||||
type Instruction;
|
||||
type TimeInner;
|
||||
fn next_instruction(
|
||||
&self,
|
||||
time_limit: Time<Self::TimeInner>,
|
||||
) -> Option<TimedInstruction<Self::Instruction, Self::TimeInner>>;
|
||||
}
|
||||
pub trait InstructionConsumer {
|
||||
type Instruction;
|
||||
type TimeInner;
|
||||
fn process_instruction(
|
||||
&mut self,
|
||||
instruction: TimedInstruction<Self::Instruction, Self::TimeInner>,
|
||||
);
|
||||
}
|
||||
|
||||
//PROPER PRIVATE FIELDS!!!
|
||||
pub struct InstructionCollector<I, T> {
|
||||
time: Time<T>,
|
||||
instruction: Option<I>,
|
||||
}
|
||||
impl<I, T> InstructionCollector<I, T>
|
||||
where
|
||||
Time<T>: Copy + PartialOrd,
|
||||
{
|
||||
pub const fn new(time: Time<T>) -> Self {
|
||||
Self {
|
||||
time,
|
||||
instruction: None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn time(&self) -> Time<T> {
|
||||
self.time
|
||||
}
|
||||
pub fn collect(&mut self, instruction: Option<TimedInstruction<I, T>>) {
|
||||
match instruction {
|
||||
Some(unwrap_instruction) => {
|
||||
if unwrap_instruction.time < self.time {
|
||||
self.time = unwrap_instruction.time;
|
||||
self.instruction = Some(unwrap_instruction.instruction);
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
pub fn instruction(self) -> Option<TimedInstruction<I, T>> {
|
||||
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||
match self.instruction {
|
||||
Some(instruction) => Some(TimedInstruction {
|
||||
time: self.time,
|
||||
instruction,
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
748
lib/common/src/integer.rs
Normal file
748
lib/common/src/integer.rs
Normal file
@ -0,0 +1,748 @@
|
||||
pub use fixed_wide::fixed::{Fix, Fixed};
|
||||
pub use ratio_ops::ratio::{Divide, Ratio};
|
||||
|
||||
//integer units
|
||||
|
||||
/// specific example of a "default" time type
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub enum TimeInner {}
|
||||
pub type AbsoluteTime = Time<TimeInner>;
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub struct Time<T>(i64, core::marker::PhantomData<T>);
|
||||
impl<T> Time<T> {
|
||||
pub const MIN: Self = Self::raw(i64::MIN);
|
||||
pub const MAX: Self = Self::raw(i64::MAX);
|
||||
pub const ZERO: Self = Self::raw(0);
|
||||
pub const ONE_SECOND: Self = Self::raw(1_000_000_000);
|
||||
pub const ONE_MILLISECOND: Self = Self::raw(1_000_000);
|
||||
pub const ONE_MICROSECOND: Self = Self::raw(1_000);
|
||||
pub const ONE_NANOSECOND: Self = Self::raw(1);
|
||||
#[inline]
|
||||
pub const fn raw(num: i64) -> Self {
|
||||
Self(num, core::marker::PhantomData)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn get(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_secs(num: i64) -> Self {
|
||||
Self::raw(Self::ONE_SECOND.0 * num)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_millis(num: i64) -> Self {
|
||||
Self::raw(Self::ONE_MILLISECOND.0 * num)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_micros(num: i64) -> Self {
|
||||
Self::raw(Self::ONE_MICROSECOND.0 * num)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn from_nanos(num: i64) -> Self {
|
||||
Self::raw(Self::ONE_NANOSECOND.0 * num)
|
||||
}
|
||||
//should I have checked subtraction? force all time variables to be positive?
|
||||
#[inline]
|
||||
pub const fn nanos(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
#[inline]
|
||||
pub const fn to_ratio(self) -> Ratio<Planar64, Planar64> {
|
||||
Ratio::new(Planar64::raw(self.0), Planar64::raw(1_000_000_000))
|
||||
}
|
||||
#[inline]
|
||||
pub const fn coerce<U>(self) -> Time<U> {
|
||||
Time::raw(self.0)
|
||||
}
|
||||
}
|
||||
impl<T> From<Planar64> for Time<T> {
|
||||
#[inline]
|
||||
fn from(value: Planar64) -> Self {
|
||||
Self::raw((value * Planar64::raw(1_000_000_000)).fix_1().to_raw())
|
||||
}
|
||||
}
|
||||
impl<T, Num, Den, N1, T1> From<Ratio<Num, Den>> for Time<T>
|
||||
where
|
||||
Num: core::ops::Mul<Planar64, Output = N1>,
|
||||
N1: Divide<Den, Output = T1>,
|
||||
T1: Fix<Planar64>,
|
||||
{
|
||||
#[inline]
|
||||
fn from(value: Ratio<Num, Den>) -> Self {
|
||||
Self::raw(
|
||||
(value * Planar64::raw(1_000_000_000))
|
||||
.divide()
|
||||
.fix()
|
||||
.to_raw(),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T> std::fmt::Display for Time<T> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}s+{:09}ns",
|
||||
self.0 / Self::ONE_SECOND.0,
|
||||
self.0 % Self::ONE_SECOND.0
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T> std::default::Default for Time<T> {
|
||||
fn default() -> Self {
|
||||
Self::raw(0)
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Neg for Time<T> {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Self::raw(-self.0)
|
||||
}
|
||||
}
|
||||
macro_rules! impl_time_additive_operator {
|
||||
($trait:ty, $method:ident) => {
|
||||
impl<T> $trait for Time<T> {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn $method(self, rhs: Self) -> Self::Output {
|
||||
Self::raw(self.0.$method(rhs.0))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_time_additive_operator!(core::ops::Add, add);
|
||||
impl_time_additive_operator!(core::ops::Sub, sub);
|
||||
impl_time_additive_operator!(core::ops::Rem, rem);
|
||||
macro_rules! impl_time_additive_assign_operator {
|
||||
($trait:ty, $method:ident) => {
|
||||
impl<T> $trait for Time<T> {
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: Self) {
|
||||
self.0.$method(rhs.0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_time_additive_assign_operator!(core::ops::AddAssign, add_assign);
|
||||
impl_time_additive_assign_operator!(core::ops::SubAssign, sub_assign);
|
||||
impl_time_additive_assign_operator!(core::ops::RemAssign, rem_assign);
|
||||
impl<T> std::ops::Mul for Time<T> {
|
||||
type Output = Ratio<fixed_wide::fixed::Fixed<2, 64>, fixed_wide::fixed::Fixed<2, 64>>;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Ratio::new(
|
||||
Fixed::raw(self.0) * Fixed::raw(rhs.0),
|
||||
Fixed::raw_digit(1_000_000_000i64.pow(2)),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Div<i64> for Time<T> {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn div(self, rhs: i64) -> Self::Output {
|
||||
Self::raw(self.0 / rhs)
|
||||
}
|
||||
}
|
||||
impl<T> std::ops::Mul<i64> for Time<T> {
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn mul(self, rhs: i64) -> Self::Output {
|
||||
Self::raw(self.0 * rhs)
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::Mul<Time<T>> for Planar64 {
|
||||
type Output = Ratio<Fixed<2, 64>, Planar64>;
|
||||
fn mul(self, rhs: Time<T>) -> Self::Output {
|
||||
Ratio::new(self * Fixed::raw(rhs.0), Planar64::raw(1_000_000_000))
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test_time {
|
||||
use super::*;
|
||||
type Time = super::AbsoluteTime;
|
||||
#[test]
|
||||
fn time_from_planar64() {
|
||||
let a: Time = Planar64::from(1).into();
|
||||
assert_eq!(a, Time::ONE_SECOND);
|
||||
}
|
||||
#[test]
|
||||
fn time_from_ratio() {
|
||||
let a: Time = Ratio::new(Planar64::from(1), Planar64::from(1)).into();
|
||||
assert_eq!(a, Time::ONE_SECOND);
|
||||
}
|
||||
#[test]
|
||||
fn time_squared() {
|
||||
let a = Time::from_secs(2);
|
||||
assert_eq!(
|
||||
a * a,
|
||||
Ratio::new(
|
||||
Fixed::<2, 64>::raw_digit(1_000_000_000i64.pow(2)) * 4,
|
||||
Fixed::<2, 64>::raw_digit(1_000_000_000i64.pow(2))
|
||||
)
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn time_times_planar64() {
|
||||
let a = Time::from_secs(2);
|
||||
let b = Planar64::from(2);
|
||||
assert_eq!(
|
||||
b * a,
|
||||
Ratio::new(
|
||||
Fixed::<2, 64>::raw_digit(1_000_000_000 * (1 << 32)) << 2,
|
||||
Fixed::<1, 32>::raw_digit(1_000_000_000)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn gcd(mut a: u64, mut b: u64) -> u64 {
|
||||
while b != 0 {
|
||||
(a, b) = (b, a.rem_euclid(b));
|
||||
}
|
||||
a
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Hash)]
|
||||
pub struct Ratio64 {
|
||||
num: i64,
|
||||
den: u64,
|
||||
}
|
||||
impl Ratio64 {
|
||||
pub const ZERO: Self = Ratio64 { num: 0, den: 1 };
|
||||
pub const ONE: Self = Ratio64 { num: 1, den: 1 };
|
||||
#[inline]
|
||||
pub const fn new(num: i64, den: u64) -> Option<Ratio64> {
|
||||
if den == 0 {
|
||||
None
|
||||
} else {
|
||||
let d = gcd(num.unsigned_abs(), den);
|
||||
Some(Self {
|
||||
num: num / (d as i64),
|
||||
den: den / d,
|
||||
})
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub const fn num(self) -> i64 {
|
||||
self.num
|
||||
}
|
||||
#[inline]
|
||||
pub const fn den(self) -> u64 {
|
||||
self.den
|
||||
}
|
||||
#[inline]
|
||||
pub const fn mul_int(&self, rhs: i64) -> i64 {
|
||||
rhs * self.num / (self.den as i64)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn rhs_div_int(&self, rhs: i64) -> i64 {
|
||||
rhs * (self.den as i64) / self.num
|
||||
}
|
||||
#[inline]
|
||||
pub const fn mul_ref(&self, rhs: &Ratio64) -> Ratio64 {
|
||||
let (num, den) = (self.num * rhs.num, self.den * rhs.den);
|
||||
let d = gcd(num.unsigned_abs(), den);
|
||||
Self {
|
||||
num: num / (d as i64),
|
||||
den: den / d,
|
||||
}
|
||||
}
|
||||
}
|
||||
//from num_traits crate
|
||||
#[inline]
|
||||
fn integer_decode_f32(f: f32) -> (u64, i16, i8) {
|
||||
let bits: u32 = f.to_bits();
|
||||
let sign: i8 = if bits >> 31 == 0 { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits >> 23) & 0xff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0x7fffff) << 1
|
||||
} else {
|
||||
(bits & 0x7fffff) | 0x800000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 127 + 23;
|
||||
(mantissa as u64, exponent, sign)
|
||||
}
|
||||
#[inline]
|
||||
fn integer_decode_f64(f: f64) -> (u64, i16, i8) {
|
||||
let bits: u64 = f.to_bits();
|
||||
let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
|
||||
let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
|
||||
let mantissa = if exponent == 0 {
|
||||
(bits & 0xfffffffffffff) << 1
|
||||
} else {
|
||||
(bits & 0xfffffffffffff) | 0x10000000000000
|
||||
};
|
||||
// Exponent bias + mantissa shift
|
||||
exponent -= 1023 + 52;
|
||||
(mantissa, exponent, sign)
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum Ratio64TryFromFloatError {
|
||||
Nan,
|
||||
Infinite,
|
||||
Subnormal,
|
||||
HighlyNegativeExponent(i16),
|
||||
HighlyPositiveExponent(i16),
|
||||
}
|
||||
const MAX_DENOMINATOR: u128 = u64::MAX as u128;
|
||||
#[inline]
|
||||
fn ratio64_from_mes((m, e, s): (u64, i16, i8)) -> Result<Ratio64, Ratio64TryFromFloatError> {
|
||||
if e < -127 {
|
||||
//this can also just be zero
|
||||
Err(Ratio64TryFromFloatError::HighlyNegativeExponent(e))
|
||||
} else if e < -63 {
|
||||
//approximate input ratio within denominator limit
|
||||
let mut target_num = m as u128;
|
||||
let mut target_den = 1u128 << -e;
|
||||
|
||||
let mut num = 1;
|
||||
let mut den = 0;
|
||||
let mut prev_num = 0;
|
||||
let mut prev_den = 1;
|
||||
|
||||
while target_den != 0 {
|
||||
let whole = target_num / target_den;
|
||||
(target_num, target_den) = (target_den, target_num - whole * target_den);
|
||||
let new_num = whole * num + prev_num;
|
||||
let new_den = whole * den + prev_den;
|
||||
if MAX_DENOMINATOR < new_den {
|
||||
break;
|
||||
} else {
|
||||
(prev_num, prev_den) = (num, den);
|
||||
(num, den) = (new_num, new_den);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Ratio64::new(num as i64, den as u64).unwrap())
|
||||
} else if e < 0 {
|
||||
Ok(Ratio64::new((m as i64) * (s as i64), 1 << -e).unwrap())
|
||||
} else if (64 - m.leading_zeros() as i16) + e < 64 {
|
||||
Ok(Ratio64::new((m as i64) * (s as i64) * (1 << e), 1).unwrap())
|
||||
} else {
|
||||
Err(Ratio64TryFromFloatError::HighlyPositiveExponent(e))
|
||||
}
|
||||
}
|
||||
impl TryFrom<f32> for Ratio64 {
|
||||
type Error = Ratio64TryFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value: f32) -> Result<Self, Self::Error> {
|
||||
match value.classify() {
|
||||
std::num::FpCategory::Nan => Err(Self::Error::Nan),
|
||||
std::num::FpCategory::Infinite => Err(Self::Error::Infinite),
|
||||
std::num::FpCategory::Zero => Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => {
|
||||
ratio64_from_mes(integer_decode_f32(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<f64> for Ratio64 {
|
||||
type Error = Ratio64TryFromFloatError;
|
||||
#[inline]
|
||||
fn try_from(value: f64) -> Result<Self, Self::Error> {
|
||||
match value.classify() {
|
||||
std::num::FpCategory::Nan => Err(Self::Error::Nan),
|
||||
std::num::FpCategory::Infinite => Err(Self::Error::Infinite),
|
||||
std::num::FpCategory::Zero => Ok(Self::ZERO),
|
||||
std::num::FpCategory::Subnormal | std::num::FpCategory::Normal => {
|
||||
ratio64_from_mes(integer_decode_f64(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Ratio64> for Ratio64 {
|
||||
type Output = Ratio64;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Ratio64) -> Self::Output {
|
||||
let (num, den) = (self.num * rhs.num, self.den * rhs.den);
|
||||
let d = gcd(num.unsigned_abs(), den);
|
||||
Self {
|
||||
num: num / (d as i64),
|
||||
den: den / d,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i64> for Ratio64 {
|
||||
type Output = Ratio64;
|
||||
#[inline]
|
||||
fn mul(self, rhs: i64) -> Self::Output {
|
||||
Self {
|
||||
num: self.num * rhs,
|
||||
den: self.den,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Div<u64> for Ratio64 {
|
||||
type Output = Ratio64;
|
||||
#[inline]
|
||||
fn div(self, rhs: u64) -> Self::Output {
|
||||
Self {
|
||||
num: self.num,
|
||||
den: self.den * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Hash)]
|
||||
pub struct Ratio64Vec2 {
|
||||
pub x: Ratio64,
|
||||
pub y: Ratio64,
|
||||
}
|
||||
impl Ratio64Vec2 {
|
||||
pub const ONE: Self = Self {
|
||||
x: Ratio64::ONE,
|
||||
y: Ratio64::ONE,
|
||||
};
|
||||
#[inline]
|
||||
pub const fn new(x: Ratio64, y: Ratio64) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
#[inline]
|
||||
pub const fn mul_int(&self, rhs: glam::I64Vec2) -> glam::I64Vec2 {
|
||||
glam::i64vec2(self.x.mul_int(rhs.x), self.y.mul_int(rhs.y))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i64> for Ratio64Vec2 {
|
||||
type Output = Ratio64Vec2;
|
||||
#[inline]
|
||||
fn mul(self, rhs: i64) -> Self::Output {
|
||||
Self {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///[-pi,pi) = [-2^31,2^31-1]
|
||||
#[derive(Clone, Copy, Hash)]
|
||||
pub struct Angle32(i32);
|
||||
impl Angle32 {
|
||||
const ANGLE32_TO_FLOAT64_RADIANS: f64 = std::f64::consts::PI / ((1i64 << 31) as f64);
|
||||
pub const FRAC_PI_2: Self = Self(1 << 30);
|
||||
pub const NEG_FRAC_PI_2: Self = Self(-1 << 30);
|
||||
pub const PI: Self = Self(-1 << 31);
|
||||
#[inline]
|
||||
pub const fn wrap_from_i64(theta: i64) -> Self {
|
||||
//take lower bits
|
||||
//note: this was checked on compiler explorer and compiles to 1 instruction!
|
||||
Self(i32::from_ne_bytes(
|
||||
((theta & ((1 << 32) - 1)) as u32).to_ne_bytes(),
|
||||
))
|
||||
}
|
||||
#[inline]
|
||||
pub fn clamp_from_i64(theta: i64) -> Self {
|
||||
//the assembly is a bit confusing for this, I thought it was checking the same thing twice
|
||||
//but it's just checking and then overwriting the value for both upper and lower bounds.
|
||||
Self(theta.clamp(i32::MIN as i64, i32::MAX as i64) as i32)
|
||||
}
|
||||
#[inline]
|
||||
pub const fn get(&self) -> i32 {
|
||||
self.0
|
||||
}
|
||||
/// Clamps the value towards the midpoint of the range.
|
||||
/// Note that theta_min can be larger than theta_max and it will wrap clamp the other way around
|
||||
#[inline]
|
||||
pub fn clamp(&self, theta_min: Self, theta_max: Self) -> Self {
|
||||
//((max-min as u32)/2 as i32)+min
|
||||
let midpoint = (((theta_max.0 as u32).wrapping_sub(theta_min.0 as u32) / 2) as i32) //(u32::MAX/2) as i32 ALWAYS works
|
||||
.wrapping_add(theta_min.0);
|
||||
//(theta-mid).clamp(max-mid,min-mid)+mid
|
||||
Self(
|
||||
self.0
|
||||
.wrapping_sub(midpoint)
|
||||
.max(theta_min.0.wrapping_sub(midpoint))
|
||||
.min(theta_max.0.wrapping_sub(midpoint))
|
||||
.wrapping_add(midpoint),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
pub fn cos_sin(&self) -> (Planar64, Planar64) {
|
||||
/*
|
||||
//cordic
|
||||
let a=self.0 as u32;
|
||||
//initialize based on the quadrant
|
||||
let (mut x,mut y)=match (a&(1<<31)!=0,a&(1<<30)!=0){
|
||||
(false,false)=>( 1i64<<32, 0i64 ),//TR
|
||||
(false,true )=>( 0i64 , 1i64<<32),//TL
|
||||
(true ,false)=>(-1i64<<32, 0i64 ),//BL
|
||||
(true ,true )=>( 0i64 ,-1i64<<32),//BR
|
||||
};
|
||||
println!("x={} y={}",Planar64::raw(x),Planar64::raw(y));
|
||||
for i in 0..30{
|
||||
if a&(1<<(29-i))!=0{
|
||||
(x,y)=(x-(y>>i),y+(x>>i));
|
||||
}
|
||||
println!("i={i} t={} x={} y={}",(a&(1<<(29-i))!=0) as u8,Planar64::raw(x),Planar64::raw(y));
|
||||
}
|
||||
//don't forget the gain
|
||||
(Planar64::raw(x),Planar64::raw(y))
|
||||
*/
|
||||
let (s, c) = (self.0 as f64 * Self::ANGLE32_TO_FLOAT64_RADIANS).sin_cos();
|
||||
(
|
||||
Planar64::raw((c * ((1u64 << 32) as f64)) as i64),
|
||||
Planar64::raw((s * ((1u64 << 32) as f64)) as i64),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Into<f32> for Angle32 {
|
||||
#[inline]
|
||||
fn into(self) -> f32 {
|
||||
(self.0 as f64 * Self::ANGLE32_TO_FLOAT64_RADIANS) as f32
|
||||
}
|
||||
}
|
||||
impl std::ops::Neg for Angle32 {
|
||||
type Output = Angle32;
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Angle32(self.0.wrapping_neg())
|
||||
}
|
||||
}
|
||||
impl std::ops::Add<Angle32> for Angle32 {
|
||||
type Output = Angle32;
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Angle32(self.0.wrapping_add(rhs.0))
|
||||
}
|
||||
}
|
||||
impl std::ops::Sub<Angle32> for Angle32 {
|
||||
type Output = Angle32;
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Angle32(self.0.wrapping_sub(rhs.0))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<i32> for Angle32 {
|
||||
type Output = Angle32;
|
||||
#[inline]
|
||||
fn mul(self, rhs: i32) -> Self::Output {
|
||||
Angle32(self.0.wrapping_mul(rhs))
|
||||
}
|
||||
}
|
||||
impl std::ops::Mul<Angle32> for Angle32 {
|
||||
type Output = Angle32;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
Angle32(self.0.wrapping_mul(rhs.0))
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn angle_sin_cos() {
|
||||
fn close_enough(lhs: Planar64, rhs: Planar64) -> bool {
|
||||
(lhs - rhs).abs() < Planar64::EPSILON * 4
|
||||
}
|
||||
fn test_angle(f: f64) {
|
||||
let a = Angle32((f / Angle32::ANGLE32_TO_FLOAT64_RADIANS) as i32);
|
||||
println!("a={:#034b}", a.0);
|
||||
let (c, s) = a.cos_sin();
|
||||
let h = (s * s + c * c).sqrt();
|
||||
println!("cordic s={} c={}", (s / h).divide(), (c / h).divide());
|
||||
let (fs, fc) = f.sin_cos();
|
||||
println!("float s={} c={}", fs, fc);
|
||||
assert!(close_enough(
|
||||
(c / h).divide().fix_1(),
|
||||
Planar64::raw((fc * ((1u64 << 32) as f64)) as i64)
|
||||
));
|
||||
assert!(close_enough(
|
||||
(s / h).divide().fix_1(),
|
||||
Planar64::raw((fs * ((1u64 << 32) as f64)) as i64)
|
||||
));
|
||||
}
|
||||
test_angle(1.0);
|
||||
test_angle(std::f64::consts::PI / 4.0);
|
||||
test_angle(std::f64::consts::PI / 8.0);
|
||||
}
|
||||
|
||||
/* Unit type unused for now, may revive it for map files
|
||||
///[-1.0,1.0] = [-2^30,2^30]
|
||||
pub struct Unit32(i32);
|
||||
impl Unit32{
|
||||
#[inline]
|
||||
pub fn as_planar64(&self) -> Planar64{
|
||||
Planar64(4*(self.0 as i64))
|
||||
}
|
||||
}
|
||||
const UNIT32_ONE_FLOAT64=((1<<30) as f64);
|
||||
///[-1.0,1.0] = [-2^30,2^30]
|
||||
pub struct Unit32Vec3(glam::IVec3);
|
||||
impl TryFrom<[f32;3]> for Unit32Vec3{
|
||||
type Error=Unit32TryFromFloatError;
|
||||
fn try_from(value:[f32;3])->Result<Self,Self::Error>{
|
||||
Ok(Self(glam::ivec3(
|
||||
Unit32::try_from(Planar64::try_from(value[0])?)?.0,
|
||||
Unit32::try_from(Planar64::try_from(value[1])?)?.0,
|
||||
Unit32::try_from(Planar64::try_from(value[2])?)?.0,
|
||||
)))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
pub type Planar64TryFromFloatError = fixed_wide::fixed::FixedFromFloatError;
|
||||
pub type Planar64 = fixed_wide::types::I32F32;
|
||||
pub type Planar64Vec3 = linear_ops::types::Vector3<Planar64>;
|
||||
pub type Planar64Mat3 = linear_ops::types::Matrix3<Planar64>;
|
||||
pub mod vec3 {
|
||||
use super::*;
|
||||
pub use linear_ops::types::Vector3;
|
||||
pub const MIN: Planar64Vec3 = Planar64Vec3::new([Planar64::MIN; 3]);
|
||||
pub const MAX: Planar64Vec3 = Planar64Vec3::new([Planar64::MAX; 3]);
|
||||
pub const ZERO: Planar64Vec3 = Planar64Vec3::new([Planar64::ZERO; 3]);
|
||||
pub const ZERO_2: linear_ops::types::Vector3<Fixed<2, 64>> =
|
||||
linear_ops::types::Vector3::new([Fixed::<2, 64>::ZERO; 3]);
|
||||
pub const X: Planar64Vec3 = Planar64Vec3::new([Planar64::ONE, Planar64::ZERO, Planar64::ZERO]);
|
||||
pub const Y: Planar64Vec3 = Planar64Vec3::new([Planar64::ZERO, Planar64::ONE, Planar64::ZERO]);
|
||||
pub const Z: Planar64Vec3 = Planar64Vec3::new([Planar64::ZERO, Planar64::ZERO, Planar64::ONE]);
|
||||
pub const ONE: Planar64Vec3 = Planar64Vec3::new([Planar64::ONE, Planar64::ONE, Planar64::ONE]);
|
||||
pub const NEG_X: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::NEG_ONE, Planar64::ZERO, Planar64::ZERO]);
|
||||
pub const NEG_Y: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::ZERO, Planar64::NEG_ONE, Planar64::ZERO]);
|
||||
pub const NEG_Z: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::ZERO, Planar64::ZERO, Planar64::NEG_ONE]);
|
||||
pub const NEG_ONE: Planar64Vec3 =
|
||||
Planar64Vec3::new([Planar64::NEG_ONE, Planar64::NEG_ONE, Planar64::NEG_ONE]);
|
||||
#[inline]
|
||||
pub const fn int(x: i32, y: i32, z: i32) -> Planar64Vec3 {
|
||||
Planar64Vec3::new([
|
||||
Planar64::raw((x as i64) << 32),
|
||||
Planar64::raw((y as i64) << 32),
|
||||
Planar64::raw((z as i64) << 32),
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn raw_array(array: [i64; 3]) -> Planar64Vec3 {
|
||||
Planar64Vec3::new(array.map(Planar64::raw))
|
||||
}
|
||||
#[inline]
|
||||
pub fn raw_xyz(x: i64, y: i64, z: i64) -> Planar64Vec3 {
|
||||
Planar64Vec3::new([Planar64::raw(x), Planar64::raw(y), Planar64::raw(z)])
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32_array(
|
||||
[x, y, z]: [f32; 3],
|
||||
) -> Result<Planar64Vec3, Planar64TryFromFloatError> {
|
||||
Ok(Planar64Vec3::new([
|
||||
try_from_f32(x)?,
|
||||
try_from_f32(y)?,
|
||||
try_from_f32(z)?,
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn int(value: i32) -> Planar64 {
|
||||
Planar64::from(value)
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32(value: f32) -> Result<Planar64, Planar64TryFromFloatError> {
|
||||
let result: Result<Planar64, _> = value.try_into();
|
||||
match result {
|
||||
Ok(ok) => Ok(ok),
|
||||
Err(e) => e.underflow_to_zero(),
|
||||
}
|
||||
}
|
||||
pub mod mat3 {
|
||||
use super::*;
|
||||
pub use linear_ops::types::Matrix3;
|
||||
#[inline]
|
||||
pub const fn identity() -> Planar64Mat3 {
|
||||
Planar64Mat3::new([
|
||||
[Planar64::ONE, Planar64::ZERO, Planar64::ZERO],
|
||||
[Planar64::ZERO, Planar64::ONE, Planar64::ZERO],
|
||||
[Planar64::ZERO, Planar64::ZERO, Planar64::ONE],
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_diagonal(diag: Planar64Vec3) -> Planar64Mat3 {
|
||||
Planar64Mat3::new([
|
||||
[diag.x, Planar64::ZERO, Planar64::ZERO],
|
||||
[Planar64::ZERO, diag.y, Planar64::ZERO],
|
||||
[Planar64::ZERO, Planar64::ZERO, diag.z],
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_rotation_yx(x: Angle32, y: Angle32) -> Planar64Mat3 {
|
||||
let (xc, xs) = x.cos_sin();
|
||||
let (yc, ys) = y.cos_sin();
|
||||
Planar64Mat3::from_cols([
|
||||
Planar64Vec3::new([xc, Planar64::ZERO, -xs]),
|
||||
Planar64Vec3::new([(xs * ys).fix_1(), yc, (xc * ys).fix_1()]),
|
||||
Planar64Vec3::new([(xs * yc).fix_1(), -ys, (xc * yc).fix_1()]),
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_rotation_y(y: Angle32) -> Planar64Mat3 {
|
||||
let (c, s) = y.cos_sin();
|
||||
Planar64Mat3::from_cols([
|
||||
Planar64Vec3::new([c, Planar64::ZERO, -s]),
|
||||
vec3::Y,
|
||||
Planar64Vec3::new([s, Planar64::ZERO, c]),
|
||||
])
|
||||
}
|
||||
#[inline]
|
||||
pub fn try_from_f32_array_2d(
|
||||
[x_axis, y_axis, z_axis]: [[f32; 3]; 3],
|
||||
) -> Result<Planar64Mat3, Planar64TryFromFloatError> {
|
||||
Ok(Planar64Mat3::new([
|
||||
vec3::try_from_f32_array(x_axis)?.to_array(),
|
||||
vec3::try_from_f32_array(y_axis)?.to_array(),
|
||||
vec3::try_from_f32_array(z_axis)?.to_array(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Hash, Eq, PartialEq)]
|
||||
pub struct Planar64Affine3 {
|
||||
pub matrix3: Planar64Mat3, //includes scale above 1
|
||||
pub translation: Planar64Vec3,
|
||||
}
|
||||
impl Planar64Affine3 {
|
||||
#[inline]
|
||||
pub const fn new(matrix3: Planar64Mat3, translation: Planar64Vec3) -> Self {
|
||||
Self {
|
||||
matrix3,
|
||||
translation,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn transform_point3(&self, point: Planar64Vec3) -> vec3::Vector3<Fixed<2, 64>> {
|
||||
self.translation.fix_2() + self.matrix3 * point
|
||||
}
|
||||
}
|
||||
impl Into<glam::Mat4> for Planar64Affine3 {
|
||||
#[inline]
|
||||
fn into(self) -> glam::Mat4 {
|
||||
let matrix3 = self
|
||||
.matrix3
|
||||
.to_array()
|
||||
.map(|row| row.map(Into::<f32>::into));
|
||||
let translation = self.translation.to_array().map(Into::<f32>::into);
|
||||
glam::Mat4::from_cols_array(&[
|
||||
matrix3[0][0],
|
||||
matrix3[0][1],
|
||||
matrix3[0][2],
|
||||
0.0,
|
||||
matrix3[1][0],
|
||||
matrix3[1][1],
|
||||
matrix3[1][2],
|
||||
0.0,
|
||||
matrix3[2][0],
|
||||
matrix3[2][1],
|
||||
matrix3[2][2],
|
||||
0.0,
|
||||
translation[0],
|
||||
translation[1],
|
||||
translation[2],
|
||||
1.0,
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqrt() {
|
||||
let r = int(400);
|
||||
assert_eq!(r, Planar64::raw(1717986918400));
|
||||
let s = r.sqrt();
|
||||
assert_eq!(s, Planar64::raw(85899345920));
|
||||
}
|
16
lib/common/src/lib.rs
Normal file
16
lib/common/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
||||
pub mod aabb;
|
||||
pub mod bvh;
|
||||
pub mod controls_bitflag;
|
||||
pub mod gameplay_attributes;
|
||||
pub mod gameplay_modes;
|
||||
pub mod gameplay_style;
|
||||
pub mod instruction;
|
||||
pub mod integer;
|
||||
pub mod map;
|
||||
pub mod model;
|
||||
pub mod mouse;
|
||||
pub mod physics;
|
||||
pub mod run;
|
||||
pub mod session;
|
||||
pub mod timer;
|
||||
pub mod updatable;
|
14
lib/common/src/map.rs
Normal file
14
lib/common/src/map.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use crate::gameplay_attributes;
|
||||
use crate::gameplay_modes;
|
||||
use crate::model;
|
||||
//this is a temporary struct to try to get the code running again
|
||||
//TODO: use snf::map::Region to update the data in physics and graphics instead of this
|
||||
pub struct CompleteMap {
|
||||
pub modes: gameplay_modes::Modes,
|
||||
pub attributes: Vec<gameplay_attributes::CollisionAttributes>,
|
||||
pub meshes: Vec<model::Mesh>,
|
||||
pub models: Vec<model::Model>,
|
||||
//RenderPattern
|
||||
pub textures: Vec<Vec<u8>>,
|
||||
pub render_configs: Vec<model::RenderConfig>,
|
||||
}
|
138
lib/common/src/model.rs
Normal file
138
lib/common/src/model.rs
Normal file
@ -0,0 +1,138 @@
|
||||
use crate::gameplay_attributes;
|
||||
use crate::integer::{Planar64Affine3, Planar64Vec3};
|
||||
|
||||
pub type TextureCoordinate = glam::Vec2;
|
||||
pub type Color4 = glam::Vec4;
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
pub struct PositionId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
pub struct TextureCoordinateId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
pub struct NormalId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
pub struct ColorId(u32);
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct IndexedVertex {
|
||||
pub pos: PositionId,
|
||||
pub tex: TextureCoordinateId,
|
||||
pub normal: NormalId,
|
||||
pub color: ColorId,
|
||||
}
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
pub struct VertexId(u32);
|
||||
pub type IndexedVertexList = Vec<VertexId>;
|
||||
pub trait PolygonIter {
|
||||
fn polys(&self) -> impl Iterator<Item = &[VertexId]>;
|
||||
}
|
||||
pub trait MapVertexId {
|
||||
fn map_vertex_id<F: Fn(VertexId) -> VertexId>(self, f: F) -> Self;
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct PolygonList(Vec<IndexedVertexList>);
|
||||
impl PolygonList {
|
||||
pub const fn new(list: Vec<IndexedVertexList>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
pub fn extend<T: IntoIterator<Item = IndexedVertexList>>(&mut self, iter: T) {
|
||||
self.0.extend(iter);
|
||||
}
|
||||
}
|
||||
impl PolygonIter for PolygonList {
|
||||
fn polys(&self) -> impl Iterator<Item = &[VertexId]> {
|
||||
self.0.iter().map(|poly| poly.as_slice())
|
||||
}
|
||||
}
|
||||
impl MapVertexId for PolygonList {
|
||||
fn map_vertex_id<F: Fn(VertexId) -> VertexId>(self, f: F) -> Self {
|
||||
Self(
|
||||
self.0
|
||||
.into_iter()
|
||||
.map(|ivl| ivl.into_iter().map(&f).collect())
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
// pub struct TriangleStrip(IndexedVertexList);
|
||||
// impl PolygonIter for TriangleStrip{
|
||||
// fn polys(&self)->impl Iterator<Item=&[VertexId]>{
|
||||
// self.0.vertices.windows(3).enumerate().map(|(i,s)|if i&0!=0{return s.iter().rev()}else{return s.iter()})
|
||||
// }
|
||||
// }
|
||||
#[derive(Clone, Copy, Hash, id::Id, PartialEq, Eq)]
|
||||
pub struct PolygonGroupId(u32);
|
||||
#[derive(Clone)]
|
||||
pub enum PolygonGroup {
|
||||
PolygonList(PolygonList),
|
||||
//TriangleStrip(TriangleStrip),
|
||||
}
|
||||
impl PolygonIter for PolygonGroup {
|
||||
fn polys(&self) -> impl Iterator<Item = &[VertexId]> {
|
||||
match self {
|
||||
PolygonGroup::PolygonList(list) => list.polys(),
|
||||
//PolygonGroup::TriangleStrip(strip)=>strip.polys(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl MapVertexId for PolygonGroup {
|
||||
fn map_vertex_id<F: Fn(VertexId) -> VertexId>(self, f: F) -> Self {
|
||||
match self {
|
||||
PolygonGroup::PolygonList(polys) => Self::PolygonList(polys.map_vertex_id(f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Ah yes, a group of things to render at the same time
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)]
|
||||
pub struct TextureId(u32);
|
||||
#[derive(Clone, Copy, Hash, id::Id, Eq, PartialEq)]
|
||||
pub struct RenderConfigId(u32);
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RenderConfig {
|
||||
pub texture: Option<TextureId>,
|
||||
}
|
||||
impl RenderConfig {
|
||||
pub const fn texture(texture: TextureId) -> Self {
|
||||
Self {
|
||||
texture: Some(texture),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct IndexedGraphicsGroup {
|
||||
//Render pattern material/texture/shader/flat color
|
||||
pub render: RenderConfigId,
|
||||
pub groups: Vec<PolygonGroupId>,
|
||||
}
|
||||
#[derive(Clone, Default)]
|
||||
pub struct IndexedPhysicsGroup {
|
||||
//the polygons in this group are guaranteed to make a closed convex shape
|
||||
pub groups: Vec<PolygonGroupId>,
|
||||
}
|
||||
//This is a superset of PhysicsModel and GraphicsModel
|
||||
#[derive(Clone, Copy, Debug, Hash, id::Id, Eq, PartialEq)]
|
||||
pub struct MeshId(u32);
|
||||
#[derive(Clone)]
|
||||
pub struct Mesh {
|
||||
pub unique_pos: Vec<Planar64Vec3>, //Unit32Vec3
|
||||
pub unique_normal: Vec<Planar64Vec3>, //Unit32Vec3
|
||||
pub unique_tex: Vec<TextureCoordinate>,
|
||||
pub unique_color: Vec<Color4>,
|
||||
pub unique_vertices: Vec<IndexedVertex>,
|
||||
//polygon groups are constant texture AND convexity slices
|
||||
//note that this may need to be changed to be a list of individual faces
|
||||
//for submeshes to work since face ids need to be consistent across submeshes
|
||||
//so face == polygon_groups[face_id]
|
||||
pub polygon_groups: Vec<PolygonGroup>,
|
||||
//graphics indexed (by texture)
|
||||
pub graphics_groups: Vec<IndexedGraphicsGroup>,
|
||||
//physics indexed (by convexity)
|
||||
pub physics_groups: Vec<IndexedPhysicsGroup>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, id::Id, Eq, PartialEq)]
|
||||
pub struct ModelId(u32);
|
||||
pub struct Model {
|
||||
pub mesh: MeshId,
|
||||
pub attributes: gameplay_attributes::CollisionAttributesId,
|
||||
pub color: Color4, //transparency is in here
|
||||
pub transform: Planar64Affine3,
|
||||
}
|
29
lib/common/src/mouse.rs
Normal file
29
lib/common/src/mouse.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::integer::Time;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MouseState<T> {
|
||||
pub pos: glam::IVec2,
|
||||
pub time: Time<T>,
|
||||
}
|
||||
impl<T> Default for MouseState<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
time: Time::ZERO,
|
||||
pos: glam::IVec2::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T> MouseState<T>
|
||||
where
|
||||
Time<T>: Copy,
|
||||
{
|
||||
pub fn lerp(&self, target: &MouseState<T>, time: Time<T>) -> glam::IVec2 {
|
||||
let m0 = self.pos.as_i64vec2();
|
||||
let m1 = target.pos.as_i64vec2();
|
||||
//these are deltas
|
||||
let t1t = (target.time - time).nanos();
|
||||
let tt0 = (time - self.time).nanos();
|
||||
let dt = (target.time - self.time).nanos();
|
||||
((m0 * t1t + m1 * tt0) / dt).as_ivec2()
|
||||
}
|
||||
}
|
37
lib/common/src/physics.rs
Normal file
37
lib/common/src/physics.rs
Normal file
@ -0,0 +1,37 @@
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub enum TimeInner {}
|
||||
pub type Time = crate::integer::Time<TimeInner>;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Instruction {
|
||||
ReplaceMouse(
|
||||
crate::mouse::MouseState<TimeInner>,
|
||||
crate::mouse::MouseState<TimeInner>,
|
||||
),
|
||||
SetNextMouse(crate::mouse::MouseState<TimeInner>),
|
||||
SetMoveRight(bool),
|
||||
SetMoveUp(bool),
|
||||
SetMoveBack(bool),
|
||||
SetMoveLeft(bool),
|
||||
SetMoveDown(bool),
|
||||
SetMoveForward(bool),
|
||||
SetJump(bool),
|
||||
SetZoom(bool),
|
||||
/// Reset: fully replace the physics state.
|
||||
/// This forgets all inputs and settings which need to be reapplied.
|
||||
Reset,
|
||||
/// Restart: Teleport to the start zone.
|
||||
Restart,
|
||||
/// Spawn: Teleport to a specific mode's spawn
|
||||
/// Sets current mode & spawn
|
||||
Spawn(
|
||||
crate::gameplay_modes::ModeId,
|
||||
crate::gameplay_modes::StageId,
|
||||
),
|
||||
Idle,
|
||||
//Idle: there were no input events, but the simulation is safe to advance to this timestep
|
||||
//for interpolation / networking / playback reasons, most playback heads will always want
|
||||
//to be 1 instruction ahead to generate the next state for interpolation.
|
||||
PracticeFly,
|
||||
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||
}
|
117
lib/common/src/run.rs
Normal file
117
lib/common/src/run.rs
Normal file
@ -0,0 +1,117 @@
|
||||
use crate::timer::{Paused, Realtime, TimerFixed, Unpaused};
|
||||
|
||||
use crate::physics::{Time as PhysicsTime, TimeInner as PhysicsTimeInner};
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub enum TimeInner {}
|
||||
pub type Time = crate::integer::Time<TimeInner>;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum FlagReason {
|
||||
Anticheat,
|
||||
StyleChange,
|
||||
Clock,
|
||||
Pause,
|
||||
Flying,
|
||||
Gravity,
|
||||
Timescale,
|
||||
TimeTravel,
|
||||
Teleport,
|
||||
}
|
||||
impl ToString for FlagReason {
|
||||
fn to_string(&self) -> String {
|
||||
self.as_ref().to_owned()
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for FlagReason {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
FlagReason::Anticheat => "Passed through anticheat zone.",
|
||||
FlagReason::StyleChange => "Changed style.",
|
||||
FlagReason::Clock => "Incorrect clock. (This can be caused by internet hiccups)",
|
||||
FlagReason::Pause => "Pausing is not allowed in this style.",
|
||||
FlagReason::Flying => "Flying is not allowed in this style.",
|
||||
FlagReason::Gravity => "Gravity modification is not allowed in this style.",
|
||||
FlagReason::Timescale => "Timescale is not allowed in this style.",
|
||||
FlagReason::TimeTravel => "Time travel is not allowed in this style.",
|
||||
FlagReason::Teleport => "Illegal teleport.",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
NotStarted,
|
||||
AlreadyStarted,
|
||||
AlreadyFinished,
|
||||
}
|
||||
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 {}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum RunState {
|
||||
Created,
|
||||
Started {
|
||||
timer: TimerFixed<Realtime<PhysicsTimeInner, TimeInner>, Unpaused>,
|
||||
},
|
||||
Finished {
|
||||
timer: TimerFixed<Realtime<PhysicsTimeInner, TimeInner>, Paused>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Run {
|
||||
state: RunState,
|
||||
flagged: Option<FlagReason>,
|
||||
}
|
||||
|
||||
impl Run {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: RunState::Created,
|
||||
flagged: None,
|
||||
}
|
||||
}
|
||||
pub fn time(&self, time: PhysicsTime) -> Time {
|
||||
match &self.state {
|
||||
RunState::Created => Time::ZERO,
|
||||
RunState::Started { timer } => timer.time(time),
|
||||
RunState::Finished { timer } => timer.time(time),
|
||||
}
|
||||
}
|
||||
pub fn start(&mut self, time: PhysicsTime) -> Result<(), Error> {
|
||||
match &self.state {
|
||||
RunState::Created => {
|
||||
self.state = RunState::Started {
|
||||
timer: TimerFixed::new(time, Time::ZERO),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
RunState::Started { .. } => Err(Error::AlreadyStarted),
|
||||
RunState::Finished { .. } => Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn finish(&mut self, time: PhysicsTime) -> Result<(), Error> {
|
||||
//this uses Copy
|
||||
match &self.state {
|
||||
RunState::Created => Err(Error::NotStarted),
|
||||
RunState::Started { timer } => {
|
||||
self.state = RunState::Finished {
|
||||
timer: timer.into_paused(time),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
RunState::Finished { .. } => Err(Error::AlreadyFinished),
|
||||
}
|
||||
}
|
||||
pub fn flag(&mut self, flag_reason: FlagReason) {
|
||||
//don't replace the first reason the run was flagged
|
||||
if self.flagged.is_none() {
|
||||
self.flagged = Some(flag_reason);
|
||||
}
|
||||
}
|
||||
}
|
3
lib/common/src/session.rs
Normal file
3
lib/common/src/session.rs
Normal file
@ -0,0 +1,3 @@
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
pub enum TimeInner {}
|
||||
pub type Time = crate::integer::Time<TimeInner>;
|
372
lib/common/src/timer.rs
Normal file
372
lib/common/src/timer.rs
Normal file
@ -0,0 +1,372 @@
|
||||
use crate::integer::{Ratio64, Time};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Paused;
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Unpaused;
|
||||
|
||||
pub trait PauseState: Copy + std::fmt::Debug {
|
||||
const IS_PAUSED: bool;
|
||||
fn new() -> Self;
|
||||
}
|
||||
impl PauseState for Paused {
|
||||
const IS_PAUSED: bool = true;
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
impl PauseState for Unpaused {
|
||||
const IS_PAUSED: bool = false;
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
enum Inner {}
|
||||
type InnerTime = Time<Inner>;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Realtime<In, Out> {
|
||||
offset: InnerTime,
|
||||
_in: core::marker::PhantomData<In>,
|
||||
_out: core::marker::PhantomData<Out>,
|
||||
}
|
||||
impl<In, Out> Realtime<In, Out> {
|
||||
pub const fn new(offset: InnerTime) -> Self {
|
||||
Self {
|
||||
offset,
|
||||
_in: core::marker::PhantomData,
|
||||
_out: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Scaled<In, Out> {
|
||||
scale: Ratio64,
|
||||
offset: InnerTime,
|
||||
_in: core::marker::PhantomData<In>,
|
||||
_out: core::marker::PhantomData<Out>,
|
||||
}
|
||||
impl<In, Out> Scaled<In, Out>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
{
|
||||
pub const fn new(scale: Ratio64, offset: InnerTime) -> Self {
|
||||
Self {
|
||||
scale,
|
||||
offset,
|
||||
_in: core::marker::PhantomData,
|
||||
_out: core::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
const fn with_scale(scale: Ratio64) -> Self {
|
||||
Self::new(scale, InnerTime::ZERO)
|
||||
}
|
||||
const fn scale(&self, time: Time<In>) -> InnerTime {
|
||||
InnerTime::raw(self.scale.mul_int(time.get()))
|
||||
}
|
||||
const fn get_scale(&self) -> Ratio64 {
|
||||
self.scale
|
||||
}
|
||||
fn set_scale(&mut self, time: Time<In>, new_scale: Ratio64) {
|
||||
let new_time = self.get_time(time);
|
||||
self.scale = new_scale;
|
||||
self.set_time(time, new_time);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait TimerState {
|
||||
type In;
|
||||
type Out;
|
||||
fn identity() -> Self;
|
||||
fn get_time(&self, time: Time<Self::In>) -> Time<Self::Out>;
|
||||
fn set_time(&mut self, time: Time<Self::In>, new_time: Time<Self::Out>);
|
||||
fn get_offset(&self) -> InnerTime;
|
||||
fn set_offset(&mut self, offset: InnerTime);
|
||||
}
|
||||
impl<In, Out> TimerState for Realtime<In, Out> {
|
||||
type In = In;
|
||||
type Out = Out;
|
||||
fn identity() -> Self {
|
||||
Self::new(InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self, time: Time<In>) -> Time<Out> {
|
||||
time.coerce() + self.offset.coerce()
|
||||
}
|
||||
fn set_time(&mut self, time: Time<In>, new_time: Time<Out>) {
|
||||
self.offset = new_time.coerce() - time.coerce();
|
||||
}
|
||||
fn get_offset(&self) -> InnerTime {
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self, offset: InnerTime) {
|
||||
self.offset = offset;
|
||||
}
|
||||
}
|
||||
impl<In, Out> TimerState for Scaled<In, Out>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
{
|
||||
type In = In;
|
||||
type Out = Out;
|
||||
fn identity() -> Self {
|
||||
Self::new(Ratio64::ONE, InnerTime::ZERO)
|
||||
}
|
||||
fn get_time(&self, time: Time<In>) -> Time<Out> {
|
||||
(self.scale(time) + self.offset).coerce()
|
||||
}
|
||||
fn set_time(&mut self, time: Time<In>, new_time: Time<Out>) {
|
||||
self.offset = new_time.coerce() - self.scale(time);
|
||||
}
|
||||
fn get_offset(&self) -> InnerTime {
|
||||
self.offset
|
||||
}
|
||||
fn set_offset(&mut self, offset: InnerTime) {
|
||||
self.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct TimerFixed<T: TimerState, P: PauseState> {
|
||||
state: T,
|
||||
_paused: P,
|
||||
}
|
||||
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<P: PauseState, In, Out> TimerFixed<Scaled<In, Out>, P>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
{
|
||||
pub fn scaled(time: Time<In>, new_time: Time<Out>, scale: Ratio64) -> Self {
|
||||
let mut timer = Self {
|
||||
state: Scaled::with_scale(scale),
|
||||
_paused: P::new(),
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
pub const fn get_scale(&self) -> Ratio64 {
|
||||
self.state.get_scale()
|
||||
}
|
||||
pub fn set_scale(&mut self, time: Time<In>, new_scale: Ratio64) {
|
||||
self.state.set_scale(time, new_scale)
|
||||
}
|
||||
}
|
||||
|
||||
//pause and unpause is generic across TimerState
|
||||
impl<T: TimerState> TimerFixed<T, Paused>
|
||||
where
|
||||
Time<T::In>: Copy,
|
||||
{
|
||||
pub fn into_unpaused(self, time: Time<T::In>) -> TimerFixed<T, Unpaused> {
|
||||
let new_time = self.time(time);
|
||||
let mut timer = TimerFixed {
|
||||
state: self.state,
|
||||
_paused: Unpaused,
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
}
|
||||
impl<T: TimerState> TimerFixed<T, Unpaused>
|
||||
where
|
||||
Time<T::In>: Copy,
|
||||
{
|
||||
pub fn into_paused(self, time: Time<T::In>) -> TimerFixed<T, Paused> {
|
||||
let new_time = self.time(time);
|
||||
let mut timer = TimerFixed {
|
||||
state: self.state,
|
||||
_paused: Paused,
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
}
|
||||
|
||||
//the new constructor and time queries are generic across both
|
||||
impl<T: TimerState, P: PauseState> TimerFixed<T, P> {
|
||||
pub fn new(time: Time<T::In>, new_time: Time<T::Out>) -> Self {
|
||||
let mut timer = Self {
|
||||
state: T::identity(),
|
||||
_paused: P::new(),
|
||||
};
|
||||
timer.set_time(time, new_time);
|
||||
timer
|
||||
}
|
||||
pub fn from_state(state: T) -> Self {
|
||||
Self {
|
||||
state,
|
||||
_paused: P::new(),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self) -> T {
|
||||
self.state
|
||||
}
|
||||
pub fn time(&self, time: Time<T::In>) -> Time<T::Out> {
|
||||
match P::IS_PAUSED {
|
||||
true => self.state.get_offset().coerce(),
|
||||
false => self.state.get_time(time),
|
||||
}
|
||||
}
|
||||
pub fn set_time(&mut self, time: Time<T::In>, new_time: Time<T::Out>) {
|
||||
match P::IS_PAUSED {
|
||||
true => self.state.set_offset(new_time.coerce()),
|
||||
false => self.state.set_time(time, new_time),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
AlreadyPaused,
|
||||
AlreadyUnpaused,
|
||||
}
|
||||
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 {}
|
||||
|
||||
//wrapper type which holds type state internally
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Timer<T: TimerState> {
|
||||
Paused(TimerFixed<T, Paused>),
|
||||
Unpaused(TimerFixed<T, Unpaused>),
|
||||
}
|
||||
impl<T: TimerState> Timer<T>
|
||||
where
|
||||
T: Copy,
|
||||
Time<T::In>: Copy,
|
||||
{
|
||||
pub fn from_state(state: T, paused: bool) -> Self {
|
||||
match paused {
|
||||
true => Self::Paused(TimerFixed::from_state(state)),
|
||||
false => Self::Unpaused(TimerFixed::from_state(state)),
|
||||
}
|
||||
}
|
||||
pub fn into_state(self) -> (T, bool) {
|
||||
match self {
|
||||
Self::Paused(timer) => (timer.into_state(), true),
|
||||
Self::Unpaused(timer) => (timer.into_state(), false),
|
||||
}
|
||||
}
|
||||
pub fn paused(time: Time<T::In>, new_time: Time<T::Out>) -> Self {
|
||||
Self::Paused(TimerFixed::new(time, new_time))
|
||||
}
|
||||
pub fn unpaused(time: Time<T::In>, new_time: Time<T::Out>) -> Self {
|
||||
Self::Unpaused(TimerFixed::new(time, new_time))
|
||||
}
|
||||
pub fn time(&self, time: Time<T::In>) -> Time<T::Out> {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.time(time),
|
||||
Self::Unpaused(timer) => timer.time(time),
|
||||
}
|
||||
}
|
||||
pub fn set_time(&mut self, time: Time<T::In>, new_time: Time<T::Out>) {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.set_time(time, new_time),
|
||||
Self::Unpaused(timer) => timer.set_time(time, new_time),
|
||||
}
|
||||
}
|
||||
pub fn pause(&mut self, time: Time<T::In>) -> Result<(), Error> {
|
||||
*self = match *self {
|
||||
Self::Paused(_) => return Err(Error::AlreadyPaused),
|
||||
Self::Unpaused(timer) => Self::Paused(timer.into_paused(time)),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn unpause(&mut self, time: Time<T::In>) -> Result<(), Error> {
|
||||
*self = match *self {
|
||||
Self::Paused(timer) => Self::Unpaused(timer.into_unpaused(time)),
|
||||
Self::Unpaused(_) => return Err(Error::AlreadyUnpaused),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
pub fn is_paused(&self) -> bool {
|
||||
match self {
|
||||
Self::Paused(_) => true,
|
||||
Self::Unpaused(_) => false,
|
||||
}
|
||||
}
|
||||
pub fn set_paused(&mut self, time: Time<T::In>, paused: bool) -> Result<(), Error> {
|
||||
match paused {
|
||||
true => self.pause(time),
|
||||
false => self.unpause(time),
|
||||
}
|
||||
}
|
||||
}
|
||||
//scaled timer methods are generic across PauseState
|
||||
impl<In, Out> Timer<Scaled<In, Out>>
|
||||
where
|
||||
Time<In>: Copy,
|
||||
{
|
||||
pub const fn get_scale(&self) -> Ratio64 {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.get_scale(),
|
||||
Self::Unpaused(timer) => timer.get_scale(),
|
||||
}
|
||||
}
|
||||
pub fn set_scale(&mut self, time: Time<In>, new_scale: Ratio64) {
|
||||
match self {
|
||||
Self::Paused(timer) => timer.set_scale(time, new_scale),
|
||||
Self::Unpaused(timer) => timer.set_scale(time, new_scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
macro_rules! sec {
|
||||
($s: expr) => {
|
||||
Time::from_secs($s)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
enum Parent {}
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Debug)]
|
||||
enum Calculated {}
|
||||
#[test]
|
||||
fn test_timerfixed_scaled() {
|
||||
//create a paused timer that reads 0s
|
||||
let timer = TimerFixed::<Scaled<Parent, Calculated>, Paused>::from_state(Scaled::new(
|
||||
0.5f32.try_into().unwrap(),
|
||||
sec!(0),
|
||||
));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(sec!(1)), sec!(0));
|
||||
|
||||
//unpause it after one second
|
||||
let timer = timer.into_unpaused(sec!(1));
|
||||
//the timer at 6 seconds should read 2.5s
|
||||
assert_eq!(timer.time(sec!(6)), Time::from_millis(2500));
|
||||
|
||||
//pause the timer after 11 seconds
|
||||
let timer = timer.into_paused(sec!(11));
|
||||
//the paused timer at 20 seconds should read 5s
|
||||
assert_eq!(timer.time(sec!(20)), sec!(5));
|
||||
}
|
||||
#[test]
|
||||
fn test_timer() -> Result<(), Error> {
|
||||
//create a paused timer that reads 0s
|
||||
let mut timer = Timer::<Realtime<Parent, Calculated>>::paused(sec!(0), sec!(0));
|
||||
//the paused timer at 1 second should read 0s
|
||||
assert_eq!(timer.time(sec!(1)), sec!(0));
|
||||
|
||||
//unpause it after one second
|
||||
timer.unpause(sec!(1))?;
|
||||
//the timer at 6 seconds should read 5s
|
||||
assert_eq!(timer.time(sec!(6)), sec!(5));
|
||||
|
||||
//pause the timer after 11 seconds
|
||||
timer.pause(sec!(11))?;
|
||||
//the paused timer at 20 seconds should read 10s
|
||||
assert_eq!(timer.time(sec!(20)), sec!(10));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
56
lib/common/src/updatable.rs
Normal file
56
lib/common/src/updatable.rs
Normal file
@ -0,0 +1,56 @@
|
||||
pub trait Updatable<Updater> {
|
||||
fn update(&mut self, update: Updater);
|
||||
}
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
struct InnerId(u32);
|
||||
#[derive(Clone)]
|
||||
struct Inner {
|
||||
id: InnerId,
|
||||
enabled: bool,
|
||||
}
|
||||
#[derive(Clone, Copy, Hash, Eq, PartialEq)]
|
||||
struct OuterId(u32);
|
||||
struct Outer {
|
||||
id: OuterId,
|
||||
inners: std::collections::HashMap<InnerId, Inner>,
|
||||
}
|
||||
|
||||
enum Update<I, U> {
|
||||
Insert(I),
|
||||
Update(U),
|
||||
Remove,
|
||||
}
|
||||
|
||||
struct InnerUpdate {
|
||||
//#[updatable(Update)]
|
||||
enabled: Option<bool>,
|
||||
}
|
||||
struct OuterUpdate {
|
||||
//#[updatable(Insert,Update,Remove)]
|
||||
inners: std::collections::HashMap<InnerId, Update<Inner, InnerUpdate>>,
|
||||
//#[updatable(Update)]
|
||||
//inners:std::collections::HashMap<InnerId,InnerUpdate>,
|
||||
}
|
||||
impl Updatable<InnerUpdate> for Inner {
|
||||
fn update(&mut self, update: InnerUpdate) {
|
||||
if let Some(enabled) = update.enabled {
|
||||
self.enabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Updatable<OuterUpdate> for Outer {
|
||||
fn update(&mut self, update: OuterUpdate) {
|
||||
for (id, up) in update.inners {
|
||||
match up {
|
||||
Update::Insert(new_inner) => self.inners.insert(id, new_inner),
|
||||
Update::Update(inner_update) => self.inners.get_mut(&id).map(|inner| {
|
||||
let old = inner.clone();
|
||||
inner.update(inner_update);
|
||||
old
|
||||
}),
|
||||
Update::Remove => self.inners.remove(&id),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
//*/
|
1
lib/deferred_loader/.gitignore
vendored
Normal file
1
lib/deferred_loader/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
21
lib/deferred_loader/Cargo.toml
Normal file
21
lib/deferred_loader/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "strafesnet_deferred_loader"
|
||||
version = "0.4.1"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Acquire IDs for objects before loading them in bulk."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["legacy"]
|
||||
legacy = ["dep:url","dep:vbsp"]
|
||||
#roblox = ["dep:lazy-regex"]
|
||||
#source = ["dep:vbsp"]
|
||||
|
||||
[dependencies]
|
||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
||||
url = { version = "2.5.2", optional = true }
|
||||
vbsp = { version = "0.6.0", optional = true }
|
176
lib/deferred_loader/LICENSE-APACHE
Normal file
176
lib/deferred_loader/LICENSE-APACHE
Normal file
@ -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
|
23
lib/deferred_loader/LICENSE-MIT
Normal file
23
lib/deferred_loader/LICENSE-MIT
Normal file
@ -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.
|
19
lib/deferred_loader/README.md
Normal file
19
lib/deferred_loader/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
Texture Loader
|
||||
==============
|
||||
|
||||
## Texture loader, designed to be used in conjunction with rbx_loader, bsp_loader or Strafe Client
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
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.
|
||||
</sub>
|
34
lib/deferred_loader/src/lib.rs
Normal file
34
lib/deferred_loader/src/lib.rs
Normal file
@ -0,0 +1,34 @@
|
||||
#[cfg(feature="legacy")]
|
||||
mod roblox_legacy;
|
||||
#[cfg(feature="legacy")]
|
||||
mod source_legacy;
|
||||
#[cfg(feature="roblox")]
|
||||
mod roblox;
|
||||
#[cfg(feature="source")]
|
||||
mod source;
|
||||
|
||||
#[cfg(any(feature="roblox",feature="legacy"))]
|
||||
pub mod rbxassetid;
|
||||
|
||||
pub mod texture;
|
||||
#[cfg(any(feature="source",feature="legacy"))]
|
||||
pub mod valve_mesh;
|
||||
#[cfg(any(feature="roblox",feature="legacy"))]
|
||||
pub mod roblox_mesh;
|
||||
|
||||
#[cfg(feature="legacy")]
|
||||
pub fn roblox_legacy()->roblox_legacy::Loader{
|
||||
roblox_legacy::Loader::new()
|
||||
}
|
||||
#[cfg(feature="legacy")]
|
||||
pub fn source_legacy()->source_legacy::Loader{
|
||||
source_legacy::Loader::new()
|
||||
}
|
||||
#[cfg(feature="roblox")]
|
||||
pub fn roblox()->roblox::Loader{
|
||||
roblox::Loader::new()
|
||||
}
|
||||
#[cfg(feature="source")]
|
||||
pub fn source()->source::Loader{
|
||||
source::Loader::new()
|
||||
}
|
48
lib/deferred_loader/src/rbxassetid.rs
Normal file
48
lib/deferred_loader/src/rbxassetid.rs
Normal file
@ -0,0 +1,48 @@
|
||||
#[derive(Hash,Eq,PartialEq)]
|
||||
pub struct RobloxAssetId(pub u64);
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub struct StringWithError{
|
||||
string:String,
|
||||
error:RobloxAssetIdParseErr,
|
||||
}
|
||||
impl std::fmt::Display for StringWithError{
|
||||
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||
write!(f,"{self:?}")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for StringWithError{}
|
||||
impl StringWithError{
|
||||
const fn new(
|
||||
string:String,
|
||||
error:RobloxAssetIdParseErr,
|
||||
)->Self{
|
||||
Self{string,error}
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum RobloxAssetIdParseErr{
|
||||
Url(url::ParseError),
|
||||
UnknownScheme,
|
||||
ParseInt(std::num::ParseIntError),
|
||||
MissingAssetId,
|
||||
}
|
||||
impl std::str::FromStr for RobloxAssetId{
|
||||
type Err=StringWithError;
|
||||
fn from_str(s:&str)->Result<Self,Self::Err>{
|
||||
let url=url::Url::parse(s).map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::Url(e)))?;
|
||||
let parsed_asset_id=match url.scheme(){
|
||||
"rbxassetid"=>url.domain().ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?.parse(),
|
||||
"http"|"https"=>{
|
||||
let (_,asset_id)=url.query_pairs()
|
||||
.find(|(id,_)|match id.as_ref(){
|
||||
"ID"|"id"|"Id"|"iD"=>true,
|
||||
_=>false,
|
||||
}).ok_or_else(||StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::MissingAssetId))?;
|
||||
asset_id.parse()
|
||||
},
|
||||
_=>Err(StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::UnknownScheme))?,
|
||||
};
|
||||
Ok(Self(parsed_asset_id.map_err(|e|StringWithError::new(s.to_owned(),RobloxAssetIdParseErr::ParseInt(e)))?))
|
||||
}
|
||||
}
|
0
lib/deferred_loader/src/roblox.rs
Normal file
0
lib/deferred_loader/src/roblox.rs
Normal file
112
lib/deferred_loader/src/roblox_legacy.rs
Normal file
112
lib/deferred_loader/src/roblox_legacy.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
use crate::roblox_mesh;
|
||||
use crate::texture::{RenderConfigs,Texture};
|
||||
use strafesnet_common::model::{MeshId,RenderConfig,RenderConfigId,TextureId};
|
||||
use crate::rbxassetid::RobloxAssetId;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderConfigLoader{
|
||||
texture_count:u32,
|
||||
render_configs:Vec<RenderConfig>,
|
||||
render_config_id_from_asset_id:HashMap<Option<RobloxAssetId>,RenderConfigId>,
|
||||
}
|
||||
|
||||
impl RenderConfigLoader{
|
||||
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
|
||||
let render_id=RenderConfigId::new(self.render_config_id_from_asset_id.len() as u32);
|
||||
let index=name.and_then(|name|{
|
||||
match name.parse::<RobloxAssetId>(){
|
||||
Ok(asset_id)=>Some(asset_id),
|
||||
Err(e)=>{
|
||||
println!("Failed to parse AssetId: {e}");
|
||||
None
|
||||
},
|
||||
}
|
||||
});
|
||||
*self.render_config_id_from_asset_id.entry(index).or_insert_with(||{
|
||||
//create the render config.
|
||||
let render_config=if name.is_some(){
|
||||
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
||||
self.texture_count+=1;
|
||||
render_config
|
||||
}else{
|
||||
RenderConfig::default()
|
||||
};
|
||||
self.render_configs.push(render_config);
|
||||
render_id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MeshLoader{
|
||||
mesh_id_from_asset_id:HashMap<Option<RobloxAssetId>,MeshId>,
|
||||
}
|
||||
|
||||
impl MeshLoader{
|
||||
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
|
||||
let mesh_id=MeshId::new(self.mesh_id_from_asset_id.len() as u32);
|
||||
let index=match name.parse::<RobloxAssetId>(){
|
||||
Ok(asset_id)=>Some(asset_id),
|
||||
Err(e)=>{
|
||||
println!("Failed to parse AssetId: {e}");
|
||||
None
|
||||
},
|
||||
};
|
||||
*self.mesh_id_from_asset_id.entry(index).or_insert(mesh_id)
|
||||
}
|
||||
pub fn load_meshes(&mut self)->Result<roblox_mesh::Meshes,std::io::Error>{
|
||||
let mut mesh_data=vec![None;self.mesh_id_from_asset_id.len()];
|
||||
for (asset_id_option,mesh_id) in &self.mesh_id_from_asset_id{
|
||||
if let Some(asset_id)=asset_id_option{
|
||||
if let Ok(mut file)=std::fs::File::open(format!("meshes/{}",asset_id.0)){
|
||||
//TODO: parallel
|
||||
let mut data=Vec::<u8>::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
mesh_data[mesh_id.get() as usize]=Some(roblox_mesh::RobloxMeshData::new(data));
|
||||
}else{
|
||||
println!("[roblox_legacy] no mesh name={}",asset_id.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(roblox_mesh::Meshes::new(mesh_data))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Loader{
|
||||
render_config_loader:RenderConfigLoader,
|
||||
mesh_loader:MeshLoader,
|
||||
}
|
||||
impl Loader{
|
||||
pub fn new()->Self{
|
||||
Self{
|
||||
render_config_loader:RenderConfigLoader::default(),
|
||||
mesh_loader:MeshLoader::default(),
|
||||
}
|
||||
}
|
||||
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
|
||||
(&mut self.render_config_loader,&mut self.mesh_loader)
|
||||
}
|
||||
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
|
||||
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
|
||||
for (asset_id_option,render_config_id) in self.render_config_loader.render_config_id_from_asset_id{
|
||||
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
|
||||
if let (Some(asset_id),Some(texture_id))=(asset_id_option,render_config.texture){
|
||||
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",asset_id.0)){
|
||||
//TODO: parallel
|
||||
let mut data=Vec::<u8>::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
|
||||
}else{
|
||||
//texture failed to load
|
||||
render_config.texture=None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(RenderConfigs::new(
|
||||
sorted_textures,
|
||||
self.render_config_loader.render_configs,
|
||||
))
|
||||
}
|
||||
}
|
30
lib/deferred_loader/src/roblox_mesh.rs
Normal file
30
lib/deferred_loader/src/roblox_mesh.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use strafesnet_common::model::MeshId;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RobloxMeshData(Vec<u8>);
|
||||
impl RobloxMeshData{
|
||||
pub(crate) fn new(data:Vec<u8>)->Self{
|
||||
Self(data)
|
||||
}
|
||||
pub fn get(self)->Vec<u8>{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
pub struct Meshes{
|
||||
meshes:Vec<Option<RobloxMeshData>>,
|
||||
}
|
||||
impl Meshes{
|
||||
pub(crate) const fn new(meshes:Vec<Option<RobloxMeshData>>)->Self{
|
||||
Self{
|
||||
meshes,
|
||||
}
|
||||
}
|
||||
pub fn get_texture(&self,texture_id:MeshId)->Option<&RobloxMeshData>{
|
||||
self.meshes.get(texture_id.get() as usize)?.as_ref()
|
||||
}
|
||||
pub fn into_iter(self)->impl Iterator<Item=(MeshId,RobloxMeshData)>{
|
||||
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
||||
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
||||
)
|
||||
}
|
||||
}
|
102
lib/deferred_loader/src/source_legacy.rs
Normal file
102
lib/deferred_loader/src/source_legacy.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use std::io::Read;
|
||||
use std::collections::HashMap;
|
||||
use crate::valve_mesh;
|
||||
use crate::texture::{Texture,RenderConfigs};
|
||||
use strafesnet_common::model::{MeshId,TextureId,RenderConfig,RenderConfigId};
|
||||
|
||||
pub struct RenderConfigLoader{
|
||||
texture_count:u32,
|
||||
render_configs:Vec<RenderConfig>,
|
||||
texture_paths:HashMap<Option<Box<str>>,RenderConfigId>,
|
||||
}
|
||||
impl RenderConfigLoader{
|
||||
pub fn acquire_render_config_id(&mut self,name:Option<&str>)->RenderConfigId{
|
||||
let render_id=RenderConfigId::new(self.texture_paths.len() as u32);
|
||||
*self.texture_paths.entry(name.map(Into::into)).or_insert_with(||{
|
||||
//create the render config.
|
||||
let render_config=if name.is_some(){
|
||||
let render_config=RenderConfig::texture(TextureId::new(self.texture_count));
|
||||
self.texture_count+=1;
|
||||
render_config
|
||||
}else{
|
||||
RenderConfig::default()
|
||||
};
|
||||
self.render_configs.push(render_config);
|
||||
render_id
|
||||
})
|
||||
}
|
||||
}
|
||||
pub struct MeshLoader{
|
||||
mesh_paths:HashMap<Box<str>,MeshId>,
|
||||
}
|
||||
impl MeshLoader{
|
||||
pub fn acquire_mesh_id(&mut self,name:&str)->MeshId{
|
||||
let mesh_id=MeshId::new(self.mesh_paths.len() as u32);
|
||||
*self.mesh_paths.entry(name.into()).or_insert(mesh_id)
|
||||
}
|
||||
//load_meshes should look like load_textures
|
||||
pub fn load_meshes(&mut self,bsp:&vbsp::Bsp)->valve_mesh::Meshes{
|
||||
let mut mesh_data=vec![None;self.mesh_paths.len()];
|
||||
for (mesh_path,mesh_id) in &self.mesh_paths{
|
||||
let mesh_path_lower=mesh_path.to_lowercase();
|
||||
//.mdl, .vvd, .dx90.vtx
|
||||
let path=std::path::PathBuf::from(mesh_path_lower.as_str());
|
||||
let mut vvd_path=path.clone();
|
||||
let mut vtx_path=path.clone();
|
||||
vvd_path.set_extension("vvd");
|
||||
vtx_path.set_extension("dx90.vtx");
|
||||
match (bsp.pack.get(mesh_path_lower.as_str()),bsp.pack.get(vvd_path.as_os_str().to_str().unwrap()),bsp.pack.get(vtx_path.as_os_str().to_str().unwrap())){
|
||||
(Ok(Some(mdl_file)),Ok(Some(vvd_file)),Ok(Some(vtx_file)))=>{
|
||||
mesh_data[mesh_id.get() as usize]=Some(valve_mesh::ModelData{
|
||||
mdl:valve_mesh::MdlData::new(mdl_file),
|
||||
vtx:valve_mesh::VtxData::new(vtx_file),
|
||||
vvd:valve_mesh::VvdData::new(vvd_file),
|
||||
});
|
||||
},
|
||||
_=>println!("no model name={}",mesh_path),
|
||||
}
|
||||
}
|
||||
valve_mesh::Meshes::new(mesh_data)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Loader{
|
||||
render_config_loader:RenderConfigLoader,
|
||||
mesh_loader:MeshLoader,
|
||||
}
|
||||
impl Loader{
|
||||
pub fn new()->Self{
|
||||
Self{
|
||||
render_config_loader:RenderConfigLoader{
|
||||
texture_count:0,
|
||||
texture_paths:HashMap::new(),
|
||||
render_configs:Vec::new(),
|
||||
},
|
||||
mesh_loader:MeshLoader{mesh_paths:HashMap::new()},
|
||||
}
|
||||
}
|
||||
pub fn get_inner_mut(&mut self)->(&mut RenderConfigLoader,&mut MeshLoader){
|
||||
(&mut self.render_config_loader,&mut self.mesh_loader)
|
||||
}
|
||||
pub fn into_render_configs(mut self)->Result<RenderConfigs,std::io::Error>{
|
||||
let mut sorted_textures=vec![None;self.render_config_loader.texture_count as usize];
|
||||
for (texture_path,render_config_id) in self.render_config_loader.texture_paths{
|
||||
let render_config=self.render_config_loader.render_configs.get_mut(render_config_id.get() as usize).unwrap();
|
||||
if let (Some(texture_path),Some(texture_id))=(texture_path,render_config.texture){
|
||||
if let Ok(mut file)=std::fs::File::open(format!("textures/{}.dds",texture_path)){
|
||||
//TODO: parallel
|
||||
let mut data=Vec::<u8>::new();
|
||||
file.read_to_end(&mut data)?;
|
||||
sorted_textures[texture_id.get() as usize]=Some(Texture::ImageDDS(data));
|
||||
}else{
|
||||
//texture failed to load
|
||||
render_config.texture=None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(RenderConfigs::new(
|
||||
sorted_textures,
|
||||
self.render_config_loader.render_configs,
|
||||
))
|
||||
}
|
||||
}
|
39
lib/deferred_loader/src/texture.rs
Normal file
39
lib/deferred_loader/src/texture.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use strafesnet_common::model::{TextureId,RenderConfigId,RenderConfig};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Texture{
|
||||
ImageDDS(Vec<u8>),
|
||||
}
|
||||
impl AsRef<[u8]> for Texture{
|
||||
fn as_ref(&self)->&[u8]{
|
||||
match self{
|
||||
Texture::ImageDDS(data)=>data.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RenderConfigs{
|
||||
textures:Vec<Option<Texture>>,
|
||||
render_configs:Vec<RenderConfig>,
|
||||
}
|
||||
impl RenderConfigs{
|
||||
pub(crate) const fn new(textures:Vec<Option<Texture>>,render_configs:Vec<RenderConfig>)->Self{
|
||||
Self{
|
||||
textures,
|
||||
render_configs,
|
||||
}
|
||||
}
|
||||
pub fn consume(self)->(
|
||||
impl Iterator<Item=(TextureId,Texture)>,
|
||||
impl Iterator<Item=(RenderConfigId,RenderConfig)>
|
||||
){
|
||||
(
|
||||
self.textures.into_iter().enumerate().filter_map(|(texture_id,maybe_texture)|
|
||||
maybe_texture.map(|texture|(TextureId::new(texture_id as u32),texture))
|
||||
),
|
||||
self.render_configs.into_iter().enumerate().map(|(render_id,render)|
|
||||
(RenderConfigId::new(render_id as u32),render)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
60
lib/deferred_loader/src/valve_mesh.rs
Normal file
60
lib/deferred_loader/src/valve_mesh.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use strafesnet_common::model::MeshId;
|
||||
|
||||
//duplicate this code for now
|
||||
#[derive(Clone)]
|
||||
pub struct MdlData(Vec<u8>);
|
||||
impl MdlData{
|
||||
pub const fn new(value:Vec<u8>)->Self{
|
||||
Self(value)
|
||||
}
|
||||
pub fn get(self)->Vec<u8>{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct VtxData(Vec<u8>);
|
||||
impl VtxData{
|
||||
pub const fn new(value:Vec<u8>)->Self{
|
||||
Self(value)
|
||||
}
|
||||
pub fn get(self)->Vec<u8>{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
#[derive(Clone)]
|
||||
pub struct VvdData(Vec<u8>);
|
||||
impl VvdData{
|
||||
pub const fn new(value:Vec<u8>)->Self{
|
||||
Self(value)
|
||||
}
|
||||
pub fn get(self)->Vec<u8>{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ModelData{
|
||||
pub mdl:MdlData,
|
||||
pub vtx:VtxData,
|
||||
pub vvd:VvdData,
|
||||
}
|
||||
|
||||
//meshes is more prone to failure
|
||||
pub struct Meshes{
|
||||
meshes:Vec<Option<ModelData>>,
|
||||
}
|
||||
impl Meshes{
|
||||
pub(crate) const fn new(meshes:Vec<Option<ModelData>>)->Self{
|
||||
Self{
|
||||
meshes,
|
||||
}
|
||||
}
|
||||
pub fn get_texture(&self,texture_id:MeshId)->Option<&ModelData>{
|
||||
self.meshes.get(texture_id.get() as usize)?.as_ref()
|
||||
}
|
||||
pub fn into_iter(self)->impl Iterator<Item=(MeshId,ModelData)>{
|
||||
self.meshes.into_iter().enumerate().filter_map(|(mesh_id,maybe_mesh)|
|
||||
maybe_mesh.map(|mesh|(MeshId::new(mesh_id as u32),mesh))
|
||||
)
|
||||
}
|
||||
}
|
1
lib/fixed_wide/.gitignore
vendored
Normal file
1
lib/fixed_wide/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
20
lib/fixed_wide/Cargo.toml
Normal file
20
lib/fixed_wide/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "fixed_wide"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Fixed point numbers with optional widening Mul operator."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[features]
|
||||
default=[]
|
||||
deferred-division=["dep:ratio_ops"]
|
||||
wide-mul=[]
|
||||
zeroes=["dep:arrayvec"]
|
||||
|
||||
[dependencies]
|
||||
bnum = "0.12.0"
|
||||
arrayvec = { version = "0.7.6", optional = true }
|
||||
paste = "1.0.15"
|
||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
176
lib/fixed_wide/LICENSE-APACHE
Normal file
176
lib/fixed_wide/LICENSE-APACHE
Normal file
@ -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
|
23
lib/fixed_wide/LICENSE-MIT
Normal file
23
lib/fixed_wide/LICENSE-MIT
Normal file
@ -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.
|
1204
lib/fixed_wide/src/fixed.rs
Normal file
1204
lib/fixed_wide/src/fixed.rs
Normal file
File diff suppressed because it is too large
Load Diff
8
lib/fixed_wide/src/lib.rs
Normal file
8
lib/fixed_wide/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub mod fixed;
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature = "zeroes")]
|
||||
pub mod zeroes;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
230
lib/fixed_wide/src/tests.rs
Normal file
230
lib/fixed_wide/src/tests.rs
Normal file
@ -0,0 +1,230 @@
|
||||
use crate::types::I256F256;
|
||||
use crate::types::I32F32;
|
||||
|
||||
#[test]
|
||||
fn you_can_add_numbers() {
|
||||
let a = I256F256::from((3i128 * 2).pow(4));
|
||||
assert_eq!(a + a, I256F256::from((3i128 * 2).pow(4) * 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_f32() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let f: f32 = a.into();
|
||||
assert_eq!(f, 0.25f32);
|
||||
let f: f32 = (-a).into();
|
||||
assert_eq!(f, -0.25f32);
|
||||
let a = I256F256::from(0);
|
||||
let f: f32 = (-a).into();
|
||||
assert_eq!(f, 0f32);
|
||||
let a = I256F256::from(237946589723468975i64) << 16;
|
||||
let f: f32 = a.into();
|
||||
assert_eq!(f, 237946589723468975f32 * 2.0f32.powi(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_f64() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let f: f64 = a.into();
|
||||
assert_eq!(f, 0.25f64);
|
||||
let f: f64 = (-a).into();
|
||||
assert_eq!(f, -0.25f64);
|
||||
let a = I256F256::from(0);
|
||||
let f: f64 = (-a).into();
|
||||
assert_eq!(f, 0f64);
|
||||
let a = I256F256::from(237946589723468975i64) << 16;
|
||||
let f: f64 = a.into();
|
||||
assert_eq!(f, 237946589723468975f64 * 2.0f64.powi(16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_f32() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let b: Result<I256F256, _> = 0.25f32.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(-1) >> 2;
|
||||
let b: Result<I256F256, _> = (-0.25f32).try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0);
|
||||
let b: Result<I256F256, _> = 0.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0b101011110101001010101010000000000000000000000000000i64) << 16;
|
||||
let b: Result<I256F256, _> = (0b101011110101001010101010000000000000000000000000000u64 as f32
|
||||
* 2.0f32.powi(16))
|
||||
.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
//I32F32::MAX into f32 is truncated into this value
|
||||
let a = I32F32::raw(0b111111111111111111111111000000000000000000000000000000000000000i64);
|
||||
let b: Result<I32F32, _> = Into::<f32>::into(I32F32::MAX).try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
//I32F32::MIN hits a special case since it's not representable as a positive signed integer
|
||||
//TODO: don't return an overflow because this is technically possible
|
||||
let a = I32F32::MIN;
|
||||
let b: Result<I32F32, _> = Into::<f32>::into(I32F32::MIN).try_into();
|
||||
assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
//16 is within the 24 bits of float precision
|
||||
let b: Result<I32F32, _> = Into::<f32>::into(-I32F32::MIN.fix_2()).try_into();
|
||||
assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Overflow));
|
||||
let b: Result<I32F32, _> = f32::MIN_POSITIVE.try_into();
|
||||
assert_eq!(b, Err(crate::fixed::FixedFromFloatError::Underflow));
|
||||
//test many cases
|
||||
for i in 0..64 {
|
||||
let a = crate::fixed::Fixed::<2, 64>::raw_digit(
|
||||
0b111111111111111111111111000000000000000000000000000000000000000i64,
|
||||
) << i;
|
||||
let f: f32 = a.into();
|
||||
let b: Result<crate::fixed::Fixed<2, 64>, _> = f.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_f64() {
|
||||
let a = I256F256::from(1) >> 2;
|
||||
let b: Result<I256F256, _> = 0.25f64.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(-1) >> 2;
|
||||
let b: Result<I256F256, _> = (-0.25f64).try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0);
|
||||
let b: Result<I256F256, _> = 0.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
let a = I256F256::from(0b101011110101001010101010000000000000000000000000000i64) << 16;
|
||||
let b: Result<I256F256, _> = (0b101011110101001010101010000000000000000000000000000u64 as f64
|
||||
* 2.0f64.powi(16))
|
||||
.try_into();
|
||||
assert_eq!(b, Ok(a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn you_can_shr_numbers() {
|
||||
let a = I32F32::from(4);
|
||||
assert_eq!(a >> 1, I32F32::from(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_mul() {
|
||||
let a = I32F32::ONE;
|
||||
let aa = a.wide_mul_1_1(a);
|
||||
assert_eq!(aa, crate::types::I64F64::ONE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_div() {
|
||||
let a = I32F32::ONE * 4;
|
||||
let b = I32F32::ONE * 2;
|
||||
let wide_a = a.wide_mul_1_1(I32F32::ONE);
|
||||
let wide_b = b.wide_mul_1_1(I32F32::ONE);
|
||||
let ab = a.wide_div_1_1(b);
|
||||
assert_eq!(ab, crate::types::I64F64::ONE * 2);
|
||||
let wab = wide_a.wide_div_2_1(b);
|
||||
assert_eq!(wab, crate::fixed::Fixed::<3, 96>::ONE * 2);
|
||||
let awb = a.wide_div_1_2(wide_b);
|
||||
assert_eq!(awb, crate::fixed::Fixed::<3, 96>::ONE * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wide_mul_repeated() {
|
||||
let a = I32F32::from(2);
|
||||
let b = I32F32::from(3);
|
||||
|
||||
let w1 = a.wide_mul_1_1(b);
|
||||
let w2 = w1.wide_mul_2_2(w1);
|
||||
let w3 = w2.wide_mul_4_4(w2);
|
||||
|
||||
assert_eq!(w3, I256F256::from((3i128 * 2).pow(4)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bint() {
|
||||
let a = I32F32::ONE;
|
||||
assert_eq!(a * 2, I32F32::from(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fix() {
|
||||
assert_eq!(I32F32::ONE.fix_8(), I256F256::ONE);
|
||||
assert_eq!(I32F32::ONE, I256F256::ONE.fix_1());
|
||||
assert_eq!(I32F32::NEG_ONE.fix_8(), I256F256::NEG_ONE);
|
||||
assert_eq!(I32F32::NEG_ONE, I256F256::NEG_ONE.fix_1());
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt() {
|
||||
let a = I32F32::ONE * 4;
|
||||
assert_eq!(a.sqrt(), I32F32::from(2));
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_zero() {
|
||||
let a = I32F32::ZERO;
|
||||
assert_eq!(a.sqrt(), I32F32::ZERO);
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_low() {
|
||||
let a = I32F32::HALF;
|
||||
let b = a.fixed_mul(a);
|
||||
assert_eq!(b.sqrt(), a);
|
||||
}
|
||||
fn find_equiv_sqrt_via_f64(n: I32F32) -> I32F32 {
|
||||
//GIMME THEM BITS BOY
|
||||
let &[bits] = n.to_bits().to_bits().digits();
|
||||
let ibits = bits as i64;
|
||||
let f = (ibits as f64) / ((1u64 << 32) as f64);
|
||||
let f_ans = f.sqrt();
|
||||
let i = (f_ans * ((1u64 << 32) as f64)) as i64;
|
||||
let r = I32F32::from_bits(bnum::BInt::<1>::from(i));
|
||||
//mimic the behaviour of the algorithm,
|
||||
//return the result if it truncates to the exact answer
|
||||
if (r + I32F32::EPSILON).wide_mul_1_1(r + I32F32::EPSILON) == n.wide_mul_1_1(I32F32::ONE) {
|
||||
return r + I32F32::EPSILON;
|
||||
}
|
||||
if (r - I32F32::EPSILON).wide_mul_1_1(r - I32F32::EPSILON) == n.wide_mul_1_1(I32F32::ONE) {
|
||||
return r - I32F32::EPSILON;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
fn test_exact(n: I32F32) {
|
||||
assert_eq!(n.sqrt(), find_equiv_sqrt_via_f64(n));
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_exact() {
|
||||
//43
|
||||
for i in 0..((i64::MAX as f32).ln() as u32) {
|
||||
let n = I32F32::from_bits(bnum::BInt::<1>::from((i as f32).exp() as i64));
|
||||
test_exact(n);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_sqrt_max() {
|
||||
let a = I32F32::MAX;
|
||||
test_exact(a);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(feature = "zeroes", not(feature = "deferred-division")))]
|
||||
fn test_zeroes_normal() {
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes = I32F32::zeroes2(I32F32::NEG_ONE, I32F32::ZERO, I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE, I32F32::ONE])
|
||||
);
|
||||
let zeroes = I32F32::zeroes2(I32F32::NEG_ONE * 3, I32F32::ONE * 2, I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([I32F32::NEG_ONE * 3, I32F32::ONE])
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
#[cfg(all(feature = "zeroes", feature = "deferred-division"))]
|
||||
fn test_zeroes_deferred_division() {
|
||||
// (x-1)*(x+1)
|
||||
// x^2-1
|
||||
let zeroes = I32F32::zeroes2(I32F32::NEG_ONE, I32F32::ZERO, I32F32::ONE);
|
||||
assert_eq!(
|
||||
zeroes,
|
||||
arrayvec::ArrayVec::from_iter([
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE * 2, I32F32::NEG_ONE * 2),
|
||||
ratio_ops::ratio::Ratio::new(I32F32::ONE * 2, I32F32::ONE * 2),
|
||||
])
|
||||
);
|
||||
}
|
4
lib/fixed_wide/src/types.rs
Normal file
4
lib/fixed_wide/src/types.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub type I32F32 = crate::fixed::Fixed<1, 32>;
|
||||
pub type I64F64 = crate::fixed::Fixed<2, 64>;
|
||||
pub type I128F128 = crate::fixed::Fixed<4, 128>;
|
||||
pub type I256F256 = crate::fixed::Fixed<8, 256>;
|
71
lib/fixed_wide/src/zeroes.rs
Normal file
71
lib/fixed_wide/src/zeroes.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use crate::fixed::Fixed;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use std::cmp::Ordering;
|
||||
macro_rules! impl_zeroes {
|
||||
($n:expr) => {
|
||||
impl Fixed<$n, { $n * 32 }> {
|
||||
#[inline]
|
||||
pub fn zeroes2(
|
||||
a0: Self,
|
||||
a1: Self,
|
||||
a2: Self,
|
||||
) -> ArrayVec<<Self as core::ops::Div>::Output, 2> {
|
||||
let a2pos = match a2.cmp(&Self::ZERO) {
|
||||
Ordering::Greater => true,
|
||||
Ordering::Equal => {
|
||||
return ArrayVec::from_iter(Self::zeroes1(a0, a1).into_iter())
|
||||
}
|
||||
Ordering::Less => false,
|
||||
};
|
||||
let radicand = a1 * a1 - a2 * a0 * 4;
|
||||
match radicand.cmp(&<Self as core::ops::Mul>::Output::ZERO) {
|
||||
Ordering::Greater => {
|
||||
paste::item! {
|
||||
let planar_radicand=radicand.sqrt().[<fix_ $n>]();
|
||||
}
|
||||
//sort roots ascending and avoid taking the difference of large numbers
|
||||
let zeroes = match (a2pos, Self::ZERO < a1) {
|
||||
(true, true) => [
|
||||
(-a1 - planar_radicand) / (a2 * 2),
|
||||
(a0 * 2) / (-a1 - planar_radicand),
|
||||
],
|
||||
(true, false) => [
|
||||
(a0 * 2) / (-a1 + planar_radicand),
|
||||
(-a1 + planar_radicand) / (a2 * 2),
|
||||
],
|
||||
(false, true) => [
|
||||
(a0 * 2) / (-a1 - planar_radicand),
|
||||
(-a1 - planar_radicand) / (a2 * 2),
|
||||
],
|
||||
(false, false) => [
|
||||
(-a1 + planar_radicand) / (a2 * 2),
|
||||
(a0 * 2) / (-a1 + planar_radicand),
|
||||
],
|
||||
};
|
||||
ArrayVec::from_iter(zeroes)
|
||||
}
|
||||
Ordering::Equal => ArrayVec::from_iter([(a1) / (a2 * -2)]),
|
||||
Ordering::Less => ArrayVec::new_const(),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
pub fn zeroes1(a0: Self, a1: Self) -> ArrayVec<<Self as core::ops::Div>::Output, 1> {
|
||||
if a1 == Self::ZERO {
|
||||
ArrayVec::new_const()
|
||||
} else {
|
||||
ArrayVec::from_iter([(-a0) / (a1)])
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_zeroes!(1);
|
||||
impl_zeroes!(2);
|
||||
impl_zeroes!(3);
|
||||
impl_zeroes!(4);
|
||||
//sqrt doubles twice!
|
||||
//impl_zeroes!(5);
|
||||
//impl_zeroes!(6);
|
||||
//impl_zeroes!(7);
|
||||
//impl_zeroes!(8);
|
1
lib/linear_ops/.gitignore
vendored
Normal file
1
lib/linear_ops/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
22
lib/linear_ops/Cargo.toml
Normal file
22
lib/linear_ops/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "linear_ops"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Vector/Matrix operations using trait bounds."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[features]
|
||||
default=["named-fields","fixed-wide"]
|
||||
named-fields=[]
|
||||
fixed-wide=["dep:fixed_wide","dep:paste"]
|
||||
deferred-division=["dep:ratio_ops"]
|
||||
|
||||
[dependencies]
|
||||
ratio_ops = { path = "../ratio_ops", registry = "strafesnet", optional = true }
|
||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", optional = true }
|
||||
paste = { version = "1.0.15", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
fixed_wide = { path = "../fixed_wide", registry = "strafesnet", features = ["wide-mul"] }
|
176
lib/linear_ops/LICENSE-APACHE
Normal file
176
lib/linear_ops/LICENSE-APACHE
Normal file
@ -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
|
23
lib/linear_ops/LICENSE-MIT
Normal file
23
lib/linear_ops/LICENSE-MIT
Normal file
@ -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.
|
10
lib/linear_ops/src/lib.rs
Normal file
10
lib/linear_ops/src/lib.rs
Normal file
@ -0,0 +1,10 @@
|
||||
mod macros;
|
||||
pub mod matrix;
|
||||
pub mod types;
|
||||
pub mod vector;
|
||||
|
||||
#[cfg(feature = "named-fields")]
|
||||
mod named;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
1
lib/linear_ops/src/macros/common.rs
Normal file
1
lib/linear_ops/src/macros/common.rs
Normal file
@ -0,0 +1 @@
|
||||
|
328
lib/linear_ops/src/macros/fixed_wide.rs
Normal file
328
lib/linear_ops/src/macros/fixed_wide.rs
Normal file
@ -0,0 +1,328 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fixed_wide_vector_not_const_generic {
|
||||
(
|
||||
(),
|
||||
$n:expr
|
||||
) => {
|
||||
impl<const N: usize> Vector<N, fixed_wide::fixed::Fixed<$n, { $n * 32 }>> {
|
||||
#[inline]
|
||||
pub fn length(
|
||||
self,
|
||||
) -> <fixed_wide::fixed::Fixed<$n, { $n * 32 }> as core::ops::Mul>::Output {
|
||||
self.length_squared().sqrt_unchecked()
|
||||
}
|
||||
#[inline]
|
||||
pub fn with_length<U, V>(
|
||||
self,
|
||||
length: U,
|
||||
) -> <Vector<N, V> as core::ops::Div<
|
||||
<fixed_wide::fixed::Fixed<$n, { $n * 32 }> as core::ops::Mul>::Output,
|
||||
>>::Output
|
||||
where
|
||||
fixed_wide::fixed::Fixed<$n, { $n * 32 }>: core::ops::Mul<U, Output = V>,
|
||||
U: Copy,
|
||||
V: core::ops::Div<
|
||||
<fixed_wide::fixed::Fixed<$n, { $n * 32 }> as core::ops::Mul>::Output,
|
||||
>,
|
||||
{
|
||||
self * length / self.length()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! macro_4 {
|
||||
( $macro: ident, $any:tt ) => {
|
||||
$crate::macro_repeated!($macro, $any, 1, 2, 3, 4);
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fixed_wide_vector {
|
||||
() => {
|
||||
$crate::macro_4!(impl_fixed_wide_vector_not_const_generic, ());
|
||||
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||
$crate::macro_repeated!(
|
||||
impl_fix_not_const_generic,
|
||||
(),
|
||||
(1, 1),
|
||||
(2, 1),
|
||||
(3, 1),
|
||||
(4, 1),
|
||||
(5, 1),
|
||||
(6, 1),
|
||||
(7, 1),
|
||||
(8, 1),
|
||||
(9, 1),
|
||||
(10, 1),
|
||||
(11, 1),
|
||||
(12, 1),
|
||||
(13, 1),
|
||||
(14, 1),
|
||||
(15, 1),
|
||||
(16, 1),
|
||||
(1, 2),
|
||||
(2, 2),
|
||||
(3, 2),
|
||||
(4, 2),
|
||||
(5, 2),
|
||||
(6, 2),
|
||||
(7, 2),
|
||||
(8, 2),
|
||||
(9, 2),
|
||||
(10, 2),
|
||||
(11, 2),
|
||||
(12, 2),
|
||||
(13, 2),
|
||||
(14, 2),
|
||||
(15, 2),
|
||||
(16, 2),
|
||||
(1, 3),
|
||||
(2, 3),
|
||||
(3, 3),
|
||||
(4, 3),
|
||||
(5, 3),
|
||||
(6, 3),
|
||||
(7, 3),
|
||||
(8, 3),
|
||||
(9, 3),
|
||||
(10, 3),
|
||||
(11, 3),
|
||||
(12, 3),
|
||||
(13, 3),
|
||||
(14, 3),
|
||||
(15, 3),
|
||||
(16, 3),
|
||||
(1, 4),
|
||||
(2, 4),
|
||||
(3, 4),
|
||||
(4, 4),
|
||||
(5, 4),
|
||||
(6, 4),
|
||||
(7, 4),
|
||||
(8, 4),
|
||||
(9, 4),
|
||||
(10, 4),
|
||||
(11, 4),
|
||||
(12, 4),
|
||||
(13, 4),
|
||||
(14, 4),
|
||||
(15, 4),
|
||||
(16, 4),
|
||||
(1, 5),
|
||||
(2, 5),
|
||||
(3, 5),
|
||||
(4, 5),
|
||||
(5, 5),
|
||||
(6, 5),
|
||||
(7, 5),
|
||||
(8, 5),
|
||||
(9, 5),
|
||||
(10, 5),
|
||||
(11, 5),
|
||||
(12, 5),
|
||||
(13, 5),
|
||||
(14, 5),
|
||||
(15, 5),
|
||||
(16, 5),
|
||||
(1, 6),
|
||||
(2, 6),
|
||||
(3, 6),
|
||||
(4, 6),
|
||||
(5, 6),
|
||||
(6, 6),
|
||||
(7, 6),
|
||||
(8, 6),
|
||||
(9, 6),
|
||||
(10, 6),
|
||||
(11, 6),
|
||||
(12, 6),
|
||||
(13, 6),
|
||||
(14, 6),
|
||||
(15, 6),
|
||||
(16, 6),
|
||||
(1, 7),
|
||||
(2, 7),
|
||||
(3, 7),
|
||||
(4, 7),
|
||||
(5, 7),
|
||||
(6, 7),
|
||||
(7, 7),
|
||||
(8, 7),
|
||||
(9, 7),
|
||||
(10, 7),
|
||||
(11, 7),
|
||||
(12, 7),
|
||||
(13, 7),
|
||||
(14, 7),
|
||||
(15, 7),
|
||||
(16, 7),
|
||||
(1, 8),
|
||||
(2, 8),
|
||||
(3, 8),
|
||||
(4, 8),
|
||||
(5, 8),
|
||||
(6, 8),
|
||||
(7, 8),
|
||||
(8, 8),
|
||||
(9, 8),
|
||||
(10, 8),
|
||||
(11, 8),
|
||||
(12, 8),
|
||||
(13, 8),
|
||||
(14, 8),
|
||||
(15, 8),
|
||||
(16, 8),
|
||||
(1, 9),
|
||||
(2, 9),
|
||||
(3, 9),
|
||||
(4, 9),
|
||||
(5, 9),
|
||||
(6, 9),
|
||||
(7, 9),
|
||||
(8, 9),
|
||||
(9, 9),
|
||||
(10, 9),
|
||||
(11, 9),
|
||||
(12, 9),
|
||||
(13, 9),
|
||||
(14, 9),
|
||||
(15, 9),
|
||||
(16, 9),
|
||||
(1, 10),
|
||||
(2, 10),
|
||||
(3, 10),
|
||||
(4, 10),
|
||||
(5, 10),
|
||||
(6, 10),
|
||||
(7, 10),
|
||||
(8, 10),
|
||||
(9, 10),
|
||||
(10, 10),
|
||||
(11, 10),
|
||||
(12, 10),
|
||||
(13, 10),
|
||||
(14, 10),
|
||||
(15, 10),
|
||||
(16, 10),
|
||||
(1, 11),
|
||||
(2, 11),
|
||||
(3, 11),
|
||||
(4, 11),
|
||||
(5, 11),
|
||||
(6, 11),
|
||||
(7, 11),
|
||||
(8, 11),
|
||||
(9, 11),
|
||||
(10, 11),
|
||||
(11, 11),
|
||||
(12, 11),
|
||||
(13, 11),
|
||||
(14, 11),
|
||||
(15, 11),
|
||||
(16, 11),
|
||||
(1, 12),
|
||||
(2, 12),
|
||||
(3, 12),
|
||||
(4, 12),
|
||||
(5, 12),
|
||||
(6, 12),
|
||||
(7, 12),
|
||||
(8, 12),
|
||||
(9, 12),
|
||||
(10, 12),
|
||||
(11, 12),
|
||||
(12, 12),
|
||||
(13, 12),
|
||||
(14, 12),
|
||||
(15, 12),
|
||||
(16, 12),
|
||||
(1, 13),
|
||||
(2, 13),
|
||||
(3, 13),
|
||||
(4, 13),
|
||||
(5, 13),
|
||||
(6, 13),
|
||||
(7, 13),
|
||||
(8, 13),
|
||||
(9, 13),
|
||||
(10, 13),
|
||||
(11, 13),
|
||||
(12, 13),
|
||||
(13, 13),
|
||||
(14, 13),
|
||||
(15, 13),
|
||||
(16, 13),
|
||||
(1, 14),
|
||||
(2, 14),
|
||||
(3, 14),
|
||||
(4, 14),
|
||||
(5, 14),
|
||||
(6, 14),
|
||||
(7, 14),
|
||||
(8, 14),
|
||||
(9, 14),
|
||||
(10, 14),
|
||||
(11, 14),
|
||||
(12, 14),
|
||||
(13, 14),
|
||||
(14, 14),
|
||||
(15, 14),
|
||||
(16, 14),
|
||||
(1, 15),
|
||||
(2, 15),
|
||||
(3, 15),
|
||||
(4, 15),
|
||||
(5, 15),
|
||||
(6, 15),
|
||||
(7, 15),
|
||||
(8, 15),
|
||||
(9, 15),
|
||||
(10, 15),
|
||||
(11, 15),
|
||||
(12, 15),
|
||||
(13, 15),
|
||||
(14, 15),
|
||||
(15, 15),
|
||||
(16, 15),
|
||||
(1, 16),
|
||||
(2, 16),
|
||||
(3, 16),
|
||||
(4, 16),
|
||||
(5, 16),
|
||||
(6, 16),
|
||||
(7, 16),
|
||||
(8, 16),
|
||||
(9, 16),
|
||||
(10, 16),
|
||||
(11, 16),
|
||||
(12, 16),
|
||||
(13, 16),
|
||||
(14, 16),
|
||||
(15, 16),
|
||||
(16, 16)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_fix_not_const_generic {
|
||||
(
|
||||
(),
|
||||
($lhs:expr,$rhs:expr)
|
||||
) => {
|
||||
impl<const N: usize> Vector<N, fixed_wide::fixed::Fixed<$lhs, { $lhs * 32 }>> {
|
||||
paste::item! {
|
||||
#[inline]
|
||||
pub fn [<fix_ $rhs>](self)->Vector<N,fixed_wide::fixed::Fixed<$rhs,{$rhs*32}>>{
|
||||
self.map(|t|t.[<fix_ $rhs>]())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
282
lib/linear_ops/src/macros/matrix.rs
Normal file
282
lib/linear_ops/src/macros/matrix.rs
Normal file
@ -0,0 +1,282 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix {
|
||||
() => {
|
||||
impl<const X: usize, const Y: usize, T> Matrix<X, Y, T> {
|
||||
#[inline(always)]
|
||||
pub const fn new(array: [[T; Y]; X]) -> Self {
|
||||
Self { array }
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self) -> [[T; Y]; X] {
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn from_cols(cols: [Vector<Y, T>; X]) -> Self {
|
||||
Matrix::new(cols.map(|col| col.array))
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F, U>(self, f: F) -> Matrix<X, Y, U>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
{
|
||||
Matrix::new(self.array.map(|inner| inner.map(&f)))
|
||||
}
|
||||
#[inline]
|
||||
pub fn transpose(self) -> Matrix<Y, X, T> {
|
||||
//how did I think of this
|
||||
let mut array_of_iterators = self.array.map(|axis| axis.into_iter());
|
||||
Matrix::new(core::array::from_fn(|_| {
|
||||
array_of_iterators
|
||||
.each_mut()
|
||||
.map(|iter| iter.next().unwrap())
|
||||
}))
|
||||
}
|
||||
#[inline]
|
||||
// old (list of rows) MatY<VecX>.MatX<VecZ> = MatY<VecZ>
|
||||
// new (list of columns) MatX<VecY>.MatZ<VecX> = MatZ<VecY>
|
||||
pub fn dot<const Z: usize, U, V>(self, rhs: Matrix<Z, X, U>) -> Matrix<Z, Y, V>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V> + Copy,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
let mut array_of_iterators = self.array.map(|axis| axis.into_iter().cycle());
|
||||
Matrix {
|
||||
array: rhs.array.map(|rhs_axis| {
|
||||
core::array::from_fn(|_| {
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs_axis.iter())
|
||||
.map(|(lhs_iter, &rhs_value)| lhs_iter.next().unwrap() * rhs_value)
|
||||
.sum()
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
// MatX<VecY>.VecY = VecX
|
||||
pub fn transform_vector<U, V>(self, rhs: Vector<X, U>) -> Vector<Y, V>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V>,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
let mut array_of_iterators = self.array.map(|axis| axis.into_iter());
|
||||
Vector::new(core::array::from_fn(|_| {
|
||||
array_of_iterators
|
||||
.iter_mut()
|
||||
.zip(rhs.array.iter())
|
||||
.map(|(lhs_iter, &rhs_value)| lhs_iter.next().unwrap() * rhs_value)
|
||||
.sum()
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl<const X: usize, const Y: usize, T> Matrix<X, Y, T>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value: T) -> Self {
|
||||
Self::new([[value; Y]; X])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize, T: Default> Default for Matrix<X, Y, T> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(core::array::from_fn(|_| {
|
||||
core::array::from_fn(|_| Default::default())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize, T: core::fmt::Display> core::fmt::Display
|
||||
for Matrix<X, Y, T>
|
||||
{
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
for col in &self.array[0..X] {
|
||||
core::write!(f, "\n")?;
|
||||
for elem in &col[0..Y - 1] {
|
||||
core::write!(f, "{}, ", elem)?;
|
||||
}
|
||||
// assume we will be using matrices of size 1x1 or greater
|
||||
core::write!(f, "{}", col.last().unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const X: usize, const Y: usize, const Z: usize, T, U, V>
|
||||
core::ops::Mul<Matrix<Z, X, U>> for Matrix<X, Y, T>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V> + Copy,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
type Output = Matrix<Z, Y, V>;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Matrix<Z, X, U>) -> Self::Output {
|
||||
self.dot(rhs)
|
||||
}
|
||||
}
|
||||
impl<const X: usize, const Y: usize, T, U, V> core::ops::Mul<Vector<X, U>>
|
||||
for Matrix<X, Y, T>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V>,
|
||||
V: core::iter::Sum,
|
||||
U: Copy,
|
||||
{
|
||||
type Output = Vector<Y, V>;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Vector<X, U>) -> Self::Output {
|
||||
self.transform_vector(rhs)
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "deferred-division")]
|
||||
$crate::impl_matrix_deferred_division!();
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_deferred_division {
|
||||
() => {
|
||||
impl<
|
||||
const X: usize,
|
||||
const Y: usize,
|
||||
T: ratio_ops::ratio::Divide<U, Output = V>,
|
||||
U: Copy,
|
||||
V,
|
||||
> ratio_ops::ratio::Divide<U> for Matrix<X, Y, T>
|
||||
{
|
||||
type Output = Matrix<X, Y, V>;
|
||||
#[inline]
|
||||
fn divide(self, rhs: U) -> Self::Output {
|
||||
self.map(|t| t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const X: usize, const Y: usize, T, U> core::ops::Div<U> for Matrix<X, Y, T> {
|
||||
type Output = ratio_ops::ratio::Ratio<Matrix<X, Y, T>, U>;
|
||||
#[inline]
|
||||
fn div(self, rhs: U) -> Self::Output {
|
||||
ratio_ops::ratio::Ratio::new(self, rhs)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_extend {
|
||||
( $x: expr, $y: expr ) => {
|
||||
impl<T> Matrix<$x, $y, T> {
|
||||
#[inline]
|
||||
pub fn extend_column(self, value: Vector<$y, T>) -> Matrix<{ $x + 1 }, $y, T> {
|
||||
let mut iter = self.array.into_iter().chain(core::iter::once(value.array));
|
||||
Matrix::new(core::array::from_fn(|_| iter.next().unwrap()))
|
||||
}
|
||||
#[inline]
|
||||
pub fn extend_row(self, value: Vector<$x, T>) -> Matrix<$x, { $y + 1 }, T> {
|
||||
let mut iter_rows = value.array.into_iter();
|
||||
Matrix::new(self.array.map(|axis| {
|
||||
let mut elements_iter = axis
|
||||
.into_iter()
|
||||
.chain(core::iter::once(iter_rows.next().unwrap()));
|
||||
core::array::from_fn(|_| elements_iter.next().unwrap())
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_named_fields_shape {
|
||||
(
|
||||
($struct_outer:ident, $size_outer: expr),
|
||||
($size_inner: expr)
|
||||
) => {
|
||||
impl<T> core::ops::Deref for Matrix<$size_outer, $size_inner, T> {
|
||||
type Target = $struct_outer<Vector<$size_inner, T>>;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { core::mem::transmute(&self.array) }
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Matrix<$size_outer, $size_inner, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { core::mem::transmute(&mut self.array) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_named_fields_shape_shim {
|
||||
(
|
||||
($($vector_info:tt),+),
|
||||
$matrix_info:tt
|
||||
) => {
|
||||
$crate::macro_repeated!(impl_matrix_named_fields_shape,$matrix_info,$($vector_info),+);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_named_fields {
|
||||
(
|
||||
($($matrix_info:tt),+),
|
||||
$vector_infos:tt
|
||||
) => {
|
||||
$crate::macro_repeated!(impl_matrix_named_fields_shape_shim,$vector_infos,$($matrix_info),+);
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_matrix_3x3 {
|
||||
() => {
|
||||
impl<T, T2, T3> Matrix<3, 3, T>
|
||||
where
|
||||
//cross
|
||||
T: core::ops::Mul<T, Output = T2> + Copy,
|
||||
T2: core::ops::Sub,
|
||||
//dot
|
||||
T: core::ops::Mul<<T2 as core::ops::Sub>::Output, Output = T3>,
|
||||
T3: core::iter::Sum,
|
||||
{
|
||||
pub fn det(self) -> T3 {
|
||||
self.x_axis.dot(self.y_axis.cross(self.z_axis))
|
||||
}
|
||||
}
|
||||
impl<T, T2> Matrix<3, 3, T>
|
||||
where
|
||||
T: core::ops::Mul<T, Output = T2> + Copy,
|
||||
T2: core::ops::Sub,
|
||||
{
|
||||
pub fn adjugate(self) -> Matrix<3, 3, <T2 as core::ops::Sub>::Output> {
|
||||
Matrix::new([
|
||||
[
|
||||
self.y_axis.y * self.z_axis.z - self.y_axis.z * self.z_axis.y,
|
||||
self.x_axis.z * self.z_axis.y - self.x_axis.y * self.z_axis.z,
|
||||
self.x_axis.y * self.y_axis.z - self.x_axis.z * self.y_axis.y,
|
||||
],
|
||||
[
|
||||
self.y_axis.z * self.z_axis.x - self.y_axis.x * self.z_axis.z,
|
||||
self.x_axis.x * self.z_axis.z - self.x_axis.z * self.z_axis.x,
|
||||
self.x_axis.z * self.y_axis.x - self.x_axis.x * self.y_axis.z,
|
||||
],
|
||||
[
|
||||
self.y_axis.x * self.z_axis.y - self.y_axis.y * self.z_axis.x,
|
||||
self.x_axis.y * self.z_axis.x - self.x_axis.x * self.z_axis.y,
|
||||
self.x_axis.x * self.y_axis.y - self.x_axis.y * self.y_axis.x,
|
||||
],
|
||||
])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
20
lib/linear_ops/src/macros/mod.rs
Normal file
20
lib/linear_ops/src/macros/mod.rs
Normal file
@ -0,0 +1,20 @@
|
||||
pub mod common;
|
||||
pub mod matrix;
|
||||
pub mod vector;
|
||||
|
||||
#[cfg(feature = "fixed-wide")]
|
||||
pub mod fixed_wide;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! macro_repeated{
|
||||
(
|
||||
$macro:ident,
|
||||
$any:tt,
|
||||
$($repeated:tt),*
|
||||
)=>{
|
||||
$(
|
||||
$crate::$macro!($any, $repeated);
|
||||
)*
|
||||
};
|
||||
}
|
368
lib/linear_ops/src/macros/vector.rs
Normal file
368
lib/linear_ops/src/macros/vector.rs
Normal file
@ -0,0 +1,368 @@
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector {
|
||||
() => {
|
||||
impl<const N: usize, T> Vector<N, T> {
|
||||
#[inline(always)]
|
||||
pub const fn new(array: [T; N]) -> Self {
|
||||
Self { array }
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn to_array(self) -> [T; N] {
|
||||
self.array
|
||||
}
|
||||
#[inline]
|
||||
pub fn map<F, U>(self, f: F) -> Vector<N, U>
|
||||
where
|
||||
F: Fn(T) -> U,
|
||||
{
|
||||
Vector::new(self.array.map(f))
|
||||
}
|
||||
#[inline]
|
||||
pub fn map_zip<F, U, V>(self, other: Vector<N, U>, f: F) -> Vector<N, V>
|
||||
where
|
||||
F: Fn((T, U)) -> V,
|
||||
{
|
||||
let mut iter = self.array.into_iter().zip(other.array);
|
||||
Vector::new(core::array::from_fn(|_| f(iter.next().unwrap())))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: Copy> Vector<N, T> {
|
||||
#[inline(always)]
|
||||
pub const fn from_value(value: T) -> Self {
|
||||
Self::new([value; N])
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: Default> Default for Vector<N, T> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(core::array::from_fn(|_| Default::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: core::fmt::Display> core::fmt::Display for Vector<N, T> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
|
||||
for elem in &self.array[0..N - 1] {
|
||||
core::write!(f, "{}, ", elem)?;
|
||||
}
|
||||
// assume we will be using vectors of length 1 or greater
|
||||
core::write!(f, "{}", self.array.last().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: Ord> Vector<N, T> {
|
||||
#[inline]
|
||||
pub fn min(self, rhs: Self) -> Self {
|
||||
self.map_zip(rhs, |(a, b)| a.min(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn max(self, rhs: Self) -> Self {
|
||||
self.map_zip(rhs, |(a, b)| a.max(b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn cmp(self, rhs: Self) -> Vector<N, core::cmp::Ordering> {
|
||||
self.map_zip(rhs, |(a, b)| a.cmp(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn lt(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.lt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn gt(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.gt(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn ge(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.ge(&b))
|
||||
}
|
||||
#[inline]
|
||||
pub fn le(self, rhs: Self) -> Vector<N, bool> {
|
||||
self.map_zip(rhs, |(a, b)| a.le(&b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Vector<N, bool> {
|
||||
#[inline]
|
||||
pub fn all(&self) -> bool {
|
||||
self.array == [true; N]
|
||||
}
|
||||
#[inline]
|
||||
pub fn any(&self) -> bool {
|
||||
self.array != [false; N]
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: core::ops::Neg<Output = V>, V> core::ops::Neg for Vector<N, T> {
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Vector::new(self.array.map(|t| -t))
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T> Vector<N, T> {
|
||||
#[inline]
|
||||
pub fn dot<U, V>(self, rhs: Vector<N, U>) -> V
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V>,
|
||||
V: core::iter::Sum,
|
||||
{
|
||||
self.array
|
||||
.into_iter()
|
||||
.zip(rhs.array)
|
||||
.map(|(a, b)| a * b)
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T, V> Vector<N, T>
|
||||
where
|
||||
T: core::ops::Mul<Output = V> + Copy,
|
||||
V: core::iter::Sum,
|
||||
{
|
||||
#[inline]
|
||||
pub fn length_squared(self) -> V {
|
||||
self.array.into_iter().map(|t| t * t).sum()
|
||||
}
|
||||
}
|
||||
|
||||
// Impl arithmetic operators
|
||||
$crate::impl_vector_assign_operator!(AddAssign, add_assign);
|
||||
$crate::impl_vector_operator!(Add, add);
|
||||
$crate::impl_vector_assign_operator!(SubAssign, sub_assign);
|
||||
$crate::impl_vector_operator!(Sub, sub);
|
||||
$crate::impl_vector_assign_operator!(RemAssign, rem_assign);
|
||||
$crate::impl_vector_operator!(Rem, rem);
|
||||
|
||||
// mul and div are special, usually you multiply by a scalar
|
||||
// and implementing both vec*vec and vec*scalar is conflicting implementations Q_Q
|
||||
$crate::impl_vector_assign_operator_scalar!(MulAssign, mul_assign);
|
||||
$crate::impl_vector_operator_scalar!(Mul, mul);
|
||||
$crate::impl_vector_assign_operator_scalar!(DivAssign, div_assign);
|
||||
#[cfg(not(feature = "deferred-division"))]
|
||||
$crate::impl_vector_operator_scalar!(Div, div);
|
||||
#[cfg(feature = "deferred-division")]
|
||||
$crate::impl_vector_deferred_division!();
|
||||
|
||||
// Impl bitwise operators
|
||||
$crate::impl_vector_assign_operator!(BitAndAssign, bitand_assign);
|
||||
$crate::impl_vector_operator!(BitAnd, bitand);
|
||||
$crate::impl_vector_assign_operator!(BitOrAssign, bitor_assign);
|
||||
$crate::impl_vector_operator!(BitOr, bitor);
|
||||
$crate::impl_vector_assign_operator!(BitXorAssign, bitxor_assign);
|
||||
$crate::impl_vector_operator!(BitXor, bitxor);
|
||||
|
||||
// Impl shift operators
|
||||
$crate::impl_vector_shift_assign_operator!(ShlAssign, shl_assign);
|
||||
$crate::impl_vector_shift_operator!(Shl, shl);
|
||||
$crate::impl_vector_shift_assign_operator!(ShrAssign, shr_assign);
|
||||
$crate::impl_vector_shift_operator!(Shr, shr);
|
||||
|
||||
// dedicated methods for this type
|
||||
#[cfg(feature = "fixed-wide")]
|
||||
$crate::impl_fixed_wide_vector!();
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_deferred_division {
|
||||
() => {
|
||||
impl<const N: usize, T: ratio_ops::ratio::Divide<U, Output = V>, U: Copy, V>
|
||||
ratio_ops::ratio::Divide<U> for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn divide(self, rhs: U) -> Self::Output {
|
||||
self.map(|t| t.divide(rhs))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T, U> core::ops::Div<U> for Vector<N, T> {
|
||||
type Output = ratio_ops::ratio::Ratio<Vector<N, T>, U>;
|
||||
#[inline]
|
||||
fn div(self, rhs: U) -> Self::Output {
|
||||
ratio_ops::ratio::Ratio::new(self, rhs)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_operator_scalar {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U, Output = V>, U: Copy, V> core::ops::$trait<U>
|
||||
for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: U) -> Self::Output {
|
||||
self.map(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U, Output = V>, U, V>
|
||||
core::ops::$trait<Vector<N, U>> for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: Vector<N, U>) -> Self::Output {
|
||||
self.map_zip(rhs, |(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<i64, Output = T>> core::ops::$trait<i64>
|
||||
for Vector<N, T>
|
||||
{
|
||||
type Output = Self;
|
||||
#[inline]
|
||||
fn $method(self, rhs: i64) -> Self::Output {
|
||||
self.map(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_assign_operator_scalar {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U>, U: Copy> core::ops::$trait<U>
|
||||
for Vector<N, T>
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: U) {
|
||||
self.array.iter_mut().for_each(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_assign_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U>, U> core::ops::$trait<Vector<N, U>>
|
||||
for Vector<N, T>
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: Vector<N, U>) {
|
||||
self.array
|
||||
.iter_mut()
|
||||
.zip(rhs.array)
|
||||
.for_each(|(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<i64>> core::ops::$trait<i64> for Vector<N, T> {
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: i64) {
|
||||
self.array.iter_mut().for_each(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_shift_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U, Output = V>, U, V>
|
||||
core::ops::$trait<Vector<N, U>> for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: Vector<N, U>) -> Self::Output {
|
||||
self.map_zip(rhs, |(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<u32, Output = V>, V> core::ops::$trait<u32>
|
||||
for Vector<N, T>
|
||||
{
|
||||
type Output = Vector<N, V>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: u32) -> Self::Output {
|
||||
self.map(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_shift_assign_operator {
|
||||
($trait: ident, $method: ident ) => {
|
||||
impl<const N: usize, T: core::ops::$trait<U>, U> core::ops::$trait<Vector<N, U>>
|
||||
for Vector<N, T>
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: Vector<N, U>) {
|
||||
self.array
|
||||
.iter_mut()
|
||||
.zip(rhs.array)
|
||||
.for_each(|(a, b)| a.$method(b))
|
||||
}
|
||||
}
|
||||
impl<const N: usize, T: core::ops::$trait<u32>> core::ops::$trait<u32> for Vector<N, T> {
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: u32) {
|
||||
self.array.iter_mut().for_each(|t| t.$method(rhs))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_extend {
|
||||
( $size: expr ) => {
|
||||
impl<T> Vector<$size, T> {
|
||||
#[inline]
|
||||
pub fn extend(self, value: T) -> Vector<{ $size + 1 }, T> {
|
||||
let mut iter = self.array.into_iter().chain(core::iter::once(value));
|
||||
Vector::new(core::array::from_fn(|_| iter.next().unwrap()))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_named_fields {
|
||||
( $struct:ident, $size: expr ) => {
|
||||
impl<T> core::ops::Deref for Vector<$size, T> {
|
||||
type Target = $struct<T>;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { core::mem::transmute(&self.array) }
|
||||
}
|
||||
}
|
||||
impl<T> core::ops::DerefMut for Vector<$size, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
unsafe { core::mem::transmute(&mut self.array) }
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_vector_3 {
|
||||
() => {
|
||||
impl<T> Vector<3, T> {
|
||||
#[inline]
|
||||
pub fn cross<U, V>(self, rhs: Vector<3, U>) -> Vector<3, <V as core::ops::Sub>::Output>
|
||||
where
|
||||
T: core::ops::Mul<U, Output = V> + Copy,
|
||||
U: Copy,
|
||||
V: core::ops::Sub,
|
||||
{
|
||||
Vector::new([
|
||||
self.y * rhs.z - self.z * rhs.y,
|
||||
self.z * rhs.x - self.x * rhs.z,
|
||||
self.x * rhs.y - self.y * rhs.x,
|
||||
])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
17
lib/linear_ops/src/matrix.rs
Normal file
17
lib/linear_ops/src/matrix.rs
Normal file
@ -0,0 +1,17 @@
|
||||
use crate::vector::Vector;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Matrix<const X: usize, const Y: usize, T> {
|
||||
pub(crate) array: [[T; Y]; X],
|
||||
}
|
||||
|
||||
crate::impl_matrix!();
|
||||
|
||||
crate::impl_matrix_extend!(2, 2);
|
||||
crate::impl_matrix_extend!(2, 3);
|
||||
crate::impl_matrix_extend!(3, 2);
|
||||
crate::impl_matrix_extend!(3, 3);
|
||||
|
||||
//Special case 3x3 matrix operations because I cba to write macros for the arbitrary cases
|
||||
#[cfg(feature = "named-fields")]
|
||||
crate::impl_matrix_3x3!();
|
51
lib/linear_ops/src/named.rs
Normal file
51
lib/linear_ops/src/named.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::matrix::Matrix;
|
||||
use crate::vector::Vector;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Vector2<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Vector3<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Vector4<T> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
pub z: T,
|
||||
pub w: T,
|
||||
}
|
||||
|
||||
crate::impl_vector_named_fields!(Vector2, 2);
|
||||
crate::impl_vector_named_fields!(Vector3, 3);
|
||||
crate::impl_vector_named_fields!(Vector4, 4);
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Matrix2<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Matrix3<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
}
|
||||
#[repr(C)]
|
||||
pub struct Matrix4<T> {
|
||||
pub x_axis: T,
|
||||
pub y_axis: T,
|
||||
pub z_axis: T,
|
||||
pub w_axis: T,
|
||||
}
|
||||
|
||||
crate::impl_matrix_named_fields!(
|
||||
//outer struct
|
||||
((Matrix2, 2), (Matrix3, 3), (Matrix4, 4)),
|
||||
//inner struct
|
||||
((2), (3), (4))
|
||||
);
|
131
lib/linear_ops/src/tests/fixed_wide.rs
Normal file
131
lib/linear_ops/src/tests/fixed_wide.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use crate::types::{Matrix3, Matrix3x2, Matrix3x4, Matrix4x2, Vector3};
|
||||
|
||||
type Planar64 = fixed_wide::types::I32F32;
|
||||
type Planar64Wide1 = fixed_wide::types::I64F64;
|
||||
//type Planar64Wide2=fixed_wide::types::I128F128;
|
||||
type Planar64Wide3 = fixed_wide::types::I256F256;
|
||||
|
||||
#[test]
|
||||
fn wide_vec3() {
|
||||
let v = Vector3::from_value(Planar64::from(3));
|
||||
let v1 = v * v.x;
|
||||
let v2 = v1 * v1.y;
|
||||
let v3 = v2 * v2.z;
|
||||
|
||||
assert_eq!(
|
||||
v3.array,
|
||||
Vector3::from_value(Planar64Wide3::from(3i128.pow(8))).array
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_vec3_dot() {
|
||||
let v = Vector3::from_value(Planar64::from(3));
|
||||
let v1 = v * v.x;
|
||||
let v2 = v1 * v1.y;
|
||||
let v3 = v2.dot(v2);
|
||||
|
||||
assert_eq!(v3, Planar64Wide3::from(3i128.pow(8) * 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_vec3_length_squared() {
|
||||
let v = Vector3::from_value(Planar64::from(3));
|
||||
let v1 = v * v.x;
|
||||
let v2 = v1 * v1.y;
|
||||
let v3 = v2.length_squared();
|
||||
|
||||
assert_eq!(v3, Planar64Wide3::from(3i128.pow(8) * 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wide_matrix_dot() {
|
||||
let lhs = Matrix3x4::new([
|
||||
[
|
||||
Planar64::from(1),
|
||||
Planar64::from(2),
|
||||
Planar64::from(3),
|
||||
Planar64::from(4),
|
||||
],
|
||||
[
|
||||
Planar64::from(5),
|
||||
Planar64::from(6),
|
||||
Planar64::from(7),
|
||||
Planar64::from(8),
|
||||
],
|
||||
[
|
||||
Planar64::from(9),
|
||||
Planar64::from(10),
|
||||
Planar64::from(11),
|
||||
Planar64::from(12),
|
||||
],
|
||||
])
|
||||
.transpose();
|
||||
let rhs = Matrix4x2::new([
|
||||
[Planar64::from(1), Planar64::from(2)],
|
||||
[Planar64::from(3), Planar64::from(4)],
|
||||
[Planar64::from(5), Planar64::from(6)],
|
||||
[Planar64::from(7), Planar64::from(8)],
|
||||
])
|
||||
.transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot = lhs * rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([
|
||||
[Planar64Wide1::from(50), Planar64Wide1::from(60)],
|
||||
[Planar64Wide1::from(114), Planar64Wide1::from(140)],
|
||||
[Planar64Wide1::from(178), Planar64Wide1::from(220)],
|
||||
])
|
||||
.transpose()
|
||||
.array
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "named-fields")]
|
||||
fn wide_matrix_det() {
|
||||
let m = Matrix3::new([
|
||||
[Planar64::from(1), Planar64::from(2), Planar64::from(3)],
|
||||
[Planar64::from(4), Planar64::from(5), Planar64::from(7)],
|
||||
[Planar64::from(6), Planar64::from(8), Planar64::from(9)],
|
||||
]);
|
||||
// In[2]:= Det[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[2]= 7
|
||||
assert_eq!(m.det(), fixed_wide::fixed::Fixed::<3, 96>::from(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "named-fields")]
|
||||
fn wide_matrix_adjugate() {
|
||||
let m = Matrix3::new([
|
||||
[Planar64::from(1), Planar64::from(2), Planar64::from(3)],
|
||||
[Planar64::from(4), Planar64::from(5), Planar64::from(7)],
|
||||
[Planar64::from(6), Planar64::from(8), Planar64::from(9)],
|
||||
]);
|
||||
// In[6]:= Adjugate[{{1, 2, 3}, {4, 5, 7}, {6, 8, 9}}]
|
||||
// Out[6]= {{-11, 6, -1}, {6, -9, 5}, {2, 4, -3}}
|
||||
assert_eq!(
|
||||
m.adjugate().array,
|
||||
Matrix3::new([
|
||||
[
|
||||
Planar64Wide1::from(-11),
|
||||
Planar64Wide1::from(6),
|
||||
Planar64Wide1::from(-1)
|
||||
],
|
||||
[
|
||||
Planar64Wide1::from(6),
|
||||
Planar64Wide1::from(-9),
|
||||
Planar64Wide1::from(5)
|
||||
],
|
||||
[
|
||||
Planar64Wide1::from(2),
|
||||
Planar64Wide1::from(4),
|
||||
Planar64Wide1::from(-3)
|
||||
],
|
||||
])
|
||||
.array
|
||||
);
|
||||
}
|
6
lib/linear_ops/src/tests/mod.rs
Normal file
6
lib/linear_ops/src/tests/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "named-fields")]
|
||||
mod named;
|
||||
|
||||
mod fixed_wide;
|
29
lib/linear_ops/src/tests/named.rs
Normal file
29
lib/linear_ops/src/tests/named.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::types::{Matrix3, Vector3};
|
||||
|
||||
#[test]
|
||||
fn test_vector() {
|
||||
let mut v = Vector3::new([1, 2, 3]);
|
||||
assert_eq!(v.x, 1);
|
||||
assert_eq!(v.y, 2);
|
||||
assert_eq!(v.z, 3);
|
||||
|
||||
v.x = 5;
|
||||
assert_eq!(v.x, 5);
|
||||
|
||||
v.y *= v.x;
|
||||
assert_eq!(v.y, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_matrix() {
|
||||
let mut v = Matrix3::from_value(2);
|
||||
assert_eq!(v.x_axis.x, 2);
|
||||
assert_eq!(v.y_axis.y, 2);
|
||||
assert_eq!(v.z_axis.z, 2);
|
||||
|
||||
v.x_axis.x = 5;
|
||||
assert_eq!(v.x_axis.x, 5);
|
||||
|
||||
v.y_axis.z *= v.x_axis.x;
|
||||
assert_eq!(v.y_axis.z, 10);
|
||||
}
|
51
lib/linear_ops/src/tests/tests.rs
Normal file
51
lib/linear_ops/src/tests/tests.rs
Normal file
@ -0,0 +1,51 @@
|
||||
use crate::types::{Matrix2x3, Matrix3x2, Matrix3x4, Matrix4x2, Vector2, Vector3};
|
||||
|
||||
#[test]
|
||||
fn test_bool() {
|
||||
assert_eq!(Vector3::new([false, false, false]).any(), false);
|
||||
assert_eq!(Vector3::new([false, false, true]).any(), true);
|
||||
assert_eq!(Vector3::new([false, false, true]).all(), false);
|
||||
assert_eq!(Vector3::new([true, true, true]).all(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_length_squared() {
|
||||
assert_eq!(Vector3::new([1, 2, 3]).length_squared(), 14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arithmetic() {
|
||||
let a = Vector3::new([1, 2, 3]);
|
||||
assert_eq!((a + a * 2).array, Vector3::new([1 * 3, 2 * 3, 3 * 3]).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_transform_vector() {
|
||||
let m = Matrix2x3::new([[1, 2, 3], [4, 5, 6]]).transpose();
|
||||
let v = Vector3::new([1, 2, 3]);
|
||||
let transformed = m * v;
|
||||
assert_eq!(transformed.array, Vector2::new([14, 32]).array);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_dot() {
|
||||
// All this code was written row major and I converted the lib to colum major
|
||||
let rhs = Matrix4x2::new([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0], [7.0, 8.0]]).transpose(); // | | |
|
||||
let lhs = Matrix3x4::new([
|
||||
// | | |
|
||||
[1.0, 2.0, 3.0, 4.0], // [ 50.0, 60.0],
|
||||
[5.0, 6.0, 7.0, 8.0], // [114.0,140.0],
|
||||
[9.0, 10.0, 11.0, 12.0], // [178.0,220.0],
|
||||
])
|
||||
.transpose();
|
||||
// Mat3<Vec4>.dot(Mat4<Vec2>) -> Mat3<Vec2>
|
||||
let m_dot = lhs * rhs;
|
||||
//In[1]:= {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} . {{1, 2}, {3, 4}, {5, 6}, {7, 8}}
|
||||
//Out[1]= {{50, 60}, {114, 140}, {178, 220}}
|
||||
assert_eq!(
|
||||
m_dot.array,
|
||||
Matrix3x2::new([[50.0, 60.0], [114.0, 140.0], [178.0, 220.0],])
|
||||
.transpose()
|
||||
.array
|
||||
);
|
||||
}
|
18
lib/linear_ops/src/types.rs
Normal file
18
lib/linear_ops/src/types.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use crate::matrix::Matrix;
|
||||
use crate::vector::Vector;
|
||||
|
||||
pub type Vector2<T> = Vector<2, T>;
|
||||
pub type Vector3<T> = Vector<3, T>;
|
||||
pub type Vector4<T> = Vector<4, T>;
|
||||
|
||||
pub type Matrix2<T> = Matrix<2, 2, T>;
|
||||
pub type Matrix2x3<T> = Matrix<2, 3, T>;
|
||||
pub type Matrix2x4<T> = Matrix<2, 4, T>;
|
||||
|
||||
pub type Matrix3x2<T> = Matrix<3, 2, T>;
|
||||
pub type Matrix3<T> = Matrix<3, 3, T>;
|
||||
pub type Matrix3x4<T> = Matrix<3, 4, T>;
|
||||
|
||||
pub type Matrix4x2<T> = Matrix<4, 2, T>;
|
||||
pub type Matrix4x3<T> = Matrix<4, 3, T>;
|
||||
pub type Matrix4<T> = Matrix<4, 4, T>;
|
19
lib/linear_ops/src/vector.rs
Normal file
19
lib/linear_ops/src/vector.rs
Normal file
@ -0,0 +1,19 @@
|
||||
/// An array-backed vector type. Named fields are made accessible via the Deref/DerefMut traits which are implmented for 2-4 dimensions.
|
||||
/// let mut v = Vector::new([1.0,2.0,3.0]);
|
||||
/// v.x += v.z;
|
||||
/// println!("v.x={}",v.x);
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
|
||||
pub struct Vector<const N: usize, T> {
|
||||
pub(crate) array: [T; N],
|
||||
}
|
||||
|
||||
crate::impl_vector!();
|
||||
|
||||
// Needs const generics for generic case
|
||||
crate::impl_vector_extend!(2);
|
||||
crate::impl_vector_extend!(3);
|
||||
|
||||
//cross product
|
||||
#[cfg(feature = "named-fields")]
|
||||
crate::impl_vector_3!();
|
1
lib/ratio_ops/.gitignore
vendored
Normal file
1
lib/ratio_ops/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
10
lib/ratio_ops/Cargo.toml
Normal file
10
lib/ratio_ops/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "ratio_ops"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Ratio operations using trait bounds for avoiding division like the plague."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[dependencies]
|
176
lib/ratio_ops/LICENSE-APACHE
Normal file
176
lib/ratio_ops/LICENSE-APACHE
Normal file
@ -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
|
23
lib/ratio_ops/LICENSE-MIT
Normal file
23
lib/ratio_ops/LICENSE-MIT
Normal file
@ -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.
|
4
lib/ratio_ops/src/lib.rs
Normal file
4
lib/ratio_ops/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod ratio;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
317
lib/ratio_ops/src/ratio.rs
Normal file
317
lib/ratio_ops/src/ratio.rs
Normal file
@ -0,0 +1,317 @@
|
||||
#[derive(Clone, Copy, Debug, Hash)]
|
||||
pub struct Ratio<Num, Den> {
|
||||
pub num: Num,
|
||||
pub den: Den,
|
||||
}
|
||||
impl<Num, Den> Ratio<Num, Den> {
|
||||
#[inline(always)]
|
||||
pub const fn new(num: Num, den: Den) -> Self {
|
||||
Self { num, den }
|
||||
}
|
||||
}
|
||||
|
||||
/// The actual divide implementation, Div is replaced with a Ratio constructor
|
||||
pub trait Divide<Rhs = Self> {
|
||||
type Output;
|
||||
fn divide(self, rhs: Rhs) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<Num, Den> Ratio<Num, Den>
|
||||
where
|
||||
Num: Divide<Den>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn divide(self) -> <Num as Divide<Den>>::Output {
|
||||
self.num.divide(self.den)
|
||||
}
|
||||
}
|
||||
|
||||
//take care to use the ratio methods to avoid nested ratios
|
||||
|
||||
impl<LhsNum, LhsDen> Ratio<LhsNum, LhsDen> {
|
||||
#[inline]
|
||||
pub fn mul_ratio<RhsNum, RhsDen>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> Ratio<<LhsNum as core::ops::Mul<RhsNum>>::Output, <LhsDen as core::ops::Mul<RhsDen>>::Output>
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsNum>,
|
||||
LhsDen: core::ops::Mul<RhsDen>,
|
||||
{
|
||||
Ratio::new(self.num * rhs.num, self.den * rhs.den)
|
||||
}
|
||||
#[inline]
|
||||
pub fn div_ratio<RhsNum, RhsDen>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> Ratio<<LhsNum as core::ops::Mul<RhsDen>>::Output, <LhsDen as core::ops::Mul<RhsNum>>::Output>
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsDen>,
|
||||
LhsDen: core::ops::Mul<RhsNum>,
|
||||
{
|
||||
Ratio::new(self.num * rhs.den, self.den * rhs.num)
|
||||
}
|
||||
}
|
||||
macro_rules! impl_ratio_method {
|
||||
($trait:ident, $method:ident, $ratio_method:ident) => {
|
||||
impl<LhsNum, LhsDen> Ratio<LhsNum, LhsDen> {
|
||||
#[inline]
|
||||
pub fn $ratio_method<RhsNum, RhsDen, LhsCrossMul, RhsCrossMul>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> Ratio<
|
||||
<LhsCrossMul as core::ops::$trait<RhsCrossMul>>::Output,
|
||||
<LhsDen as core::ops::Mul<RhsDen>>::Output,
|
||||
>
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = LhsCrossMul>,
|
||||
LhsDen: core::ops::Mul<RhsNum, Output = RhsCrossMul>,
|
||||
LhsDen: core::ops::Mul<RhsDen>,
|
||||
LhsDen: Copy,
|
||||
RhsDen: Copy,
|
||||
LhsCrossMul: core::ops::$trait<RhsCrossMul>,
|
||||
{
|
||||
Ratio::new(
|
||||
(self.num * rhs.den).$method(self.den * rhs.num),
|
||||
self.den * rhs.den,
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_ratio_method!(Add, add, add_ratio);
|
||||
impl_ratio_method!(Sub, sub, sub_ratio);
|
||||
impl_ratio_method!(Rem, rem, rem_ratio);
|
||||
|
||||
/// Comparing two ratios needs to know the parity of the denominators
|
||||
/// For signed integers this can be implemented with is_negative()
|
||||
pub trait Parity {
|
||||
fn parity(&self) -> bool;
|
||||
}
|
||||
macro_rules! impl_parity_unsigned{
|
||||
($($type:ty),*)=>{
|
||||
$(
|
||||
impl Parity for $type{
|
||||
fn parity(&self)->bool{
|
||||
false
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
macro_rules! impl_parity_signed{
|
||||
($($type:ty),*)=>{
|
||||
$(
|
||||
impl Parity for $type{
|
||||
fn parity(&self)->bool{
|
||||
self.is_negative()
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
macro_rules! impl_parity_float{
|
||||
($($type:ty),*)=>{
|
||||
$(
|
||||
impl Parity for $type{
|
||||
fn parity(&self)->bool{
|
||||
self.is_sign_negative()
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_parity_unsigned!(u8, u16, u32, u64, u128, usize);
|
||||
impl_parity_signed!(i8, i16, i32, i64, i128, isize);
|
||||
impl_parity_float!(f32, f64);
|
||||
|
||||
macro_rules! impl_ratio_ord_method {
|
||||
($method:ident, $ratio_method:ident, $output:ty) => {
|
||||
impl<LhsNum, LhsDen: Parity> Ratio<LhsNum, LhsDen> {
|
||||
#[inline]
|
||||
pub fn $ratio_method<RhsNum, RhsDen: Parity, T>(
|
||||
self,
|
||||
rhs: Ratio<RhsNum, RhsDen>,
|
||||
) -> $output
|
||||
where
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = T>,
|
||||
LhsDen: core::ops::Mul<RhsNum, Output = T>,
|
||||
T: Ord,
|
||||
{
|
||||
match self.den.parity() ^ rhs.den.parity() {
|
||||
true => (self.den * rhs.num).$method(&(self.num * rhs.den)),
|
||||
false => (self.num * rhs.den).$method(&(self.den * rhs.num)),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
//PartialEq
|
||||
impl_ratio_ord_method!(eq, eq_ratio, bool);
|
||||
//PartialOrd
|
||||
impl_ratio_ord_method!(lt, lt_ratio, bool);
|
||||
impl_ratio_ord_method!(gt, gt_ratio, bool);
|
||||
impl_ratio_ord_method!(le, le_ratio, bool);
|
||||
impl_ratio_ord_method!(ge, ge_ratio, bool);
|
||||
impl_ratio_ord_method!(partial_cmp, partial_cmp_ratio, Option<core::cmp::Ordering>);
|
||||
//Ord
|
||||
impl_ratio_ord_method!(cmp, cmp_ratio, core::cmp::Ordering);
|
||||
|
||||
/* generic rhs mul is not possible!
|
||||
impl<Lhs,RhsNum,RhsDen> core::ops::Mul<Ratio<RhsNum,RhsDen>> for Lhs
|
||||
where
|
||||
Lhs:core::ops::Mul<RhsNum>,
|
||||
{
|
||||
type Output=Ratio<<Lhs as core::ops::Mul<RhsNum>>::Output,RhsDen>;
|
||||
#[inline]
|
||||
fn mul(self,rhs:Ratio<RhsNum,RhsDen>)->Self::Output{
|
||||
Ratio::new(self*rhs.num,rhs.den)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
//operators
|
||||
|
||||
impl<LhsNum, LhsDen> core::ops::Neg for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::Neg,
|
||||
{
|
||||
type Output = Ratio<<LhsNum as core::ops::Neg>::Output, LhsDen>;
|
||||
#[inline]
|
||||
fn neg(self) -> Self::Output {
|
||||
Ratio::new(-self.num, self.den)
|
||||
}
|
||||
}
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::Mul<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::Mul<Rhs>,
|
||||
{
|
||||
type Output = Ratio<<LhsNum as core::ops::Mul<Rhs>>::Output, LhsDen>;
|
||||
#[inline]
|
||||
fn mul(self, rhs: Rhs) -> Self::Output {
|
||||
Ratio::new(self.num * rhs, self.den)
|
||||
}
|
||||
}
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::Div<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsDen: core::ops::Mul<Rhs>,
|
||||
{
|
||||
type Output = Ratio<LhsNum, <LhsDen as core::ops::Mul<Rhs>>::Output>;
|
||||
#[inline]
|
||||
fn div(self, rhs: Rhs) -> Self::Output {
|
||||
Ratio::new(self.num, self.den * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_ratio_operator {
|
||||
($trait:ident, $method:ident) => {
|
||||
impl<LhsNum, LhsDen, Rhs, Intermediate> core::ops::$trait<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::$trait<Intermediate>,
|
||||
LhsDen: Copy,
|
||||
Rhs: core::ops::Mul<LhsDen, Output = Intermediate>,
|
||||
{
|
||||
type Output = Ratio<<LhsNum as core::ops::$trait<Intermediate>>::Output, LhsDen>;
|
||||
#[inline]
|
||||
fn $method(self, rhs: Rhs) -> Self::Output {
|
||||
Ratio::new(self.num.$method(rhs * self.den), self.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_ratio_operator!(Add, add);
|
||||
impl_ratio_operator!(Sub, sub);
|
||||
impl_ratio_operator!(Rem, rem);
|
||||
|
||||
//assign operators
|
||||
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::MulAssign<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::MulAssign<Rhs>,
|
||||
{
|
||||
#[inline]
|
||||
fn mul_assign(&mut self, rhs: Rhs) {
|
||||
self.num *= rhs;
|
||||
}
|
||||
}
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::DivAssign<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsDen: core::ops::MulAssign<Rhs>,
|
||||
{
|
||||
#[inline]
|
||||
fn div_assign(&mut self, rhs: Rhs) {
|
||||
self.den *= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_ratio_assign_operator {
|
||||
($trait:ident, $method:ident) => {
|
||||
impl<LhsNum, LhsDen, Rhs> core::ops::$trait<Rhs> for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: core::ops::$trait,
|
||||
LhsDen: Copy,
|
||||
Rhs: core::ops::Mul<LhsDen, Output = LhsNum>,
|
||||
{
|
||||
#[inline]
|
||||
fn $method(&mut self, rhs: Rhs) {
|
||||
self.num.$method(rhs * self.den)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_ratio_assign_operator!(AddAssign, add_assign);
|
||||
impl_ratio_assign_operator!(SubAssign, sub_assign);
|
||||
impl_ratio_assign_operator!(RemAssign, rem_assign);
|
||||
|
||||
// Only implement PartialEq<Self>
|
||||
// Rust's operators aren't actually that good
|
||||
|
||||
impl<LhsNum, LhsDen, RhsNum, RhsDen, T, U> PartialEq<Ratio<RhsNum, RhsDen>>
|
||||
for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: Copy,
|
||||
LhsDen: Copy,
|
||||
RhsNum: Copy,
|
||||
RhsDen: Copy,
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = T>,
|
||||
RhsNum: core::ops::Mul<LhsDen, Output = U>,
|
||||
T: PartialEq<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn eq(&self, other: &Ratio<RhsNum, RhsDen>) -> bool {
|
||||
(self.num * other.den).eq(&(other.num * self.den))
|
||||
}
|
||||
}
|
||||
impl<Num, Den> Eq for Ratio<Num, Den> where Self: PartialEq {}
|
||||
|
||||
impl<LhsNum, LhsDen, RhsNum, RhsDen, T, U> PartialOrd<Ratio<RhsNum, RhsDen>>
|
||||
for Ratio<LhsNum, LhsDen>
|
||||
where
|
||||
LhsNum: Copy,
|
||||
LhsDen: Copy,
|
||||
RhsNum: Copy,
|
||||
RhsDen: Copy,
|
||||
LhsNum: core::ops::Mul<RhsDen, Output = T>,
|
||||
RhsNum: core::ops::Mul<LhsDen, Output = U>,
|
||||
T: PartialOrd<U>,
|
||||
{
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Ratio<RhsNum, RhsDen>) -> Option<core::cmp::Ordering> {
|
||||
(self.num * other.den).partial_cmp(&(other.num * self.den))
|
||||
}
|
||||
}
|
||||
impl<Num, Den, T> Ord for Ratio<Num, Den>
|
||||
where
|
||||
Num: Copy,
|
||||
Den: Copy,
|
||||
Num: core::ops::Mul<Den, Output = T>,
|
||||
T: Ord,
|
||||
{
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
(self.num * other.den).cmp(&(other.num * self.den))
|
||||
}
|
||||
}
|
58
lib/ratio_ops/src/tests.rs
Normal file
58
lib/ratio_ops/src/tests.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use crate::ratio::Ratio;
|
||||
|
||||
macro_rules! test_op {
|
||||
($ratio_op:ident,$op:ident,$a:expr,$b:expr,$c:expr,$d:expr) => {
|
||||
assert_eq!(
|
||||
Ratio::new($a, $b).$ratio_op(Ratio::new($c, $d)),
|
||||
(($a as f32) / ($b as f32)).$op(&(($c as f32) / ($d as f32)))
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! test_many_ops {
|
||||
($ratio_op:ident,$op:ident) => {
|
||||
test_op!($ratio_op, $op, 1, 2, 3, 4);
|
||||
test_op!($ratio_op, $op, 1, 2, -3, 4);
|
||||
test_op!($ratio_op, $op, -1, 2, -3, 4);
|
||||
test_op!($ratio_op, $op, -1, -2, -3, 4);
|
||||
test_op!($ratio_op, $op, 2, 1, 6, 3);
|
||||
test_op!($ratio_op, $op, -2, 1, 6, 3);
|
||||
test_op!($ratio_op, $op, 2, -1, -6, 3);
|
||||
test_op!($ratio_op, $op, 2, 1, 6, -3);
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lt() {
|
||||
test_many_ops!(lt_ratio, lt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gt() {
|
||||
test_many_ops!(gt_ratio, gt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_le() {
|
||||
test_many_ops!(le_ratio, le);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ge() {
|
||||
test_many_ops!(ge_ratio, ge);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eq() {
|
||||
test_many_ops!(eq_ratio, eq);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_cmp() {
|
||||
test_many_ops!(partial_cmp_ratio, partial_cmp);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_cmp(){
|
||||
// test_many_ops!(cmp_ratio,cmp);
|
||||
// }
|
1
lib/rbx_loader/.gitignore
vendored
Normal file
1
lib/rbx_loader/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
22
lib/rbx_loader/Cargo.toml
Normal file
22
lib/rbx_loader/Cargo.toml
Normal file
@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "strafesnet_rbx_loader"
|
||||
version = "0.5.2"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
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]
|
||||
bytemuck = "1.14.3"
|
||||
glam = "0.29.0"
|
||||
lazy-regex = "3.1.0"
|
||||
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" }
|
||||
roblox_emulator = { path = "../roblox_emulator", registry = "strafesnet" }
|
||||
strafesnet_common = { path = "../common", registry = "strafesnet" }
|
176
lib/rbx_loader/LICENSE-APACHE
Normal file
176
lib/rbx_loader/LICENSE-APACHE
Normal file
@ -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
|
23
lib/rbx_loader/LICENSE-MIT
Normal file
23
lib/rbx_loader/LICENSE-MIT
Normal file
@ -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.
|
19
lib/rbx_loader/README.md
Normal file
19
lib/rbx_loader/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
StrafesNET Roblox Loader
|
||||
========================
|
||||
|
||||
## Convert Roblox files into StrafesNET data structures
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
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.
|
||||
</sub>
|
113
lib/rbx_loader/src/lib.rs
Normal file
113
lib/rbx_loader/src/lib.rs
Normal file
@ -0,0 +1,113 @@
|
||||
use rbx_dom_weak::WeakDom;
|
||||
use std::io::Read;
|
||||
|
||||
mod mesh;
|
||||
mod primitives;
|
||||
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 Model {
|
||||
dom: WeakDom,
|
||||
}
|
||||
impl Model {
|
||||
fn new(dom: WeakDom) -> Self {
|
||||
Self { dom }
|
||||
}
|
||||
pub fn into_place(self) -> Place {
|
||||
let Self { mut dom } = self;
|
||||
let context = roblox_emulator::context::Context::from_mut(&mut dom);
|
||||
let services = context.convert_into_place();
|
||||
Place { dom, services }
|
||||
}
|
||||
}
|
||||
impl AsRef<WeakDom> for Model {
|
||||
fn as_ref(&self) -> &WeakDom {
|
||||
&self.dom
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Place {
|
||||
dom: WeakDom,
|
||||
services: roblox_emulator::context::Services,
|
||||
}
|
||||
impl Place {
|
||||
fn new(dom: WeakDom) -> Option<Self> {
|
||||
let context = roblox_emulator::context::Context::from_ref(&dom);
|
||||
Some(Self {
|
||||
services: context.find_services()?,
|
||||
dom,
|
||||
})
|
||||
}
|
||||
pub fn run_scripts(&mut self) {
|
||||
let Place { dom, services } = self;
|
||||
let runner = roblox_emulator::runner::Runner::new().unwrap();
|
||||
let context = roblox_emulator::context::Context::from_mut(dom);
|
||||
let scripts = context.scripts();
|
||||
let runnable = runner
|
||||
.runnable_context_with_services(context, services)
|
||||
.unwrap();
|
||||
for script in scripts {
|
||||
if let Err(e) = runnable.run_script(script) {
|
||||
println!("runner error: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AsRef<WeakDom> for Place {
|
||||
fn as_ref(&self) -> &WeakDom {
|
||||
&self.dom
|
||||
}
|
||||
}
|
||||
|
||||
#[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<Model, 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(Model::new)
|
||||
.map_err(ReadError::RbxBinary),
|
||||
b"<roblox " => rbx_xml::from_reader_default(buf)
|
||||
.map(Model::new)
|
||||
.map_err(ReadError::RbxXml),
|
||||
_ => Err(ReadError::UnknownFileFormat),
|
||||
}
|
||||
}
|
||||
|
||||
//ConvertError
|
||||
|
||||
pub fn convert<AcquireRenderConfigId, AcquireMeshId>(
|
||||
dom: impl AsRef<WeakDom>,
|
||||
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.as_ref(), acquire_render_config_id, acquire_mesh_id)
|
||||
}
|
284
lib/rbx_loader/src/mesh.rs
Normal file
284
lib/rbx_loader/src/mesh.rs
Normal file
@ -0,0 +1,284 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rbx_mesh::mesh::{Vertex2, Vertex2Truncated};
|
||||
use strafesnet_common::{
|
||||
integer::vec3,
|
||||
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 = vec3::try_from_f32_array(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 = vec3::try_from_f32_array(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(),
|
||||
})
|
||||
}
|
536
lib/rbx_loader/src/primitives.rs
Normal file
536
lib/rbx_loader/src/primitives.rs
Normal file
@ -0,0 +1,536 @@
|
||||
use strafesnet_common::integer::{vec3, Planar64Vec3};
|
||||
use strafesnet_common::model::{
|
||||
Color4, ColorId, IndexedGraphicsGroup, IndexedPhysicsGroup, IndexedVertex, IndexedVertexList,
|
||||
Mesh, NormalId, PolygonGroup, PolygonGroupId, PolygonList, PositionId, RenderConfigId,
|
||||
TextureCoordinate, TextureCoordinateId, VertexId,
|
||||
};
|
||||
|
||||
#[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] = [
|
||||
vec3::int(-1, -1, 1), //0 left bottom back
|
||||
vec3::int(1, -1, 1), //1 right bottom back
|
||||
vec3::int(1, 1, 1), //2 right top back
|
||||
vec3::int(-1, 1, 1), //3 left top back
|
||||
vec3::int(-1, 1, -1), //4 left top front
|
||||
vec3::int(1, 1, -1), //5 right top front
|
||||
vec3::int(1, -1, -1), //6 right bottom front
|
||||
vec3::int(-1, -1, -1), //7 left bottom front
|
||||
];
|
||||
const CUBE_DEFAULT_NORMALS: [Planar64Vec3; 6] = [
|
||||
vec3::int(1, 0, 0), //CubeFace::Right
|
||||
vec3::int(0, 1, 0), //CubeFace::Top
|
||||
vec3::int(0, 0, 1), //CubeFace::Back
|
||||
vec3::int(-1, 0, 0), //CubeFace::Left
|
||||
vec3::int(0, -1, 0), //CubeFace::Bottom
|
||||
vec3::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] = [
|
||||
vec3::int(1, 0, 0), //Wedge::Right
|
||||
vec3::int(0, 1, -1), //Wedge::TopFront
|
||||
vec3::int(0, 0, 1), //Wedge::Back
|
||||
vec3::int(-1, 0, 0), //Wedge::Left
|
||||
vec3::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] = [
|
||||
vec3::int(1, 0, 0), //CornerWedge::Right
|
||||
vec3::int(0, 1, 1), //CornerWedge::BackTop
|
||||
vec3::int(-1, 1, 0), //CornerWedge::LeftTop
|
||||
vec3::int(0, -1, 0), //CornerWedge::Bottom
|
||||
vec3::int(0, 0, -1), //CornerWedge::Front
|
||||
];
|
||||
pub fn unit_sphere(render: RenderConfigId) -> Mesh {
|
||||
unit_cube(render)
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct CubeFaceDescription([Option<FaceDescription>; 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<
|
||||
std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>, 6>>,
|
||||
impl FnMut((usize, Option<FaceDescription>)) -> Option<(usize, FaceDescription)>,
|
||||
> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|v| v.1.map(|u| (v.0, u)))
|
||||
}
|
||||
}
|
||||
pub fn unit_cube(render: RenderConfigId) -> Mesh {
|
||||
let mut t = CubeFaceDescription::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(render: RenderConfigId) -> Mesh {
|
||||
//lmao
|
||||
unit_cube(render)
|
||||
}
|
||||
#[derive(Default)]
|
||||
pub struct WedgeFaceDescription([Option<FaceDescription>; 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<
|
||||
std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>, 5>>,
|
||||
impl FnMut((usize, Option<FaceDescription>)) -> Option<(usize, FaceDescription)>,
|
||||
> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|v| v.1.map(|u| (v.0, u)))
|
||||
}
|
||||
}
|
||||
pub fn unit_wedge(render: RenderConfigId) -> Mesh {
|
||||
let mut t = WedgeFaceDescription::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)]
|
||||
pub struct CornerWedgeFaceDescription([Option<FaceDescription>; 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<
|
||||
std::iter::Enumerate<std::array::IntoIter<Option<FaceDescription>, 5>>,
|
||||
impl FnMut((usize, Option<FaceDescription>)) -> Option<(usize, FaceDescription)>,
|
||||
> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|v| v.1.map(|u| (v.0, u)))
|
||||
}
|
||||
}
|
||||
pub fn unit_cornerwedge(render: RenderConfigId) -> Mesh {
|
||||
let mut t = CornerWedgeFaceDescription::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 render: RenderConfigId,
|
||||
pub transform: glam::Affine2,
|
||||
pub color: Color4,
|
||||
}
|
||||
impl FaceDescription {
|
||||
pub fn new_with_render_id(render: RenderConfigId) -> Self {
|
||||
Self {
|
||||
render,
|
||||
transform: glam::Affine2::IDENTITY,
|
||||
color: Color4::new(1.0, 1.0, 1.0, 0.0), //zero alpha to hide the default texture
|
||||
}
|
||||
}
|
||||
}
|
||||
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 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() {
|
||||
//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);
|
||||
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)
|
||||
{
|
||||
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
|
||||
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);
|
||||
}
|
||||
Mesh {
|
||||
unique_pos: generated_pos,
|
||||
unique_tex: generated_tex,
|
||||
unique_normal: generated_normal,
|
||||
unique_color: generated_color,
|
||||
unique_vertices: generated_vertices,
|
||||
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) -> Mesh {
|
||||
let wedge_default_polys = [
|
||||
// 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 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() {
|
||||
//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);
|
||||
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)
|
||||
{
|
||||
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
|
||||
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);
|
||||
}
|
||||
Mesh {
|
||||
unique_pos: generated_pos,
|
||||
unique_tex: generated_tex,
|
||||
unique_normal: generated_normal,
|
||||
unique_color: generated_color,
|
||||
unique_vertices: generated_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups: vec![physics_group],
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
[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 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() {
|
||||
//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);
|
||||
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)
|
||||
{
|
||||
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
|
||||
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);
|
||||
}
|
||||
Mesh {
|
||||
unique_pos: generated_pos,
|
||||
unique_tex: generated_tex,
|
||||
unique_normal: generated_normal,
|
||||
unique_color: generated_color,
|
||||
unique_vertices: generated_vertices,
|
||||
polygon_groups,
|
||||
graphics_groups,
|
||||
physics_groups: vec![physics_group],
|
||||
}
|
||||
}
|
1178
lib/rbx_loader/src/rbx.rs
Normal file
1178
lib/rbx_loader/src/rbx.rs
Normal file
File diff suppressed because it is too large
Load Diff
1
lib/roblox_emulator/.gitignore
vendored
Normal file
1
lib/roblox_emulator/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
21
lib/roblox_emulator/Cargo.toml
Normal file
21
lib/roblox_emulator/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "roblox_emulator"
|
||||
version = "0.4.7"
|
||||
edition = "2021"
|
||||
repository = "https://git.itzana.me/StrafesNET/strafe-project"
|
||||
license = "MIT OR Apache-2.0"
|
||||
description = "Run embedded Luau scripts which manipulate the DOM."
|
||||
authors = ["Rhys Lloyd <krakow20@gmail.com>"]
|
||||
|
||||
[features]
|
||||
default=["run-service"]
|
||||
run-service=[]
|
||||
|
||||
[dependencies]
|
||||
glam = "0.29.0"
|
||||
mlua = { version = "0.10.1", features = ["luau"] }
|
||||
phf = { version = "0.11.2", features = ["macros"] }
|
||||
rbx_dom_weak = { version = "2.7.0", registry = "strafesnet" }
|
||||
rbx_reflection = { version = "4.7.0", registry = "strafesnet" }
|
||||
rbx_reflection_database = { version = "0.2.10", registry = "strafesnet" }
|
||||
rbx_types = { version = "1.10.0", registry = "strafesnet" }
|
176
lib/roblox_emulator/LICENSE-APACHE
Normal file
176
lib/roblox_emulator/LICENSE-APACHE
Normal file
@ -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
|
23
lib/roblox_emulator/LICENSE-MIT
Normal file
23
lib/roblox_emulator/LICENSE-MIT
Normal file
@ -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.
|
19
lib/roblox_emulator/README.md
Normal file
19
lib/roblox_emulator/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
Roblox Emulator
|
||||
===============
|
||||
|
||||
## Run embedded Lua scripts which manipulate the DOM
|
||||
|
||||
#### License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
|
||||
<br>
|
||||
|
||||
<sub>
|
||||
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.
|
||||
</sub>
|
106
lib/roblox_emulator/src/context.rs
Normal file
106
lib/roblox_emulator/src/context.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use rbx_dom_weak::{types::Ref, InstanceBuilder, WeakDom};
|
||||
|
||||
pub fn class_is_a(class: &str, superclass: &str) -> bool {
|
||||
class == superclass
|
||||
|| rbx_reflection_database::get()
|
||||
.classes
|
||||
.get(class)
|
||||
.is_some_and(|descriptor| {
|
||||
descriptor
|
||||
.superclass
|
||||
.as_ref()
|
||||
.is_some_and(|class_super| class_is_a(class_super, superclass))
|
||||
})
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
pub struct Context {
|
||||
pub(crate) dom: WeakDom,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub const fn new(dom: WeakDom) -> Self {
|
||||
Self { dom }
|
||||
}
|
||||
pub fn script_singleton(
|
||||
source: String,
|
||||
) -> (Context, crate::runner::instance::Instance, Services) {
|
||||
let script = InstanceBuilder::new("Script")
|
||||
.with_property("Source", rbx_types::Variant::String(source));
|
||||
let script_ref = script.referent();
|
||||
let mut context = Self::new(WeakDom::new(
|
||||
InstanceBuilder::new("DataModel").with_child(script),
|
||||
));
|
||||
let services = context.convert_into_place();
|
||||
(
|
||||
context,
|
||||
crate::runner::instance::Instance::new(script_ref),
|
||||
services,
|
||||
)
|
||||
}
|
||||
pub fn from_ref(dom: &WeakDom) -> &Context {
|
||||
unsafe { &*(dom as *const WeakDom as *const Context) }
|
||||
}
|
||||
pub fn from_mut(dom: &mut WeakDom) -> &mut Context {
|
||||
unsafe { &mut *(dom as *mut WeakDom as *mut Context) }
|
||||
}
|
||||
/// Creates an iterator over all items of a particular class.
|
||||
pub fn superclass_iter<'a>(&'a self, superclass: &'a str) -> impl Iterator<Item = Ref> + 'a {
|
||||
self.dom
|
||||
.descendants()
|
||||
.filter(|&instance| class_is_a(instance.class.as_ref(), superclass))
|
||||
.map(|instance| instance.referent())
|
||||
}
|
||||
pub fn scripts(&self) -> Vec<crate::runner::instance::Instance> {
|
||||
self.superclass_iter("LuaSourceContainer")
|
||||
.map(crate::runner::instance::Instance::new)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn find_services(&self) -> Option<Services> {
|
||||
Some(Services {
|
||||
workspace: *self.dom.root().children().iter().find(|&&r| {
|
||||
self.dom
|
||||
.get_by_ref(r)
|
||||
.is_some_and(|instance| instance.class == "Workspace")
|
||||
})?,
|
||||
game: self.dom.root_ref(),
|
||||
})
|
||||
}
|
||||
pub fn convert_into_place(&mut self) -> Services {
|
||||
//snapshot root instances
|
||||
let children = self.dom.root().children().to_owned();
|
||||
|
||||
//insert services
|
||||
let game = self.dom.root_ref();
|
||||
let terrain_bldr = InstanceBuilder::new("Terrain");
|
||||
let workspace = self.dom.insert(
|
||||
game,
|
||||
InstanceBuilder::new("Workspace")
|
||||
//Set Workspace.Terrain property equal to Terrain
|
||||
.with_property("Terrain", terrain_bldr.referent())
|
||||
.with_child(terrain_bldr),
|
||||
);
|
||||
{
|
||||
//Lowercase and upper case workspace property!
|
||||
let game = self.dom.root_mut();
|
||||
game.properties
|
||||
.insert("workspace".to_owned(), rbx_types::Variant::Ref(workspace));
|
||||
game.properties
|
||||
.insert("Workspace".to_owned(), rbx_types::Variant::Ref(workspace));
|
||||
}
|
||||
self.dom.insert(game, InstanceBuilder::new("Lighting"));
|
||||
|
||||
//transfer original root instances into workspace
|
||||
for instance in children {
|
||||
self.dom.transfer_within(instance, workspace);
|
||||
}
|
||||
|
||||
Services { game, workspace }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Services {
|
||||
pub game: Ref,
|
||||
pub workspace: Ref,
|
||||
}
|
7
lib/roblox_emulator/src/lib.rs
Normal file
7
lib/roblox_emulator/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod context;
|
||||
pub mod runner;
|
||||
#[cfg(feature = "run-service")]
|
||||
pub(crate) mod scheduler;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
233
lib/roblox_emulator/src/runner/cframe.rs
Normal file
233
lib/roblox_emulator/src/runner/cframe.rs
Normal file
@ -0,0 +1,233 @@
|
||||
use super::vector3::Vector3;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct CFrame(pub(crate) glam::Affine3A);
|
||||
|
||||
impl CFrame {
|
||||
pub fn new(
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
xx: f32,
|
||||
yx: f32,
|
||||
zx: f32,
|
||||
xy: f32,
|
||||
yy: f32,
|
||||
zy: f32,
|
||||
xz: f32,
|
||||
yz: f32,
|
||||
zz: f32,
|
||||
) -> Self {
|
||||
Self(glam::Affine3A::from_mat3_translation(
|
||||
glam::mat3(
|
||||
glam::vec3(xx, yx, zx),
|
||||
glam::vec3(xy, yy, zy),
|
||||
glam::vec3(xz, yz, zz),
|
||||
),
|
||||
glam::vec3(x, y, z),
|
||||
))
|
||||
}
|
||||
pub fn point(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(glam::Affine3A::from_translation(glam::vec3(x, y, z)))
|
||||
}
|
||||
pub fn angles(x: f32, y: f32, z: f32) -> Self {
|
||||
Self(glam::Affine3A::from_mat3(glam::Mat3::from_euler(
|
||||
glam::EulerRot::YXZ,
|
||||
y,
|
||||
x,
|
||||
z,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn vec3_to_glam(v: glam::Vec3A) -> rbx_types::Vector3 {
|
||||
rbx_types::Vector3::new(v.x, v.y, v.z)
|
||||
}
|
||||
fn vec3_from_glam(v: rbx_types::Vector3) -> glam::Vec3A {
|
||||
glam::vec3a(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
impl Into<rbx_types::CFrame> for CFrame {
|
||||
fn into(self) -> rbx_types::CFrame {
|
||||
rbx_types::CFrame::new(
|
||||
vec3_to_glam(self.0.translation),
|
||||
rbx_types::Matrix3::new(
|
||||
vec3_to_glam(self.0.matrix3.x_axis),
|
||||
vec3_to_glam(self.0.matrix3.y_axis),
|
||||
vec3_to_glam(self.0.matrix3.z_axis),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
impl From<rbx_types::CFrame> for CFrame {
|
||||
fn from(value: rbx_types::CFrame) -> Self {
|
||||
CFrame(glam::Affine3A {
|
||||
matrix3: glam::mat3a(
|
||||
vec3_from_glam(value.orientation.x),
|
||||
vec3_from_glam(value.orientation.y),
|
||||
vec3_from_glam(value.orientation.z),
|
||||
),
|
||||
translation: vec3_from_glam(value.position),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_globals(lua: &mlua::Lua, globals: &mlua::Table) -> Result<(), mlua::Error> {
|
||||
let cframe_table = lua.create_table()?;
|
||||
|
||||
//CFrame.new
|
||||
cframe_table.raw_set(
|
||||
"new",
|
||||
lua.create_function(
|
||||
|_,
|
||||
tuple: (
|
||||
mlua::Value,
|
||||
mlua::Value,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
Option<f32>,
|
||||
)| match tuple {
|
||||
//CFrame.new(pos)
|
||||
(
|
||||
mlua::Value::UserData(pos),
|
||||
mlua::Value::Nil,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) => {
|
||||
let pos: Vector3 = pos.take()?;
|
||||
Ok(CFrame::point(pos.0.x, pos.0.y, pos.0.z))
|
||||
}
|
||||
//TODO: CFrame.new(pos,look)
|
||||
(
|
||||
mlua::Value::UserData(pos),
|
||||
mlua::Value::UserData(look),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) => {
|
||||
let _pos: Vector3 = pos.take()?;
|
||||
let _look: Vector3 = look.take()?;
|
||||
Err(mlua::Error::runtime("Not yet implemented"))
|
||||
}
|
||||
//CFrame.new(x,y,z)
|
||||
(
|
||||
mlua::Value::Number(x),
|
||||
mlua::Value::Number(y),
|
||||
Some(z),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
) => Ok(CFrame::point(x as f32, y as f32, z)),
|
||||
//CFrame.new(x,y,z,xx,yx,zx,xy,yy,zy,xz,yz,zz)
|
||||
(
|
||||
mlua::Value::Number(x),
|
||||
mlua::Value::Number(y),
|
||||
Some(z),
|
||||
Some(xx),
|
||||
Some(yx),
|
||||
Some(zx),
|
||||
Some(xy),
|
||||
Some(yy),
|
||||
Some(zy),
|
||||
Some(xz),
|
||||
Some(yz),
|
||||
Some(zz),
|
||||
) => Ok(CFrame::new(
|
||||
x as f32, y as f32, z, xx, yx, zx, xy, yy, zy, xz, yz, zz,
|
||||
)),
|
||||
_ => Err(mlua::Error::runtime("Invalid arguments")),
|
||||
},
|
||||
)?,
|
||||
)?;
|
||||
|
||||
//CFrame.Angles
|
||||
cframe_table.raw_set(
|
||||
"Angles",
|
||||
lua.create_function(|_, (x, y, z): (f32, f32, f32)| Ok(CFrame::angles(x, y, z)))?,
|
||||
)?;
|
||||
|
||||
globals.set("CFrame", cframe_table)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl mlua::UserData for CFrame {
|
||||
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
|
||||
//CFrame.p
|
||||
fields.add_field_method_get("p", |_, this| Ok(Vector3(this.0.translation)));
|
||||
}
|
||||
|
||||
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_method("components", |_, this, ()| {
|
||||
Ok((
|
||||
this.0.translation.x,
|
||||
this.0.translation.y,
|
||||
this.0.translation.z,
|
||||
this.0.matrix3.x_axis.x,
|
||||
this.0.matrix3.y_axis.x,
|
||||
this.0.matrix3.z_axis.x,
|
||||
this.0.matrix3.x_axis.y,
|
||||
this.0.matrix3.y_axis.y,
|
||||
this.0.matrix3.z_axis.y,
|
||||
this.0.matrix3.x_axis.z,
|
||||
this.0.matrix3.y_axis.z,
|
||||
this.0.matrix3.z_axis.z,
|
||||
))
|
||||
});
|
||||
methods.add_method("VectorToWorldSpace", |_, this, v: Vector3| {
|
||||
Ok(Vector3(this.0.transform_vector3a(v.0)))
|
||||
});
|
||||
|
||||
//methods.add_meta_method(mlua::MetaMethod::Mul,|_,this,val:&Vector3|Ok(Vector3(this.0.matrix3*val.0+this.0.translation)));
|
||||
methods.add_meta_function(mlua::MetaMethod::Mul, |_, (this, val): (Self, Self)| {
|
||||
Ok(Self(this.0 * val.0))
|
||||
});
|
||||
methods.add_meta_function(mlua::MetaMethod::ToString, |_, this: Self| {
|
||||
Ok(format!(
|
||||
"CFrame.new({},{},{},{},{},{},{},{},{},{},{},{})",
|
||||
this.0.translation.x,
|
||||
this.0.translation.y,
|
||||
this.0.translation.z,
|
||||
this.0.matrix3.x_axis.x,
|
||||
this.0.matrix3.y_axis.x,
|
||||
this.0.matrix3.z_axis.x,
|
||||
this.0.matrix3.x_axis.y,
|
||||
this.0.matrix3.y_axis.y,
|
||||
this.0.matrix3.z_axis.y,
|
||||
this.0.matrix3.x_axis.z,
|
||||
this.0.matrix3.y_axis.z,
|
||||
this.0.matrix3.z_axis.z,
|
||||
))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
type_from_lua_userdata!(CFrame);
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user