mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Compare commits
550 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1318567e3 | ||
|
|
d1a02c52a9 | ||
|
|
b3efcd19db | ||
|
|
66e4e4e0a0 | ||
|
|
7c37fdb24e | ||
|
|
ceb1cc77d8 | ||
|
|
8df4726d22 | ||
|
|
408df5273d | ||
|
|
7a4d087be2 | ||
|
|
11a15ab5b1 | ||
|
|
f6ffd63b05 | ||
|
|
0afe9d2ef8 | ||
|
|
c02b95346f | ||
|
|
db33c561ab | ||
|
|
df2cfa1e57 | ||
|
|
d40e2c2ce2 | ||
|
|
215f3b9f44 | ||
|
|
766cf9ae72 | ||
|
|
26d808deb7 | ||
|
|
f9259c976d | ||
|
|
39c3285a0a | ||
|
|
57f3624ee7 | ||
|
|
5a4b656a74 | ||
|
|
bf7e13d923 | ||
|
|
dedec668e1 | ||
|
|
1e4013be98 | ||
|
|
76c3210788 | ||
|
|
4a99eff854 | ||
|
|
237dabce17 | ||
|
|
6f36f7624a | ||
|
|
1dea626422 | ||
|
|
c74cc539c9 | ||
|
|
910e32bb3e | ||
|
|
6ad3c862ce | ||
|
|
05819baf97 | ||
|
|
d84ba2aef5 | ||
|
|
ec9e7d94bc | ||
|
|
a800749b87 | ||
|
|
76fe83452e | ||
|
|
6d940503af | ||
|
|
0ea5b58b50 | ||
|
|
94c079815f | ||
|
|
11ab7a6cb6 | ||
|
|
7f67135e69 | ||
|
|
a1717bbf7a | ||
|
|
c99375364d | ||
|
|
94460ed7ab | ||
|
|
413332462f | ||
|
|
18386d57d6 | ||
|
|
f72045438e | ||
|
|
17f74c693c | ||
|
|
3fb87a4688 | ||
|
|
9205f6cab7 | ||
|
|
d0ee364fb9 | ||
|
|
3a80ad0632 | ||
|
|
93c353bbc6 | ||
|
|
9f883dccec | ||
|
|
ad3b697517 | ||
|
|
5f3097185e | ||
|
|
7ac7f7d424 | ||
|
|
ba58f4df6a | ||
|
|
297bfddf6c | ||
|
|
e4d0aca744 | ||
|
|
8b5ac226c3 | ||
|
|
cfc264fa33 | ||
|
|
9e12958ca6 | ||
|
|
8609fb9e97 | ||
|
|
9d6dda8b17 | ||
|
|
92b64f0f12 | ||
|
|
677fa71ba1 | ||
|
|
9f3f34ce70 | ||
|
|
2d2b6588b3 | ||
|
|
44fa266697 | ||
|
|
57f7ea6745 | ||
|
|
326c7e969a | ||
|
|
23f9b3b276 | ||
|
|
1e74deede3 | ||
|
|
2317ad94f5 | ||
|
|
58d1f2de96 | ||
|
|
de101b3576 | ||
|
|
9fbfc43655 | ||
|
|
e3e37023e8 | ||
|
|
63409b61ea | ||
|
|
c827e7725b | ||
|
|
0bcaed719b | ||
|
|
c77d73c460 | ||
|
|
9fc5d41e1b | ||
|
|
ac0cb9e9cb | ||
|
|
f961318035 | ||
|
|
52fa670293 | ||
|
|
d0c3e0ae8d | ||
|
|
0b2491ead5 | ||
|
|
1723d19882 | ||
|
|
416f96fe44 | ||
|
|
7b700b4a0c | ||
|
|
bde578fb9d | ||
|
|
c41dc490ed | ||
|
|
6d9e816d19 | ||
|
|
8f19afee88 | ||
|
|
784ec877d8 | ||
|
|
48c509ef54 | ||
|
|
0a5f06816d | ||
|
|
ecbeaf10ed | ||
|
|
c8e4918b4e | ||
|
|
4ab5609dbd | ||
|
|
cfe8eb62e7 | ||
|
|
f2ceb84969 | ||
|
|
039628d7e4 | ||
|
|
91fb4bc035 | ||
|
|
d89ccc3401 | ||
|
|
7ab9a37989 | ||
|
|
cf7054b6da | ||
|
|
758f9e8f2f | ||
|
|
fef21906f5 | ||
|
|
37ce6a69c1 | ||
|
|
aaed8fbd49 | ||
|
|
5fbac9d861 | ||
|
|
c54298709f | ||
|
|
b7d1637b18 | ||
|
|
015fa551b7 | ||
|
|
a681f0e5b3 | ||
|
|
281810dfbb | ||
|
|
38b8e65d53 | ||
|
|
74ccaa6cf1 | ||
|
|
4d2083df27 | ||
|
|
98068232a6 | ||
|
|
60711df671 | ||
|
|
c6b7c11418 | ||
|
|
ece8811293 | ||
|
|
8c95c491b9 | ||
|
|
45091565b4 | ||
|
|
7c0222189a | ||
|
|
a84c735761 | ||
|
|
946c5d4527 | ||
|
|
2ac6208302 | ||
|
|
0cd65c825c | ||
|
|
43d97a7b97 | ||
|
|
9923556212 | ||
|
|
0a65783637 | ||
|
|
b5bd7e0a26 | ||
|
|
82779d101b | ||
|
|
9c18d349dd | ||
|
|
8c74071e30 | ||
|
|
256e5079cf | ||
|
|
4263e86189 | ||
|
|
dadb0bb378 | ||
|
|
8ba25ca840 | ||
|
|
09ac04331f | ||
|
|
c21caa76d5 | ||
|
|
4073d8fc32 | ||
|
|
f8b7be8572 | ||
|
|
d6ab811de0 | ||
|
|
9fa6722f2b | ||
|
|
c51204e9cd | ||
|
|
74794d8610 | ||
|
|
16978f5acd | ||
|
|
71e24d0cb9 | ||
|
|
9a45f99b0e | ||
|
|
7f829f56a3 | ||
|
|
347531f4fe | ||
|
|
a1c33a0852 | ||
|
|
9cd441654e | ||
|
|
88c866a735 | ||
|
|
b11492c3f3 | ||
|
|
52e30b6323 | ||
|
|
1ebad806f9 | ||
|
|
51129d3b5d | ||
|
|
379e1cdcf0 | ||
|
|
0a8f76cfd4 | ||
|
|
a382698c32 | ||
|
|
d43002952c | ||
|
|
718f1d02c6 | ||
|
|
542de22550 | ||
|
|
327d458f00 | ||
|
|
5f206dd12e | ||
|
|
e18ce338e9 | ||
|
|
51e6255e9d | ||
|
|
7fa7e6c2cc | ||
|
|
710d154817 | ||
|
|
f354a10a81 | ||
|
|
8bb8135f41 | ||
|
|
e7b9968519 | ||
|
|
bfc58d51f2 | ||
|
|
c158022da5 | ||
|
|
9e010b0026 | ||
|
|
a085b09e07 | ||
|
|
37da4ab7fe | ||
|
|
c5945d784b | ||
|
|
16ca08446b | ||
|
|
b487350714 | ||
|
|
753b12b49e | ||
|
|
0d90cf9c20 | ||
|
|
658fd2916d | ||
|
|
ed4ea675fb | ||
|
|
2746fd7a67 | ||
|
|
a6b92c60b0 | ||
|
|
d4d36a65ec | ||
|
|
97df8ee12f | ||
|
|
2ab637b4e8 | ||
|
|
9a821b051f | ||
|
|
e2f09fc93d | ||
|
|
891d8e0468 | ||
|
|
494e31e41f | ||
|
|
b369ae6a2a | ||
|
|
5c22ef0192 | ||
|
|
d99cbb9c85 | ||
|
|
4f14f01830 | ||
|
|
930b5c701a | ||
|
|
bd9e2c47a3 | ||
|
|
53ca2cf881 | ||
|
|
55e17e7625 | ||
|
|
13b37aa2a9 | ||
|
|
fe4dde69ea | ||
|
|
0268a5a3ec | ||
|
|
4bddb529d4 | ||
|
|
435f5d1751 | ||
|
|
af8ce60dbe | ||
|
|
25f53232cd | ||
|
|
012cb518b3 | ||
|
|
2d1ffbc5b0 | ||
|
|
c88a6802a9 | ||
|
|
f5d608ac6a | ||
|
|
5d49bbfb29 | ||
|
|
5ce520a316 | ||
|
|
98c2c6e202 | ||
|
|
a2fc809d71 | ||
|
|
eb302aaef1 | ||
|
|
3b354f103a | ||
|
|
03c24a5d39 | ||
|
|
a8f13bb570 | ||
|
|
f8e35e0a72 | ||
|
|
27c153f5d3 | ||
|
|
82fdedfa14 | ||
|
|
f5bfbb13e2 | ||
|
|
80de578334 | ||
|
|
3b0acbe406 | ||
|
|
1062361612 | ||
|
|
7d81fe6f92 | ||
|
|
0285eca613 | ||
|
|
b0aea9ba89 | ||
|
|
b98eafb66f | ||
|
|
566df6793c | ||
|
|
4949e0a7f3 | ||
|
|
572dcdf1bb | ||
|
|
80ce825494 | ||
|
|
bdc3b1971c | ||
|
|
cee400a4c6 | ||
|
|
e58706d212 | ||
|
|
ad03caa064 | ||
|
|
e2d6189547 | ||
|
|
7b15d242f7 | ||
|
|
b81458d034 | ||
|
|
0595422f69 | ||
|
|
8e58adb279 | ||
|
|
a5e4b4d8e2 | ||
|
|
be2c010b5a | ||
|
|
e107326fee | ||
|
|
4da54f6dec | ||
|
|
1a3a955e10 | ||
|
|
3f304f7275 | ||
|
|
0690d742c7 | ||
|
|
43bbd4b4dd | ||
|
|
77fa27a698 | ||
|
|
8991e4f9f8 | ||
|
|
c9740651ba | ||
|
|
473550ba07 | ||
|
|
a7cbe91e73 | ||
|
|
40281f68b9 | ||
|
|
d3049751ba | ||
|
|
68164756c2 | ||
|
|
91c0ddef28 | ||
|
|
0641ca1b7b | ||
|
|
e3d9eb6ac9 | ||
|
|
7eec89bd53 | ||
|
|
289a7f8313 | ||
|
|
f49b2556ae | ||
|
|
318c43077c | ||
|
|
5691b68166 | ||
|
|
0ea44f988c | ||
|
|
59e2f796eb | ||
|
|
f1a75a98d0 | ||
|
|
76ac3fcf55 | ||
|
|
6cc3c8e84b | ||
|
|
36907c3244 | ||
|
|
dfeb02c1a7 | ||
|
|
6adf5f4090 | ||
|
|
e44ce3fb0b | ||
|
|
76ab69e44e | ||
|
|
57d009df5c | ||
|
|
b4d07b0b7e | ||
|
|
da3afa6f8e | ||
|
|
ec1c491e93 | ||
|
|
baccb556e8 | ||
|
|
eedfa61d74 | ||
|
|
2a9225178f | ||
|
|
259190e478 | ||
|
|
24a0c3b136 | ||
|
|
f46f2bc913 | ||
|
|
77f048c48e | ||
|
|
4055d51758 | ||
|
|
21eff5e1ba | ||
|
|
34174b442f | ||
|
|
77d37ab2a7 | ||
|
|
350fdbce9d | ||
|
|
e19cec4d3e | ||
|
|
e56d7665ed | ||
|
|
a0e23bfbe9 | ||
|
|
b7dc33f79e | ||
|
|
d8c31eec81 | ||
|
|
e84ee760b1 | ||
|
|
6ba1cc6bdb | ||
|
|
1b5e534592 | ||
|
|
1a3e21007b | ||
|
|
55e11ffb5c | ||
|
|
957a040996 | ||
|
|
460a42b135 | ||
|
|
8c356b401c | ||
|
|
820d06f008 | ||
|
|
3fa22fa716 | ||
|
|
333daa9cfb | ||
|
|
0e61460cdb | ||
|
|
268da18bb5 | ||
|
|
3fec29cbc6 | ||
|
|
4f3e79a4ac | ||
|
|
8b3699024b | ||
|
|
dd071f2538 | ||
|
|
ebe74dac17 | ||
|
|
909a51892f | ||
|
|
35c26bec0f | ||
|
|
8f05284792 | ||
|
|
e1d3af0734 | ||
|
|
c3fc5607ba | ||
|
|
a326d76f6a | ||
|
|
472fcab821 | ||
|
|
e7f81eb1aa | ||
|
|
31375855a0 | ||
|
|
385890d0f0 | ||
|
|
c9b4b5f18f | ||
|
|
c49af7fb33 | ||
|
|
4f042de2f4 | ||
|
|
f3e2b2ca40 | ||
|
|
a690447391 | ||
|
|
01593352ab | ||
|
|
0e4618529d | ||
|
|
57c97cd5e1 | ||
|
|
8606c0daa3 | ||
|
|
e94551d4f7 | ||
|
|
ffcd4f32ed | ||
|
|
2248f577df | ||
|
|
8a57553986 | ||
|
|
bb01ce776b | ||
|
|
631870846c | ||
|
|
a45abb7992 | ||
|
|
c7aaffa5e6 | ||
|
|
7def0d0b4e | ||
|
|
c035135fb7 | ||
|
|
15ffdeeeb8 | ||
|
|
74f6902a1b | ||
|
|
b2ae71333a | ||
|
|
fc88269f2d | ||
|
|
a191fcf803 | ||
|
|
37b109bd73 | ||
|
|
27b2a77f48 | ||
|
|
0f34dd0967 | ||
|
|
10fc717500 | ||
|
|
250068c6c2 | ||
|
|
488838752b | ||
|
|
dd0f4deae3 | ||
|
|
2df4106c92 | ||
|
|
ed58076c68 | ||
|
|
a4b300198d | ||
|
|
6980023c5a | ||
|
|
9f4d956345 | ||
|
|
ce4a90dc55 | ||
|
|
82ba193bb4 | ||
|
|
a3a004536d | ||
|
|
bb1d7e06c2 | ||
|
|
3689e6723c | ||
|
|
ef504c40b6 | ||
|
|
5e4a128d25 | ||
|
|
67d93d87b5 | ||
|
|
56b8b58606 | ||
|
|
97bc9343c1 | ||
|
|
18cea30f72 | ||
|
|
d5c048600e | ||
|
|
6d103d4ff9 | ||
|
|
7008197760 | ||
|
|
da66f33edc | ||
|
|
4109d1c825 | ||
|
|
a300663a9e | ||
|
|
cb33dac3b9 | ||
|
|
582bac8050 | ||
|
|
5e1c45bc09 | ||
|
|
b3073e6938 | ||
|
|
63e259689f | ||
|
|
d92ea0a39e | ||
|
|
81bbb4008b | ||
|
|
45ad0698b1 | ||
|
|
bc542a7bb1 | ||
|
|
efb065f558 | ||
|
|
00e63db029 | ||
|
|
f6a2926033 | ||
|
|
5b0f8afa4e | ||
|
|
c5b4421eae | ||
|
|
4d3f04e227 | ||
|
|
75ad29a68d | ||
|
|
62e1e65dda | ||
|
|
02f64314b8 | ||
|
|
12d7f191ee | ||
|
|
37135e1c8e | ||
|
|
85a0c25d0e | ||
|
|
1f081392df | ||
|
|
c02f9b827d | ||
|
|
7e95c9f999 | ||
|
|
4836b025e9 | ||
|
|
9a76838602 | ||
|
|
6c27186ce9 | ||
|
|
74c25c2ca3 | ||
|
|
91ee6ff6c0 | ||
|
|
05ba866bef | ||
|
|
af3aab86dc | ||
|
|
4370defb69 | ||
|
|
a105879c9a | ||
|
|
9383be678c | ||
|
|
7c8a394147 | ||
|
|
ffc8a9dae4 | ||
|
|
bb4a65882d | ||
|
|
5ebbb907e7 | ||
|
|
2ba66d7f91 | ||
|
|
7f8fe9e4f4 | ||
|
|
12292070ce | ||
|
|
8bc2b72ab0 | ||
|
|
208ae6b6d6 | ||
|
|
ba8121a8a7 | ||
|
|
c8da0427f9 | ||
|
|
fed13e8fda | ||
|
|
e3b977c636 | ||
|
|
337912f3d0 | ||
|
|
3b61f88343 | ||
|
|
5f6a6ba1c5 | ||
|
|
bb29844101 | ||
|
|
5600d95684 | ||
|
|
45ba341ccf | ||
|
|
7992526d2d | ||
|
|
9a6cb23659 | ||
|
|
bc132b7757 | ||
|
|
b05d726ad0 | ||
|
|
a7a612aa9b | ||
|
|
32a29c8bc7 | ||
|
|
23a3babf33 | ||
|
|
3d90f0b284 | ||
|
|
1e6448c61f | ||
|
|
a1f709ca12 | ||
|
|
a8ca2fd6e6 | ||
|
|
f835eeecdd | ||
|
|
70f5228d1c | ||
|
|
89e2ea610a | ||
|
|
6b68a983a5 | ||
|
|
4a2878b92e | ||
|
|
4f8165c8e1 | ||
|
|
855d15cec5 | ||
|
|
e1d17f61c4 | ||
|
|
380ae27762 | ||
|
|
4775dcd57a | ||
|
|
807ccc69ce | ||
|
|
aaee64ce02 | ||
|
|
294141e223 | ||
|
|
e3b125f244 | ||
|
|
7fd3297fed | ||
|
|
a2f54f67a3 | ||
|
|
d77724a911 | ||
|
|
1dc05ba196 | ||
|
|
cd7d4d102b | ||
|
|
0ba2450152 | ||
|
|
eee7d67591 | ||
|
|
760dbfa5b9 | ||
|
|
a52384de2e | ||
|
|
0dab8f0c94 | ||
|
|
629984c792 | ||
|
|
707e4c6dde | ||
|
|
efbe409399 | ||
|
|
faf0be6c53 | ||
|
|
1a078993f3 | ||
|
|
e0ac3bb853 | ||
|
|
c6cd13d9cd | ||
|
|
ef88e2e4a9 | ||
|
|
d9bcc39ee6 | ||
|
|
84f4a47df1 | ||
|
|
1408a5c357 | ||
|
|
202b15e8a8 | ||
|
|
110554a180 | ||
|
|
90e3715426 | ||
|
|
644fee2d1f | ||
|
|
c163effe60 | ||
|
|
0a39c746a3 | ||
|
|
2f46b3ff58 | ||
|
|
d35760d3a2 | ||
|
|
19a1101b43 | ||
|
|
8b209eaf27 | ||
|
|
fc6f7d3004 | ||
|
|
1abab9f92d | ||
|
|
c3b50983e3 | ||
|
|
a0857090a0 | ||
|
|
1ce9be3ed3 | ||
|
|
11d461380d | ||
|
|
6c0eb07c0b | ||
|
|
fb9ae3e78c | ||
|
|
52f59fbfb3 | ||
|
|
4a59017269 | ||
|
|
5c6d02de30 | ||
|
|
83b0e78b9e | ||
|
|
ac192e2416 | ||
|
|
52932d63d3 | ||
|
|
6a45e5d32c | ||
|
|
6f09598243 | ||
|
|
467e79d1c0 | ||
|
|
fa66c94ec3 | ||
|
|
2b4ce4b920 | ||
|
|
4e7585b87e | ||
|
|
de13db4627 | ||
|
|
ca8ceb428b | ||
|
|
c18597387a | ||
|
|
962504b788 | ||
|
|
8d1886d045 | ||
|
|
1c5167bb7c | ||
|
|
e248cca4e7 | ||
|
|
89ee97872d | ||
|
|
b22a2368d4 | ||
|
|
9ecfc57e44 | ||
|
|
cd141258c5 | ||
|
|
5dc027a9e2 | ||
|
|
3e55b0e417 | ||
|
|
9ca6b21c61 | ||
|
|
8ec528d4a0 | ||
|
|
961946bd29 | ||
|
|
da5fc860cf | ||
|
|
fdd39c4010 | ||
|
|
900e96a3a6 | ||
|
|
6a9f42f126 | ||
|
|
72ea196359 | ||
|
|
90a8abeed2 | ||
|
|
34fcbfa96f | ||
|
|
0f360cf892 | ||
|
|
22d4196bed | ||
|
|
fb9860d51d | ||
|
|
c8ba9d62aa | ||
|
|
4a66128af5 | ||
|
|
f061410205 | ||
|
|
1250e77771 | ||
|
|
a20334d026 |
33
.classpath
33
.classpath
@@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="bin/main" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="gradle_scope" value="main"/>
|
||||
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="bin/main" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="gradle_scope" value="main"/>
|
||||
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="bin/test" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="gradle_scope" value="test"/>
|
||||
<attribute name="gradle_used_by_scope" value="test"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="bin/test" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="gradle_scope" value="test"/>
|
||||
<attribute name="gradle_used_by_scope" value="test"/>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
|
||||
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/Slime Java Commons"/>
|
||||
<classpathentry kind="output" path="bin/default"/>
|
||||
</classpath>
|
||||
12
.gitattributes
vendored
12
.gitattributes
vendored
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
#
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
#
|
||||
# These are explicitly windows files and should use crlf
|
||||
*.bat text eol=crlf
|
||||
|
||||
|
||||
61
.github/workflows/gradle.yaml
vendored
Normal file
61
.github/workflows/gradle.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# This workflow will build a Java project with Gradle
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: SlimeVR Server
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: "11"
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Check code formatting
|
||||
run: ./gradlew spotlessCheck
|
||||
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew test
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: "11"
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew shadowJar
|
||||
|
||||
- name: Upload the Server JAR as a Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
# Artifact name
|
||||
name: "SlimeVR-Server" # optional, default is artifact
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: build/libs/*
|
||||
48
.github/workflows/gradle.yml
vendored
48
.github/workflows/gradle.yml
vendored
@@ -1,48 +0,0 @@
|
||||
# This workflow will build a Java project with Gradle
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: Build SlimeVR Server with Gradle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Clone Slime Java Commons
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
repository: Eirenliel/slime-java-commons
|
||||
# Relative path under $GITHUB_WORKSPACE to place the repository
|
||||
path: Slime Java Commons
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2.1.0
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Test with Gradle
|
||||
run: ./gradlew clean test
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew clean serverJar
|
||||
|
||||
- name: Upload the Server JAR as a Build Artifact
|
||||
uses: actions/upload-artifact@v2.2.4
|
||||
with:
|
||||
# Artifact name
|
||||
name: "SlimeVR-Server" # optional, default is artifact
|
||||
# A file, directory or wildcard pattern that describes what to upload
|
||||
path: build/libs/*
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@@ -6,7 +6,29 @@ build
|
||||
|
||||
/bin/
|
||||
|
||||
# Ignore .idea
|
||||
.idea
|
||||
|
||||
# Syncthing ignore file
|
||||
.stignore
|
||||
|
||||
MagnetoLib.dll
|
||||
MagnetoLib.dll
|
||||
vrconfig.yml
|
||||
|
||||
# BVH
|
||||
BVH Recordings
|
||||
Recordings
|
||||
|
||||
# Logs
|
||||
*.log.*
|
||||
*.log.lck
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Ignore eclipse stuff
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
|
||||
# VSCode stuff
|
||||
/.vscode/settings.json
|
||||
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "slime-java-commons"]
|
||||
path = slime-java-commons
|
||||
url = https://github.com/Eirenliel/slime-java-commons.git
|
||||
[submodule "solarxr-protocol"]
|
||||
path = solarxr-protocol
|
||||
url = https://github.com/SlimeVR/SolarXR-Protocol.git
|
||||
23
.project
23
.project
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>SlimeVR Server</name>
|
||||
<comment>SlimeVR Server</comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -1,2 +0,0 @@
|
||||
connection.project.dir=
|
||||
eclipse.preferences.version=1
|
||||
@@ -1,2 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding/<project>=UTF-8
|
||||
@@ -1,2 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
line.separator=\r\n
|
||||
@@ -1,416 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled
|
||||
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
|
||||
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
|
||||
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
|
||||
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
|
||||
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.APILeak=warning
|
||||
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
|
||||
org.eclipse.jdt.core.compiler.problem.deadCode=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.deprecation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.discouragedReference=info
|
||||
org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.fallthroughCase=warning
|
||||
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.fieldHiding=warning
|
||||
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
|
||||
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
|
||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=info
|
||||
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
|
||||
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
|
||||
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning
|
||||
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
|
||||
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=error
|
||||
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning
|
||||
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=error
|
||||
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=error
|
||||
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
|
||||
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
|
||||
org.eclipse.jdt.core.compiler.problem.nullReference=error
|
||||
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
|
||||
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
|
||||
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
|
||||
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
|
||||
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning
|
||||
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
|
||||
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
|
||||
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
|
||||
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
|
||||
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
|
||||
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
|
||||
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
|
||||
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
|
||||
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
||||
org.eclipse.jdt.core.compiler.source=1.8
|
||||
org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647
|
||||
org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=48
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_compact_if=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48
|
||||
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_module_statements=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80
|
||||
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=48
|
||||
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0
|
||||
org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_before_package=0
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1
|
||||
org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line
|
||||
org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false
|
||||
org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_block_comments=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_header=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_html=true
|
||||
org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_line_comments=false
|
||||
org.eclipse.jdt.core.formatter.comment.format_source_code=true
|
||||
org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true
|
||||
org.eclipse.jdt.core.formatter.comment.indent_root_tags=true
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert
|
||||
org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.comment.line_length=80
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true
|
||||
org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false
|
||||
org.eclipse.jdt.core.formatter.compact_else_if=true
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation=2
|
||||
org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2
|
||||
org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off
|
||||
org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on
|
||||
org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true
|
||||
org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_empty_lines=true
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true
|
||||
org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true
|
||||
org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false
|
||||
org.eclipse.jdt.core.formatter.indentation.size=4
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert
|
||||
org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert
|
||||
org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert
|
||||
org.eclipse.jdt.core.formatter.join_lines_in_comments=true
|
||||
org.eclipse.jdt.core.formatter.join_wrapped_lines=false
|
||||
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
||||
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false
|
||||
org.eclipse.jdt.core.formatter.lineSplit=200
|
||||
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
||||
org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0
|
||||
org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines
|
||||
org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines
|
||||
org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true
|
||||
org.eclipse.jdt.core.formatter.tabulation.char=tab
|
||||
org.eclipse.jdt.core.formatter.tabulation.size=4
|
||||
org.eclipse.jdt.core.formatter.use_on_off_tags=true
|
||||
org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false
|
||||
org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false
|
||||
org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true
|
||||
org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true
|
||||
org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true
|
||||
org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true
|
||||
org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter
|
||||
@@ -1,3 +0,0 @@
|
||||
eclipse.preferences.version=1
|
||||
formatter_profile=_Essentia
|
||||
formatter_settings_version=13
|
||||
11
.vscode/extensions.json
vendored
Normal file
11
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||
// List of extensions which should be recommended for users of this workspace.
|
||||
"recommendations": [
|
||||
"richardwillis.vscode-spotless-gradle",
|
||||
"gaborv.flatbuffers",
|
||||
],
|
||||
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
75
CONTRIBUTING.md
Normal file
75
CONTRIBUTING.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# Contributing to SlimeVR
|
||||
|
||||
This document describes essential knowledge for contributors to SlimeVR.
|
||||
|
||||
## How to get started
|
||||
|
||||
### Getting the code
|
||||
First, clone the codebase using `git`. If you don't have `git` installed, go install it.
|
||||
|
||||
```bash
|
||||
# Clone repositories
|
||||
git clone --recursive https://github.com/SlimeVR/SlimeVR-Server.git
|
||||
|
||||
# Enter the directory of the codebase
|
||||
cd SlimeVR-Server
|
||||
```
|
||||
|
||||
Now you can open the codebase in your favorite IDE or text editor.
|
||||
|
||||
### Building the code
|
||||
The code is built with `gradle`, a cli tool that manages java projects and their
|
||||
dependencies. You can build the code with `./gradlew build` and run it with
|
||||
`./gradlew run`.
|
||||
|
||||
|
||||
## Code Style
|
||||
Code is autoformatted with [spotless](https://github.com/diffplug/spotless/tree/main/plugin-gradle).
|
||||
Code is checked for autoformatting whenever you build, but you can also run
|
||||
`./gradlew spotlessCheck` if you prefer.
|
||||
|
||||
To autoformat your code from the command line, you can run `./gradlew spotlessApply`.
|
||||
We recommend installing support for spotless in your IDE of choice, and formatting
|
||||
whenever you save a file, to make things easy.
|
||||
|
||||
If you need to prevent autoformatting for a particular region of code, use
|
||||
`// @formatter:off` and `// @formatter:on`
|
||||
|
||||
### Setting up spotless in VSCode
|
||||
* Install the `richardwillis.vscode-spotless-gradle` extension
|
||||
* Add the following to your workspace settings, at `.vscode/settings.json`:
|
||||
```json
|
||||
"spotlessGradle.format.enable": true,
|
||||
"editor.formatOnSave": true,
|
||||
"[java]": {
|
||||
"editor.defaultFormatter": "richardwillis.vscode-spotless-gradle"
|
||||
}
|
||||
```
|
||||
|
||||
### Setting up spotless for IntelliJ
|
||||
* Install https://plugins.jetbrains.com/plugin/18321-spotless-gradle.
|
||||
* Add a keyboard shortcut for `Code` > `Reformat Code with Spotless`
|
||||
* They are working on support to do this on save without a keybind
|
||||
[here](https://github.com/ragurney/spotless-intellij-gradle/issues/8)
|
||||
|
||||
### Setting up Eclipse autoformatting
|
||||
Import the formatting settings defined in `spotless.xml`, like this:
|
||||
* Go to `File > Properties`, then `Java Code Style > Formatter`
|
||||
* Check `Enable project specific settings`
|
||||
* Click `Import`, then open `spotless.xml`, then `Apply`
|
||||
* Go to `Java Editor > Save Actions`
|
||||
* Select `Enable project specific settings`, `Perform the selected actions on save`,
|
||||
`Format source code`, `Format all lines`
|
||||
|
||||
Eclipse will only do a subset of the checks in `spotless`, so you may still want to do
|
||||
`./gradlew spotlessApply` if you ever see an error from spotless.
|
||||
|
||||
## Code Licensing
|
||||
SlimeVR uses an MIT license, and some parts of the project use a dual MIT/Apache 2.0
|
||||
license. Be sure that any code that you reference, or dependencies you add, are
|
||||
compatible with these licenses. `GPL-v3` for example is not compatible because it
|
||||
requires any and all code that depends on it to *also* be licensed under `GPL-v3`.
|
||||
|
||||
## Discord
|
||||
We use discord *a lot* to coordinate and discuss development. Come join us at
|
||||
https://discord.gg/SlimeVR!
|
||||
42
LICENSE
42
LICENSE
@@ -1,21 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Eiren Rain
|
||||
|
||||
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.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Eiren Rain, SlimeVR
|
||||
|
||||
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.
|
||||
|
||||
22
README.md
22
README.md
@@ -5,7 +5,7 @@ Server orchestrates communication between multiple sensors and integrations, lik
|
||||
|
||||
Sensors implementations:
|
||||
* [SlimeVR Tracker for ESP](https://github.com/SlimeVR/SlimeVR-Tracker-ESP) - ESP microcontrollers and multiple IMUs are supported
|
||||
* [owoTrack Mobile App](https://github.com/abb128/owoTrackVRSyncMobile) - use phone as a tracker (limited functionality and copmatibility)
|
||||
* [owoTrack Mobile App](https://github.com/abb128/owoTrackVRSyncMobile) - use phone as a tracker (limited functionality and compatibility)
|
||||
|
||||
Integrations:
|
||||
* Use [SlimeVR OpenVR Driver](https://github.com/SlimeVR/SlimeVR-OpenVR-Driver) as a driver for SteamVR
|
||||
@@ -13,4 +13,22 @@ Integrations:
|
||||
|
||||
## How to use
|
||||
|
||||
Latest instructions are currently [here](https://gist.github.com/Eirenliel/8c0eefcdbda1076d5c2e1bf634831d20). Will be updated and republished as time goes on.
|
||||
It's recommended to download installer from here: https://github.com/SlimeVR/SlimeVR-Installer/releases/latest/download/slimevr_web_installer.exe
|
||||
|
||||
Latest instructions are [on our site](https://docs.slimevr.dev/server-setup/slimevr-setup.html).
|
||||
|
||||
## License Clarification
|
||||
|
||||
**SlimeVR software** (including server, firmware, drivers, installator, documents, and others - see licence for each case specifically) **is distributed under the [MIT License](https://github.com/SlimeVR/SlimeVR-Server/blob/main/LICENSE) and is copyright of Eiren Rain and SlimeVR.** The MIT Licence is a permissive license giving you rights to modify and distribute the software with little strings attached.
|
||||
|
||||
**However, the MIT License has some limits, and if you wish to distribute software based on SlimeVR, you need to be aware of them:**
|
||||
|
||||
* When distributing any software that uses or is based on SlimeVR, you have to provide to the end-user the original, unmodified `LICENSE` file from SlimeVR. This file is located [here](https://github.com/SlimeVR/SlimeVR-Server/blob/main/LICENSE). This includes the `Copyright (c) 2021 Eiren Rain, SlimeVR` part of the license. It is not sufficient to use a generic MIT License, it must be the original license file.
|
||||
* This applies even if you distribute software without the source code. In this case, one way to provide it to the end-user is to have a menu in your application that lists all the open source licenses used, including SlimeVR's.
|
||||
|
||||
Please refer to the [LICENSE](https://github.com/SlimeVR/SlimeVR-Server/blob/main/LICENSE) file if you are at any point uncertain what the exact the requirements are.
|
||||
|
||||
## Contributions
|
||||
By contributing to this project you are placing all your code under MIT or less restricting licenses, and you certify that the code you have used is compatible with those licenses or is authored by you. If you're doing so on your work time, you certify that your employer is okay with this.
|
||||
|
||||
For a how-to on contributing, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
116
build.gradle
116
build.gradle
@@ -7,43 +7,101 @@
|
||||
*/
|
||||
|
||||
plugins {
|
||||
// Apply the java-library plugin to add support for Java Library
|
||||
id 'java-library'
|
||||
id 'application'
|
||||
id "com.github.johnrengelman.shadow" version "7.1.2"
|
||||
id "com.diffplug.spotless" version "6.5.1"
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Use jcenter for resolving dependencies.
|
||||
// You can declare any Maven/Ivy/file repository here.
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
sourceCompatibility = 1.11
|
||||
targetCompatibility = 1.11
|
||||
|
||||
// Set compiler to use UTF-8
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
compileTestJava.options.encoding = 'UTF-8'
|
||||
javadoc.options.encoding = 'UTF-8'
|
||||
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = 'UTF-8'
|
||||
// if (JavaVersion.current().isJava9Compatible()) {
|
||||
// options.release = 8
|
||||
// }
|
||||
}
|
||||
tasks.withType(Test) {
|
||||
systemProperty('file.encoding', 'UTF-8')
|
||||
}
|
||||
tasks.withType(Javadoc) {
|
||||
options.encoding = 'UTF-8'
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
// Use jcenter for resolving dependencies.
|
||||
// You can declare any Maven/Ivy/file repository here.
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':Slime Java Commons')
|
||||
implementation project(':slime-java-commons')
|
||||
implementation project(":solarxr-protocol")
|
||||
|
||||
// This dependency is exported to consumers, that is to say found on their compile classpath.
|
||||
api 'org.apache.commons:commons-math3:3.6.1'
|
||||
api 'org.yaml:snakeyaml:1.25'
|
||||
api 'net.java.dev.jna:jna:5.6.0'
|
||||
api 'net.java.dev.jna:jna-platform:5.6.0'
|
||||
api 'com.illposed.osc:javaosc-core:0.8'
|
||||
implementation group: 'com.google.flatbuffers', name: 'flatbuffers-java', version: '2.0.3'
|
||||
|
||||
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
|
||||
implementation 'com.google.guava:guava:28.2-jre'
|
||||
|
||||
implementation group: 'commons-cli', name: 'commons-cli', version: '1.3.1'
|
||||
|
||||
// Use JUnit test framework
|
||||
testImplementation 'junit:junit:4.12'
|
||||
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
|
||||
implementation 'org.apache.commons:commons-math3:3.6.1'
|
||||
implementation 'org.apache.commons:commons-lang3:3.12.0'
|
||||
implementation 'org.yaml:snakeyaml:1.30'
|
||||
implementation 'net.java.dev.jna:jna:5.10.0'
|
||||
implementation 'net.java.dev.jna:jna-platform:5.10.0'
|
||||
implementation 'com.illposed.osc:javaosc-core:0.8'
|
||||
implementation 'com.fazecast:jSerialComm:2.9.0'
|
||||
implementation 'com.google.protobuf:protobuf-java:3.19.4'
|
||||
implementation "org.java-websocket:Java-WebSocket:1.5.2"
|
||||
implementation 'com.melloware:jintellitype:1.4.0'
|
||||
|
||||
// Use JUnit test framework
|
||||
testImplementation platform('org.junit:junit-bom:5.8.2')
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||
testImplementation 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
subprojects.each { subproject -> evaluationDependsOn(subproject.path) }
|
||||
task serverJar (type: Jar, dependsOn: subprojects.tasks['build']) {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'io.eiren.vr.Main'
|
||||
}
|
||||
|
||||
from {
|
||||
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
with jar
|
||||
shadowJar {
|
||||
archiveBaseName.set('slimevr')
|
||||
archiveClassifier.set('')
|
||||
archiveVersion.set('')
|
||||
}
|
||||
application {
|
||||
getMainClass().set('dev.slimevr.Main')
|
||||
}
|
||||
|
||||
spotless {
|
||||
// optional: limit format enforcement to just the files changed by this feature branch
|
||||
// ratchetFrom 'origin/main'
|
||||
|
||||
format 'misc', {
|
||||
// define the files to apply `misc` to
|
||||
target '*.gradle', '*.md', '.gitignore'
|
||||
|
||||
// define the steps to apply to those files
|
||||
trimTrailingWhitespace()
|
||||
endWithNewline()
|
||||
indentWithTabs()
|
||||
}
|
||||
// format 'yaml', {
|
||||
// target '*.yml', '*.yaml',
|
||||
|
||||
// trimTrailingWhitespace()
|
||||
// endWithNewline()
|
||||
// indentWithSpaces(2) // YAML cannot contain tabs: https://yaml.org/faq.html
|
||||
// }
|
||||
java {
|
||||
removeUnusedImports()
|
||||
// Use eclipse JDT formatter
|
||||
eclipse().configFile("spotless.xml")
|
||||
}
|
||||
}
|
||||
|
||||
6
gradle.properties
Normal file
6
gradle.properties
Normal file
@@ -0,0 +1,6 @@
|
||||
# Fixes bug with spotless. See https://github.com/diffplug/spotless/issues/834#issuecomment-819118761
|
||||
org.gradle.jvmargs=--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED \
|
||||
--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
269
gradlew
vendored
Normal file → Executable file
269
gradlew
vendored
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,78 +17,113 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -105,79 +140,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -54,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -64,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
1
protobuf_update.bat
Normal file
1
protobuf_update.bat
Normal file
@@ -0,0 +1 @@
|
||||
protoc --proto_path=../SlimeVR-OpenVR-Driver/src/bridge --java_out=./src/main/java ProtobufMessages.proto
|
||||
21
resources/LICENSE.txt
Normal file
21
resources/LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Eiren Rain, SlimeVR
|
||||
|
||||
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.
|
||||
17
resources/firewall.bat
Normal file
17
resources/firewall.bat
Normal file
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
echo Installing firewall rules...
|
||||
|
||||
rem Discovery default port
|
||||
netsh advfirewall firewall add rule name="SlimeVR UDP 35903 incoming" dir=in action=allow protocol=UDP localport=35903
|
||||
netsh advfirewall firewall add rule name="SlimeVR UDP 35903 outgoing" dir=out action=allow protocol=UDP localport=35903
|
||||
|
||||
rem Rotational data default port
|
||||
netsh advfirewall firewall add rule name="SlimeVR UDP 6969 incoming" dir=in action=allow protocol=UDP localport=6969
|
||||
netsh advfirewall firewall add rule name="SlimeVR UDP 6969 outgoing" dir=out action=allow protocol=UDP localport=6969
|
||||
|
||||
rem WebSocket server default port
|
||||
netsh advfirewall firewall add rule name="SlimeVR TCP 21110 incoming" dir=in action=allow protocol=TCP localport=21110
|
||||
netsh advfirewall firewall add rule name="SlimeVR TCP 21110 outgoing" dir=out action=allow protocol=TCP localport=21110
|
||||
|
||||
echo Done!
|
||||
pause
|
||||
17
resources/firewall_uninstall.bat
Normal file
17
resources/firewall_uninstall.bat
Normal file
@@ -0,0 +1,17 @@
|
||||
@echo off
|
||||
echo Uninstalling firewall rules...
|
||||
|
||||
rem Discovery defauly port
|
||||
netsh advfirewall firewall delete rule name="SlimeVR UDP 35903 incoming"
|
||||
netsh advfirewall firewall delete rule name="SlimeVR UDP 35903 outgoing"
|
||||
|
||||
rem Rotational data default port
|
||||
netsh advfirewall firewall delete rule name="SlimeVR UDP 6969 incoming"
|
||||
netsh advfirewall firewall delete rule name="SlimeVR UDP 6969 outgoing"
|
||||
|
||||
rem WebSocket server default port
|
||||
netsh advfirewall firewall delete rule name="SlimeVR TCP 21110 incoming"
|
||||
netsh advfirewall firewall delete rule name="SlimeVR TCP 21110 outgoing"
|
||||
|
||||
echo Done!
|
||||
pause
|
||||
19
resources/run.bat
Normal file
19
resources/run.bat
Normal file
@@ -0,0 +1,19 @@
|
||||
@echo off
|
||||
setlocal enableextensions
|
||||
cd /d "%~dp0"
|
||||
|
||||
where java >nul 2>&1
|
||||
if %errorlevel% EQU 0 (
|
||||
java -Xmx512M -jar slimevr.jar
|
||||
) else (
|
||||
echo Java was not found in your system.
|
||||
echo.
|
||||
echo Either use SlimeVR Installer to install the server by following this link:
|
||||
echo https://github.com/SlimeVR/SlimeVR-Installer/releases/latest/download/slimevr_web_installer.exe
|
||||
echo.
|
||||
echo Or download Java 11 by following this link:
|
||||
echo https://adoptium.net/releases.html?variant=openjdk11^&jvmVariant=hotspot
|
||||
)
|
||||
if %errorlevel% NEQ 0 (
|
||||
pause
|
||||
)
|
||||
@@ -8,4 +8,8 @@
|
||||
*/
|
||||
|
||||
rootProject.name = 'SlimeVR Server'
|
||||
include('Slime Java Commons')
|
||||
include ':slime-java-commons'
|
||||
|
||||
|
||||
include ':solarxr-protocol'
|
||||
project(':solarxr-protocol').projectDir = new File('solarxr-protocol/protocol/java')
|
||||
|
||||
1
slime-java-commons
Submodule
1
slime-java-commons
Submodule
Submodule slime-java-commons added at 23a977aefe
1
solarxr-protocol
Submodule
1
solarxr-protocol
Submodule
Submodule solarxr-protocol added at ceaae26ea2
400
spotless.xml
Normal file
400
spotless.xml
Normal file
@@ -0,0 +1,400 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<profiles version="22">
|
||||
<profile kind="CodeFormatterProfile" name="Spotless" version="22">
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="8"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_with_spaces" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.enabling_tag" value="@formatter:on"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_record_components" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_logical_operator" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line" value="one_line_if_empty"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line" value="one_line_if_empty"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_shift_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_loops" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.text_block_indentation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_module_statements" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line" value="one_line_if_empty"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_permitted_types" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_header" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_annotations" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines" value="2147483647"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_not_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_record_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_tag_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_constructor" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_string_concatenation" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_shift_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_additive_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.disabling_tag" value="@formatter:off"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="80"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_method_body_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line" value="one_line_if_empty"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_additive_operator" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_constructor" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_relational_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_relational_operator" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="2"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_record_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_additive_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_shift_operator" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_code_block_on_one_line" value="one_line_if_empty"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_record_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line" value="one_line_if_empty"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method" value="49"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assertion_message" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" value="1"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_logical_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration" value="16"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_relational_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration" value="48"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_logical_operator" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration" value="separate_lines_if_wrapped"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_permitted_types" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line" value="one_line_never"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" value="insert"/>
|
||||
<setting id="org.eclipse.jdt.core.javaFormatter" value="org.eclipse.jdt.core.defaultJavaFormatter"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" value="false"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block" value="0"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.wrap_before_string_concatenation" value="true"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="100"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation" value="do not insert"/>
|
||||
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" value="insert"/>
|
||||
</profile>
|
||||
</profiles>
|
||||
118
src/main/java/dev/slimevr/Main.java
Normal file
118
src/main/java/dev/slimevr/Main.java
Normal file
@@ -0,0 +1,118 @@
|
||||
package dev.slimevr;
|
||||
|
||||
import dev.slimevr.gui.Keybinding;
|
||||
import dev.slimevr.gui.VRServerGUI;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import org.apache.commons.cli.*;
|
||||
import org.apache.commons.lang3.JavaVersion;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
|
||||
public class Main {
|
||||
|
||||
public static String VERSION = "0.1.6";
|
||||
|
||||
public static VRServer vrServer;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("awt.useSystemAAFontSettings", "on");
|
||||
System.setProperty("swing.aatext", "true");
|
||||
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
HelpFormatter formatter = new HelpFormatter();
|
||||
CommandLine cmd = null;
|
||||
|
||||
Options options = new Options();
|
||||
|
||||
Option noGui = new Option(
|
||||
"g",
|
||||
"no-gui",
|
||||
false,
|
||||
"disable swing gui (allow for other gui to be used)"
|
||||
);
|
||||
Option help = new Option("h", "help", false, "Show help");
|
||||
|
||||
options.addOption(noGui);
|
||||
options.addOption(help);
|
||||
try {
|
||||
cmd = parser.parse(options, args);
|
||||
} catch (ParseException e) {
|
||||
System.out.println(e.getMessage());
|
||||
formatter.printHelp("slimevr.jar", options);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
if (cmd.hasOption("help")) {
|
||||
formatter.printHelp("slimevr.jar", options);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
File dir = new File("").getAbsoluteFile();
|
||||
try {
|
||||
LogManager.initialize(new File(dir, "logs/"), dir);
|
||||
} catch (Exception e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
if (!SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_11)) {
|
||||
LogManager.severe("SlimeVR start-up error! A minimum of Java 11 is required.");
|
||||
JOptionPane
|
||||
.showMessageDialog(
|
||||
null,
|
||||
"SlimeVR start-up error! A minimum of Java 11 is required.",
|
||||
"SlimeVR: Java Runtime Mismatch",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new ServerSocket(6969).close();
|
||||
new ServerSocket(35903).close();
|
||||
new ServerSocket(21110).close();
|
||||
} catch (IOException e) {
|
||||
LogManager
|
||||
.severe(
|
||||
"SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running."
|
||||
);
|
||||
JOptionPane
|
||||
.showMessageDialog(
|
||||
null,
|
||||
"SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running.",
|
||||
"SlimeVR: Ports are busy",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
vrServer = new VRServer();
|
||||
vrServer.start();
|
||||
new Keybinding(vrServer);
|
||||
if (!cmd.hasOption("no-gui"))
|
||||
new VRServerGUI(vrServer);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
Thread.sleep(2000L);
|
||||
} catch (InterruptedException e2) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
System.exit(1); // Exit in case error happened on init and window
|
||||
// not appeared, but some thread
|
||||
// started
|
||||
} finally {
|
||||
try {
|
||||
Thread.sleep(2000L);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/main/java/dev/slimevr/NetworkProtocol.java
Normal file
8
src/main/java/dev/slimevr/NetworkProtocol.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package dev.slimevr;
|
||||
|
||||
public enum NetworkProtocol {
|
||||
OWO_LEGACY,
|
||||
SLIMEVR_RAW,
|
||||
SLIMEVR_FLATBUFFER,
|
||||
SLIMEVR_WEBSOCKET
|
||||
}
|
||||
371
src/main/java/dev/slimevr/VRServer.java
Normal file
371
src/main/java/dev/slimevr/VRServer.java
Normal file
@@ -0,0 +1,371 @@
|
||||
package dev.slimevr;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneHandler;
|
||||
import dev.slimevr.bridge.Bridge;
|
||||
import dev.slimevr.bridge.VMCBridge;
|
||||
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
|
||||
import dev.slimevr.poserecorder.BVHRecorder;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.serial.SerialHandler;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.HumanPoseProcessor;
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
import dev.slimevr.vr.trackers.*;
|
||||
import dev.slimevr.vr.trackers.udp.TrackersUDPServer;
|
||||
import dev.slimevr.websocketapi.WebSocketVRBridge;
|
||||
import io.eiren.util.OperatingSystem;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.ann.ThreadSecure;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.yaml.YamlException;
|
||||
import io.eiren.yaml.YamlFile;
|
||||
import io.eiren.yaml.YamlNode;
|
||||
import solarxr_protocol.datatypes.TrackerIdT;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
public class VRServer extends Thread {
|
||||
|
||||
public final HumanPoseProcessor humanPoseProcessor;
|
||||
public final YamlFile config = new YamlFile();
|
||||
public final HMDTracker hmdTracker;
|
||||
private final List<Tracker> trackers = new FastList<>();
|
||||
private final TrackersUDPServer trackersServer;
|
||||
private final List<Bridge> bridges = new FastList<>();
|
||||
private final Queue<Runnable> tasks = new LinkedBlockingQueue<>();
|
||||
private final Map<String, TrackerConfig> configuration = new HashMap<>();
|
||||
private final List<Consumer<Tracker>> newTrackersConsumers = new FastList<>();
|
||||
private final List<Runnable> onTick = new FastList<>();
|
||||
private final List<? extends ShareableTracker> shareTrackers;
|
||||
private final BVHRecorder bvhRecorder;
|
||||
private final SerialHandler serialHandler;
|
||||
private final AutoBoneHandler autoBoneHandler;
|
||||
private final ProtocolAPI protocolAPI;
|
||||
private final String configPath;
|
||||
|
||||
public VRServer() {
|
||||
this("vrconfig.yml");
|
||||
}
|
||||
|
||||
public VRServer(String configPath) {
|
||||
super("VRServer");
|
||||
this.configPath = configPath;
|
||||
loadConfig();
|
||||
|
||||
serialHandler = new SerialHandler();
|
||||
autoBoneHandler = new AutoBoneHandler(this);
|
||||
protocolAPI = new ProtocolAPI(this);
|
||||
|
||||
hmdTracker = new HMDTracker("HMD");
|
||||
hmdTracker.position.set(0, 1.8f, 0); // Set starting position for easier
|
||||
// debugging
|
||||
// TODO Multiple processors
|
||||
humanPoseProcessor = new HumanPoseProcessor(this, hmdTracker);
|
||||
shareTrackers = humanPoseProcessor.getComputedTrackers();
|
||||
|
||||
// Start server for SlimeVR trackers
|
||||
trackersServer = new TrackersUDPServer(6969, "Sensors UDP server", this::registerTracker);
|
||||
|
||||
// OpenVR bridge currently only supports Windows
|
||||
if (OperatingSystem.getCurrentPlatform() == OperatingSystem.WINDOWS) {
|
||||
|
||||
// Create named pipe bridge for SteamVR driver
|
||||
WindowsNamedPipeBridge driverBridge = new WindowsNamedPipeBridge(
|
||||
hmdTracker,
|
||||
"steamvr",
|
||||
"SteamVR Driver Bridge",
|
||||
"\\\\.\\pipe\\SlimeVRDriver",
|
||||
shareTrackers
|
||||
);
|
||||
tasks.add(() -> driverBridge.startBridge());
|
||||
bridges.add(driverBridge);
|
||||
|
||||
// Create named pipe bridge for SteamVR input
|
||||
// TODO: how do we want to handle HMD input from the feeder app?
|
||||
WindowsNamedPipeBridge feederBridge = new WindowsNamedPipeBridge(
|
||||
null,
|
||||
"steamvr_feeder",
|
||||
"SteamVR Feeder Bridge",
|
||||
"\\\\.\\pipe\\SlimeVRInput",
|
||||
new FastList<ShareableTracker>()
|
||||
);
|
||||
tasks.add(() -> feederBridge.startBridge());
|
||||
bridges.add(feederBridge);
|
||||
}
|
||||
|
||||
// Create WebSocket server
|
||||
WebSocketVRBridge wsBridge = new WebSocketVRBridge(hmdTracker, shareTrackers, this);
|
||||
tasks.add(() -> wsBridge.startBridge());
|
||||
bridges.add(wsBridge);
|
||||
|
||||
// Create VMCBridge
|
||||
try {
|
||||
VMCBridge vmcBridge = new VMCBridge(39539, 39540, InetAddress.getLocalHost());
|
||||
tasks.add(() -> vmcBridge.startBridge());
|
||||
bridges.add(vmcBridge);
|
||||
} catch (UnknownHostException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
bvhRecorder = new BVHRecorder(this);
|
||||
|
||||
registerTracker(hmdTracker);
|
||||
for (Tracker tracker : shareTrackers) {
|
||||
registerTracker(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasBridge(Class<? extends Bridge> bridgeClass) {
|
||||
for (Bridge bridge : bridges) {
|
||||
if (bridgeClass.isAssignableFrom(bridge.getClass())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public <E extends Bridge> E getVRBridge(Class<E> bridgeClass) {
|
||||
for (Bridge bridge : bridges) {
|
||||
if (bridgeClass.isAssignableFrom(bridge.getClass())) {
|
||||
return bridgeClass.cast(bridge);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public TrackerConfig getTrackerConfig(Tracker tracker) {
|
||||
synchronized (configuration) {
|
||||
TrackerConfig config = configuration.get(tracker.getName());
|
||||
if (config == null) {
|
||||
config = new TrackerConfig(tracker);
|
||||
configuration.put(tracker.getName(), config);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
try {
|
||||
config.load(new FileInputStream(new File(this.configPath)));
|
||||
} catch (FileNotFoundException e) {
|
||||
// Config file didn't exist, is not an error
|
||||
} catch (YamlException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
List<YamlNode> trackersConfig = config.getNodeList("trackers", null);
|
||||
for (YamlNode node : trackersConfig) {
|
||||
TrackerConfig cfg = new TrackerConfig(node);
|
||||
synchronized (configuration) {
|
||||
configuration.put(cfg.trackerName, cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addOnTick(Runnable runnable) {
|
||||
this.onTick.add(runnable);
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void addNewTrackerConsumer(Consumer<Tracker> consumer) {
|
||||
queueTask(() -> {
|
||||
newTrackersConsumers.add(consumer);
|
||||
for (Tracker tracker : trackers) {
|
||||
consumer.accept(tracker);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void trackerUpdated(Tracker tracker) {
|
||||
queueTask(() -> {
|
||||
humanPoseProcessor.trackerUpdated(tracker);
|
||||
TrackerConfig tc = getTrackerConfig(tracker);
|
||||
tracker.saveConfig(tc);
|
||||
saveConfig();
|
||||
});
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void addSkeletonUpdatedCallback(Consumer<Skeleton> consumer) {
|
||||
queueTask(() -> {
|
||||
humanPoseProcessor.addSkeletonUpdatedCallback(consumer);
|
||||
});
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public synchronized void saveConfig() {
|
||||
List<YamlNode> nodes = config.getNodeList("trackers", null);
|
||||
List<Map<String, Object>> trackersConfig = new FastList<>(nodes.size());
|
||||
for (YamlNode node : nodes) {
|
||||
trackersConfig.add(node.root);
|
||||
}
|
||||
config.setProperty("trackers", trackersConfig);
|
||||
synchronized (configuration) {
|
||||
Iterator<TrackerConfig> iterator = configuration.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
TrackerConfig tc = iterator.next();
|
||||
Map<String, Object> cfg = null;
|
||||
for (Map<String, Object> c : trackersConfig) {
|
||||
if (tc.trackerName.equals(c.get("name"))) {
|
||||
cfg = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cfg == null) {
|
||||
cfg = new HashMap<>();
|
||||
trackersConfig.add(cfg);
|
||||
}
|
||||
tc.saveConfig(new YamlNode(cfg));
|
||||
}
|
||||
}
|
||||
File cfgFile = new File(this.configPath);
|
||||
try {
|
||||
config.save(new FileOutputStream(cfgFile));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void run() {
|
||||
trackersServer.start();
|
||||
while (true) {
|
||||
// final long start = System.currentTimeMillis();
|
||||
do {
|
||||
Runnable task = tasks.poll();
|
||||
if (task == null)
|
||||
break;
|
||||
task.run();
|
||||
} while (true);
|
||||
for (Runnable task : onTick) {
|
||||
task.run();
|
||||
}
|
||||
for (Bridge bridge : bridges) {
|
||||
bridge.dataRead();
|
||||
}
|
||||
for (Tracker tracker : trackers) {
|
||||
tracker.tick();
|
||||
}
|
||||
humanPoseProcessor.update();
|
||||
for (Bridge bridge : bridges) {
|
||||
bridge.dataWrite();
|
||||
}
|
||||
// final long time = System.currentTimeMillis() - start;
|
||||
try {
|
||||
Thread.sleep(1); // 1000Hz
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void queueTask(Runnable r) {
|
||||
tasks.add(r);
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
private void trackerAdded(Tracker tracker) {
|
||||
humanPoseProcessor.trackerAdded(tracker);
|
||||
}
|
||||
|
||||
@ThreadSecure
|
||||
public void registerTracker(Tracker tracker) {
|
||||
TrackerConfig config = getTrackerConfig(tracker);
|
||||
tracker.loadConfig(config);
|
||||
queueTask(() -> {
|
||||
trackers.add(tracker);
|
||||
trackerAdded(tracker);
|
||||
for (Consumer<Tracker> tc : newTrackersConsumers) {
|
||||
tc.accept(tracker);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updateTrackersFilters(TrackerFilters filter, float amount, int ticks) {
|
||||
config.setProperty("filters.type", filter.name());
|
||||
config.setProperty("filters.amount", amount);
|
||||
config.setProperty("filters.tickCount", ticks);
|
||||
saveConfig();
|
||||
|
||||
IMUTracker imu;
|
||||
for (Tracker t : this.getAllTrackers()) {
|
||||
Tracker tracker = t.get();
|
||||
if (tracker instanceof IMUTracker) {
|
||||
imu = (IMUTracker) tracker;
|
||||
imu.setFilter(filter.name(), amount, ticks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void resetTrackers() {
|
||||
queueTask(() -> {
|
||||
humanPoseProcessor.resetTrackers();
|
||||
});
|
||||
}
|
||||
|
||||
public void resetTrackersYaw() {
|
||||
queueTask(() -> {
|
||||
humanPoseProcessor.resetTrackersYaw();
|
||||
});
|
||||
}
|
||||
|
||||
public int getTrackersCount() {
|
||||
return trackers.size();
|
||||
}
|
||||
|
||||
public List<Tracker> getAllTrackers() {
|
||||
return new FastList<>(trackers);
|
||||
}
|
||||
|
||||
public Tracker getTrackerById(TrackerIdT id) {
|
||||
for (Tracker tracker : trackers) {
|
||||
if (tracker.getTrackerNum() != id.getTrackerNum()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle synthetic devices
|
||||
if (id.getDeviceId() == null && tracker.getDevice() == null) {
|
||||
return tracker;
|
||||
}
|
||||
|
||||
if (
|
||||
tracker.getDevice() != null
|
||||
&& id.getDeviceId() != null
|
||||
&& id.getDeviceId().getId() == tracker.getDevice().getId()
|
||||
) {
|
||||
// This is a physical tracker, and both device id and the
|
||||
// tracker num match
|
||||
return tracker;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public BVHRecorder getBvhRecorder() {
|
||||
return this.bvhRecorder;
|
||||
}
|
||||
|
||||
public SerialHandler getSerialHandler() {
|
||||
return this.serialHandler;
|
||||
}
|
||||
|
||||
public AutoBoneHandler getAutoBoneHandler() {
|
||||
return this.autoBoneHandler;
|
||||
}
|
||||
|
||||
public ProtocolAPI getProtocolAPI() {
|
||||
return protocolAPI;
|
||||
}
|
||||
|
||||
public TrackersUDPServer getTrackersServer() {
|
||||
return trackersServer;
|
||||
}
|
||||
}
|
||||
928
src/main/java/dev/slimevr/autobone/AutoBone.java
Normal file
928
src/main/java/dev/slimevr/autobone/AutoBone.java
Normal file
@@ -0,0 +1,928 @@
|
||||
package dev.slimevr.autobone;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.autobone.errors.AutoBoneException;
|
||||
import dev.slimevr.autobone.errors.BodyProportionError;
|
||||
import dev.slimevr.autobone.errors.FootHeightOffsetError;
|
||||
import dev.slimevr.autobone.errors.HeightError;
|
||||
import dev.slimevr.autobone.errors.OffsetSlideError;
|
||||
import dev.slimevr.autobone.errors.PositionError;
|
||||
import dev.slimevr.autobone.errors.PositionOffsetError;
|
||||
import dev.slimevr.autobone.errors.SlideError;
|
||||
import dev.slimevr.poserecorder.*;
|
||||
import dev.slimevr.vr.processor.HumanPoseProcessor;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.processor.skeleton.BoneType;
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfig;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
|
||||
public class AutoBone {
|
||||
|
||||
private static final File saveDir = new File("Recordings");
|
||||
private static final File loadDir = new File("LoadRecordings");
|
||||
// This is filled by reloadConfigValues()
|
||||
public final EnumMap<BoneType, Float> offsets = new EnumMap<BoneType, Float>(
|
||||
BoneType.class
|
||||
);
|
||||
|
||||
public final FastList<BoneType> adjustOffsets = new FastList<BoneType>(
|
||||
new BoneType[] {
|
||||
BoneType.HEAD,
|
||||
BoneType.NECK,
|
||||
BoneType.CHEST,
|
||||
BoneType.WAIST,
|
||||
BoneType.HIP,
|
||||
|
||||
// This one doesn't seem to work very well and is generally going to
|
||||
// be similar between users
|
||||
// BoneType.LEFT_HIP,
|
||||
|
||||
BoneType.LEFT_UPPER_LEG,
|
||||
BoneType.LEFT_LOWER_LEG,
|
||||
}
|
||||
);
|
||||
|
||||
public final FastList<BoneType> heightOffsets = new FastList<BoneType>(
|
||||
new BoneType[] {
|
||||
BoneType.NECK,
|
||||
BoneType.CHEST,
|
||||
BoneType.WAIST,
|
||||
BoneType.HIP,
|
||||
|
||||
BoneType.LEFT_UPPER_LEG,
|
||||
BoneType.RIGHT_UPPER_LEG,
|
||||
BoneType.LEFT_LOWER_LEG,
|
||||
BoneType.RIGHT_LOWER_LEG,
|
||||
}
|
||||
);
|
||||
|
||||
public final FastList<SkeletonConfigValue> legacyHeightConfigs = new FastList<SkeletonConfigValue>(
|
||||
new SkeletonConfigValue[] {
|
||||
SkeletonConfigValue.NECK,
|
||||
SkeletonConfigValue.TORSO,
|
||||
|
||||
SkeletonConfigValue.LEGS_LENGTH,
|
||||
}
|
||||
);
|
||||
|
||||
public final EnumMap<SkeletonConfigValue, Float> legacyConfigs = new EnumMap<SkeletonConfigValue, Float>(
|
||||
SkeletonConfigValue.class
|
||||
);
|
||||
|
||||
protected final VRServer server;
|
||||
public int cursorIncrement = 2;
|
||||
public int minDataDistance = 1;
|
||||
public int maxDataDistance = 1;
|
||||
public int numEpochs = 100;
|
||||
public float initialAdjustRate = 10f;
|
||||
public float adjustRateMultiplier = 0.995f;
|
||||
|
||||
// #region Error functions
|
||||
public SlideError slideError = new SlideError();
|
||||
public float slideErrorFactor = 0.0f;
|
||||
|
||||
public OffsetSlideError offsetSlideError = new OffsetSlideError();
|
||||
public float offsetSlideErrorFactor = 1.0f;
|
||||
|
||||
public FootHeightOffsetError footHeightOffsetError = new FootHeightOffsetError();
|
||||
public float footHeightOffsetErrorFactor = 0.0f;
|
||||
|
||||
public BodyProportionError bodyProportionError = new BodyProportionError();
|
||||
public float bodyProportionErrorFactor = 0.2f;
|
||||
|
||||
public HeightError heightError = new HeightError();
|
||||
public float heightErrorFactor = 0.0f;
|
||||
|
||||
public PositionError positionError = new PositionError();
|
||||
public float positionErrorFactor = 0.0f;
|
||||
|
||||
public PositionOffsetError positionOffsetError = new PositionOffsetError();
|
||||
public float positionOffsetErrorFactor = 0.0f;
|
||||
// #endregion
|
||||
|
||||
public boolean randomizeFrameOrder = true;
|
||||
public boolean scaleEachStep = true;
|
||||
|
||||
public boolean calcInitError = false;
|
||||
public float targetHeight = -1;
|
||||
|
||||
// TODO hip tracker stuff... Hip tracker should be around 3 to 5
|
||||
// centimeters.
|
||||
// Human average is probably 1.1235 (SD 0.07)
|
||||
public float legBodyRatio = 1.1235f;
|
||||
// SD of 0.07, capture 68% within range
|
||||
public float legBodyRatioRange = 0.07f;
|
||||
// kneeLegRatio seems to be around 0.54 to 0.6 after asking a few people in
|
||||
// the
|
||||
// SlimeVR discord.
|
||||
public float kneeLegRatio = 0.55f;
|
||||
// kneeLegRatio seems to be around 0.55 to 0.64 after asking a few people in
|
||||
// the
|
||||
// SlimeVR discord. TODO : Chest should be a bit shorter (0.54?) if user has
|
||||
// an
|
||||
// additional hip tracker.
|
||||
public float chestTorsoRatio = 0.57f;
|
||||
|
||||
private final Random rand = new Random();
|
||||
|
||||
public AutoBone(VRServer server) {
|
||||
this.server = server;
|
||||
reloadConfigValues();
|
||||
|
||||
this.minDataDistance = server.config
|
||||
.getInt("autobone.minimumDataDistance", this.minDataDistance);
|
||||
this.maxDataDistance = server.config
|
||||
.getInt("autobone.maximumDataDistance", this.maxDataDistance);
|
||||
|
||||
this.numEpochs = server.config.getInt("autobone.epochCount", this.numEpochs);
|
||||
|
||||
this.initialAdjustRate = server.config
|
||||
.getFloat("autobone.adjustRate", this.initialAdjustRate);
|
||||
this.adjustRateMultiplier = server.config
|
||||
.getFloat("autobone.adjustRateMultiplier", this.adjustRateMultiplier);
|
||||
|
||||
this.slideErrorFactor = server.config
|
||||
.getFloat("autobone.slideErrorFactor", this.slideErrorFactor);
|
||||
this.offsetSlideErrorFactor = server.config
|
||||
.getFloat("autobone.offsetSlideErrorFactor", this.offsetSlideErrorFactor);
|
||||
this.footHeightOffsetErrorFactor = server.config
|
||||
.getFloat("autobone.offsetErrorFactor", this.footHeightOffsetErrorFactor);
|
||||
this.bodyProportionErrorFactor = server.config
|
||||
.getFloat("autobone.proportionErrorFactor", this.bodyProportionErrorFactor);
|
||||
this.heightErrorFactor = server.config
|
||||
.getFloat("autobone.heightErrorFactor", this.heightErrorFactor);
|
||||
this.positionErrorFactor = server.config
|
||||
.getFloat("autobone.positionErrorFactor", this.positionErrorFactor);
|
||||
this.positionOffsetErrorFactor = server.config
|
||||
.getFloat("autobone.positionOffsetErrorFactor", this.positionOffsetErrorFactor);
|
||||
|
||||
this.calcInitError = server.config.getBoolean("autobone.calculateInitialError", true);
|
||||
this.targetHeight = server.config.getFloat("autobone.manualTargetHeight", -1f);
|
||||
}
|
||||
|
||||
// Mean square error function
|
||||
protected static float errorFunc(float errorDeriv) {
|
||||
return 0.5f * (errorDeriv * errorDeriv);
|
||||
}
|
||||
|
||||
public static File getLoadDir() {
|
||||
return loadDir;
|
||||
}
|
||||
|
||||
public void reloadConfigValues() {
|
||||
reloadConfigValues(null);
|
||||
}
|
||||
|
||||
public void reloadConfigValues(List<PoseFrameTracker> trackers) {
|
||||
for (BoneType offset : adjustOffsets) {
|
||||
offsets.put(offset, 0.4f);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3f getBoneDirection(
|
||||
HumanSkeleton skeleton,
|
||||
BoneType node,
|
||||
boolean rightSide,
|
||||
Vector3f buffer
|
||||
) {
|
||||
if (buffer == null) {
|
||||
buffer = new Vector3f();
|
||||
}
|
||||
|
||||
switch (node) {
|
||||
case LEFT_HIP:
|
||||
case RIGHT_HIP:
|
||||
node = rightSide ? BoneType.RIGHT_HIP : BoneType.LEFT_HIP;
|
||||
break;
|
||||
|
||||
case LEFT_UPPER_LEG:
|
||||
case RIGHT_UPPER_LEG:
|
||||
node = rightSide ? BoneType.RIGHT_UPPER_LEG : BoneType.LEFT_UPPER_LEG;
|
||||
break;
|
||||
|
||||
case LEFT_LOWER_LEG:
|
||||
case RIGHT_LOWER_LEG:
|
||||
node = rightSide ? BoneType.RIGHT_LOWER_LEG : BoneType.LEFT_LOWER_LEG;
|
||||
break;
|
||||
}
|
||||
|
||||
TransformNode relevantTransform = skeleton.getNode(node);
|
||||
return relevantTransform.worldTransform
|
||||
.getTranslation()
|
||||
.subtract(relevantTransform.getParent().worldTransform.getTranslation(), buffer)
|
||||
.normalizeLocal();
|
||||
}
|
||||
|
||||
public float getDotProductDiff(
|
||||
HumanSkeleton skeleton1,
|
||||
HumanSkeleton skeleton2,
|
||||
BoneType node,
|
||||
boolean rightSide,
|
||||
Vector3f offset
|
||||
) {
|
||||
Vector3f normalizedOffset = offset.normalize();
|
||||
|
||||
Vector3f boneRotation = new Vector3f();
|
||||
getBoneDirection(skeleton1, node, rightSide, boneRotation);
|
||||
float dot1 = normalizedOffset.dot(boneRotation);
|
||||
|
||||
getBoneDirection(skeleton2, node, rightSide, boneRotation);
|
||||
float dot2 = normalizedOffset.dot(boneRotation);
|
||||
|
||||
return dot2 - dot1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple utility method to get the {@link Skeleton} from the
|
||||
* {@link VRServer}
|
||||
*
|
||||
* @return The {@link Skeleton} associated with the {@link VRServer}, or
|
||||
* null if there is none available
|
||||
* @see {@link VRServer}, {@link Skeleton}
|
||||
*/
|
||||
private Skeleton getSkeleton() {
|
||||
HumanPoseProcessor humanPoseProcessor = server != null ? server.humanPoseProcessor : null;
|
||||
return humanPoseProcessor != null ? humanPoseProcessor.getSkeleton() : null;
|
||||
}
|
||||
|
||||
public void applyAndSaveConfig() {
|
||||
if (!applyAndSaveConfig(getSkeleton())) {
|
||||
// Unable to apply to skeleton, save directly
|
||||
// saveConfigs();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyConfig(
|
||||
BiConsumer<SkeletonConfigValue, Float> configConsumer,
|
||||
Map<BoneType, Float> offsets
|
||||
) {
|
||||
if (configConsumer == null || offsets == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Float headOffset = offsets.get(BoneType.HEAD);
|
||||
if (headOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigValue.HEAD, headOffset);
|
||||
}
|
||||
|
||||
Float neckOffset = offsets.get(BoneType.NECK);
|
||||
if (neckOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigValue.NECK, neckOffset);
|
||||
}
|
||||
|
||||
Float chestOffset = offsets.get(BoneType.CHEST);
|
||||
Float hipOffset = offsets.get(BoneType.HIP);
|
||||
Float waistOffset = offsets.get(BoneType.WAIST);
|
||||
if (chestOffset != null && hipOffset != null && waistOffset != null) {
|
||||
configConsumer
|
||||
.accept(SkeletonConfigValue.TORSO, chestOffset + hipOffset + waistOffset);
|
||||
}
|
||||
|
||||
if (chestOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigValue.CHEST, chestOffset);
|
||||
}
|
||||
|
||||
if (hipOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigValue.WAIST, hipOffset);
|
||||
}
|
||||
|
||||
Float hipWidthOffset = offsets.get(BoneType.LEFT_HIP);
|
||||
if (hipWidthOffset == null) {
|
||||
hipWidthOffset = offsets.get(BoneType.RIGHT_HIP);
|
||||
}
|
||||
if (hipWidthOffset != null) {
|
||||
configConsumer
|
||||
.accept(SkeletonConfigValue.HIPS_WIDTH, hipWidthOffset * 2f);
|
||||
}
|
||||
|
||||
Float upperLegOffset = offsets.get(BoneType.LEFT_UPPER_LEG);
|
||||
if (upperLegOffset == null) {
|
||||
upperLegOffset = offsets.get(BoneType.RIGHT_UPPER_LEG);
|
||||
}
|
||||
Float lowerLegOffset = offsets.get(BoneType.LEFT_LOWER_LEG);
|
||||
if (lowerLegOffset == null) {
|
||||
lowerLegOffset = offsets.get(BoneType.RIGHT_LOWER_LEG);
|
||||
}
|
||||
if (upperLegOffset != null && lowerLegOffset != null) {
|
||||
configConsumer
|
||||
.accept(SkeletonConfigValue.LEGS_LENGTH, upperLegOffset + lowerLegOffset);
|
||||
}
|
||||
|
||||
if (lowerLegOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigValue.KNEE_HEIGHT, lowerLegOffset);
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean applyConfig(BiConsumer<SkeletonConfigValue, Float> configConsumer) {
|
||||
return applyConfig(configConsumer, offsets);
|
||||
}
|
||||
|
||||
public boolean applyConfig(
|
||||
Map<SkeletonConfigValue, Float> skeletonConfig,
|
||||
Map<BoneType, Float> offsets
|
||||
) {
|
||||
if (skeletonConfig == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return applyConfig(skeletonConfig::put, offsets);
|
||||
}
|
||||
|
||||
public boolean applyConfig(Map<SkeletonConfigValue, Float> skeletonConfig) {
|
||||
return applyConfig(skeletonConfig, offsets);
|
||||
}
|
||||
|
||||
public boolean applyConfig(SkeletonConfig skeletonConfig, Map<BoneType, Float> offsets) {
|
||||
if (skeletonConfig == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return applyConfig(skeletonConfig::setConfig, offsets);
|
||||
}
|
||||
|
||||
public boolean applyConfig(SkeletonConfig skeletonConfig) {
|
||||
return applyConfig(skeletonConfig, offsets);
|
||||
}
|
||||
|
||||
public boolean applyAndSaveConfig(Skeleton skeleton) {
|
||||
if (skeleton == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SkeletonConfig skeletonConfig = skeleton.getSkeletonConfig();
|
||||
if (!applyConfig(skeletonConfig))
|
||||
return false;
|
||||
|
||||
skeletonConfig.saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
|
||||
LogManager.info("[AutoBone] Configured skeleton bone lengths");
|
||||
return true;
|
||||
}
|
||||
|
||||
public Float getConfig(BoneType config) {
|
||||
return offsets.get(config);
|
||||
}
|
||||
|
||||
public <T> float sumSelectConfigs(
|
||||
List<T> selection,
|
||||
Function<T, Float> configs
|
||||
) {
|
||||
float sum = 0f;
|
||||
|
||||
for (T config : selection) {
|
||||
Float length = configs.apply(config);
|
||||
if (length != null) {
|
||||
sum += length;
|
||||
}
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public <T> float sumSelectConfigs(
|
||||
List<T> selection,
|
||||
Map<T, Float> configs
|
||||
) {
|
||||
return sumSelectConfigs(selection, configs::get);
|
||||
}
|
||||
|
||||
public float sumSelectConfigs(
|
||||
List<SkeletonConfigValue> selection,
|
||||
SkeletonConfig config
|
||||
) {
|
||||
return sumSelectConfigs(selection, config::getConfig);
|
||||
}
|
||||
|
||||
public float getLengthSum(Map<BoneType, Float> configs) {
|
||||
return getLengthSum(configs, null);
|
||||
}
|
||||
|
||||
public float getLengthSum(
|
||||
Map<BoneType, Float> configs,
|
||||
Map<BoneType, Float> configsAlt
|
||||
) {
|
||||
float length = 0f;
|
||||
|
||||
if (configsAlt != null) {
|
||||
for (Entry<BoneType, Float> config : configsAlt.entrySet()) {
|
||||
// If there isn't a duplicate config
|
||||
if (!configs.containsKey(config.getKey())) {
|
||||
length += config.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Float boneLength : configs.values()) {
|
||||
length += boneLength;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
public float getTargetHeight(PoseFrames frames) {
|
||||
float targetHeight;
|
||||
// Get the current skeleton from the server
|
||||
Skeleton skeleton = getSkeleton();
|
||||
if (skeleton != null) {
|
||||
// If there is a skeleton available, calculate the target height
|
||||
// from its configs
|
||||
targetHeight = sumSelectConfigs(legacyHeightConfigs, skeleton.getSkeletonConfig());
|
||||
LogManager
|
||||
.warning(
|
||||
"[AutoBone] Target height loaded from skeleton (Make sure you reset before running!): "
|
||||
+ targetHeight
|
||||
);
|
||||
} else {
|
||||
// Otherwise if there is no skeleton available, attempt to get the
|
||||
// max HMD height from the recording
|
||||
float hmdHeight = frames.getMaxHmdHeight();
|
||||
if (hmdHeight <= 0.50f) {
|
||||
LogManager
|
||||
.warning(
|
||||
"[AutoBone] Max headset height detected (Value seems too low, did you not stand up straight while measuring?): "
|
||||
+ hmdHeight
|
||||
);
|
||||
} else {
|
||||
LogManager.info("[AutoBone] Max headset height detected: " + hmdHeight);
|
||||
}
|
||||
|
||||
// Estimate target height from HMD height
|
||||
targetHeight = hmdHeight;
|
||||
}
|
||||
|
||||
return targetHeight;
|
||||
}
|
||||
|
||||
public AutoBoneResults processFrames(PoseFrames frames, Consumer<Epoch> epochCallback)
|
||||
throws AutoBoneException {
|
||||
return processFrames(frames, -1f, epochCallback);
|
||||
}
|
||||
|
||||
public AutoBoneResults processFrames(
|
||||
PoseFrames frames,
|
||||
float targetHeight,
|
||||
Consumer<Epoch> epochCallback
|
||||
) throws AutoBoneException {
|
||||
return processFrames(frames, true, targetHeight, epochCallback);
|
||||
}
|
||||
|
||||
public AutoBoneResults processFrames(
|
||||
PoseFrames frames,
|
||||
boolean calcInitError,
|
||||
float targetHeight,
|
||||
Consumer<Epoch> epochCallback
|
||||
) throws AutoBoneException {
|
||||
final int frameCount = frames.getMaxFrameCount();
|
||||
|
||||
List<PoseFrameTracker> trackers = frames.getTrackers();
|
||||
reloadConfigValues(trackers); // Reload configs and detect chest tracker
|
||||
// from the first frame
|
||||
|
||||
final PoseFrameSkeleton skeleton1 = new PoseFrameSkeleton(
|
||||
trackers,
|
||||
null
|
||||
);
|
||||
final PoseFrameSkeleton skeleton2 = new PoseFrameSkeleton(
|
||||
trackers,
|
||||
null
|
||||
);
|
||||
|
||||
EnumMap<BoneType, Float> intermediateOffsets = new EnumMap<BoneType, Float>(
|
||||
offsets
|
||||
);
|
||||
|
||||
AutoBoneTrainingStep trainingStep = new AutoBoneTrainingStep(
|
||||
targetHeight,
|
||||
skeleton1,
|
||||
skeleton2,
|
||||
frames,
|
||||
intermediateOffsets
|
||||
);
|
||||
|
||||
// If target height isn't specified, auto-detect
|
||||
if (targetHeight < 0f) {
|
||||
targetHeight = getTargetHeight(frames);
|
||||
}
|
||||
|
||||
// Epoch loop, each epoch is one full iteration over the full dataset
|
||||
for (int epoch = calcInitError ? -1 : 0; epoch < numEpochs; epoch++) {
|
||||
float sumError = 0f;
|
||||
int errorCount = 0;
|
||||
|
||||
float adjustRate = epoch >= 0
|
||||
? (initialAdjustRate * FastMath.pow(adjustRateMultiplier, epoch))
|
||||
: 0f;
|
||||
|
||||
int[] randomFrameIndices = null;
|
||||
if (randomizeFrameOrder) {
|
||||
randomFrameIndices = new int[frameCount];
|
||||
|
||||
int zeroPos = -1;
|
||||
for (int i = 0; i < frameCount; i++) {
|
||||
int index = rand.nextInt(frameCount);
|
||||
|
||||
if (i > 0) {
|
||||
while (index == zeroPos || randomFrameIndices[index] > 0) {
|
||||
index = rand.nextInt(frameCount);
|
||||
}
|
||||
} else {
|
||||
zeroPos = index;
|
||||
}
|
||||
|
||||
randomFrameIndices[index] = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the frames using a cursor and an offset for
|
||||
// comparing frames a
|
||||
// certain number of frames apart
|
||||
for (
|
||||
int cursorOffset = minDataDistance; cursorOffset <= maxDataDistance
|
||||
&& cursorOffset < frameCount;
|
||||
cursorOffset++
|
||||
) {
|
||||
for (
|
||||
int frameCursor = 0; frameCursor < frameCount - cursorOffset;
|
||||
frameCursor += cursorIncrement
|
||||
) {
|
||||
int frameCursor2 = frameCursor + cursorOffset;
|
||||
|
||||
applyConfig(skeleton1.skeletonConfig);
|
||||
skeleton2.skeletonConfig.setConfigs(skeleton1.skeletonConfig);
|
||||
|
||||
if (randomizeFrameOrder) {
|
||||
trainingStep
|
||||
.setCursors(
|
||||
randomFrameIndices[frameCursor],
|
||||
randomFrameIndices[frameCursor2]
|
||||
);
|
||||
} else {
|
||||
trainingStep.setCursors(frameCursor, frameCursor2);
|
||||
}
|
||||
|
||||
skeleton1.setCursor(trainingStep.getCursor1());
|
||||
skeleton2.setCursor(trainingStep.getCursor2());
|
||||
|
||||
skeleton1.updatePose();
|
||||
skeleton2.updatePose();
|
||||
|
||||
float totalLength = getLengthSum(offsets);
|
||||
float curHeight = sumSelectConfigs(heightOffsets, offsets);
|
||||
trainingStep.setCurrentHeight(curHeight);
|
||||
|
||||
float errorDeriv = getErrorDeriv(trainingStep);
|
||||
float error = errorFunc(errorDeriv);
|
||||
|
||||
// In case of fire
|
||||
if (Float.isNaN(error) || Float.isInfinite(error)) {
|
||||
// Extinguish
|
||||
LogManager
|
||||
.warning(
|
||||
"[AutoBone] Error value is invalid, resetting variables to recover"
|
||||
);
|
||||
reloadConfigValues(trackers);
|
||||
|
||||
// Reset error sum values
|
||||
sumError = 0f;
|
||||
errorCount = 0;
|
||||
|
||||
// Continue on new data
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store the error count for logging purposes
|
||||
sumError += errorDeriv;
|
||||
errorCount++;
|
||||
|
||||
float adjustVal = error * adjustRate;
|
||||
|
||||
// If there is no adjustment whatsoever, skip this
|
||||
if (adjustVal == 0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3f slideLeft = skeleton2
|
||||
.getComputedTracker(TrackerRole.LEFT_FOOT).position
|
||||
.subtract(
|
||||
skeleton1.getComputedTracker(TrackerRole.LEFT_FOOT).position
|
||||
);
|
||||
|
||||
Vector3f slideRight = skeleton2
|
||||
.getComputedTracker(TrackerRole.RIGHT_FOOT).position
|
||||
.subtract(
|
||||
skeleton1
|
||||
.getComputedTracker(TrackerRole.RIGHT_FOOT).position
|
||||
);
|
||||
|
||||
intermediateOffsets.putAll(offsets);
|
||||
for (Entry<BoneType, Float> entry : offsets.entrySet()) {
|
||||
// Skip adjustment if the epoch is before starting (for
|
||||
// logging only)
|
||||
if (epoch < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
float originalLength = entry.getValue();
|
||||
boolean isHeightVar = heightOffsets.contains(entry.getKey());
|
||||
|
||||
float leftDotProduct = getDotProductDiff(
|
||||
skeleton1,
|
||||
skeleton2,
|
||||
entry.getKey(),
|
||||
false,
|
||||
slideLeft
|
||||
);
|
||||
|
||||
float rightDotProduct = getDotProductDiff(
|
||||
skeleton1,
|
||||
skeleton2,
|
||||
entry.getKey(),
|
||||
true,
|
||||
slideRight
|
||||
);
|
||||
|
||||
float dotLength = originalLength
|
||||
* ((leftDotProduct + rightDotProduct) / 2f);
|
||||
|
||||
// Scale by the ratio for smooth adjustment and more
|
||||
// stable results
|
||||
float curAdjustVal = (adjustVal * -dotLength) / totalLength;
|
||||
float newLength = originalLength + curAdjustVal;
|
||||
|
||||
// No small or negative numbers!!! Bad algorithm!
|
||||
if (newLength < 0.01f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Apply new offset length
|
||||
intermediateOffsets.put(entry.getKey(), newLength);
|
||||
applyConfig(skeleton1.skeletonConfig, intermediateOffsets);
|
||||
skeleton2.skeletonConfig.setConfigs(skeleton1.skeletonConfig);
|
||||
|
||||
// Update the skeleton poses for the new offset length
|
||||
skeleton1.updatePose();
|
||||
skeleton2.updatePose();
|
||||
|
||||
float newHeight = isHeightVar ? curHeight + curAdjustVal : curHeight;
|
||||
trainingStep.setCurrentHeight(newHeight);
|
||||
|
||||
float newErrorDeriv = getErrorDeriv(trainingStep);
|
||||
|
||||
if (newErrorDeriv < errorDeriv) {
|
||||
entry.setValue(newLength);
|
||||
}
|
||||
|
||||
// Reset the length to minimize bias in other variables,
|
||||
// it's applied later
|
||||
intermediateOffsets.put(entry.getKey(), originalLength);
|
||||
applyConfig(skeleton1.skeletonConfig, intermediateOffsets);
|
||||
skeleton2.skeletonConfig.setConfigs(skeleton1.skeletonConfig);
|
||||
}
|
||||
|
||||
if (scaleEachStep) {
|
||||
float stepHeight = sumSelectConfigs(heightOffsets, offsets);
|
||||
|
||||
if (stepHeight > 0f) {
|
||||
float stepHeightDiff = targetHeight - stepHeight;
|
||||
for (Entry<BoneType, Float> entry : offsets.entrySet()) {
|
||||
// Only height variables
|
||||
if (
|
||||
entry.getKey() == BoneType.NECK
|
||||
|| !heightOffsets.contains(entry.getKey())
|
||||
)
|
||||
continue;
|
||||
|
||||
float length = entry.getValue();
|
||||
|
||||
// Multiply the diff by the length to height
|
||||
// ratio
|
||||
float adjVal = stepHeightDiff * (length / stepHeight);
|
||||
|
||||
// Scale the length to fit the target height
|
||||
entry.setValue(Math.max(length + (adjVal / 2f), 0.01f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate average error over the epoch
|
||||
float avgError = errorCount > 0 ? sumError / errorCount : -1f;
|
||||
LogManager.info("[AutoBone] Epoch " + (epoch + 1) + " average error: " + avgError);
|
||||
|
||||
applyConfig(legacyConfigs);
|
||||
if (epochCallback != null) {
|
||||
epochCallback.accept(new Epoch(epoch + 1, numEpochs, avgError, legacyConfigs));
|
||||
}
|
||||
}
|
||||
|
||||
float finalHeight = sumSelectConfigs(heightOffsets, offsets);
|
||||
LogManager
|
||||
.info(
|
||||
"[AutoBone] Target height: "
|
||||
+ targetHeight
|
||||
+ " New height: "
|
||||
+ finalHeight
|
||||
);
|
||||
|
||||
return new AutoBoneResults(finalHeight, targetHeight, legacyConfigs);
|
||||
}
|
||||
|
||||
protected float getErrorDeriv(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
float totalError = 0f;
|
||||
float sumWeight = 0f;
|
||||
|
||||
if (slideErrorFactor > 0f) {
|
||||
totalError += slideError.getStepError(trainingStep) * slideErrorFactor;
|
||||
sumWeight += slideErrorFactor;
|
||||
}
|
||||
|
||||
if (offsetSlideErrorFactor > 0f) {
|
||||
totalError += offsetSlideError.getStepError(trainingStep) * offsetSlideErrorFactor;
|
||||
sumWeight += offsetSlideErrorFactor;
|
||||
}
|
||||
|
||||
if (footHeightOffsetErrorFactor > 0f) {
|
||||
totalError += footHeightOffsetError.getStepError(trainingStep)
|
||||
* footHeightOffsetErrorFactor;
|
||||
sumWeight += footHeightOffsetErrorFactor;
|
||||
}
|
||||
|
||||
if (bodyProportionErrorFactor > 0f) {
|
||||
totalError += bodyProportionError.getStepError(trainingStep)
|
||||
* bodyProportionErrorFactor;
|
||||
sumWeight += bodyProportionErrorFactor;
|
||||
}
|
||||
|
||||
if (heightErrorFactor > 0f) {
|
||||
totalError += heightError.getStepError(trainingStep) * heightErrorFactor;
|
||||
sumWeight += heightErrorFactor;
|
||||
}
|
||||
|
||||
if (positionErrorFactor > 0f) {
|
||||
totalError += positionError.getStepError(trainingStep) * positionErrorFactor;
|
||||
sumWeight += positionErrorFactor;
|
||||
}
|
||||
|
||||
if (positionOffsetErrorFactor > 0f) {
|
||||
totalError += positionOffsetError.getStepError(trainingStep)
|
||||
* positionOffsetErrorFactor;
|
||||
sumWeight += positionOffsetErrorFactor;
|
||||
}
|
||||
|
||||
return sumWeight > 0f ? totalError / sumWeight : 0f;
|
||||
}
|
||||
|
||||
public String getLengthsString() {
|
||||
final StringBuilder configInfo = new StringBuilder();
|
||||
this.offsets.forEach((key, value) -> {
|
||||
if (configInfo.length() > 0) {
|
||||
configInfo.append(", ");
|
||||
}
|
||||
|
||||
configInfo.append(key.toString() + ": " + StringUtils.prettyNumber(value * 100f, 2));
|
||||
});
|
||||
|
||||
return configInfo.toString();
|
||||
}
|
||||
|
||||
public void saveRecording(PoseFrames frames) {
|
||||
if (saveDir.isDirectory() || saveDir.mkdirs()) {
|
||||
File saveRecording;
|
||||
int recordingIndex = 1;
|
||||
do {
|
||||
saveRecording = new File(saveDir, "ABRecording" + recordingIndex++ + ".pfr");
|
||||
} while (saveRecording.exists());
|
||||
|
||||
LogManager
|
||||
.info("[AutoBone] Exporting frames to \"" + saveRecording.getPath() + "\"...");
|
||||
if (PoseFrameIO.writeToFile(saveRecording, frames)) {
|
||||
LogManager
|
||||
.info(
|
||||
"[AutoBone] Done exporting! Recording can be found at \""
|
||||
+ saveRecording.getPath()
|
||||
+ "\"."
|
||||
);
|
||||
} else {
|
||||
LogManager
|
||||
.severe(
|
||||
"[AutoBone] Failed to export the recording to \""
|
||||
+ saveRecording.getPath()
|
||||
+ "\"."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
LogManager
|
||||
.severe(
|
||||
"[AutoBone] Failed to create the recording directory \""
|
||||
+ saveDir.getPath()
|
||||
+ "\"."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public List<Pair<String, PoseFrames>> loadRecordings() {
|
||||
List<Pair<String, PoseFrames>> recordings = new FastList<Pair<String, PoseFrames>>();
|
||||
if (loadDir.isDirectory()) {
|
||||
File[] files = loadDir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (
|
||||
file.isFile()
|
||||
&& org.apache.commons.lang3.StringUtils
|
||||
.endsWithIgnoreCase(file.getName(), ".pfr")
|
||||
) {
|
||||
LogManager
|
||||
.info(
|
||||
"[AutoBone] Detected recording at \""
|
||||
+ file.getPath()
|
||||
+ "\", loading frames..."
|
||||
);
|
||||
PoseFrames frames = PoseFrameIO.readFromFile(file);
|
||||
|
||||
if (frames == null) {
|
||||
LogManager
|
||||
.severe("Reading frames from \"" + file.getPath() + "\" failed...");
|
||||
} else {
|
||||
recordings.add(Pair.of(file.getName(), frames));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recordings;
|
||||
}
|
||||
|
||||
public class Epoch {
|
||||
|
||||
public final int epoch;
|
||||
public final int totalEpochs;
|
||||
public final float epochError;
|
||||
public final EnumMap<SkeletonConfigValue, Float> configValues;
|
||||
|
||||
public Epoch(
|
||||
int epoch,
|
||||
int totalEpochs,
|
||||
float epochError,
|
||||
EnumMap<SkeletonConfigValue, Float> configValues
|
||||
) {
|
||||
this.epoch = epoch;
|
||||
this.totalEpochs = totalEpochs;
|
||||
this.epochError = epochError;
|
||||
this.configValues = configValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Epoch: " + epoch + ", Epoch Error: " + epochError;
|
||||
}
|
||||
}
|
||||
|
||||
public class AutoBoneResults {
|
||||
|
||||
public final float finalHeight;
|
||||
public final float targetHeight;
|
||||
public final EnumMap<SkeletonConfigValue, Float> configValues;
|
||||
|
||||
public AutoBoneResults(
|
||||
float finalHeight,
|
||||
float targetHeight,
|
||||
EnumMap<SkeletonConfigValue, Float> configValues
|
||||
) {
|
||||
this.finalHeight = finalHeight;
|
||||
this.targetHeight = targetHeight;
|
||||
this.configValues = configValues;
|
||||
}
|
||||
|
||||
public float getHeightDifference() {
|
||||
return FastMath.abs(targetHeight - finalHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
427
src/main/java/dev/slimevr/autobone/AutoBoneHandler.java
Normal file
427
src/main/java/dev/slimevr/autobone/AutoBoneHandler.java
Normal file
@@ -0,0 +1,427 @@
|
||||
package dev.slimevr.autobone;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.autobone.AutoBone.AutoBoneResults;
|
||||
import dev.slimevr.autobone.errors.AutoBoneException;
|
||||
import dev.slimevr.poserecorder.PoseFrameTracker;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.poserecorder.PoseRecorder;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfig;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
|
||||
public class AutoBoneHandler {
|
||||
|
||||
private final VRServer server;
|
||||
private final PoseRecorder poseRecorder;
|
||||
private final AutoBone autoBone;
|
||||
|
||||
private ReentrantLock recordingLock = new ReentrantLock();
|
||||
private Thread recordingThread = null;
|
||||
|
||||
private ReentrantLock saveRecordingLock = new ReentrantLock();
|
||||
private Thread saveRecordingThread = null;
|
||||
|
||||
private ReentrantLock autoBoneLock = new ReentrantLock();
|
||||
private Thread autoBoneThread = null;
|
||||
|
||||
private List<AutoBoneListener> listeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
public AutoBoneHandler(VRServer server) {
|
||||
this.server = server;
|
||||
this.poseRecorder = new PoseRecorder(server);
|
||||
this.autoBone = new AutoBone(server);
|
||||
}
|
||||
|
||||
public void addListener(AutoBoneListener listener) {
|
||||
this.listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeListener(AutoBoneListener listener) {
|
||||
this.listeners.removeIf(l -> listener == l);
|
||||
}
|
||||
|
||||
private void announceProcessStatus(
|
||||
AutoBoneProcessType processType,
|
||||
String message,
|
||||
long current,
|
||||
long total,
|
||||
boolean completed,
|
||||
boolean success
|
||||
) {
|
||||
listeners.forEach(listener -> {
|
||||
listener
|
||||
.onAutoBoneProcessStatus(processType, message, current, total, completed, success);
|
||||
});
|
||||
}
|
||||
|
||||
private void announceProcessStatus(
|
||||
AutoBoneProcessType processType,
|
||||
String message,
|
||||
boolean completed,
|
||||
boolean success
|
||||
) {
|
||||
announceProcessStatus(processType, message, 0, 0, completed, success);
|
||||
}
|
||||
|
||||
private void announceProcessStatus(AutoBoneProcessType processType, String message) {
|
||||
announceProcessStatus(processType, message, false, true);
|
||||
}
|
||||
|
||||
private void announceProcessStatus(AutoBoneProcessType processType, long current, long total) {
|
||||
announceProcessStatus(processType, null, current, total, false, true);
|
||||
}
|
||||
|
||||
public String getLengthsString() {
|
||||
return autoBone.getLengthsString();
|
||||
}
|
||||
|
||||
private AutoBoneResults processFrames(PoseFrames frames) throws AutoBoneException {
|
||||
return autoBone
|
||||
.processFrames(frames, autoBone.calcInitError, autoBone.targetHeight, (epoch) -> {
|
||||
listeners.forEach(listener -> {
|
||||
listener.onAutoBoneEpoch(epoch);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public boolean startProcessByType(AutoBoneProcessType processType) {
|
||||
switch (processType) {
|
||||
case RECORD:
|
||||
startRecording();
|
||||
break;
|
||||
|
||||
case SAVE:
|
||||
saveRecording();
|
||||
break;
|
||||
|
||||
case PROCESS:
|
||||
processRecording();
|
||||
break;
|
||||
|
||||
case APPLY:
|
||||
applyValues();
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void startRecording() {
|
||||
recordingLock.lock();
|
||||
|
||||
try {
|
||||
// Prevent running multiple times
|
||||
if (recordingThread != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Thread thread = new Thread(this::startRecordingThread);
|
||||
recordingThread = thread;
|
||||
thread.start();
|
||||
} finally {
|
||||
recordingLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecordingThread() {
|
||||
try {
|
||||
if (poseRecorder.isReadyToRecord()) {
|
||||
announceProcessStatus(AutoBoneProcessType.RECORD, "Recording...");
|
||||
|
||||
// 1000 samples at 20 ms per sample is 20 seconds
|
||||
int sampleCount = server.config.getInt("autobone.sampleCount", 1000);
|
||||
long sampleRate = server.config.getLong("autobone.sampleRateMs", 20L);
|
||||
Future<PoseFrames> framesFuture = poseRecorder
|
||||
.startFrameRecording(sampleCount, sampleRate, progress -> {
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.RECORD,
|
||||
progress.frame,
|
||||
progress.totalFrames
|
||||
);
|
||||
});
|
||||
PoseFrames frames = framesFuture.get();
|
||||
LogManager.info("[AutoBone] Done recording!");
|
||||
|
||||
if (server.config.getBoolean("autobone.saveRecordings", false)) {
|
||||
announceProcessStatus(AutoBoneProcessType.RECORD, "Saving recording...");
|
||||
autoBone.saveRecording(frames);
|
||||
}
|
||||
|
||||
listeners.forEach(listener -> {
|
||||
listener.onAutoBoneRecordingEnd(frames);
|
||||
});
|
||||
|
||||
announceProcessStatus(AutoBoneProcessType.RECORD, "Done recording!", true, true);
|
||||
} else {
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.RECORD,
|
||||
"The server is not ready to record",
|
||||
true,
|
||||
false
|
||||
);
|
||||
LogManager.severe("[AutoBone] Unable to record...");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.RECORD,
|
||||
String.format("Recording failed: %s", e.getMessage()),
|
||||
true,
|
||||
false
|
||||
);
|
||||
LogManager.severe("[AutoBone] Failed recording!", e);
|
||||
} finally {
|
||||
recordingThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void saveRecording() {
|
||||
saveRecordingLock.lock();
|
||||
|
||||
try {
|
||||
// Prevent running multiple times
|
||||
if (saveRecordingThread != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Thread thread = new Thread(this::saveRecordingThread);
|
||||
saveRecordingThread = thread;
|
||||
thread.start();
|
||||
} finally {
|
||||
saveRecordingLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void saveRecordingThread() {
|
||||
try {
|
||||
Future<PoseFrames> framesFuture = poseRecorder.getFramesAsync();
|
||||
if (framesFuture != null) {
|
||||
announceProcessStatus(AutoBoneProcessType.SAVE, "Waiting for recording...");
|
||||
PoseFrames frames = framesFuture.get();
|
||||
|
||||
if (frames.getTrackerCount() <= 0) {
|
||||
throw new IllegalStateException("Recording has no trackers");
|
||||
}
|
||||
|
||||
if (frames.getMaxFrameCount() <= 0) {
|
||||
throw new IllegalStateException("Recording has no frames");
|
||||
}
|
||||
|
||||
announceProcessStatus(AutoBoneProcessType.SAVE, "Saving recording...");
|
||||
autoBone.saveRecording(frames);
|
||||
|
||||
announceProcessStatus(AutoBoneProcessType.SAVE, "Recording saved!", true, true);
|
||||
} else {
|
||||
announceProcessStatus(AutoBoneProcessType.SAVE, "No recording found", true, false);
|
||||
LogManager.severe("[AutoBone] Unable to save, no recording was done...");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.SAVE,
|
||||
String.format("Failed to save recording: %s", e.getMessage()),
|
||||
true,
|
||||
false
|
||||
);
|
||||
LogManager.severe("[AutoBone] Failed to save recording!", e);
|
||||
} finally {
|
||||
saveRecordingThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void processRecording() {
|
||||
autoBoneLock.lock();
|
||||
|
||||
try {
|
||||
// Prevent running multiple times
|
||||
if (autoBoneThread != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Thread thread = new Thread(this::processRecordingThread);
|
||||
autoBoneThread = thread;
|
||||
thread.start();
|
||||
} finally {
|
||||
autoBoneLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void processRecordingThread() {
|
||||
try {
|
||||
announceProcessStatus(AutoBoneProcessType.PROCESS, "Loading recordings...");
|
||||
List<Pair<String, PoseFrames>> frameRecordings = autoBone.loadRecordings();
|
||||
|
||||
if (!frameRecordings.isEmpty()) {
|
||||
LogManager.info("[AutoBone] Done loading frames!");
|
||||
} else {
|
||||
Future<PoseFrames> framesFuture = poseRecorder.getFramesAsync();
|
||||
if (framesFuture != null) {
|
||||
announceProcessStatus(AutoBoneProcessType.PROCESS, "Waiting for recording...");
|
||||
PoseFrames frames = framesFuture.get();
|
||||
|
||||
if (frames.getTrackerCount() <= 0) {
|
||||
throw new IllegalStateException("Recording has no trackers");
|
||||
}
|
||||
|
||||
if (frames.getMaxFrameCount() <= 0) {
|
||||
throw new IllegalStateException("Recording has no frames");
|
||||
}
|
||||
|
||||
frameRecordings.add(Pair.of("<Recording>", frames));
|
||||
} else {
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.PROCESS,
|
||||
"No recordings found...",
|
||||
true,
|
||||
false
|
||||
);
|
||||
LogManager
|
||||
.severe(
|
||||
"[AutoBone] No recordings found in \""
|
||||
+ AutoBone.getLoadDir().getPath()
|
||||
+ "\" and no recording was done..."
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
announceProcessStatus(AutoBoneProcessType.PROCESS, "Processing recording(s)...");
|
||||
LogManager.info("[AutoBone] Processing frames...");
|
||||
FastList<Float> heightPercentError = new FastList<Float>(frameRecordings.size());
|
||||
SkeletonConfig skeletonConfigBuffer = new SkeletonConfig(false);
|
||||
for (Pair<String, PoseFrames> recording : frameRecordings) {
|
||||
LogManager
|
||||
.info("[AutoBone] Processing frames from \"" + recording.getKey() + "\"...");
|
||||
|
||||
List<PoseFrameTracker> trackers = recording.getValue().getTrackers();
|
||||
|
||||
StringBuilder trackerInfo = new StringBuilder();
|
||||
for (PoseFrameTracker tracker : trackers) {
|
||||
if (tracker == null)
|
||||
continue;
|
||||
|
||||
TrackerPosition position = tracker
|
||||
.getBodyPosition();
|
||||
if (position == null)
|
||||
continue;
|
||||
|
||||
if (trackerInfo.length() > 0) {
|
||||
trackerInfo.append(", ");
|
||||
}
|
||||
|
||||
trackerInfo.append(position.designation);
|
||||
}
|
||||
|
||||
LogManager
|
||||
.info(
|
||||
"[AutoBone] ("
|
||||
+ trackers.size()
|
||||
+ " trackers) ["
|
||||
+ trackerInfo.toString()
|
||||
+ "]"
|
||||
);
|
||||
|
||||
AutoBoneResults autoBoneResults = processFrames(recording.getValue());
|
||||
heightPercentError.add(autoBoneResults.getHeightDifference());
|
||||
LogManager.info("[AutoBone] Done processing!");
|
||||
|
||||
// #region Stats/Values
|
||||
skeletonConfigBuffer.setConfigs(autoBoneResults.configValues, null);
|
||||
|
||||
float neckLength = skeletonConfigBuffer.getConfig(SkeletonConfigValue.NECK);
|
||||
float chestDistance = skeletonConfigBuffer.getConfig(SkeletonConfigValue.CHEST);
|
||||
float torsoLength = skeletonConfigBuffer.getConfig(SkeletonConfigValue.TORSO);
|
||||
float hipWidth = skeletonConfigBuffer.getConfig(SkeletonConfigValue.HIPS_WIDTH);
|
||||
float legsLength = skeletonConfigBuffer.getConfig(SkeletonConfigValue.LEGS_LENGTH);
|
||||
float kneeHeight = skeletonConfigBuffer.getConfig(SkeletonConfigValue.KNEE_HEIGHT);
|
||||
|
||||
float neckTorso = neckLength / torsoLength;
|
||||
float chestTorso = chestDistance / torsoLength;
|
||||
float torsoWaist = hipWidth / torsoLength;
|
||||
float legTorso = legsLength / torsoLength;
|
||||
float legBody = legsLength / (torsoLength + neckLength);
|
||||
float kneeLeg = kneeHeight / legsLength;
|
||||
|
||||
LogManager
|
||||
.info(
|
||||
"[AutoBone] Ratios: [{Neck-Torso: "
|
||||
+ StringUtils.prettyNumber(neckTorso)
|
||||
+ "}, {Chest-Torso: "
|
||||
+ StringUtils.prettyNumber(chestTorso)
|
||||
+ "}, {Torso-Waist: "
|
||||
+ StringUtils.prettyNumber(torsoWaist)
|
||||
+ "}, {Leg-Torso: "
|
||||
+ StringUtils.prettyNumber(legTorso)
|
||||
+ "}, {Leg-Body: "
|
||||
+ StringUtils.prettyNumber(legBody)
|
||||
+ "}, {Knee-Leg: "
|
||||
+ StringUtils.prettyNumber(kneeLeg)
|
||||
+ "}]"
|
||||
);
|
||||
LogManager.info("[AutoBone] Length values: " + autoBone.getLengthsString());
|
||||
}
|
||||
|
||||
if (!heightPercentError.isEmpty()) {
|
||||
float mean = 0f;
|
||||
for (float val : heightPercentError) {
|
||||
mean += val;
|
||||
}
|
||||
mean /= heightPercentError.size();
|
||||
|
||||
float std = 0f;
|
||||
for (float val : heightPercentError) {
|
||||
float stdVal = val - mean;
|
||||
std += stdVal * stdVal;
|
||||
}
|
||||
std = (float) Math.sqrt(std / heightPercentError.size());
|
||||
|
||||
LogManager
|
||||
.info(
|
||||
"[AutoBone] Average height error: "
|
||||
+ StringUtils.prettyNumber(mean, 6)
|
||||
+ " (SD "
|
||||
+ StringUtils.prettyNumber(std, 6)
|
||||
+ ")"
|
||||
);
|
||||
}
|
||||
// #endregion
|
||||
|
||||
listeners.forEach(listener -> {
|
||||
listener.onAutoBoneEnd(autoBone.legacyConfigs);
|
||||
});
|
||||
|
||||
announceProcessStatus(AutoBoneProcessType.PROCESS, "Done processing!", true, true);
|
||||
} catch (Exception e) {
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.PROCESS,
|
||||
String.format("Processing failed: %s", e.getMessage()),
|
||||
true,
|
||||
false
|
||||
);
|
||||
LogManager.severe("[AutoBone] Failed adjustment!", e);
|
||||
} finally {
|
||||
autoBoneThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void applyValues() {
|
||||
autoBone.applyAndSaveConfig();
|
||||
announceProcessStatus(AutoBoneProcessType.APPLY, "Adjusted values applied!", true, true);
|
||||
// TODO Update GUI values after applying? Is that needed here?
|
||||
}
|
||||
}
|
||||
26
src/main/java/dev/slimevr/autobone/AutoBoneListener.java
Normal file
26
src/main/java/dev/slimevr/autobone/AutoBoneListener.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package dev.slimevr.autobone;
|
||||
|
||||
import java.util.EnumMap;
|
||||
|
||||
import dev.slimevr.autobone.AutoBone.Epoch;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
|
||||
|
||||
public interface AutoBoneListener {
|
||||
|
||||
public void onAutoBoneProcessStatus(
|
||||
AutoBoneProcessType processType,
|
||||
String message,
|
||||
long current,
|
||||
long total,
|
||||
boolean completed,
|
||||
boolean success
|
||||
);
|
||||
|
||||
public void onAutoBoneRecordingEnd(PoseFrames recording);
|
||||
|
||||
public void onAutoBoneEpoch(Epoch epoch);
|
||||
|
||||
public void onAutoBoneEnd(EnumMap<SkeletonConfigValue, Float> configValues);
|
||||
}
|
||||
31
src/main/java/dev/slimevr/autobone/AutoBoneProcessType.java
Normal file
31
src/main/java/dev/slimevr/autobone/AutoBoneProcessType.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package dev.slimevr.autobone;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public enum AutoBoneProcessType {
|
||||
NONE(0),
|
||||
RECORD(1),
|
||||
SAVE(2),
|
||||
PROCESS(3),
|
||||
APPLY(4);
|
||||
|
||||
public final int id;
|
||||
|
||||
private static final Map<Integer, AutoBoneProcessType> byId = new HashMap<>();
|
||||
|
||||
private AutoBoneProcessType(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static AutoBoneProcessType getById(int id) {
|
||||
return byId.get(id);
|
||||
}
|
||||
|
||||
static {
|
||||
for (AutoBoneProcessType abpt : values()) {
|
||||
byId.put(abpt.id, abpt);
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/main/java/dev/slimevr/autobone/AutoBoneTrainingStep.java
Normal file
108
src/main/java/dev/slimevr/autobone/AutoBoneTrainingStep.java
Normal file
@@ -0,0 +1,108 @@
|
||||
package dev.slimevr.autobone;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import dev.slimevr.poserecorder.PoseFrameSkeleton;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.vr.processor.skeleton.BoneType;
|
||||
|
||||
|
||||
public class AutoBoneTrainingStep {
|
||||
private int cursor1 = 0;
|
||||
private int cursor2 = 0;
|
||||
|
||||
private float currentHeight;
|
||||
private final float targetHeight;
|
||||
|
||||
private final PoseFrameSkeleton skeleton1;
|
||||
private final PoseFrameSkeleton skeleton2;
|
||||
|
||||
private final PoseFrames trainingFrames;
|
||||
|
||||
private final Map<BoneType, Float> intermediateOffsets;
|
||||
|
||||
public AutoBoneTrainingStep(
|
||||
int cursor1,
|
||||
int cursor2,
|
||||
float targetHeight,
|
||||
PoseFrameSkeleton skeleton1,
|
||||
PoseFrameSkeleton skeleton2,
|
||||
PoseFrames trainingFrames,
|
||||
Map<BoneType, Float> intermediateOffsets
|
||||
) {
|
||||
this.cursor1 = cursor1;
|
||||
this.cursor2 = cursor2;
|
||||
this.targetHeight = targetHeight;
|
||||
this.skeleton1 = skeleton1;
|
||||
this.skeleton2 = skeleton2;
|
||||
this.trainingFrames = trainingFrames;
|
||||
this.intermediateOffsets = intermediateOffsets;
|
||||
}
|
||||
|
||||
public AutoBoneTrainingStep(
|
||||
float targetHeight,
|
||||
PoseFrameSkeleton skeleton1,
|
||||
PoseFrameSkeleton skeleton2,
|
||||
PoseFrames trainingFrames,
|
||||
Map<BoneType, Float> intermediateOffsets
|
||||
) {
|
||||
this.targetHeight = targetHeight;
|
||||
this.skeleton1 = skeleton1;
|
||||
this.skeleton2 = skeleton2;
|
||||
this.trainingFrames = trainingFrames;
|
||||
this.intermediateOffsets = intermediateOffsets;
|
||||
}
|
||||
|
||||
public int getCursor1() {
|
||||
return cursor1;
|
||||
}
|
||||
|
||||
public void setCursor1(int cursor1) {
|
||||
this.cursor1 = cursor1;
|
||||
}
|
||||
|
||||
public int getCursor2() {
|
||||
return cursor2;
|
||||
}
|
||||
|
||||
public void setCursor2(int cursor2) {
|
||||
this.cursor2 = cursor2;
|
||||
}
|
||||
|
||||
public void setCursors(int cursor1, int cursor2) {
|
||||
this.cursor1 = cursor1;
|
||||
this.cursor2 = cursor2;
|
||||
}
|
||||
|
||||
public float getCurrentHeight() {
|
||||
return currentHeight;
|
||||
}
|
||||
|
||||
public void setCurrentHeight(float currentHeight) {
|
||||
this.currentHeight = currentHeight;
|
||||
}
|
||||
|
||||
public float getTargetHeight() {
|
||||
return targetHeight;
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton getSkeleton1() {
|
||||
return skeleton1;
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton getSkeleton2() {
|
||||
return skeleton2;
|
||||
}
|
||||
|
||||
public PoseFrames getTrainingFrames() {
|
||||
return trainingFrames;
|
||||
}
|
||||
|
||||
public Map<BoneType, Float> getIntermediateOffsets() {
|
||||
return intermediateOffsets;
|
||||
}
|
||||
|
||||
public float getHeightOffset() {
|
||||
return getTargetHeight() - getCurrentHeight();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
public class AutoBoneException extends Exception {
|
||||
|
||||
public AutoBoneException() {
|
||||
}
|
||||
|
||||
public AutoBoneException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AutoBoneException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AutoBoneException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AutoBoneException(
|
||||
String message,
|
||||
Throwable cause,
|
||||
boolean enableSuppression,
|
||||
boolean writableStackTrace
|
||||
) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfig;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
|
||||
|
||||
// The distance from average human proportions
|
||||
public class BodyProportionError implements IAutoBoneError {
|
||||
|
||||
// TODO hip tracker stuff... Hip tracker should be around 3 to 5
|
||||
// centimeters. Human average is probably 1.1235 (SD 0.07)
|
||||
public float legBodyRatio = 1.1235f;
|
||||
|
||||
// SD of 0.07, capture 68% within range
|
||||
public float legBodyRatioRange = 0.07f;
|
||||
|
||||
// kneeLegRatio seems to be around 0.54 to 0.6 after asking a few people in
|
||||
// the SlimeVR discord.
|
||||
public float kneeLegRatio = 0.55f;
|
||||
|
||||
// kneeLegRatio seems to be around 0.55 to 0.64 after asking a few people in
|
||||
// the SlimeVR discord. TODO : Chest should be a bit shorter (0.54?) if user
|
||||
// has an additional hip tracker.
|
||||
public float chestTorsoRatio = 0.57f;
|
||||
|
||||
@Override
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
return getBodyProportionError(trainingStep.getSkeleton1().skeletonConfig);
|
||||
}
|
||||
|
||||
public float getBodyProportionError(SkeletonConfig config) {
|
||||
float neckLength = config.getConfig(SkeletonConfigValue.NECK);
|
||||
float chestLength = config.getConfig(SkeletonConfigValue.CHEST);
|
||||
float torsoLength = config.getConfig(SkeletonConfigValue.TORSO);
|
||||
float legsLength = config.getConfig(SkeletonConfigValue.LEGS_LENGTH);
|
||||
float kneeHeight = config.getConfig(SkeletonConfigValue.KNEE_HEIGHT);
|
||||
|
||||
float chestTorso = FastMath.abs((chestLength / torsoLength) - chestTorsoRatio);
|
||||
float legBody = FastMath.abs((legsLength / (torsoLength + neckLength)) - legBodyRatio);
|
||||
float kneeLeg = FastMath.abs((kneeHeight / legsLength) - kneeLegRatio);
|
||||
|
||||
if (legBody <= legBodyRatioRange) {
|
||||
legBody = 0f;
|
||||
} else {
|
||||
legBody -= legBodyRatioRange;
|
||||
}
|
||||
|
||||
return (chestTorso + legBody + kneeLeg) / 3f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
|
||||
|
||||
// The offset between the height both feet at one instant and over time
|
||||
public class FootHeightOffsetError implements IAutoBoneError {
|
||||
@Override
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
return getSlideError(trainingStep.getSkeleton1(), trainingStep.getSkeleton2());
|
||||
}
|
||||
|
||||
public static float getSlideError(HumanSkeleton skeleton1, HumanSkeleton skeleton2) {
|
||||
ComputedTracker leftTracker1 = skeleton1.getComputedTracker(TrackerRole.LEFT_FOOT);
|
||||
ComputedTracker rightTracker1 = skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT);
|
||||
|
||||
ComputedTracker leftTracker2 = skeleton2.getComputedTracker(TrackerRole.LEFT_FOOT);
|
||||
ComputedTracker rightTracker2 = skeleton2.getComputedTracker(TrackerRole.RIGHT_FOOT);
|
||||
|
||||
return getFootHeightError(leftTracker1, rightTracker1, leftTracker2, rightTracker2);
|
||||
}
|
||||
|
||||
public static float getFootHeightError(
|
||||
ComputedTracker leftTracker1,
|
||||
ComputedTracker rightTracker1,
|
||||
ComputedTracker leftTracker2,
|
||||
ComputedTracker rightTracker2
|
||||
) {
|
||||
float leftFoot1 = leftTracker1.position.y;
|
||||
float rightFoot1 = rightTracker1.position.y;
|
||||
|
||||
float leftFoot2 = leftTracker2.position.y;
|
||||
float rightFoot2 = rightTracker2.position.y;
|
||||
|
||||
// Compute all combinations of heights
|
||||
float dist1 = FastMath.abs(leftFoot1 - rightFoot1);
|
||||
float dist2 = FastMath.abs(leftFoot1 - leftFoot2);
|
||||
float dist3 = FastMath.abs(leftFoot1 - rightFoot2);
|
||||
|
||||
float dist4 = FastMath.abs(rightFoot1 - leftFoot2);
|
||||
float dist5 = FastMath.abs(rightFoot1 - rightFoot2);
|
||||
|
||||
float dist6 = FastMath.abs(leftFoot2 - rightFoot2);
|
||||
|
||||
// Divide by 12 (6 values * 2 to halve) to halve and average, it's
|
||||
// halved because you want to approach a midpoint, not the other point
|
||||
return (dist1 + dist2 + dist3 + dist4 + dist5 + dist6) / 12f;
|
||||
}
|
||||
}
|
||||
21
src/main/java/dev/slimevr/autobone/errors/HeightError.java
Normal file
21
src/main/java/dev/slimevr/autobone/errors/HeightError.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
|
||||
|
||||
// The difference from the current height to the target height
|
||||
public class HeightError implements IAutoBoneError {
|
||||
@Override
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
return getHeightError(
|
||||
trainingStep.getCurrentHeight(),
|
||||
trainingStep.getTargetHeight()
|
||||
);
|
||||
}
|
||||
|
||||
public float getHeightError(float currentHeight, float targetHeight) {
|
||||
return FastMath.abs(targetHeight - currentHeight);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
|
||||
|
||||
public interface IAutoBoneError {
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
|
||||
|
||||
// The change in distance between both of the ankles over time
|
||||
public class OffsetSlideError implements IAutoBoneError {
|
||||
@Override
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
return getSlideError(trainingStep.getSkeleton1(), trainingStep.getSkeleton2());
|
||||
}
|
||||
|
||||
public static float getSlideError(HumanSkeleton skeleton1, HumanSkeleton skeleton2) {
|
||||
ComputedTracker leftTracker1 = skeleton1.getComputedTracker(TrackerRole.LEFT_FOOT);
|
||||
ComputedTracker rightTracker1 = skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT);
|
||||
|
||||
ComputedTracker leftTracker2 = skeleton2.getComputedTracker(TrackerRole.LEFT_FOOT);
|
||||
ComputedTracker rightTracker2 = skeleton2.getComputedTracker(TrackerRole.RIGHT_FOOT);
|
||||
|
||||
return getSlideError(leftTracker1, rightTracker1, leftTracker2, rightTracker2);
|
||||
}
|
||||
|
||||
public static float getSlideError(
|
||||
ComputedTracker leftTracker1,
|
||||
ComputedTracker rightTracker1,
|
||||
ComputedTracker leftTracker2,
|
||||
ComputedTracker rightTracker2
|
||||
) {
|
||||
Vector3f leftFoot1 = leftTracker1.position;
|
||||
Vector3f rightFoot1 = rightTracker1.position;
|
||||
|
||||
Vector3f leftFoot2 = leftTracker2.position;
|
||||
Vector3f rightFoot2 = rightTracker2.position;
|
||||
|
||||
float slideDist1 = leftFoot1.distance(rightFoot1);
|
||||
float slideDist2 = leftFoot2.distance(rightFoot2);
|
||||
|
||||
float slideDist3 = leftFoot1.distance(rightFoot2);
|
||||
float slideDist4 = leftFoot2.distance(rightFoot1);
|
||||
|
||||
// Compute all combinations of distances
|
||||
float dist1 = FastMath.abs(slideDist1 - slideDist2);
|
||||
float dist2 = FastMath.abs(slideDist1 - slideDist3);
|
||||
float dist3 = FastMath.abs(slideDist1 - slideDist4);
|
||||
|
||||
float dist4 = FastMath.abs(slideDist2 - slideDist3);
|
||||
float dist5 = FastMath.abs(slideDist2 - slideDist4);
|
||||
|
||||
float dist6 = FastMath.abs(slideDist3 - slideDist4);
|
||||
|
||||
// Divide by 12 (6 values * 2 to halve) to halve and average, it's
|
||||
// halved because you want to approach a midpoint, not the other point
|
||||
return (dist1 + dist2 + dist3 + dist4 + dist5 + dist6) / 12f;
|
||||
}
|
||||
}
|
||||
61
src/main/java/dev/slimevr/autobone/errors/PositionError.java
Normal file
61
src/main/java/dev/slimevr/autobone/errors/PositionError.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
import dev.slimevr.poserecorder.PoseFrameTracker;
|
||||
import dev.slimevr.poserecorder.TrackerFrame;
|
||||
import dev.slimevr.poserecorder.TrackerFrameData;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
|
||||
|
||||
// The distance of any points to the corresponding absolute position
|
||||
public class PositionError implements IAutoBoneError {
|
||||
@Override
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
List<PoseFrameTracker> trackers = trainingStep.getTrainingFrames().getTrackers();
|
||||
return (getPositionError(
|
||||
trackers,
|
||||
trainingStep.getCursor1(),
|
||||
trainingStep.getSkeleton1()
|
||||
)
|
||||
+ getPositionError(
|
||||
trackers,
|
||||
trainingStep.getCursor2(),
|
||||
trainingStep.getSkeleton2()
|
||||
))
|
||||
/ 2f;
|
||||
}
|
||||
|
||||
public static float getPositionError(
|
||||
List<PoseFrameTracker> trackers,
|
||||
int cursor,
|
||||
HumanSkeleton skeleton
|
||||
) {
|
||||
float offset = 0f;
|
||||
int offsetCount = 0;
|
||||
|
||||
for (PoseFrameTracker tracker : trackers) {
|
||||
TrackerFrame trackerFrame = tracker.safeGetFrame(cursor);
|
||||
if (
|
||||
trackerFrame == null
|
||||
|| !trackerFrame.hasData(TrackerFrameData.POSITION)
|
||||
|| trackerFrame.designation.trackerRole.isEmpty()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ComputedTracker computedTracker = skeleton
|
||||
.getComputedTracker(trackerFrame.designation.trackerRole.get());
|
||||
if (computedTracker != null) {
|
||||
offset += FastMath.abs(computedTracker.position.distance(trackerFrame.position));
|
||||
offsetCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return offsetCount > 0 ? offset / offsetCount : 0f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
import dev.slimevr.poserecorder.PoseFrameTracker;
|
||||
import dev.slimevr.poserecorder.TrackerFrame;
|
||||
import dev.slimevr.poserecorder.TrackerFrameData;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
|
||||
|
||||
// The difference between offset of absolute position and the corresponding point over time
|
||||
public class PositionOffsetError implements IAutoBoneError {
|
||||
@Override
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
List<PoseFrameTracker> trackers = trainingStep.getTrainingFrames().getTrackers();
|
||||
return getPositionOffsetError(
|
||||
trackers,
|
||||
trainingStep.getCursor1(),
|
||||
trainingStep.getCursor2(),
|
||||
trainingStep.getSkeleton1(),
|
||||
trainingStep.getSkeleton2()
|
||||
);
|
||||
}
|
||||
|
||||
public float getPositionOffsetError(
|
||||
List<PoseFrameTracker> trackers,
|
||||
int cursor1,
|
||||
int cursor2,
|
||||
HumanSkeleton skeleton1,
|
||||
HumanSkeleton skeleton2
|
||||
) {
|
||||
float offset = 0f;
|
||||
int offsetCount = 0;
|
||||
|
||||
for (PoseFrameTracker tracker : trackers) {
|
||||
TrackerFrame trackerFrame1 = tracker.safeGetFrame(cursor1);
|
||||
if (
|
||||
trackerFrame1 == null
|
||||
|| !trackerFrame1.hasData(TrackerFrameData.POSITION)
|
||||
|| trackerFrame1.designation.trackerRole.isEmpty()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TrackerFrame trackerFrame2 = tracker.safeGetFrame(cursor2);
|
||||
if (
|
||||
trackerFrame2 == null
|
||||
|| !trackerFrame2.hasData(TrackerFrameData.POSITION)
|
||||
|| trackerFrame2.designation.trackerRole.isEmpty()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ComputedTracker computedTracker1 = skeleton1
|
||||
.getComputedTracker(trackerFrame1.designation.trackerRole.get());
|
||||
if (computedTracker1 == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ComputedTracker computedTracker2 = skeleton2
|
||||
.getComputedTracker(trackerFrame2.designation.trackerRole.get());
|
||||
if (computedTracker2 == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float dist1 = FastMath.abs(computedTracker1.position.distance(trackerFrame1.position));
|
||||
float dist2 = FastMath.abs(computedTracker2.position.distance(trackerFrame2.position));
|
||||
|
||||
offset += FastMath.abs(dist2 - dist1);
|
||||
offsetCount++;
|
||||
}
|
||||
|
||||
return offsetCount > 0 ? offset / offsetCount : 0f;
|
||||
}
|
||||
}
|
||||
38
src/main/java/dev/slimevr/autobone/errors/SlideError.java
Normal file
38
src/main/java/dev/slimevr/autobone/errors/SlideError.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package dev.slimevr.autobone.errors;
|
||||
|
||||
import dev.slimevr.autobone.AutoBoneTrainingStep;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
|
||||
|
||||
// The change in position of the ankle over time
|
||||
public class SlideError implements IAutoBoneError {
|
||||
@Override
|
||||
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException {
|
||||
return getSlideError(trainingStep.getSkeleton1(), trainingStep.getSkeleton2());
|
||||
}
|
||||
|
||||
public static float getSlideError(HumanSkeleton skeleton1, HumanSkeleton skeleton2) {
|
||||
// Calculate and average between both feet
|
||||
return (getSlideError(skeleton1, skeleton2, TrackerRole.LEFT_FOOT)
|
||||
+ getSlideError(skeleton1, skeleton2, TrackerRole.RIGHT_FOOT)) / 2f;
|
||||
}
|
||||
|
||||
public static float getSlideError(
|
||||
HumanSkeleton skeleton1,
|
||||
HumanSkeleton skeleton2,
|
||||
TrackerRole trackerRole
|
||||
) {
|
||||
// Calculate and average between both feet
|
||||
return getSlideError(
|
||||
skeleton1.getComputedTracker(trackerRole),
|
||||
skeleton2.getComputedTracker(trackerRole)
|
||||
);
|
||||
}
|
||||
|
||||
public static float getSlideError(ComputedTracker tracker1, ComputedTracker tracker2) {
|
||||
// Return the midpoint distance
|
||||
return tracker1.position.distance(tracker2.position) / 2f;
|
||||
}
|
||||
}
|
||||
44
src/main/java/dev/slimevr/bridge/Bridge.java
Normal file
44
src/main/java/dev/slimevr/bridge/Bridge.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
|
||||
|
||||
/**
|
||||
* Bridge handles sending and receiving tracker data between SlimeVR and other
|
||||
* systems like VR APIs (SteamVR, OpenXR, etc), apps and protocols (VMC,
|
||||
* WebSocket, TIP). It can create and manage tracker received from the <b>remote
|
||||
* side</b> or send shared <b>local trackers</b> to the other side.
|
||||
*/
|
||||
public interface Bridge {
|
||||
|
||||
@VRServerThread
|
||||
void dataRead();
|
||||
|
||||
@VRServerThread
|
||||
void dataWrite();
|
||||
|
||||
/**
|
||||
* Adds shared tracker to the bridge. Bridge should notify the other side of
|
||||
* this tracker, if it's the type of tracker this bridge serves, and start
|
||||
* sending data each update
|
||||
*
|
||||
* @param tracker
|
||||
*/
|
||||
@VRServerThread
|
||||
void addSharedTracker(ShareableTracker tracker);
|
||||
|
||||
/**
|
||||
* Removes tracker from a bridge. If the other side supports tracker
|
||||
* removal, bridge should notify it and stop sending new data. If it doesn't
|
||||
* support tracker removal, the bridge can either stop sending new data, or
|
||||
* keep sending it if it's available.
|
||||
*
|
||||
* @param tracker
|
||||
*/
|
||||
@VRServerThread
|
||||
void removeSharedTracker(ShareableTracker tracker);
|
||||
|
||||
@VRServerThread
|
||||
void startBridge();
|
||||
}
|
||||
9
src/main/java/dev/slimevr/bridge/BridgeThread.java
Normal file
9
src/main/java/dev/slimevr/bridge/BridgeThread.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
|
||||
@Retention(value = RetentionPolicy.SOURCE)
|
||||
public @interface BridgeThread {
|
||||
}
|
||||
42
src/main/java/dev/slimevr/bridge/OpenVRNativeBridge.java
Normal file
42
src/main/java/dev/slimevr/bridge/OpenVRNativeBridge.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
|
||||
|
||||
public class OpenVRNativeBridge implements Bridge {
|
||||
|
||||
public OpenVRNativeBridge() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataRead() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataWrite() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSharedTracker(ShareableTracker tracker) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSharedTracker(ShareableTracker tracker) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void startBridge() {
|
||||
|
||||
}
|
||||
}
|
||||
7
src/main/java/dev/slimevr/bridge/PipeState.java
Normal file
7
src/main/java/dev/slimevr/bridge/PipeState.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
public enum PipeState {
|
||||
CREATED,
|
||||
OPEN,
|
||||
ERROR
|
||||
}
|
||||
253
src/main/java/dev/slimevr/bridge/ProtobufBridge.java
Normal file
253
src/main/java/dev/slimevr/bridge/ProtobufBridge.java
Normal file
@@ -0,0 +1,253 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import dev.slimevr.Main;
|
||||
import dev.slimevr.bridge.ProtobufMessages.TrackerStatus;
|
||||
import dev.slimevr.bridge.ProtobufMessages.*;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.*;
|
||||
import io.eiren.util.ann.Synchronize;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.collections.FastList;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
|
||||
public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
|
||||
|
||||
@VRServerThread
|
||||
protected final List<ShareableTracker> sharedTrackers = new FastList<>();
|
||||
protected final String bridgeName;
|
||||
private final Vector3f vec1 = new Vector3f();
|
||||
private final Quaternion quat1 = new Quaternion();
|
||||
@ThreadSafe
|
||||
private final Queue<ProtobufMessage> inputQueue = new LinkedBlockingQueue<>();
|
||||
@ThreadSafe
|
||||
private final Queue<ProtobufMessage> outputQueue = new LinkedBlockingQueue<>();
|
||||
@Synchronize("self")
|
||||
private final Map<String, T> remoteTrackersBySerial = new HashMap<>();
|
||||
@Synchronize("self")
|
||||
private final Map<Integer, T> remoteTrackersByTrackerId = new HashMap<>();
|
||||
private final HMDTracker hmd;
|
||||
private boolean hadNewData = false;
|
||||
private T hmdTracker;
|
||||
|
||||
public ProtobufBridge(String bridgeName, HMDTracker hmd) {
|
||||
this.bridgeName = bridgeName;
|
||||
this.hmd = hmd;
|
||||
}
|
||||
|
||||
@BridgeThread
|
||||
protected abstract boolean sendMessageReal(ProtobufMessage message);
|
||||
|
||||
@BridgeThread
|
||||
protected void messageReceived(ProtobufMessage message) {
|
||||
inputQueue.add(message);
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
protected void sendMessage(ProtobufMessage message) {
|
||||
outputQueue.add(message);
|
||||
}
|
||||
|
||||
@BridgeThread
|
||||
protected void updateMessageQueue() {
|
||||
ProtobufMessage message = null;
|
||||
while ((message = outputQueue.poll()) != null) {
|
||||
if (!sendMessageReal(message))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
@Override
|
||||
public void dataRead() {
|
||||
hadNewData = false;
|
||||
ProtobufMessage message = null;
|
||||
while ((message = inputQueue.poll()) != null) {
|
||||
processMessageReceived(message);
|
||||
hadNewData = true;
|
||||
}
|
||||
if (hadNewData && hmdTracker != null) {
|
||||
trackerOverrideUpdate(hmdTracker, hmd);
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void trackerOverrideUpdate(T source, ComputedTracker target) {
|
||||
target.position.set(source.position);
|
||||
target.rotation.set(source.rotation);
|
||||
target.setStatus(source.getStatus());
|
||||
target.dataTick();
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
@Override
|
||||
public void dataWrite() {
|
||||
if (!hadNewData) // Don't write anything if no message were received, we
|
||||
// always process at the
|
||||
// speed of the other side
|
||||
return;
|
||||
for (ShareableTracker tracker : sharedTrackers) {
|
||||
writeTrackerUpdate(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void writeTrackerUpdate(ShareableTracker localTracker) {
|
||||
Position.Builder builder = Position.newBuilder().setTrackerId(localTracker.getTrackerId());
|
||||
if (localTracker.getPosition(vec1)) {
|
||||
builder.setX(vec1.x);
|
||||
builder.setY(vec1.y);
|
||||
builder.setZ(vec1.z);
|
||||
}
|
||||
if (localTracker.getRotation(quat1)) {
|
||||
builder.setQx(quat1.getX());
|
||||
builder.setQy(quat1.getY());
|
||||
builder.setQz(quat1.getZ());
|
||||
builder.setQw(quat1.getW());
|
||||
}
|
||||
sendMessage(ProtobufMessage.newBuilder().setPosition(builder).build());
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void processMessageReceived(ProtobufMessage message) {
|
||||
// if(!message.hasPosition())
|
||||
// LogManager.log.info("[" + bridgeName + "] MSG: " + message);
|
||||
if (message.hasPosition()) {
|
||||
positionReceived(message.getPosition());
|
||||
} else if (message.hasUserAction()) {
|
||||
userActionReceived(message.getUserAction());
|
||||
} else if (message.hasTrackerStatus()) {
|
||||
trackerStatusReceived(message.getTrackerStatus());
|
||||
} else if (message.hasTrackerAdded()) {
|
||||
trackerAddedReceived(message.getTrackerAdded());
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void positionReceived(Position positionMessage) {
|
||||
T tracker = getInternalRemoteTrackerById(positionMessage.getTrackerId());
|
||||
if (tracker != null) {
|
||||
if (positionMessage.hasX())
|
||||
tracker.position
|
||||
.set(positionMessage.getX(), positionMessage.getY(), positionMessage.getZ());
|
||||
tracker.rotation
|
||||
.set(
|
||||
positionMessage.getQx(),
|
||||
positionMessage.getQy(),
|
||||
positionMessage.getQz(),
|
||||
positionMessage.getQw()
|
||||
);
|
||||
tracker.dataTick();
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected abstract T createNewTracker(TrackerAdded trackerAdded);
|
||||
|
||||
@VRServerThread
|
||||
protected void trackerAddedReceived(TrackerAdded trackerAdded) {
|
||||
T tracker = getInternalRemoteTrackerById(trackerAdded.getTrackerId());
|
||||
if (tracker != null) {
|
||||
// TODO reinit?
|
||||
return;
|
||||
}
|
||||
tracker = createNewTracker(trackerAdded);
|
||||
synchronized (remoteTrackersBySerial) {
|
||||
remoteTrackersBySerial.put(tracker.getName(), tracker);
|
||||
}
|
||||
synchronized (remoteTrackersByTrackerId) {
|
||||
remoteTrackersByTrackerId.put(tracker.getTrackerId(), tracker);
|
||||
}
|
||||
if (trackerAdded.getTrackerRole() == TrackerRole.HMD.id) {
|
||||
hmdTracker = tracker;
|
||||
} else {
|
||||
Main.vrServer.registerTracker(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void userActionReceived(UserAction userAction) {
|
||||
switch (userAction.getName()) {
|
||||
case "calibrate":
|
||||
// TODO : Check pose field
|
||||
Main.vrServer.resetTrackers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void trackerStatusReceived(TrackerStatus trackerStatus) {
|
||||
T tracker = getInternalRemoteTrackerById(trackerStatus.getTrackerId());
|
||||
if (tracker != null) {
|
||||
tracker
|
||||
.setStatus(
|
||||
dev.slimevr.vr.trackers.TrackerStatus.getById(trackerStatus.getStatusValue())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
protected T getInternalRemoteTrackerById(int trackerId) {
|
||||
synchronized (remoteTrackersByTrackerId) {
|
||||
return remoteTrackersByTrackerId.get(trackerId);
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void reconnected() {
|
||||
for (ShareableTracker tracker : sharedTrackers) {
|
||||
TrackerAdded.Builder builder = TrackerAdded
|
||||
.newBuilder()
|
||||
.setTrackerId(tracker.getTrackerId())
|
||||
.setTrackerName(tracker.getDescriptiveName())
|
||||
.setTrackerSerial(tracker.getName())
|
||||
.setTrackerRole(tracker.getTrackerRole().id);
|
||||
sendMessage(ProtobufMessage.newBuilder().setTrackerAdded(builder).build());
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
protected void disconnected() {
|
||||
synchronized (remoteTrackersByTrackerId) {
|
||||
Iterator<Entry<Integer, T>> iterator = remoteTrackersByTrackerId.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
iterator
|
||||
.next()
|
||||
.getValue()
|
||||
.setStatus(dev.slimevr.vr.trackers.TrackerStatus.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
if (hmdTracker != null) {
|
||||
hmd.setStatus(dev.slimevr.vr.trackers.TrackerStatus.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
@Override
|
||||
public void addSharedTracker(ShareableTracker tracker) {
|
||||
if (sharedTrackers.contains(tracker))
|
||||
return;
|
||||
sharedTrackers.add(tracker);
|
||||
TrackerAdded.Builder builder = TrackerAdded
|
||||
.newBuilder()
|
||||
.setTrackerId(tracker.getTrackerId())
|
||||
.setTrackerName(tracker.getDescriptiveName())
|
||||
.setTrackerSerial(tracker.getName())
|
||||
.setTrackerRole(tracker.getTrackerRole().id);
|
||||
sendMessage(ProtobufMessage.newBuilder().setTrackerAdded(builder).build());
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
@Override
|
||||
public void removeSharedTracker(ShareableTracker tracker) {
|
||||
sharedTrackers.remove(tracker);
|
||||
// No message can be sent to the remote side, protocol doesn't support
|
||||
// tracker
|
||||
// removal (yet)
|
||||
}
|
||||
}
|
||||
7495
src/main/java/dev/slimevr/bridge/ProtobufMessages.java
Normal file
7495
src/main/java/dev/slimevr/bridge/ProtobufMessages.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,51 @@
|
||||
package io.eiren.vr.bridge;
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
public class VMCBridge extends Thread implements VRBridge {
|
||||
|
||||
|
||||
public class VMCBridge extends Thread implements Bridge {
|
||||
|
||||
public final int readPort;
|
||||
public final int writePort;
|
||||
public final InetAddress writeAddr;
|
||||
|
||||
|
||||
public VMCBridge(int readPort, int writePort, InetAddress writeAddr) {
|
||||
super("Virtual Motion Capture bridge");
|
||||
if(readPort == writePort)
|
||||
if (readPort == writePort)
|
||||
throw new IllegalArgumentException("Read and write port shouldn't be the same!");
|
||||
this.readPort = readPort;
|
||||
this.writePort = writePort;
|
||||
this.writeAddr = writeAddr;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dataRead() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dataWrite() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSharedTracker(ShareableTracker tracker) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSharedTracker(ShareableTracker tracker) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startBridge() {
|
||||
start();
|
||||
}
|
||||
|
||||
}
|
||||
24
src/main/java/dev/slimevr/gui/AbstractComponentListener.java
Normal file
24
src/main/java/dev/slimevr/gui/AbstractComponentListener.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.ComponentListener;
|
||||
|
||||
|
||||
public abstract class AbstractComponentListener implements ComponentListener {
|
||||
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentShown(ComponentEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
}
|
||||
}
|
||||
36
src/main/java/dev/slimevr/gui/AbstractWindowListener.java
Normal file
36
src/main/java/dev/slimevr/gui/AbstractWindowListener.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
|
||||
|
||||
public abstract class AbstractWindowListener implements WindowListener {
|
||||
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowIconified(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeiconified(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowActivated(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeactivated(WindowEvent e) {
|
||||
}
|
||||
}
|
||||
219
src/main/java/dev/slimevr/gui/AutoBoneWindow.java
Normal file
219
src/main/java/dev/slimevr/gui/AutoBoneWindow.java
Normal file
@@ -0,0 +1,219 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.ScrollPaneConstants;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.EnumMap;
|
||||
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.autobone.AutoBoneListener;
|
||||
import dev.slimevr.autobone.AutoBoneProcessType;
|
||||
import dev.slimevr.autobone.AutoBone.Epoch;
|
||||
import dev.slimevr.gui.swing.EJBox;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
|
||||
|
||||
public class AutoBoneWindow extends JFrame implements AutoBoneListener {
|
||||
|
||||
private EJBox pane;
|
||||
|
||||
private final transient VRServer server;
|
||||
private final transient SkeletonConfigGUI skeletonConfig;
|
||||
|
||||
private JButton saveRecordingButton;
|
||||
private JButton applyButton;
|
||||
|
||||
private JLabel processLabel;
|
||||
private JLabel lengthsLabel;
|
||||
|
||||
public AutoBoneWindow(VRServer server, SkeletonConfigGUI skeletonConfig) {
|
||||
super("Skeleton Auto-Configuration");
|
||||
|
||||
this.server = server;
|
||||
this.skeletonConfig = skeletonConfig;
|
||||
|
||||
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS));
|
||||
add(
|
||||
new JScrollPane(
|
||||
pane = new EJBox(BoxLayout.PAGE_AXIS),
|
||||
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
|
||||
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
|
||||
)
|
||||
);
|
||||
|
||||
server.getAutoBoneHandler().addListener(this);
|
||||
|
||||
build();
|
||||
}
|
||||
|
||||
|
||||
@AWTThread
|
||||
private void build() {
|
||||
pane.add(new EJBox(BoxLayout.LINE_AXIS) {
|
||||
{
|
||||
setBorder(new EmptyBorder(i(5)));
|
||||
add(new JButton("Start Recording") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.getAutoBoneHandler().startRecording();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add(saveRecordingButton = new JButton("Save Recording") {
|
||||
{
|
||||
setEnabled(false);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.getAutoBoneHandler().saveRecording();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add(new JButton("Auto-Adjust") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.getAutoBoneHandler().processRecording();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add(applyButton = new JButton("Apply Values") {
|
||||
{
|
||||
setEnabled(false);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
server.getAutoBoneHandler().applyValues();
|
||||
// Update GUI values after applying
|
||||
skeletonConfig.refreshAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
pane.add(new EJBox(BoxLayout.LINE_AXIS) {
|
||||
{
|
||||
setBorder(new EmptyBorder(i(5)));
|
||||
add(processLabel = new JLabel("Processing has not been started..."));
|
||||
}
|
||||
});
|
||||
|
||||
pane.add(new EJBox(BoxLayout.LINE_AXIS) {
|
||||
{
|
||||
setBorder(new EmptyBorder(i(5)));
|
||||
add(lengthsLabel = new JLabel("No config changes"));
|
||||
}
|
||||
});
|
||||
|
||||
// Pack and display
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneProcessStatus(
|
||||
AutoBoneProcessType processType,
|
||||
String message,
|
||||
long current,
|
||||
long total,
|
||||
boolean completed,
|
||||
boolean success
|
||||
) {
|
||||
if (message != null) {
|
||||
if (total == 0) {
|
||||
processLabel.setText(String.format("%s: %s", processType.name(), message));
|
||||
} else {
|
||||
processLabel
|
||||
.setText(
|
||||
String
|
||||
.format(
|
||||
"%s (%d/%d) [%.2f%%]: %s",
|
||||
processType.name(),
|
||||
current,
|
||||
total,
|
||||
(current / (double) total) * 100.0,
|
||||
message
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (total != 0) {
|
||||
processLabel
|
||||
.setText(
|
||||
String
|
||||
.format(
|
||||
"%s (%d/%d) [%.2f%%]",
|
||||
processType.name(),
|
||||
current,
|
||||
total,
|
||||
(current / (double) total) * 100.0
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneRecordingEnd(PoseFrames recording) {
|
||||
saveRecordingButton.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEpoch(Epoch epoch) {
|
||||
processLabel
|
||||
.setText(
|
||||
String
|
||||
.format(
|
||||
"PROCESS: Epoch %d/%d (%.2f%%) Error: %.4f",
|
||||
epoch.epoch,
|
||||
epoch.totalEpochs,
|
||||
(epoch.epoch / (double) epoch.totalEpochs) * 100.0,
|
||||
epoch.epochError
|
||||
)
|
||||
);
|
||||
lengthsLabel.setText(server.getAutoBoneHandler().getLengthsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEnd(EnumMap<SkeletonConfigValue, Float> configValues) {
|
||||
applyButton.setEnabled(true);
|
||||
}
|
||||
}
|
||||
92
src/main/java/dev/slimevr/gui/CalibrationWindow.java
Normal file
92
src/main/java/dev/slimevr/gui/CalibrationWindow.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import dev.slimevr.gui.swing.EJBox;
|
||||
import dev.slimevr.vr.trackers.CalibratingTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
|
||||
public class CalibrationWindow extends JFrame {
|
||||
|
||||
public final Tracker tracker;
|
||||
private JTextArea currentCalibration;
|
||||
private JTextArea newCalibration;
|
||||
private JButton calibrateButton;
|
||||
|
||||
public CalibrationWindow(Tracker t) {
|
||||
super(t.getName() + " calibration");
|
||||
this.tracker = t;
|
||||
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.LINE_AXIS));
|
||||
|
||||
build();
|
||||
}
|
||||
|
||||
public void currentCalibrationReceived(String str) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
currentCalibration.setText(str);
|
||||
pack();
|
||||
});
|
||||
}
|
||||
|
||||
public void newCalibrationReceived(String str) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
calibrateButton.setText("Calibrate");
|
||||
newCalibration.setText(str);
|
||||
pack();
|
||||
});
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
private void build() {
|
||||
Container pane = getContentPane();
|
||||
|
||||
pane.add(calibrateButton = new JButton("Calibrate") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
calibrateButton.setText("Calibrating...");
|
||||
((CalibratingTracker) tracker)
|
||||
.startCalibration(CalibrationWindow.this::newCalibrationReceived);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
pane.add(new EJBox(BoxLayout.PAGE_AXIS) {
|
||||
{
|
||||
setBorder(new EmptyBorder(i(5)));
|
||||
add(new JLabel("Current calibration"));
|
||||
add(currentCalibration = new JTextArea(10, 25));
|
||||
|
||||
((CalibratingTracker) tracker)
|
||||
.requestCalibrationData(CalibrationWindow.this::currentCalibrationReceived);
|
||||
}
|
||||
});
|
||||
pane.add(new EJBox(BoxLayout.PAGE_AXIS) {
|
||||
{
|
||||
setBorder(new EmptyBorder(i(5)));
|
||||
add(new JLabel("New calibration"));
|
||||
add(newCalibration = new JTextArea(10, 25));
|
||||
}
|
||||
});
|
||||
|
||||
// Pack and display
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
setVisible(true);
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
toFront();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
70
src/main/java/dev/slimevr/gui/Keybinding.java
Normal file
70
src/main/java/dev/slimevr/gui/Keybinding.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import com.melloware.jintellitype.HotkeyListener;
|
||||
import com.melloware.jintellitype.JIntellitype;
|
||||
import dev.slimevr.VRServer;
|
||||
import io.eiren.util.OperatingSystem;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
|
||||
public class Keybinding implements HotkeyListener {
|
||||
private static final int RESET = 1;
|
||||
private static final int QUICK_RESET = 2;
|
||||
public final VRServer server;
|
||||
|
||||
@AWTThread
|
||||
public Keybinding(VRServer server) {
|
||||
this.server = server;
|
||||
|
||||
if (OperatingSystem.getCurrentPlatform() != OperatingSystem.WINDOWS) {
|
||||
LogManager
|
||||
.info(
|
||||
"[Keybinding] Currently only supported on Windows. Keybindings will be disabled."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (JIntellitype.getInstance() instanceof JIntellitype) {
|
||||
JIntellitype.getInstance().addHotKeyListener(this);
|
||||
|
||||
String resetBinding = this.server.config.getString("keybindings.reset");
|
||||
if (resetBinding == null) {
|
||||
resetBinding = "CTRL+ALT+SHIFT+Y";
|
||||
this.server.config.setProperty("keybindings.reset", resetBinding);
|
||||
}
|
||||
JIntellitype.getInstance().registerHotKey(RESET, resetBinding);
|
||||
LogManager.info("[Keybinding] Bound reset to " + resetBinding);
|
||||
|
||||
String quickResetBinding = this.server.config.getString("keybindings.quickReset");
|
||||
if (quickResetBinding == null) {
|
||||
quickResetBinding = "CTRL+ALT+SHIFT+U";
|
||||
this.server.config.setProperty("keybindings.quickReset", quickResetBinding);
|
||||
}
|
||||
JIntellitype.getInstance().registerHotKey(QUICK_RESET, quickResetBinding);
|
||||
LogManager.info("[Keybinding] Bound quick reset to " + quickResetBinding);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
LogManager
|
||||
.info(
|
||||
"[Keybinding] JIntellitype initialization failed. Keybindings will be disabled. Try restarting your computer."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
@Override
|
||||
public void onHotKey(int identifier) {
|
||||
switch (identifier) {
|
||||
case RESET:
|
||||
LogManager.info("[Keybinding] Reset pressed");
|
||||
server.resetTrackers();
|
||||
break;
|
||||
case QUICK_RESET:
|
||||
LogManager.info("[Keybinding] Quick reset pressed");
|
||||
server.resetTrackersYaw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,33 @@
|
||||
package io.eiren.gui;
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.*;
|
||||
import java.text.AttributedCharacterIterator.Attribute;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class ScalableFont extends Font {
|
||||
|
||||
|
||||
protected float scale = 1.0f;
|
||||
|
||||
|
||||
protected int initSize;
|
||||
protected float initPointSize;
|
||||
|
||||
|
||||
public ScalableFont(Map<? extends Attribute, ?> attributes) {
|
||||
super(attributes);
|
||||
|
||||
|
||||
this.initSize = this.size;
|
||||
this.initPointSize = this.pointSize;
|
||||
}
|
||||
|
||||
|
||||
public ScalableFont(Font font) {
|
||||
super(font);
|
||||
|
||||
if(font instanceof ScalableFont) {
|
||||
ScalableFont sourceFont = (ScalableFont)font;
|
||||
|
||||
|
||||
if (font instanceof ScalableFont) {
|
||||
ScalableFont sourceFont = (ScalableFont) font;
|
||||
|
||||
this.initSize = sourceFont.getInitSize();
|
||||
this.initPointSize = sourceFont.getInitSize2D();
|
||||
|
||||
|
||||
this.size = this.initSize;
|
||||
this.pointSize = this.initPointSize;
|
||||
} else {
|
||||
@@ -34,58 +35,57 @@ public class ScalableFont extends Font {
|
||||
this.initPointSize = this.pointSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public ScalableFont(Font font, float scale) {
|
||||
super(font);
|
||||
|
||||
if(font instanceof ScalableFont) {
|
||||
ScalableFont sourceFont = (ScalableFont)font;
|
||||
|
||||
|
||||
if (font instanceof ScalableFont) {
|
||||
ScalableFont sourceFont = (ScalableFont) font;
|
||||
|
||||
this.initSize = sourceFont.getInitSize();
|
||||
this.initPointSize = sourceFont.getInitSize2D();
|
||||
} else {
|
||||
this.initSize = this.size;
|
||||
this.initPointSize = this.pointSize;
|
||||
}
|
||||
|
||||
|
||||
setScale(scale);
|
||||
}
|
||||
|
||||
|
||||
public ScalableFont(String name, int style, int size) {
|
||||
super(name, style, size);
|
||||
|
||||
|
||||
this.initSize = this.size;
|
||||
this.initPointSize = this.pointSize;
|
||||
}
|
||||
|
||||
|
||||
public ScalableFont(String name, int style, int size, float scale) {
|
||||
super(name, style, size);
|
||||
|
||||
|
||||
this.initSize = this.size;
|
||||
this.initPointSize = this.pointSize;
|
||||
|
||||
|
||||
setScale(scale);
|
||||
}
|
||||
|
||||
|
||||
public int getInitSize() {
|
||||
return initSize;
|
||||
}
|
||||
|
||||
|
||||
public float getInitSize2D() {
|
||||
return initPointSize;
|
||||
}
|
||||
|
||||
|
||||
public float getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
|
||||
private void setScale(float scale) {
|
||||
this.scale = scale;
|
||||
|
||||
|
||||
float newPointSize = initPointSize * scale;
|
||||
|
||||
this.size = (int)(newPointSize + 0.5);
|
||||
|
||||
this.size = (int) (newPointSize + 0.5);
|
||||
this.pointSize = newPointSize;
|
||||
}
|
||||
|
||||
}
|
||||
201
src/main/java/dev/slimevr/gui/SkeletonConfigGUI.java
Normal file
201
src/main/java/dev/slimevr/gui/SkeletonConfigGUI.java
Normal file
@@ -0,0 +1,201 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.ButtonTimer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class SkeletonConfigGUI extends EJBagNoStretch {
|
||||
|
||||
private final VRServer server;
|
||||
private final VRServerGUI gui;
|
||||
private final AutoBoneWindow autoBone;
|
||||
private final Map<SkeletonConfigValue, SkeletonLabel> labels = new HashMap<>();
|
||||
private JCheckBox precisionCb;
|
||||
|
||||
public SkeletonConfigGUI(VRServer server, VRServerGUI gui) {
|
||||
super(false, true);
|
||||
this.server = server;
|
||||
this.gui = gui;
|
||||
this.autoBone = new AutoBoneWindow(server, this);
|
||||
|
||||
setAlignmentY(TOP_ALIGNMENT);
|
||||
server.humanPoseProcessor.addSkeletonUpdatedCallback(this::skeletonUpdated);
|
||||
skeletonUpdated(null);
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void skeletonUpdated(Skeleton newSkeleton) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
removeAll();
|
||||
|
||||
int row = 0;
|
||||
|
||||
add(new TimedResetButton("Reset All"), s(c(1, row, 2), 3, 1));
|
||||
add(new JButton("Auto") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
autoBone.setVisible(true);
|
||||
autoBone.toFront();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, s(c(4, row, 2), 3, 1));
|
||||
|
||||
add(precisionCb = new JCheckBox("Precision adjust"), c(0, row, 2));
|
||||
precisionCb.setSelected(false);
|
||||
|
||||
row++;
|
||||
|
||||
for (SkeletonConfigValue config : SkeletonConfigValue.values) {
|
||||
add(new JLabel(config.label), c(0, row, 2));
|
||||
add(new AdjButton("+", config, false), c(1, row, 2));
|
||||
add(new SkeletonLabel(config), c(2, row, 2));
|
||||
add(new AdjButton("-", config, true), c(3, row, 2));
|
||||
|
||||
// Only use a timer on configs that need time to get into
|
||||
// position for
|
||||
switch (config) {
|
||||
case TORSO:
|
||||
case LEGS_LENGTH:
|
||||
add(new TimedResetButton("Reset", config), c(4, row, 2));
|
||||
break;
|
||||
default:
|
||||
add(new ResetButton("Reset", config), c(4, row, 2));
|
||||
break;
|
||||
}
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
gui.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
float proportionsIncrement(Boolean negative) {
|
||||
float increment = 0.01f;
|
||||
if (negative)
|
||||
increment = -0.01f;
|
||||
if (precisionCb.isSelected())
|
||||
increment /= 2f;
|
||||
return increment;
|
||||
}
|
||||
|
||||
String getBoneLengthString(SkeletonConfigValue joint) { // Rounded to the
|
||||
// nearest 0.5
|
||||
return (StringUtils
|
||||
.prettyNumber(
|
||||
Math.round(server.humanPoseProcessor.getSkeletonConfig(joint) * 200) / 2.0f,
|
||||
1
|
||||
));
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void refreshAll() {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
labels.forEach((joint, label) -> {
|
||||
label.setText(getBoneLengthString(joint));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void change(SkeletonConfigValue joint, float diff) {
|
||||
// Update config value
|
||||
float current = server.humanPoseProcessor.getSkeletonConfig(joint);
|
||||
server.humanPoseProcessor.setSkeletonConfig(joint, current + diff);
|
||||
server.humanPoseProcessor.getSkeletonConfig().saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
|
||||
// Update GUI
|
||||
labels.get(joint).setText(getBoneLengthString(joint));
|
||||
}
|
||||
|
||||
private void reset(SkeletonConfigValue joint) {
|
||||
// Update config value
|
||||
server.humanPoseProcessor.resetSkeletonConfig(joint);
|
||||
server.humanPoseProcessor.getSkeletonConfig().saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
|
||||
// Update GUI
|
||||
labels.get(joint).setText(getBoneLengthString(joint));
|
||||
}
|
||||
|
||||
private void resetAll() {
|
||||
// Update config value
|
||||
server.humanPoseProcessor.resetAllSkeletonConfigs();
|
||||
server.humanPoseProcessor.getSkeletonConfig().saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
|
||||
// Update GUI
|
||||
refreshAll();
|
||||
}
|
||||
|
||||
private class SkeletonLabel extends JLabel {
|
||||
|
||||
public SkeletonLabel(SkeletonConfigValue joint) {
|
||||
super(getBoneLengthString(joint));
|
||||
labels.put(joint, this);
|
||||
}
|
||||
}
|
||||
|
||||
private class AdjButton extends JButton {
|
||||
|
||||
public AdjButton(String text, SkeletonConfigValue joint, boolean negative) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
change(joint, proportionsIncrement(negative));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class ResetButton extends JButton {
|
||||
|
||||
public ResetButton(String text, SkeletonConfigValue joint) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
reset(joint);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class TimedResetButton extends JButton {
|
||||
|
||||
public TimedResetButton(String text, SkeletonConfigValue joint) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
ButtonTimer.runTimer(TimedResetButton.this, 3, text, () -> reset(joint));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public TimedResetButton(String text) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
ButtonTimer.runTimer(TimedResetButton.this, 3, text, () -> resetAll());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +1,46 @@
|
||||
package io.eiren.gui;
|
||||
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
import io.eiren.vr.processor.TransformNode;
|
||||
|
||||
public class SkeletonList extends EJBag {
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class SkeletonList extends EJBagNoStretch {
|
||||
|
||||
private static final long UPDATE_DELAY = 50;
|
||||
private final VRServerGUI gui;
|
||||
private final List<NodeStatus> nodes = new FastList<>();
|
||||
Quaternion q = new Quaternion();
|
||||
Vector3f v = new Vector3f();
|
||||
float[] angles = new float[3];
|
||||
|
||||
private final VRServer server;
|
||||
private final VRServerGUI gui;
|
||||
private final List<NodeStatus> nodes = new FastList<>();
|
||||
|
||||
private long lastUpdate = 0;
|
||||
|
||||
public SkeletonList(VRServer server, VRServerGUI gui) {
|
||||
super();
|
||||
this.server = server;
|
||||
super(false, true);
|
||||
this.gui = gui;
|
||||
|
||||
setAlignmentY(TOP_ALIGNMENT);
|
||||
server.addSkeletonUpdatedCallback(this::skeletonUpdated);
|
||||
}
|
||||
|
||||
|
||||
@ThreadSafe
|
||||
public void skeletonUpdated(HumanSkeleton newSkeleton) {
|
||||
public void skeletonUpdated(Skeleton newSkeleton) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
removeAll();
|
||||
nodes.clear();
|
||||
|
||||
|
||||
add(new JLabel("Joint"), c(0, 0, 2));
|
||||
add(new JLabel("X"), c(1, 0, 2));
|
||||
add(new JLabel("Y"), c(2, 0, 2));
|
||||
@@ -49,25 +48,29 @@ public class SkeletonList extends EJBag {
|
||||
add(new JLabel("Pitch"), c(4, 0, 2));
|
||||
add(new JLabel("Yaw"), c(5, 0, 2));
|
||||
add(new JLabel("Roll"), c(6, 0, 2));
|
||||
|
||||
newSkeleton.getRootNode().depthFirstTraversal((node) -> {
|
||||
int n = nodes.size();
|
||||
nodes.add(new NodeStatus(node, n + 1));
|
||||
});
|
||||
|
||||
|
||||
|
||||
TransformNode[] allNodes = newSkeleton.getAllNodes();
|
||||
|
||||
for (int i = 0; i < allNodes.length; i++) {
|
||||
nodes.add(new NodeStatus(allNodes[i], i + 1));
|
||||
}
|
||||
|
||||
gui.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void updateBones() {
|
||||
if (lastUpdate + UPDATE_DELAY > System.currentTimeMillis())
|
||||
return;
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
for(int i = 0; i < nodes.size(); ++i)
|
||||
nodes.get(i).update();
|
||||
for (NodeStatus node : nodes) {
|
||||
node.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private class NodeStatus {
|
||||
|
||||
TransformNode n;
|
||||
@@ -77,7 +80,7 @@ public class SkeletonList extends EJBag {
|
||||
JLabel a1;
|
||||
JLabel a2;
|
||||
JLabel a3;
|
||||
|
||||
|
||||
public NodeStatus(TransformNode node, int n) {
|
||||
this.n = node;
|
||||
add(new JLabel(node.getName()), c(0, n, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
@@ -88,12 +91,12 @@ public class SkeletonList extends EJBag {
|
||||
add(a2 = new JLabel("0"), c(5, n, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(a3 = new JLabel("0"), c(6, n, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
}
|
||||
|
||||
|
||||
public void update() {
|
||||
n.worldTransform.getTranslation(v);
|
||||
n.worldTransform.getRotation(q);
|
||||
q.toAngles(angles);
|
||||
|
||||
|
||||
x.setText(StringUtils.prettyNumber(v.x, 2));
|
||||
y.setText(StringUtils.prettyNumber(v.y, 2));
|
||||
z.setText(StringUtils.prettyNumber(v.z, 2));
|
||||
104
src/main/java/dev/slimevr/gui/TrackersFiltersGUI.java
Normal file
104
src/main/java/dev/slimevr/gui/TrackersFiltersGUI.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.vr.trackers.TrackerFilters;
|
||||
import io.eiren.util.StringUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
|
||||
|
||||
public class TrackersFiltersGUI extends EJBagNoStretch {
|
||||
|
||||
private final VRServer server;
|
||||
private final JLabel amountLabel;
|
||||
private final JLabel ticksLabel;
|
||||
TrackerFilters filterType;
|
||||
float filterAmount;
|
||||
int filterTicks;
|
||||
|
||||
public TrackersFiltersGUI(VRServer server, VRServerGUI gui) {
|
||||
|
||||
super(false, true);
|
||||
this.server = server;
|
||||
|
||||
int row = 0;
|
||||
|
||||
setAlignmentY(TOP_ALIGNMENT);
|
||||
add(Box.createVerticalStrut(10));
|
||||
filterType = TrackerFilters.valueOf(server.config.getString("filters.type", "NONE"));
|
||||
|
||||
JComboBox<String> filterSelect;
|
||||
add(filterSelect = new JComboBox<>(), s(c(0, row, 2), 4, 1));
|
||||
|
||||
for (TrackerFilters f : TrackerFilters.values()) {
|
||||
filterSelect.addItem(f.name());
|
||||
}
|
||||
filterSelect.setSelectedItem(filterType.toString());
|
||||
|
||||
filterSelect.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
filterType = TrackerFilters.valueOf(filterSelect.getSelectedItem().toString());
|
||||
server.updateTrackersFilters(filterType, filterAmount, filterTicks);
|
||||
}
|
||||
});
|
||||
add(Box.createVerticalStrut(40));
|
||||
row++;
|
||||
|
||||
filterAmount = FastMath.clamp(server.config.getFloat("filters.amount", 0.3f), 0, 1);
|
||||
|
||||
add(new JLabel("Intensity"), c(0, row, 2));
|
||||
add(new AdjButton("+", 0, false), c(1, row, 2));
|
||||
add(
|
||||
amountLabel = new JLabel(StringUtils.prettyNumber(filterAmount * 100f) + "%"),
|
||||
c(2, row, 2)
|
||||
);
|
||||
add(new AdjButton("-", 0, true), c(3, row, 2));
|
||||
row++;
|
||||
filterTicks = (int) FastMath.clamp(server.config.getInt("filters.tickCount", 1), 0, 80);
|
||||
|
||||
add(new JLabel("Ticks"), c(0, row, 2));
|
||||
add(new AdjButton("+", 1, false), c(1, row, 2));
|
||||
add(ticksLabel = new JLabel(StringUtils.prettyNumber(filterTicks)), c(2, row, 2));
|
||||
add(new AdjButton("-", 1, true), c(3, row, 2));
|
||||
}
|
||||
|
||||
void adjustValues(int cat, boolean neg) {
|
||||
if (cat == 0) {
|
||||
if (neg) {
|
||||
filterAmount = FastMath.clamp(filterAmount - 0.1f, 0, 1);
|
||||
} else {
|
||||
filterAmount = FastMath.clamp(filterAmount + 0.1f, 0, 1);
|
||||
}
|
||||
amountLabel.setText((StringUtils.prettyNumber(filterAmount * 100f)) + "%");
|
||||
} else if (cat == 1) {
|
||||
if (neg) {
|
||||
filterTicks = (int) FastMath.clamp(filterTicks - 1, 0, 80);
|
||||
} else {
|
||||
filterTicks = (int) FastMath.clamp(filterTicks + 1, 0, 80);
|
||||
}
|
||||
ticksLabel.setText((StringUtils.prettyNumber(filterTicks)));
|
||||
}
|
||||
|
||||
server.updateTrackersFilters(filterType, filterAmount, filterTicks);
|
||||
}
|
||||
|
||||
private class AdjButton extends JButton {
|
||||
|
||||
public AdjButton(String text, int category, boolean neg) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
adjustValues(category, neg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
530
src/main/java/dev/slimevr/gui/TrackersList.java
Normal file
530
src/main/java/dev/slimevr/gui/TrackersList.java
Normal file
@@ -0,0 +1,530 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.gui.swing.EJBoxNoStretch;
|
||||
import dev.slimevr.vr.trackers.*;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.collections.FastList;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class TrackersList extends EJBoxNoStretch {
|
||||
|
||||
private static final long UPDATE_DELAY = 50;
|
||||
private final VRServer server;
|
||||
private final VRServerGUI gui;
|
||||
private final List<TrackerPanel> trackers = new FastList<>();
|
||||
Quaternion q = new Quaternion();
|
||||
Vector3f v = new Vector3f();
|
||||
float[] angles = new float[3];
|
||||
private long lastUpdate = 0;
|
||||
private boolean debug = false;
|
||||
|
||||
public TrackersList(VRServer server, VRServerGUI gui) {
|
||||
super(BoxLayout.PAGE_AXIS, false, true);
|
||||
this.server = server;
|
||||
this.gui = gui;
|
||||
|
||||
setAlignmentY(TOP_ALIGNMENT);
|
||||
|
||||
server.addNewTrackerConsumer(this::newTrackerAdded);
|
||||
}
|
||||
|
||||
private static int getTrackerSort(Tracker t) {
|
||||
Tracker tracker = t.get();
|
||||
if (tracker instanceof IMUTracker)
|
||||
return 0;
|
||||
if (tracker instanceof HMDTracker)
|
||||
return 100;
|
||||
if (tracker instanceof ComputedTracker)
|
||||
return 200;
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
public void setDebug(boolean debug) {
|
||||
this.debug = debug;
|
||||
build();
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
private void build() {
|
||||
removeAll();
|
||||
|
||||
trackers.sort((tr1, tr2) -> getTrackerSort(tr1.t) - getTrackerSort(tr2.t));
|
||||
|
||||
Class<? extends Tracker> currentClass = null;
|
||||
|
||||
EJBoxNoStretch line = null;
|
||||
boolean first = true;
|
||||
|
||||
for (TrackerPanel tr : trackers) {
|
||||
Tracker t = tr.t.get();
|
||||
if (currentClass != t.getClass()) {
|
||||
currentClass = t.getClass();
|
||||
if (line != null)
|
||||
line.add(Box.createHorizontalGlue());
|
||||
line = null;
|
||||
line = new EJBoxNoStretch(BoxLayout.LINE_AXIS, false, true);
|
||||
line.add(Box.createHorizontalGlue());
|
||||
JLabel nameLabel;
|
||||
line.add(nameLabel = new JLabel(currentClass.getSimpleName()));
|
||||
nameLabel.setFont(nameLabel.getFont().deriveFont(Font.BOLD));
|
||||
line.add(Box.createHorizontalGlue());
|
||||
add(line);
|
||||
line = null;
|
||||
}
|
||||
|
||||
if (line == null) {
|
||||
line = new EJBoxNoStretch(BoxLayout.LINE_AXIS, false, true);
|
||||
add(Box.createVerticalStrut(3));
|
||||
add(line);
|
||||
first = true;
|
||||
} else {
|
||||
line.add(Box.createHorizontalStrut(3));
|
||||
first = false;
|
||||
}
|
||||
tr.build();
|
||||
line.add(tr);
|
||||
if (!first)
|
||||
line = null;
|
||||
}
|
||||
validate();
|
||||
gui.refresh();
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void updateTrackers() {
|
||||
if (lastUpdate + UPDATE_DELAY > System.currentTimeMillis())
|
||||
return;
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
for (TrackerPanel tr : trackers) {
|
||||
tr.update();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void newTrackerAdded(Tracker t) {
|
||||
java.awt.EventQueue.invokeLater(() -> {
|
||||
trackers.add(new TrackerPanel(t));
|
||||
build();
|
||||
});
|
||||
}
|
||||
|
||||
private class TrackerPanel extends EJBagNoStretch {
|
||||
|
||||
final Tracker t;
|
||||
JLabel position;
|
||||
JLabel rotation;
|
||||
JLabel status;
|
||||
JLabel tps;
|
||||
JLabel bat;
|
||||
JLabel ping;
|
||||
JLabel raw;
|
||||
JLabel rawMag;
|
||||
JLabel calibration;
|
||||
JLabel magAccuracy;
|
||||
JLabel adj;
|
||||
JLabel adjYaw;
|
||||
JLabel adjGyro;
|
||||
JLabel correction;
|
||||
JLabel signalStrength;
|
||||
JLabel rotQuat;
|
||||
JLabel rotAdj;
|
||||
JLabel temperature;
|
||||
|
||||
@AWTThread
|
||||
public TrackerPanel(Tracker t) {
|
||||
super(false, true);
|
||||
|
||||
this.t = t;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@AWTThread
|
||||
public TrackerPanel build() {
|
||||
int row = 0;
|
||||
|
||||
Tracker tracker = t.get();
|
||||
removeAll();
|
||||
JLabel nameLabel;
|
||||
add(
|
||||
nameLabel = new JLabel(t.getDescriptiveName()),
|
||||
s(c(0, row, 2, GridBagConstraints.FIRST_LINE_START), 4, 1)
|
||||
);
|
||||
nameLabel.setFont(nameLabel.getFont().deriveFont(Font.BOLD));
|
||||
row++;
|
||||
|
||||
if (t.userEditable()) {
|
||||
TrackerConfig cfg = server.getTrackerConfig(t);
|
||||
JComboBox<String> desSelect;
|
||||
add(
|
||||
desSelect = new JComboBox<>(),
|
||||
s(c(0, row, 2, GridBagConstraints.FIRST_LINE_START), 2, 1)
|
||||
);
|
||||
desSelect.addItem("NONE");
|
||||
for (TrackerPosition p : TrackerPosition.values) {
|
||||
desSelect.addItem(p.name());
|
||||
}
|
||||
if (cfg.designation != null) {
|
||||
TrackerPosition
|
||||
.getByDesignation(cfg.designation)
|
||||
.ifPresentOrElse(
|
||||
trackerPosition -> desSelect.setSelectedItem(trackerPosition.name()),
|
||||
() -> desSelect.setSelectedItem("NONE")
|
||||
);
|
||||
}
|
||||
desSelect.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (desSelect.getSelectedItem() == "NONE") {
|
||||
t.setBodyPosition(null);
|
||||
} else {
|
||||
TrackerPosition p = TrackerPosition
|
||||
.valueOf(String.valueOf(desSelect.getSelectedItem()));
|
||||
t.setBodyPosition(p);
|
||||
}
|
||||
server.trackerUpdated(t);
|
||||
}
|
||||
});
|
||||
if (tracker instanceof IMUTracker) {
|
||||
IMUTracker imu = (IMUTracker) tracker;
|
||||
JComboBox<String> mountSelect;
|
||||
add(
|
||||
mountSelect = new JComboBox<>(),
|
||||
s(c(2, row, 2, GridBagConstraints.FIRST_LINE_START), 2, 1)
|
||||
);
|
||||
for (TrackerMountingRotation p : TrackerMountingRotation.values) {
|
||||
mountSelect.addItem(p.name());
|
||||
}
|
||||
|
||||
TrackerMountingRotation selected = TrackerMountingRotation
|
||||
.fromQuaternion(imu.getMountingRotation());
|
||||
mountSelect
|
||||
.setSelectedItem(
|
||||
Objects
|
||||
.requireNonNullElse(selected, TrackerMountingRotation.BACK)
|
||||
.name()
|
||||
);
|
||||
mountSelect.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
TrackerMountingRotation tr = TrackerMountingRotation
|
||||
.valueOf(String.valueOf(mountSelect.getSelectedItem()));
|
||||
imu.setMountingRotation(tr.quaternion);
|
||||
server.trackerUpdated(t);
|
||||
}
|
||||
});
|
||||
}
|
||||
row++;
|
||||
}
|
||||
if (t.hasRotation())
|
||||
add(new JLabel("Rotation"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
if (t.hasPosition())
|
||||
add(new JLabel("Position"), c(1, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(new JLabel("TPS"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
if (tracker instanceof IMUTracker) {
|
||||
add(new JLabel("Ping"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(new JLabel("Signal"), c(4, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
}
|
||||
row++;
|
||||
if (t.hasRotation())
|
||||
add(
|
||||
rotation = new JLabel("0 0 0"),
|
||||
c(0, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
if (t.hasPosition())
|
||||
add(
|
||||
position = new JLabel("0 0 0"),
|
||||
c(1, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
if (tracker instanceof IMUTracker) {
|
||||
add(ping = new JLabel(""), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
signalStrength = new JLabel(""),
|
||||
c(4, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
}
|
||||
if (tracker instanceof TrackerWithTPS) {
|
||||
add(tps = new JLabel("0"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
} else {
|
||||
add(new JLabel(""), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
}
|
||||
row++;
|
||||
add(new JLabel("Status:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
status = new JLabel(t.getStatus().toString().toLowerCase()),
|
||||
c(1, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
if (tracker instanceof TrackerWithBattery) {
|
||||
add(new JLabel("Battery:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(bat = new JLabel("0"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
}
|
||||
row++;
|
||||
add(new JLabel("Raw:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
raw = new JLabel("0 0 0"),
|
||||
s(c(1, row, 2, GridBagConstraints.FIRST_LINE_START), 3, 1)
|
||||
);
|
||||
|
||||
if (debug && tracker instanceof IMUTracker) {
|
||||
add(new JLabel("Quat:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(rotQuat = new JLabel("0"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
}
|
||||
row++;
|
||||
|
||||
if (debug && tracker instanceof IMUTracker) {
|
||||
add(new JLabel("Raw mag:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
rawMag = new JLabel("0 0 0"),
|
||||
s(c(1, row, 2, GridBagConstraints.FIRST_LINE_START), 3, 1)
|
||||
);
|
||||
add(new JLabel("Gyro fix:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
new JLabel(String.format("0x%8x", tracker.hashCode())),
|
||||
s(c(3, row, 2, GridBagConstraints.FIRST_LINE_START), 3, 1)
|
||||
);
|
||||
row++;
|
||||
add(new JLabel("Cal:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
calibration = new JLabel("0"),
|
||||
c(1, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
add(new JLabel("Mag acc:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
magAccuracy = new JLabel("0°"),
|
||||
c(3, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
row++;
|
||||
add(new JLabel("Correction:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
correction = new JLabel("0 0 0"),
|
||||
s(c(1, row, 2, GridBagConstraints.FIRST_LINE_START), 3, 1)
|
||||
);
|
||||
add(new JLabel("RotAdj:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(rotAdj = new JLabel("0"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
row++;
|
||||
}
|
||||
|
||||
if (debug && t instanceof ReferenceAdjustedTracker) {
|
||||
add(new JLabel("Att fix:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(adj = new JLabel("0 0 0 0"), c(1, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(new JLabel("Yaw Fix:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
adjYaw = new JLabel("0 0 0 0"),
|
||||
c(3, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
row++;
|
||||
add(new JLabel("Gyro Fix:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
adjGyro = new JLabel("0 0 0 0"),
|
||||
c(1, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
add(new JLabel("Temp:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
|
||||
add(
|
||||
temperature = new JLabel("?"),
|
||||
c(3, row, 2, GridBagConstraints.FIRST_LINE_START)
|
||||
);
|
||||
}
|
||||
|
||||
setBorder(BorderFactory.createLineBorder(new Color(0x663399), 2, false));
|
||||
TrackersList.this.add(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@AWTThread
|
||||
public void update() {
|
||||
if (position == null && rotation == null)
|
||||
return;
|
||||
Tracker tracker = t.get();
|
||||
t.getRotation(q);
|
||||
t.getPosition(v);
|
||||
q.toAngles(angles);
|
||||
|
||||
if (position != null)
|
||||
position
|
||||
.setText(
|
||||
StringUtils.prettyNumber(v.x, 1)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(v.y, 1)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(v.z, 1)
|
||||
);
|
||||
if (rotation != null)
|
||||
rotation
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
status.setText(t.getStatus().toString().toLowerCase());
|
||||
|
||||
if (tracker instanceof TrackerWithTPS) {
|
||||
tps.setText(StringUtils.prettyNumber(((TrackerWithTPS) tracker).getTPS(), 1));
|
||||
}
|
||||
if (tracker instanceof TrackerWithBattery) {
|
||||
TrackerWithBattery twb = (TrackerWithBattery) tracker;
|
||||
float level = twb.getBatteryLevel();
|
||||
float voltage = twb.getBatteryVoltage();
|
||||
if (level == 0.0f) {
|
||||
bat.setText(String.format("%sV", StringUtils.prettyNumber(voltage, 2)));
|
||||
} else if (voltage == 0.0f) {
|
||||
bat.setText(String.format("%d%%", Math.round(level)));
|
||||
} else {
|
||||
bat
|
||||
.setText(
|
||||
String
|
||||
.format(
|
||||
"%d%% (%sV)",
|
||||
Math.round(level),
|
||||
StringUtils.prettyNumber(voltage, 2)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (t instanceof ReferenceAdjustedTracker) {
|
||||
ReferenceAdjustedTracker<Tracker> rat = (ReferenceAdjustedTracker<Tracker>) t;
|
||||
if (adj != null) {
|
||||
rat.attachmentFix.toAngles(angles);
|
||||
adj
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
}
|
||||
if (adjYaw != null) {
|
||||
rat.yawFix.toAngles(angles);
|
||||
adjYaw
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
}
|
||||
if (adjGyro != null) {
|
||||
rat.gyroFix.toAngles(angles);
|
||||
adjGyro
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
if (tracker instanceof IMUTracker) {
|
||||
if (ping != null)
|
||||
ping.setText(String.valueOf(((IMUTracker) tracker).ping));
|
||||
if (signalStrength != null) {
|
||||
int signal = ((IMUTracker) tracker).signalStrength;
|
||||
if (signal == -1) {
|
||||
signalStrength.setText("N/A");
|
||||
} else {
|
||||
// -40 dBm is excellent, -95 dBm is very poor
|
||||
int percentage = (signal - -95) * (100 - 0) / (-40 - -95) + 0;
|
||||
percentage = Math.max(Math.min(percentage, 100), 0);
|
||||
signalStrength.setText(percentage + "% " + "(" + signal + " dBm" + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
tracker.getRotation(q);
|
||||
q.toAngles(angles);
|
||||
raw
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
if (tracker instanceof IMUTracker) {
|
||||
IMUTracker imu = (IMUTracker) tracker;
|
||||
if (rawMag != null) {
|
||||
imu.rotMagQuaternion.toAngles(angles);
|
||||
rawMag
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
}
|
||||
if (calibration != null)
|
||||
calibration.setText(imu.calibrationStatus + " / " + imu.magCalibrationStatus);
|
||||
if (magAccuracy != null)
|
||||
magAccuracy
|
||||
.setText(
|
||||
StringUtils
|
||||
.prettyNumber(imu.magnetometerAccuracy * FastMath.RAD_TO_DEG, 1)
|
||||
+ "°"
|
||||
);
|
||||
if (correction != null) {
|
||||
imu.getCorrection(q);
|
||||
q.toAngles(angles);
|
||||
correction
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
}
|
||||
if (rotQuat != null) {
|
||||
imu.rotQuaternion.toAngles(angles);
|
||||
rotQuat
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
}
|
||||
if (rotAdj != null) {
|
||||
imu.rotAdjust.toAngles(angles);
|
||||
rotAdj
|
||||
.setText(
|
||||
StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
|
||||
+ " "
|
||||
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0)
|
||||
);
|
||||
}
|
||||
if (temperature != null) {
|
||||
if (imu.temperature == 0.0f) {
|
||||
// Can't be exact 0, so no info received
|
||||
temperature.setText("?");
|
||||
} else {
|
||||
temperature.setText(StringUtils.prettyNumber(imu.temperature, 1) + "∘C");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
491
src/main/java/dev/slimevr/gui/VRServerGUI.java
Normal file
491
src/main/java/dev/slimevr/gui/VRServerGUI.java
Normal file
@@ -0,0 +1,491 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import dev.slimevr.Main;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.ButtonTimer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.gui.swing.EJBox;
|
||||
import dev.slimevr.gui.swing.EJBoxNoStretch;
|
||||
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import io.eiren.util.MacOSX;
|
||||
import io.eiren.util.OperatingSystem;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static javax.swing.BoxLayout.LINE_AXIS;
|
||||
import static javax.swing.BoxLayout.PAGE_AXIS;
|
||||
|
||||
|
||||
public class VRServerGUI extends JFrame {
|
||||
|
||||
public static final String TITLE = "SlimeVR Server (" + Main.VERSION + ")";
|
||||
|
||||
public final VRServer server;
|
||||
private final TrackersList trackersList;
|
||||
private final TrackersFiltersGUI trackersFiltersGUI;
|
||||
private final SkeletonList skeletonList;
|
||||
private final EJBox pane;
|
||||
private JButton resetButton;
|
||||
private float zoom = 1.5f;
|
||||
private float initZoom = zoom;
|
||||
|
||||
@AWTThread
|
||||
public VRServerGUI(VRServer server) {
|
||||
super(TITLE);
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (OperatingSystem.getCurrentPlatform() == OperatingSystem.OSX)
|
||||
MacOSX.setTitle(TITLE);
|
||||
try {
|
||||
List<BufferedImage> images = new ArrayList<BufferedImage>(6);
|
||||
images.add(ImageIO.read(VRServerGUI.class.getResource("/icon16.png")));
|
||||
images.add(ImageIO.read(VRServerGUI.class.getResource("/icon32.png")));
|
||||
images.add(ImageIO.read(VRServerGUI.class.getResource("/icon48.png")));
|
||||
images.add(ImageIO.read(VRServerGUI.class.getResource("/icon64.png")));
|
||||
images.add(ImageIO.read(VRServerGUI.class.getResource("/icon128.png")));
|
||||
images.add(ImageIO.read(VRServerGUI.class.getResource("/icon256.png")));
|
||||
setIconImages(images);
|
||||
if (OperatingSystem.getCurrentPlatform() == OperatingSystem.OSX) {
|
||||
MacOSX.setIcons(images);
|
||||
}
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
|
||||
this.server = server;
|
||||
|
||||
this.zoom = server.config.getFloat("zoom", zoom);
|
||||
this.initZoom = zoom;
|
||||
setDefaultFontSize(zoom);
|
||||
// All components should be constructed to the current zoom level by
|
||||
// default
|
||||
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
getContentPane().setLayout(new BoxLayout(getContentPane(), PAGE_AXIS));
|
||||
|
||||
this.trackersList = new TrackersList(server, this);
|
||||
trackersFiltersGUI = new TrackersFiltersGUI(server, this);
|
||||
this.skeletonList = new SkeletonList(server, this);
|
||||
|
||||
JScrollPane scrollPane = (JScrollPane) add(
|
||||
new JScrollPane(
|
||||
pane = new EJBox(PAGE_AXIS),
|
||||
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
|
||||
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
|
||||
)
|
||||
);
|
||||
scrollPane.getVerticalScrollBar().setUnitIncrement(16);
|
||||
|
||||
GraphicsConfiguration gc = getGraphicsConfiguration();
|
||||
Rectangle screenBounds = gc.getBounds();
|
||||
setMinimumSize(new Dimension(100, 100));
|
||||
setSize(
|
||||
Math.min(server.config.getInt("window.width", 800), screenBounds.width),
|
||||
Math.min(server.config.getInt("window.height", 800), screenBounds.height)
|
||||
);
|
||||
setLocation(
|
||||
server.config
|
||||
.getInt("window.posx", screenBounds.x + (screenBounds.width - getSize().width) / 2),
|
||||
screenBounds.y
|
||||
+ server.config.getInt("window.posy", (screenBounds.height - getSize().height) / 2)
|
||||
);
|
||||
|
||||
// Resize and close listeners to save position and size betwen launcher
|
||||
// starts
|
||||
addComponentListener(new AbstractComponentListener() {
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e) {
|
||||
saveFrameInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void componentMoved(ComponentEvent e) {
|
||||
saveFrameInfo();
|
||||
}
|
||||
});
|
||||
|
||||
build();
|
||||
}
|
||||
|
||||
private static void processNewZoom(float zoom, Component comp) {
|
||||
if (comp.isFontSet()) {
|
||||
Font newFont = new ScalableFont(comp.getFont(), zoom);
|
||||
comp.setFont(newFont);
|
||||
}
|
||||
if (comp instanceof Container) {
|
||||
Container cont = (Container) comp;
|
||||
for (Component child : cont.getComponents())
|
||||
processNewZoom(zoom, child);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setDefaultFontSize(float zoom) {
|
||||
java.util.Enumeration<Object> keys = UIManager.getDefaults().keys();
|
||||
while (keys.hasMoreElements()) {
|
||||
Object key = keys.nextElement();
|
||||
Object value = UIManager.get(key);
|
||||
if (value instanceof javax.swing.plaf.FontUIResource) {
|
||||
javax.swing.plaf.FontUIResource f = (javax.swing.plaf.FontUIResource) value;
|
||||
javax.swing.plaf.FontUIResource f2 = new javax.swing.plaf.FontUIResource(
|
||||
f.deriveFont(f.getSize() * zoom)
|
||||
);
|
||||
UIManager.put(key, f2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void saveFrameInfo() {
|
||||
Rectangle b = getBounds();
|
||||
server.config.setProperty("window.width", b.width);
|
||||
server.config.setProperty("window.height", b.height);
|
||||
server.config.setProperty("window.posx", b.x);
|
||||
server.config.setProperty("window.posy", b.y);
|
||||
server.saveConfig();
|
||||
}
|
||||
|
||||
public float getZoom() {
|
||||
return this.zoom;
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
// Pack and display
|
||||
// pack();
|
||||
setVisible(true);
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
private void build() {
|
||||
pane.removeAll();
|
||||
|
||||
pane.add(new EJBoxNoStretch(LINE_AXIS, false, true) {
|
||||
{
|
||||
setBorder(new EmptyBorder(i(5)));
|
||||
|
||||
add(Box.createHorizontalGlue());
|
||||
add(resetButton = new JButton("RESET") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
add(Box.createHorizontalStrut(10));
|
||||
add(new JButton("Fast Reset") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
resetFast();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
add(Box.createHorizontalGlue());
|
||||
add(new JButton("Record BVH") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (!server.getBvhRecorder().isRecording()) {
|
||||
setText("Stop Recording BVH...");
|
||||
server.getBvhRecorder().startRecording();
|
||||
} else {
|
||||
server.getBvhRecorder().endRecording();
|
||||
setText("Record BVH");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
add(Box.createHorizontalGlue());
|
||||
add(new JButton("GUI Zoom (x" + StringUtils.prettyNumber(zoom, 2) + ")") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
guiZoom();
|
||||
setText("GUI Zoom (x" + StringUtils.prettyNumber(zoom, 2) + ")");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
add(Box.createHorizontalStrut(10));
|
||||
add(new JButton("WiFi") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
new WiFiWindow(VRServerGUI.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
add(Box.createHorizontalStrut(10));
|
||||
}
|
||||
});
|
||||
|
||||
pane.add(new EJBox(LINE_AXIS) {
|
||||
{
|
||||
setBorder(new EmptyBorder(i(5)));
|
||||
add(new EJBoxNoStretch(PAGE_AXIS, false, true) {
|
||||
{
|
||||
setAlignmentY(TOP_ALIGNMENT);
|
||||
JLabel l;
|
||||
add(l = new JLabel("Trackers list"));
|
||||
l.setFont(l.getFont().deriveFont(Font.BOLD));
|
||||
l.setAlignmentX(0.5f);
|
||||
add(trackersList);
|
||||
add(Box.createVerticalGlue());
|
||||
}
|
||||
});
|
||||
|
||||
add(new EJBoxNoStretch(PAGE_AXIS, false, true) {
|
||||
{
|
||||
setAlignmentY(TOP_ALIGNMENT);
|
||||
|
||||
JCheckBox debugCb;
|
||||
add(debugCb = new JCheckBox("Show debug information"));
|
||||
debugCb.setSelected(false);
|
||||
debugCb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
trackersList.setDebug(debugCb.isSelected());
|
||||
}
|
||||
});
|
||||
|
||||
JLabel l;
|
||||
add(l = new JLabel("Body proportions"));
|
||||
l.setFont(l.getFont().deriveFont(Font.BOLD));
|
||||
l.setAlignmentX(0.5f);
|
||||
add(new SkeletonConfigGUI(server, VRServerGUI.this));
|
||||
add(Box.createVerticalStrut(10));
|
||||
if (server.hasBridge(WindowsNamedPipeBridge.class)) {
|
||||
WindowsNamedPipeBridge br = server
|
||||
.getVRBridge(WindowsNamedPipeBridge.class);
|
||||
add(l = new JLabel("SteamVR Trackers"));
|
||||
l.setFont(l.getFont().deriveFont(Font.BOLD));
|
||||
l.setAlignmentX(0.5f);
|
||||
add(l = new JLabel("Changes may require restart of SteamVR"));
|
||||
l.setFont(l.getFont().deriveFont(Font.ITALIC));
|
||||
l.setAlignmentX(0.5f);
|
||||
|
||||
add(new EJBagNoStretch(false, true) {
|
||||
{
|
||||
JCheckBox waistCb;
|
||||
add(waistCb = new JCheckBox("Waist"), c(1, 1));
|
||||
waistCb.setSelected(br.getShareSetting(TrackerRole.WAIST));
|
||||
waistCb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
server.queueTask(() -> {
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.WAIST,
|
||||
waistCb.isSelected()
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JCheckBox legsCb;
|
||||
add(legsCb = new JCheckBox("Feet"), c(2, 1));
|
||||
legsCb
|
||||
.setSelected(
|
||||
br.getShareSetting(TrackerRole.LEFT_FOOT)
|
||||
&& br.getShareSetting(TrackerRole.RIGHT_FOOT)
|
||||
);
|
||||
legsCb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
server.queueTask(() -> {
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.LEFT_FOOT,
|
||||
legsCb.isSelected()
|
||||
);
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.RIGHT_FOOT,
|
||||
legsCb.isSelected()
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JCheckBox chestCb;
|
||||
add(chestCb = new JCheckBox("Chest"), c(1, 2));
|
||||
chestCb.setSelected(br.getShareSetting(TrackerRole.CHEST));
|
||||
chestCb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
server.queueTask(() -> {
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.CHEST,
|
||||
chestCb.isSelected()
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JCheckBox kneesCb;
|
||||
add(kneesCb = new JCheckBox("Knees"), c(2, 2));
|
||||
kneesCb
|
||||
.setSelected(
|
||||
br.getShareSetting(TrackerRole.LEFT_KNEE)
|
||||
&& br.getShareSetting(TrackerRole.RIGHT_KNEE)
|
||||
);
|
||||
kneesCb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
server.queueTask(() -> {
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.LEFT_KNEE,
|
||||
kneesCb.isSelected()
|
||||
);
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.RIGHT_KNEE,
|
||||
kneesCb.isSelected()
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JCheckBox elbowsCb;
|
||||
add(elbowsCb = new JCheckBox("Elbows"), c(1, 3));
|
||||
elbowsCb
|
||||
.setSelected(
|
||||
br.getShareSetting(TrackerRole.LEFT_ELBOW)
|
||||
&& br.getShareSetting(TrackerRole.RIGHT_ELBOW)
|
||||
);
|
||||
elbowsCb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
server.queueTask(() -> {
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.LEFT_ELBOW,
|
||||
elbowsCb.isSelected()
|
||||
);
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.RIGHT_ELBOW,
|
||||
elbowsCb.isSelected()
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
JCheckBox handsCb;
|
||||
add(handsCb = new JCheckBox("Hands"), c(2, 3));
|
||||
handsCb
|
||||
.setSelected(
|
||||
br.getShareSetting(TrackerRole.LEFT_HAND)
|
||||
&& br.getShareSetting(TrackerRole.RIGHT_HAND)
|
||||
);
|
||||
handsCb.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
server.queueTask(() -> {
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.LEFT_HAND,
|
||||
handsCb.isSelected()
|
||||
);
|
||||
br
|
||||
.changeShareSettings(
|
||||
TrackerRole.RIGHT_HAND,
|
||||
handsCb.isSelected()
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
add(Box.createVerticalStrut(10));
|
||||
}
|
||||
|
||||
add(l = new JLabel("Trackers filtering"));
|
||||
l.setFont(l.getFont().deriveFont(Font.BOLD));
|
||||
l.setAlignmentX(0.5f);
|
||||
add(trackersFiltersGUI);
|
||||
|
||||
add(Box.createVerticalStrut(10));
|
||||
|
||||
add(new JLabel("Skeleton data"));
|
||||
add(skeletonList);
|
||||
add(Box.createVerticalGlue());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
pane.add(Box.createVerticalGlue());
|
||||
|
||||
refresh();
|
||||
|
||||
server.addOnTick(trackersList::updateTrackers);
|
||||
server.addOnTick(skeletonList::updateBones);
|
||||
}
|
||||
|
||||
// For now only changes font size, but should change fixed components size
|
||||
// in
|
||||
// the future too
|
||||
private void guiZoom() {
|
||||
if (zoom <= 1.0f) {
|
||||
zoom = 1.5f;
|
||||
} else if (zoom <= 1.5f) {
|
||||
zoom = 1.75f;
|
||||
} else if (zoom <= 1.75f) {
|
||||
zoom = 2.0f;
|
||||
} else if (zoom <= 2.0f) {
|
||||
zoom = 2.5f;
|
||||
} else {
|
||||
zoom = 1.0f;
|
||||
}
|
||||
processNewZoom(zoom / initZoom, pane);
|
||||
refresh();
|
||||
server.config.setProperty("zoom", zoom);
|
||||
server.saveConfig();
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
private void resetFast() {
|
||||
server.resetTrackersYaw();
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
private void reset() {
|
||||
ButtonTimer.runTimer(resetButton, 3, "RESET", server::resetTrackers);
|
||||
}
|
||||
}
|
||||
150
src/main/java/dev/slimevr/gui/WiFiWindow.java
Normal file
150
src/main/java/dev/slimevr/gui/WiFiWindow.java
Normal file
@@ -0,0 +1,150 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import com.fazecast.jSerialComm.SerialPort;
|
||||
import dev.slimevr.gui.swing.EJBox;
|
||||
import dev.slimevr.serial.SerialListener;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
|
||||
|
||||
public class WiFiWindow extends JFrame implements SerialListener {
|
||||
|
||||
private static String savedSSID = "";
|
||||
private static String savedPassword = "";
|
||||
private final VRServerGUI gui;
|
||||
JTextField ssidField;
|
||||
JPasswordField passwdField;
|
||||
JTextArea log;
|
||||
|
||||
public WiFiWindow(VRServerGUI gui) {
|
||||
super("WiFi Settings");
|
||||
this.gui = gui;
|
||||
|
||||
getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.LINE_AXIS));
|
||||
|
||||
this.gui.server.getSerialHandler().addListener(this);
|
||||
|
||||
build();
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
private void build() {
|
||||
if (!this.gui.server.getSerialHandler().openSerial()) {
|
||||
JOptionPane
|
||||
.showMessageDialog(
|
||||
null,
|
||||
"Unable to open a serial connection. Check that your drivers are installed and nothing is using the serial port already (like Cura or VScode or another slimeVR server)",
|
||||
"SlimeVR: Serial connection error",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@AWTThread
|
||||
public void onSerialConnected(SerialPort port) {
|
||||
Container pane = getContentPane();
|
||||
pane.add(new EJBox(BoxLayout.PAGE_AXIS) {
|
||||
{
|
||||
add(
|
||||
new JLabel(
|
||||
"Tracker connected to "
|
||||
+ port.getSystemPortName()
|
||||
+ " ("
|
||||
+ port.getDescriptivePortName()
|
||||
+ ")"
|
||||
)
|
||||
);
|
||||
JScrollPane scroll;
|
||||
add(
|
||||
scroll = new JScrollPane(
|
||||
log = new JTextArea(10, 20),
|
||||
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
|
||||
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
|
||||
)
|
||||
);
|
||||
log.setLineWrap(true);
|
||||
scroll.setAutoscrolls(true);
|
||||
add(new JLabel("Enter WiFi credentials:"));
|
||||
add(new EJBox(BoxLayout.LINE_AXIS) {
|
||||
{
|
||||
add(new JLabel("Network name:"));
|
||||
add(ssidField = new JTextField(savedSSID));
|
||||
}
|
||||
});
|
||||
add(new EJBox(BoxLayout.LINE_AXIS) {
|
||||
{
|
||||
add(new JLabel("Network password:"));
|
||||
passwdField = new JPasswordField(savedPassword);
|
||||
passwdField.setEchoChar('\u25cf');
|
||||
add(passwdField);
|
||||
add(new JCheckBox("Show Password") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if (isSelected())
|
||||
passwdField.setEchoChar((char) 0);
|
||||
else
|
||||
passwdField.setEchoChar('\u25cf');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
add(new JButton("Send") {
|
||||
{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
savedSSID = ssidField.getText();
|
||||
savedPassword = new String(passwdField.getPassword());
|
||||
gui.server.getSerialHandler().setWifi(savedSSID, savedPassword);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
pack();
|
||||
setLocationRelativeTo(null);
|
||||
setVisible(true);
|
||||
java.awt.EventQueue.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
toFront();
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||
final WiFiWindow window = this;
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent windowEvent) {
|
||||
gui.server.getSerialHandler().closeSerial();
|
||||
dispose();
|
||||
gui.server.getSerialHandler().removeListener(window);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@AWTThread
|
||||
public void onSerialDisconnected() {
|
||||
log.append("[SERVER] Serial port disconnected\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
@AWTThread
|
||||
public void onSerialLog(String str) {
|
||||
log.append(str);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,21 @@
|
||||
package io.eiren.gui;
|
||||
package dev.slimevr.gui.swing;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.swing.AbstractButton;
|
||||
|
||||
public class ButtonTimer {
|
||||
|
||||
private static Timer timer = new Timer();
|
||||
|
||||
public static void runTimer(AbstractButton button, int seconds, String defaultText, Runnable runnable) {
|
||||
if(seconds <= 0) {
|
||||
private static final Timer timer = new Timer();
|
||||
|
||||
public static void runTimer(
|
||||
AbstractButton button,
|
||||
int seconds,
|
||||
String defaultText,
|
||||
Runnable runnable
|
||||
) {
|
||||
if (seconds <= 0) {
|
||||
button.setText(defaultText);
|
||||
runnable.run();
|
||||
} else {
|
||||
@@ -18,15 +23,20 @@ public class ButtonTimer {
|
||||
timer.schedule(new ButtonTimerTask(button, seconds - 1, defaultText, runnable), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class ButtonTimerTask extends TimerTask {
|
||||
|
||||
|
||||
private final AbstractButton button;
|
||||
private final int seconds;
|
||||
private final String defaultText;
|
||||
private final Runnable runnable;
|
||||
|
||||
private ButtonTimerTask(AbstractButton button, int seconds, String defaultText, Runnable runnable) {
|
||||
|
||||
private ButtonTimerTask(
|
||||
AbstractButton button,
|
||||
int seconds,
|
||||
String defaultText,
|
||||
Runnable runnable
|
||||
) {
|
||||
this.button = button;
|
||||
this.seconds = seconds;
|
||||
this.defaultText = defaultText;
|
||||
@@ -37,6 +47,5 @@ public class ButtonTimer {
|
||||
public void run() {
|
||||
runTimer(button, seconds, defaultText, runnable);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package io.eiren.gui;
|
||||
package dev.slimevr.gui.swing;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import java.awt.GridBagLayout;
|
||||
|
||||
public class EJBag extends EJPanel {
|
||||
|
||||
|
||||
public EJBag() {
|
||||
super(new GridBagLayout());
|
||||
}
|
||||
32
src/main/java/dev/slimevr/gui/swing/EJBagNoStretch.java
Normal file
32
src/main/java/dev/slimevr/gui/swing/EJBagNoStretch.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package dev.slimevr.gui.swing;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
|
||||
public class EJBagNoStretch extends EJPanel {
|
||||
|
||||
public EJBagNoStretch(boolean stretchVertical, boolean stretchHorizontal) {
|
||||
super(new EGridBagLayoutNoStretch(stretchVertical, stretchHorizontal));
|
||||
}
|
||||
|
||||
private static class EGridBagLayoutNoStretch extends GridBagLayout {
|
||||
|
||||
private final boolean stretchVertical;
|
||||
private final boolean stretchHorizontal;
|
||||
|
||||
public EGridBagLayoutNoStretch(boolean stretchVertical, boolean stretchHorizontal) {
|
||||
this.stretchVertical = stretchVertical;
|
||||
this.stretchHorizontal = stretchHorizontal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension maximumLayoutSize(Container target) {
|
||||
Dimension pref = preferredLayoutSize(target);
|
||||
if (stretchVertical)
|
||||
pref.height = Integer.MAX_VALUE;
|
||||
if (stretchHorizontal)
|
||||
pref.width = Integer.MAX_VALUE;
|
||||
return pref;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package io.eiren.gui;
|
||||
package dev.slimevr.gui.swing;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import javax.swing.BoxLayout;
|
||||
|
||||
public class EJBox extends EJPanel {
|
||||
|
||||
|
||||
public EJBox(int layout) {
|
||||
super();
|
||||
setLayout(new BoxLayout(this, layout));
|
||||
40
src/main/java/dev/slimevr/gui/swing/EJBoxNoStretch.java
Normal file
40
src/main/java/dev/slimevr/gui/swing/EJBoxNoStretch.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package dev.slimevr.gui.swing;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
|
||||
public class EJBoxNoStretch extends EJPanel {
|
||||
|
||||
public EJBoxNoStretch(int layout, boolean stretchVertical, boolean stretchHorizontal) {
|
||||
super();
|
||||
setLayout(new BoxLayoutNoStretch(this, layout, stretchVertical, stretchHorizontal));
|
||||
}
|
||||
|
||||
private static class BoxLayoutNoStretch extends BoxLayout {
|
||||
|
||||
private final boolean stretchVertical;
|
||||
private final boolean stretchHorizontal;
|
||||
|
||||
public BoxLayoutNoStretch(
|
||||
Container target,
|
||||
int axis,
|
||||
boolean stretchVertical,
|
||||
boolean stretchHorizontal
|
||||
) {
|
||||
super(target, axis);
|
||||
this.stretchVertical = stretchVertical;
|
||||
this.stretchHorizontal = stretchHorizontal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension maximumLayoutSize(Container target) {
|
||||
Dimension pref = preferredLayoutSize(target);
|
||||
if (stretchVertical)
|
||||
pref.height = Integer.MAX_VALUE;
|
||||
if (stretchHorizontal)
|
||||
pref.width = Integer.MAX_VALUE;
|
||||
return pref;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,24 @@
|
||||
package io.eiren.gui;
|
||||
package dev.slimevr.gui.swing;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.Insets;
|
||||
import java.awt.LayoutManager;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
public abstract class EJPanel extends JPanel {
|
||||
|
||||
|
||||
public static boolean NEEDS_DOWNSCALE = false;
|
||||
public static float DOWNSCALE_FACTOR = 0.75f;
|
||||
|
||||
|
||||
public EJPanel() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
public EJPanel(LayoutManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
|
||||
public static void s(Component c, int width, int height) {
|
||||
if(NEEDS_DOWNSCALE) {
|
||||
if (NEEDS_DOWNSCALE) {
|
||||
width = (int) Math.ceil(width * DOWNSCALE_FACTOR);
|
||||
height = (int) Math.ceil(height * DOWNSCALE_FACTOR);
|
||||
}
|
||||
@@ -33,9 +28,9 @@ public abstract class EJPanel extends JPanel {
|
||||
c.setMaximumSize(d);
|
||||
c.setMinimumSize(d);
|
||||
}
|
||||
|
||||
|
||||
public static void minWidth(Component c, int width, int height) {
|
||||
if(NEEDS_DOWNSCALE) {
|
||||
if (NEEDS_DOWNSCALE) {
|
||||
height = (int) Math.ceil(height * DOWNSCALE_FACTOR);
|
||||
width = (int) Math.ceil(width * DOWNSCALE_FACTOR);
|
||||
}
|
||||
@@ -43,9 +38,9 @@ public abstract class EJPanel extends JPanel {
|
||||
c.setMaximumSize(new Dimension(Short.MAX_VALUE, height));
|
||||
c.setMinimumSize(new Dimension(width, height));
|
||||
}
|
||||
|
||||
|
||||
public static void minHeight(Component c, int width, int height) {
|
||||
if(NEEDS_DOWNSCALE) {
|
||||
if (NEEDS_DOWNSCALE) {
|
||||
height = (int) Math.ceil(height * DOWNSCALE_FACTOR);
|
||||
width = (int) Math.ceil(width * DOWNSCALE_FACTOR);
|
||||
}
|
||||
@@ -53,14 +48,14 @@ public abstract class EJPanel extends JPanel {
|
||||
c.setMaximumSize(new Dimension(width, Short.MAX_VALUE));
|
||||
c.setMinimumSize(new Dimension(width, height));
|
||||
}
|
||||
|
||||
|
||||
public static GridBagConstraints c(int x, int y) {
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridx = x;
|
||||
c.gridy = y;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
public static GridBagConstraints c(int x, int y, int padding) {
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridx = x;
|
||||
@@ -68,7 +63,7 @@ public abstract class EJPanel extends JPanel {
|
||||
c.insets = new Insets(padding, padding, padding, padding);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
public static GridBagConstraints c(int x, int y, int padding, int anchor) {
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridx = x;
|
||||
@@ -77,7 +72,7 @@ public abstract class EJPanel extends JPanel {
|
||||
c.anchor = anchor;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
public static GridBagConstraints c(int x, int y, Insets insets) {
|
||||
GridBagConstraints c = new GridBagConstraints();
|
||||
c.gridx = x;
|
||||
@@ -85,13 +80,13 @@ public abstract class EJPanel extends JPanel {
|
||||
c.insets = insets;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
public static GridBagConstraints s(GridBagConstraints c, int gridwidth, int gridheight) {
|
||||
c.gridwidth = gridwidth;
|
||||
c.gridheight = gridheight;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
public static Insets i(int s) {
|
||||
return new Insets(s, s, s, s);
|
||||
}
|
||||
@@ -99,11 +94,11 @@ public abstract class EJPanel extends JPanel {
|
||||
public static Insets i(int h, int v) {
|
||||
return new Insets(v, h, v, h);
|
||||
}
|
||||
|
||||
|
||||
public static Component padding(int width, int height) {
|
||||
return Box.createRigidArea(new Dimension(width, height));
|
||||
}
|
||||
|
||||
|
||||
public static int fontSize(int baseSize) {
|
||||
return NEEDS_DOWNSCALE ? (int) Math.ceil(baseSize * DOWNSCALE_FACTOR) : baseSize;
|
||||
}
|
||||
8
src/main/java/dev/slimevr/gui/swing/EJlabel.java
Normal file
8
src/main/java/dev/slimevr/gui/swing/EJlabel.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package dev.slimevr.gui.swing;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
|
||||
public class EJlabel extends JLabel {
|
||||
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package io.eiren.hardware.magentometer;
|
||||
package dev.slimevr.hardware.magentometer;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
|
||||
|
||||
public interface Magneto extends Library {
|
||||
|
||||
|
||||
Magneto INSTANCE = Native.load("MagnetoLib", Magneto.class);
|
||||
|
||||
|
||||
void calculate(double[] data, int nlines, double nxsrej, double hm, double[] B, double[] A_1);
|
||||
|
||||
double calculateHnorm(double data[], int nlines);
|
||||
|
||||
double calculateHnorm(double[] data, int nlines);
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
package dev.slimevr.platform.windows;
|
||||
|
||||
import com.google.protobuf.CodedOutputStream;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinError;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
import dev.slimevr.Main;
|
||||
import dev.slimevr.bridge.BridgeThread;
|
||||
import dev.slimevr.bridge.PipeState;
|
||||
import dev.slimevr.bridge.ProtobufBridge;
|
||||
import dev.slimevr.bridge.ProtobufMessages.ProtobufMessage;
|
||||
import dev.slimevr.bridge.ProtobufMessages.TrackerAdded;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.*;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class WindowsNamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnable {
|
||||
|
||||
protected final String pipeName;
|
||||
protected final String bridgeSettingsKey;
|
||||
protected final Thread runnerThread;
|
||||
private final TrackerRole[] defaultRoles = new TrackerRole[] { TrackerRole.WAIST,
|
||||
TrackerRole.LEFT_FOOT, TrackerRole.RIGHT_FOOT };
|
||||
private final byte[] buffArray = new byte[2048];
|
||||
private final List<? extends ShareableTracker> shareableTrackers;
|
||||
protected WindowsPipe pipe;
|
||||
|
||||
public WindowsNamedPipeBridge(
|
||||
HMDTracker hmd,
|
||||
String bridgeSettingsKey,
|
||||
String bridgeName,
|
||||
String pipeName,
|
||||
List<? extends ShareableTracker> shareableTrackers
|
||||
) {
|
||||
super(bridgeName, hmd);
|
||||
this.pipeName = pipeName;
|
||||
this.bridgeSettingsKey = bridgeSettingsKey;
|
||||
this.runnerThread = new Thread(this, "Named pipe thread");
|
||||
this.shareableTrackers = shareableTrackers;
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void startBridge() {
|
||||
for (TrackerRole role : defaultRoles) {
|
||||
changeShareSettings(
|
||||
role,
|
||||
Main.vrServer.config
|
||||
.getBoolean(
|
||||
"bridge." + bridgeSettingsKey + ".trackers." + role.name().toLowerCase(),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
for (ShareableTracker tr : shareableTrackers) {
|
||||
TrackerRole role = tr.getTrackerRole();
|
||||
changeShareSettings(
|
||||
role,
|
||||
Main.vrServer.config
|
||||
.getBoolean(
|
||||
"bridge." + bridgeSettingsKey + ".trackers." + role.name().toLowerCase(),
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
runnerThread.start();
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public boolean getShareSetting(TrackerRole role) {
|
||||
for (ShareableTracker tr : shareableTrackers) {
|
||||
if (tr.getTrackerRole() == role) {
|
||||
return sharedTrackers.contains(tr);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public void changeShareSettings(TrackerRole role, boolean share) {
|
||||
if (role == null)
|
||||
return;
|
||||
for (ShareableTracker tr : shareableTrackers) {
|
||||
if (tr.getTrackerRole() == role) {
|
||||
if (share) {
|
||||
addSharedTracker(tr);
|
||||
} else {
|
||||
removeSharedTracker(tr);
|
||||
}
|
||||
Main.vrServer.config
|
||||
.setProperty(
|
||||
"bridge." + bridgeSettingsKey + ".trackers." + role.name().toLowerCase(),
|
||||
share
|
||||
);
|
||||
Main.vrServer.saveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
protected VRTracker createNewTracker(TrackerAdded trackerAdded) {
|
||||
VRTracker tracker = new VRTracker(
|
||||
trackerAdded.getTrackerId(),
|
||||
trackerAdded.getTrackerSerial(),
|
||||
trackerAdded.getTrackerName(),
|
||||
true,
|
||||
true
|
||||
);
|
||||
TrackerRole role = TrackerRole.getById(trackerAdded.getTrackerRole());
|
||||
if (role != null) {
|
||||
tracker.setBodyPosition(TrackerPosition.getByTrackerRole(role).orElse(null));
|
||||
}
|
||||
return tracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
@BridgeThread
|
||||
public void run() {
|
||||
try {
|
||||
createPipe();
|
||||
while (true) {
|
||||
boolean pipesUpdated = false;
|
||||
if (pipe.state == PipeState.CREATED) {
|
||||
tryOpeningPipe(pipe);
|
||||
}
|
||||
if (pipe.state == PipeState.OPEN) {
|
||||
pipesUpdated = updatePipe();
|
||||
updateMessageQueue();
|
||||
}
|
||||
if (pipe.state == PipeState.ERROR) {
|
||||
resetPipe();
|
||||
}
|
||||
if (!pipesUpdated) {
|
||||
try {
|
||||
Thread.sleep(5); // Up to 200Hz
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@BridgeThread
|
||||
protected boolean sendMessageReal(ProtobufMessage message) {
|
||||
if (pipe.state == PipeState.OPEN) {
|
||||
try {
|
||||
int size = message.getSerializedSize();
|
||||
CodedOutputStream os = CodedOutputStream.newInstance(buffArray, 4, size);
|
||||
message.writeTo(os);
|
||||
size += 4;
|
||||
buffArray[0] = (byte) (size & 0xFF);
|
||||
buffArray[1] = (byte) ((size >> 8) & 0xFF);
|
||||
buffArray[2] = (byte) ((size >> 16) & 0xFF);
|
||||
buffArray[3] = (byte) ((size >> 24) & 0xFF);
|
||||
if (Kernel32.INSTANCE.WriteFile(pipe.pipeHandle, buffArray, size, null, null)) {
|
||||
return true;
|
||||
}
|
||||
pipe.state = PipeState.ERROR;
|
||||
LogManager
|
||||
.severe("[" + bridgeName + "] Pipe error: " + Kernel32.INSTANCE.GetLastError());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean updatePipe() throws IOException {
|
||||
if (pipe.state == PipeState.OPEN) {
|
||||
boolean readAnything = false;
|
||||
IntByReference bytesAvailable = new IntByReference(0);
|
||||
while (
|
||||
Kernel32.INSTANCE
|
||||
.PeekNamedPipe(pipe.pipeHandle, buffArray, 4, null, bytesAvailable, null)
|
||||
) {
|
||||
if (bytesAvailable.getValue() >= 4) { // Got size
|
||||
int messageLength = (buffArray[3] << 24)
|
||||
| (buffArray[2] << 16)
|
||||
| (buffArray[1] << 8)
|
||||
| buffArray[0];
|
||||
if (messageLength > 1024) { // Overflow
|
||||
LogManager
|
||||
.severe(
|
||||
"["
|
||||
+ bridgeName
|
||||
+ "] Pipe overflow. Message length: "
|
||||
+ messageLength
|
||||
);
|
||||
pipe.state = PipeState.ERROR;
|
||||
return readAnything;
|
||||
}
|
||||
if (bytesAvailable.getValue() >= messageLength) {
|
||||
if (
|
||||
Kernel32.INSTANCE
|
||||
.ReadFile(
|
||||
pipe.pipeHandle,
|
||||
buffArray,
|
||||
messageLength,
|
||||
bytesAvailable,
|
||||
null
|
||||
)
|
||||
) {
|
||||
ProtobufMessage message = ProtobufMessage
|
||||
.parser()
|
||||
.parseFrom(buffArray, 4, messageLength - 4);
|
||||
messageReceived(message);
|
||||
readAnything = true;
|
||||
} else {
|
||||
pipe.state = PipeState.ERROR;
|
||||
LogManager
|
||||
.severe(
|
||||
"["
|
||||
+ bridgeName
|
||||
+ "] Pipe error: "
|
||||
+ Kernel32.INSTANCE.GetLastError()
|
||||
);
|
||||
return readAnything;
|
||||
}
|
||||
} else {
|
||||
return readAnything; // Wait for more data
|
||||
}
|
||||
} else {
|
||||
return readAnything; // Wait for more data
|
||||
}
|
||||
}
|
||||
pipe.state = PipeState.ERROR;
|
||||
LogManager
|
||||
.severe("[" + bridgeName + "] Pipe error: " + Kernel32.INSTANCE.GetLastError());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void resetPipe() {
|
||||
WindowsPipe.safeDisconnect(pipe);
|
||||
pipe.state = PipeState.CREATED;
|
||||
Main.vrServer.queueTask(this::disconnected);
|
||||
}
|
||||
|
||||
private void createPipe() throws IOException {
|
||||
try {
|
||||
pipe = new WindowsPipe(
|
||||
Kernel32.INSTANCE
|
||||
.CreateNamedPipe(
|
||||
pipeName,
|
||||
WinBase.PIPE_ACCESS_DUPLEX, // dwOpenMode
|
||||
WinBase.PIPE_TYPE_BYTE | WinBase.PIPE_READMODE_BYTE | WinBase.PIPE_WAIT, // dwPipeMode
|
||||
1, // nMaxInstances,
|
||||
1024 * 16, // nOutBufferSize,
|
||||
1024 * 16, // nInBufferSize,
|
||||
0, // nDefaultTimeOut,
|
||||
null
|
||||
),
|
||||
pipeName
|
||||
); // lpSecurityAttributes
|
||||
LogManager.info("[" + bridgeName + "] Pipe " + pipe.name + " created");
|
||||
if (WinBase.INVALID_HANDLE_VALUE.equals(pipe.pipeHandle))
|
||||
throw new IOException(
|
||||
"Can't open " + pipeName + " pipe: " + Kernel32.INSTANCE.GetLastError()
|
||||
);
|
||||
LogManager.info("[" + bridgeName + "] Pipes are created");
|
||||
} catch (IOException e) {
|
||||
WindowsPipe.safeDisconnect(pipe);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryOpeningPipe(WindowsPipe pipe) {
|
||||
if (
|
||||
Kernel32.INSTANCE.ConnectNamedPipe(pipe.pipeHandle, null)
|
||||
|| Kernel32.INSTANCE.GetLastError() == WinError.ERROR_PIPE_CONNECTED
|
||||
) {
|
||||
pipe.state = PipeState.OPEN;
|
||||
LogManager.info("[" + bridgeName + "] Pipe " + pipe.name + " is open");
|
||||
Main.vrServer.queueTask(this::reconnected);
|
||||
return true;
|
||||
}
|
||||
LogManager
|
||||
.info(
|
||||
"["
|
||||
+ bridgeName
|
||||
+ "] Error connecting to pipe "
|
||||
+ pipe.name
|
||||
+ ": "
|
||||
+ Kernel32.INSTANCE.GetLastError()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
package dev.slimevr.platform.windows;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.bridge.Bridge;
|
||||
import dev.slimevr.bridge.PipeState;
|
||||
import dev.slimevr.vr.trackers.*;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
|
||||
public class WindowsNamedPipeVRBridge extends Thread implements Bridge {
|
||||
|
||||
public static final String HMDPipeName = "\\\\.\\pipe\\HMDPipe";
|
||||
public static final String TrackersPipeName = "\\\\.\\pipe\\TrackPipe";
|
||||
public static final Charset ASCII = StandardCharsets.US_ASCII;
|
||||
private static final int MAX_COMMAND_LENGTH = 2048;
|
||||
private final byte[] buffArray = new byte[1024];
|
||||
private final StringBuilder commandBuilder = new StringBuilder(1024);
|
||||
private final StringBuilder sbBuffer = new StringBuilder(1024);
|
||||
private final Vector3f vBuffer = new Vector3f();
|
||||
private final Vector3f vBuffer2 = new Vector3f();
|
||||
private final Quaternion qBuffer = new Quaternion();
|
||||
private final Quaternion qBuffer2 = new Quaternion();
|
||||
private final HMDTracker hmd;
|
||||
private final List<WindowsPipe> trackerPipes;
|
||||
private final List<? extends Tracker> shareTrackers;
|
||||
private final List<ComputedTracker> internalTrackers;
|
||||
private final HMDTracker internalHMDTracker = new HMDTracker("internal://HMD");
|
||||
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
|
||||
private WindowsPipe hmdPipe;
|
||||
|
||||
public WindowsNamedPipeVRBridge(
|
||||
HMDTracker hmd,
|
||||
List<? extends Tracker> shareTrackers,
|
||||
VRServer server
|
||||
) {
|
||||
super("Named Pipe VR Bridge");
|
||||
this.hmd = hmd;
|
||||
this.shareTrackers = new FastList<>(shareTrackers);
|
||||
this.trackerPipes = new FastList<>(shareTrackers.size());
|
||||
this.internalTrackers = new FastList<>(shareTrackers.size());
|
||||
for (Tracker t : shareTrackers) {
|
||||
ComputedTracker ct = new ComputedTracker(
|
||||
t.getTrackerId(),
|
||||
"internal://" + t.getName(),
|
||||
true,
|
||||
true
|
||||
);
|
||||
ct.setStatus(TrackerStatus.OK);
|
||||
this.internalTrackers.add(ct);
|
||||
}
|
||||
}
|
||||
|
||||
public static void safeDisconnect(WindowsPipe pipe) {
|
||||
try {
|
||||
if (pipe != null && pipe.pipeHandle != null)
|
||||
Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
createPipes();
|
||||
while (true) {
|
||||
waitForPipesToOpen();
|
||||
if (areAllPipesOpen()) {
|
||||
boolean hmdUpdated = updateHMD(); // Update at HMDs
|
||||
// frequency
|
||||
for (int i = 0; i < trackerPipes.size(); ++i) {
|
||||
updateTracker(i, hmdUpdated);
|
||||
}
|
||||
if (!hmdUpdated) {
|
||||
Thread.sleep(5); // Up to 200Hz
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataRead() {
|
||||
if (newHMDData.compareAndSet(true, false)) {
|
||||
hmd.position.set(internalHMDTracker.position);
|
||||
hmd.rotation.set(internalHMDTracker.rotation);
|
||||
hmd.dataTick();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dataWrite() {
|
||||
for (int i = 0; i < shareTrackers.size(); ++i) {
|
||||
Tracker t = shareTrackers.get(i);
|
||||
ComputedTracker it = this.internalTrackers.get(i);
|
||||
if (t.getPosition(vBuffer2))
|
||||
it.position.set(vBuffer2);
|
||||
if (t.getRotation(qBuffer2))
|
||||
it.rotation.set(qBuffer2);
|
||||
}
|
||||
}
|
||||
|
||||
private void waitForPipesToOpen() {
|
||||
if (hmdPipe.state == PipeState.CREATED) {
|
||||
if (tryOpeningPipe(hmdPipe))
|
||||
initHMDPipe(hmdPipe);
|
||||
}
|
||||
for (int i = 0; i < trackerPipes.size(); ++i) {
|
||||
WindowsPipe trackerPipe = trackerPipes.get(i);
|
||||
if (trackerPipe.state == PipeState.CREATED) {
|
||||
if (tryOpeningPipe(trackerPipe))
|
||||
initTrackerPipe(trackerPipe, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean updateHMD() throws IOException {
|
||||
if (hmdPipe.state == PipeState.OPEN) {
|
||||
IntByReference bytesAvailable = new IntByReference(0);
|
||||
if (
|
||||
Kernel32.INSTANCE
|
||||
.PeekNamedPipe(hmdPipe.pipeHandle, null, 0, null, bytesAvailable, null)
|
||||
) {
|
||||
if (bytesAvailable.getValue() > 0) {
|
||||
while (
|
||||
Kernel32.INSTANCE
|
||||
.ReadFile(
|
||||
hmdPipe.pipeHandle,
|
||||
buffArray,
|
||||
buffArray.length,
|
||||
bytesAvailable,
|
||||
null
|
||||
)
|
||||
) {
|
||||
int bytesRead = bytesAvailable.getValue();
|
||||
for (int i = 0; i < bytesRead; ++i) {
|
||||
char c = (char) buffArray[i];
|
||||
if (c == '\n') {
|
||||
executeHMDInput();
|
||||
commandBuilder.setLength(0);
|
||||
} else {
|
||||
commandBuilder.append(c);
|
||||
if (commandBuilder.length() >= MAX_COMMAND_LENGTH) {
|
||||
LogManager
|
||||
.severe(
|
||||
"[VRBridge] Command from the pipe is too long, flushing buffer"
|
||||
);
|
||||
commandBuilder.setLength(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytesRead < buffArray.length)
|
||||
break; // Don't repeat, we read all available bytes
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void executeHMDInput() throws IOException {
|
||||
String[] split = commandBuilder.toString().split(" ");
|
||||
if (split.length < 7) {
|
||||
LogManager.severe("[VRBridge] Short HMD data received: " + commandBuilder);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
double x = Double.parseDouble(split[0]);
|
||||
double y = Double.parseDouble(split[1]);
|
||||
double z = Double.parseDouble(split[2]);
|
||||
double qw = Double.parseDouble(split[3]);
|
||||
double qx = Double.parseDouble(split[4]);
|
||||
double qy = Double.parseDouble(split[5]);
|
||||
double qz = Double.parseDouble(split[6]);
|
||||
|
||||
internalHMDTracker.position.set((float) x, (float) y, (float) z);
|
||||
internalHMDTracker.rotation.set((float) qx, (float) qy, (float) qz, (float) qw);
|
||||
internalHMDTracker.dataTick();
|
||||
newHMDData.set(true);
|
||||
} catch (NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void updateTracker(int trackerId, boolean hmdUpdated) {
|
||||
Tracker sensor = internalTrackers.get(trackerId);
|
||||
if (sensor.getStatus().sendData) {
|
||||
WindowsPipe trackerPipe = trackerPipes.get(trackerId);
|
||||
if (hmdUpdated && trackerPipe.state == PipeState.OPEN) {
|
||||
sbBuffer.setLength(0);
|
||||
sensor.getPosition(vBuffer);
|
||||
sensor.getRotation(qBuffer);
|
||||
sbBuffer
|
||||
.append(vBuffer.x)
|
||||
.append(' ')
|
||||
.append(vBuffer.y)
|
||||
.append(' ')
|
||||
.append(vBuffer.z)
|
||||
.append(' ');
|
||||
sbBuffer
|
||||
.append(qBuffer.getW())
|
||||
.append(' ')
|
||||
.append(qBuffer.getX())
|
||||
.append(' ')
|
||||
.append(qBuffer.getY())
|
||||
.append(' ')
|
||||
.append(qBuffer.getZ())
|
||||
.append('\n');
|
||||
String str = sbBuffer.toString();
|
||||
System.arraycopy(str.getBytes(ASCII), 0, buffArray, 0, str.length());
|
||||
buffArray[str.length()] = '\0';
|
||||
IntByReference lpNumberOfBytesWritten = new IntByReference(0);
|
||||
Kernel32.INSTANCE
|
||||
.WriteFile(
|
||||
trackerPipe.pipeHandle,
|
||||
buffArray,
|
||||
str.length() + 1,
|
||||
lpNumberOfBytesWritten,
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void initHMDPipe(WindowsPipe pipe) {
|
||||
hmd.setStatus(TrackerStatus.OK);
|
||||
}
|
||||
|
||||
private void initTrackerPipe(WindowsPipe pipe, int trackerId) {
|
||||
String trackerHello = this.shareTrackers.size() + " 0";
|
||||
System.arraycopy(trackerHello.getBytes(ASCII), 0, buffArray, 0, trackerHello.length());
|
||||
buffArray[trackerHello.length()] = '\0';
|
||||
IntByReference lpNumberOfBytesWritten = new IntByReference(0);
|
||||
Kernel32.INSTANCE
|
||||
.WriteFile(
|
||||
pipe.pipeHandle,
|
||||
buffArray,
|
||||
trackerHello.length() + 1,
|
||||
lpNumberOfBytesWritten,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private boolean tryOpeningPipe(WindowsPipe pipe) {
|
||||
if (Kernel32.INSTANCE.ConnectNamedPipe(pipe.pipeHandle, null)) {
|
||||
pipe.state = PipeState.OPEN;
|
||||
LogManager.info("[VRBridge] Pipe " + pipe.name + " is open");
|
||||
return true;
|
||||
}
|
||||
|
||||
LogManager
|
||||
.info(
|
||||
"[VRBridge] Error connecting to pipe "
|
||||
+ pipe.name
|
||||
+ ": "
|
||||
+ Kernel32.INSTANCE.GetLastError()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean areAllPipesOpen() {
|
||||
if (hmdPipe == null || hmdPipe.state == PipeState.CREATED) {
|
||||
return false;
|
||||
}
|
||||
for (WindowsPipe pipe : trackerPipes) {
|
||||
if (pipe.state == PipeState.CREATED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void createPipes() throws IOException {
|
||||
try {
|
||||
hmdPipe = new WindowsPipe(
|
||||
Kernel32.INSTANCE
|
||||
.CreateNamedPipe(
|
||||
HMDPipeName,
|
||||
WinBase.PIPE_ACCESS_DUPLEX, // dwOpenMode
|
||||
WinBase.PIPE_TYPE_BYTE | WinBase.PIPE_READMODE_BYTE | WinBase.PIPE_WAIT, // dwPipeMode
|
||||
1, // nMaxInstances,
|
||||
1024 * 16, // nOutBufferSize,
|
||||
1024 * 16, // nInBufferSize,
|
||||
0, // nDefaultTimeOut,
|
||||
null
|
||||
),
|
||||
HMDPipeName
|
||||
); // lpSecurityAttributes
|
||||
LogManager.info("[VRBridge] Pipe " + hmdPipe.name + " created");
|
||||
if (WinBase.INVALID_HANDLE_VALUE.equals(hmdPipe.pipeHandle))
|
||||
throw new IOException(
|
||||
"Can't open " + HMDPipeName + " pipe: " + Kernel32.INSTANCE.GetLastError()
|
||||
);
|
||||
for (int i = 0; i < this.shareTrackers.size(); ++i) {
|
||||
String pipeName = TrackersPipeName + i;
|
||||
HANDLE pipeHandle = Kernel32.INSTANCE
|
||||
.CreateNamedPipe(
|
||||
pipeName,
|
||||
WinBase.PIPE_ACCESS_DUPLEX, // dwOpenMode
|
||||
WinBase.PIPE_TYPE_BYTE | WinBase.PIPE_READMODE_BYTE | WinBase.PIPE_WAIT, // dwPipeMode
|
||||
1, // nMaxInstances,
|
||||
1024 * 16, // nOutBufferSize,
|
||||
1024 * 16, // nInBufferSize,
|
||||
0, // nDefaultTimeOut,
|
||||
null
|
||||
); // lpSecurityAttributes
|
||||
if (WinBase.INVALID_HANDLE_VALUE.equals(pipeHandle))
|
||||
throw new IOException(
|
||||
"Can't open " + pipeName + " pipe: " + Kernel32.INSTANCE.GetLastError()
|
||||
);
|
||||
LogManager.info("[VRBridge] Pipe " + pipeName + " created");
|
||||
trackerPipes.add(new WindowsPipe(pipeHandle, pipeName));
|
||||
}
|
||||
LogManager.info("[VRBridge] Pipes are open");
|
||||
} catch (IOException e) {
|
||||
safeDisconnect(hmdPipe);
|
||||
for (WindowsPipe pipe : trackerPipes) {
|
||||
safeDisconnect(pipe);
|
||||
}
|
||||
trackerPipes.clear();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSharedTracker(ShareableTracker tracker) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSharedTracker(ShareableTracker tracker) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startBridge() {
|
||||
start();
|
||||
}
|
||||
}
|
||||
25
src/main/java/dev/slimevr/platform/windows/WindowsPipe.java
Normal file
25
src/main/java/dev/slimevr/platform/windows/WindowsPipe.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package dev.slimevr.platform.windows;
|
||||
|
||||
import com.sun.jna.platform.win32.Kernel32;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
import dev.slimevr.bridge.PipeState;
|
||||
|
||||
|
||||
public class WindowsPipe {
|
||||
|
||||
public final String name;
|
||||
public final HANDLE pipeHandle;
|
||||
public PipeState state = PipeState.CREATED;
|
||||
|
||||
public WindowsPipe(HANDLE pipeHandle, String name) {
|
||||
this.pipeHandle = pipeHandle;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static void safeDisconnect(WindowsPipe pipe) {
|
||||
try {
|
||||
if (pipe != null && pipe.pipeHandle != null)
|
||||
Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
75
src/main/java/dev/slimevr/poserecorder/BVHRecorder.java
Normal file
75
src/main/java/dev/slimevr/poserecorder/BVHRecorder.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.posestreamer.BVHFileStream;
|
||||
import dev.slimevr.posestreamer.PoseDataStream;
|
||||
import dev.slimevr.posestreamer.ServerPoseStreamer;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class BVHRecorder {
|
||||
|
||||
private static final File bvhSaveDir = new File("BVH Recordings");
|
||||
private final ServerPoseStreamer poseStreamer;
|
||||
private PoseDataStream poseDataStream = null;
|
||||
|
||||
public BVHRecorder(VRServer server) {
|
||||
this.poseStreamer = new ServerPoseStreamer(server);
|
||||
}
|
||||
|
||||
public void startRecording() {
|
||||
File bvhFile = getBvhFile();
|
||||
if (bvhFile != null) {
|
||||
try {
|
||||
poseDataStream = new BVHFileStream(bvhFile);
|
||||
poseStreamer.setOutput(poseDataStream, 1000L / 100L);
|
||||
} catch (IOException e1) {
|
||||
LogManager
|
||||
.severe(
|
||||
"[BVH] Failed to create the recording file \"" + bvhFile.getPath() + "\"."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
LogManager.severe("[BVH] Unable to get file to save to");
|
||||
}
|
||||
}
|
||||
|
||||
public void endRecording() {
|
||||
|
||||
try {
|
||||
poseStreamer.closeOutput(poseDataStream);
|
||||
} catch (Exception e1) {
|
||||
LogManager.severe("[BVH] Exception while closing poseDataStream", e1);
|
||||
} finally {
|
||||
poseDataStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
private File getBvhFile() {
|
||||
if (bvhSaveDir.isDirectory() || bvhSaveDir.mkdirs()) {
|
||||
File saveRecording;
|
||||
int recordingIndex = 1;
|
||||
do {
|
||||
saveRecording = new File(bvhSaveDir, "BVH-Recording" + recordingIndex++ + ".bvh");
|
||||
} while (saveRecording.exists());
|
||||
|
||||
return saveRecording;
|
||||
} else {
|
||||
LogManager
|
||||
.severe(
|
||||
"[BVH] Failed to create the recording directory \""
|
||||
+ bvhSaveDir.getPath()
|
||||
+ "\"."
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isRecording() {
|
||||
return poseDataStream != null;
|
||||
}
|
||||
}
|
||||
142
src/main/java/dev/slimevr/poserecorder/PoseFrameIO.java
Normal file
142
src/main/java/dev/slimevr/poserecorder/PoseFrameIO.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
public final class PoseFrameIO {
|
||||
|
||||
private PoseFrameIO() {
|
||||
// Do not allow instantiating
|
||||
}
|
||||
|
||||
public static boolean writeFrames(DataOutputStream outputStream, PoseFrames frames) {
|
||||
try {
|
||||
if (frames != null) {
|
||||
outputStream.writeInt(frames.getTrackerCount());
|
||||
for (PoseFrameTracker tracker : frames.getTrackers()) {
|
||||
outputStream.writeUTF(tracker.name);
|
||||
outputStream.writeInt(tracker.getFrameCount());
|
||||
for (int i = 0; i < tracker.getFrameCount(); i++) {
|
||||
TrackerFrame trackerFrame = tracker.safeGetFrame(i);
|
||||
if (trackerFrame == null) {
|
||||
outputStream.writeInt(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
outputStream.writeInt(trackerFrame.getDataFlags());
|
||||
|
||||
if (trackerFrame.hasData(TrackerFrameData.DESIGNATION)) {
|
||||
outputStream.writeUTF(trackerFrame.designation.designation);
|
||||
}
|
||||
|
||||
if (trackerFrame.hasData(TrackerFrameData.ROTATION)) {
|
||||
outputStream.writeFloat(trackerFrame.rotation.getX());
|
||||
outputStream.writeFloat(trackerFrame.rotation.getY());
|
||||
outputStream.writeFloat(trackerFrame.rotation.getZ());
|
||||
outputStream.writeFloat(trackerFrame.rotation.getW());
|
||||
}
|
||||
|
||||
if (trackerFrame.hasData(TrackerFrameData.POSITION)) {
|
||||
outputStream.writeFloat(trackerFrame.position.getX());
|
||||
outputStream.writeFloat(trackerFrame.position.getY());
|
||||
outputStream.writeFloat(trackerFrame.position.getZ());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
outputStream.writeInt(0);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogManager.severe("Error writing frame to stream", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean writeToFile(File file, PoseFrames frames) {
|
||||
try (
|
||||
DataOutputStream outputStream = new DataOutputStream(
|
||||
new BufferedOutputStream(new FileOutputStream(file))
|
||||
)
|
||||
) {
|
||||
writeFrames(outputStream, frames);
|
||||
} catch (Exception e) {
|
||||
LogManager.severe("Error writing frames to file", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static PoseFrames readFrames(DataInputStream inputStream) {
|
||||
try {
|
||||
|
||||
int trackerCount = inputStream.readInt();
|
||||
FastList<PoseFrameTracker> trackers = new FastList<PoseFrameTracker>(trackerCount);
|
||||
for (int i = 0; i < trackerCount; i++) {
|
||||
|
||||
String name = inputStream.readUTF();
|
||||
int trackerFrameCount = inputStream.readInt();
|
||||
FastList<TrackerFrame> trackerFrames = new FastList<TrackerFrame>(
|
||||
trackerFrameCount
|
||||
);
|
||||
for (int j = 0; j < trackerFrameCount; j++) {
|
||||
int dataFlags = inputStream.readInt();
|
||||
|
||||
TrackerPosition designation = null;
|
||||
if (TrackerFrameData.DESIGNATION.check(dataFlags)) {
|
||||
designation = TrackerPosition
|
||||
.getByDesignation(inputStream.readUTF())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
Quaternion rotation = null;
|
||||
if (TrackerFrameData.ROTATION.check(dataFlags)) {
|
||||
float quatX = inputStream.readFloat();
|
||||
float quatY = inputStream.readFloat();
|
||||
float quatZ = inputStream.readFloat();
|
||||
float quatW = inputStream.readFloat();
|
||||
rotation = new Quaternion(quatX, quatY, quatZ, quatW);
|
||||
}
|
||||
|
||||
Vector3f position = null;
|
||||
if (TrackerFrameData.POSITION.check(dataFlags)) {
|
||||
float posX = inputStream.readFloat();
|
||||
float posY = inputStream.readFloat();
|
||||
float posZ = inputStream.readFloat();
|
||||
position = new Vector3f(posX, posY, posZ);
|
||||
}
|
||||
|
||||
trackerFrames.add(new TrackerFrame(designation, rotation, position));
|
||||
}
|
||||
|
||||
trackers.add(new PoseFrameTracker(name, trackerFrames));
|
||||
}
|
||||
|
||||
return new PoseFrames(trackers);
|
||||
} catch (Exception e) {
|
||||
LogManager.severe("Error reading frame from stream", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PoseFrames readFromFile(File file) {
|
||||
try {
|
||||
return readFrames(
|
||||
new DataInputStream(new BufferedInputStream(new FileInputStream(file)))
|
||||
);
|
||||
} catch (Exception e) {
|
||||
LogManager.severe("Error reading frame from file", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.vr.processor.ComputedHumanPoseTracker;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class PoseFrameSkeleton extends HumanSkeleton {
|
||||
|
||||
private int frameCursor = 0;
|
||||
|
||||
protected PoseFrameSkeleton(List<? extends ComputedHumanPoseTracker> computedTrackers) {
|
||||
super(computedTrackers);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(
|
||||
VRServer server,
|
||||
List<? extends ComputedHumanPoseTracker> computedTrackers
|
||||
) {
|
||||
super(server, computedTrackers);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(
|
||||
List<? extends Tracker> trackers,
|
||||
List<? extends ComputedHumanPoseTracker> computedTrackers
|
||||
) {
|
||||
super(trackers, computedTrackers);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(
|
||||
List<? extends Tracker> trackers,
|
||||
List<? extends ComputedHumanPoseTracker> computedTrackers,
|
||||
Map<SkeletonConfigValue, Float> configs,
|
||||
Map<SkeletonConfigValue, Float> altConfigs
|
||||
) {
|
||||
super(trackers, computedTrackers, configs, altConfigs);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(
|
||||
List<? extends Tracker> trackers,
|
||||
List<? extends ComputedHumanPoseTracker> computedTrackers,
|
||||
Map<SkeletonConfigValue, Float> configs
|
||||
) {
|
||||
super(trackers, computedTrackers, configs);
|
||||
}
|
||||
|
||||
private int limitCursor() {
|
||||
if (frameCursor < 0) {
|
||||
frameCursor = 0;
|
||||
}
|
||||
|
||||
return frameCursor;
|
||||
}
|
||||
|
||||
public int setCursor(int index) {
|
||||
frameCursor = index;
|
||||
return limitCursor();
|
||||
}
|
||||
|
||||
public int incrementCursor(int increment) {
|
||||
frameCursor += increment;
|
||||
return limitCursor();
|
||||
}
|
||||
|
||||
public int incrementCursor() {
|
||||
return incrementCursor(1);
|
||||
}
|
||||
|
||||
public int getCursor() {
|
||||
return frameCursor;
|
||||
}
|
||||
|
||||
// Get tracker for specific frame
|
||||
@Override
|
||||
protected Tracker trackerPreUpdate(Tracker tracker) {
|
||||
if (tracker instanceof PoseFrameTracker) {
|
||||
// Return frame if available, otherwise return the original tracker
|
||||
TrackerFrame frame = ((PoseFrameTracker) tracker).safeGetFrame(frameCursor);
|
||||
return frame == null ? tracker : frame;
|
||||
}
|
||||
return tracker;
|
||||
}
|
||||
}
|
||||
269
src/main/java/dev/slimevr/poserecorder/PoseFrameTracker.java
Normal file
269
src/main/java/dev/slimevr/poserecorder/PoseFrameTracker.java
Normal file
@@ -0,0 +1,269 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerConfig;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
import dev.slimevr.vr.trackers.udp.UDPDevice;
|
||||
import io.eiren.util.collections.FastList;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
public class PoseFrameTracker implements Tracker, Iterable<TrackerFrame> {
|
||||
|
||||
public final String name;
|
||||
|
||||
private final FastList<TrackerFrame> frames;
|
||||
private final int trackerId = Tracker.getNextLocalTrackerId();
|
||||
private int frameCursor = 0;
|
||||
|
||||
public PoseFrameTracker(String name, FastList<TrackerFrame> frames) {
|
||||
if (frames == null) {
|
||||
throw new NullPointerException("frames must not be null");
|
||||
}
|
||||
|
||||
this.name = name != null ? name : "";
|
||||
this.frames = frames;
|
||||
}
|
||||
|
||||
public PoseFrameTracker(String name, int initialCapacity) {
|
||||
this(name, new FastList<TrackerFrame>(initialCapacity));
|
||||
}
|
||||
|
||||
public PoseFrameTracker(Tracker parent, int initialCapacity) {
|
||||
this(parent.getName(), initialCapacity);
|
||||
}
|
||||
|
||||
public PoseFrameTracker(String name) {
|
||||
this(name, 5);
|
||||
}
|
||||
|
||||
public PoseFrameTracker(Tracker parent) {
|
||||
this(parent.getName());
|
||||
}
|
||||
|
||||
private int limitCursor() {
|
||||
if (frameCursor < 0 || frames.isEmpty()) {
|
||||
frameCursor = 0;
|
||||
} else if (frameCursor >= frames.size()) {
|
||||
frameCursor = frames.size() - 1;
|
||||
}
|
||||
|
||||
return frameCursor;
|
||||
}
|
||||
|
||||
public int setCursor(int index) {
|
||||
frameCursor = index;
|
||||
return limitCursor();
|
||||
}
|
||||
|
||||
public int incrementCursor(int increment) {
|
||||
frameCursor += increment;
|
||||
return limitCursor();
|
||||
}
|
||||
|
||||
public int incrementCursor() {
|
||||
return incrementCursor(1);
|
||||
}
|
||||
|
||||
public int getCursor() {
|
||||
return frameCursor;
|
||||
}
|
||||
|
||||
public int getFrameCount() {
|
||||
return frames.size();
|
||||
}
|
||||
|
||||
public TrackerFrame addFrame(int index, TrackerFrame trackerFrame) {
|
||||
frames.add(index, trackerFrame);
|
||||
return trackerFrame;
|
||||
}
|
||||
|
||||
public TrackerFrame addFrame(int index, Tracker tracker) {
|
||||
return addFrame(index, TrackerFrame.fromTracker(tracker));
|
||||
}
|
||||
|
||||
public TrackerFrame addFrame(TrackerFrame trackerFrame) {
|
||||
frames.add(trackerFrame);
|
||||
return trackerFrame;
|
||||
}
|
||||
|
||||
public TrackerFrame addFrame(Tracker tracker) {
|
||||
return addFrame(TrackerFrame.fromTracker(tracker));
|
||||
}
|
||||
|
||||
public TrackerFrame removeFrame(int index) {
|
||||
TrackerFrame trackerFrame = frames.remove(index);
|
||||
limitCursor();
|
||||
return trackerFrame;
|
||||
}
|
||||
|
||||
public TrackerFrame removeFrame(TrackerFrame trackerFrame) {
|
||||
frames.remove(trackerFrame);
|
||||
limitCursor();
|
||||
return trackerFrame;
|
||||
}
|
||||
|
||||
public void clearFrames() {
|
||||
frames.clear();
|
||||
limitCursor();
|
||||
}
|
||||
|
||||
public void fakeClearFrames() {
|
||||
frames.fakeClear();
|
||||
limitCursor();
|
||||
}
|
||||
|
||||
public TrackerFrame getFrame(int index) {
|
||||
return frames.get(index);
|
||||
}
|
||||
|
||||
public TrackerFrame getFrame() {
|
||||
return getFrame(frameCursor);
|
||||
}
|
||||
|
||||
public TrackerFrame safeGetFrame(int index) {
|
||||
try {
|
||||
return getFrame(index);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public TrackerFrame safeGetFrame() {
|
||||
return safeGetFrame(frameCursor);
|
||||
}
|
||||
|
||||
// #region Tracker Interface Implementation
|
||||
@Override
|
||||
public boolean getRotation(Quaternion store) {
|
||||
TrackerFrame frame = safeGetFrame();
|
||||
if (frame != null && frame.hasData(TrackerFrameData.ROTATION)) {
|
||||
store.set(frame.rotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(Quaternion.IDENTITY);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPosition(Vector3f store) {
|
||||
TrackerFrame frame = safeGetFrame();
|
||||
if (frame != null && frame.hasData(TrackerFrameData.POSITION)) {
|
||||
store.set(frame.position);
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(Vector3f.ZERO);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackerStatus getStatus() {
|
||||
return TrackerStatus.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig(TrackerConfig config) {
|
||||
throw new UnsupportedOperationException(
|
||||
"PoseFrameTracker does not implement configuration"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveConfig(TrackerConfig config) {
|
||||
throw new UnsupportedOperationException(
|
||||
"PoseFrameTracker does not implement configuration"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getConfidenceLevel() {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetFull(Quaternion reference) {
|
||||
throw new UnsupportedOperationException("PoseFrameTracker does not implement calibration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetYaw(Quaternion reference) {
|
||||
throw new UnsupportedOperationException("PoseFrameTracker does not implement calibration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
throw new UnsupportedOperationException("PoseFrameTracker does not implement this method");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackerPosition getBodyPosition() {
|
||||
TrackerFrame frame = safeGetFrame();
|
||||
return frame == null ? null : frame.designation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBodyPosition(TrackerPosition position) {
|
||||
throw new UnsupportedOperationException(
|
||||
"PoseFrameTracker does not allow setting the body position"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRotation() {
|
||||
TrackerFrame frame = safeGetFrame();
|
||||
return frame != null && frame.hasData(TrackerFrameData.ROTATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPosition() {
|
||||
TrackerFrame frame = safeGetFrame();
|
||||
return frame != null && frame.hasData(TrackerFrameData.POSITION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComputed() {
|
||||
return true;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
@Override
|
||||
public Iterator<TrackerFrame> iterator() {
|
||||
return frames.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackerId() {
|
||||
return this.trackerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackerNum() {
|
||||
return this.getTrackerId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UDPDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tracker get() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
290
src/main/java/dev/slimevr/poserecorder/PoseFrames.java
Normal file
290
src/main/java/dev/slimevr/poserecorder/PoseFrames.java
Normal file
@@ -0,0 +1,290 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerUtils;
|
||||
import io.eiren.util.collections.FastList;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
|
||||
public final class PoseFrames implements Iterable<TrackerFrame[]> {
|
||||
|
||||
private final FastList<PoseFrameTracker> trackers;
|
||||
|
||||
/**
|
||||
* Creates a {@link PoseFrames} object with the provided list of
|
||||
* {@link PoseFrameTracker}s as the internal {@link PoseFrameTracker} list
|
||||
*
|
||||
* @see {@link FastList}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public PoseFrames(FastList<PoseFrameTracker> trackers) {
|
||||
this.trackers = trackers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PoseFrames} object with the specified initial tracker
|
||||
* capacity
|
||||
*
|
||||
* @see {@link #PoseFrames(FastList)}
|
||||
*/
|
||||
public PoseFrames(int initialCapacity) {
|
||||
this.trackers = new FastList<PoseFrameTracker>(initialCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link PoseFrames} object with the default initial tracker
|
||||
* capacity of {@code 5}
|
||||
*
|
||||
* @see {@link #PoseFrames(int)}
|
||||
*/
|
||||
public PoseFrames() {
|
||||
this(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the provided {@link PoseFrameTracker} into the internal
|
||||
* {@link PoseFrameTracker} list
|
||||
*
|
||||
* @return The {@link PoseFrameTracker} provided
|
||||
* @see {@link List#add(Object)}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public PoseFrameTracker addTracker(PoseFrameTracker tracker) {
|
||||
trackers.add(tracker);
|
||||
return tracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the {@link PoseFrameTracker} at the specified index from the
|
||||
* internal {@link PoseFrameTracker} list
|
||||
*
|
||||
* @return The {@link PoseFrameTracker} previously at the specified index
|
||||
* @see {@link List#remove(int)}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public PoseFrameTracker removeTracker(int index) {
|
||||
return trackers.remove(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified {@link PoseFrameTracker} from the internal
|
||||
* {@link PoseFrameTracker} list
|
||||
*
|
||||
* @return {@code true} if the internal {@link PoseFrameTracker} list
|
||||
* contained the specified {@link PoseFrameTracker}
|
||||
* @see {@link List#remove(Object)}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public boolean removeTracker(PoseFrameTracker tracker) {
|
||||
return trackers.remove(tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the internal {@link PoseFrameTracker} list
|
||||
*
|
||||
* @see {@link List#clear()}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public void clearTrackers() {
|
||||
trackers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake clears the internal {@link PoseFrameTracker} list by setting the
|
||||
* size to zero
|
||||
*
|
||||
* @see {@link FastList#fakeClear()}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public void fakeClearTrackers() {
|
||||
trackers.fakeClear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of contained {@link PoseFrameTracker} objects
|
||||
* @see {@link List#size()}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public int getTrackerCount() {
|
||||
return trackers.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of the contained {@link PoseFrameTracker} objects
|
||||
* @see {@link List}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public List<PoseFrameTracker> getTrackers() {
|
||||
return trackers;
|
||||
}
|
||||
|
||||
// #region Data Utilities
|
||||
|
||||
/**
|
||||
* A utility function to get the maximum Y value of the tracker associated
|
||||
* with the {@link TrackerPosition#HMD} tracker position
|
||||
*
|
||||
* @return The maximum Y value of the tracker associated with the
|
||||
* {@link TrackerPosition#HMD} tracker position
|
||||
* @see {@link #getMaxHeight(TrackerPosition)}, {@link TrackerPosition#HMD}
|
||||
*/
|
||||
public float getMaxHmdHeight() {
|
||||
return getMaxHeight(TrackerPosition.HMD);
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility function to get the maximum Y value of the tracker associated
|
||||
* with the specified {@link TrackerPosition}
|
||||
*
|
||||
* @return The maximum Y value of the tracker associated with the specified
|
||||
* {@link TrackerPosition}
|
||||
* @see {@link TrackerPosition}
|
||||
*/
|
||||
public float getMaxHeight(TrackerPosition trackerPosition) {
|
||||
float maxHeight = 0f;
|
||||
|
||||
PoseFrameTracker hmd = TrackerUtils
|
||||
.findNonComputedHumanPoseTrackerForBodyPosition(trackers, trackerPosition);
|
||||
|
||||
if (hmd == null) {
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
for (TrackerFrame frame : hmd) {
|
||||
if (frame.hasData(TrackerFrameData.POSITION) && frame.position.y > maxHeight) {
|
||||
maxHeight = frame.position.y;
|
||||
}
|
||||
}
|
||||
|
||||
return maxHeight;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* @return The maximum number of {@link TrackerFrame}s contained within each
|
||||
* {@link PoseFrameTracker} in the internal {@link PoseFrameTracker} list
|
||||
* @see {@link PoseFrameTracker#getFrameCount()}, {@link PoseFrameTracker}
|
||||
*/
|
||||
public int getMaxFrameCount() {
|
||||
int maxFrames = 0;
|
||||
|
||||
for (PoseFrameTracker tracker : trackers) {
|
||||
if (tracker != null && tracker.getFrameCount() > maxFrames) {
|
||||
maxFrames = tracker.getFrameCount();
|
||||
}
|
||||
}
|
||||
|
||||
return maxFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the provided array buffer, get the {@link TrackerFrame}s contained
|
||||
* within each {@link PoseFrameTracker} in the internal
|
||||
* {@link PoseFrameTracker} list at the specified index
|
||||
*
|
||||
* @return The number of frames written to the buffer
|
||||
* @see {@link PoseFrameTracker#safeGetFrame(int)}, {@link TrackerFrame},
|
||||
* {@link PoseFrameTracker}
|
||||
*/
|
||||
public int getFrames(int frameIndex, TrackerFrame[] buffer) {
|
||||
int frameCount = 0;
|
||||
|
||||
for (PoseFrameTracker tracker : trackers) {
|
||||
if (tracker == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TrackerFrame frame = tracker.safeGetFrame(frameIndex);
|
||||
|
||||
if (frame == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer[frameCount++] = frame;
|
||||
}
|
||||
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the provided {@link List} buffer, get the {@link TrackerFrame}s
|
||||
* contained within each {@link PoseFrameTracker} in the internal
|
||||
* {@link PoseFrameTracker} list at the specified index
|
||||
*
|
||||
* @return The number of frames written to the buffer
|
||||
* @see {@link PoseFrameTracker#safeGetFrame(int)},
|
||||
* {@link List#set(int, Object)}, {@link TrackerFrame},
|
||||
* {@link PoseFrameTracker}
|
||||
*/
|
||||
public int getFrames(int frameIndex, List<TrackerFrame> buffer) {
|
||||
int frameCount = 0;
|
||||
|
||||
for (PoseFrameTracker tracker : trackers) {
|
||||
if (tracker == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TrackerFrame frame = tracker.safeGetFrame(frameIndex);
|
||||
|
||||
if (frame == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
buffer.set(frameCount++, frame);
|
||||
}
|
||||
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link TrackerFrame}s contained within each
|
||||
* {@link PoseFrameTracker} in the internal {@link PoseFrameTracker} list at
|
||||
* the specified index
|
||||
* @see {@link PoseFrameTracker#safeGetFrame(int)}, {@link TrackerFrame},
|
||||
* {@link PoseFrameTracker}
|
||||
*/
|
||||
public TrackerFrame[] getFrames(int frameIndex) {
|
||||
TrackerFrame[] trackerFrames = new TrackerFrame[trackers.size()];
|
||||
getFrames(frameIndex, trackerFrames);
|
||||
return trackerFrames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<TrackerFrame[]> iterator() {
|
||||
return new PoseFrameIterator(this);
|
||||
}
|
||||
|
||||
public class PoseFrameIterator implements Iterator<TrackerFrame[]> {
|
||||
|
||||
private final PoseFrames poseFrame;
|
||||
private final TrackerFrame[] trackerFrameBuffer;
|
||||
|
||||
private int cursor = 0;
|
||||
|
||||
public PoseFrameIterator(PoseFrames poseFrame) {
|
||||
this.poseFrame = poseFrame;
|
||||
trackerFrameBuffer = new TrackerFrame[poseFrame.getTrackerCount()];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
if (trackers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PoseFrameTracker tracker : trackers) {
|
||||
if (tracker != null && cursor < tracker.getFrameCount()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackerFrame[] next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
|
||||
poseFrame.getFrames(cursor++, trackerFrameBuffer);
|
||||
|
||||
return trackerFrameBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
231
src/main/java/dev/slimevr/poserecorder/PoseRecorder.java
Normal file
231
src/main/java/dev/slimevr/poserecorder/PoseRecorder.java
Normal file
@@ -0,0 +1,231 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
|
||||
public class PoseRecorder {
|
||||
|
||||
public class RecordingProgress {
|
||||
|
||||
public final int frame;
|
||||
public final int totalFrames;
|
||||
|
||||
public RecordingProgress(int frame, int totalFrames) {
|
||||
this.frame = frame;
|
||||
this.totalFrames = totalFrames;
|
||||
}
|
||||
}
|
||||
|
||||
protected PoseFrames poseFrame = null;
|
||||
|
||||
protected int numFrames = -1;
|
||||
protected int frameCursor = 0;
|
||||
protected long frameRecordingInterval = 60L;
|
||||
protected long nextFrameTimeMs = -1L;
|
||||
|
||||
protected CompletableFuture<PoseFrames> currentRecording;
|
||||
protected Consumer<RecordingProgress> currentFrameCallback;
|
||||
|
||||
protected final VRServer server;
|
||||
FastList<Pair<Tracker, PoseFrameTracker>> trackers = new FastList<Pair<Tracker, PoseFrameTracker>>();
|
||||
|
||||
public PoseRecorder(VRServer server) {
|
||||
this.server = server;
|
||||
server.addOnTick(this::onTick);
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public void onTick() {
|
||||
if (numFrames <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
PoseFrames poseFrame = this.poseFrame;
|
||||
List<Pair<Tracker, PoseFrameTracker>> trackers = this.trackers;
|
||||
if (poseFrame == null || trackers == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frameCursor >= numFrames) {
|
||||
// If done and hasn't yet, send finished recording
|
||||
stopFrameRecording();
|
||||
return;
|
||||
}
|
||||
|
||||
long curTime = System.currentTimeMillis();
|
||||
if (curTime < nextFrameTimeMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextFrameTimeMs += frameRecordingInterval;
|
||||
|
||||
// To prevent duplicate frames, make sure the frame time is always in
|
||||
// the future
|
||||
if (nextFrameTimeMs <= curTime) {
|
||||
nextFrameTimeMs = curTime + frameRecordingInterval;
|
||||
}
|
||||
|
||||
// Make sure it's synchronized since this is the server thread
|
||||
// interacting with
|
||||
// an unknown outside thread controlling this class
|
||||
synchronized (this) {
|
||||
// A stopped recording will be accounted for by an empty "trackers"
|
||||
// list
|
||||
int cursor = frameCursor++;
|
||||
for (Pair<Tracker, PoseFrameTracker> tracker : trackers) {
|
||||
// Add a frame for each tracker
|
||||
tracker.getRight().addFrame(cursor, tracker.getLeft());
|
||||
}
|
||||
|
||||
if (currentFrameCallback != null) {
|
||||
currentFrameCallback.accept(new RecordingProgress(frameCursor, numFrames));
|
||||
}
|
||||
|
||||
// If done, send finished recording
|
||||
if (frameCursor >= numFrames) {
|
||||
stopFrameRecording();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long intervalMs) {
|
||||
return startFrameRecording(numFrames, intervalMs, server.getAllTrackers(), null);
|
||||
}
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(
|
||||
int numFrames,
|
||||
long intervalMs,
|
||||
Consumer<RecordingProgress> frameCallback
|
||||
) {
|
||||
return startFrameRecording(numFrames, intervalMs, server.getAllTrackers(), frameCallback);
|
||||
}
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(
|
||||
int numFrames,
|
||||
long intervalMs,
|
||||
List<Tracker> trackers
|
||||
) {
|
||||
return startFrameRecording(numFrames, intervalMs, trackers, null);
|
||||
}
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(
|
||||
int numFrames,
|
||||
long intervalMs,
|
||||
List<Tracker> trackers,
|
||||
Consumer<RecordingProgress> frameCallback
|
||||
) {
|
||||
if (numFrames < 1) {
|
||||
throw new IllegalArgumentException("numFrames must at least have a value of 1");
|
||||
}
|
||||
if (intervalMs < 1) {
|
||||
throw new IllegalArgumentException("intervalMs must at least have a value of 1");
|
||||
}
|
||||
if (trackers == null) {
|
||||
throw new IllegalArgumentException("trackers must not be null");
|
||||
}
|
||||
if (trackers.isEmpty()) {
|
||||
throw new IllegalArgumentException("trackers must have at least one entry");
|
||||
}
|
||||
if (!isReadyToRecord()) {
|
||||
throw new IllegalStateException("PoseRecorder isn't ready to record!");
|
||||
}
|
||||
|
||||
cancelFrameRecording();
|
||||
|
||||
poseFrame = new PoseFrames(trackers.size());
|
||||
|
||||
// Update tracker list
|
||||
this.trackers.ensureCapacity(trackers.size());
|
||||
for (Tracker tracker : trackers) {
|
||||
// Ignore null and computed trackers
|
||||
if (tracker == null || tracker.isComputed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a tracker recording
|
||||
PoseFrameTracker poseFrameTracker = new PoseFrameTracker(tracker, numFrames);
|
||||
poseFrame.addTracker(poseFrameTracker);
|
||||
|
||||
// Pair tracker with recording
|
||||
this.trackers.add(Pair.of(tracker, poseFrameTracker));
|
||||
}
|
||||
|
||||
this.frameCursor = 0;
|
||||
this.numFrames = numFrames;
|
||||
|
||||
frameRecordingInterval = intervalMs;
|
||||
nextFrameTimeMs = -1L;
|
||||
|
||||
LogManager
|
||||
.info(
|
||||
"[PoseRecorder] Recording "
|
||||
+ numFrames
|
||||
+ " samples at a "
|
||||
+ intervalMs
|
||||
+ " ms frame interval"
|
||||
);
|
||||
|
||||
currentFrameCallback = frameCallback;
|
||||
currentRecording = new CompletableFuture<PoseFrames>();
|
||||
return currentRecording;
|
||||
}
|
||||
|
||||
public synchronized void stopFrameRecording() {
|
||||
CompletableFuture<PoseFrames> currentRecording = this.currentRecording;
|
||||
if (currentRecording != null && !currentRecording.isDone()) {
|
||||
// Stop the recording, returning the frames recorded
|
||||
currentRecording.complete(poseFrame);
|
||||
}
|
||||
|
||||
numFrames = -1;
|
||||
frameCursor = 0;
|
||||
trackers.clear();
|
||||
poseFrame = null;
|
||||
}
|
||||
|
||||
public synchronized void cancelFrameRecording() {
|
||||
CompletableFuture<PoseFrames> currentRecording = this.currentRecording;
|
||||
if (currentRecording != null && !currentRecording.isDone()) {
|
||||
// Cancel the current recording and return nothing
|
||||
currentRecording.cancel(true);
|
||||
}
|
||||
|
||||
numFrames = -1;
|
||||
frameCursor = 0;
|
||||
trackers.clear();
|
||||
poseFrame = null;
|
||||
}
|
||||
|
||||
public synchronized boolean isReadyToRecord() {
|
||||
return server.getTrackersCount() > 0;
|
||||
}
|
||||
|
||||
public synchronized boolean isRecording() {
|
||||
return numFrames > frameCursor;
|
||||
}
|
||||
|
||||
public synchronized boolean hasRecording() {
|
||||
return currentRecording != null;
|
||||
}
|
||||
|
||||
public synchronized Future<PoseFrames> getFramesAsync() {
|
||||
return currentRecording;
|
||||
}
|
||||
|
||||
public synchronized PoseFrames getFrames() throws ExecutionException, InterruptedException {
|
||||
CompletableFuture<PoseFrames> currentRecording = this.currentRecording;
|
||||
return currentRecording != null ? currentRecording.get() : null;
|
||||
}
|
||||
}
|
||||
200
src/main/java/dev/slimevr/poserecorder/TrackerFrame.java
Normal file
200
src/main/java/dev/slimevr/poserecorder/TrackerFrame.java
Normal file
@@ -0,0 +1,200 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerConfig;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
import dev.slimevr.vr.trackers.udp.UDPDevice;
|
||||
|
||||
|
||||
public final class TrackerFrame implements Tracker {
|
||||
|
||||
public final TrackerPosition designation;
|
||||
public final Quaternion rotation;
|
||||
public final Vector3f position;
|
||||
private final int trackerId = Tracker.getNextLocalTrackerId();
|
||||
private int dataFlags = 0;
|
||||
|
||||
public TrackerFrame(TrackerPosition designation, Quaternion rotation, Vector3f position) {
|
||||
this.designation = designation;
|
||||
if (designation != null) {
|
||||
dataFlags |= TrackerFrameData.DESIGNATION.flag;
|
||||
}
|
||||
|
||||
this.rotation = rotation;
|
||||
if (rotation != null) {
|
||||
dataFlags |= TrackerFrameData.ROTATION.flag;
|
||||
}
|
||||
|
||||
this.position = position;
|
||||
if (position != null) {
|
||||
dataFlags |= TrackerFrameData.POSITION.flag;
|
||||
}
|
||||
}
|
||||
|
||||
public static TrackerFrame fromTracker(Tracker tracker) {
|
||||
if (tracker == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the tracker is not ready
|
||||
if (
|
||||
tracker.getStatus() != TrackerStatus.OK
|
||||
&& tracker.getStatus() != TrackerStatus.BUSY
|
||||
&& tracker.getStatus() != TrackerStatus.OCCLUDED
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If tracker has no data
|
||||
if (tracker.getBodyPosition() == null && !tracker.hasRotation() && !tracker.hasPosition()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Quaternion rotation = null;
|
||||
if (tracker.hasRotation()) {
|
||||
rotation = new Quaternion();
|
||||
if (!tracker.getRotation(rotation)) {
|
||||
// If getting the rotation failed, set it back to null
|
||||
rotation = null;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3f position = null;
|
||||
if (tracker.hasPosition()) {
|
||||
position = new Vector3f();
|
||||
if (!tracker.getPosition(position)) {
|
||||
// If getting the position failed, set it back to null
|
||||
position = null;
|
||||
}
|
||||
}
|
||||
|
||||
return new TrackerFrame(tracker.getBodyPosition(), rotation, position);
|
||||
}
|
||||
|
||||
public int getDataFlags() {
|
||||
return dataFlags;
|
||||
}
|
||||
|
||||
public boolean hasData(TrackerFrameData flag) {
|
||||
return flag.check(dataFlags);
|
||||
}
|
||||
|
||||
// #region Tracker Interface Implementation
|
||||
@Override
|
||||
public boolean getRotation(Quaternion store) {
|
||||
if (hasData(TrackerFrameData.ROTATION)) {
|
||||
store.set(rotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(Quaternion.IDENTITY);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getPosition(Vector3f store) {
|
||||
if (hasData(TrackerFrameData.POSITION)) {
|
||||
store.set(position);
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(Vector3f.ZERO);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "TrackerFrame:/" + (designation != null ? designation.designation : "null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackerStatus getStatus() {
|
||||
return TrackerStatus.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadConfig(TrackerConfig config) {
|
||||
throw new UnsupportedOperationException("TrackerFrame does not implement configuration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveConfig(TrackerConfig config) {
|
||||
throw new UnsupportedOperationException("TrackerFrame does not implement configuration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getConfidenceLevel() {
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetFull(Quaternion reference) {
|
||||
throw new UnsupportedOperationException("TrackerFrame does not implement calibration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetYaw(Quaternion reference) {
|
||||
throw new UnsupportedOperationException("TrackerFrame does not implement calibration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
throw new UnsupportedOperationException("TrackerFrame does not implement this method");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TrackerPosition getBodyPosition() {
|
||||
return designation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBodyPosition(TrackerPosition position) {
|
||||
throw new UnsupportedOperationException(
|
||||
"TrackerFrame does not allow setting the body position"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean userEditable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRotation() {
|
||||
return hasData(TrackerFrameData.ROTATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPosition() {
|
||||
return hasData(TrackerFrameData.POSITION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComputed() {
|
||||
return true;
|
||||
}
|
||||
// #endregion
|
||||
|
||||
@Override
|
||||
public int getTrackerId() {
|
||||
return this.trackerId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackerNum() {
|
||||
return this.getTrackerId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UDPDevice getDevice() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tracker get() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
16
src/main/java/dev/slimevr/poserecorder/TrackerFrameData.java
Normal file
16
src/main/java/dev/slimevr/poserecorder/TrackerFrameData.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
public enum TrackerFrameData {
|
||||
|
||||
DESIGNATION(0), ROTATION(1), POSITION(2),;
|
||||
|
||||
public final int flag;
|
||||
|
||||
TrackerFrameData(int id) {
|
||||
this.flag = 1 << id;
|
||||
}
|
||||
|
||||
public boolean check(int dataFlags) {
|
||||
return (dataFlags & this.flag) != 0;
|
||||
}
|
||||
}
|
||||
297
src/main/java/dev/slimevr/posestreamer/BVHFileStream.java
Normal file
297
src/main/java/dev/slimevr/posestreamer/BVHFileStream.java
Normal file
@@ -0,0 +1,297 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
public class BVHFileStream extends PoseDataStream {
|
||||
|
||||
private static final int LONG_MAX_VALUE_DIGITS = Long.toString(Long.MAX_VALUE).length();
|
||||
private static final float OFFSET_SCALE = 100f;
|
||||
private static final float POSITION_SCALE = 100f;
|
||||
private final BufferedWriter writer;
|
||||
private long frameCount = 0;
|
||||
private long frameCountOffset;
|
||||
|
||||
private float[] angleBuf = new float[3];
|
||||
private Quaternion rotBuf = new Quaternion();
|
||||
|
||||
private Skeleton wrappedSkeleton;
|
||||
private TransformNodeWrapper rootNode;
|
||||
|
||||
public BVHFileStream(OutputStream outputStream) {
|
||||
super(outputStream);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 4096);
|
||||
}
|
||||
|
||||
public BVHFileStream(File file) throws FileNotFoundException {
|
||||
super(file);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 4096);
|
||||
}
|
||||
|
||||
public BVHFileStream(String file) throws FileNotFoundException {
|
||||
super(file);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 4096);
|
||||
}
|
||||
|
||||
private String getBufferedFrameCount(long frameCount) {
|
||||
String frameString = Long.toString(frameCount);
|
||||
int bufferCount = LONG_MAX_VALUE_DIGITS - frameString.length();
|
||||
|
||||
return bufferCount > 0 ? frameString + StringUtils.repeat(' ', bufferCount) : frameString;
|
||||
}
|
||||
|
||||
private TransformNodeWrapper wrapSkeletonIfNew(Skeleton skeleton) {
|
||||
TransformNodeWrapper wrapper = rootNode;
|
||||
|
||||
// If the wrapped skeleton is missing or the skeleton is updated
|
||||
if (wrapper == null || skeleton != wrappedSkeleton) {
|
||||
wrapper = wrapSkeleton(skeleton);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private TransformNodeWrapper wrapSkeleton(Skeleton skeleton) {
|
||||
TransformNodeWrapper wrapper = wrapSkeletonNodes(skeleton.getRootNode());
|
||||
|
||||
wrappedSkeleton = skeleton;
|
||||
rootNode = wrapper;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
protected TransformNodeWrapper wrapSkeletonNodes(TransformNode rootNode) {
|
||||
return TransformNodeWrapper.wrapFullHierarchy(rootNode);
|
||||
}
|
||||
|
||||
private void writeNodeHierarchy(TransformNodeWrapper node) throws IOException {
|
||||
writeNodeHierarchy(node, 0);
|
||||
}
|
||||
|
||||
private void writeNodeHierarchy(TransformNodeWrapper node, int level) throws IOException {
|
||||
// Don't write end sites at populated nodes
|
||||
if (node.children.isEmpty() && node.getParent().children.size() > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
String indentLevel = StringUtils.repeat("\t", level);
|
||||
String nextIndentLevel = indentLevel + "\t";
|
||||
|
||||
// Handle ends
|
||||
if (node.children.isEmpty()) {
|
||||
writer.write(indentLevel + "End Site\n");
|
||||
} else {
|
||||
writer.write((level > 0 ? indentLevel + "JOINT " : "ROOT ") + node.getName() + "\n");
|
||||
}
|
||||
writer.write(indentLevel + "{\n");
|
||||
|
||||
// Ignore the root offset and original root offset
|
||||
if (level > 0 && node.wrappedNode.getParent() != null) {
|
||||
Vector3f offset = node.localTransform.getTranslation();
|
||||
float reverseMultiplier = node.hasReversedHierarchy() ? -1 : 1;
|
||||
writer
|
||||
.write(
|
||||
nextIndentLevel
|
||||
+ "OFFSET "
|
||||
+ offset.getX() * OFFSET_SCALE * reverseMultiplier
|
||||
+ " "
|
||||
+ offset.getY() * OFFSET_SCALE * reverseMultiplier
|
||||
+ " "
|
||||
+ offset.getZ() * OFFSET_SCALE * reverseMultiplier
|
||||
+ "\n"
|
||||
);
|
||||
} else {
|
||||
writer.write(nextIndentLevel + "OFFSET 0.0 0.0 0.0\n");
|
||||
}
|
||||
|
||||
// Handle ends
|
||||
if (!node.children.isEmpty()) {
|
||||
// Only give position for root
|
||||
if (level > 0) {
|
||||
writer.write(nextIndentLevel + "CHANNELS 3 Zrotation Xrotation Yrotation\n");
|
||||
} else {
|
||||
writer
|
||||
.write(
|
||||
nextIndentLevel
|
||||
+ "CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation\n"
|
||||
);
|
||||
}
|
||||
|
||||
for (TransformNodeWrapper childNode : node.children) {
|
||||
writeNodeHierarchy(childNode, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
writer.write(indentLevel + "}\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeHeader(Skeleton skeleton, PoseStreamer streamer) throws IOException {
|
||||
if (skeleton == null) {
|
||||
throw new NullPointerException("skeleton must not be null");
|
||||
}
|
||||
if (streamer == null) {
|
||||
throw new NullPointerException("streamer must not be null");
|
||||
}
|
||||
|
||||
writer.write("HIERARCHY\n");
|
||||
writeNodeHierarchy(wrapSkeletonIfNew(skeleton));
|
||||
|
||||
writer.write("MOTION\n");
|
||||
writer.write("Frames: ");
|
||||
|
||||
// Get frame offset for finishing writing the file
|
||||
if (outputStream instanceof FileOutputStream) {
|
||||
FileOutputStream fileOutputStream = (FileOutputStream) outputStream;
|
||||
// Flush buffer to get proper offset
|
||||
writer.flush();
|
||||
frameCountOffset = fileOutputStream.getChannel().position();
|
||||
}
|
||||
|
||||
writer.write(getBufferedFrameCount(frameCount) + "\n");
|
||||
|
||||
// Frame time in seconds
|
||||
writer.write("Frame Time: " + (streamer.getFrameInterval() / 1000d) + "\n");
|
||||
}
|
||||
|
||||
// Roughly based off code from
|
||||
// https://github.com/TrackLab/ViRe/blob/50a987eff4db31036b2ebaeb5a28983cd473f267/Assets/Scripts/BVH/BVHRecorder.cs
|
||||
private float[] quatToXyzAngles(Quaternion q, float[] angles) {
|
||||
if (angles == null) {
|
||||
angles = new float[3];
|
||||
} else if (angles.length != 3) {
|
||||
throw new IllegalArgumentException("Angles array must have three elements");
|
||||
}
|
||||
|
||||
float x = q.getX();
|
||||
float y = q.getY();
|
||||
float z = q.getZ();
|
||||
float w = q.getW();
|
||||
|
||||
// Roll (X)
|
||||
float sinrCosp = -2f * (x * y - w * z);
|
||||
float cosrCosp = w * w - x * x + y * y - z * z;
|
||||
angles[0] = FastMath.atan2(sinrCosp, cosrCosp);
|
||||
|
||||
// Pitch (Y)
|
||||
float sinp = 2f * (y * z + w * x);
|
||||
// Use 90 degrees if out of range
|
||||
angles[1] = FastMath.abs(sinp) >= 1f
|
||||
? FastMath.copysign(FastMath.PI / 2f, sinp)
|
||||
: FastMath
|
||||
.asin(sinp);
|
||||
|
||||
// Yaw (Z)
|
||||
float sinyCosp = -2f * (x * z - w * y);
|
||||
float cosyCosp = w * w - x * x - y * y + z * z;
|
||||
angles[2] = FastMath.atan2(sinyCosp, cosyCosp);
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
private void writeNodeHierarchyRotation(TransformNodeWrapper node, Quaternion inverseRootRot)
|
||||
throws IOException {
|
||||
Transform transform = node.worldTransform;
|
||||
|
||||
/*
|
||||
* if (node.hasReversedHierarchy()) { for (TransformNodeWrapper
|
||||
* childNode : node.children) { // If the hierarchy is fully reversed,
|
||||
* set the rotation for the upper bone if
|
||||
* (childNode.hasReversedHierarchy()) { transform =
|
||||
* childNode.worldTransform; break; } } }
|
||||
*/
|
||||
|
||||
rotBuf = transform.getRotation(rotBuf);
|
||||
|
||||
// Adjust to local rotation
|
||||
if (inverseRootRot != null) {
|
||||
rotBuf = inverseRootRot.mult(rotBuf, rotBuf);
|
||||
}
|
||||
|
||||
// Yaw (Z), roll (X), pitch (Y) (intrinsic)
|
||||
// angleBuf = rotBuf.toAngles(angleBuf);
|
||||
|
||||
// Roll (X), pitch (Y), yaw (Z) (intrinsic)
|
||||
angleBuf = quatToXyzAngles(rotBuf.normalizeLocal(), angleBuf);
|
||||
|
||||
// Output in order of roll (Z), pitch (X), yaw (Y) (extrinsic)
|
||||
writer
|
||||
.write(
|
||||
angleBuf[0] * FastMath.RAD_TO_DEG
|
||||
+ " "
|
||||
+ angleBuf[1] * FastMath.RAD_TO_DEG
|
||||
+ " "
|
||||
+ angleBuf[2] * FastMath.RAD_TO_DEG
|
||||
);
|
||||
|
||||
// Get inverse rotation for child local rotations
|
||||
if (!node.children.isEmpty()) {
|
||||
Quaternion inverseRot = transform.getRotation().inverse();
|
||||
for (TransformNodeWrapper childNode : node.children) {
|
||||
if (childNode.children.isEmpty()) {
|
||||
// If it's an end node, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add spacing
|
||||
writer.write(" ");
|
||||
writeNodeHierarchyRotation(childNode, inverseRot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFrame(Skeleton skeleton) throws IOException {
|
||||
if (skeleton == null) {
|
||||
throw new NullPointerException("skeleton must not be null");
|
||||
}
|
||||
|
||||
TransformNodeWrapper rootNode = wrapSkeletonIfNew(skeleton);
|
||||
|
||||
Vector3f rootPos = rootNode.worldTransform.getTranslation();
|
||||
|
||||
// Write root position
|
||||
writer
|
||||
.write(
|
||||
rootPos.getX() * POSITION_SCALE
|
||||
+ " "
|
||||
+ rootPos.getY() * POSITION_SCALE
|
||||
+ " "
|
||||
+ rootPos.getZ() * POSITION_SCALE
|
||||
+ " "
|
||||
);
|
||||
writeNodeHierarchyRotation(rootNode, null);
|
||||
|
||||
writer.newLine();
|
||||
|
||||
frameCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFooter(Skeleton skeleton) throws IOException {
|
||||
// Write the final frame count for files
|
||||
if (outputStream instanceof FileOutputStream) {
|
||||
FileOutputStream fileOutputStream = (FileOutputStream) outputStream;
|
||||
// Flush before anything else
|
||||
writer.flush();
|
||||
// Seek to the count offset
|
||||
fileOutputStream.getChannel().position(frameCountOffset);
|
||||
// Overwrite the count with a new value
|
||||
writer.write(Long.toString(frameCount));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
writer.close();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
42
src/main/java/dev/slimevr/posestreamer/PoseDataStream.java
Normal file
42
src/main/java/dev/slimevr/posestreamer/PoseDataStream.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
|
||||
public abstract class PoseDataStream implements AutoCloseable {
|
||||
|
||||
protected final OutputStream outputStream;
|
||||
protected boolean closed = false;
|
||||
|
||||
protected PoseDataStream(OutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
}
|
||||
|
||||
protected PoseDataStream(File file) throws FileNotFoundException {
|
||||
this(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
protected PoseDataStream(String file) throws FileNotFoundException {
|
||||
this(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
public void writeHeader(Skeleton skeleton, PoseStreamer streamer) throws IOException {
|
||||
}
|
||||
|
||||
abstract void writeFrame(Skeleton skeleton) throws IOException;
|
||||
|
||||
public void writeFooter(Skeleton skeleton) throws IOException {
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
outputStream.close();
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import dev.slimevr.poserecorder.PoseFrameIO;
|
||||
import dev.slimevr.poserecorder.PoseFrameSkeleton;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
public class PoseFrameStreamer extends PoseStreamer {
|
||||
|
||||
private final PoseFrames frames;
|
||||
|
||||
public PoseFrameStreamer(String path) {
|
||||
this(new File(path));
|
||||
}
|
||||
|
||||
public PoseFrameStreamer(File file) {
|
||||
this(PoseFrameIO.readFromFile(file));
|
||||
}
|
||||
|
||||
public PoseFrameStreamer(PoseFrames frames) {
|
||||
super(new PoseFrameSkeleton(frames.getTrackers(), null));
|
||||
this.frames = frames;
|
||||
}
|
||||
|
||||
public PoseFrames getFrames() {
|
||||
return frames;
|
||||
}
|
||||
|
||||
public synchronized void streamAllFrames() {
|
||||
PoseFrameSkeleton skeleton = (PoseFrameSkeleton) this.skeleton;
|
||||
for (int i = 0; i < frames.getMaxFrameCount(); i++) {
|
||||
skeleton.setCursor(i);
|
||||
skeleton.updatePose();
|
||||
captureFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/main/java/dev/slimevr/posestreamer/PoseStreamer.java
Normal file
80
src/main/java/dev/slimevr/posestreamer/PoseStreamer.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class PoseStreamer {
|
||||
|
||||
protected long frameRecordingInterval = 60L;
|
||||
|
||||
protected Skeleton skeleton;
|
||||
protected PoseDataStream poseFileStream;
|
||||
|
||||
public PoseStreamer(Skeleton skeleton) {
|
||||
this.skeleton = skeleton;
|
||||
}
|
||||
|
||||
public synchronized void captureFrame() {
|
||||
// Make sure the stream is open before trying to write
|
||||
if (poseFileStream.isClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
poseFileStream.writeFrame(skeleton);
|
||||
} catch (Exception e) {
|
||||
// Handle any exceptions without crashing the program
|
||||
LogManager.severe("[PoseStreamer] Exception while saving frame", e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized long getFrameInterval() {
|
||||
return frameRecordingInterval;
|
||||
}
|
||||
|
||||
public synchronized void setFrameInterval(long intervalMs) {
|
||||
if (intervalMs < 1) {
|
||||
throw new IllegalArgumentException("intervalMs must at least have a value of 1");
|
||||
}
|
||||
|
||||
this.frameRecordingInterval = intervalMs;
|
||||
}
|
||||
|
||||
public synchronized Skeleton getSkeleton() {
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
public synchronized void setOutput(PoseDataStream poseFileStream, long intervalMs)
|
||||
throws IOException {
|
||||
setFrameInterval(intervalMs);
|
||||
setOutput(poseFileStream);
|
||||
}
|
||||
|
||||
public synchronized PoseDataStream getOutput() {
|
||||
return poseFileStream;
|
||||
}
|
||||
|
||||
public synchronized void setOutput(PoseDataStream poseFileStream) throws IOException {
|
||||
poseFileStream.writeHeader(skeleton, this);
|
||||
this.poseFileStream = poseFileStream;
|
||||
}
|
||||
|
||||
public synchronized void closeOutput() throws IOException {
|
||||
PoseDataStream poseFileStream = this.poseFileStream;
|
||||
|
||||
if (poseFileStream != null) {
|
||||
closeOutput(poseFileStream);
|
||||
this.poseFileStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void closeOutput(PoseDataStream poseFileStream) throws IOException {
|
||||
if (poseFileStream != null) {
|
||||
poseFileStream.writeFooter(skeleton);
|
||||
poseFileStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
|
||||
|
||||
public class ServerPoseStreamer extends TickPoseStreamer {
|
||||
|
||||
protected final VRServer server;
|
||||
|
||||
public ServerPoseStreamer(VRServer server) {
|
||||
super(null); // Skeleton is registered later
|
||||
this.server = server;
|
||||
|
||||
// Register callbacks/events
|
||||
server.addSkeletonUpdatedCallback(this::onSkeletonUpdated);
|
||||
server.addOnTick(this::onTick);
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public void onSkeletonUpdated(Skeleton skeleton) {
|
||||
this.skeleton = skeleton;
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public void onTick() {
|
||||
super.doTick();
|
||||
}
|
||||
}
|
||||
48
src/main/java/dev/slimevr/posestreamer/StdBVHFileStream.java
Normal file
48
src/main/java/dev/slimevr/posestreamer/StdBVHFileStream.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
public class StdBVHFileStream extends BVHFileStream {
|
||||
|
||||
public StdBVHFileStream(OutputStream outputStream) {
|
||||
super(outputStream);
|
||||
}
|
||||
|
||||
public StdBVHFileStream(File file) throws FileNotFoundException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
public StdBVHFileStream(String file) throws FileNotFoundException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransformNodeWrapper wrapSkeletonNodes(TransformNode rootNode) {
|
||||
TransformNode newRoot = getNodeFromHierarchy(rootNode, "Hip");
|
||||
if (newRoot == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return TransformNodeWrapper.wrapFullHierarchy(newRoot);
|
||||
}
|
||||
|
||||
private TransformNode getNodeFromHierarchy(TransformNode node, String name) {
|
||||
if (node.getName().equalsIgnoreCase(name)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
for (TransformNode child : node.children) {
|
||||
TransformNode result = getNodeFromHierarchy(child, name);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
48
src/main/java/dev/slimevr/posestreamer/TickPoseStreamer.java
Normal file
48
src/main/java/dev/slimevr/posestreamer/TickPoseStreamer.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import dev.slimevr.vr.processor.skeleton.Skeleton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
public class TickPoseStreamer extends PoseStreamer {
|
||||
|
||||
protected long nextFrameTimeMs = -1L;
|
||||
|
||||
public TickPoseStreamer(Skeleton skeleton) {
|
||||
super(skeleton);
|
||||
}
|
||||
|
||||
public void doTick() {
|
||||
PoseDataStream poseFileStream = this.poseFileStream;
|
||||
if (poseFileStream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Skeleton skeleton = this.skeleton;
|
||||
if (skeleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
long curTime = System.currentTimeMillis();
|
||||
if (curTime < nextFrameTimeMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
nextFrameTimeMs += frameRecordingInterval;
|
||||
|
||||
// To prevent duplicate frames, make sure the frame time is always in
|
||||
// the future
|
||||
if (nextFrameTimeMs <= curTime) {
|
||||
nextFrameTimeMs = curTime + frameRecordingInterval;
|
||||
}
|
||||
|
||||
captureFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setOutput(PoseDataStream poseFileStream) throws IOException {
|
||||
super.setOutput(poseFileStream);
|
||||
nextFrameTimeMs = -1L; // Reset the frame timing
|
||||
}
|
||||
}
|
||||
196
src/main/java/dev/slimevr/posestreamer/TransformNodeWrapper.java
Normal file
196
src/main/java/dev/slimevr/posestreamer/TransformNodeWrapper.java
Normal file
@@ -0,0 +1,196 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import io.eiren.util.collections.FastList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class TransformNodeWrapper {
|
||||
|
||||
public final TransformNode wrappedNode;
|
||||
public final Transform localTransform;
|
||||
public final Transform worldTransform;
|
||||
public final List<TransformNodeWrapper> children;
|
||||
protected String name;
|
||||
protected TransformNodeWrapper parent;
|
||||
private boolean reversedHierarchy = false;
|
||||
|
||||
public TransformNodeWrapper(
|
||||
TransformNode nodeToWrap,
|
||||
String name,
|
||||
boolean reversedHierarchy,
|
||||
int initialChildCapacity
|
||||
) {
|
||||
this.wrappedNode = nodeToWrap;
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.localTransform = nodeToWrap.localTransform;
|
||||
this.worldTransform = nodeToWrap.worldTransform;
|
||||
|
||||
this.reversedHierarchy = reversedHierarchy;
|
||||
|
||||
this.children = new FastList<>(initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, String name, int initialChildCapacity) {
|
||||
this(nodeToWrap, name, false, initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, String name) {
|
||||
this(nodeToWrap, name, false, 5);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(
|
||||
TransformNode nodeToWrap,
|
||||
boolean reversedHierarchy,
|
||||
int initialChildCapacity
|
||||
) {
|
||||
this(nodeToWrap, nodeToWrap.getName(), reversedHierarchy, initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, boolean reversedHierarchy) {
|
||||
this(nodeToWrap, nodeToWrap.getName(), reversedHierarchy, 5);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, int initialChildCapacity) {
|
||||
this(nodeToWrap, nodeToWrap.getName(), initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap) {
|
||||
this(nodeToWrap, nodeToWrap.getName());
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapFullHierarchyWithFakeRoot(TransformNode root) {
|
||||
// Allocate a "fake" root with appropriate size depending on connections
|
||||
// the
|
||||
// root has
|
||||
TransformNodeWrapper fakeRoot = new TransformNodeWrapper(
|
||||
root,
|
||||
root.getParent() != null ? 2 : 1
|
||||
);
|
||||
|
||||
// Attach downwards hierarchy to the fake root
|
||||
wrapNodeHierarchyDown(root, fakeRoot);
|
||||
|
||||
// Attach upwards hierarchy to the fake root
|
||||
fakeRoot.attachChild(wrapHierarchyUp(root));
|
||||
|
||||
return fakeRoot;
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapFullHierarchy(TransformNode root) {
|
||||
return wrapNodeHierarchyUp(wrapHierarchyDown(root));
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapHierarchyDown(TransformNode root) {
|
||||
return wrapNodeHierarchyDown(root, new TransformNodeWrapper(root, root.children.size()));
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapNodeHierarchyDown(
|
||||
TransformNode root,
|
||||
TransformNodeWrapper target
|
||||
) {
|
||||
for (TransformNode child : root.children) {
|
||||
target.attachChild(wrapHierarchyDown(child));
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapHierarchyUp(TransformNode root) {
|
||||
return wrapNodeHierarchyUp(
|
||||
new TransformNodeWrapper(root, true, root.getParent() != null ? 1 : 0)
|
||||
);
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapNodeHierarchyUp(TransformNodeWrapper root) {
|
||||
return wrapNodeHierarchyUp(root.wrappedNode, root);
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapNodeHierarchyUp(
|
||||
TransformNode root,
|
||||
TransformNodeWrapper target
|
||||
) {
|
||||
TransformNode parent = root.getParent();
|
||||
if (parent == null) {
|
||||
return target;
|
||||
}
|
||||
|
||||
// Flip the offset for these reversed nodes
|
||||
TransformNodeWrapper wrapper = new TransformNodeWrapper(
|
||||
parent,
|
||||
true,
|
||||
(parent.getParent() != null ? 1 : 0) + Math.max(0, parent.children.size() - 1)
|
||||
);
|
||||
target.attachChild(wrapper);
|
||||
|
||||
// Re-attach other children
|
||||
if (parent.children.size() > 1) {
|
||||
for (TransformNode child : parent.children) {
|
||||
// Skip the original node
|
||||
if (child == target.wrappedNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wrapper.attachChild(wrapHierarchyDown(child));
|
||||
}
|
||||
}
|
||||
|
||||
// Continue up the hierarchy
|
||||
wrapNodeHierarchyUp(wrapper);
|
||||
// Return original node
|
||||
return target;
|
||||
}
|
||||
|
||||
public boolean hasReversedHierarchy() {
|
||||
return reversedHierarchy;
|
||||
}
|
||||
|
||||
public void setReversedHierarchy(boolean reversedHierarchy) {
|
||||
this.reversedHierarchy = reversedHierarchy;
|
||||
}
|
||||
|
||||
public boolean hasLocalRotation() {
|
||||
return wrappedNode.localRotation;
|
||||
}
|
||||
|
||||
public Quaternion calculateLocalRotation(Quaternion relativeTo, Quaternion result) {
|
||||
return calculateLocalRotationInverse(relativeTo.inverse(), result);
|
||||
}
|
||||
|
||||
public Quaternion calculateLocalRotationInverse(
|
||||
Quaternion inverseRelativeTo,
|
||||
Quaternion result
|
||||
) {
|
||||
if (result == null) {
|
||||
result = new Quaternion();
|
||||
}
|
||||
|
||||
return inverseRelativeTo.mult(worldTransform.getRotation(), result);
|
||||
}
|
||||
|
||||
public void attachChild(TransformNodeWrapper node) {
|
||||
if (node.parent != null) {
|
||||
throw new IllegalArgumentException("The child node must not already have a parent");
|
||||
}
|
||||
|
||||
this.children.add(node);
|
||||
node.parent = this;
|
||||
}
|
||||
|
||||
public TransformNodeWrapper getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
45
src/main/java/dev/slimevr/protocol/ConnectionContext.java
Normal file
45
src/main/java/dev/slimevr/protocol/ConnectionContext.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import solarxr_protocol.data_feed.DataFeedConfigT;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class ConnectionContext {
|
||||
|
||||
private final List<DataFeedConfigT> dataFeedConfigList = new ArrayList<>();
|
||||
|
||||
// I did it in a separate array because it was more convenient than making a
|
||||
// parent object of the DataFeedConfigT
|
||||
// idk if it should be a concern or not, i think it is fine tbh
|
||||
// Futurabeast
|
||||
private final List<Long> dataFeedTimers = new ArrayList<>();
|
||||
|
||||
private boolean useSerial = false;
|
||||
private boolean useAutoBone = false;
|
||||
|
||||
public List<DataFeedConfigT> getDataFeedConfigList() {
|
||||
return dataFeedConfigList;
|
||||
}
|
||||
|
||||
public List<Long> getDataFeedTimers() {
|
||||
return dataFeedTimers;
|
||||
}
|
||||
|
||||
public boolean useSerial() {
|
||||
return useSerial;
|
||||
}
|
||||
|
||||
public void setUseSerial(boolean useSerial) {
|
||||
this.useSerial = useSerial;
|
||||
}
|
||||
|
||||
public boolean useAutoBone() {
|
||||
return useAutoBone;
|
||||
}
|
||||
|
||||
public void setUseAutoBone(boolean useAutoBone) {
|
||||
this.useAutoBone = useAutoBone;
|
||||
}
|
||||
}
|
||||
291
src/main/java/dev/slimevr/protocol/DataFeedBuilder.java
Normal file
291
src/main/java/dev/slimevr/protocol/DataFeedBuilder.java
Normal file
@@ -0,0 +1,291 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.vr.processor.skeleton.BoneInfo;
|
||||
import dev.slimevr.vr.trackers.IMUTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.udp.UDPDevice;
|
||||
import solarxr_protocol.data_feed.Bone;
|
||||
import solarxr_protocol.data_feed.DataFeedUpdate;
|
||||
import solarxr_protocol.data_feed.device_data.DeviceData;
|
||||
import solarxr_protocol.data_feed.device_data.DeviceDataMaskT;
|
||||
import solarxr_protocol.data_feed.tracker.TrackerData;
|
||||
import solarxr_protocol.data_feed.tracker.TrackerDataMaskT;
|
||||
import solarxr_protocol.data_feed.tracker.TrackerInfo;
|
||||
import solarxr_protocol.datatypes.DeviceId;
|
||||
import solarxr_protocol.datatypes.Temperature;
|
||||
import solarxr_protocol.datatypes.TrackerId;
|
||||
import solarxr_protocol.datatypes.hardware_info.HardwareInfo;
|
||||
import solarxr_protocol.datatypes.hardware_info.HardwareStatus;
|
||||
import solarxr_protocol.datatypes.math.Quat;
|
||||
import solarxr_protocol.datatypes.math.Vec3f;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class DataFeedBuilder {
|
||||
|
||||
public static int createHardwareInfo(FlatBufferBuilder fbb, UDPDevice device) {
|
||||
IMUTracker tracker = device.sensors.get(0);
|
||||
|
||||
HardwareInfo.startHardwareInfo(fbb);
|
||||
// BRUH MOMENT
|
||||
// TODO need support: HardwareInfo.addFirmwareVersion(fbb,
|
||||
// firmwareVersionOffset);
|
||||
// TODO need support: HardwareInfo.addHardwareRevision(fbb,
|
||||
// hardwareRevisionOffset);
|
||||
// TODO need support: HardwareInfo.addManufacturer(fbb, device.m);
|
||||
// TODO need support: HardwareInfo.addDisplayName(fbb, de);
|
||||
// TODO need support: HardwareInfo.addHardwareAddress(fbb, tracker.);
|
||||
// TODO need support: HardwareInfo.addMcuId(device);
|
||||
return HardwareInfo.endHardwareInfo(fbb);
|
||||
}
|
||||
|
||||
public static int createTrackerId(FlatBufferBuilder fbb, Tracker tracker) {
|
||||
TrackerId.startTrackerId(fbb);
|
||||
|
||||
TrackerId.addTrackerNum(fbb, tracker.getTrackerNum());
|
||||
if (tracker.getDevice() != null)
|
||||
TrackerId.addDeviceId(fbb, DeviceId.createDeviceId(fbb, tracker.getDevice().getId()));
|
||||
|
||||
return TrackerId.endTrackerId(fbb);
|
||||
}
|
||||
|
||||
public static int createTrackerInfos(FlatBufferBuilder fbb, boolean infoMask, Tracker tracker) {
|
||||
|
||||
if (!infoMask)
|
||||
return 0;
|
||||
|
||||
TrackerInfo.startTrackerInfo(fbb);
|
||||
if (tracker.getBodyPosition() != null)
|
||||
TrackerInfo.addBodyPart(fbb, tracker.getBodyPosition().bodyPart);
|
||||
TrackerInfo.addEditable(fbb, tracker.userEditable());
|
||||
TrackerInfo.addComputed(fbb, tracker.isComputed());
|
||||
// TODO need support: TrackerInfo.addImuType(fbb, tracker.im);
|
||||
// TODO need support: TrackerInfo.addPollRate(fbb, tracker.);
|
||||
|
||||
if (tracker instanceof IMUTracker) {
|
||||
IMUTracker imuTracker = (IMUTracker) tracker;
|
||||
if (imuTracker.getMountingRotation() != null) {
|
||||
Quaternion quaternion = imuTracker.getMountingRotation();
|
||||
TrackerInfo
|
||||
.addMountingOrientation(
|
||||
fbb,
|
||||
Quat
|
||||
.createQuat(
|
||||
fbb,
|
||||
quaternion.getX(),
|
||||
quaternion.getY(),
|
||||
quaternion.getZ(),
|
||||
quaternion.getW()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
return TrackerInfo.endTrackerInfo(fbb);
|
||||
}
|
||||
|
||||
public static int createTrackerPosition(FlatBufferBuilder fbb, Tracker tracker) {
|
||||
Vector3f pos = new Vector3f();
|
||||
tracker.getPosition(pos);
|
||||
|
||||
return Vec3f.createVec3f(fbb, pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
public static int createTrackerRotation(FlatBufferBuilder fbb, Tracker tracker) {
|
||||
Quaternion quaternion = new Quaternion();
|
||||
tracker.getRotation(quaternion);
|
||||
|
||||
return Quat
|
||||
.createQuat(
|
||||
fbb,
|
||||
quaternion.getX(),
|
||||
quaternion.getY(),
|
||||
quaternion.getZ(),
|
||||
quaternion.getW()
|
||||
);
|
||||
}
|
||||
|
||||
public static int createTrackerTemperature(FlatBufferBuilder fbb, Tracker tracker) {
|
||||
if (!(tracker instanceof IMUTracker))
|
||||
return 0;
|
||||
IMUTracker imuTracker = (IMUTracker) tracker;
|
||||
return Temperature.createTemperature(fbb, imuTracker.temperature);
|
||||
}
|
||||
|
||||
public static int createTrackerData(
|
||||
FlatBufferBuilder fbb,
|
||||
TrackerDataMaskT mask,
|
||||
Tracker tracker
|
||||
) {
|
||||
int trackerInfosOffset = DataFeedBuilder.createTrackerInfos(fbb, mask.getInfo(), tracker);
|
||||
int trackerIdOffset = DataFeedBuilder.createTrackerId(fbb, tracker);
|
||||
|
||||
TrackerData.startTrackerData(fbb);
|
||||
|
||||
TrackerData.addTrackerId(fbb, trackerIdOffset);
|
||||
|
||||
if (trackerInfosOffset != 0)
|
||||
TrackerData.addInfo(fbb, trackerInfosOffset);
|
||||
if (mask.getStatus())
|
||||
TrackerData.addStatus(fbb, tracker.getStatus().id + 1);
|
||||
if (mask.getPosition())
|
||||
TrackerData.addPosition(fbb, DataFeedBuilder.createTrackerPosition(fbb, tracker));
|
||||
if (mask.getRotation())
|
||||
TrackerData.addRotation(fbb, DataFeedBuilder.createTrackerRotation(fbb, tracker));
|
||||
if (mask.getTemp()) {
|
||||
int trackerTemperatureOffset = DataFeedBuilder.createTrackerTemperature(fbb, tracker);
|
||||
if (trackerTemperatureOffset != 0)
|
||||
TrackerData.addTemp(fbb, trackerTemperatureOffset);
|
||||
}
|
||||
|
||||
return TrackerData.endTrackerData(fbb);
|
||||
}
|
||||
|
||||
public static int createTrackersData(
|
||||
FlatBufferBuilder fbb,
|
||||
DeviceDataMaskT mask,
|
||||
UDPDevice device
|
||||
) {
|
||||
if (mask.getTrackerData() == null)
|
||||
return 0;
|
||||
|
||||
List<Integer> trackersOffsets = new ArrayList<>();
|
||||
|
||||
device.sensors.forEach((key, value) -> {
|
||||
trackersOffsets
|
||||
.add(DataFeedBuilder.createTrackerData(fbb, mask.getTrackerData(), value));
|
||||
});
|
||||
|
||||
DeviceData.startTrackersVector(fbb, trackersOffsets.size());
|
||||
trackersOffsets.forEach(offset -> {
|
||||
DeviceData.addTrackers(fbb, offset);
|
||||
});
|
||||
return fbb.endVector();
|
||||
}
|
||||
|
||||
public static int createDeviceData(
|
||||
FlatBufferBuilder fbb,
|
||||
int id,
|
||||
DeviceDataMaskT mask,
|
||||
UDPDevice device
|
||||
) {
|
||||
if (!mask.getDeviceData())
|
||||
return 0;
|
||||
|
||||
IMUTracker tracker = device.sensors.get(0);
|
||||
|
||||
if (tracker == null)
|
||||
return 0;
|
||||
|
||||
int hardwareDataOffset = HardwareStatus
|
||||
.createHardwareStatus(
|
||||
fbb,
|
||||
tracker.getStatus().id,
|
||||
(int) tracker.getTPS(),
|
||||
tracker.ping,
|
||||
(short) tracker.signalStrength,
|
||||
tracker.temperature,
|
||||
tracker.getBatteryVoltage(),
|
||||
(int) tracker.getBatteryLevel(),
|
||||
-1
|
||||
);
|
||||
int hardwareInfoOffset = DataFeedBuilder.createHardwareInfo(fbb, device);
|
||||
int trackersOffset = DataFeedBuilder.createTrackersData(fbb, mask, device);
|
||||
|
||||
DeviceData.startDeviceData(fbb);
|
||||
// TODO need support: DeviceData.addCustomName(fbb, nameOffset);
|
||||
DeviceData.addId(fbb, DeviceId.createDeviceId(fbb, id));
|
||||
DeviceData.addHardwareStatus(fbb, hardwareDataOffset);
|
||||
DeviceData.addHardwareInfo(fbb, hardwareInfoOffset);
|
||||
DeviceData.addTrackers(fbb, trackersOffset);
|
||||
|
||||
return DeviceData.endDeviceData(fbb);
|
||||
}
|
||||
|
||||
public static int createSyntheticTrackersData(
|
||||
FlatBufferBuilder fbb,
|
||||
TrackerDataMaskT trackerDataMaskT,
|
||||
List<Tracker> trackers
|
||||
) {
|
||||
if (trackerDataMaskT == null)
|
||||
return 0;
|
||||
|
||||
List<Integer> trackerOffsets = new ArrayList<>();
|
||||
|
||||
trackers.stream().filter(Tracker::isComputed).forEach((tracker) -> {
|
||||
trackerOffsets.add(DataFeedBuilder.createTrackerData(fbb, trackerDataMaskT, tracker));
|
||||
});
|
||||
|
||||
DataFeedUpdate.startSyntheticTrackersVector(fbb, trackerOffsets.size());
|
||||
trackerOffsets.forEach((tracker -> {
|
||||
DataFeedUpdate.addSyntheticTrackers(fbb, tracker);
|
||||
}));
|
||||
return fbb.endVector();
|
||||
}
|
||||
|
||||
public static int createDevicesData(
|
||||
FlatBufferBuilder fbb,
|
||||
DeviceDataMaskT deviceDataMaskT,
|
||||
List<UDPDevice> devices
|
||||
) {
|
||||
if (deviceDataMaskT == null)
|
||||
return 0;
|
||||
|
||||
int[] devicesDataOffsets = new int[devices.size()];
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
UDPDevice device = devices.get(i);
|
||||
devicesDataOffsets[i] = DataFeedBuilder
|
||||
.createDeviceData(fbb, i, deviceDataMaskT, device);
|
||||
}
|
||||
|
||||
return DataFeedUpdate.createDevicesVector(fbb, devicesDataOffsets);
|
||||
}
|
||||
|
||||
public static int createBonesData(
|
||||
FlatBufferBuilder fbb,
|
||||
boolean shouldSend,
|
||||
List<BoneInfo> boneInfos
|
||||
) {
|
||||
if (!shouldSend) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var boneOffsets = new int[boneInfos.size()];
|
||||
for (var i = 0; i < boneInfos.size(); ++i) {
|
||||
var bi = boneInfos.get(i);
|
||||
|
||||
var headPosG = bi.tailNode.getParent().worldTransform.getTranslation();
|
||||
var rotG = bi.getGlobalRotation();
|
||||
|
||||
// TODO: figure out why this value is stale, so that we don't need
|
||||
// to recalculate it all the time, since thats not performant.
|
||||
bi.updateLength();
|
||||
var length = bi.length;
|
||||
|
||||
Bone.startBone(fbb);
|
||||
|
||||
var rotGOffset = Quat
|
||||
.createQuat(
|
||||
fbb,
|
||||
rotG.getX(),
|
||||
rotG.getY(),
|
||||
rotG.getZ(),
|
||||
rotG.getW()
|
||||
);
|
||||
Bone.addRotationG(fbb, rotGOffset);
|
||||
var headPosGOffset = Vec3f.createVec3f(fbb, headPosG.x, headPosG.y, headPosG.z);
|
||||
Bone.addHeadPositionG(fbb, headPosGOffset);
|
||||
Bone.addBodyPart(fbb, bi.boneType.bodyPart);
|
||||
Bone.addBoneLength(fbb, length);
|
||||
|
||||
boneOffsets[i] = Bone.endBone(fbb);
|
||||
}
|
||||
|
||||
return DataFeedUpdate.createBonesVector(fbb, boneOffsets);
|
||||
}
|
||||
}
|
||||
152
src/main/java/dev/slimevr/protocol/DataFeedHandler.java
Normal file
152
src/main/java/dev/slimevr/protocol/DataFeedHandler.java
Normal file
@@ -0,0 +1,152 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.data_feed.*;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
||||
public class DataFeedHandler extends ProtocolHandler<DataFeedMessageHeader> {
|
||||
|
||||
private final ProtocolAPI api;
|
||||
|
||||
public DataFeedHandler(ProtocolAPI api) {
|
||||
this.api = api;
|
||||
|
||||
registerPacketListener(DataFeedMessage.StartDataFeed, this::onStartDataFeed);
|
||||
registerPacketListener(DataFeedMessage.PollDataFeed, this::onPollDataFeedRequest);
|
||||
|
||||
this.api.server.addOnTick(this::sendDataFeedUpdate);
|
||||
}
|
||||
|
||||
private void onStartDataFeed(GenericConnection conn, DataFeedMessageHeader header) {
|
||||
StartDataFeed req = (StartDataFeed) header.message(new StartDataFeed());
|
||||
if (req == null)
|
||||
return;
|
||||
int dataFeeds = req.dataFeedsLength();
|
||||
|
||||
conn.getContext().getDataFeedConfigList().clear();
|
||||
for (int i = 0; i < dataFeeds; i++) {
|
||||
// Using the object api here because we need to copy from the buffer
|
||||
// anyway so
|
||||
// let's do it from here and send the reference to an arraylist
|
||||
DataFeedConfigT config = req.dataFeeds(i).unpack();
|
||||
conn.getContext().getDataFeedConfigList().add(config);
|
||||
conn.getContext().getDataFeedTimers().add(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
private void onPollDataFeedRequest(
|
||||
GenericConnection conn,
|
||||
DataFeedMessageHeader messageHeader
|
||||
) {
|
||||
|
||||
PollDataFeed req = (PollDataFeed) messageHeader.message(new PollDataFeed());
|
||||
if (req == null)
|
||||
return;
|
||||
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(300);
|
||||
|
||||
int messageOffset = this.buildDatafeed(fbb, req.config().unpack());
|
||||
|
||||
DataFeedMessageHeader.startDataFeedMessageHeader(fbb);
|
||||
DataFeedMessageHeader.addMessage(fbb, messageOffset);
|
||||
DataFeedMessageHeader.addMessageType(fbb, DataFeedMessage.DataFeedUpdate);
|
||||
int headerOffset = DataFeedMessageHeader.endDataFeedMessageHeader(fbb);
|
||||
|
||||
MessageBundle.startDataFeedMsgsVector(fbb, 1);
|
||||
MessageBundle.addDataFeedMsgs(fbb, headerOffset);
|
||||
int datafeedMessagesOffset = fbb.endVector();
|
||||
|
||||
int packet = createMessage(fbb, datafeedMessagesOffset, 0);
|
||||
fbb.finish(packet);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
|
||||
public int buildDatafeed(FlatBufferBuilder fbb, DataFeedConfigT config) {
|
||||
int devicesOffset = DataFeedBuilder
|
||||
.createDevicesData(
|
||||
fbb,
|
||||
config.getDataMask(),
|
||||
this.api.server.getTrackersServer().getConnections()
|
||||
);
|
||||
int trackersOffset = DataFeedBuilder
|
||||
.createSyntheticTrackersData(
|
||||
fbb,
|
||||
config.getSyntheticTrackersMask(),
|
||||
this.api.server.getAllTrackers()
|
||||
);
|
||||
|
||||
var s = this.api.server.humanPoseProcessor.getSkeleton();
|
||||
int bonesOffset = DataFeedBuilder
|
||||
.createBonesData(
|
||||
fbb,
|
||||
config.getBoneMask(),
|
||||
s.currentBoneInfo
|
||||
);
|
||||
|
||||
return DataFeedUpdate.createDataFeedUpdate(fbb, devicesOffset, trackersOffset, bonesOffset);
|
||||
}
|
||||
|
||||
public void sendDataFeedUpdate() {
|
||||
long currTime = System.currentTimeMillis();
|
||||
|
||||
this.api.getAPIServers().forEach((server) -> {
|
||||
server.getAPIConnections().forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = null;
|
||||
|
||||
int configsCount = conn.getContext().getDataFeedConfigList().size();
|
||||
|
||||
int[] data = new int[configsCount];
|
||||
|
||||
for (int index = 0; index < configsCount; index++) {
|
||||
Long lastTimeSent = conn.getContext().getDataFeedTimers().get(index);
|
||||
DataFeedConfigT configT = conn.getContext().getDataFeedConfigList().get(index);
|
||||
if (currTime - lastTimeSent > configT.getMinimumTimeSinceLast()) {
|
||||
if (fbb == null) {
|
||||
// That way we create a buffer only when needed
|
||||
fbb = new FlatBufferBuilder(300);
|
||||
}
|
||||
|
||||
int messageOffset = this.buildDatafeed(fbb, configT);
|
||||
|
||||
DataFeedMessageHeader.startDataFeedMessageHeader(fbb);
|
||||
DataFeedMessageHeader.addMessage(fbb, messageOffset);
|
||||
DataFeedMessageHeader.addMessageType(fbb, DataFeedMessage.DataFeedUpdate);
|
||||
data[index] = DataFeedMessageHeader.endDataFeedMessageHeader(fbb);
|
||||
|
||||
conn.getContext().getDataFeedTimers().set(index, currTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (fbb != null) {
|
||||
int messages = MessageBundle.createDataFeedMsgsVector(fbb, data);
|
||||
int packet = createMessage(fbb, messages, 0);
|
||||
fbb.finish(packet);
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(GenericConnection conn, DataFeedMessageHeader message) {
|
||||
BiConsumer<GenericConnection, DataFeedMessageHeader> consumer = this.handlers[message
|
||||
.messageType()];
|
||||
if (consumer != null)
|
||||
consumer.accept(conn, message);
|
||||
else
|
||||
LogManager
|
||||
.info(
|
||||
"[ProtocolAPI] Unhandled Datafeed packet received id: " + message.messageType()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int messagesCount() {
|
||||
return DataFeedMessage.names.length;
|
||||
}
|
||||
}
|
||||
14
src/main/java/dev/slimevr/protocol/GenericConnection.java
Normal file
14
src/main/java/dev/slimevr/protocol/GenericConnection.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.UUID;
|
||||
|
||||
|
||||
public interface GenericConnection {
|
||||
|
||||
UUID getConnectionId();
|
||||
|
||||
ConnectionContext getContext();
|
||||
|
||||
void send(ByteBuffer bytes);
|
||||
}
|
||||
52
src/main/java/dev/slimevr/protocol/ProtocolAPI.java
Normal file
52
src/main/java/dev/slimevr/protocol/ProtocolAPI.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.data_feed.DataFeedMessageHeader;
|
||||
import solarxr_protocol.rpc.RpcMessageHeader;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class ProtocolAPI {
|
||||
|
||||
public final VRServer server;
|
||||
public final RPCHandler rpcHandler;
|
||||
public final DataFeedHandler dataFeedHandler;
|
||||
|
||||
private final List<ProtocolAPIServer> servers = new ArrayList<>();
|
||||
|
||||
public ProtocolAPI(VRServer server) {
|
||||
this.server = server;
|
||||
this.rpcHandler = new RPCHandler(this);
|
||||
this.dataFeedHandler = new DataFeedHandler(this);
|
||||
}
|
||||
|
||||
public void onMessage(GenericConnection conn, ByteBuffer message) {
|
||||
MessageBundle messageBundle = MessageBundle.getRootAsMessageBundle(message);
|
||||
|
||||
for (int index = 0; index < messageBundle.dataFeedMsgsLength(); index++) {
|
||||
DataFeedMessageHeader header = messageBundle.dataFeedMsgsVector().get(index);
|
||||
this.dataFeedHandler.onMessage(conn, header);
|
||||
}
|
||||
|
||||
for (int index = 0; index < messageBundle.rpcMsgsLength(); index++) {
|
||||
RpcMessageHeader header = messageBundle.rpcMsgsVector().get(index);
|
||||
this.rpcHandler.onMessage(conn, header);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ProtocolAPIServer> getAPIServers() {
|
||||
return servers;
|
||||
}
|
||||
|
||||
public void registerAPIServer(ProtocolAPIServer server) {
|
||||
this.servers.add(server);
|
||||
}
|
||||
|
||||
public void removeAPIServer(ProtocolAPIServer server) {
|
||||
this.servers.remove(server);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dev.slimevr.protocol;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
public interface ProtocolAPIServer {
|
||||
|
||||
Stream<GenericConnection> getAPIConnections();
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user