forked from StrafesNET/strafe-project
Compare commits
1148 Commits
load_roblo
...
master
Author | SHA1 | Date | |
---|---|---|---|
2f26662dda | |||
6f739dba8d | |||
2eaddd493d | |||
d5a6f8e1bc | |||
95fb43b908 | |||
3e814cb41a | |||
ea854a548f | |||
5f2cf8f32e | |||
b6b090de78 | |||
affbada62e | |||
8d2ba28700 | |||
3eb4e76ab2 | |||
5d31419370 | |||
44a58044c7 | |||
83ac776b78 | |||
871aadb78a | |||
65b49d2726 | |||
ed70b7c484 | |||
c7575f2e21 | |||
c2f78eab48 | |||
195014400f | |||
ee9585a4c2 | |||
77012d6caa | |||
f9509353dd | |||
5bce4a84cf | |||
4bbccd68ed | |||
8be3be4002 | |||
76bafa4d0a | |||
a612d1f864 | |||
ad9ef141bf | |||
4c11980989 | |||
091da88b5c | |||
045e540020 | |||
c14f8975fc | |||
8e228033e0 | |||
eaecbc5b73 | |||
91a1b3d65e | |||
e1d51ff2da | |||
c5f2395b3a | |||
77aa83d6ac | |||
184b12a9cc | |||
9ba3484e25 | |||
adcd7db4f1 | |||
710670d78e | |||
70e1514ef9 | |||
45e9b3d0ce | |||
1eac734c35 | |||
25e1312fe4 | |||
cc8f6b059d | |||
4cd287dbb8 | |||
40ea0e6c7d | |||
b45b92c627 | |||
e90f53a111 | |||
6beb6c5f9a | |||
a1507d4f26 | |||
77db5a7a6b | |||
75c8bc2bbb | |||
d49a6b2f0a | |||
19778ac7aa | |||
5b62052222 | |||
5a8bc141d3 | |||
83a067320b | |||
2faa61225f | |||
28499800cb | |||
57cc49dc1a | |||
d517b78a8c | |||
e6d1d69241 | |||
b28fa25279 | |||
713b235816 | |||
d2002383cb | |||
52f7de809d | |||
4efe1209b8 | |||
15a9136fc4 | |||
035736e7af | |||
7f9a16a56d | |||
814e573d91 | |||
6fa0f1c83e | |||
870cb56dac | |||
1aac2a9303 | |||
38661b3a68 | |||
7ad76740d4 | |||
c2d6af8bda | |||
fbacef83b9 | |||
2a9e848541 | |||
3413ec8740 | |||
168d6708d1 | |||
13cae4c7c5 | |||
0dc462a3b1 | |||
ca003edbc3 | |||
16abe23e97 | |||
67f8569178 | |||
121c9c5258 | |||
411b997b87 | |||
4d587b6c0b | |||
6ff74f08ab | |||
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 | |||
0ac1c1aa6b | |||
0a75e78c90 | |||
2273f881b2 | |||
ecc8d2395b | |||
c0a85cd719 | |||
3cab9afa32 | |||
e2bd8b4038 | |||
4a53040011 | |||
b1d860edf1 | |||
db6e1e43c1 | |||
03970edeb8 | |||
f91de2d669 | |||
e2da41ec99 | |||
261e42c33d | |||
e0739fa792 | |||
ae4d539ab1 | |||
9de60a8e19 | |||
df8189b874 | |||
45b2af405b | |||
c03cd0e905 | |||
5a0893cbb9 | |||
a2eac57282 | |||
34017ea72c | |||
bcaa28cf6a | |||
b25bcc627d | |||
977069c4eb | |||
63655ef931 | |||
39924db94d | |||
093a54c527 | |||
45dc9e4bd3 | |||
f2635be0fb | |||
f3174cd191 | |||
f58cf4ec2a | |||
6a8f34098d | |||
c1ac2eb1d8 | |||
b8283d921f | |||
32a28880db | |||
38efed0d04 | |||
3b3ccefebb | |||
ae6e4ee6aa | |||
746d3eb7ee | |||
7be93d2114 | |||
2c7ced7151 | |||
0b630576d4 | |||
1c33646765 | |||
3b038687b6 | |||
e7f01eff80 | |||
f58a17adba | |||
3be9730b52 | |||
93eeb3354f | |||
048e408390 | |||
ed0dcdd051 | |||
0f3bd4420e | |||
93bc4dc0fb | |||
c182e46001 | |||
47cdea0c8a | |||
6043c23c19 | |||
274cca6568 | |||
8c9af0e205 | |||
2f70b11abd | |||
aba7fbdb8a | |||
dbb05bd4ef | |||
69bab269db | |||
856386e9bf | |||
3c43b6c6a6 | |||
99351848b7 | |||
3bad427f61 | |||
90cca51e6e | |||
0c439bf2e6 | |||
f38d990a54 | |||
0532b47fec | |||
40e7717aec | |||
3e737282dc | |||
708c0c48ef | |||
20bbb6924e | |||
5ee826d948 | |||
03d1550f60 | |||
79b8120106 | |||
0d2c27f60f | |||
4f56b9b6bd | |||
2496f181e3 | |||
ce6f8074a9 | |||
480cd0e3be | |||
515ca20fb5 | |||
6dff6a2c33 | |||
434ca29aef | |||
dea408daee | |||
7b263ae30e | |||
ae9fc15320 | |||
d5af08730b | |||
14e7e4df29 | |||
040b607792 | |||
5ea8a2db5f | |||
2d420d7676 | |||
6f22aaeec4 | |||
134e3fc79a | |||
b2673f1732 | |||
ec60086109 | |||
6ae058d834 | |||
517c4914ac | |||
6ce057ac64 | |||
fc1c9e843f | |||
a9504a192b | |||
fe2b9921b4 | |||
c86824bdc1 | |||
a7f7edef00 | |||
5b8e5c8899 | |||
14000c016e | |||
1c4191cfc9 | |||
b2f067e0b4 | |||
aec82358ee | |||
5da5006027 | |||
97a1b57b65 | |||
a359650ff8 | |||
1790390055 | |||
49e077996d | |||
9bfcf0b083 | |||
82b3201b0a | |||
513414d4bd | |||
5c4bd4c3c7 | |||
92ec137f33 | |||
5cedf91709 | |||
c201a1a626 | |||
fbae4d9f80 | |||
9374e93801 | |||
0585cfe6f1 | |||
3d96517213 | |||
eeb1561c3d | |||
19e602544a | |||
444c3cbad9 | |||
2006f81804 | |||
de7b7ba7be | |||
008c66e052 | |||
ba250277bd | |||
085d4e7912 | |||
dd6b5bed24 | |||
9d4cadda30 | |||
242b4ab470 | |||
eaef3d3fd5 | |||
9d726c5419 | |||
9592f82c4e | |||
873c6ab935 | |||
7ba94c1b30 | |||
8fc6a7fb8f | |||
a3340143db | |||
9fc7884270 | |||
953b17bc69 | |||
8fcb4e5c6c | |||
073a076fe8 | |||
9848d66001 | |||
6474ddcbd1 | |||
c4cd73e914 | |||
bbb1857377 | |||
883be0bc01 | |||
5885036f8f | |||
076dab23cf | |||
c1001d6f37 | |||
08d4e7d997 | |||
e1f4f6e535 | |||
3a4c4ef9fe | |||
41dd155c50 | |||
5fd6ca219b | |||
1c22f4a3c3 | |||
1e6c489750 | |||
9f62f5f2fd | |||
7be7d2c0df | |||
cb6b0acd44 | |||
cbcf047c3f | |||
6e5de4aa46 | |||
cc776e7cb4 | |||
5a66ac46b9 | |||
38f6e1df3f | |||
849dcf98f7 | |||
d04d1be27e | |||
35bfd1d366 | |||
586bf8ce89 | |||
127b205401 | |||
4f596ca5d7 | |||
87f781a656 | |||
cd9cf164e9 | |||
497ca93071 | |||
747f628f04 | |||
7e1cf7041a | |||
50543ffcea | |||
54498f20f9 | |||
2240b80656 | |||
d18f2168e4 | |||
381b7b3c2f | |||
0d6741a81c | |||
2e8cdf968c | |||
dd0ac7cc7e | |||
e2af6fc4ed | |||
bdc0dd1b3b | |||
95fb316a23 | |||
9dec53d764 | |||
3552491a9a | |||
dd13a066d0 | |||
f3dd43b171 | |||
82d71df94e | |||
684dbda73a | |||
e398da3aa6 | |||
944393dabe | |||
4adce7acd3 | |||
5b935c32fe | |||
436706bc4d | |||
bde24d35a2 | |||
fc91d644e6 | |||
2b47827383 | |||
a942e10554 | |||
5d1e38c36c | |||
e78cabf0f5 | |||
4e90da2228 | |||
9fa4ea6716 | |||
aedef03e7c | |||
6a9af0441f | |||
8cf66f3446 | |||
1cb0d6e586 | |||
12a4bf7948 | |||
f2e4286a08 | |||
bd6cd5eacc | |||
f2dfb438d0 | |||
7c8bc8d647 | |||
4943bc6a7f | |||
55eebba1c5 | |||
b8f13539db | |||
fb2e2afeb9 | |||
f30f246e5f | |||
0ac49308a0 | |||
30cbbbca1b | |||
66fa8fd637 | |||
f2c71caae3 | |||
c8ec1f05d1 | |||
b102319b33 | |||
50e9152ee2 | |||
7a8de938af | |||
696f383aee | |||
bfd6f4493f | |||
ed96572a24 | |||
5914db3599 | |||
f72acaf2d4 | |||
734ce661f2 | |||
bb8c53aee2 | |||
de0eb0790a | |||
9e9550885f | |||
58be446297 | |||
d16404167b | |||
79262ce3b4 | |||
c47020c149 | |||
5854171619 | |||
616b09a857 | |||
6ff2620bbc | |||
d3e4918d3e | |||
6c2eb5ff29 | |||
02a509868a | |||
af750151f7 | |||
bf4560193d | |||
514c45fc21 | |||
95d16271de | |||
355d391ea5 | |||
d8c6444af3 | |||
fddd4576bd | |||
c7538869b4 | |||
923889d956 | |||
215ac47fcb | |||
d86aed5ae1 | |||
92bbbce1c3 | |||
5cd40afa56 | |||
602816a618 | |||
d7010956b3 | |||
b3f7802046 | |||
977c8e565c | |||
4ee29911a3 | |||
9ce9eb50be | |||
ccc94839e5 | |||
c85a84a52e | |||
2df76f020b | |||
7e3bfeb59e | |||
402def667f | |||
d4835187a8 | |||
f36b681614 | |||
a618f305e1 | |||
575d343276 | |||
ac4ba19ed3 | |||
ed712933e5 | |||
665a83d174 | |||
ba21ce262a | |||
5b770fc8a9 | |||
23a1a8690b | |||
60be7f14e5 | |||
37e9299f7d | |||
099865c682 | |||
c65354c23f | |||
1b29db0daf | |||
aa3e717f36 | |||
a06a28c595 | |||
0d6e989812 | |||
da3d0ca254 | |||
e685ef7388 | |||
b5c689f8ff | |||
0913063a00 | |||
f492a09377 | |||
b404908a55 | |||
f0d9c219b6 | |||
8fc87a59ce | |||
f0b3e87abb | |||
836749df47 | |||
31156aadfb | |||
ff7b12e90e | |||
e70dc9ad0f | |||
c5deef8753 | |||
7c2666fdf5 | |||
6da4c81826 | |||
c868a91a06 | |||
b513e4037d | |||
a803ada0e4 | |||
eafcbae677 | |||
2e786b090f | |||
70e8f7a0ad | |||
48091fc15d | |||
23857d38d9 | |||
1c9bc347f6 | |||
c9afa2d059 | |||
1a66dfbaf7 | |||
847209aac4 | |||
42ba757ec0 | |||
1cee3b52ac | |||
e27ce3b507 | |||
bc8f2bd566 | |||
eed932212d | |||
73edb9ff95 | |||
ae0c9e73ee | |||
953d424a57 | |||
ca919b92fd | |||
1de3501e89 | |||
0135b17917 | |||
1878528a4f | |||
25e80a7c17 | |||
21835d13f6 | |||
91f6a5261f | |||
fb4a5efa14 | |||
acb658f3e9 | |||
7e427b3879 | |||
d16485ae6d | |||
cdf695ee6e | |||
5fc4044284 | |||
6f4fda8cc0 | |||
d58f915082 | |||
8e95fe484a | |||
5854128164 | |||
db5b3328bd | |||
4951d1513d | |||
17e71d884f | |||
1dc98d9c2d | |||
fd38502e07 | |||
0632e322cf | |||
7544c6e6ef | |||
2f0a073fd5 | |||
21dc425fc2 | |||
ca141c800c | |||
63ce06f069 | |||
fe14d5e0fa | |||
bffc254a0d | |||
a5f203484b | |||
e67479a9bd | |||
ad7abbdf1c | |||
3a0b3900ec | |||
765ed42b9d | |||
8137a26f81 | |||
21ae7a0e4f | |||
5a886b76d1 | |||
f1e26cb07a | |||
14a74c6e1e | |||
5b55873bd5 | |||
fd5d71e1af | |||
28c3f21736 | |||
a58464efb0 | |||
b070b9706f | |||
dcfbee8de1 | |||
c5636f7fcd | |||
a5aa89064b | |||
0fe14749d3 | |||
8daf432991 | |||
4f5c9afed3 | |||
53605746d4 | |||
cead05b08b | |||
949897a558 | |||
a4ed50fc38 | |||
846f681648 | |||
ff54a03487 | |||
8c2dda5205 | |||
addde65caa | |||
2c4e6f642b | |||
f11742ef3b | |||
e6862b5bad | |||
e18e8a9a7d | |||
f600092f13 | |||
1f1ef5d3ad | |||
54bf0358d6 | |||
6caa273623 | |||
101f0f8d12 | |||
fc751a9fe8 | |||
5391b635fb | |||
230469496e | |||
ea3134de51 | |||
05156d1741 | |||
d72e3fcabd | |||
99d4e51ee8 | |||
7effceff03 | |||
355956e532 | |||
8aad878d49 | |||
5fb9f8b1bc | |||
a9977c0bd2 | |||
fd34fbe367 | |||
75695946a0 | |||
2dba9554ea | |||
b5880d484a | |||
0b230b7597 | |||
988f204358 |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[registries.strafesnet]
|
||||||
|
index = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/"
|
@ -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).
|
|
3018
Cargo.lock
generated
3018
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
39
Cargo.toml
39
Cargo.toml
@ -1,23 +1,24 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "strafe-client"
|
members = [
|
||||||
version = "0.2.0"
|
"engine/graphics",
|
||||||
edition = "2021"
|
"engine/physics",
|
||||||
|
"engine/session",
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
"engine/settings",
|
||||||
|
"integration-testing",
|
||||||
[dependencies]
|
"lib/bsp_loader",
|
||||||
async-executor = "1.5.1"
|
"lib/common",
|
||||||
bytemuck = { version = "1.13.1", features = ["derive"] }
|
"lib/deferred_loader",
|
||||||
ddsfile = "0.5.1"
|
"lib/fixed_wide",
|
||||||
env_logger = "0.10.0"
|
"lib/linear_ops",
|
||||||
glam = "0.24.1"
|
"lib/ratio_ops",
|
||||||
log = "0.4.20"
|
"lib/rbx_loader",
|
||||||
obj = "0.10.2"
|
"lib/roblox_emulator",
|
||||||
pollster = "0.3.0"
|
"lib/snf",
|
||||||
wgpu = "0.17.0"
|
"strafe-client",
|
||||||
winit = "0.28.6"
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
#lto = true
|
||||||
strip = true
|
strip = true
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
18
README.md
18
README.md
@ -1,10 +1,16 @@
|
|||||||
<img align="right" width="25%" src="strafe.png">
|
<img align="right" width="25%" src="logo.png">
|
||||||
|
|
||||||
# Strafe Client
|
# Strafe Project
|
||||||
In development client for jumping on squares (and riding on triangles)
|
Monorepo for working on projects related to strafe client.
|
||||||
|
|
||||||
|
## Try it out
|
||||||
|
See [releases](https://git.itzana.me/StrafesNET/strafe-project/releases) for downloads.
|
||||||
|
|
||||||
## How to build and run
|
## How to build and run
|
||||||
1. Have rust and git installed
|
1. Have rust and git installed
|
||||||
2. `git clone https://git.itzana.me/StrafesNET/strafe-client`
|
2. `git clone https://git.itzana.me/StrafesNET/strafe-project`
|
||||||
3. `cd strafe-client`
|
3. `cd strafe-project`
|
||||||
4. `cargo run --release`
|
4. `cargo run --release --bin strafe-client`
|
||||||
|
|
||||||
|
## Licenses
|
||||||
|
Each project has its own license. Most crates are MIT/Apache but notably the Strafe Client and engine crates have a sole proprietor license.
|
||||||
|
14
engine/graphics/Cargo.toml
Normal file
14
engine/graphics/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_graphics"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytemuck = { version = "1.13.1", features = ["derive"] }
|
||||||
|
ddsfile = "0.5.1"
|
||||||
|
glam = "0.29.0"
|
||||||
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||||
|
strafesnet_session = { path = "../session", registry = "strafesnet" }
|
||||||
|
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
|
||||||
|
wgpu = "24.0.0"
|
@ -1,5 +1,5 @@
|
|||||||
/*******************************************************
|
/*******************************************************
|
||||||
* Copyright (C) 2023 Rhys Lloyd <krakow20@gmail.com>
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
*
|
*
|
||||||
* This file is part of the StrafesNET bhop/surf client.
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
*
|
*
|
988
engine/graphics/src/graphics.rs
Normal file
988
engine/graphics/src/graphics.rs
Normal file
@ -0,0 +1,988 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::{HashSet,HashMap};
|
||||||
|
use strafesnet_common::map;
|
||||||
|
use strafesnet_settings::settings;
|
||||||
|
use strafesnet_session::session;
|
||||||
|
use strafesnet_common::model::{self, ColorId, NormalId, PolygonIter, PositionId, RenderConfigId, TextureCoordinateId, VertexId};
|
||||||
|
use wgpu::{util::DeviceExt,AstcBlock,AstcChannel};
|
||||||
|
use crate::model::{self as model_graphics,IndexedGraphicsMeshOwnedRenderConfig,IndexedGraphicsMeshOwnedRenderConfigId,GraphicsMeshOwnedRenderConfig,GraphicsModelColor4,GraphicsModelOwned,GraphicsVertex};
|
||||||
|
|
||||||
|
pub fn required_limits()->wgpu::Limits{
|
||||||
|
wgpu::Limits::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Indices{
|
||||||
|
count:u32,
|
||||||
|
buf:wgpu::Buffer,
|
||||||
|
format:wgpu::IndexFormat,
|
||||||
|
}
|
||||||
|
impl Indices{
|
||||||
|
fn new<T:bytemuck::Pod>(device:&wgpu::Device,indices:&Vec<T>,format:wgpu::IndexFormat)->Self{
|
||||||
|
Self{
|
||||||
|
buf:device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||||
|
label:Some("Index"),
|
||||||
|
contents:bytemuck::cast_slice(indices),
|
||||||
|
usage:wgpu::BufferUsages::INDEX,
|
||||||
|
}),
|
||||||
|
count:indices.len() as u32,
|
||||||
|
format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct GraphicsModel{
|
||||||
|
indices:Indices,
|
||||||
|
vertex_buf:wgpu::Buffer,
|
||||||
|
bind_group:wgpu::BindGroup,
|
||||||
|
instance_count:u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GraphicsSamplers{
|
||||||
|
repeat:wgpu::Sampler,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GraphicsBindGroupLayouts{
|
||||||
|
model:wgpu::BindGroupLayout,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GraphicsBindGroups{
|
||||||
|
camera:wgpu::BindGroup,
|
||||||
|
skybox_texture:wgpu::BindGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GraphicsPipelines{
|
||||||
|
skybox:wgpu::RenderPipeline,
|
||||||
|
model:wgpu::RenderPipeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GraphicsCamera{
|
||||||
|
screen_size:glam::UVec2,
|
||||||
|
fov:glam::Vec2,//slope
|
||||||
|
//camera angles and such are extrapolated and passed in every time
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn perspective_rh(fov_x_slope:f32,fov_y_slope:f32,z_near:f32,z_far:f32)->glam::Mat4{
|
||||||
|
//glam_assert!(z_near > 0.0 && z_far > 0.0);
|
||||||
|
let r=z_far/(z_near-z_far);
|
||||||
|
glam::Mat4::from_cols(
|
||||||
|
glam::Vec4::new(1.0/fov_x_slope,0.0,0.0,0.0),
|
||||||
|
glam::Vec4::new(0.0,1.0/fov_y_slope,0.0,0.0),
|
||||||
|
glam::Vec4::new(0.0,0.0,r,-1.0),
|
||||||
|
glam::Vec4::new(0.0,0.0,r*z_near,0.0),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
impl GraphicsCamera{
|
||||||
|
pub fn proj(&self)->glam::Mat4{
|
||||||
|
perspective_rh(self.fov.x,self.fov.y,0.4,4000.0)
|
||||||
|
}
|
||||||
|
pub fn world(&self,pos:glam::Vec3,angles:glam::Vec2)->glam::Mat4{
|
||||||
|
//f32 good enough for view matrix
|
||||||
|
glam::Mat4::from_translation(pos)*glam::Mat4::from_euler(glam::EulerRot::YXZ,angles.x,angles.y,0f32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_uniform_data(&self,pos:glam::Vec3,angles:glam::Vec2)->[f32;16*4]{
|
||||||
|
let proj=self.proj();
|
||||||
|
let proj_inv=proj.inverse();
|
||||||
|
let view_inv=self.world(pos,angles);
|
||||||
|
let view=view_inv.inverse();
|
||||||
|
|
||||||
|
let mut raw=[0f32; 16 * 4];
|
||||||
|
raw[..16].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj)[..]);
|
||||||
|
raw[16..32].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&proj_inv)[..]);
|
||||||
|
raw[32..48].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view)[..]);
|
||||||
|
raw[48..64].copy_from_slice(&AsRef::<[f32; 16]>::as_ref(&view_inv)[..]);
|
||||||
|
raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::default::Default for GraphicsCamera{
|
||||||
|
fn default()->Self{
|
||||||
|
Self{
|
||||||
|
screen_size:glam::UVec2::ONE,
|
||||||
|
fov:glam::Vec2::ONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GraphicsState{
|
||||||
|
pipelines:GraphicsPipelines,
|
||||||
|
bind_groups:GraphicsBindGroups,
|
||||||
|
bind_group_layouts:GraphicsBindGroupLayouts,
|
||||||
|
samplers:GraphicsSamplers,
|
||||||
|
camera:GraphicsCamera,
|
||||||
|
camera_buf:wgpu::Buffer,
|
||||||
|
temp_squid_texture_view:wgpu::TextureView,
|
||||||
|
models:Vec<GraphicsModel>,
|
||||||
|
depth_view:wgpu::TextureView,
|
||||||
|
staging_belt:wgpu::util::StagingBelt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicsState{
|
||||||
|
const DEPTH_FORMAT:wgpu::TextureFormat=wgpu::TextureFormat::Depth24Plus;
|
||||||
|
fn create_depth_texture(
|
||||||
|
config:&wgpu::SurfaceConfiguration,
|
||||||
|
device:&wgpu::Device,
|
||||||
|
)->wgpu::TextureView{
|
||||||
|
let depth_texture=device.create_texture(&wgpu::TextureDescriptor{
|
||||||
|
size:wgpu::Extent3d{
|
||||||
|
width:config.width,
|
||||||
|
height:config.height,
|
||||||
|
depth_or_array_layers:1,
|
||||||
|
},
|
||||||
|
mip_level_count:1,
|
||||||
|
sample_count:1,
|
||||||
|
dimension:wgpu::TextureDimension::D2,
|
||||||
|
format:Self::DEPTH_FORMAT,
|
||||||
|
usage:wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
label:None,
|
||||||
|
view_formats:&[],
|
||||||
|
});
|
||||||
|
|
||||||
|
depth_texture.create_view(&wgpu::TextureViewDescriptor::default())
|
||||||
|
}
|
||||||
|
pub fn clear(&mut self){
|
||||||
|
self.models.clear();
|
||||||
|
}
|
||||||
|
pub fn load_user_settings(&mut self,user_settings:&settings::UserSettings){
|
||||||
|
self.camera.fov=user_settings.calculate_fov(1.0,&self.camera.screen_size).as_vec2();
|
||||||
|
}
|
||||||
|
pub fn generate_models(&mut self,device:&wgpu::Device,queue:&wgpu::Queue,map:&map::CompleteMap){
|
||||||
|
//generate texture view per texture
|
||||||
|
let texture_views:HashMap<strafesnet_common::model::TextureId,wgpu::TextureView>=map.textures.iter().enumerate().filter_map(|(texture_id,texture_data)|{
|
||||||
|
let texture_id=model::TextureId::new(texture_id as u32);
|
||||||
|
let image=match ddsfile::Dds::read(std::io::Cursor::new(texture_data)){
|
||||||
|
Ok(image)=>image,
|
||||||
|
Err(e)=>{
|
||||||
|
println!("Error loading texture: {e}");
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let (mut width,mut height)=(image.get_width(),image.get_height());
|
||||||
|
|
||||||
|
let format=match image.header10.unwrap().dxgi_format{
|
||||||
|
ddsfile::DxgiFormat::R8G8B8A8_UNorm_sRGB=>wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
ddsfile::DxgiFormat::BC7_UNorm_sRGB =>{
|
||||||
|
//floor(w,4),should be ceil(w,4)
|
||||||
|
width=width/4*4;
|
||||||
|
height=height/4*4;
|
||||||
|
wgpu::TextureFormat::Bc7RgbaUnormSrgb
|
||||||
|
},
|
||||||
|
other=>{
|
||||||
|
println!("unsupported texture format{:?}",other);
|
||||||
|
return None;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let size=wgpu::Extent3d{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers:1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer_size=wgpu::Extent3d{
|
||||||
|
depth_or_array_layers:1,
|
||||||
|
..size
|
||||||
|
};
|
||||||
|
let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);
|
||||||
|
|
||||||
|
let texture=device.create_texture_with_data(
|
||||||
|
queue,
|
||||||
|
&wgpu::TextureDescriptor{
|
||||||
|
size,
|
||||||
|
mip_level_count:max_mips,
|
||||||
|
sample_count:1,
|
||||||
|
dimension:wgpu::TextureDimension::D2,
|
||||||
|
format,
|
||||||
|
usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
|
||||||
|
label:Some(format!("Texture{}",texture_id.get()).as_str()),
|
||||||
|
view_formats:&[],
|
||||||
|
},
|
||||||
|
wgpu::util::TextureDataOrder::LayerMajor,
|
||||||
|
&image.data,
|
||||||
|
);
|
||||||
|
Some((texture_id,texture.create_view(&wgpu::TextureViewDescriptor{
|
||||||
|
label:Some(format!("Texture{} View",texture_id.get()).as_str()),
|
||||||
|
dimension:Some(wgpu::TextureViewDimension::D2),
|
||||||
|
..wgpu::TextureViewDescriptor::default()
|
||||||
|
})))
|
||||||
|
}).collect();
|
||||||
|
let num_textures=texture_views.len();
|
||||||
|
|
||||||
|
//split groups with different textures into separate models
|
||||||
|
//the models received here are supposed to be tightly packed,i.e. no code needs to check if two models are using the same groups.
|
||||||
|
let indexed_models_len=map.models.len();
|
||||||
|
//models split into graphics_group.RenderConfigId
|
||||||
|
let mut owned_mesh_id_from_mesh_id_render_config_id:HashMap<model::MeshId,HashMap<RenderConfigId,IndexedGraphicsMeshOwnedRenderConfigId>>=HashMap::new();
|
||||||
|
let mut unique_render_config_models:Vec<IndexedGraphicsMeshOwnedRenderConfig>=Vec::with_capacity(indexed_models_len);
|
||||||
|
for model in &map.models{
|
||||||
|
//wow
|
||||||
|
let instance=GraphicsModelOwned{
|
||||||
|
transform:model.transform.into(),
|
||||||
|
normal_transform:glam::Mat3::from_cols_array_2d(&model.transform.matrix3.to_array().map(|row|row.map(Into::into))).inverse().transpose(),
|
||||||
|
color:GraphicsModelColor4::new(model.color),
|
||||||
|
};
|
||||||
|
//get or create owned mesh map
|
||||||
|
let owned_mesh_map=owned_mesh_id_from_mesh_id_render_config_id
|
||||||
|
.entry(model.mesh).or_insert_with(||{
|
||||||
|
let mut owned_mesh_map=HashMap::new();
|
||||||
|
//add mesh if renderid never before seen for this model
|
||||||
|
//add instance
|
||||||
|
//convert Model into GraphicsModelOwned
|
||||||
|
//check each group, if it's using a new render config then make a new clone of the model
|
||||||
|
if let Some(mesh)=map.meshes.get(model.mesh.get() as usize){
|
||||||
|
for graphics_group in mesh.graphics_groups.iter(){
|
||||||
|
//get or create owned mesh
|
||||||
|
let owned_mesh_id=owned_mesh_map
|
||||||
|
.entry(graphics_group.render).or_insert_with(||{
|
||||||
|
//create
|
||||||
|
let owned_mesh_id=IndexedGraphicsMeshOwnedRenderConfigId::new(unique_render_config_models.len() as u32);
|
||||||
|
unique_render_config_models.push(IndexedGraphicsMeshOwnedRenderConfig{
|
||||||
|
unique_pos:mesh.unique_pos.iter().map(|v|v.to_array().map(Into::into)).collect(),
|
||||||
|
unique_tex:mesh.unique_tex.iter().map(|v|*v.as_ref()).collect(),
|
||||||
|
unique_normal:mesh.unique_normal.iter().map(|v|v.to_array().map(Into::into)).collect(),
|
||||||
|
unique_color:mesh.unique_color.iter().map(|v|*v.as_ref()).collect(),
|
||||||
|
unique_vertices:mesh.unique_vertices.clone(),
|
||||||
|
render_config:graphics_group.render,
|
||||||
|
polys:model::PolygonGroup::PolygonList(model::PolygonList::new(Vec::new())),
|
||||||
|
instances:Vec::new(),
|
||||||
|
});
|
||||||
|
owned_mesh_id
|
||||||
|
});
|
||||||
|
let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
|
||||||
|
match &mut owned_mesh.polys{
|
||||||
|
model::PolygonGroup::PolygonList(polygon_list)=>polygon_list.extend(
|
||||||
|
graphics_group.groups.iter().flat_map(|polygon_group_id|{
|
||||||
|
mesh.polygon_groups[polygon_group_id.get() as usize].polys()
|
||||||
|
})
|
||||||
|
.map(|vertex_id_slice|
|
||||||
|
vertex_id_slice.to_vec()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
owned_mesh_map
|
||||||
|
});
|
||||||
|
for owned_mesh_id in owned_mesh_map.values(){
|
||||||
|
let owned_mesh=unique_render_config_models.get_mut(owned_mesh_id.get() as usize).unwrap();
|
||||||
|
let render_config=&map.render_configs[owned_mesh.render_config.get() as usize];
|
||||||
|
if model.color.w==0.0&&render_config.texture.is_none(){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
owned_mesh.instances.push(instance.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//check every model to see if it's using the same (texture,color) but has few instances,if it is combine it into one model
|
||||||
|
//1. collect unique instances of texture and color,note model id
|
||||||
|
//2. for each model id,check if removing it from the pool decreases both the model count and instance count by more than one
|
||||||
|
//3. transpose all models that stay in the set
|
||||||
|
|
||||||
|
//best plan:benchmark set_bind_group,set_vertex_buffer,set_index_buffer and draw_indexed
|
||||||
|
//check if the estimated render performance is better by transposing multiple model instances into one model instance
|
||||||
|
|
||||||
|
//for now:just deduplicate single models...
|
||||||
|
let mut deduplicated_models=Vec::with_capacity(indexed_models_len);//use indexed_models_len because the list will likely get smaller instead of bigger
|
||||||
|
let mut unique_texture_color=HashMap::new();//texture->color->vec![(model_id,instance_id)]
|
||||||
|
for (model_id,model) in unique_render_config_models.iter().enumerate(){
|
||||||
|
//for now:filter out models with more than one instance
|
||||||
|
if 1<model.instances.len(){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//populate hashmap
|
||||||
|
let unique_color=unique_texture_color
|
||||||
|
.entry(model.render_config)
|
||||||
|
.or_insert_with(||HashMap::new());
|
||||||
|
//separate instances by color
|
||||||
|
for (instance_id,instance) in model.instances.iter().enumerate(){
|
||||||
|
let model_instance_list=unique_color
|
||||||
|
.entry(instance.color)
|
||||||
|
.or_insert_with(||Vec::new());
|
||||||
|
//add model instance to list
|
||||||
|
model_instance_list.push((model_id,instance_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//populate a hashset of models selected for transposition
|
||||||
|
//construct transposed models
|
||||||
|
let mut selected_model_instances=HashSet::new();
|
||||||
|
for (render_config,unique_color) in unique_texture_color.into_iter(){
|
||||||
|
for (color,model_instance_list) in unique_color.into_iter(){
|
||||||
|
//world transforming one model does not meet the definition of deduplicaiton
|
||||||
|
if 1<model_instance_list.len(){
|
||||||
|
//create model
|
||||||
|
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 polys=Vec::new();
|
||||||
|
//transform instance vertices
|
||||||
|
for (model_id,instance_id) in model_instance_list.into_iter(){
|
||||||
|
//populate hashset to prevent these models from being copied
|
||||||
|
selected_model_instances.insert(model_id);
|
||||||
|
//there is only one instance per model
|
||||||
|
let model=&unique_render_config_models[model_id];
|
||||||
|
let instance=&model.instances[instance_id];
|
||||||
|
//just hash word slices LOL
|
||||||
|
let map_pos_id:Vec<PositionId>=model.unique_pos.iter().map(|untransformed_pos|{
|
||||||
|
let pos=instance.transform.transform_point3(glam::Vec3::from_array(untransformed_pos.clone())).to_array();
|
||||||
|
let h=bytemuck::cast::<[f32;3],[u32;3]>(pos);
|
||||||
|
PositionId::new(*pos_id_from.entry(h).or_insert_with(||{
|
||||||
|
let pos_id=unique_pos.len();
|
||||||
|
unique_pos.push(pos);
|
||||||
|
pos_id
|
||||||
|
}) as u32)
|
||||||
|
}).collect();
|
||||||
|
let map_tex_id:Vec<TextureCoordinateId>=model.unique_tex.iter().map(|&tex|{
|
||||||
|
let h=bytemuck::cast::<[f32;2],[u32;2]>(tex);
|
||||||
|
TextureCoordinateId::new(*tex_id_from.entry(h).or_insert_with(||{
|
||||||
|
let tex_id=unique_tex.len();
|
||||||
|
unique_tex.push(tex);
|
||||||
|
tex_id
|
||||||
|
}) as u32)
|
||||||
|
}).collect();
|
||||||
|
let map_normal_id:Vec<NormalId>=model.unique_normal.iter().map(|untransformed_normal|{
|
||||||
|
let normal=(instance.normal_transform*glam::Vec3::from_array(untransformed_normal.clone())).to_array();
|
||||||
|
let h=bytemuck::cast::<[f32;3],[u32;3]>(normal);
|
||||||
|
NormalId::new(*normal_id_from.entry(h).or_insert_with(||{
|
||||||
|
let normal_id=unique_normal.len();
|
||||||
|
unique_normal.push(normal);
|
||||||
|
normal_id
|
||||||
|
}) as u32)
|
||||||
|
}).collect();
|
||||||
|
let map_color_id:Vec<ColorId>=model.unique_color.iter().map(|&color|{
|
||||||
|
let h=bytemuck::cast::<[f32;4],[u32;4]>(color);
|
||||||
|
ColorId::new(*color_id_from.entry(h).or_insert_with(||{
|
||||||
|
let color_id=unique_color.len();
|
||||||
|
unique_color.push(color);
|
||||||
|
color_id
|
||||||
|
}) as u32)
|
||||||
|
}).collect();
|
||||||
|
//map the indexed vertices onto new indices
|
||||||
|
//creating the vertex map is slightly different because the vertices are directly hashable
|
||||||
|
let map_vertex_id:Vec<VertexId>=model.unique_vertices.iter().map(|unmapped_vertex|{
|
||||||
|
let vertex=model::IndexedVertex{
|
||||||
|
pos:map_pos_id[unmapped_vertex.pos.get() as usize],
|
||||||
|
tex:map_tex_id[unmapped_vertex.tex.get() as usize],
|
||||||
|
normal:map_normal_id[unmapped_vertex.normal.get() as usize],
|
||||||
|
color:map_color_id[unmapped_vertex.color.get() as usize],
|
||||||
|
};
|
||||||
|
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)
|
||||||
|
}).collect();
|
||||||
|
polys.extend(model.polys.polys().map(|poly|
|
||||||
|
poly.iter().map(|vertex_id|
|
||||||
|
map_vertex_id[vertex_id.get() as usize]
|
||||||
|
).collect()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
//push model into dedup
|
||||||
|
deduplicated_models.push(IndexedGraphicsMeshOwnedRenderConfig{
|
||||||
|
unique_pos,
|
||||||
|
unique_tex,
|
||||||
|
unique_normal,
|
||||||
|
unique_color,
|
||||||
|
unique_vertices,
|
||||||
|
render_config,
|
||||||
|
polys:model::PolygonGroup::PolygonList(model::PolygonList::new(polys)),
|
||||||
|
instances:vec![GraphicsModelOwned{
|
||||||
|
transform:glam::Mat4::IDENTITY,
|
||||||
|
normal_transform:glam::Mat3::IDENTITY,
|
||||||
|
color
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//fill untouched models
|
||||||
|
for (model_id,model) in unique_render_config_models.into_iter().enumerate(){
|
||||||
|
if !selected_model_instances.contains(&model_id){
|
||||||
|
deduplicated_models.push(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//de-index models
|
||||||
|
let deduplicated_models_len=deduplicated_models.len();
|
||||||
|
let models:Vec<GraphicsMeshOwnedRenderConfig>=deduplicated_models.into_iter().map(|model|{
|
||||||
|
let mut vertices=Vec::new();
|
||||||
|
let mut index_from_vertex=HashMap::new();//::<IndexedVertex,usize>
|
||||||
|
//this mut be combined in a more complex way if the models use different render patterns per group
|
||||||
|
let mut indices=Vec::new();
|
||||||
|
for poly in model.polys.polys(){
|
||||||
|
let mut poly_vertices=poly.iter()
|
||||||
|
.map(|&vertex_index|*index_from_vertex.entry(vertex_index).or_insert_with(||{
|
||||||
|
let i=vertices.len();
|
||||||
|
let vertex=&model.unique_vertices[vertex_index.get() as usize];
|
||||||
|
vertices.push(GraphicsVertex{
|
||||||
|
pos:model.unique_pos[vertex.pos.get() as usize],
|
||||||
|
tex:model.unique_tex[vertex.tex.get() as usize],
|
||||||
|
normal:model.unique_normal[vertex.normal.get() as usize],
|
||||||
|
color:model.unique_color[vertex.color.get() as usize],
|
||||||
|
});
|
||||||
|
i
|
||||||
|
}));
|
||||||
|
|
||||||
|
let a=poly_vertices.next().unwrap();
|
||||||
|
let mut b=poly_vertices.next().unwrap();
|
||||||
|
|
||||||
|
poly_vertices.for_each(|c|{
|
||||||
|
indices.extend([a,b,c]);
|
||||||
|
b=c;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
GraphicsMeshOwnedRenderConfig{
|
||||||
|
instances:model.instances,
|
||||||
|
indices:if (u32::MAX as usize)<vertices.len(){
|
||||||
|
panic!("Model has too many vertices!")
|
||||||
|
}else if (u16::MAX as usize)<vertices.len(){
|
||||||
|
model_graphics::Indices::U32(indices.into_iter().map(|vertex_idx|vertex_idx as u32).collect())
|
||||||
|
}else{
|
||||||
|
model_graphics::Indices::U16(indices.into_iter().map(|vertex_idx|vertex_idx as u16).collect())
|
||||||
|
},
|
||||||
|
vertices,
|
||||||
|
render_config:model.render_config,
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
//.into_iter() the modeldata vec so entities can be /moved/ to models.entities
|
||||||
|
let mut model_count=0;
|
||||||
|
let mut instance_count=0;
|
||||||
|
let uniform_buffer_binding_size=required_limits().max_uniform_buffer_binding_size as usize;
|
||||||
|
let chunk_size=uniform_buffer_binding_size/MODEL_BUFFER_SIZE_BYTES;
|
||||||
|
self.models.reserve(models.len());
|
||||||
|
for model in models.into_iter(){
|
||||||
|
instance_count+=model.instances.len();
|
||||||
|
for instances_chunk in model.instances.rchunks(chunk_size){
|
||||||
|
model_count+=1;
|
||||||
|
let mut model_uniforms=get_instances_buffer_data(instances_chunk);
|
||||||
|
//TEMP: fill with zeroes to pass validation
|
||||||
|
model_uniforms.resize(MODEL_BUFFER_SIZE*512,0.0f32);
|
||||||
|
let model_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||||
|
label:Some(format!("Model{} Buf",model_count).as_str()),
|
||||||
|
contents:bytemuck::cast_slice(&model_uniforms),
|
||||||
|
usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
|
||||||
|
});
|
||||||
|
let render_config=&map.render_configs[model.render_config.get() as usize];
|
||||||
|
let texture_view=render_config.texture.and_then(|texture_id|
|
||||||
|
texture_views.get(&texture_id)
|
||||||
|
).unwrap_or(&self.temp_squid_texture_view);
|
||||||
|
let bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
|
||||||
|
layout:&self.bind_group_layouts.model,
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupEntry{
|
||||||
|
binding:0,
|
||||||
|
resource:model_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry{
|
||||||
|
binding:1,
|
||||||
|
resource:wgpu::BindingResource::TextureView(texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry{
|
||||||
|
binding:2,
|
||||||
|
resource:wgpu::BindingResource::Sampler(&self.samplers.repeat),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label:Some(format!("Model{} Bind Group",model_count).as_str()),
|
||||||
|
});
|
||||||
|
let vertex_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||||
|
label:Some("Vertex"),
|
||||||
|
contents:bytemuck::cast_slice(&model.vertices),
|
||||||
|
usage:wgpu::BufferUsages::VERTEX,
|
||||||
|
});
|
||||||
|
//all of these are being moved here
|
||||||
|
self.models.push(GraphicsModel{
|
||||||
|
instance_count:instances_chunk.len() as u32,
|
||||||
|
vertex_buf,
|
||||||
|
indices:match &model.indices{
|
||||||
|
model_graphics::Indices::U32(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint32),
|
||||||
|
model_graphics::Indices::U16(indices)=>Indices::new(device,indices,wgpu::IndexFormat::Uint16),
|
||||||
|
},
|
||||||
|
bind_group,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println!("Texture References={}",num_textures);
|
||||||
|
println!("Textures Loaded={}",texture_views.len());
|
||||||
|
println!("Indexed Models={}",indexed_models_len);
|
||||||
|
println!("Deduplicated Models={}",deduplicated_models_len);
|
||||||
|
println!("Graphics Objects:{}",self.models.len());
|
||||||
|
println!("Graphics Instances:{}",instance_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(
|
||||||
|
device:&wgpu::Device,
|
||||||
|
queue:&wgpu::Queue,
|
||||||
|
config:&wgpu::SurfaceConfiguration,
|
||||||
|
)->Self{
|
||||||
|
let camera_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
|
||||||
|
label:None,
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupLayoutEntry{
|
||||||
|
binding:0,
|
||||||
|
visibility:wgpu::ShaderStages::VERTEX,
|
||||||
|
ty:wgpu::BindingType::Buffer{
|
||||||
|
ty:wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset:false,
|
||||||
|
min_binding_size:None,
|
||||||
|
},
|
||||||
|
count:None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
let skybox_texture_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
|
||||||
|
label:Some("Skybox Texture Bind Group Layout"),
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupLayoutEntry{
|
||||||
|
binding:0,
|
||||||
|
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty:wgpu::BindingType::Texture{
|
||||||
|
sample_type:wgpu::TextureSampleType::Float{filterable:true},
|
||||||
|
multisampled:false,
|
||||||
|
view_dimension:wgpu::TextureViewDimension::Cube,
|
||||||
|
},
|
||||||
|
count:None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry{
|
||||||
|
binding:1,
|
||||||
|
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
|
count:None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
let model_bind_group_layout=device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor{
|
||||||
|
label:Some("Model Bind Group Layout"),
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupLayoutEntry{
|
||||||
|
binding:0,
|
||||||
|
visibility:wgpu::ShaderStages::VERTEX,
|
||||||
|
ty:wgpu::BindingType::Buffer{
|
||||||
|
ty:wgpu::BufferBindingType::Uniform,
|
||||||
|
has_dynamic_offset:false,
|
||||||
|
min_binding_size:None,
|
||||||
|
},
|
||||||
|
count:None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry{
|
||||||
|
binding:1,
|
||||||
|
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty:wgpu::BindingType::Texture{
|
||||||
|
sample_type:wgpu::TextureSampleType::Float{filterable:true},
|
||||||
|
multisampled:false,
|
||||||
|
view_dimension:wgpu::TextureViewDimension::D2,
|
||||||
|
},
|
||||||
|
count:None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry{
|
||||||
|
binding:2,
|
||||||
|
visibility:wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty:wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
|
count:None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
let clamp_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
|
||||||
|
label:Some("Clamp Sampler"),
|
||||||
|
address_mode_u:wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v:wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w:wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter:wgpu::FilterMode::Linear,
|
||||||
|
min_filter:wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter:wgpu::FilterMode::Linear,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
let repeat_sampler=device.create_sampler(&wgpu::SamplerDescriptor{
|
||||||
|
label:Some("Repeat Sampler"),
|
||||||
|
address_mode_u:wgpu::AddressMode::Repeat,
|
||||||
|
address_mode_v:wgpu::AddressMode::Repeat,
|
||||||
|
address_mode_w:wgpu::AddressMode::Repeat,
|
||||||
|
mag_filter:wgpu::FilterMode::Linear,
|
||||||
|
min_filter:wgpu::FilterMode::Linear,
|
||||||
|
mipmap_filter:wgpu::FilterMode::Linear,
|
||||||
|
anisotropy_clamp:16,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipeline
|
||||||
|
let shader=device.create_shader_module(wgpu::ShaderModuleDescriptor{
|
||||||
|
label:None,
|
||||||
|
source:wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../../../strafe-client/src/shader.wgsl"))),
|
||||||
|
});
|
||||||
|
|
||||||
|
//load textures
|
||||||
|
let device_features=device.features();
|
||||||
|
|
||||||
|
let skybox_texture_view={
|
||||||
|
let skybox_format=if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ASTC){
|
||||||
|
println!("Using ASTC");
|
||||||
|
wgpu::TextureFormat::Astc{
|
||||||
|
block:AstcBlock::B4x4,
|
||||||
|
channel:AstcChannel::UnormSrgb,
|
||||||
|
}
|
||||||
|
}else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_ETC2){
|
||||||
|
println!("Using ETC2");
|
||||||
|
wgpu::TextureFormat::Etc2Rgb8UnormSrgb
|
||||||
|
}else if device_features.contains(wgpu::Features::TEXTURE_COMPRESSION_BC){
|
||||||
|
println!("Using BC");
|
||||||
|
wgpu::TextureFormat::Bc1RgbaUnormSrgb
|
||||||
|
}else{
|
||||||
|
println!("Using plain");
|
||||||
|
wgpu::TextureFormat::Bgra8UnormSrgb
|
||||||
|
};
|
||||||
|
|
||||||
|
let bytes=match skybox_format{
|
||||||
|
wgpu::TextureFormat::Astc{
|
||||||
|
block:AstcBlock::B4x4,
|
||||||
|
channel:AstcChannel::UnormSrgb,
|
||||||
|
}=>&include_bytes!("../../../strafe-client/images/astc.dds")[..],
|
||||||
|
wgpu::TextureFormat::Etc2Rgb8UnormSrgb=>&include_bytes!("../../../strafe-client/images/etc2.dds")[..],
|
||||||
|
wgpu::TextureFormat::Bc1RgbaUnormSrgb=>&include_bytes!("../../../strafe-client/images/bc1.dds")[..],
|
||||||
|
wgpu::TextureFormat::Bgra8UnormSrgb=>&include_bytes!("../../../strafe-client/images/bgra.dds")[..],
|
||||||
|
_=>unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let skybox_image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
|
||||||
|
|
||||||
|
let size=wgpu::Extent3d{
|
||||||
|
width:skybox_image.get_width(),
|
||||||
|
height:skybox_image.get_height(),
|
||||||
|
depth_or_array_layers:6,
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer_size=wgpu::Extent3d{
|
||||||
|
depth_or_array_layers:1,
|
||||||
|
..size
|
||||||
|
};
|
||||||
|
let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);
|
||||||
|
|
||||||
|
let skybox_texture=device.create_texture_with_data(
|
||||||
|
queue,
|
||||||
|
&wgpu::TextureDescriptor{
|
||||||
|
size,
|
||||||
|
mip_level_count:max_mips,
|
||||||
|
sample_count:1,
|
||||||
|
dimension:wgpu::TextureDimension::D2,
|
||||||
|
format:skybox_format,
|
||||||
|
usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
|
||||||
|
label:Some("Skybox Texture"),
|
||||||
|
view_formats:&[],
|
||||||
|
},
|
||||||
|
wgpu::util::TextureDataOrder::LayerMajor,
|
||||||
|
&skybox_image.data,
|
||||||
|
);
|
||||||
|
|
||||||
|
skybox_texture.create_view(&wgpu::TextureViewDescriptor{
|
||||||
|
label:Some("Skybox Texture View"),
|
||||||
|
dimension:Some(wgpu::TextureViewDimension::Cube),
|
||||||
|
..wgpu::TextureViewDescriptor::default()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
//squid
|
||||||
|
let squid_texture_view={
|
||||||
|
let bytes=include_bytes!("../../../strafe-client/images/squid.dds");
|
||||||
|
|
||||||
|
let image=ddsfile::Dds::read(&mut std::io::Cursor::new(bytes)).unwrap();
|
||||||
|
|
||||||
|
let size=wgpu::Extent3d{
|
||||||
|
width:image.get_width(),
|
||||||
|
height:image.get_height(),
|
||||||
|
depth_or_array_layers:1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer_size=wgpu::Extent3d{
|
||||||
|
depth_or_array_layers:1,
|
||||||
|
..size
|
||||||
|
};
|
||||||
|
let max_mips=layer_size.max_mips(wgpu::TextureDimension::D2);
|
||||||
|
|
||||||
|
let texture=device.create_texture_with_data(
|
||||||
|
queue,
|
||||||
|
&wgpu::TextureDescriptor{
|
||||||
|
size,
|
||||||
|
mip_level_count:max_mips,
|
||||||
|
sample_count:1,
|
||||||
|
dimension:wgpu::TextureDimension::D2,
|
||||||
|
format:wgpu::TextureFormat::Bc7RgbaUnorm,
|
||||||
|
usage:wgpu::TextureUsages::TEXTURE_BINDING|wgpu::TextureUsages::COPY_DST,
|
||||||
|
label:Some("Squid Texture"),
|
||||||
|
view_formats:&[],
|
||||||
|
},
|
||||||
|
wgpu::util::TextureDataOrder::LayerMajor,
|
||||||
|
&image.data,
|
||||||
|
);
|
||||||
|
|
||||||
|
texture.create_view(&wgpu::TextureViewDescriptor{
|
||||||
|
label:Some("Squid Texture View"),
|
||||||
|
dimension:Some(wgpu::TextureViewDimension::D2),
|
||||||
|
..wgpu::TextureViewDescriptor::default()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let model_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
|
||||||
|
label:None,
|
||||||
|
bind_group_layouts:&[
|
||||||
|
&camera_bind_group_layout,
|
||||||
|
&skybox_texture_bind_group_layout,
|
||||||
|
&model_bind_group_layout,
|
||||||
|
],
|
||||||
|
push_constant_ranges:&[],
|
||||||
|
});
|
||||||
|
let sky_pipeline_layout=device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor{
|
||||||
|
label:None,
|
||||||
|
bind_group_layouts:&[
|
||||||
|
&camera_bind_group_layout,
|
||||||
|
&skybox_texture_bind_group_layout,
|
||||||
|
],
|
||||||
|
push_constant_ranges:&[],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the render pipelines
|
||||||
|
let sky_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
|
||||||
|
label:Some("Sky Pipeline"),
|
||||||
|
layout:Some(&sky_pipeline_layout),
|
||||||
|
vertex:wgpu::VertexState{
|
||||||
|
module:&shader,
|
||||||
|
entry_point:Some("vs_sky"),
|
||||||
|
buffers:&[],
|
||||||
|
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
fragment:Some(wgpu::FragmentState{
|
||||||
|
module:&shader,
|
||||||
|
entry_point:Some("fs_sky"),
|
||||||
|
targets:&[Some(config.view_formats[0].into())],
|
||||||
|
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
primitive:wgpu::PrimitiveState{
|
||||||
|
front_face:wgpu::FrontFace::Cw,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil:Some(wgpu::DepthStencilState{
|
||||||
|
format:Self::DEPTH_FORMAT,
|
||||||
|
depth_write_enabled:false,
|
||||||
|
depth_compare:wgpu::CompareFunction::LessEqual,
|
||||||
|
stencil:wgpu::StencilState::default(),
|
||||||
|
bias:wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample:wgpu::MultisampleState::default(),
|
||||||
|
multiview:None,
|
||||||
|
cache:None,
|
||||||
|
});
|
||||||
|
let model_pipeline=device.create_render_pipeline(&wgpu::RenderPipelineDescriptor{
|
||||||
|
label:Some("Model Pipeline"),
|
||||||
|
layout:Some(&model_pipeline_layout),
|
||||||
|
vertex:wgpu::VertexState{
|
||||||
|
module:&shader,
|
||||||
|
entry_point:Some("vs_entity_texture"),
|
||||||
|
buffers:&[wgpu::VertexBufferLayout{
|
||||||
|
array_stride:std::mem::size_of::<GraphicsVertex>() as wgpu::BufferAddress,
|
||||||
|
step_mode:wgpu::VertexStepMode::Vertex,
|
||||||
|
attributes:&wgpu::vertex_attr_array![0=>Float32x3,1=>Float32x2,2=>Float32x3,3=>Float32x4],
|
||||||
|
}],
|
||||||
|
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||||
|
},
|
||||||
|
fragment:Some(wgpu::FragmentState{
|
||||||
|
module:&shader,
|
||||||
|
entry_point:Some("fs_entity_texture"),
|
||||||
|
targets:&[Some(config.view_formats[0].into())],
|
||||||
|
compilation_options:wgpu::PipelineCompilationOptions::default(),
|
||||||
|
}),
|
||||||
|
primitive:wgpu::PrimitiveState{
|
||||||
|
front_face:wgpu::FrontFace::Cw,
|
||||||
|
cull_mode:Some(wgpu::Face::Front),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
depth_stencil:Some(wgpu::DepthStencilState{
|
||||||
|
format:Self::DEPTH_FORMAT,
|
||||||
|
depth_write_enabled:true,
|
||||||
|
depth_compare:wgpu::CompareFunction::LessEqual,
|
||||||
|
stencil:wgpu::StencilState::default(),
|
||||||
|
bias:wgpu::DepthBiasState::default(),
|
||||||
|
}),
|
||||||
|
multisample:wgpu::MultisampleState::default(),
|
||||||
|
multiview:None,
|
||||||
|
cache:None,
|
||||||
|
});
|
||||||
|
|
||||||
|
let camera=GraphicsCamera::default();
|
||||||
|
let camera_uniforms=camera.to_uniform_data(glam::Vec3::ZERO,glam::Vec2::ZERO);
|
||||||
|
let camera_buf=device.create_buffer_init(&wgpu::util::BufferInitDescriptor{
|
||||||
|
label:Some("Camera"),
|
||||||
|
contents:bytemuck::cast_slice(&camera_uniforms),
|
||||||
|
usage:wgpu::BufferUsages::UNIFORM|wgpu::BufferUsages::COPY_DST,
|
||||||
|
});
|
||||||
|
let camera_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
|
||||||
|
layout:&camera_bind_group_layout,
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupEntry{
|
||||||
|
binding:0,
|
||||||
|
resource:camera_buf.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label:Some("Camera"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let skybox_texture_bind_group=device.create_bind_group(&wgpu::BindGroupDescriptor{
|
||||||
|
layout:&skybox_texture_bind_group_layout,
|
||||||
|
entries:&[
|
||||||
|
wgpu::BindGroupEntry{
|
||||||
|
binding:0,
|
||||||
|
resource:wgpu::BindingResource::TextureView(&skybox_texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry{
|
||||||
|
binding:1,
|
||||||
|
resource:wgpu::BindingResource::Sampler(&clamp_sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label:Some("Sky Texture"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let depth_view=Self::create_depth_texture(config,device);
|
||||||
|
|
||||||
|
Self{
|
||||||
|
pipelines:GraphicsPipelines{
|
||||||
|
skybox:sky_pipeline,
|
||||||
|
model:model_pipeline
|
||||||
|
},
|
||||||
|
bind_groups:GraphicsBindGroups{
|
||||||
|
camera:camera_bind_group,
|
||||||
|
skybox_texture:skybox_texture_bind_group,
|
||||||
|
},
|
||||||
|
camera,
|
||||||
|
camera_buf,
|
||||||
|
models:Vec::new(),
|
||||||
|
depth_view,
|
||||||
|
staging_belt:wgpu::util::StagingBelt::new(0x100),
|
||||||
|
bind_group_layouts:GraphicsBindGroupLayouts{model:model_bind_group_layout},
|
||||||
|
samplers:GraphicsSamplers{repeat:repeat_sampler},
|
||||||
|
temp_squid_texture_view:squid_texture_view,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn resize(
|
||||||
|
&mut self,
|
||||||
|
device:&wgpu::Device,
|
||||||
|
config:&wgpu::SurfaceConfiguration,
|
||||||
|
user_settings:&settings::UserSettings,
|
||||||
|
){
|
||||||
|
self.depth_view=Self::create_depth_texture(config,device);
|
||||||
|
self.camera.screen_size=glam::uvec2(config.width,config.height);
|
||||||
|
self.load_user_settings(user_settings);
|
||||||
|
}
|
||||||
|
pub fn render(
|
||||||
|
&mut self,
|
||||||
|
view:&wgpu::TextureView,
|
||||||
|
device:&wgpu::Device,
|
||||||
|
queue:&wgpu::Queue,
|
||||||
|
frame_state:session::FrameState,
|
||||||
|
){
|
||||||
|
//TODO:use scheduled frame times to create beautiful smoothing simulation physics extrapolation assuming no input
|
||||||
|
|
||||||
|
let mut encoder=device.create_command_encoder(&wgpu::CommandEncoderDescriptor{label:None});
|
||||||
|
|
||||||
|
// update rotation
|
||||||
|
let camera_uniforms=self.camera.to_uniform_data(
|
||||||
|
frame_state.body.extrapolated_position(frame_state.time).map(Into::<f32>::into).to_array().into(),
|
||||||
|
frame_state.camera.simulate_move_angles(glam::IVec2::ZERO)
|
||||||
|
);
|
||||||
|
self.staging_belt
|
||||||
|
.write_buffer(
|
||||||
|
&mut encoder,
|
||||||
|
&self.camera_buf,
|
||||||
|
0,
|
||||||
|
wgpu::BufferSize::new((camera_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
|
||||||
|
device,
|
||||||
|
)
|
||||||
|
.copy_from_slice(bytemuck::cast_slice(&camera_uniforms));
|
||||||
|
//This code only needs to run when the uniforms change
|
||||||
|
/*
|
||||||
|
for model in self.models.iter(){
|
||||||
|
let model_uniforms=get_instances_buffer_data(&model.instances);
|
||||||
|
self.staging_belt
|
||||||
|
.write_buffer(
|
||||||
|
&mut encoder,
|
||||||
|
&model.model_buf,//description of where data will be written when command is executed
|
||||||
|
0,//offset in staging belt?
|
||||||
|
wgpu::BufferSize::new((model_uniforms.len() * 4) as wgpu::BufferAddress).unwrap(),
|
||||||
|
device,
|
||||||
|
)
|
||||||
|
.copy_from_slice(bytemuck::cast_slice(&model_uniforms));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
self.staging_belt.finish();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut rpass=encoder.begin_render_pass(&wgpu::RenderPassDescriptor{
|
||||||
|
label:None,
|
||||||
|
color_attachments:&[Some(wgpu::RenderPassColorAttachment{
|
||||||
|
view,
|
||||||
|
resolve_target:None,
|
||||||
|
ops:wgpu::Operations{
|
||||||
|
load:wgpu::LoadOp::Clear(wgpu::Color{
|
||||||
|
r:0.1,
|
||||||
|
g:0.2,
|
||||||
|
b:0.3,
|
||||||
|
a:1.0,
|
||||||
|
}),
|
||||||
|
store:wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment:Some(wgpu::RenderPassDepthStencilAttachment{
|
||||||
|
view:&self.depth_view,
|
||||||
|
depth_ops:Some(wgpu::Operations{
|
||||||
|
load:wgpu::LoadOp::Clear(1.0),
|
||||||
|
store:wgpu::StoreOp::Discard,
|
||||||
|
}),
|
||||||
|
stencil_ops:None,
|
||||||
|
}),
|
||||||
|
timestamp_writes:Default::default(),
|
||||||
|
occlusion_query_set:Default::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
rpass.set_bind_group(0,&self.bind_groups.camera,&[]);
|
||||||
|
rpass.set_bind_group(1,&self.bind_groups.skybox_texture,&[]);
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.pipelines.model);
|
||||||
|
for model in &self.models{
|
||||||
|
rpass.set_bind_group(2,&model.bind_group,&[]);
|
||||||
|
rpass.set_vertex_buffer(0,model.vertex_buf.slice(..));
|
||||||
|
rpass.set_index_buffer(model.indices.buf.slice(..),model.indices.format);
|
||||||
|
//TODO: loop over triangle strips
|
||||||
|
rpass.draw_indexed(0..model.indices.count,0,0..model.instance_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpass.set_pipeline(&self.pipelines.skybox);
|
||||||
|
rpass.draw(0..3,0..1);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
|
||||||
|
self.staging_belt.recall();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const MODEL_BUFFER_SIZE:usize=4*4 + 12 + 4;//let size=std::mem::size_of::<ModelInstance>();
|
||||||
|
const MODEL_BUFFER_SIZE_BYTES:usize=MODEL_BUFFER_SIZE*4;
|
||||||
|
fn get_instances_buffer_data(instances:&[GraphicsModelOwned])->Vec<f32>{
|
||||||
|
let mut raw=Vec::with_capacity(MODEL_BUFFER_SIZE*instances.len());
|
||||||
|
for mi in instances{
|
||||||
|
//model transform
|
||||||
|
raw.extend_from_slice(&AsRef::<[f32; 4*4]>::as_ref(&mi.transform)[..]);
|
||||||
|
//normal transform
|
||||||
|
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.x_axis));
|
||||||
|
raw.extend_from_slice(&[0.0]);
|
||||||
|
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.y_axis));
|
||||||
|
raw.extend_from_slice(&[0.0]);
|
||||||
|
raw.extend_from_slice(AsRef::<[f32; 3]>::as_ref(&mi.normal_transform.z_axis));
|
||||||
|
raw.extend_from_slice(&[0.0]);
|
||||||
|
//color
|
||||||
|
raw.extend_from_slice(AsRef::<[f32; 4]>::as_ref(&mi.color.get()));
|
||||||
|
}
|
||||||
|
raw
|
||||||
|
}
|
2
engine/graphics/src/lib.rs
Normal file
2
engine/graphics/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod model;
|
||||||
|
pub mod graphics;
|
48
engine/graphics/src/model.rs
Normal file
48
engine/graphics/src/model.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use bytemuck::{Pod,Zeroable};
|
||||||
|
use strafesnet_common::model::{IndexedVertex,PolygonGroup,RenderConfigId};
|
||||||
|
#[derive(Clone,Copy,Pod,Zeroable)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct GraphicsVertex{
|
||||||
|
pub pos:[f32;3],
|
||||||
|
pub tex:[f32;2],
|
||||||
|
pub normal:[f32;3],
|
||||||
|
pub color:[f32;4],
|
||||||
|
}
|
||||||
|
#[derive(Clone,Copy,id::Id)]
|
||||||
|
pub struct IndexedGraphicsMeshOwnedRenderConfigId(u32);
|
||||||
|
pub struct IndexedGraphicsMeshOwnedRenderConfig{
|
||||||
|
pub unique_pos:Vec<[f32;3]>,
|
||||||
|
pub unique_tex:Vec<[f32;2]>,
|
||||||
|
pub unique_normal:Vec<[f32;3]>,
|
||||||
|
pub unique_color:Vec<[f32;4]>,
|
||||||
|
pub unique_vertices:Vec<IndexedVertex>,
|
||||||
|
pub render_config:RenderConfigId,
|
||||||
|
pub polys:PolygonGroup,
|
||||||
|
pub instances:Vec<GraphicsModelOwned>,
|
||||||
|
}
|
||||||
|
pub enum Indices{
|
||||||
|
U32(Vec<u32>),
|
||||||
|
U16(Vec<u16>),
|
||||||
|
}
|
||||||
|
pub struct GraphicsMeshOwnedRenderConfig{
|
||||||
|
pub vertices:Vec<GraphicsVertex>,
|
||||||
|
pub indices:Indices,
|
||||||
|
pub render_config:RenderConfigId,
|
||||||
|
pub instances:Vec<GraphicsModelOwned>,
|
||||||
|
}
|
||||||
|
#[derive(Clone,Copy,PartialEq,id::Id)]
|
||||||
|
pub struct GraphicsModelColor4(glam::Vec4);
|
||||||
|
impl std::hash::Hash for GraphicsModelColor4{
|
||||||
|
fn hash<H:std::hash::Hasher>(&self,state:&mut H) {
|
||||||
|
for &f in self.0.as_ref(){
|
||||||
|
bytemuck::cast::<f32,u32>(f).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for GraphicsModelColor4{}
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GraphicsModelOwned{
|
||||||
|
pub transform:glam::Mat4,
|
||||||
|
pub normal_transform:glam::Mat3,
|
||||||
|
pub color:GraphicsModelColor4,
|
||||||
|
}
|
10
engine/physics/Cargo.toml
Normal file
10
engine/physics/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_physics"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
arrayvec = "0.7.6"
|
||||||
|
glam = "0.29.0"
|
||||||
|
id = { version = "0.1.0", registry = "strafesnet" }
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
8
engine/physics/LICENSE
Normal file
8
engine/physics/LICENSE
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
|
*
|
||||||
|
* StrafesNET can not be copied and/or distributed
|
||||||
|
* without the express permission of Rhys Lloyd
|
||||||
|
*******************************************************/
|
160
engine/physics/src/body.rs
Normal file
160
engine/physics/src/body.rs
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
use strafesnet_common::aabb;
|
||||||
|
use strafesnet_common::integer::{self,vec3,Time,Planar64,Planar64Vec3};
|
||||||
|
#[derive(Clone,Copy,Debug,Hash)]
|
||||||
|
pub struct Body<T>{
|
||||||
|
pub position:Planar64Vec3,//I64 where 2^32 = 1 u
|
||||||
|
pub velocity:Planar64Vec3,//I64 where 2^32 = 1 u/s
|
||||||
|
pub acceleration:Planar64Vec3,//I64 where 2^32 = 1 u/s/s
|
||||||
|
pub time:Time<T>,//nanoseconds x xxxxD!
|
||||||
|
}
|
||||||
|
impl<T> std::ops::Neg for Body<T>{
|
||||||
|
type Output=Self;
|
||||||
|
fn neg(self)->Self::Output{
|
||||||
|
Self{
|
||||||
|
position:self.position,
|
||||||
|
velocity:-self.velocity,
|
||||||
|
acceleration:self.acceleration,
|
||||||
|
time:-self.time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Body<T>
|
||||||
|
where Time<T>:Copy,
|
||||||
|
{
|
||||||
|
pub const ZERO:Self=Self::new(vec3::ZERO,vec3::ZERO,vec3::ZERO,Time::ZERO);
|
||||||
|
pub const fn new(position:Planar64Vec3,velocity:Planar64Vec3,acceleration:Planar64Vec3,time:Time<T>)->Self{
|
||||||
|
Self{
|
||||||
|
position,
|
||||||
|
velocity,
|
||||||
|
acceleration,
|
||||||
|
time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub const fn relative_to<'a>(&'a self,body0:&'a Body<T>)->VirtualBody<'a,T>{
|
||||||
|
//(p0,v0,a0,t0)
|
||||||
|
//(p1,v1,a1,t1)
|
||||||
|
VirtualBody{
|
||||||
|
body0,
|
||||||
|
body1:self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
let dt=time-self.time;
|
||||||
|
self.position
|
||||||
|
+(self.velocity*dt).map(|elem|elem.divide().fix_1())
|
||||||
|
+self.acceleration.map(|elem|(dt*dt*elem/2).divide().fix_1())
|
||||||
|
}
|
||||||
|
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
let dt=time-self.time;
|
||||||
|
self.velocity+(self.acceleration*dt).map(|elem|elem.divide().fix_1())
|
||||||
|
}
|
||||||
|
pub fn advance_time(&mut self,time:Time<T>){
|
||||||
|
self.position=self.extrapolated_position(time);
|
||||||
|
self.velocity=self.extrapolated_velocity(time);
|
||||||
|
self.time=time;
|
||||||
|
}
|
||||||
|
pub fn extrapolated_position_ratio_dt<Num,Den,N1,D1,N2,N3,D2,N4,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
||||||
|
where
|
||||||
|
// Why?
|
||||||
|
// All of this can be removed with const generics because the type can be specified as
|
||||||
|
// Ratio<Fixed<N,NF>,Fixed<D,DF>>
|
||||||
|
// which is known to implement all the necessary traits
|
||||||
|
Num:Copy,
|
||||||
|
Den:Copy+core::ops::Mul<i64,Output=D1>,
|
||||||
|
D1:Copy,
|
||||||
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
|
Planar64:core::ops::Mul<D1,Output=N2>,
|
||||||
|
N1:core::ops::Add<N2,Output=N3>,
|
||||||
|
Num:core::ops::Mul<N3,Output=N4>,
|
||||||
|
Den:core::ops::Mul<D1,Output=D2>,
|
||||||
|
D2:Copy,
|
||||||
|
Planar64:core::ops::Mul<D2,Output=N4>,
|
||||||
|
N4:integer::Divide<D2,Output=T1>,
|
||||||
|
T1:integer::Fix<Planar64>,
|
||||||
|
{
|
||||||
|
// a*dt^2/2 + v*dt + p
|
||||||
|
// (a*dt/2+v)*dt+p
|
||||||
|
(self.acceleration.map(|elem|dt*elem/2)+self.velocity).map(|elem|dt.mul_ratio(elem))
|
||||||
|
.map(|elem|elem.divide().fix())+self.position
|
||||||
|
}
|
||||||
|
pub fn extrapolated_velocity_ratio_dt<Num,Den,N1,T1>(&self,dt:integer::Ratio<Num,Den>)->Planar64Vec3
|
||||||
|
where
|
||||||
|
Num:Copy,
|
||||||
|
Den:Copy,
|
||||||
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
|
Planar64:core::ops::Mul<Den,Output=N1>,
|
||||||
|
N1:integer::Divide<Den,Output=T1>,
|
||||||
|
T1:integer::Fix<Planar64>,
|
||||||
|
{
|
||||||
|
// a*dt + v
|
||||||
|
self.acceleration.map(|elem|(dt*elem).divide().fix())+self.velocity
|
||||||
|
}
|
||||||
|
pub fn advance_time_ratio_dt(&mut self,dt:crate::model::GigaTime){
|
||||||
|
self.position=self.extrapolated_position_ratio_dt(dt);
|
||||||
|
self.velocity=self.extrapolated_velocity_ratio_dt(dt);
|
||||||
|
self.time+=dt.into();
|
||||||
|
}
|
||||||
|
pub fn infinity_dir(&self)->Option<Planar64Vec3>{
|
||||||
|
if self.velocity==vec3::ZERO{
|
||||||
|
if self.acceleration==vec3::ZERO{
|
||||||
|
None
|
||||||
|
}else{
|
||||||
|
Some(self.acceleration)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
Some(self.velocity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn grow_aabb(&self,aabb:&mut aabb::Aabb,t0:Time<T>,t1:Time<T>){
|
||||||
|
aabb.grow(self.extrapolated_position(t0));
|
||||||
|
aabb.grow(self.extrapolated_position(t1));
|
||||||
|
//v+a*t==0
|
||||||
|
//goober code
|
||||||
|
if !self.acceleration.x.is_zero(){
|
||||||
|
let t=-self.velocity.x/self.acceleration.x;
|
||||||
|
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||||
|
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.acceleration.y.is_zero(){
|
||||||
|
let t=-self.velocity.y/self.acceleration.y;
|
||||||
|
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||||
|
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.acceleration.z.is_zero(){
|
||||||
|
let t=-self.velocity.z/self.acceleration.z;
|
||||||
|
if t0.to_ratio().lt_ratio(t)&&t.lt_ratio(t1.to_ratio()){
|
||||||
|
aabb.grow(self.extrapolated_position_ratio_dt(t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
impl<T> std::fmt::Display for Body<T>{
|
||||||
|
fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
|
||||||
|
write!(f,"p({}) v({}) a({}) t({})",self.position,self.velocity,self.acceleration,self.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualBody<'a,T>{
|
||||||
|
body0:&'a Body<T>,
|
||||||
|
body1:&'a Body<T>,
|
||||||
|
}
|
||||||
|
impl<T> VirtualBody<'_,T>
|
||||||
|
where Time<T>:Copy,
|
||||||
|
{
|
||||||
|
pub fn extrapolated_position(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
self.body1.extrapolated_position(time)-self.body0.extrapolated_position(time)
|
||||||
|
}
|
||||||
|
pub fn extrapolated_velocity(&self,time:Time<T>)->Planar64Vec3{
|
||||||
|
self.body1.extrapolated_velocity(time)-self.body0.extrapolated_velocity(time)
|
||||||
|
}
|
||||||
|
pub fn acceleration(&self)->Planar64Vec3{
|
||||||
|
self.body1.acceleration-self.body0.acceleration
|
||||||
|
}
|
||||||
|
pub fn body(&self,time:Time<T>)->Body<T>{
|
||||||
|
Body::new(self.extrapolated_position(time),self.extrapolated_velocity(time),self.acceleration(),time)
|
||||||
|
}
|
||||||
|
}
|
148
engine/physics/src/face_crawler.rs
Normal file
148
engine/physics/src/face_crawler.rs
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
use crate::model::{GigaTime,FEV,MeshQuery,DirectedEdge};
|
||||||
|
use strafesnet_common::integer::{Fixed,Ratio,vec3::Vector3};
|
||||||
|
use crate::physics::{Time,Body};
|
||||||
|
|
||||||
|
enum Transition<M:MeshQuery>{
|
||||||
|
Miss,
|
||||||
|
Next(FEV<M>,GigaTime),
|
||||||
|
Hit(M::Face,GigaTime),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum CrawlResult<M:MeshQuery>{
|
||||||
|
Miss(FEV<M>),
|
||||||
|
Hit(M::Face,GigaTime),
|
||||||
|
}
|
||||||
|
impl<M:MeshQuery> CrawlResult<M>{
|
||||||
|
pub fn hit(self)->Option<(M::Face,GigaTime)>{
|
||||||
|
match self{
|
||||||
|
CrawlResult::Miss(_)=>None,
|
||||||
|
CrawlResult::Hit(face,time)=>Some((face,time)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn miss(self)->Option<FEV<M>>{
|
||||||
|
match self{
|
||||||
|
CrawlResult::Miss(fev)=>Some(fev),
|
||||||
|
CrawlResult::Hit(_,_)=>None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F:Copy,M:MeshQuery<Normal=Vector3<F>,Offset=Fixed<4,128>>> FEV<M>
|
||||||
|
where
|
||||||
|
// This is hardcoded for MinkowskiMesh lol
|
||||||
|
M::Face:Copy,
|
||||||
|
M::Edge:Copy,
|
||||||
|
M::Vert:Copy,
|
||||||
|
F:core::ops::Mul<Fixed<1,32>,Output=Fixed<4,128>>,
|
||||||
|
<F as core::ops::Mul<Fixed<1,32>>>::Output:core::iter::Sum,
|
||||||
|
<M as MeshQuery>::Offset:core::ops::Sub<<F as std::ops::Mul<Fixed<1,32>>>::Output>,
|
||||||
|
{
|
||||||
|
fn next_transition(&self,body_time:GigaTime,mesh:&M,body:&Body,mut best_time:GigaTime)->Transition<M>{
|
||||||
|
//conflicting derivative means it crosses in the wrong direction.
|
||||||
|
//if the transition time is equal to an already tested transition, do not replace the current best.
|
||||||
|
let mut best_transition=Transition::Miss;
|
||||||
|
match self{
|
||||||
|
&FEV::Face(face_id)=>{
|
||||||
|
//test own face collision time, ignoring roots with zero or conflicting derivative
|
||||||
|
//n=face.normal d=face.dot
|
||||||
|
//n.a t^2+n.v t+n.p-d==0
|
||||||
|
let (n,d)=mesh.face_nd(face_id);
|
||||||
|
//TODO: use higher precision d value?
|
||||||
|
//use the mesh transform translation instead of baking it into the d value.
|
||||||
|
for dt in Fixed::<4,128>::zeroes2((n.dot(body.position)-d)*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||||
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
|
best_time=dt;
|
||||||
|
best_transition=Transition::Hit(face_id,dt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||||
|
for &directed_edge_id in mesh.face_edges(face_id).iter(){
|
||||||
|
let edge_n=mesh.directed_edge_n(directed_edge_id);
|
||||||
|
let n=n.cross(edge_n);
|
||||||
|
let verts=mesh.edge_verts(directed_edge_id.as_undirected());
|
||||||
|
//WARNING: d is moved out of the *2 block because of adding two vertices!
|
||||||
|
//WARNING: precision is swept under the rug!
|
||||||
|
for dt in Fixed::<4,128>::zeroes2(n.dot(body.position*2-(mesh.vert(verts[0])+mesh.vert(verts[1]))).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||||
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
|
best_time=dt;
|
||||||
|
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if none:
|
||||||
|
},
|
||||||
|
&FEV::Edge(edge_id)=>{
|
||||||
|
//test each face collision time, ignoring roots with zero or conflicting derivative
|
||||||
|
let edge_n=mesh.edge_n(edge_id);
|
||||||
|
let edge_verts=mesh.edge_verts(edge_id);
|
||||||
|
let delta_pos=body.position*2-(mesh.vert(edge_verts[0])+mesh.vert(edge_verts[1]));
|
||||||
|
for (i,&edge_face_id) in mesh.edge_faces(edge_id).iter().enumerate(){
|
||||||
|
let face_n=mesh.face_nd(edge_face_id).0;
|
||||||
|
//edge_n gets parity from the order of edge_faces
|
||||||
|
let n=face_n.cross(edge_n)*((i as i64)*2-1);
|
||||||
|
//WARNING yada yada d *2
|
||||||
|
for dt in Fixed::<4,128>::zeroes2(n.dot(delta_pos).fix_4(),n.dot(body.velocity).fix_4()*2,n.dot(body.acceleration).fix_4()){
|
||||||
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
|
best_time=dt;
|
||||||
|
best_transition=Transition::Next(FEV::Face(edge_face_id),dt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//test each vertex collision time, ignoring roots with zero or conflicting derivative
|
||||||
|
for (i,&vert_id) in edge_verts.iter().enumerate(){
|
||||||
|
//vertex normal gets parity from vert index
|
||||||
|
let n=edge_n*(1-2*(i as i64));
|
||||||
|
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||||
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
|
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||||
|
best_time=dt;
|
||||||
|
best_transition=Transition::Next(FEV::Vert(vert_id),dt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if none:
|
||||||
|
},
|
||||||
|
&FEV::Vert(vert_id)=>{
|
||||||
|
//test each edge collision time, ignoring roots with zero or conflicting derivative
|
||||||
|
for &directed_edge_id in mesh.vert_edges(vert_id).iter(){
|
||||||
|
//edge is directed away from vertex, but we want the dot product to turn out negative
|
||||||
|
let n=-mesh.directed_edge_n(directed_edge_id);
|
||||||
|
for dt in Fixed::<2,64>::zeroes2((n.dot(body.position-mesh.vert(vert_id)))*2,n.dot(body.velocity)*2,n.dot(body.acceleration)){
|
||||||
|
if body_time.le_ratio(dt)&&dt.lt_ratio(best_time)&&n.dot(body.extrapolated_velocity_ratio_dt(dt)).is_negative(){
|
||||||
|
let dt=Ratio::new(dt.num.fix_4(),dt.den.fix_4());
|
||||||
|
best_time=dt;
|
||||||
|
best_transition=Transition::Next(FEV::Edge(directed_edge_id.as_undirected()),dt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//if none:
|
||||||
|
},
|
||||||
|
}
|
||||||
|
best_transition
|
||||||
|
}
|
||||||
|
pub fn crawl(mut self,mesh:&M,relative_body:&Body,start_time:Time,time_limit:Time)->CrawlResult<M>{
|
||||||
|
let mut body_time={
|
||||||
|
let r=(start_time-relative_body.time).to_ratio();
|
||||||
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
|
};
|
||||||
|
let time_limit={
|
||||||
|
let r=(time_limit-relative_body.time).to_ratio();
|
||||||
|
Ratio::new(r.num.fix_4(),r.den.fix_4())
|
||||||
|
};
|
||||||
|
for _ in 0..20{
|
||||||
|
match self.next_transition(body_time,mesh,relative_body,time_limit){
|
||||||
|
Transition::Miss=>return CrawlResult::Miss(self),
|
||||||
|
Transition::Next(next_fev,next_time)=>(self,body_time)=(next_fev,next_time),
|
||||||
|
Transition::Hit(face,time)=>return CrawlResult::Hit(face,time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO: fix all bugs
|
||||||
|
//println!("Too many iterations! Using default behaviour instead of crashing...");
|
||||||
|
CrawlResult::Miss(self)
|
||||||
|
}
|
||||||
|
}
|
45
engine/physics/src/lib.rs
Normal file
45
engine/physics/src/lib.rs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
mod body;
|
||||||
|
mod push_solve;
|
||||||
|
mod face_crawler;
|
||||||
|
mod model;
|
||||||
|
|
||||||
|
pub mod physics;
|
||||||
|
|
||||||
|
// Physics bug fixes can easily desync all bots.
|
||||||
|
//
|
||||||
|
// When replaying a bot, use the exact physics version which it was recorded with.
|
||||||
|
//
|
||||||
|
// When validating a new bot, ignore the version and use the latest version,
|
||||||
|
// and overwrite the version in the file.
|
||||||
|
//
|
||||||
|
// Compatible physics versions should be determined
|
||||||
|
// empirically at development time via leaderboard resimulation.
|
||||||
|
//
|
||||||
|
// Compatible physics versions should result in an identical leaderboard state,
|
||||||
|
// or the only bots which fail are ones exploiting a surgically patched bug.
|
||||||
|
#[derive(Clone,Copy,Hash,Debug,id::Id,Eq,PartialEq,Ord,PartialOrd)]
|
||||||
|
pub struct PhysicsVersion(u32);
|
||||||
|
pub const VERSION:PhysicsVersion=PhysicsVersion(0);
|
||||||
|
const LATEST_COMPATIBLE_VERSION:[u32;1+VERSION.0 as usize]=const{
|
||||||
|
let compat=[0];
|
||||||
|
|
||||||
|
let mut input_version=0;
|
||||||
|
while input_version<compat.len(){
|
||||||
|
// compatible version must be greater that or equal to the input version
|
||||||
|
assert!(input_version as u32<=compat[input_version]);
|
||||||
|
// compatible version must be a version that exists
|
||||||
|
assert!(compat[input_version]<=VERSION.0);
|
||||||
|
input_version+=1;
|
||||||
|
}
|
||||||
|
compat
|
||||||
|
};
|
||||||
|
pub enum PhysicsVersionError{
|
||||||
|
UnknownPhysicsVersion,
|
||||||
|
}
|
||||||
|
pub const fn get_latest_compatible_version(PhysicsVersion(version):PhysicsVersion)->Result<PhysicsVersion,PhysicsVersionError>{
|
||||||
|
if (version as usize)<LATEST_COMPATIBLE_VERSION.len(){
|
||||||
|
Ok(PhysicsVersion(LATEST_COMPATIBLE_VERSION[version as usize]))
|
||||||
|
}else{
|
||||||
|
Err(PhysicsVersionError::UnknownPhysicsVersion)
|
||||||
|
}
|
||||||
|
}
|
1013
engine/physics/src/model.rs
Normal file
1013
engine/physics/src/model.rs
Normal file
File diff suppressed because it is too large
Load Diff
2090
engine/physics/src/physics.rs
Normal file
2090
engine/physics/src/physics.rs
Normal file
File diff suppressed because it is too large
Load Diff
349
engine/physics/src/push_solve.rs
Normal file
349
engine/physics/src/push_solve.rs
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
use strafesnet_common::integer::{self,vec3::{self,Vector3},Fixed,Planar64,Planar64Vec3,Ratio};
|
||||||
|
|
||||||
|
// This algorithm is based on Lua code
|
||||||
|
// written by Trey Reynolds in 2021
|
||||||
|
|
||||||
|
// EPSILON=1/2^10
|
||||||
|
|
||||||
|
// A stack-allocated variable-size list that holds up to 4 elements
|
||||||
|
// Direct references are used instead of indices i0, i1, i2, i3
|
||||||
|
type Conts<'a>=arrayvec::ArrayVec<&'a Contact,4>;
|
||||||
|
|
||||||
|
// hack to allow comparing ratios to zero
|
||||||
|
const RATIO_ZERO:Ratio<Fixed<1,32>,Fixed<1,32>>=Ratio::new(Fixed::ZERO,Fixed::EPSILON);
|
||||||
|
|
||||||
|
struct Ray{
|
||||||
|
origin:Planar64Vec3,
|
||||||
|
direction:Planar64Vec3,
|
||||||
|
}
|
||||||
|
impl Ray{
|
||||||
|
fn extrapolate<Num,Den,N1,T1>(&self,t:Ratio<Num,Den>)->Planar64Vec3
|
||||||
|
where
|
||||||
|
Num:Copy,
|
||||||
|
Den:Copy,
|
||||||
|
Num:core::ops::Mul<Planar64,Output=N1>,
|
||||||
|
Planar64:core::ops::Mul<Den,Output=N1>,
|
||||||
|
N1:integer::Divide<Den,Output=T1>,
|
||||||
|
T1:integer::Fix<Planar64>,
|
||||||
|
{
|
||||||
|
self.origin+self.direction.map(|elem|(t*elem).divide().fix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Information about a contact restriction
|
||||||
|
pub struct Contact{
|
||||||
|
pub position:Planar64Vec3,
|
||||||
|
pub velocity:Planar64Vec3,
|
||||||
|
pub normal:Planar64Vec3,
|
||||||
|
}
|
||||||
|
impl Contact{
|
||||||
|
fn relative_to(&self,point:Planar64Vec3)->Self{
|
||||||
|
Self{
|
||||||
|
position:self.position-point,
|
||||||
|
velocity:self.velocity,
|
||||||
|
normal:self.normal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn relative_dot(&self,direction:Planar64Vec3)->Fixed<2,64>{
|
||||||
|
(direction-self.velocity).dot(self.normal)
|
||||||
|
}
|
||||||
|
/// Calculate the time of intersection. (previously get_touch_time)
|
||||||
|
fn solve(&self,ray:&Ray)->Ratio<Fixed<2,64>,Fixed<2,64>>{
|
||||||
|
(self.position-ray.origin).dot(self.normal)/(ray.direction-self.velocity).dot(self.normal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//note that this is horrible with fixed point arithmetic
|
||||||
|
fn solve1(c0:&Contact)->Option<Ratio<Vector3<Fixed<3,96>>,Fixed<2,64>>>{
|
||||||
|
const EPSILON:Fixed<2,64>=Fixed::from_bits(Fixed::<2,64>::ONE.to_bits().shr(10));
|
||||||
|
let det=c0.normal.dot(c0.velocity);
|
||||||
|
if det.abs()<EPSILON{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let d0=c0.normal.dot(c0.position);
|
||||||
|
Some(c0.normal*d0/det)
|
||||||
|
}
|
||||||
|
fn solve2(c0:&Contact,c1:&Contact)->Option<Ratio<Vector3<Fixed<5,160>>,Fixed<4,128>>>{
|
||||||
|
const EPSILON:Fixed<4,128>=Fixed::from_bits(Fixed::<4,128>::ONE.to_bits().shr(10));
|
||||||
|
let u0_u1=c0.velocity.cross(c1.velocity);
|
||||||
|
let n0_n1=c0.normal.cross(c1.normal);
|
||||||
|
let det=u0_u1.dot(n0_n1);
|
||||||
|
if det.abs()<EPSILON{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let d0=c0.normal.dot(c0.position);
|
||||||
|
let d1=c1.normal.dot(c1.position);
|
||||||
|
Some((c1.normal.cross(u0_u1)*d0+u0_u1.cross(c0.normal)*d1)/det)
|
||||||
|
}
|
||||||
|
fn solve3(c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ratio<Vector3<Fixed<4,128>>,Fixed<3,96>>>{
|
||||||
|
const EPSILON:Fixed<3,96>=Fixed::from_bits(Fixed::<3,96>::ONE.to_bits().shr(10));
|
||||||
|
let n0_n1=c0.normal.cross(c1.normal);
|
||||||
|
let det=c2.normal.dot(n0_n1);
|
||||||
|
if det.abs()<EPSILON{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let d0=c0.normal.dot(c0.position);
|
||||||
|
let d1=c1.normal.dot(c1.position);
|
||||||
|
let d2=c2.normal.dot(c2.position);
|
||||||
|
Some((c1.normal.cross(c2.normal)*d0+c2.normal.cross(c0.normal)*d1+c0.normal.cross(c1.normal)*d2)/det)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decompose1(point:Planar64Vec3,u0:Planar64Vec3)->Option<[Ratio<Fixed<2,64>,Fixed<2,64>>;1]>{
|
||||||
|
let det=u0.dot(u0);
|
||||||
|
if det==Fixed::ZERO{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let s0=u0.dot(point)/det;
|
||||||
|
Some([s0])
|
||||||
|
}
|
||||||
|
fn decompose2(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3)->Option<[Ratio<Fixed<4,128>,Fixed<4,128>>;2]>{
|
||||||
|
let u0_u1=u0.cross(u1);
|
||||||
|
let det=u0_u1.dot(u0_u1);
|
||||||
|
if det==Fixed::ZERO{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let s0=u0_u1.dot(point.cross(u1))/det;
|
||||||
|
let s1=u0_u1.dot(u0.cross(point))/det;
|
||||||
|
Some([s0,s1])
|
||||||
|
}
|
||||||
|
fn decompose3(point:Planar64Vec3,u0:Planar64Vec3,u1:Planar64Vec3,u2:Planar64Vec3)->Option<[Ratio<Fixed<3,96>,Fixed<3,96>>;3]>{
|
||||||
|
let det=u0.cross(u1).dot(u2);
|
||||||
|
if det==Fixed::ZERO{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let s0=point.cross(u1).dot(u2)/det;
|
||||||
|
let s1=u0.cross(point).dot(u2)/det;
|
||||||
|
let s2=u0.cross(u1).dot(point)/det;
|
||||||
|
Some([s0,s1,s2])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_space_enclosed_2(
|
||||||
|
a:Planar64Vec3,
|
||||||
|
b:Planar64Vec3,
|
||||||
|
)->bool{
|
||||||
|
a.cross(b)==Vector3::new([Fixed::ZERO;3])
|
||||||
|
&&a.dot(b).is_negative()
|
||||||
|
}
|
||||||
|
fn is_space_enclosed_3(
|
||||||
|
a:Planar64Vec3,
|
||||||
|
b:Planar64Vec3,
|
||||||
|
c:Planar64Vec3
|
||||||
|
)->bool{
|
||||||
|
a.cross(b).dot(c)==Fixed::ZERO
|
||||||
|
&&{
|
||||||
|
let det_abac=a.cross(b).dot(a.cross(c));
|
||||||
|
let det_abbc=a.cross(b).dot(b.cross(c));
|
||||||
|
let det_acbc=a.cross(c).dot(b.cross(c));
|
||||||
|
return!( det_abac*det_abbc).is_positive()
|
||||||
|
&&!( det_abbc*det_acbc).is_positive()
|
||||||
|
&&!(-det_acbc*det_abac).is_positive()
|
||||||
|
||is_space_enclosed_2(a,b)
|
||||||
|
||is_space_enclosed_2(a,c)
|
||||||
|
||is_space_enclosed_2(b,c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_space_enclosed_4(
|
||||||
|
a:Planar64Vec3,
|
||||||
|
b:Planar64Vec3,
|
||||||
|
c:Planar64Vec3,
|
||||||
|
d:Planar64Vec3,
|
||||||
|
)->bool{
|
||||||
|
let det_abc=a.cross(b).dot(c);
|
||||||
|
let det_abd=a.cross(b).dot(d);
|
||||||
|
let det_acd=a.cross(c).dot(d);
|
||||||
|
let det_bcd=b.cross(c).dot(d);
|
||||||
|
return( det_abc*det_abd).is_negative()
|
||||||
|
&&(-det_abc*det_acd).is_negative()
|
||||||
|
&&( det_abd*det_acd).is_negative()
|
||||||
|
&&( det_abc*det_bcd).is_negative()
|
||||||
|
&&(-det_abd*det_bcd).is_negative()
|
||||||
|
&&( det_acd*det_bcd).is_negative()
|
||||||
|
||is_space_enclosed_3(a,b,c)
|
||||||
|
||is_space_enclosed_3(a,b,d)
|
||||||
|
||is_space_enclosed_3(a,c,d)
|
||||||
|
||is_space_enclosed_3(b,c,d)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn get_push_ray_0(point:Planar64Vec3)->Ray{
|
||||||
|
Ray{origin:point,direction:vec3::ZERO}
|
||||||
|
}
|
||||||
|
fn get_push_ray_1(point:Planar64Vec3,c0:&Contact)->Option<Ray>{
|
||||||
|
let direction=solve1(c0)?.divide().fix_1();
|
||||||
|
let [s0]=decompose1(direction,c0.velocity)?;
|
||||||
|
if s0.lt_ratio(RATIO_ZERO){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let origin=point+solve1(
|
||||||
|
&c0.relative_to(point),
|
||||||
|
)?.divide().fix_1();
|
||||||
|
Some(Ray{origin,direction})
|
||||||
|
}
|
||||||
|
fn get_push_ray_2(point:Planar64Vec3,c0:&Contact,c1:&Contact)->Option<Ray>{
|
||||||
|
let direction=solve2(c0,c1)?.divide().fix_1();
|
||||||
|
let [s0,s1]=decompose2(direction,c0.velocity,c1.velocity)?;
|
||||||
|
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let origin=point+solve2(
|
||||||
|
&c0.relative_to(point),
|
||||||
|
&c1.relative_to(point),
|
||||||
|
)?.divide().fix_1();
|
||||||
|
Some(Ray{origin,direction})
|
||||||
|
}
|
||||||
|
fn get_push_ray_3(point:Planar64Vec3,c0:&Contact,c1:&Contact,c2:&Contact)->Option<Ray>{
|
||||||
|
let direction=solve3(c0,c1,c2)?.divide().fix_1();
|
||||||
|
let [s0,s1,s2]=decompose3(direction,c0.velocity,c1.velocity,c2.velocity)?;
|
||||||
|
if s0.lt_ratio(RATIO_ZERO)||s1.lt_ratio(RATIO_ZERO)||s2.lt_ratio(RATIO_ZERO){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let origin=point+solve3(
|
||||||
|
&c0.relative_to(point),
|
||||||
|
&c1.relative_to(point),
|
||||||
|
&c2.relative_to(point),
|
||||||
|
)?.divide().fix_1();
|
||||||
|
Some(Ray{origin,direction})
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn get_best_push_ray_and_conts_0<'a>(point:Planar64Vec3)->(Ray,Conts<'a>){
|
||||||
|
(get_push_ray_0(point),Conts::new_const())
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_1(point:Planar64Vec3,c0:&Contact)->Option<(Ray,Conts)>{
|
||||||
|
get_push_ray_1(point,c0)
|
||||||
|
.map(|ray|(ray,Conts::from_iter([c0])))
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_2<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||||
|
if is_space_enclosed_2(c0.normal,c1.normal){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c1])));
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_1(point,c0){
|
||||||
|
if !c1.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_3<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||||
|
if is_space_enclosed_3(c0.normal,c1.normal,c2.normal){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_3(point,c0,c1,c2){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c1,c2])));
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_2(point,c0,c1){
|
||||||
|
if !c2.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c1])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_2(point,c0,c2){
|
||||||
|
if !c1.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0,c2])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(ray)=get_push_ray_1(point,c0){
|
||||||
|
if !c1.relative_dot(ray.direction).is_negative()
|
||||||
|
&&!c2.relative_dot(ray.direction).is_negative(){
|
||||||
|
return Some((ray,Conts::from_iter([c0])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
fn get_best_push_ray_and_conts_4<'a>(point:Planar64Vec3,c0:&'a Contact,c1:&'a Contact,c2:&'a Contact,c3:&'a Contact)->Option<(Ray,Conts<'a>)>{
|
||||||
|
if is_space_enclosed_4(c0.normal,c1.normal,c2.normal,c3.normal){
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (ray012,conts012)=get_best_push_ray_and_conts_3(point,c0,c1,c2)?;
|
||||||
|
let (ray013,conts013)=get_best_push_ray_and_conts_3(point,c0,c1,c3)?;
|
||||||
|
let (ray023,conts023)=get_best_push_ray_and_conts_3(point,c0,c2,c3)?;
|
||||||
|
|
||||||
|
let err012=c3.relative_dot(ray012.direction);
|
||||||
|
let err013=c2.relative_dot(ray013.direction);
|
||||||
|
let err023=c1.relative_dot(ray023.direction);
|
||||||
|
|
||||||
|
let best_err=err012.max(err013).max(err023);
|
||||||
|
|
||||||
|
if best_err==err012{
|
||||||
|
return Some((ray012,conts012))
|
||||||
|
}else if best_err==err013{
|
||||||
|
return Some((ray013,conts013))
|
||||||
|
}else if best_err==err023{
|
||||||
|
return Some((ray023,conts023))
|
||||||
|
}
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_best_push_ray_and_conts<'a>(
|
||||||
|
point:Planar64Vec3,
|
||||||
|
conts:&[&'a Contact],
|
||||||
|
)->Option<(Ray,Conts<'a>)>{
|
||||||
|
match conts{
|
||||||
|
&[c0,c1,c2,c3]=>get_best_push_ray_and_conts_4(point,c0,c1,c2,c3),
|
||||||
|
&[c0,c1,c2]=>get_best_push_ray_and_conts_3(point,c0,c1,c2),
|
||||||
|
&[c0,c1]=>get_best_push_ray_and_conts_2(point,c0,c1),
|
||||||
|
&[c0]=>get_best_push_ray_and_conts_1(point,c0),
|
||||||
|
&[]=>Some(get_best_push_ray_and_conts_0(point)),
|
||||||
|
_=>unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_first_touch<'a>(contacts:&'a [Contact],ray:&Ray,conts:&Conts)->Option<(Ratio<Fixed<2,64>,Fixed<2,64>>,&'a Contact)>{
|
||||||
|
contacts.iter()
|
||||||
|
.filter(|&contact|
|
||||||
|
!conts.iter().any(|&c|std::ptr::eq(c,contact))
|
||||||
|
&&contact.relative_dot(ray.direction).is_negative()
|
||||||
|
)
|
||||||
|
.map(|contact|(contact.solve(ray),contact))
|
||||||
|
.min_by_key(|&(t,_)|t)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_solve(contacts:&[Contact],point:Planar64Vec3)->Planar64Vec3{
|
||||||
|
let (mut ray,mut conts)=get_best_push_ray_and_conts_0(point);
|
||||||
|
loop{
|
||||||
|
let (next_t,next_cont)=match get_first_touch(contacts,&ray,&conts){
|
||||||
|
Some((t,cont))=>(t,cont),
|
||||||
|
None=>return ray.origin,
|
||||||
|
};
|
||||||
|
|
||||||
|
if RATIO_ZERO.le_ratio(next_t){
|
||||||
|
return ray.origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
//push_front
|
||||||
|
if conts.len()==conts.capacity(){
|
||||||
|
//this is a dead case, new_conts never has more than 3 elements
|
||||||
|
conts.rotate_right(1);
|
||||||
|
conts[0]=next_cont;
|
||||||
|
}else{
|
||||||
|
conts.push(next_cont);
|
||||||
|
conts.rotate_right(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let meet_point=ray.extrapolate(next_t);
|
||||||
|
match get_best_push_ray_and_conts(meet_point,conts.as_slice()){
|
||||||
|
Some((new_ray,new_conts))=>(ray,conts)=(new_ray,new_conts),
|
||||||
|
None=>return meet_point,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests{
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_push_solve(){
|
||||||
|
let contacts=vec![
|
||||||
|
Contact{
|
||||||
|
position:vec3::ZERO,
|
||||||
|
velocity:vec3::Y,
|
||||||
|
normal:vec3::Y,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
assert_eq!(
|
||||||
|
vec3::ZERO,
|
||||||
|
push_solve(&contacts,vec3::NEG_Y)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
12
engine/session/Cargo.toml
Normal file
12
engine/session/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_session"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
glam = "0.29.0"
|
||||||
|
replace_with = "0.1.7"
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
||||||
|
strafesnet_physics = { path = "../physics", registry = "strafesnet" }
|
||||||
|
strafesnet_settings = { path = "../settings", registry = "strafesnet" }
|
||||||
|
strafesnet_snf = { path = "../../lib/snf", registry = "strafesnet" }
|
8
engine/session/LICENSE
Normal file
8
engine/session/LICENSE
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
|
*
|
||||||
|
* StrafesNET can not be copied and/or distributed
|
||||||
|
* without the express permission of Rhys Lloyd
|
||||||
|
*******************************************************/
|
2
engine/session/src/lib.rs
Normal file
2
engine/session/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod mouse_interpolator;
|
||||||
|
pub mod session;
|
281
engine/session/src/mouse_interpolator.rs
Normal file
281
engine/session/src/mouse_interpolator.rs
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
use strafesnet_common::mouse::MouseState;
|
||||||
|
use strafesnet_common::physics::{
|
||||||
|
MouseInstruction,SetControlInstruction,ModeInstruction,MiscInstruction,
|
||||||
|
Instruction as PhysicsInstruction,
|
||||||
|
TimeInner as PhysicsTimeInner,
|
||||||
|
Time as PhysicsTime,
|
||||||
|
};
|
||||||
|
use strafesnet_common::session::{Time as SessionTime,TimeInner as SessionTimeInner};
|
||||||
|
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,TimedInstruction};
|
||||||
|
|
||||||
|
type TimedSelfInstruction=TimedInstruction<Instruction,PhysicsTimeInner>;
|
||||||
|
type DoubleTimedSelfInstruction=TimedInstruction<TimedSelfInstruction,SessionTimeInner>;
|
||||||
|
|
||||||
|
type TimedPhysicsInstruction=TimedInstruction<PhysicsInstruction,PhysicsTimeInner>;
|
||||||
|
|
||||||
|
const MOUSE_TIMEOUT:SessionTime=SessionTime::from_millis(10);
|
||||||
|
|
||||||
|
/// To be fed into MouseInterpolator
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub(crate) enum Instruction{
|
||||||
|
MoveMouse(glam::IVec2),
|
||||||
|
SetControl(SetControlInstruction),
|
||||||
|
Mode(ModeInstruction),
|
||||||
|
Misc(MiscInstruction),
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
enum UnbufferedInstruction{
|
||||||
|
MoveMouse(glam::IVec2),
|
||||||
|
NonMouse(NonMouseInstruction),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
enum BufferedInstruction{
|
||||||
|
Mouse(MouseInstruction),
|
||||||
|
NonMouse(NonMouseInstruction),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub(crate) enum NonMouseInstruction{
|
||||||
|
SetControl(SetControlInstruction),
|
||||||
|
Mode(ModeInstruction),
|
||||||
|
Misc(MiscInstruction),
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
impl From<Instruction> for UnbufferedInstruction{
|
||||||
|
#[inline]
|
||||||
|
fn from(value:Instruction)->Self{
|
||||||
|
match value{
|
||||||
|
Instruction::MoveMouse(mouse_instruction)=>UnbufferedInstruction::MoveMouse(mouse_instruction),
|
||||||
|
Instruction::SetControl(set_control_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::SetControl(set_control_instruction)),
|
||||||
|
Instruction::Mode(mode_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Mode(mode_instruction)),
|
||||||
|
Instruction::Misc(misc_instruction)=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Misc(misc_instruction)),
|
||||||
|
Instruction::Idle=>UnbufferedInstruction::NonMouse(NonMouseInstruction::Idle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<BufferedInstruction> for PhysicsInstruction{
|
||||||
|
#[inline]
|
||||||
|
fn from(value:BufferedInstruction)->Self{
|
||||||
|
match value{
|
||||||
|
BufferedInstruction::Mouse(mouse_instruction)=>PhysicsInstruction::Mouse(mouse_instruction),
|
||||||
|
BufferedInstruction::NonMouse(non_mouse_instruction)=>match non_mouse_instruction{
|
||||||
|
NonMouseInstruction::SetControl(set_control_instruction)=>PhysicsInstruction::SetControl(set_control_instruction),
|
||||||
|
NonMouseInstruction::Mode(mode_instruction)=>PhysicsInstruction::Mode(mode_instruction),
|
||||||
|
NonMouseInstruction::Misc(misc_instruction)=>PhysicsInstruction::Misc(misc_instruction),
|
||||||
|
NonMouseInstruction::Idle=>PhysicsInstruction::Idle,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum StepInstruction{
|
||||||
|
Pop,
|
||||||
|
Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
enum BufferState{
|
||||||
|
Unbuffered,
|
||||||
|
Initializing(SessionTime,MouseState<PhysicsTimeInner>),
|
||||||
|
Buffered(SessionTime,MouseState<PhysicsTimeInner>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MouseInterpolator{
|
||||||
|
buffer_state:BufferState,
|
||||||
|
// double timestamped timeline?
|
||||||
|
buffer:std::collections::VecDeque<TimedPhysicsInstruction>,
|
||||||
|
output:std::collections::VecDeque<TimedPhysicsInstruction>,
|
||||||
|
}
|
||||||
|
// Maybe MouseInterpolator manipulation is better expressed using impls
|
||||||
|
// and called from Instruction trait impls in session
|
||||||
|
impl InstructionConsumer<TimedSelfInstruction> for MouseInterpolator{
|
||||||
|
type TimeInner=SessionTimeInner;
|
||||||
|
fn process_instruction(&mut self,ins:DoubleTimedSelfInstruction){
|
||||||
|
self.push_unbuffered_input(ins.time,ins.instruction.time,ins.instruction.instruction.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstructionEmitter<StepInstruction> for MouseInterpolator{
|
||||||
|
type TimeInner=SessionTimeInner;
|
||||||
|
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
||||||
|
self.buffered_instruction_with_timeout(time_limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl MouseInterpolator{
|
||||||
|
pub fn new()->MouseInterpolator{
|
||||||
|
MouseInterpolator{
|
||||||
|
buffer_state:BufferState::Unbuffered,
|
||||||
|
buffer:std::collections::VecDeque::new(),
|
||||||
|
output:std::collections::VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn push_mouse_and_flush_buffer(&mut self,ins:TimedInstruction<MouseInstruction,PhysicsTimeInner>){
|
||||||
|
self.buffer.push_front(TimedInstruction{
|
||||||
|
time:ins.time,
|
||||||
|
instruction:BufferedInstruction::Mouse(ins.instruction).into(),
|
||||||
|
});
|
||||||
|
// flush buffer to output
|
||||||
|
if self.output.len()==0{
|
||||||
|
// swap buffers
|
||||||
|
core::mem::swap(&mut self.buffer,&mut self.output);
|
||||||
|
}else{
|
||||||
|
// append buffer contents to output
|
||||||
|
self.output.append(&mut self.buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_mouse_timedout_at(&self,time_limit:SessionTime)->Option<SessionTime>{
|
||||||
|
match &self.buffer_state{
|
||||||
|
BufferState::Unbuffered=>None,
|
||||||
|
BufferState::Initializing(time,_mouse_state)
|
||||||
|
|BufferState::Buffered(time,_mouse_state)=>{
|
||||||
|
let timeout=*time+MOUSE_TIMEOUT;
|
||||||
|
(timeout<time_limit).then_some(timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn timeout_mouse(&mut self,timeout_time:PhysicsTime){
|
||||||
|
// the state always changes to unbuffered
|
||||||
|
let buffer_state=core::mem::replace(&mut self.buffer_state,BufferState::Unbuffered);
|
||||||
|
match buffer_state{
|
||||||
|
BufferState::Unbuffered=>(),
|
||||||
|
BufferState::Initializing(_time,mouse_state)=>{
|
||||||
|
// only a single mouse move was sent in 10ms, this is very much an edge case!
|
||||||
|
self.push_mouse_and_flush_buffer(TimedInstruction{
|
||||||
|
time:mouse_state.time,
|
||||||
|
instruction:MouseInstruction::ReplaceMouse{
|
||||||
|
m1:MouseState{pos:mouse_state.pos,time:timeout_time},
|
||||||
|
m0:mouse_state,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
BufferState::Buffered(_time,mouse_state)=>{
|
||||||
|
// duplicate the currently buffered mouse state but at a later (future, from the physics perspective) time
|
||||||
|
self.push_mouse_and_flush_buffer(TimedInstruction{
|
||||||
|
time:mouse_state.time,
|
||||||
|
instruction:MouseInstruction::SetNextMouse(MouseState{pos:mouse_state.pos,time:timeout_time}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn push_unbuffered_input(&mut self,session_time:SessionTime,physics_time:PhysicsTime,ins:UnbufferedInstruction){
|
||||||
|
// new input
|
||||||
|
// if there is zero instruction buffered, it means the mouse is not moving
|
||||||
|
// case 1: unbuffered
|
||||||
|
// no mouse event is buffered
|
||||||
|
// - ins is mouse event? change to buffered
|
||||||
|
// - ins other -> write to timeline
|
||||||
|
// case 2: buffered
|
||||||
|
// a mouse event is buffered, and exists within the last 10ms
|
||||||
|
// case 3: stop
|
||||||
|
// a mouse event is buffered, but no mouse events have transpired within 10ms
|
||||||
|
|
||||||
|
// replace_with allows the enum variant to safely be replaced
|
||||||
|
// from behind a mutable reference, but a panic in the closure means that
|
||||||
|
// the entire program terminates rather than completing an unwind.
|
||||||
|
let (ins_mouse,ins_other)=replace_with::replace_with_or_abort_and_return(&mut self.buffer_state,|buffer_state|{
|
||||||
|
match ins{
|
||||||
|
UnbufferedInstruction::MoveMouse(pos)=>{
|
||||||
|
let next_mouse_state=MouseState{pos,time:physics_time};
|
||||||
|
match buffer_state{
|
||||||
|
BufferState::Unbuffered=>{
|
||||||
|
((None,None),BufferState::Initializing(session_time,next_mouse_state))
|
||||||
|
},
|
||||||
|
BufferState::Initializing(_time,mouse_state)=>{
|
||||||
|
let ins_mouse=TimedInstruction{
|
||||||
|
time:mouse_state.time,
|
||||||
|
instruction:MouseInstruction::ReplaceMouse{
|
||||||
|
m0:mouse_state,
|
||||||
|
m1:next_mouse_state.clone(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
||||||
|
},
|
||||||
|
BufferState::Buffered(_time,mouse_state)=>{
|
||||||
|
let ins_mouse=TimedInstruction{
|
||||||
|
time:mouse_state.time,
|
||||||
|
instruction:MouseInstruction::SetNextMouse(next_mouse_state.clone()),
|
||||||
|
};
|
||||||
|
((Some(ins_mouse),None),BufferState::Buffered(session_time,next_mouse_state))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UnbufferedInstruction::NonMouse(other_instruction)=>((None,Some(TimedInstruction{
|
||||||
|
time:physics_time,
|
||||||
|
instruction:other_instruction,
|
||||||
|
})),buffer_state),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(ins)=ins_mouse{
|
||||||
|
self.push_mouse_and_flush_buffer(ins);
|
||||||
|
}
|
||||||
|
if let Some(ins)=ins_other{
|
||||||
|
let instruction=TimedInstruction{
|
||||||
|
time:ins.time,
|
||||||
|
instruction:BufferedInstruction::NonMouse(ins.instruction).into(),
|
||||||
|
};
|
||||||
|
if matches!(self.buffer_state,BufferState::Unbuffered){
|
||||||
|
self.output.push_back(instruction);
|
||||||
|
}else{
|
||||||
|
self.buffer.push_back(instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn buffered_instruction_with_timeout(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,SessionTimeInner>>{
|
||||||
|
match self.get_mouse_timedout_at(time_limit){
|
||||||
|
Some(timeout)=>Some(TimedInstruction{
|
||||||
|
time:timeout,
|
||||||
|
instruction:StepInstruction::Timeout,
|
||||||
|
}),
|
||||||
|
None=>(self.output.len()!=0).then_some(TimedInstruction{
|
||||||
|
// this timestamp should not matter
|
||||||
|
time:time_limit,
|
||||||
|
instruction:StepInstruction::Pop,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn pop_buffered_instruction(&mut self,ins:TimedInstruction<StepInstruction,PhysicsTimeInner>)->Option<TimedPhysicsInstruction>{
|
||||||
|
match ins.instruction{
|
||||||
|
StepInstruction::Pop=>(),
|
||||||
|
StepInstruction::Timeout=>self.timeout_mouse(ins.time),
|
||||||
|
}
|
||||||
|
self.output.pop_front()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test{
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test(){
|
||||||
|
let mut interpolator=MouseInterpolator::new();
|
||||||
|
|
||||||
|
let timer=strafesnet_common::timer::Timer::<strafesnet_common::timer::Scaled<SessionTimeInner,PhysicsTimeInner>>::unpaused(SessionTime::ZERO,PhysicsTime::from_secs(1000));
|
||||||
|
|
||||||
|
macro_rules! push{
|
||||||
|
($time:expr,$ins:expr)=>{
|
||||||
|
println!("in={:?}",$ins);
|
||||||
|
interpolator.push_unbuffered_input(
|
||||||
|
$time,
|
||||||
|
timer.time($time),
|
||||||
|
$ins,
|
||||||
|
);
|
||||||
|
while let Some(ins)=interpolator.buffered_instruction_with_timeout($time){
|
||||||
|
let ins_retimed=TimedInstruction{
|
||||||
|
time:timer.time(ins.time),
|
||||||
|
instruction:ins.instruction,
|
||||||
|
};
|
||||||
|
let out=interpolator.pop_buffered_instruction(ins_retimed);
|
||||||
|
println!("out={out:?}");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// test each buffer_state transition
|
||||||
|
let mut t=SessionTime::ZERO;
|
||||||
|
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||||
|
t+=SessionTime::from_millis(5);
|
||||||
|
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||||
|
t+=SessionTime::from_millis(5);
|
||||||
|
push!(t,UnbufferedInstruction::MoveMouse(glam::ivec2(0,0)));
|
||||||
|
t+=SessionTime::from_millis(1);
|
||||||
|
}
|
||||||
|
}
|
443
engine/session/src/session.rs
Normal file
443
engine/session/src/session.rs
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use strafesnet_common::gameplay_modes::{ModeId,StageId};
|
||||||
|
use strafesnet_common::instruction::{InstructionConsumer,InstructionEmitter,InstructionFeedback,TimedInstruction};
|
||||||
|
// session represents the non-hardware state of the client.
|
||||||
|
// Ideally it is a deterministic state which is atomically updated by instructions, same as the simulation state.
|
||||||
|
use strafesnet_common::physics::{
|
||||||
|
ModeInstruction,MiscInstruction,
|
||||||
|
Instruction as PhysicsInputInstruction,
|
||||||
|
TimeInner as PhysicsTimeInner,
|
||||||
|
Time as PhysicsTime
|
||||||
|
};
|
||||||
|
use strafesnet_common::timer::{Scaled,Timer};
|
||||||
|
use strafesnet_common::session::{TimeInner as SessionTimeInner,Time as SessionTime};
|
||||||
|
use strafesnet_settings::directories::Directories;
|
||||||
|
|
||||||
|
use crate::mouse_interpolator::{MouseInterpolator,StepInstruction,Instruction as MouseInterpolatorInstruction};
|
||||||
|
use strafesnet_physics::physics::{self,PhysicsContext,PhysicsData};
|
||||||
|
use strafesnet_settings::settings::UserSettings;
|
||||||
|
|
||||||
|
pub enum Instruction<'a>{
|
||||||
|
Input(SessionInputInstruction),
|
||||||
|
Control(SessionControlInstruction),
|
||||||
|
Playback(SessionPlaybackInstruction),
|
||||||
|
ChangeMap(&'a strafesnet_common::map::CompleteMap),
|
||||||
|
LoadReplay(strafesnet_snf::bot::Segment),
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SessionInputInstruction{
|
||||||
|
Mouse(glam::IVec2),
|
||||||
|
SetControl(strafesnet_common::physics::SetControlInstruction),
|
||||||
|
Mode(ImplicitModeInstruction),
|
||||||
|
Misc(strafesnet_common::physics::MiscInstruction),
|
||||||
|
}
|
||||||
|
/// Implicit mode instruction are fed separately to session.
|
||||||
|
/// Session generates the explicit mode instructions interlaced with a SetSensitivity instruction
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum ImplicitModeInstruction{
|
||||||
|
ResetAndRestart,
|
||||||
|
ResetAndSpawn(ModeId,StageId),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SessionControlInstruction{
|
||||||
|
SetPaused(bool),
|
||||||
|
// copy the current session simulation recording into a replay and view it
|
||||||
|
CopyRecordingIntoReplayAndSpectate,
|
||||||
|
StopSpectate,
|
||||||
|
SaveReplay,
|
||||||
|
LoadIntoReplayState,
|
||||||
|
}
|
||||||
|
pub enum SessionPlaybackInstruction{
|
||||||
|
SkipForward,
|
||||||
|
SkipBack,
|
||||||
|
TogglePaused,
|
||||||
|
DecreaseTimescale,
|
||||||
|
IncreaseTimescale,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrameState{
|
||||||
|
pub body:physics::Body,
|
||||||
|
pub camera:physics::PhysicsCamera,
|
||||||
|
pub time:PhysicsTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Simulation{
|
||||||
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
|
physics:physics::PhysicsState,
|
||||||
|
}
|
||||||
|
impl Simulation{
|
||||||
|
pub const fn new(
|
||||||
|
timer:Timer<Scaled<SessionTimeInner,PhysicsTimeInner>>,
|
||||||
|
physics:physics::PhysicsState,
|
||||||
|
)->Self{
|
||||||
|
Self{
|
||||||
|
timer,
|
||||||
|
physics,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_frame_state(&self,time:SessionTime)->FrameState{
|
||||||
|
FrameState{
|
||||||
|
body:self.physics.camera_body(),
|
||||||
|
camera:self.physics.camera(),
|
||||||
|
time:self.timer.time(time),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Recording{
|
||||||
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||||
|
}
|
||||||
|
impl Recording{
|
||||||
|
pub fn new(
|
||||||
|
instructions:Vec<TimedInstruction<PhysicsInputInstruction,PhysicsTimeInner>>,
|
||||||
|
)->Self{
|
||||||
|
Self{instructions}
|
||||||
|
}
|
||||||
|
fn clear(&mut self){
|
||||||
|
self.instructions.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Replay{
|
||||||
|
next_instruction_id:usize,
|
||||||
|
recording:Recording,
|
||||||
|
simulation:Simulation,
|
||||||
|
}
|
||||||
|
impl Replay{
|
||||||
|
pub const fn new(
|
||||||
|
recording:Recording,
|
||||||
|
simulation:Simulation,
|
||||||
|
)->Self{
|
||||||
|
Self{
|
||||||
|
next_instruction_id:0,
|
||||||
|
recording,
|
||||||
|
simulation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn advance(&mut self,physics_data:&PhysicsData,time_limit:SessionTime){
|
||||||
|
let mut time=self.simulation.timer.time(time_limit);
|
||||||
|
loop{
|
||||||
|
if let Some(ins)=self.recording.instructions.get(self.next_instruction_id){
|
||||||
|
if ins.time<time{
|
||||||
|
PhysicsContext::run_input_instruction(&mut self.simulation.physics,physics_data,ins.clone());
|
||||||
|
self.next_instruction_id+=1;
|
||||||
|
}else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
// loop playback
|
||||||
|
self.next_instruction_id=0;
|
||||||
|
// No need to reset physics because the very first instruction is 'Reset'
|
||||||
|
let new_time=self.recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
self.simulation.timer.set_time(time_limit,new_time);
|
||||||
|
time=new_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
||||||
|
struct BotId(u32);
|
||||||
|
//#[derive(Clone,Copy,Hash,PartialEq,Eq)]
|
||||||
|
//struct PlayerId(u32);
|
||||||
|
|
||||||
|
enum ViewState{
|
||||||
|
Play,
|
||||||
|
//Spectate(PlayerId),
|
||||||
|
Replay(BotId),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Session{
|
||||||
|
directories:Directories,
|
||||||
|
user_settings:UserSettings,
|
||||||
|
mouse_interpolator:crate::mouse_interpolator::MouseInterpolator,
|
||||||
|
view_state:ViewState,
|
||||||
|
//gui:GuiState
|
||||||
|
geometry_shared:physics::PhysicsData,
|
||||||
|
simulation:Simulation,
|
||||||
|
// below fields not included in lite session
|
||||||
|
recording:Recording,
|
||||||
|
//players:HashMap<PlayerId,Simulation>,
|
||||||
|
replays:HashMap<BotId,Replay>,
|
||||||
|
}
|
||||||
|
impl Session{
|
||||||
|
pub fn new(
|
||||||
|
user_settings:UserSettings,
|
||||||
|
directories:Directories,
|
||||||
|
simulation:Simulation,
|
||||||
|
)->Self{
|
||||||
|
Self{
|
||||||
|
user_settings,
|
||||||
|
directories,
|
||||||
|
mouse_interpolator:MouseInterpolator::new(),
|
||||||
|
geometry_shared:Default::default(),
|
||||||
|
simulation,
|
||||||
|
view_state:ViewState::Play,
|
||||||
|
recording:Default::default(),
|
||||||
|
replays:HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn clear_recording(&mut self){
|
||||||
|
self.recording.clear();
|
||||||
|
}
|
||||||
|
fn change_map(&mut self,map:&strafesnet_common::map::CompleteMap){
|
||||||
|
self.simulation.physics.clear();
|
||||||
|
self.geometry_shared.generate_models(map);
|
||||||
|
}
|
||||||
|
pub fn get_frame_state(&self,time:SessionTime)->Option<FrameState>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>Some(self.simulation.get_frame_state(time)),
|
||||||
|
ViewState::Replay(bot_id)=>self.replays.get(bot_id).map(|replay|
|
||||||
|
replay.simulation.get_frame_state(time)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn user_settings(&self)->&UserSettings{
|
||||||
|
&self.user_settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mouseinterpolator consumes RawInputInstruction
|
||||||
|
// mouseinterpolator emits PhysicsInputInstruction
|
||||||
|
// mouseinterpolator consumes DoStep to move on to the next emitted instruction
|
||||||
|
// Session comsumes SessionInstruction -> forwards RawInputInstruction to mouseinterpolator
|
||||||
|
// Session consumes DoStep -> forwards DoStep to mouseinterpolator
|
||||||
|
// Session emits DoStep
|
||||||
|
|
||||||
|
impl InstructionConsumer<Instruction<'_>> for Session{
|
||||||
|
type TimeInner=SessionTimeInner;
|
||||||
|
fn process_instruction(&mut self,ins:TimedInstruction<Instruction,Self::TimeInner>){
|
||||||
|
// repetitive procedure macro
|
||||||
|
macro_rules! run_mouse_interpolator_instruction{
|
||||||
|
($instruction:expr)=>{
|
||||||
|
self.mouse_interpolator.process_instruction(TimedInstruction{
|
||||||
|
time:ins.time,
|
||||||
|
instruction:TimedInstruction{
|
||||||
|
time:self.simulation.timer.time(ins.time),
|
||||||
|
instruction:$instruction,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// process any timeouts that occured since the last instruction
|
||||||
|
self.process_exhaustive(ins.time);
|
||||||
|
|
||||||
|
match ins.instruction{
|
||||||
|
// send it down to MouseInterpolator with two timestamps, SessionTime and PhysicsTime
|
||||||
|
Instruction::Input(SessionInputInstruction::Mouse(pos))=>{
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::MoveMouse(pos));
|
||||||
|
},
|
||||||
|
Instruction::Input(SessionInputInstruction::SetControl(set_control_instruction))=>{
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::SetControl(set_control_instruction));
|
||||||
|
},
|
||||||
|
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndRestart))=>{
|
||||||
|
self.clear_recording();
|
||||||
|
let mode_id=self.simulation.physics.mode();
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Restart(mode_id)));
|
||||||
|
},
|
||||||
|
Instruction::Input(SessionInputInstruction::Mode(ImplicitModeInstruction::ResetAndSpawn(mode_id,spawn_id)))=>{
|
||||||
|
self.clear_recording();
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Reset));
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(MiscInstruction::SetSensitivity(self.user_settings().calculate_sensitivity())));
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Mode(ModeInstruction::Spawn(mode_id,spawn_id)));
|
||||||
|
},
|
||||||
|
Instruction::Input(SessionInputInstruction::Misc(misc_instruction))=>{
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Misc(misc_instruction));
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::SetPaused(paused))=>{
|
||||||
|
// don't flush the buffered instructions in the mouse interpolator
|
||||||
|
// until the mouse is confirmed to be not moving at a later time
|
||||||
|
// what if they pause for 5ms lmao
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,paused);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::CopyRecordingIntoReplayAndSpectate)=> if let ViewState::Play=self.view_state{
|
||||||
|
// Bind: B
|
||||||
|
|
||||||
|
// pause simulation
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,true);
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let mut recording=Recording::default();
|
||||||
|
recording.instructions.extend(self.recording.instructions.iter().cloned());
|
||||||
|
|
||||||
|
// create timer starting at first instruction (or zero if the list is empty)
|
||||||
|
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
let timer=Timer::unpaused(ins.time,new_time);
|
||||||
|
|
||||||
|
// create default physics state
|
||||||
|
let simulation=Simulation::new(timer,Default::default());
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
let bot_id=BotId(self.replays.len() as u32);
|
||||||
|
self.replays.insert(bot_id,Replay::new(
|
||||||
|
recording,
|
||||||
|
simulation,
|
||||||
|
));
|
||||||
|
|
||||||
|
// begin spectate
|
||||||
|
self.view_state=ViewState::Replay(bot_id);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::StopSpectate)=>{
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
// delete the bot, otherwise it's inaccessible and wastes CPU
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>{
|
||||||
|
self.replays.remove(&bot_id);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,false);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::SaveReplay)=>{
|
||||||
|
// Bind: N
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||||
|
let mut replays_path=self.directories.replays.clone();
|
||||||
|
let file_name=format!("{}.snfb",ins.time);
|
||||||
|
std::thread::spawn(move ||{
|
||||||
|
std::fs::create_dir_all(replays_path.as_path()).unwrap();
|
||||||
|
replays_path.push(file_name);
|
||||||
|
let file=std::fs::File::create(replays_path).unwrap();
|
||||||
|
strafesnet_snf::bot::write_bot(
|
||||||
|
std::io::BufWriter::new(file),
|
||||||
|
strafesnet_physics::VERSION.get(),
|
||||||
|
replay.recording.instructions
|
||||||
|
).unwrap();
|
||||||
|
println!("Finished writing bot file!");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,false);
|
||||||
|
},
|
||||||
|
Instruction::Control(SessionControlInstruction::LoadIntoReplayState)=>{
|
||||||
|
// Bind: J
|
||||||
|
let view_state=core::mem::replace(&mut self.view_state,ViewState::Play);
|
||||||
|
match view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.remove(&bot_id){
|
||||||
|
self.recording.instructions=replay.recording.instructions.into_iter().take(replay.next_instruction_id).collect();
|
||||||
|
self.simulation=replay.simulation;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// don't unpause -- use the replay timer state whether it is pasued or unpaused
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::IncreaseTimescale)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>{
|
||||||
|
// allow simulation timescale for fun
|
||||||
|
let scale=self.simulation.timer.get_scale();
|
||||||
|
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
||||||
|
},
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let scale=replay.simulation.timer.get_scale();
|
||||||
|
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*5,scale.den()*4).unwrap());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::DecreaseTimescale)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>{
|
||||||
|
// allow simulation timescale for fun
|
||||||
|
let scale=self.simulation.timer.get_scale();
|
||||||
|
self.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
||||||
|
},
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let scale=replay.simulation.timer.get_scale();
|
||||||
|
replay.simulation.timer.set_scale(ins.time,strafesnet_common::integer::Ratio64::new(scale.num()*4,scale.den()*5).unwrap());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::SkipForward)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
||||||
|
replay.simulation.timer.set_time(ins.time,time);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::SkipBack)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
let time=replay.simulation.timer.time(ins.time+SessionTime::from_secs(5));
|
||||||
|
replay.simulation.timer.set_time(ins.time,time);
|
||||||
|
// resimulate the entire playback lol
|
||||||
|
replay.next_instruction_id=0;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Instruction::Playback(SessionPlaybackInstruction::TogglePaused)=>{
|
||||||
|
match &self.view_state{
|
||||||
|
ViewState::Play=>(),
|
||||||
|
ViewState::Replay(bot_id)=>if let Some(replay)=self.replays.get_mut(bot_id){
|
||||||
|
_=replay.simulation.timer.set_paused(ins.time,!replay.simulation.timer.is_paused());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::ChangeMap(complete_map)=>{
|
||||||
|
self.clear_recording();
|
||||||
|
self.change_map(complete_map);
|
||||||
|
},
|
||||||
|
Instruction::LoadReplay(bot)=>{
|
||||||
|
// pause simulation
|
||||||
|
_=self.simulation.timer.set_paused(ins.time,true);
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let recording=Recording::new(bot.instructions);
|
||||||
|
|
||||||
|
// create timer starting at first instruction (or zero if the list is empty)
|
||||||
|
let new_time=recording.instructions.first().map_or(PhysicsTime::ZERO,|ins|ins.time);
|
||||||
|
let timer=Timer::unpaused(ins.time,new_time);
|
||||||
|
|
||||||
|
// create default physics state
|
||||||
|
let simulation=Simulation::new(timer,Default::default());
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
let bot_id=BotId(self.replays.len() as u32);
|
||||||
|
self.replays.insert(bot_id,Replay::new(
|
||||||
|
recording,
|
||||||
|
simulation,
|
||||||
|
));
|
||||||
|
|
||||||
|
// begin spectate
|
||||||
|
self.view_state=ViewState::Replay(bot_id);
|
||||||
|
},
|
||||||
|
Instruction::Idle=>{
|
||||||
|
run_mouse_interpolator_instruction!(MouseInterpolatorInstruction::Idle);
|
||||||
|
// this just refreshes the replays
|
||||||
|
for replay in self.replays.values_mut(){
|
||||||
|
// TODO: filter idles from recording, inject new idles in real time
|
||||||
|
replay.advance(&self.geometry_shared,ins.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// process all emitted output instructions
|
||||||
|
self.process_exhaustive(ins.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstructionConsumer<StepInstruction> for Session{
|
||||||
|
type TimeInner=SessionTimeInner;
|
||||||
|
fn process_instruction(&mut self,ins:TimedInstruction<StepInstruction,Self::TimeInner>){
|
||||||
|
let time=self.simulation.timer.time(ins.time);
|
||||||
|
if let Some(instruction)=self.mouse_interpolator.pop_buffered_instruction(ins.set_time(time)){
|
||||||
|
//record
|
||||||
|
self.recording.instructions.push(instruction.clone());
|
||||||
|
PhysicsContext::run_input_instruction(&mut self.simulation.physics,&self.geometry_shared,instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl InstructionEmitter<StepInstruction> for Session{
|
||||||
|
type TimeInner=SessionTimeInner;
|
||||||
|
fn next_instruction(&self,time_limit:SessionTime)->Option<TimedInstruction<StepInstruction,Self::TimeInner>>{
|
||||||
|
self.mouse_interpolator.next_instruction(time_limit)
|
||||||
|
}
|
||||||
|
}
|
10
engine/settings/Cargo.toml
Normal file
10
engine/settings/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "strafesnet_settings"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
configparser = "3.0.2"
|
||||||
|
directories = "6.0.0"
|
||||||
|
glam = "0.29.0"
|
||||||
|
strafesnet_common = { path = "../../lib/common", registry = "strafesnet" }
|
8
engine/settings/LICENSE
Normal file
8
engine/settings/LICENSE
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*******************************************************
|
||||||
|
* Copyright (C) 2023-2024 Rhys Lloyd <krakow20@gmail.com>
|
||||||
|
*
|
||||||
|
* This file is part of the StrafesNET bhop/surf client.
|
||||||
|
*
|
||||||
|
* StrafesNET can not be copied and/or distributed
|
||||||
|
* without the express permission of Rhys Lloyd
|
||||||
|
*******************************************************/
|
32
engine/settings/src/directories.rs
Normal file
32
engine/settings/src/directories.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::settings::{UserSettings,load_user_settings};
|
||||||
|
|
||||||
|
pub struct Directories{
|
||||||
|
pub settings:PathBuf,
|
||||||
|
pub maps:PathBuf,
|
||||||
|
pub replays:PathBuf,
|
||||||
|
}
|
||||||
|
impl Directories{
|
||||||
|
pub fn settings(&self)->UserSettings{
|
||||||
|
load_user_settings(&self.settings)
|
||||||
|
}
|
||||||
|
pub fn user()->Option<Self>{
|
||||||
|
let dirs=directories::ProjectDirs::from("net.strafes","StrafesNET","Strafe Client")?;
|
||||||
|
Some(Self{
|
||||||
|
settings:dirs.config_dir().join("settings.conf"),
|
||||||
|
maps:dirs.cache_dir().join("maps"),
|
||||||
|
// separate directory for remote downloaded replays (cache)
|
||||||
|
// bots:dirs.cache_dir().join("bots"),
|
||||||
|
replays:dirs.data_local_dir().join("replays"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn portable()->Result<Self,std::io::Error>{
|
||||||
|
let current_dir=std::env::current_dir()?;
|
||||||
|
Ok(Self{
|
||||||
|
settings:current_dir.join("settings.conf"),
|
||||||
|
maps:current_dir.join("maps"),
|
||||||
|
replays:current_dir.join("replays"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
2
engine/settings/src/lib.rs
Normal file
2
engine/settings/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod settings;
|
||||||
|
pub mod directories;
|
139
engine/settings/src/settings.rs
Normal file
139
engine/settings/src/settings.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use strafesnet_common::integer::{Ratio64,Ratio64Vec2};
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Ratio{
|
||||||
|
ratio:f64,
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum DerivedFov{
|
||||||
|
FromScreenAspect,
|
||||||
|
FromAspect(Ratio),
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Fov{
|
||||||
|
Exactly{x:f64,y:f64},
|
||||||
|
SpecifyXDeriveY{x:f64,y:DerivedFov},
|
||||||
|
SpecifyYDeriveX{x:DerivedFov,y:f64},
|
||||||
|
}
|
||||||
|
impl Default for Fov{
|
||||||
|
fn default()->Self{
|
||||||
|
Fov::SpecifyYDeriveX{x:DerivedFov::FromScreenAspect,y:1.0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum DerivedSensitivity{
|
||||||
|
FromRatio(Ratio64),
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Sensitivity{
|
||||||
|
Exactly{x:Ratio64,y:Ratio64},
|
||||||
|
SpecifyXDeriveY{x:Ratio64,y:DerivedSensitivity},
|
||||||
|
SpecifyYDeriveX{x:DerivedSensitivity,y:Ratio64},
|
||||||
|
}
|
||||||
|
impl Default for Sensitivity{
|
||||||
|
fn default()->Self{
|
||||||
|
Sensitivity::SpecifyXDeriveY{x:Ratio64::ONE*524288,y:DerivedSensitivity::FromRatio(Ratio64::ONE)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default,Clone)]
|
||||||
|
pub struct UserSettings{
|
||||||
|
fov:Fov,
|
||||||
|
sensitivity:Sensitivity,
|
||||||
|
}
|
||||||
|
impl UserSettings{
|
||||||
|
pub fn calculate_fov(&self,zoom:f64,screen_size:&glam::UVec2)->glam::DVec2{
|
||||||
|
zoom*match &self.fov{
|
||||||
|
&Fov::Exactly{x,y}=>glam::dvec2(x,y),
|
||||||
|
Fov::SpecifyXDeriveY{x,y}=>match y{
|
||||||
|
DerivedFov::FromScreenAspect=>glam::dvec2(*x,x*(screen_size.y as f64/screen_size.x as f64)),
|
||||||
|
DerivedFov::FromAspect(ratio)=>glam::dvec2(*x,x*ratio.ratio),
|
||||||
|
},
|
||||||
|
Fov::SpecifyYDeriveX{x,y}=>match x{
|
||||||
|
DerivedFov::FromScreenAspect=>glam::dvec2(y*(screen_size.x as f64/screen_size.y as f64),*y),
|
||||||
|
DerivedFov::FromAspect(ratio)=>glam::dvec2(y*ratio.ratio,*y),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn calculate_sensitivity(&self)->Ratio64Vec2{
|
||||||
|
match &self.sensitivity{
|
||||||
|
Sensitivity::Exactly{x,y}=>Ratio64Vec2::new(x.clone(),y.clone()),
|
||||||
|
Sensitivity::SpecifyXDeriveY{x,y}=>match y{
|
||||||
|
DerivedSensitivity::FromRatio(ratio)=>Ratio64Vec2::new(x.clone(),x.mul_ref(ratio)),
|
||||||
|
}
|
||||||
|
Sensitivity::SpecifyYDeriveX{x,y}=>match x{
|
||||||
|
DerivedSensitivity::FromRatio(ratio)=>Ratio64Vec2::new(y.mul_ref(ratio),y.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
//sensitivity is raw input dots (i.e. dpi = dots per inch) to radians conversion factor
|
||||||
|
sensitivity_x=0.001
|
||||||
|
sensitivity_y_from_x_ratio=1
|
||||||
|
Sensitivity::DeriveY{x:0.0.001,y:DerivedSensitivity{ratio:1.0}}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub fn load_user_settings(path:&std::path::Path)->UserSettings{
|
||||||
|
let mut cfg=configparser::ini::Ini::new();
|
||||||
|
if let Ok(_)=cfg.load(path){
|
||||||
|
let (cfg_fov_x,cfg_fov_y)=(cfg.getfloat("camera","fov_x"),cfg.getfloat("camera","fov_y"));
|
||||||
|
let fov=match(cfg_fov_x,cfg_fov_y){
|
||||||
|
(Ok(Some(fov_x)),Ok(Some(fov_y)))=>Fov::Exactly {
|
||||||
|
x:fov_x,
|
||||||
|
y:fov_y
|
||||||
|
},
|
||||||
|
(Ok(Some(fov_x)),Ok(None))=>Fov::SpecifyXDeriveY{
|
||||||
|
x:fov_x,
|
||||||
|
y:if let Ok(Some(fov_y_from_x_ratio))=cfg.getfloat("camera","fov_y_from_x_ratio"){
|
||||||
|
DerivedFov::FromAspect(Ratio{ratio:fov_y_from_x_ratio})
|
||||||
|
}else{
|
||||||
|
DerivedFov::FromScreenAspect
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Ok(None),Ok(Some(fov_y)))=>Fov::SpecifyYDeriveX{
|
||||||
|
x:if let Ok(Some(fov_x_from_y_ratio))=cfg.getfloat("camera","fov_x_from_y_ratio"){
|
||||||
|
DerivedFov::FromAspect(Ratio{ratio:fov_x_from_y_ratio})
|
||||||
|
}else{
|
||||||
|
DerivedFov::FromScreenAspect
|
||||||
|
},
|
||||||
|
y:fov_y,
|
||||||
|
},
|
||||||
|
_=>{
|
||||||
|
Fov::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let (cfg_sensitivity_x,cfg_sensitivity_y)=(cfg.getfloat("camera","sensitivity_x"),cfg.getfloat("camera","sensitivity_y"));
|
||||||
|
let sensitivity=match(cfg_sensitivity_x,cfg_sensitivity_y){
|
||||||
|
(Ok(Some(sensitivity_x)),Ok(Some(sensitivity_y)))=>Sensitivity::Exactly {
|
||||||
|
x:Ratio64::try_from(sensitivity_x).unwrap(),
|
||||||
|
y:Ratio64::try_from(sensitivity_y).unwrap(),
|
||||||
|
},
|
||||||
|
(Ok(Some(sensitivity_x)),Ok(None))=>Sensitivity::SpecifyXDeriveY{
|
||||||
|
x:Ratio64::try_from(sensitivity_x).unwrap(),
|
||||||
|
y:if let Ok(Some(sensitivity_y_from_x_ratio))=cfg.getfloat("camera","sensitivity_y_from_x_ratio"){
|
||||||
|
DerivedSensitivity::FromRatio(Ratio64::try_from(sensitivity_y_from_x_ratio).unwrap())
|
||||||
|
}else{
|
||||||
|
DerivedSensitivity::FromRatio(Ratio64::ONE)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(Ok(None),Ok(Some(sensitivity_y)))=>Sensitivity::SpecifyYDeriveX{
|
||||||
|
x:if let Ok(Some(sensitivity_x_from_y_ratio))=cfg.getfloat("camera","sensitivity_x_from_y_ratio"){
|
||||||
|
DerivedSensitivity::FromRatio(Ratio64::try_from(sensitivity_x_from_y_ratio).unwrap())
|
||||||
|
}else{
|
||||||
|
DerivedSensitivity::FromRatio(Ratio64::ONE)
|
||||||
|
},
|
||||||
|
y:Ratio64::try_from(sensitivity_y).unwrap(),
|
||||||
|
},
|
||||||
|
_=>{
|
||||||
|
Sensitivity::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
UserSettings{
|
||||||
|
fov,
|
||||||
|
sensitivity,
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
UserSettings::default()
|
||||||
|
}
|
||||||
|
}
|
9
integration-testing/Cargo.toml
Normal file
9
integration-testing/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "integration-testing"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
strafesnet_common = { version = "0.5.2", path = "../lib/common", registry = "strafesnet" }
|
||||||
|
strafesnet_physics = { version = "0.1.0", path = "../engine/physics", registry = "strafesnet" }
|
||||||
|
strafesnet_snf = { path = "../lib/snf", registry = "strafesnet" }
|
221
integration-testing/src/main.rs
Normal file
221
integration-testing/src/main.rs
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
|
||||||
|
use std::{io::{Cursor,Read},path::Path};
|
||||||
|
|
||||||
|
use strafesnet_physics::physics::{PhysicsData,PhysicsState,PhysicsContext};
|
||||||
|
|
||||||
|
fn main(){
|
||||||
|
test_determinism().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ReplayError{
|
||||||
|
IO(std::io::Error),
|
||||||
|
SNF(strafesnet_snf::Error),
|
||||||
|
SNFM(strafesnet_snf::map::Error),
|
||||||
|
SNFB(strafesnet_snf::bot::Error),
|
||||||
|
}
|
||||||
|
impl From<std::io::Error> for ReplayError{
|
||||||
|
fn from(value:std::io::Error)->Self{
|
||||||
|
Self::IO(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_snf::Error> for ReplayError{
|
||||||
|
fn from(value:strafesnet_snf::Error)->Self{
|
||||||
|
Self::SNF(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_snf::map::Error> for ReplayError{
|
||||||
|
fn from(value:strafesnet_snf::map::Error)->Self{
|
||||||
|
Self::SNFM(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<strafesnet_snf::bot::Error> for ReplayError{
|
||||||
|
fn from(value:strafesnet_snf::bot::Error)->Self{
|
||||||
|
Self::SNFB(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_entire_file(path:impl AsRef<Path>)->Result<Cursor<Vec<u8>>,std::io::Error>{
|
||||||
|
let mut file=std::fs::File::open(path)?;
|
||||||
|
let mut data=Vec::new();
|
||||||
|
file.read_to_end(&mut data)?;
|
||||||
|
Ok(Cursor::new(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_replay()->Result<(),ReplayError>{
|
||||||
|
println!("loading map file..");
|
||||||
|
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||||
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||||
|
|
||||||
|
println!("loading bot file..");
|
||||||
|
let data=read_entire_file("../tools/replays/535s+159764769ns.snfb")?;
|
||||||
|
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||||
|
|
||||||
|
// create recording
|
||||||
|
let mut physics_data=PhysicsData::default();
|
||||||
|
println!("generating models..");
|
||||||
|
physics_data.generate_models(&map);
|
||||||
|
println!("simulating...");
|
||||||
|
let mut physics=PhysicsState::default();
|
||||||
|
for ins in bot.instructions{
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics,&physics_data,ins);
|
||||||
|
}
|
||||||
|
match physics.get_finish_time(){
|
||||||
|
Some(time)=>println!("finish time:{}",time),
|
||||||
|
None=>println!("simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
enum DeterminismResult{
|
||||||
|
Deterministic,
|
||||||
|
NonDeterministic,
|
||||||
|
}
|
||||||
|
fn segment_determinism(bot:strafesnet_snf::bot::Segment,physics_data:&PhysicsData)->DeterminismResult{
|
||||||
|
// create default physics state
|
||||||
|
let mut physics_deterministic=PhysicsState::default();
|
||||||
|
// create a second physics state
|
||||||
|
let mut physics_filtered=PhysicsState::default();
|
||||||
|
|
||||||
|
// invent a new bot id and insert the replay
|
||||||
|
println!("simulating...");
|
||||||
|
|
||||||
|
let mut non_idle_count=0;
|
||||||
|
|
||||||
|
for (i,ins) in bot.instructions.into_iter().enumerate(){
|
||||||
|
let state_deterministic=physics_deterministic.clone();
|
||||||
|
let state_filtered=physics_filtered.clone();
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics_deterministic,&physics_data,ins.clone());
|
||||||
|
match ins{
|
||||||
|
strafesnet_common::instruction::TimedInstruction{instruction:strafesnet_common::physics::Instruction::Idle,..}=>(),
|
||||||
|
other=>{
|
||||||
|
non_idle_count+=1;
|
||||||
|
// run
|
||||||
|
PhysicsContext::run_input_instruction(&mut physics_filtered,&physics_data,other.clone());
|
||||||
|
// check if position matches
|
||||||
|
let b0=physics_deterministic.camera_body();
|
||||||
|
let b1=physics_filtered.camera_body();
|
||||||
|
if b0.position!=b1.position{
|
||||||
|
println!("desync at instruction #{}",i);
|
||||||
|
println!("non idle instructions completed={non_idle_count}");
|
||||||
|
println!("instruction #{i}={:?}",other);
|
||||||
|
println!("deterministic state0:\n{state_deterministic:?}");
|
||||||
|
println!("filtered state0:\n{state_filtered:?}");
|
||||||
|
println!("deterministic state1:\n{:?}",physics_deterministic);
|
||||||
|
println!("filtered state1:\n{:?}",physics_filtered);
|
||||||
|
return DeterminismResult::NonDeterministic;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match physics_deterministic.get_finish_time(){
|
||||||
|
Some(time)=>println!("[with idle] finish time:{}",time),
|
||||||
|
None=>println!("[with idle] simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
match physics_filtered.get_finish_time(){
|
||||||
|
Some(time)=>println!("[filtered] finish time:{}",time),
|
||||||
|
None=>println!("[filtered] simulation did not end in finished state"),
|
||||||
|
}
|
||||||
|
DeterminismResult::Deterministic
|
||||||
|
}
|
||||||
|
type ThreadResult=Result<Option<DeterminismResult>,ReplayError>;
|
||||||
|
fn read_and_run(file_path:std::path::PathBuf,physics_data:&PhysicsData)->ThreadResult{
|
||||||
|
let data=read_entire_file(file_path.as_path())?;
|
||||||
|
let bot=strafesnet_snf::read_bot(data)?.read_all()?;
|
||||||
|
println!("Running {:?}",file_path.file_stem());
|
||||||
|
Ok(Some(segment_determinism(bot,physics_data)))
|
||||||
|
}
|
||||||
|
fn do_thread<'a>(s:&'a std::thread::Scope<'a,'_>,file_path:std::path::PathBuf,send:std::sync::mpsc::Sender<ThreadResult>,physics_data:&'a PhysicsData){
|
||||||
|
s.spawn(move ||{
|
||||||
|
let result=read_and_run(file_path,physics_data);
|
||||||
|
// send when thread is complete
|
||||||
|
send.send(result).unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn get_file_path(dir_entry:std::fs::DirEntry)->Result<Option<std::path::PathBuf>,std::io::Error>{
|
||||||
|
Ok(dir_entry.file_type()?.is_file().then_some(
|
||||||
|
dir_entry.path()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn test_determinism()->Result<(),ReplayError>{
|
||||||
|
let thread_limit=std::thread::available_parallelism()?.get();
|
||||||
|
println!("loading map file..");
|
||||||
|
let data=read_entire_file("../tools/bhop_maps/5692113331.snfm")?;
|
||||||
|
let map=strafesnet_snf::read_map(data)?.into_complete_map()?;
|
||||||
|
|
||||||
|
let mut physics_data=PhysicsData::default();
|
||||||
|
println!("generating models..");
|
||||||
|
physics_data.generate_models(&map);
|
||||||
|
|
||||||
|
let (send,recv)=std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
let mut read_dir=std::fs::read_dir("../tools/replays")?;
|
||||||
|
|
||||||
|
// promise that &physics_data will outlive the spawned threads
|
||||||
|
let thread_results=std::thread::scope(|s|{
|
||||||
|
let mut thread_results=Vec::new();
|
||||||
|
|
||||||
|
// spawn threads
|
||||||
|
println!("spawning up to {thread_limit} threads...");
|
||||||
|
let mut active_thread_count=0;
|
||||||
|
while active_thread_count<thread_limit{
|
||||||
|
if let Some(dir_entry_result)=read_dir.next(){
|
||||||
|
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||||
|
active_thread_count+=1;
|
||||||
|
do_thread(s,file_path,send.clone(),&physics_data);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn another thread every time a message is received from the channel
|
||||||
|
println!("riding parallelism wave...");
|
||||||
|
while let Some(dir_entry_result)=read_dir.next(){
|
||||||
|
if let Some(file_path)=get_file_path(dir_entry_result?)?{
|
||||||
|
// wait for a thread to complete
|
||||||
|
thread_results.push(recv.recv().unwrap());
|
||||||
|
do_thread(s,file_path,send.clone(),&physics_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait for remaining threads to complete
|
||||||
|
println!("waiting for all threads to complete...");
|
||||||
|
for _ in 0..active_thread_count{
|
||||||
|
thread_results.push(recv.recv().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("done.");
|
||||||
|
Ok::<_,ReplayError>(thread_results)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// tally results
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Totals{
|
||||||
|
deterministic:u32,
|
||||||
|
nondeterministic:u32,
|
||||||
|
invalid:u32,
|
||||||
|
error:u32,
|
||||||
|
}
|
||||||
|
let Totals{deterministic,nondeterministic,invalid,error}=thread_results.into_iter().fold(Totals::default(),|mut totals,result|{
|
||||||
|
match result{
|
||||||
|
Ok(Some(DeterminismResult::Deterministic))=>totals.deterministic+=1,
|
||||||
|
Ok(Some(DeterminismResult::NonDeterministic))=>totals.nondeterministic+=1,
|
||||||
|
Ok(None)=>totals.invalid+=1,
|
||||||
|
Err(_)=>totals.error+=1,
|
||||||
|
}
|
||||||
|
totals
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("deterministic={deterministic}");
|
||||||
|
println!("nondeterministic={nondeterministic}");
|
||||||
|
println!("invalid={invalid}");
|
||||||
|
println!("error={error}");
|
||||||
|
|
||||||
|
assert!(nondeterministic==0);
|
||||||
|
assert!(invalid==0);
|
||||||
|
assert!(error==0);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
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>
|
333
lib/bsp_loader/src/bsp.rs
Normal file
333
lib/bsp_loader/src/bsp.rs
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
use strafesnet_common::{map,model,integer,gameplay_attributes};
|
||||||
|
|
||||||
|
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())?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
37
lib/bsp_loader/src/lib.rs
Normal file
37
lib/bsp_loader/src/lib.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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>
|
56
lib/common/src/aabb.rs
Normal file
56
lib/common/src/aabb.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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
|
||||||
|
// }
|
||||||
|
}
|
194
lib/common/src/bvh.rs
Normal file
194
lib/common/src/bvh.rs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
174
lib/common/src/gameplay_attributes.rs
Normal file
174
lib/common/src/gameplay_attributes.rs
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
use crate::model;
|
||||||
|
use crate::integer::{AbsoluteTime,Planar64,Planar64Vec3};
|
||||||
|
|
||||||
|
//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())
|
||||||
|
}
|
||||||
|
}
|
332
lib/common/src/gameplay_modes.rs
Normal file
332
lib/common/src/gameplay_modes.rs
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
use std::collections::{HashSet,HashMap};
|
||||||
|
use crate::model::ModelId;
|
||||||
|
use crate::gameplay_style;
|
||||||
|
use crate::updatable::Updatable;
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
612
lib/common/src/gameplay_style.rs
Normal file
612
lib/common/src/gameplay_style.rs
Normal file
@ -0,0 +1,612 @@
|
|||||||
|
const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16
|
||||||
|
|
||||||
|
use crate::integer::{int,vec3::int as int3,AbsoluteTime,Ratio64,Planar64,Planar64Vec3};
|
||||||
|
use crate::controls_bitflag::Controls;
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
lib/common/src/instruction.rs
Normal file
82
lib/common/src/instruction.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::integer::Time;
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct TimedInstruction<I,T>{
|
||||||
|
pub time:Time<T>,
|
||||||
|
pub instruction:I,
|
||||||
|
}
|
||||||
|
impl<I,T> TimedInstruction<I,T>{
|
||||||
|
#[inline]
|
||||||
|
pub fn set_time<TimeInner>(self,new_time:Time<TimeInner>)->TimedInstruction<I,TimeInner>{
|
||||||
|
TimedInstruction{
|
||||||
|
time:new_time,
|
||||||
|
instruction:self.instruction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure all emitted instructions are processed before consuming external instructions
|
||||||
|
pub trait InstructionEmitter<I>{
|
||||||
|
type TimeInner;
|
||||||
|
fn next_instruction(&self,time_limit:Time<Self::TimeInner>)->Option<TimedInstruction<I,Self::TimeInner>>;
|
||||||
|
}
|
||||||
|
/// Apply an atomic state update
|
||||||
|
pub trait InstructionConsumer<I>{
|
||||||
|
type TimeInner;
|
||||||
|
fn process_instruction(&mut self,instruction:TimedInstruction<I,Self::TimeInner>);
|
||||||
|
}
|
||||||
|
/// If the object produces its own instructions, allow exhaustively feeding them back in
|
||||||
|
pub trait InstructionFeedback<I,T>:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>
|
||||||
|
where
|
||||||
|
Time<T>:Copy,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn process_exhaustive(&mut self,time_limit:Time<T>){
|
||||||
|
while let Some(instruction)=self.next_instruction(time_limit){
|
||||||
|
self.process_instruction(instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<I,T,X> InstructionFeedback<I,T> for X
|
||||||
|
where
|
||||||
|
Time<T>:Copy,
|
||||||
|
X:InstructionEmitter<I,TimeInner=T>+InstructionConsumer<I,TimeInner=T>,
|
||||||
|
{}
|
||||||
|
|
||||||
|
//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,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
pub const fn new(time:Time<T>)->Self{
|
||||||
|
Self{
|
||||||
|
time,
|
||||||
|
instruction:None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn time(&self)->Time<T>{
|
||||||
|
self.time
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn collect(&mut self,instruction:Option<TimedInstruction<I,T>>){
|
||||||
|
if let Some(ins)=instruction{
|
||||||
|
if ins.time<self.time{
|
||||||
|
self.time=ins.time;
|
||||||
|
self.instruction=Some(ins.instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn take(self)->Option<TimedInstruction<I,T>>{
|
||||||
|
//STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR
|
||||||
|
self.instruction.map(|instruction|TimedInstruction{
|
||||||
|
time:self.time,
|
||||||
|
instruction
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
680
lib/common/src/integer.rs
Normal file
680
lib/common/src/integer.rs
Normal file
@ -0,0 +1,680 @@
|
|||||||
|
pub use fixed_wide::fixed::{Fixed,Fix};
|
||||||
|
pub use ratio_ops::ratio::{Ratio,Divide};
|
||||||
|
|
||||||
|
//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 EPSILON:Self=Self::raw(1);
|
||||||
|
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 bvh;
|
||||||
|
pub mod map;
|
||||||
|
pub mod run;
|
||||||
|
pub mod aabb;
|
||||||
|
pub mod model;
|
||||||
|
pub mod mouse;
|
||||||
|
pub mod timer;
|
||||||
|
pub mod integer;
|
||||||
|
pub mod physics;
|
||||||
|
pub mod session;
|
||||||
|
pub mod updatable;
|
||||||
|
pub mod instruction;
|
||||||
|
pub mod gameplay_attributes;
|
||||||
|
pub mod gameplay_modes;
|
||||||
|
pub mod gameplay_style;
|
||||||
|
pub mod controls_bitflag;
|
14
lib/common/src/map.rs
Normal file
14
lib/common/src/map.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use crate::model;
|
||||||
|
use crate::gameplay_modes;
|
||||||
|
use crate::gameplay_attributes;
|
||||||
|
//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>,
|
||||||
|
}
|
133
lib/common/src/model.rs
Normal file
133
lib/common/src/model.rs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
use crate::integer::{Planar64Vec3,Planar64Affine3};
|
||||||
|
use crate::gameplay_attributes;
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
28
lib/common/src/mouse.rs
Normal file
28
lib/common/src/mouse.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
56
lib/common/src/physics.rs
Normal file
56
lib/common/src/physics.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use crate::mouse::MouseState;
|
||||||
|
use crate::gameplay_modes::{ModeId,StageId};
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)]
|
||||||
|
pub enum TimeInner{}
|
||||||
|
pub type Time=crate::integer::Time<TimeInner>;
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum Instruction{
|
||||||
|
Mouse(MouseInstruction),
|
||||||
|
SetControl(SetControlInstruction),
|
||||||
|
Mode(ModeInstruction),
|
||||||
|
Misc(MiscInstruction),
|
||||||
|
/// Idle: there were no input events, but the simulation is safe to advance to this timestep
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
impl Instruction{
|
||||||
|
pub const IDLE:Self=Self::Idle;
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum MouseInstruction{
|
||||||
|
/// Replace the entire interpolation state to avoid dividing by zero when replacing twice
|
||||||
|
ReplaceMouse{
|
||||||
|
m0:MouseState<TimeInner>,
|
||||||
|
m1:MouseState<TimeInner>,
|
||||||
|
},
|
||||||
|
SetNextMouse(MouseState<TimeInner>),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum SetControlInstruction{
|
||||||
|
SetMoveRight(bool),
|
||||||
|
SetMoveUp(bool),
|
||||||
|
SetMoveBack(bool),
|
||||||
|
SetMoveLeft(bool),
|
||||||
|
SetMoveDown(bool),
|
||||||
|
SetMoveForward(bool),
|
||||||
|
SetJump(bool),
|
||||||
|
SetZoom(bool),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum ModeInstruction{
|
||||||
|
/// Reset: fully replace the physics state.
|
||||||
|
/// This forgets all inputs and settings which need to be reapplied.
|
||||||
|
Reset,
|
||||||
|
/// Restart: Teleport to the start zone.
|
||||||
|
/// This runs when you press R or teleport to a bonus
|
||||||
|
Restart(ModeId),
|
||||||
|
/// Spawn: Teleport to a specific mode's spawn
|
||||||
|
/// This runs when the map loads to put you at the map lobby
|
||||||
|
Spawn(ModeId,StageId),
|
||||||
|
}
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub enum MiscInstruction{
|
||||||
|
PracticeFly,
|
||||||
|
SetSensitivity(crate::integer::Ratio64Vec2),
|
||||||
|
}
|
119
lib/common/src/run.rs
Normal file
119
lib/common/src/run.rs
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
use crate::timer::{TimerFixed,Realtime,Paused,Unpaused};
|
||||||
|
|
||||||
|
use crate::physics::{TimeInner as PhysicsTimeInner,Time as PhysicsTime};
|
||||||
|
|
||||||
|
#[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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_finish_time(&self)->Option<Time>{
|
||||||
|
match &self.state{
|
||||||
|
RunState::Finished{timer}=>Some(timer.time()),
|
||||||
|
_=>None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
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>;
|
363
lib/common/src/timer.rs
Normal file
363
lib/common/src/timer.rs
Normal file
@ -0,0 +1,363 @@
|
|||||||
|
use crate::integer::{Time,Ratio64};
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
let mut timer=TimerFixed{
|
||||||
|
state:self.state,
|
||||||
|
_paused:Unpaused,
|
||||||
|
};
|
||||||
|
timer.set_time(time,new_time);
|
||||||
|
timer
|
||||||
|
}
|
||||||
|
pub fn time(&self)->Time<T::Out>{
|
||||||
|
self.state.get_offset().coerce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
pub fn time(&self,time:Time<T::In>)->Time<T::Out>{
|
||||||
|
self.state.get_time(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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 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(),
|
||||||
|
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!(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!(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.
|
829
lib/fixed_wide/src/fixed.rs
Normal file
829
lib/fixed_wide/src/fixed.rs
Normal file
@ -0,0 +1,829 @@
|
|||||||
|
use bnum::{BInt,cast::As};
|
||||||
|
|
||||||
|
#[derive(Clone,Copy,Debug,Default,Hash,PartialEq,Eq,PartialOrd,Ord)]
|
||||||
|
/// A Fixed point number for which multiply operations widen the bits in the output. (when the wide-mul feature is enabled)
|
||||||
|
/// N is the number of u64s to use
|
||||||
|
/// F is the number of fractional bits (always N*32 lol)
|
||||||
|
pub struct Fixed<const N:usize,const F:usize>{
|
||||||
|
pub(crate)bits:BInt<{N}>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N:usize,const F:usize> Fixed<N,F>{
|
||||||
|
pub const MAX:Self=Self::from_bits(BInt::<N>::MAX);
|
||||||
|
pub const MIN:Self=Self::from_bits(BInt::<N>::MIN);
|
||||||
|
pub const ZERO:Self=Self::from_bits(BInt::<N>::ZERO);
|
||||||
|
pub const EPSILON:Self=Self::from_bits(BInt::<N>::ONE);
|
||||||
|
pub const NEG_EPSILON:Self=Self::from_bits(BInt::<N>::NEG_ONE);
|
||||||
|
pub const ONE:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32));
|
||||||
|
pub const TWO:Self=Self::from_bits(BInt::<N>::TWO.shl(F as u32));
|
||||||
|
pub const HALF:Self=Self::from_bits(BInt::<N>::ONE.shl(F as u32-1));
|
||||||
|
pub const NEG_ONE:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32));
|
||||||
|
pub const NEG_TWO:Self=Self::from_bits(BInt::<N>::NEG_TWO.shl(F as u32));
|
||||||
|
pub const NEG_HALF:Self=Self::from_bits(BInt::<N>::NEG_ONE.shl(F as u32-1));
|
||||||
|
}
|
||||||
|
impl<const N:usize,const F:usize> Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
pub const fn from_bits(bits:BInt::<N>)->Self{
|
||||||
|
Self{
|
||||||
|
bits,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn to_bits(self)->BInt<N>{
|
||||||
|
self.bits
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn raw_digit(value:i64)->Self{
|
||||||
|
let mut digits=[0u64;N];
|
||||||
|
digits[0]=value.abs() as u64;
|
||||||
|
//sign bit
|
||||||
|
digits[N-1]|=(value&i64::MIN) as u64;
|
||||||
|
Self::from_bits(BInt::from_bits(bnum::BUint::from_digits(digits)))
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn is_zero(self)->bool{
|
||||||
|
self.bits.is_zero()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn is_negative(self)->bool{
|
||||||
|
self.bits.is_negative()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn is_positive(self)->bool{
|
||||||
|
self.bits.is_positive()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn abs(self)->Self{
|
||||||
|
Self::from_bits(self.bits.abs())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const F:usize> Fixed<1,F>{
|
||||||
|
/// My old code called this function everywhere so let's provide it
|
||||||
|
#[inline]
|
||||||
|
pub const fn raw(value:i64)->Self{
|
||||||
|
Self::from_bits(BInt::from_bits(bnum::BUint::from_digit(value as u64)))
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub const fn to_raw(self)->i64{
|
||||||
|
let &[digit]=self.to_bits().to_bits().digits();
|
||||||
|
digit as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_from {
|
||||||
|
($($from:ty),*)=>{
|
||||||
|
$(
|
||||||
|
impl<const N:usize,const F:usize> From<$from> for Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn from(value:$from)->Self{
|
||||||
|
Self::from_bits(BInt::<{N}>::from(value)<<F as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl_from!(
|
||||||
|
u8,u16,u32,u64,u128,usize,
|
||||||
|
i8,i16,i32,i64,i128,isize
|
||||||
|
);
|
||||||
|
|
||||||
|
impl<const N:usize,const F:usize,T> PartialEq<T> for Fixed<N,F>
|
||||||
|
where
|
||||||
|
T:Copy,
|
||||||
|
BInt::<N>:From<T>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn eq(&self,&other:&T)->bool{
|
||||||
|
self.bits.eq(&other.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N:usize,const F:usize,T> PartialOrd<T> for Fixed<N,F>
|
||||||
|
where
|
||||||
|
T:Copy,
|
||||||
|
BInt::<N>:From<T>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn partial_cmp(&self,&other:&T)->Option<std::cmp::Ordering>{
|
||||||
|
self.bits.partial_cmp(&other.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N:usize,const F:usize> std::ops::Neg for Fixed<N,F>{
|
||||||
|
type Output=Self;
|
||||||
|
#[inline]
|
||||||
|
fn neg(self)->Self{
|
||||||
|
Self::from_bits(self.bits.neg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N:usize,const F:usize> std::iter::Sum for Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn sum<I:Iterator<Item=Self>>(iter:I)->Self{
|
||||||
|
let mut sum=Self::ZERO;
|
||||||
|
for elem in iter{
|
||||||
|
sum+=elem;
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn signed_shift(lhs:u64,rhs:i32)->u64{
|
||||||
|
if rhs.is_negative(){
|
||||||
|
lhs>>-rhs
|
||||||
|
}else{
|
||||||
|
lhs<<rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! impl_into_float {
|
||||||
|
( $output: ty, $unsigned:ty, $exponent_bits:expr, $mantissa_bits:expr ) => {
|
||||||
|
impl<const N:usize,const F:usize> Into<$output> for Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn into(self)->$output{
|
||||||
|
const DIGIT_SHIFT:u32=6;//Log2[64]
|
||||||
|
// SBBB BBBB
|
||||||
|
// 1001 1110 0000 0000
|
||||||
|
let sign=if self.bits.is_negative(){(1 as $unsigned)<<(<$unsigned>::BITS-1)}else{0};
|
||||||
|
let unsigned=self.bits.unsigned_abs();
|
||||||
|
let most_significant_bit=unsigned.bits();
|
||||||
|
let exp=if unsigned.is_zero(){
|
||||||
|
0
|
||||||
|
}else{
|
||||||
|
let msb=most_significant_bit as $unsigned;
|
||||||
|
let _127=((1 as $unsigned)<<($exponent_bits-1))-1;
|
||||||
|
let msb_offset=msb+_127-1-F as $unsigned;
|
||||||
|
msb_offset<<($mantissa_bits-1)
|
||||||
|
};
|
||||||
|
let digits=unsigned.digits();
|
||||||
|
let digit_index=most_significant_bit.saturating_sub(1)>>DIGIT_SHIFT;
|
||||||
|
let digit=digits[digit_index as usize];
|
||||||
|
//How many bits does the mantissa take from this digit
|
||||||
|
let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
|
||||||
|
let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
|
||||||
|
let mut unmasked_mant=signed_shift(digit,rest_of_mantissa) as $unsigned;
|
||||||
|
if 0<rest_of_mantissa&&digit_index!=0{
|
||||||
|
//take the next digit down and shove some of its bits onto the bottom of the mantissa
|
||||||
|
let digit=digits[digit_index as usize-1];
|
||||||
|
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
|
||||||
|
let rest_of_mantissa=$mantissa_bits as i32-(take_bits as i32);
|
||||||
|
let unmasked_mant2=signed_shift(digit,rest_of_mantissa) as $unsigned;
|
||||||
|
unmasked_mant|=unmasked_mant2;
|
||||||
|
}
|
||||||
|
let mant=unmasked_mant&((1 as $unsigned)<<($mantissa_bits-1))-1;
|
||||||
|
let bits=sign|exp|mant;
|
||||||
|
<$output>::from_bits(bits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl_into_float!(f32,u32,8,24);
|
||||||
|
impl_into_float!(f64,u64,11,53);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn integer_decode_f32(f: f32) -> (u64, i16, bool) {
|
||||||
|
let bits: u32 = f.to_bits();
|
||||||
|
let sign: bool = bits & (1<<31) != 0;
|
||||||
|
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, bool) {
|
||||||
|
let bits: u64 = f.to_bits();
|
||||||
|
let sign: bool = bits & (1u64<<63) != 0;
|
||||||
|
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,Eq,PartialEq)]
|
||||||
|
pub enum FixedFromFloatError{
|
||||||
|
Nan,
|
||||||
|
Infinite,
|
||||||
|
Overflow,
|
||||||
|
Underflow,
|
||||||
|
}
|
||||||
|
impl FixedFromFloatError{
|
||||||
|
pub fn underflow_to_zero<const N:usize,const F:usize>(self)->Result<Fixed<N,F>,Self>{
|
||||||
|
match self{
|
||||||
|
FixedFromFloatError::Underflow=>Ok(Fixed::ZERO),
|
||||||
|
_=>Err(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! impl_from_float {
|
||||||
|
( $decode:ident, $input: ty, $mantissa_bits:expr ) => {
|
||||||
|
impl<const N:usize,const F:usize> TryFrom<$input> for Fixed<N,F>{
|
||||||
|
type Error=FixedFromFloatError;
|
||||||
|
#[inline]
|
||||||
|
fn try_from(value:$input)->Result<Self,Self::Error>{
|
||||||
|
const DIGIT_SHIFT:u32=6;
|
||||||
|
match value.classify(){
|
||||||
|
std::num::FpCategory::Nan=>Err(FixedFromFloatError::Nan),
|
||||||
|
std::num::FpCategory::Infinite=>Err(FixedFromFloatError::Infinite),
|
||||||
|
std::num::FpCategory::Zero=>Ok(Self::ZERO),
|
||||||
|
std::num::FpCategory::Subnormal
|
||||||
|
|std::num::FpCategory::Normal
|
||||||
|
=>{
|
||||||
|
let (m,e,s)=$decode(value);
|
||||||
|
let mut digits=[0u64;N];
|
||||||
|
let most_significant_bit=e as i32+$mantissa_bits as i32+F as i32;
|
||||||
|
if most_significant_bit<0{
|
||||||
|
return Err(FixedFromFloatError::Underflow);
|
||||||
|
}
|
||||||
|
let digit_index=most_significant_bit>>DIGIT_SHIFT;
|
||||||
|
let digit=digits.get_mut(digit_index as usize).ok_or(FixedFromFloatError::Overflow)?;
|
||||||
|
let take_bits=most_significant_bit-(digit_index<<DIGIT_SHIFT);
|
||||||
|
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
|
||||||
|
*digit=signed_shift(m,rest_of_mantissa);
|
||||||
|
if rest_of_mantissa<0&&digit_index!=0{
|
||||||
|
//we don't care if some float bits are partially truncated
|
||||||
|
if let Some(digit)=digits.get_mut((digit_index-1) as usize){
|
||||||
|
let take_bits=most_significant_bit-((digit_index-1)<<DIGIT_SHIFT);
|
||||||
|
let rest_of_mantissa=-($mantissa_bits as i32-(take_bits as i32));
|
||||||
|
*digit=signed_shift(m,rest_of_mantissa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let bits=BInt::from_bits(bnum::BUint::from_digits(digits));
|
||||||
|
Ok(if s{
|
||||||
|
Self::from_bits(bits.overflowing_neg().0)
|
||||||
|
}else{
|
||||||
|
Self::from_bits(bits)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl_from_float!(integer_decode_f32,f32,24);
|
||||||
|
impl_from_float!(integer_decode_f64,f64,53);
|
||||||
|
|
||||||
|
impl<const N:usize,const F:usize> core::fmt::Display for Fixed<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn fmt(&self,f:&mut core::fmt::Formatter)->Result<(),core::fmt::Error>{
|
||||||
|
let float:f32=(*self).into();
|
||||||
|
core::write!(f,"{:.3}",float)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_additive_operator {
|
||||||
|
( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
|
||||||
|
impl<const N:usize,const F:usize> $struct<N,F>{
|
||||||
|
#[inline]
|
||||||
|
pub const fn $method(self, other: Self) -> Self {
|
||||||
|
Self::from_bits(self.bits.$method(other.bits))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
|
||||||
|
type Output = $output;
|
||||||
|
#[inline]
|
||||||
|
fn $method(self, other: Self) -> Self::Output {
|
||||||
|
self.$method(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||||
|
where
|
||||||
|
BInt::<N>:From<U>,
|
||||||
|
{
|
||||||
|
type Output = $output;
|
||||||
|
#[inline]
|
||||||
|
fn $method(self, other: U) -> Self::Output {
|
||||||
|
Self::from_bits(self.bits.$method(BInt::<N>::from(other).shl(F as u32)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! impl_additive_assign_operator {
|
||||||
|
( $struct: ident, $trait: ident, $method: ident ) => {
|
||||||
|
impl<const N:usize,const F:usize> core::ops::$trait for $struct<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn $method(&mut self, other: Self) {
|
||||||
|
self.bits.$method(other.bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||||
|
where
|
||||||
|
BInt::<N>:From<U>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn $method(&mut self, other: U) {
|
||||||
|
self.bits.$method(BInt::<N>::from(other).shl(F as u32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Impl arithmetic pperators
|
||||||
|
impl_additive_assign_operator!( Fixed, AddAssign, add_assign );
|
||||||
|
impl_additive_operator!( Fixed, Add, add, Self );
|
||||||
|
impl_additive_assign_operator!( Fixed, SubAssign, sub_assign );
|
||||||
|
impl_additive_operator!( Fixed, Sub, sub, Self );
|
||||||
|
impl_additive_assign_operator!( Fixed, RemAssign, rem_assign );
|
||||||
|
impl_additive_operator!( Fixed, Rem, rem, Self );
|
||||||
|
|
||||||
|
// Impl bitwise operators
|
||||||
|
impl_additive_assign_operator!( Fixed, BitAndAssign, bitand_assign );
|
||||||
|
impl_additive_operator!( Fixed, BitAnd, bitand, Self );
|
||||||
|
impl_additive_assign_operator!( Fixed, BitOrAssign, bitor_assign );
|
||||||
|
impl_additive_operator!( Fixed, BitOr, bitor, Self );
|
||||||
|
impl_additive_assign_operator!( Fixed, BitXorAssign, bitxor_assign );
|
||||||
|
impl_additive_operator!( Fixed, BitXor, bitxor, Self );
|
||||||
|
|
||||||
|
// non-wide operators. The result is the same width as the inputs.
|
||||||
|
|
||||||
|
// This macro is not used in the default configuration.
|
||||||
|
#[allow(unused_macros)]
|
||||||
|
macro_rules! impl_multiplicative_operator_not_const_generic {
|
||||||
|
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||||
|
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
|
||||||
|
type Output = $output;
|
||||||
|
#[inline]
|
||||||
|
fn $method(self, other: Self) -> Self::Output {
|
||||||
|
paste::item!{
|
||||||
|
self.[<fixed_ $method>](other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! impl_multiplicative_assign_operator_not_const_generic {
|
||||||
|
( ($struct: ident, $trait: ident, $method: ident, $non_assign_method: ident ), $width:expr ) => {
|
||||||
|
impl<const F:usize> core::ops::$trait for $struct<$width,F>{
|
||||||
|
#[inline]
|
||||||
|
fn $method(&mut self, other: Self) {
|
||||||
|
paste::item!{
|
||||||
|
*self=self.[<fixed_ $non_assign_method>](other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_multiply_operator_not_const_generic {
|
||||||
|
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||||
|
impl<const F:usize> $struct<$width,F>{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn [<fixed_ $method>](self, rhs: Self) -> Self {
|
||||||
|
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
|
||||||
|
let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
|
||||||
|
if self.is_negative()==rhs.is_negative(){
|
||||||
|
Self::from_bits(out.shr(F as u32).as_())
|
||||||
|
}else{
|
||||||
|
-Self::from_bits(out.shr(F as u32).as_())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature="wide-mul"))]
|
||||||
|
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
|
||||||
|
#[cfg(feature="deferred-division")]
|
||||||
|
impl ratio_ops::ratio::Divide<i64> for Fixed<$width,{$width*32}>{
|
||||||
|
type Output=Self;
|
||||||
|
#[inline]
|
||||||
|
fn divide(self, other: i64)->Self::Output{
|
||||||
|
Self::from_bits(self.bits.div_euclid(BInt::from(other)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! impl_divide_operator_not_const_generic {
|
||||||
|
( ($struct: ident, $trait: ident, $method: ident, $output: ty ), $width:expr ) => {
|
||||||
|
impl<const F:usize> $struct<$width,F>{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn [<fixed_ $method>](self,other:Self)->Self{
|
||||||
|
//this only needs to be $width+F as u32/64+1 but MUH CONST GENERICS!!!!!
|
||||||
|
let lhs=self.bits.as_::<BInt::<{$width*2}>>().shl(F as u32);
|
||||||
|
let rhs=other.bits.as_::<BInt::<{$width*2}>>();
|
||||||
|
Self::from_bits(lhs.div_euclid(rhs).as_())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(not(feature="wide-mul"),not(feature="deferred-division")))]
|
||||||
|
impl_multiplicative_operator_not_const_generic!(($struct, $trait, $method, $output ), $width);
|
||||||
|
#[cfg(all(not(feature="wide-mul"),feature="deferred-division"))]
|
||||||
|
impl<const F:usize> ratio_ops::ratio::Divide for $struct<$width,F>{
|
||||||
|
type Output = $output;
|
||||||
|
#[inline]
|
||||||
|
fn divide(self, other: Self) -> Self::Output {
|
||||||
|
paste::item!{
|
||||||
|
self.[<fixed_ $method>](other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_multiplicative_operator {
|
||||||
|
( $struct: ident, $trait: ident, $method: ident, $inner_method: ident, $output: ty ) => {
|
||||||
|
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||||
|
where
|
||||||
|
BInt::<N>:From<U>+core::ops::$trait,
|
||||||
|
{
|
||||||
|
type Output = $output;
|
||||||
|
#[inline]
|
||||||
|
fn $method(self,other:U)->Self::Output{
|
||||||
|
Self::from_bits(self.bits.$inner_method(BInt::<N>::from(other)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! impl_multiplicative_assign_operator {
|
||||||
|
( $struct: ident, $trait: ident, $method: ident, $not_assign_method: ident ) => {
|
||||||
|
impl<const N:usize,const F:usize,U> core::ops::$trait<U> for $struct<N,F>
|
||||||
|
where
|
||||||
|
BInt::<N>:From<U>+core::ops::$trait,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn $method(&mut self,other:U){
|
||||||
|
self.bits=self.bits.$not_assign_method(BInt::<N>::from(other));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! macro_repeated{
|
||||||
|
(
|
||||||
|
$macro:ident,
|
||||||
|
$any:tt,
|
||||||
|
$($repeated:tt),*
|
||||||
|
)=>{
|
||||||
|
$(
|
||||||
|
$macro!($any, $repeated);
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! macro_16 {
|
||||||
|
( $macro: ident, $any:tt ) => {
|
||||||
|
macro_repeated!($macro,$any,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, MulAssign, mul_assign, mul) );
|
||||||
|
macro_16!( impl_multiply_operator_not_const_generic, (Fixed, Mul, mul, Self) );
|
||||||
|
macro_16!( impl_multiplicative_assign_operator_not_const_generic, (Fixed, DivAssign, div_assign, div) );
|
||||||
|
macro_16!( impl_divide_operator_not_const_generic, (Fixed, Div, div, Self) );
|
||||||
|
impl_multiplicative_assign_operator!( Fixed, MulAssign, mul_assign, mul );
|
||||||
|
impl_multiplicative_operator!( Fixed, Mul, mul, mul, Self );
|
||||||
|
impl_multiplicative_assign_operator!( Fixed, DivAssign, div_assign, div_euclid );
|
||||||
|
impl_multiplicative_operator!( Fixed, Div, div, div_euclid, Self );
|
||||||
|
#[cfg(feature="deferred-division")]
|
||||||
|
impl<const LHS_N:usize,const LHS_F:usize,const RHS_N:usize,const RHS_F:usize> core::ops::Div<Fixed<RHS_N,RHS_F>> for Fixed<LHS_N,LHS_F>{
|
||||||
|
type Output=ratio_ops::ratio::Ratio<Fixed<LHS_N,LHS_F>,Fixed<RHS_N,RHS_F>>;
|
||||||
|
#[inline]
|
||||||
|
fn div(self, other: Fixed<RHS_N,RHS_F>)->Self::Output{
|
||||||
|
ratio_ops::ratio::Ratio::new(self,other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature="deferred-division")]
|
||||||
|
impl<const N:usize,const F:usize> ratio_ops::ratio::Parity for Fixed<N,F>{
|
||||||
|
fn parity(&self)->bool{
|
||||||
|
self.is_negative()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! impl_shift_operator {
|
||||||
|
( $struct: ident, $trait: ident, $method: ident, $output: ty ) => {
|
||||||
|
impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
|
||||||
|
type Output = $output;
|
||||||
|
#[inline]
|
||||||
|
fn $method(self, other: u32) -> Self::Output {
|
||||||
|
Self::from_bits(self.bits.$method(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! impl_shift_assign_operator {
|
||||||
|
( $struct: ident, $trait: ident, $method: ident ) => {
|
||||||
|
impl<const N:usize,const F:usize> core::ops::$trait<u32> for $struct<N,F>{
|
||||||
|
#[inline]
|
||||||
|
fn $method(&mut self, other: u32) {
|
||||||
|
self.bits.$method(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
impl_shift_assign_operator!( Fixed, ShlAssign, shl_assign );
|
||||||
|
impl_shift_operator!( Fixed, Shl, shl, Self );
|
||||||
|
impl_shift_assign_operator!( Fixed, ShrAssign, shr_assign );
|
||||||
|
impl_shift_operator!( Fixed, Shr, shr, Self );
|
||||||
|
|
||||||
|
// wide operators. The result width is the sum of the input widths, i.e. none of the multiplication
|
||||||
|
|
||||||
|
#[allow(unused_macros)]
|
||||||
|
macro_rules! impl_wide_operators{
|
||||||
|
($lhs:expr,$rhs:expr)=>{
|
||||||
|
impl core::ops::Mul<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
|
||||||
|
#[inline]
|
||||||
|
fn mul(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
|
||||||
|
paste::item!{
|
||||||
|
self.[<wide_mul_ $lhs _ $rhs>](other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature="deferred-division"))]
|
||||||
|
impl core::ops::Div<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
|
||||||
|
#[inline]
|
||||||
|
fn div(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
|
||||||
|
paste::item!{
|
||||||
|
self.[<wide_div_ $lhs _ $rhs>](other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature="deferred-division")]
|
||||||
|
impl ratio_ops::ratio::Divide<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
type Output=Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>;
|
||||||
|
#[inline]
|
||||||
|
fn divide(self, other: Fixed<$rhs,{$rhs*32}>)->Self::Output{
|
||||||
|
paste::item!{
|
||||||
|
self.[<wide_div_ $lhs _ $rhs>](other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WIDE MUL: multiply into a wider type
|
||||||
|
// let a = I32F32::ONE;
|
||||||
|
// let b:I64F64 = a.wide_mul(a);
|
||||||
|
macro_rules! impl_wide_not_const_generic{
|
||||||
|
(
|
||||||
|
(),
|
||||||
|
($lhs:expr,$rhs:expr)
|
||||||
|
)=>{
|
||||||
|
impl Fixed<$lhs,{$lhs*32}>
|
||||||
|
{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn [<wide_mul_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
|
||||||
|
let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>();
|
||||||
|
let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
|
||||||
|
Fixed::from_bits(lhs*rhs)
|
||||||
|
}
|
||||||
|
/// This operation cannot represent the fraction exactly,
|
||||||
|
/// but it shapes the output to have precision for the
|
||||||
|
/// largest and smallest possible fractions.
|
||||||
|
#[inline]
|
||||||
|
pub fn [<wide_div_ $lhs _ $rhs>](self,rhs:Fixed<$rhs,{$rhs*32}>)->Fixed<{$lhs+$rhs},{($lhs+$rhs)*32}>{
|
||||||
|
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
|
||||||
|
let lhs=self.bits.as_::<BInt<{$lhs+$rhs}>>().shl($rhs*64);
|
||||||
|
let rhs=rhs.bits.as_::<BInt<{$lhs+$rhs}>>();
|
||||||
|
Fixed::from_bits(lhs/rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature="wide-mul")]
|
||||||
|
impl_wide_operators!($lhs,$rhs);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
macro_rules! impl_wide_same_size_not_const_generic{
|
||||||
|
(
|
||||||
|
(),
|
||||||
|
$width:expr
|
||||||
|
)=>{
|
||||||
|
impl Fixed<$width,{$width*32}>
|
||||||
|
{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn [<wide_mul_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
|
||||||
|
let (low,high)=self.bits.unsigned_abs().widening_mul(rhs.bits.unsigned_abs());
|
||||||
|
let out:BInt::<{$width*2}>=unsafe{core::mem::transmute([low,high])};
|
||||||
|
if self.is_negative()==rhs.is_negative(){
|
||||||
|
Fixed::from_bits(out)
|
||||||
|
}else{
|
||||||
|
// Normal neg is the cheapest negation operation
|
||||||
|
// And the inputs cannot reach the point where it matters
|
||||||
|
Fixed::from_bits(out.neg())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// This operation cannot represent the fraction exactly,
|
||||||
|
/// but it shapes the output to have precision for the
|
||||||
|
/// largest and smallest possible fractions.
|
||||||
|
#[inline]
|
||||||
|
pub fn [<wide_div_ $width _ $width>](self,rhs:Fixed<$width,{$width*32}>)->Fixed<{$width*2},{$width*2*32}>{
|
||||||
|
// (lhs/2^LHS_FRAC)/(rhs/2^RHS_FRAC)
|
||||||
|
let lhs=self.bits.as_::<BInt<{$width*2}>>().shl($width*64);
|
||||||
|
let rhs=rhs.bits.as_::<BInt<{$width*2}>>();
|
||||||
|
Fixed::from_bits(lhs/rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature="wide-mul")]
|
||||||
|
impl_wide_operators!($width,$width);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//const generics sidestepped wahoo
|
||||||
|
macro_repeated!(
|
||||||
|
impl_wide_not_const_generic,(),
|
||||||
|
(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),
|
||||||
|
(1,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),
|
||||||
|
(1,3),(2,3), (4,3),(5,3),(6,3),(7,3),(8,3),(9,3),(10,3),(11,3),(12,3),(13,3),
|
||||||
|
(1,4),(2,4),(3,4), (5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),
|
||||||
|
(1,5),(2,5),(3,5),(4,5), (6,5),(7,5),(8,5),(9,5),(10,5),(11,5),
|
||||||
|
(1,6),(2,6),(3,6),(4,6),(5,6), (7,6),(8,6),(9,6),(10,6),
|
||||||
|
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7), (8,7),(9,7),
|
||||||
|
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8), (9,8),
|
||||||
|
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),
|
||||||
|
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),
|
||||||
|
(1,11),(2,11),(3,11),(4,11),(5,11),
|
||||||
|
(1,12),(2,12),(3,12),(4,12),
|
||||||
|
(1,13),(2,13),(3,13),
|
||||||
|
(1,14),(2,14),
|
||||||
|
(1,15)
|
||||||
|
);
|
||||||
|
macro_repeated!(
|
||||||
|
impl_wide_same_size_not_const_generic,(),
|
||||||
|
1,2,3,4,5,6,7,8
|
||||||
|
);
|
||||||
|
|
||||||
|
pub trait Fix<Out>{
|
||||||
|
fn fix(self)->Out;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_fix_rhs_lt_lhs_not_const_generic{
|
||||||
|
(
|
||||||
|
(),
|
||||||
|
($lhs:expr,$rhs:expr)
|
||||||
|
)=>{
|
||||||
|
impl Fixed<$lhs,{$lhs*32}>
|
||||||
|
{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits.shr(($lhs-$rhs)*32)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
paste::item!{
|
||||||
|
self.[<fix_ $rhs>]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! impl_fix_lhs_lt_rhs_not_const_generic{
|
||||||
|
(
|
||||||
|
(),
|
||||||
|
($lhs:expr,$rhs:expr)
|
||||||
|
)=>{
|
||||||
|
impl Fixed<$lhs,{$lhs*32}>
|
||||||
|
{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
Fixed::from_bits(bnum::cast::As::as_::<BInt::<$rhs>>(self.bits).shl(($rhs-$lhs)*32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
paste::item!{
|
||||||
|
self.[<fix_ $rhs>]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
macro_rules! impl_fix_lhs_eq_rhs_not_const_generic{
|
||||||
|
(
|
||||||
|
(),
|
||||||
|
($lhs:expr,$rhs:expr)
|
||||||
|
)=>{
|
||||||
|
impl Fixed<$lhs,{$lhs*32}>
|
||||||
|
{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn [<fix_ $rhs>](self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Fix<Fixed<$rhs,{$rhs*32}>> for Fixed<$lhs,{$lhs*32}>{
|
||||||
|
fn fix(self)->Fixed<$rhs,{$rhs*32}>{
|
||||||
|
paste::item!{
|
||||||
|
self.[<fix_ $rhs>]()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// I LOVE NOT BEING ABLE TO USE CONST GENERICS
|
||||||
|
|
||||||
|
macro_repeated!(
|
||||||
|
impl_fix_rhs_lt_lhs_not_const_generic,(),
|
||||||
|
(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),(17,1),
|
||||||
|
(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),
|
||||||
|
(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),
|
||||||
|
(5,4),(6,4),(7,4),(8,4),(9,4),(10,4),(11,4),(12,4),(13,4),(14,4),(15,4),(16,4),
|
||||||
|
(6,5),(7,5),(8,5),(9,5),(10,5),(11,5),(12,5),(13,5),(14,5),(15,5),(16,5),
|
||||||
|
(7,6),(8,6),(9,6),(10,6),(11,6),(12,6),(13,6),(14,6),(15,6),(16,6),
|
||||||
|
(8,7),(9,7),(10,7),(11,7),(12,7),(13,7),(14,7),(15,7),(16,7),
|
||||||
|
(9,8),(10,8),(11,8),(12,8),(13,8),(14,8),(15,8),(16,8),
|
||||||
|
(10,9),(11,9),(12,9),(13,9),(14,9),(15,9),(16,9),
|
||||||
|
(11,10),(12,10),(13,10),(14,10),(15,10),(16,10),
|
||||||
|
(12,11),(13,11),(14,11),(15,11),(16,11),
|
||||||
|
(13,12),(14,12),(15,12),(16,12),
|
||||||
|
(14,13),(15,13),(16,13),
|
||||||
|
(15,14),(16,14),
|
||||||
|
(16,15)
|
||||||
|
);
|
||||||
|
macro_repeated!(
|
||||||
|
impl_fix_lhs_lt_rhs_not_const_generic,(),
|
||||||
|
(1,2),
|
||||||
|
(1,3),(2,3),
|
||||||
|
(1,4),(2,4),(3,4),
|
||||||
|
(1,5),(2,5),(3,5),(4,5),
|
||||||
|
(1,6),(2,6),(3,6),(4,6),(5,6),
|
||||||
|
(1,7),(2,7),(3,7),(4,7),(5,7),(6,7),
|
||||||
|
(1,8),(2,8),(3,8),(4,8),(5,8),(6,8),(7,8),
|
||||||
|
(1,9),(2,9),(3,9),(4,9),(5,9),(6,9),(7,9),(8,9),
|
||||||
|
(1,10),(2,10),(3,10),(4,10),(5,10),(6,10),(7,10),(8,10),(9,10),
|
||||||
|
(1,11),(2,11),(3,11),(4,11),(5,11),(6,11),(7,11),(8,11),(9,11),(10,11),
|
||||||
|
(1,12),(2,12),(3,12),(4,12),(5,12),(6,12),(7,12),(8,12),(9,12),(10,12),(11,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),
|
||||||
|
(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),
|
||||||
|
(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),
|
||||||
|
(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)
|
||||||
|
);
|
||||||
|
macro_repeated!(
|
||||||
|
impl_fix_lhs_eq_rhs_not_const_generic,(),
|
||||||
|
(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(10,10),(11,11),(12,12),(13,13),(14,14),(15,15),(16,16)
|
||||||
|
);
|
||||||
|
|
||||||
|
macro_rules! impl_not_const_generic{
|
||||||
|
($n:expr,$_2n:expr)=>{
|
||||||
|
impl Fixed<$n,{$n*32}>{
|
||||||
|
paste::item!{
|
||||||
|
#[inline]
|
||||||
|
pub fn sqrt_unchecked(self)->Self{
|
||||||
|
//1<<max_shift must be the minimum power of two which when squared is greater than self
|
||||||
|
//calculating max_shift:
|
||||||
|
//1. count "used" bits to the left of the decimal, not including the sign bit (so -1)
|
||||||
|
//2. divide by 2 via >>1 (sqrt-ish)
|
||||||
|
//3. add on fractional offset
|
||||||
|
//Voila
|
||||||
|
let used_bits=self.bits.bits() as i32-1-($n*32) as i32;
|
||||||
|
let max_shift=((used_bits>>1)+($n*32) as i32) as u32;
|
||||||
|
let mut result=Self::ZERO;
|
||||||
|
|
||||||
|
//resize self to match the wide mul output
|
||||||
|
let wide_self=self.[<fix_ $_2n>]();
|
||||||
|
//descend down the bits and check if flipping each bit would push the square over the input value
|
||||||
|
for shift in (0..=max_shift).rev(){
|
||||||
|
let new_result={
|
||||||
|
let mut bits=result.to_bits().to_bits();
|
||||||
|
bits.set_bit(shift,true);
|
||||||
|
Self::from_bits(BInt::from_bits(bits))
|
||||||
|
};
|
||||||
|
if new_result.[<wide_mul_ $n _ $n>](new_result)<=wide_self{
|
||||||
|
result=new_result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn sqrt(self)->Self{
|
||||||
|
if self<Self::ZERO{
|
||||||
|
panic!("Square root less than zero")
|
||||||
|
}else{
|
||||||
|
self.sqrt_unchecked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn sqrt_checked(self)->Option<Self>{
|
||||||
|
if self<Self::ZERO{
|
||||||
|
None
|
||||||
|
}else{
|
||||||
|
Some(self.sqrt_unchecked())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl_not_const_generic!(1,2);
|
||||||
|
impl_not_const_generic!(2,4);
|
||||||
|
impl_not_const_generic!(3,6);
|
||||||
|
impl_not_const_generic!(4,8);
|
||||||
|
impl_not_const_generic!(5,10);
|
||||||
|
impl_not_const_generic!(6,12);
|
||||||
|
impl_not_const_generic!(7,14);
|
||||||
|
impl_not_const_generic!(8,16);
|
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;
|
218
lib/fixed_wide/src/tests.rs
Normal file
218
lib/fixed_wide/src/tests.rs
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
use crate::types::I32F32;
|
||||||
|
use crate::types::I256F256;
|
||||||
|
|
||||||
|
#[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>;
|
53
lib/fixed_wide/src/zeroes.rs
Normal file
53
lib/fixed_wide/src/zeroes.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
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 types;
|
||||||
|
pub mod vector;
|
||||||
|
pub mod matrix;
|
||||||
|
|
||||||
|
#[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 @@
|
|||||||
|
|
79
lib/linear_ops/src/macros/fixed_wide.rs
Normal file
79
lib/linear_ops/src/macros/fixed_wide.rs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
#[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>]())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
272
lib/linear_ops/src/macros/matrix.rs
Normal file
272
lib/linear_ops/src/macros/matrix.rs
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
#[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 vector;
|
||||||
|
pub mod matrix;
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
357
lib/linear_ops/src/macros/vector.rs
Normal file
357
lib/linear_ops/src/macros/vector.rs
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
#[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!();
|
59
lib/linear_ops/src/named.rs
Normal file
59
lib/linear_ops/src/named.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use crate::vector::Vector;
|
||||||
|
use crate::matrix::Matrix;
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
)
|
||||||
|
);
|
96
lib/linear_ops/src/tests/fixed_wide.rs
Normal file
96
lib/linear_ops/src/tests/fixed_wide.rs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
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;
|
30
lib/linear_ops/src/tests/named.rs
Normal file
30
lib/linear_ops/src/tests/named.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use crate::types::{Vector3,Matrix3};
|
||||||
|
|
||||||
|
#[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);
|
||||||
|
}
|
59
lib/linear_ops/src/tests/tests.rs
Normal file
59
lib/linear_ops/src/tests/tests.rs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
use crate::types::{Vector2,Vector3,Matrix3x4,Matrix4x2,Matrix3x2,Matrix2x3};
|
||||||
|
|
||||||
|
#[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::vector::Vector;
|
||||||
|
use crate::matrix::Matrix;
|
||||||
|
|
||||||
|
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>;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user