Compare commits

...

248 Commits

Author SHA1 Message Date
Butterscotch!
b1318567e3 AutoBone: Adjust bone offsets for directly and implement new contribution based error calculation (#204) 2022-06-28 22:00:57 +03:00
Eiren Rain
d1a02c52a9 Monor changes, added comments 2022-06-28 20:36:04 +02:00
lucas lelievre
b3efcd19db Remove realTracker checks for cleaner code (#203) 2022-06-28 21:33:43 +03:00
Erimel
66e4e4e0a0 Basic shoulder tracking support (#200) 2022-06-28 21:32:11 +03:00
Erimel
7c37fdb24e Use split offsets and rename foot_offset to foot_shift (#199) 2022-06-28 21:20:31 +03:00
Eiren Rain
ceb1cc77d8 Minor cleanup and comments from last PRs 2022-06-28 20:11:08 +02:00
Ryan Butler
8df4726d22 [Feature] Add support for bones in DataFeedUpdate (#196) 2022-06-28 20:57:09 +03:00
Ryan Butler
408df5273d Fix NPE in Websocket logging (#202) 2022-06-28 20:21:38 +03:00
Ryan Butler
7a4d087be2 Prevent logs in gitignore (#201) 2022-06-28 20:21:01 +03:00
Erimel
11a15ab5b1 Cleaning up the skeleton a bit (#197) 2022-06-21 17:22:53 +03:00
Eiren Rain
f6ffd63b05 Fix left arm tracker not resetting (#198) 2022-06-21 01:13:09 +03:00
lucas lelievre
0afe9d2ef8 Fix optional in the feeder and bring back none option in the gui (#194) 2022-06-11 20:15:27 +03:00
Erimel
c02b95346f Extended knee model + temporary fix for null/optional (#193) 2022-06-09 13:21:40 +03:00
Erimel
db33c561ab fix minor typo in firewall.bat (#192) 2022-06-06 21:08:58 +03:00
Eiren Rain
df2cfa1e57 Skeleton data (#189) 2022-06-05 01:04:07 +03:00
Ryan Butler
d40e2c2ce2 Refactored TrackerPosition (#181) 2022-06-01 17:08:56 +03:00
Butterscotch!
215f3b9f44 Implement AutoBone into the protocol (#187)
Implement event-based AutoBoneHandler & more
2022-06-01 16:59:17 +03:00
Ryan Butler
766cf9ae72 Update instructions for spotless on IntelliJ (#183) 2022-05-31 13:42:57 +03:00
Kamilake
26d808deb7 Handling exception thrown when closing WiFiWindow (#185) 2022-05-30 17:43:43 +03:00
Eiren Rain
f9259c976d Update commons version 2022-05-24 17:17:32 +03:00
Erimel
39c3285a0a Fix getting previous rotations (#180) 2022-05-24 01:09:00 +03:00
Ryan Butler
57f3624ee7 CI & Formatting improvements (#178)
* CI & Formatting improvements

+ Added spotlessCheck to CI
+ Now formatting .yaml files (with spaces)
* Bumped version numbers of actions

* Renamed .yml to .yaml in accordance with yaml.org/faq
2022-05-23 15:54:10 +03:00
Eiren Rain
5a4b656a74 WIP of skeleton data 2022-05-19 19:03:38 +03:00
Eiren Rain
bf7e13d923 Update spotless 2022-05-19 18:08:57 +03:00
Ryan Butler
dedec668e1 Added spotless and CONTRIBUTING.md (#172) 2022-05-19 17:51:46 +03:00
Eiren Rain
1e4013be98 This time it should work 2022-05-16 20:52:28 +03:00
Eiren Rain
76c3210788 Should be the last one for gradle or I give up... 2022-05-16 20:48:42 +03:00
Eiren Rain
4a99eff854 Fix gradle build (hopefully) 2022-05-16 20:46:18 +03:00
Eiren Rain
237dabce17 Update commons with formatted code 2022-05-16 20:41:42 +03:00
Eiren Rain
6f36f7624a Update commons 2022-05-16 20:36:26 +03:00
Eiren Rain
1dea626422 Eclipse formatting (#176)
* Make eclipse formatter better follow our conventions and format all of it
* Don't join wrapped lines to allow for hand-formatting
* Fix formatting of switch statements and add breaks in enum declarations
* Use new formatter by @thebutlah, adjust formatter more
2022-05-16 20:05:46 +03:00
Eiren Rain
c74cc539c9 Clarify README regarding licencing 2022-05-08 17:56:53 +03:00
Eiren Rain
910e32bb3e Merge pull request #166 from Futurabeast/clean-get-tracker-by-id
Change getTrackerById
2022-05-03 15:33:18 +03:00
Eiren Rain
6ad3c862ce Merge pull request #171 from TheButlah/fix_concurrent_modification_datafeed
Fix concurrent modification in DataFeedHandler
2022-05-03 15:27:43 +03:00
Eiren Rain
05819baf97 Merge pull request #173 from TheButlah/trackerpos_from_role
Computed trackers use TrackerRole to determine TrackerPosition, if null
2022-05-03 15:25:52 +03:00
Eiren Rain
d84ba2aef5 Merge pull request #174 from TheButlah/fix_more_fbs_bugs
Fix position and rotation getting mixed up in flatbuffer
2022-05-03 15:24:20 +03:00
Ryan Butler
ec9e7d94bc Fix position and rotation getting mixed up in flatbuffer 2022-05-03 03:24:22 -04:00
Ryan Butler
a800749b87 Fix concurrent modification in DataFeedHandler 2022-05-03 00:41:25 -04:00
Ryan Butler
76fe83452e Computed trackers use TrackerRole to determine TrackerPosition, if null 2022-05-02 23:45:57 -04:00
Eiren Rain
6d940503af Merge pull request #169 from TheButlah/gitignore_eclipse
.gitignore eclipse stuff
2022-05-02 23:17:21 +03:00
Ryan Butler
0ea5b58b50 .gitignoring most eclipse stuff 2022-05-02 13:38:50 -04:00
Eiren Rain
94c079815f Merge pull request #168 from TheButlah/fix_computed_tracker_num
Computed trackers should use their tracker id as their num
2022-05-02 13:47:43 +03:00
Eiren Rain
11ab7a6cb6 Merge pull request #167 from TheButlah/fix_invalid_fb_serialization
Fix invalid flatbuffer offsets
2022-05-02 13:47:15 +03:00
Ryan Butler
7f67135e69 Computed trackers should use their tracker id as their num 2022-05-02 02:58:24 -04:00
Ryan Butler
a1717bbf7a Fix invalid flatbuffer offsets 2022-05-02 02:09:33 -04:00
Eiren Rain
c99375364d Merge pull request #165 from deiteris/use-foreach
Refactor for to foreach
2022-05-01 16:02:24 +03:00
Eiren Rain
94460ed7ab Merge pull request #163 from Futurabeast/fix-index-error-datafeed
Fix iteration on the sensors, i used the key of the map instead of the index of the array
2022-05-01 01:07:18 +03:00
lucas lelievre
413332462f more readable version of it 2022-04-30 23:06:14 +02:00
lucas lelievre
18386d57d6 Better tracker id with no possible null pointer exception 2022-04-30 22:50:32 +02:00
lucas lelievre
f72045438e Change getTrackerById 2022-04-30 21:58:26 +02:00
Yury
17f74c693c Refactor for to foreach 2022-04-30 20:34:13 +03:00
lucas lelievre
3fb87a4688 Fix iteration on the sensors, i used the key of the map instead of the index of the array 2022-04-30 04:03:53 +02:00
Eiren Rain
9205f6cab7 Merge pull request #162 from Futurabeast/format-all
Format all files based on .editorconfig
2022-04-28 15:32:48 +03:00
lucas lelievre
d0ee364fb9 Fix conflicts with main 2022-04-28 14:30:40 +02:00
lucas lelievre
3a80ad0632 Change the formating to tabs 2022-04-28 03:44:24 +02:00
lucas lelievre
93c353bbc6 Fix crlf 2022-04-27 23:20:14 +02:00
Eiren Rain
9f883dccec Fix eclipse prefs 2022-04-27 23:42:16 +03:00
lucas lelievre
ad3b697517 New formatting rules 2022-04-27 22:40:03 +02:00
Eiren Rain
5f3097185e Merge pull request #161 from Futurabeast/fix-forward-to-front
Fix Converting FORWARD rotation to FRONT
2022-04-27 23:33:11 +03:00
lucas lelievre
7ac7f7d424 New formatting rules 2022-04-27 22:29:09 +02:00
lucas lelievre
ba58f4df6a Reformat the formatter 2022-04-27 22:25:28 +02:00
lucas lelievre
297bfddf6c Update src/main/java/dev/slimevr/NetworkProtocol.java
Co-authored-by: TheDevMinerTV <tobigames200@gmail.com>
2022-04-27 22:21:09 +02:00
lucas lelievre
e4d0aca744 Update src/main/java/dev/slimevr/bridge/BridgeThread.java
Co-authored-by: TheDevMinerTV <tobigames200@gmail.com>
2022-04-27 22:21:05 +02:00
lucas lelievre
8b5ac226c3 Update src/main/java/dev/slimevr/bridge/BridgeThread.java
Co-authored-by: TheDevMinerTV <tobigames200@gmail.com>
2022-04-27 22:21:00 +02:00
lucas lelievre
cfc264fa33 Update src/main/java/dev/slimevr/bridge/BridgeThread.java
Co-authored-by: TheDevMinerTV <tobigames200@gmail.com>
2022-04-27 22:20:49 +02:00
lucas lelievre
9e12958ca6 reformat all 2022-04-27 21:39:54 +02:00
lucas lelievre
8609fb9e97 Fix Converting FORWARD rotation to FRON 2022-04-27 21:30:54 +02:00
Eiren Rain
9d6dda8b17 Use https for solarxr module 2022-04-27 19:38:36 +03:00
Eiren Rain
92b64f0f12 Merge pull request #159 from Futurabeast/main
Headless Server / SolarXR Protocol implementation
2022-04-27 19:33:59 +03:00
lucas lelievre
677fa71ba1 Cancel change to .settings 2022-04-26 02:08:56 +02:00
lucas lelievre
9f3f34ce70 Reformat protocol 2022-04-26 02:07:45 +02:00
lucas lelievre
2d2b6588b3 Sync with main solarxr 2022-04-26 02:04:59 +02:00
lucas lelievre
44fa266697 remove slimevr_protocol from submodules 2022-04-25 00:39:37 +02:00
lucas lelievre
57f7ea6745 clean useless stuff 2022-04-25 00:38:50 +02:00
lucas lelievre
326c7e969a remove ignored files 2022-04-25 00:37:29 +02:00
lucas lelievre
23f9b3b276 Sync with lastest server 2022-04-25 00:21:32 +02:00
lucas lelievre
1e74deede3 Current progress 2022-04-24 19:53:56 +02:00
loucass003
2317ad94f5 Update submodule to solarxr-protocol 2022-04-24 01:20:55 +02:00
Eiren Rain
58d1f2de96 Merge pull request #158 from Louka3000/skeloff-fix
Fix hip when not using legs
2022-04-23 19:03:15 +03:00
Louka
de101b3576 Fix hip when not using legs
disables pelvis averaging if user isn't using knee trackers.
2022-04-20 21:26:36 -04:00
lucas lelievre
9fbfc43655 Clean packages 2022-04-16 16:40:30 +02:00
loucass003
e3e37023e8 More rpc stuff 2022-04-15 01:17:16 +02:00
Eiren Rain
63409b61ea Merge pull request #157 from mgschwan/main
Change variable naming of configPath
2022-04-14 17:15:20 +03:00
Michael Gschwandtner
c827e7725b Revert changed files 2022-04-14 16:10:35 +02:00
Michael Gschwandtner
0bcaed719b Merge branch 'SlimeVR:main' into main 2022-04-14 16:06:17 +02:00
Michael Gschwandtner
c77d73c460 Rename configPath variable 2022-04-14 16:04:37 +02:00
loucass003
9fc5d41e1b protocol rework Sets -> Map 2022-04-14 09:15:01 +02:00
Eiren Rain
ac0cb9e9cb Merge pull request #149 from Louka3000/hand-tracking
Hand tracking support + elbows without controllers
2022-04-12 20:22:32 +03:00
lucas lelievre
f961318035 update submodule 2022-04-12 09:09:29 +02:00
lucas lelievre
52fa670293 Datafeed almost done 2022-04-12 09:07:18 +02:00
Eiren Rain
d0c3e0ae8d Merge pull request #155 from mgschwan/main
Make VRServer config path variable
2022-04-12 01:57:10 +03:00
Louka
0b2491ead5 Properly handle elbows with hand tracking
Instead of moving the controller chains to the slimevr hand, now just make the computed elbows affected by the elbow from the slimevr hand chain.

Also refactored some stuff to replace "upper arm distance" by "elbow offset". Idek if it's useful but it may be so I'm keeping it.
2022-04-11 18:39:30 -04:00
Michael Gschwandtner
1723d19882 Make VRServer config path variable
Add a constructor that accepts a path to the config file
2022-04-12 00:37:46 +02:00
Louka
416f96fe44 Merge branch 'main' into hand-tracking 2022-04-11 17:41:32 -04:00
Eiren Rain
7b700b4a0c Merge pull request #154 from mgschwan/main
Make WebSocketVRBridge compatible with Android
2022-04-11 21:37:50 +03:00
Eiren Rain
bde578fb9d Merge pull request #151 from Louka3000/neck-tracking
Neck tracking support owo
2022-04-11 21:05:37 +03:00
Michael Gschwandtner
c41dc490ed Make WebSocketVRBridge compatible with Android
Change JSONObject.optFloat (does not exist on Android) to optDouble
2022-04-11 16:00:42 +02:00
Eiren Rain
6d9e816d19 Merge pull request #153 from Louka3000/filters-fix
Make filter dependent on tracker tps
2022-04-08 00:59:28 +03:00
Erimel
8f19afee88 Merge branch 'SlimeVR:main' into hand-tracking 2022-04-07 01:18:50 -04:00
Louka
784ec877d8 Make filter dependent on tracker tps
Why was this in the wrong place wth
2022-04-06 20:35:47 -04:00
Eiren Rain
48c509ef54 Merge pull request #152 from mgschwan/main
Add full reset command to WebSocket bridge
2022-04-06 15:43:56 +03:00
Eiren Rain
0a5f06816d Merge pull request #150 from Louka3000/patch-1
Null check on designation I forgor
2022-04-06 15:42:06 +03:00
Louka
ecbeaf10ed Remove unnecessary code
Also makes my code a whole lot clearer damn.
Now I can understand it myself!!! :P
2022-04-05 18:02:30 -04:00
Michael Gschwandtner
c8e4918b4e Merge branch 'SlimeVR:main' into main 2022-04-05 22:17:55 +02:00
Michael Gschwandtner
4ab5609dbd Add full reset command to WebSocket bridge 2022-04-05 22:11:40 +02:00
Louka
cfe8eb62e7 Neck tracking support owo
This is not a joke.
As I suspected, adding a neck tracker does help with reducing sliding. I tested it in VRChat myself.
Think of it like adding a fourth spine tracker.
Anyway, this can be useful to mocap suits and furries, pls merge ty owo.
2022-04-05 00:11:43 -04:00
Erimel
f2ceb84969 Null check on designation I forgor
sowwy, this completely breaks the server whenever a new tracker (never connected before) connects.  

;-;
2022-04-04 23:41:51 -04:00
Louka
039628d7e4 Hand tracking support + elbows without controllers
This extends the HMD skeleton to support arm tracking. We do end up with kinda duplicate bones tho.

If user has controllers: elbows go from controllers with tracker on lower arm and possibility for upper arm tracker.

If user has no controller (or no feeder app) and enables Elbow SteamVR trackers: elbows go from chest.

If user has no controllers and enables Hand SteamVR trackers: SteamVR trackers on the users hand appear using upper arm, forearm and hand trackers. Use chest tracker as well for more precision.

This also adds a lot of body proportions...
2022-04-04 22:21:18 -04:00
Eiren Rain
91fb4bc035 Merge pull request #146 from Louka3000/legs-fix
Fix ambiguous usage of "Legs"
2022-04-04 16:07:59 +03:00
Eiren Rain
d89ccc3401 Merge pull request #147 from TheDevMinerTV/fix/pingpongpacket
Fix "could not parse packet" warning for PingPongPackets
2022-04-04 16:02:21 +03:00
loucass003
7ab9a37989 Started implementing new protocol 2022-04-04 07:37:26 +02:00
TheDevMinerTV
cf7054b6da Fix "could not parse packet" warning for PingPongPackets 2022-04-04 00:29:22 +02:00
Louka
758f9e8f2f support old configs + rename in skeleton 2022-04-03 11:44:40 -04:00
Louka
fef21906f5 Fix ambiguous usage of "Legs"
- Leg in tracker roles is now knee
- Legs in SteamVR trackers to enable is now Feet
2022-04-03 11:18:44 -04:00
loucass003
37ce6a69c1 update submodule 2022-04-02 22:35:45 +02:00
loucass003
aaed8fbd49 Body proportions 2022-04-02 22:33:32 +02:00
loucass003
5fbac9d861 More data 2022-04-02 08:39:31 +02:00
lucas lelievre
c54298709f update submodules url 2022-04-01 05:32:25 +02:00
lucas lelievre
b7d1637b18 Add more tracker data 2022-04-01 04:57:51 +02:00
lucas lelievre
015fa551b7 slimevr_protocol submodules 2022-03-31 16:05:34 +02:00
lucas lelievre
a681f0e5b3 rm folder 2022-03-31 16:05:03 +02:00
lucas lelievre
281810dfbb begin websocket api 2022-03-31 16:00:13 +02:00
Eiren Rain
38b8e65d53 Merge pull request #137 from Louka3000/main
Basic Interpolation and Extrapolation
2022-03-28 16:56:05 +03:00
Louka
74ccaa6cf1 fix size/capacity of circle buffer 2022-03-28 00:07:43 -04:00
Louka
4d2083df27 Use circle buffer to get previous rotations
Should help with performance.
2022-03-27 23:58:23 -04:00
Louka
98068232a6 don't create as many quaternions 2022-03-27 06:33:36 -04:00
Eiren Rain
60711df671 Merge pull request #128 from ButterscotchV/autobone-work
Consolidate PoseFrames functionality and add documentation
2022-03-24 14:05:55 +02:00
Erimel
c6b7c11418 Merge branch 'SlimeVR:main' into main 2022-03-23 21:47:52 -04:00
Eiren Rain
ece8811293 Merge pull request #138 from Louka3000/issue61
Align spine yaw with HMD when no spine trackers are present
2022-03-24 02:10:03 +02:00
Louka
8c95c491b9 use GetYaw() 2022-03-23 18:53:43 -04:00
Erimel
45091565b4 Merge branch 'SlimeVR:main' into issue61 2022-03-23 18:42:36 -04:00
Erimel
7c0222189a Merge branch 'SlimeVR:main' into main 2022-03-23 18:42:26 -04:00
Eiren Rain
a84c735761 Update commons 2022-03-24 00:40:20 +02:00
Louka
946c5d4527 calculate hmd yaw better 2022-03-23 18:34:47 -04:00
Louka
2ac6208302 / 2 * 4 = *2 2022-03-23 18:05:58 -04:00
Louka
0cd65c825c Align spine yaw with HMD when no spine tracker
issue https://github.com/SlimeVR/SlimeVR-Server/issues/61
2022-03-23 18:04:09 -04:00
Eiren Rain
43d97a7b97 Merge pull request #136 from Louka3000/upper-arms
Upper arm tracking support for VRC shoulders
2022-03-23 18:38:02 +02:00
Louka
9923556212 removed bad code lol
Code I stole was bad and did unncessery things that made extrapolation wonky
2022-03-22 21:44:29 -04:00
Louka
0a65783637 Make bone
bone yes
2022-03-22 19:58:52 -04:00
Louka
b5bd7e0a26 changed default values for filters 2022-03-22 18:35:00 -04:00
Louka
82779d101b fixed a NullPointerException
When the type was null oops
2022-03-22 01:20:40 -04:00
Louka
9c18d349dd upper arms support
for vrchat's shoulder tracking.
Not recommended since that's 2 trackers PER ARM, but good to have the option.
2022-03-22 00:46:38 -04:00
Louka
8c74071e30 Small adjustments to filter
-renamed frame to tick
- increased max intensity
2022-03-19 13:33:54 -04:00
Louka
256e5079cf Tracker filters GUI and config
I am now master at GUI.
(never again).
Might have done some dumb things like giving IMUTracker VRServer idk, tell me if there's something to fix <3
2022-03-19 03:02:06 -04:00
Louka
4263e86189 Basic interpolation and extrapolation
only the logic. Need to make config and GUI
2022-03-18 22:36:06 -04:00
Eiren Rain
dadb0bb378 Merge pull request #135 from Louka3000/main
Prettier changing proportions
2022-03-15 14:56:26 +02:00
Erimel
8ba25ca840 Merge branch 'SlimeVR:main' into main 2022-03-14 18:12:01 -04:00
Louka
09ac04331f Prettier changing proportions
- Now removes the .0 if the value is whole (only see decimal if is .5)
- Add precision adjust button unselected by default to toggle between 1 and 0.5 adjustment.
2022-03-14 18:11:29 -04:00
Eiren Rain
c21caa76d5 Bump version number to 0.1.6 2022-03-14 20:02:10 +02:00
Eiren Rain
4073d8fc32 Merge pull request #134 from Louka3000/main
Change default proportions and allow half-decimal editing
2022-03-14 15:33:49 +02:00
Erimel
f8b7be8572 Merge branch 'SlimeVR:main' into main 2022-03-13 18:27:01 -04:00
Louka
d6ab811de0 Change default proportions and allow half-decimal editing
- Better initial and default body proportions for people that don't want to calibrate
- Now changes bone length to half decimal (0.5). Rounds up to the nearest 0.5.
2022-03-13 18:25:43 -04:00
Eiren Rain
9fa6722f2b Merge pull request #133 from Blixtdraken/main
Changed the scroll speed for the ui
2022-03-13 18:21:35 +02:00
Blixtdraken
c51204e9cd Changed the scroll speed for the ui. 2022-03-13 17:12:40 +01:00
Eiren Rain
74794d8610 Merge pull request #132 from Louka3000/main
Elbow tracking support OwO
2022-03-12 12:56:49 +02:00
Louka
16978f5acd change default elbow distance
25cm to 22cm
2022-03-11 21:15:49 -05:00
Louka
71e24d0cb9 oops Mighty <3
fix FORWARD being renamed to FRONT
2022-03-10 19:47:28 -05:00
Louka
9a45f99b0e Vertical controllet offset 2022-03-10 18:45:36 -05:00
Louka
7f829f56a3 Fix reset and fix pipe creation order
owo
2022-03-10 18:35:51 -05:00
Erimel
347531f4fe Merge branch 'SlimeVR:main' into main 2022-03-10 18:21:08 -05:00
Eiren Rain
a1c33a0852 Merge pull request #131 from MightyGood/patch-1
Change FORWARD to FRONT for tracker mounting
2022-03-10 17:55:01 +02:00
MightyGood
9cd441654e Change FORWARD to FRONT for tracker mounting
Front fits better with the other options, and should cause less confusion about what the drop-down items mean.
2022-03-10 09:46:24 -06:00
Eiren Rain
88c866a735 Merge pull request #130 from kitlith/feeder_app_protobuf
Switch feeder app from old protocol to new protocol.
2022-03-10 16:00:55 +02:00
Louka
b11492c3f3 minor cleanup
removed skeleton offset for arms; useless
2022-03-10 01:07:35 -05:00
Erimel
52e30b6323 Merge pull request #3 from kitlith/feeder_app_protobuf
Switch feeder app from old protocol to new protocol.
2022-03-10 00:37:47 -05:00
Louka
1ebad806f9 Fix reset and wrist
oh wow, it works.
2022-03-09 19:24:35 -05:00
Louka
51129d3b5d Wrists
doesn't work as intended but does something
2022-03-09 01:27:13 -05:00
Louka
379e1cdcf0 Get all nodes better
for SkeletonList
2022-03-09 01:08:26 -05:00
Louka
0a8f76cfd4 Initial elbows commit
testing
2022-03-08 00:13:49 -05:00
Kitlith
a382698c32 Switch feeder app from old protocol to new protocol. 2022-03-07 20:56:22 -08:00
Butterscotch!
d43002952c Consolidate PoseFrames functionality & add documentation 2022-02-17 22:51:15 -05:00
Eiren Rain
718f1d02c6 Merge pull request #127 from ColdIce1605/ignore-runtime-created-files
Ignore runtime created files
2022-02-17 08:40:52 +02:00
ColdIce
542de22550 Revert "restart branch"
This reverts commit e18ce338e9.
2022-02-16 21:16:22 -06:00
ColdIce
327d458f00 update .gitignore 2022-02-16 21:14:49 -06:00
Eiren Rain
5f206dd12e Merge pull request #125 from Louka3000/patch-2
waist tracker will default to hip before chest
2022-02-16 12:33:09 +02:00
ColdIce
e18ce338e9 restart branch 2022-02-16 01:25:15 -06:00
Erimel
51e6255e9d waist tracker will default to hip before chest
If user has chest + waist but uses chest + hip in the server, chest would be chest+waist and waist would be hip.
This can be fixed by a better explanation of “waist vs hip” in the new GUI, or just defaulting waist to hip instead of chest when no waist tracker is found.
2022-02-15 14:48:21 -05:00
Eiren Rain
7fa7e6c2cc Merge pull request #124 from ColdIce1605/platform-package-fix
Platform Package Fix
2022-02-15 10:19:34 +02:00
ColdIce
710d154817 remove files 2022-02-15 02:17:55 -06:00
ColdIce
f354a10a81 fix .idea related things
sadly can't get rid of the one Magneto change do to LF :P
2022-02-15 02:17:14 -06:00
ColdIce
8bb8135f41 Move windows related code to its own package
Ah no more git history issues
2022-02-15 02:12:20 -06:00
Eiren Rain
e7b9968519 Better tracker timeout handling 2022-02-15 09:59:27 +02:00
Eiren Rain
bfc58d51f2 Merge pull request #122 from Kamilake/patch-1
Change the minimum compatible Java version
2022-02-11 07:14:10 +02:00
Eiren Rain
c158022da5 Merge pull request #123 from Kamilake/patch-2
Add a hide password option
2022-02-11 05:56:55 +02:00
Kamilake
9e010b0026 Add a hide password option 2022-02-11 11:44:18 +09:00
Kamilake
a085b09e07 Change the minimum compatible Java version 2022-02-11 09:36:11 +09:00
Kamilake
37da4ab7fe Change the minimum compatible Java version 2022-02-11 09:22:28 +09:00
Eiren Rain
c5945d784b Merge pull request #121 from ButterscotchV/spell-fix
Fix spelling mistakes
2022-02-09 21:09:33 +02:00
Butterscotch!
16ca08446b Fix spelling mistakes 2022-02-09 04:44:05 -05:00
Eiren Rain
b487350714 Update version to 0.1.5 2022-02-07 20:31:19 +02:00
Eiren Rain
753b12b49e Merge pull request #120 from ButterscotchV/bvh-recording
Add simple BVH recording button
2022-02-07 20:01:45 +02:00
Butterscotch!
0d90cf9c20 Add simple BVH recording button 2022-02-07 01:40:11 -05:00
Eiren Rain
658fd2916d Merge pull request #118 from deiteris/main
Switch to latest gradle and update dependencies
2022-02-05 17:43:00 +02:00
Yury
ed4ea675fb Keep JavaOSC package and remove slf4j package 2022-02-05 18:39:10 +03:00
Yury
2746fd7a67 Remove guava 2022-02-05 18:37:25 +03:00
Eiren Rain
a6b92c60b0 Update commons 2022-02-05 17:33:40 +02:00
Eiren Rain
d4d36a65ec Merge pull request #119 from ButterscotchV/bvh-fix
Fix BVH local angle calculations and abstract PoseStreamer
2022-02-05 17:25:34 +02:00
Butterscotch!
97df8ee12f Simplify PoseFrameStreamer constructor 2022-02-05 07:21:14 -05:00
Butterscotch!
2ab637b4e8 Improve PoseFrameStreamer functionality 2022-02-05 07:08:45 -05:00
Butterscotch!
9a821b051f Abstract PoseStreamer 2022-02-05 07:08:44 -05:00
Butterscotch!
e2f09fc93d Add fake root method & fix local angle calculation 2022-02-05 06:01:07 -05:00
Butterscotch!
891d8e0468 Fix BVH angles 2022-02-05 05:04:31 -05:00
Yury
494e31e41f Switch to latest gradle and update dependencies 2022-02-05 13:02:46 +03:00
Eiren Rain
b369ae6a2a Fix battery reading compatibility with owoTrack 2022-02-02 18:39:53 +02:00
Eiren Rain
5c22ef0192 Fix backwards compatibility with extensions on old firmware 2022-01-29 00:45:28 +02:00
Eiren Rain
d99cbb9c85 Fix backwards compatibility with old firmware and owoTrack 2022-01-28 02:53:08 +02:00
Eiren Rain
4f14f01830 Merge pull request #114 from SlimeVR/test
Network refactoring
2022-01-27 21:51:56 +02:00
Eiren Rain
930b5c701a Cleanup UDP responses 2022-01-27 21:38:49 +02:00
Eiren Rain
bd9e2c47a3 Merge branch 'main' into test 2022-01-27 21:30:39 +02:00
Eiren Rain
53ca2cf881 Move UDP packets and parsing to own calsses
Cleanup UDP networking significantly
2022-01-27 21:30:25 +02:00
Eiren Rain
55e17e7625 Minor network fixes 2022-01-27 19:52:30 +02:00
Eiren Rain
13b37aa2a9 Improved debug, added checkbox to display sensors debug info 2022-01-27 19:31:13 +02:00
Eiren Rain
fe4dde69ea Merge pull request #113 from Louka3000/patch-1
fix link setup
2022-01-27 02:21:07 +02:00
Erimel
0268a5a3ec fix link setup 2022-01-26 19:13:52 -05:00
Eiren Rain
4bddb529d4 Fix ping not working 2022-01-25 22:00:02 +02:00
Eiren Rain
435f5d1751 Don't create new trackers if tracker's IP changed while server is running, hand over old trackers to the new connection
Implements #70
2022-01-25 21:46:50 +02:00
Eiren Rain
af8ce60dbe Fix signal strength reading 2022-01-20 20:57:03 +02:00
Eiren Rain
25f53232cd Fix packet number reading 2022-01-20 20:27:23 +02:00
Eiren Rain
012cb518b3 Fix merge issues, track packets order, improve logging in UDP server 2022-01-20 18:03:53 +02:00
Eiren Rain
2d1ffbc5b0 Merge branch 'main' into test
# Conflicts:
#	src/main/java/dev/slimevr/vr/trackers/TrackersUDPServer.java
2022-01-20 17:54:56 +02:00
Eiren Rain
c88a6802a9 Minor debug stuff 2022-01-20 17:50:08 +02:00
Eiren Rain
f5d608ac6a Merge pull request #111 from ButterscotchV/autobone-fix
Fix AutoBone overwriting configs & improve code documentation
2022-01-20 14:19:46 +03:00
Butterscotch!
5d49bbfb29 Fix AutoBone overwriting configs & improve code documentation 2022-01-19 19:20:47 -05:00
Eiren Rain
5ce520a316 Merge pull request #110 from deiteris/main
Fix decimal places for battery voltage
2022-01-20 00:44:49 +03:00
Yury
98c2c6e202 Merge branch 'main' of https://github.com/deiteris/SlimeVR-Server 2022-01-20 00:25:12 +03:00
Yury
a2fc809d71 Fix decimal places for battery voltage 2022-01-20 00:24:47 +03:00
Eiren Rain
eb302aaef1 Merge pull request #107 from Louka3000/main
Updated default body proportions (again
2022-01-16 02:12:36 +03:00
Louka
3b354f103a Update SkeletonConfigValue.java 2022-01-15 18:00:50 -05:00
Eiren Rain
03c24a5d39 Merge pull request #106 from deiteris/main
Fix log in firewall_uninstall.bat
2022-01-13 01:11:38 +03:00
Yury
a8f13bb570 Fix log in firewall_uninstall.bat 2022-01-13 00:56:08 +03:00
Eiren Rain
f8e35e0a72 Merge pull request #105 from deiteris/main
Show battery level reported by tracker
2022-01-13 00:32:10 +03:00
Yury
27c153f5d3 Show battery level reported by tracker 2022-01-12 19:30:51 +03:00
Eiren Rain
82fdedfa14 Minor changes 2022-01-10 12:54:25 +02:00
Eiren Rain
f5bfbb13e2 Added contributions notice to the README 2022-01-10 12:50:02 +02:00
Eiren Rain
80de578334 Fix missing refactoring changes 2022-01-09 12:27:32 +02:00
Eiren Rain
3b0acbe406 Create new trackers only when sensor info packet received 2022-01-09 12:25:57 +02:00
Eiren Rain
1062361612 Merge pull request #99 from deiteris/main
Add RSSI to trackers
2022-01-09 13:17:57 +03:00
Eiren Rain
7d81fe6f92 Merge pull request #102 from Louka3000/main
Skeleton offset
2022-01-09 11:13:42 +03:00
Eiren Rain
0285eca613 Merge pull request #103 from ColdIce1605/fix-typos
Fix typos
2022-01-08 09:03:10 +03:00
James R
b0aea9ba89 fix typos 2022-01-07 20:54:34 -06:00
Louka
b98eafb66f Remove vertical foot offset
Only keep skeleton offset
2022-01-07 09:12:06 -05:00
Louka
566df6793c fixed foot rotation (I broke it)
I still think there's a better way to do this horizontal foot offset thing
2022-01-06 23:43:26 -05:00
Louka
4949e0a7f3 Skeleton offset and vertical foot offset 2022-01-06 22:30:20 -05:00
Yury
572dcdf1bb Add RSSI to trackers 2022-01-02 23:25:56 +03:00
Eiren Rain
80ce825494 Merge pull request #97 from carl-anders/keybindings-wrap-even-more-no-more-panics-plz
Keybindings: Catch even more errors, and manually check that operating system is Windows
2021-12-27 02:23:13 +03:00
Carl Andersson
bdc3b1971c Fix accidental inversion of check 2021-12-26 21:49:46 +01:00
Carl Andersson
cee400a4c6 Simplify OS & error checks 2021-12-26 21:48:52 +01:00
Carl Andersson
e58706d212 Keybindings: Catch even more errors, and manually check that operating system is Windows 2021-12-26 06:05:27 +01:00
172 changed files with 20665 additions and 13138 deletions

View File

@@ -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/jdk-11.0.1"/>
<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>

View File

@@ -1,13 +0,0 @@
# This file is for unifying the coding style for different editors and IDEs
# See editorconfig.org
root = true
[*]
indent_style = tab
end_of_line = lf
charset = utf-8
# This line causes problems with VSCode and potentially with other editors where all purely
# whitespace lines are trimmed to be empty when saved, causing excessive worthless changes with Git
#trim_trailing_whitespace = true
insert_final_newline = true

12
.gitattributes vendored
View File

@@ -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
View 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/*

View File

@@ -1,57 +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: SlimeVR Server
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Set up JDK 11
uses: actions/setup-java@v2.4.0
with:
java-version: '11'
distribution: 'adopt'
cache: 'gradle' # will restore cache of dependencies and wrappers
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
run: ./gradlew clean test
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.4.0
with:
submodules: recursive
- name: Set up JDK 11
uses: actions/setup-java@v2.4.0
with:
java-version: '11'
distribution: 'adopt'
cache: 'gradle' # will restore cache of dependencies and wrappers
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew clean shadowJar
- 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
View File

@@ -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

3
.gitmodules vendored
View File

@@ -1,3 +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

View File

@@ -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>

View File

@@ -1,2 +0,0 @@
connection.project.dir=
eclipse.preferences.version=1

View File

@@ -1,2 +0,0 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View File

@@ -1,2 +0,0 @@
eclipse.preferences.version=1
line.separator=\r\n

View File

@@ -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

View File

@@ -1,3 +0,0 @@
eclipse.preferences.version=1
formatter_profile=_Essentia
formatter_settings_version=13

11
.vscode/extensions.json vendored Normal file
View 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
View 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
View File

@@ -1,21 +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.
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.

View File

@@ -15,33 +15,20 @@ Integrations:
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/slimevr-setup.html).
Latest instructions are [on our site](https://docs.slimevr.dev/server-setup/slimevr-setup.html).
## How to build
## License Clarification
You need to execute these commands in the folder where you want this project.
**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.
```bash
# Clone repositories
git clone --recursive https://github.com/SlimeVR/SlimeVR-Server.git
**However, the MIT License has some limits, and if you wish to distribute software based on SlimeVR, you need to be aware of them:**
# Enter the directory and build the runnable server JAR
cd SlimeVR-Server
gradlew shadowJar
```
* 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.
Open Slime VR Server project in Eclipse or Intellij Idea
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.
run gradle command `shadowJar` to build a runnable server JAR
## 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.
## License Clarifications
**SlimeVR software** (including server, firmware, drivers, installator, documents, and others - see licence for each case specifically) **is distributed under MIT License and is copyright of Eiren Rain and SlimeVR.** MIT Licence is a permissive license giving you rights to modify and distribute the software with little strings attached.
**However, there are some limits, and if you wish to distribute software based on SlimeVR, you need to be aware of them:**
* When distributing any software based on SlimeVR, you have to clarify to the end user that your software is based on SlimeVR that is distributed under MIT License and is subject to copyright of Eiren Rain
* You must clarify either which parts of original software you're using, or what changes you did to the original software (i.e. clarify which parts of your software is covered by MIT License)
* You must provide a copy of the original license (see LICENSE file)
* You don't have to release your own software under MIT License or even open source at all, but you have to state that it's based on SlimeVR
* This applies even if you distribute software without the source code
For a how-to on contributing, see [CONTRIBUTING.md](CONTRIBUTING.md).

View File

@@ -8,11 +8,12 @@
plugins {
id 'application'
id "com.github.johnrengelman.shadow" version "6.1.0"
id "com.github.johnrengelman.shadow" version "7.1.2"
id "com.diffplug.spotless" version "6.5.1"
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
sourceCompatibility = 1.11
targetCompatibility = 1.11
// Set compiler to use UTF-8
compileJava.options.encoding = 'UTF-8'
@@ -21,11 +22,9 @@ javadoc.options.encoding = 'UTF-8'
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
if (JavaVersion.current().isJava9Compatible()) {
// TODO: Gradle 6.6
// options.release = 8
options.compilerArgs.addAll(['--release', '8'])
}
// if (JavaVersion.current().isJava9Compatible()) {
// options.release = 8
// }
}
tasks.withType(Test) {
systemProperty('file.encoding', 'UTF-8')
@@ -38,30 +37,32 @@ allprojects {
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
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.
compile 'org.apache.commons:commons-math3:3.6.1'
compile 'org.yaml:snakeyaml:1.25'
compile 'net.java.dev.jna:jna:5.6.0'
compile 'net.java.dev.jna:jna-platform:5.6.0'
compile 'com.illposed.osc:javaosc-core:0.8'
compile 'com.fazecast:jSerialComm:[2.0.0,3.0.0)'
compile 'com.google.protobuf:protobuf-java:3.17.3'
compile "org.java-websocket:Java-WebSocket:1.5.1"
compile 'com.melloware:jintellitype:1.4.0'
implementation group: 'com.google.flatbuffers', name: 'flatbuffers-java', version: '2.0.3'
implementation group: 'commons-cli', name: 'commons-cli', version: '1.3.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:28.2-jre'
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.7.2')
testImplementation platform('org.junit:junit-bom:5.8.2')
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.junit.platform:junit-platform-launcher'
}
@@ -75,5 +76,32 @@ shadowJar {
archiveVersion.set('')
}
application {
mainClassName = 'io.eiren.vr.Main'
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
View 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

Binary file not shown.

View File

@@ -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
View 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
View File

@@ -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

View File

@@ -1,21 +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.
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.

View File

@@ -1,7 +1,7 @@
@echo off
echo Installing firewall rules...
rem Discovery defauly port
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
@@ -14,4 +14,4 @@ netsh advfirewall firewall add rule name="SlimeVR TCP 21110 incoming" dir=in act
netsh advfirewall firewall add rule name="SlimeVR TCP 21110 outgoing" dir=out action=allow protocol=TCP localport=21110
echo Done!
pause
pause

View File

@@ -1,5 +1,5 @@
@echo off
echo Installing firewall rules...
echo Uninstalling firewall rules...
rem Discovery defauly port
netsh advfirewall firewall delete rule name="SlimeVR UDP 35903 incoming"

View File

@@ -9,3 +9,7 @@
rootProject.name = 'SlimeVR Server'
include ':slime-java-commons'
include ':solarxr-protocol'
project(':solarxr-protocol').projectDir = new File('solarxr-protocol/protocol/java')

1
solarxr-protocol Submodule

Submodule solarxr-protocol added at ceaae26ea2

400
spotless.xml Normal file
View 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>

View File

@@ -1,49 +1,93 @@
package dev.slimevr;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import javax.swing.JOptionPane;
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
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.3";
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) {
} catch (Exception e1) {
e1.printStackTrace();
}
if (!SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_11)) {
LogManager.log.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);
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.log.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);
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;
}
@@ -51,22 +95,24 @@ public class Main {
vrServer = new VRServer();
vrServer.start();
new Keybinding(vrServer);
new VRServerGUI(vrServer);
} catch(Throwable e) {
if (!cmd.hasOption("no-gui"))
new VRServerGUI(vrServer);
} catch (Throwable e) {
e.printStackTrace();
try {
Thread.sleep(2000L);
} catch(InterruptedException e2) {
} catch (InterruptedException e2) {
e.printStackTrace();
}
System.exit(1); // Exit in case error happened on init and window not appeared, but some thread started
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) {
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,8 @@
package dev.slimevr;
public enum NetworkProtocol {
OWO_LEGACY,
SLIMEVR_RAW,
SLIMEVR_FLATBUFFER,
SLIMEVR_WEBSOCKET
}

View File

@@ -1,33 +1,18 @@
package dev.slimevr;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;
import dev.slimevr.autobone.AutoBoneHandler;
import dev.slimevr.bridge.Bridge;
import dev.slimevr.bridge.NamedPipeBridge;
import dev.slimevr.bridge.SteamVRPipeInputBridge;
import dev.slimevr.bridge.VMCBridge;
import dev.slimevr.bridge.WebSocketVRBridge;
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.HumanSkeleton;
import dev.slimevr.vr.trackers.HMDTracker;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.Tracker;
import dev.slimevr.vr.trackers.TrackerConfig;
import dev.slimevr.vr.trackers.TrackersUDPServer;
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;
@@ -35,131 +20,169 @@ 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 {
private final List<Tracker> trackers = new FastList<>();
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<>();
public final YamlFile config = new YamlFile();
public final HMDTracker hmdTracker;
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
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) {
/*
if (OperatingSystem.getCurrentPlatform() == OperatingSystem.WINDOWS) {
// Create named pipe bridge for SteamVR driver
NamedPipeVRBridge driverBridge = new NamedPipeVRBridge(hmdTracker, shareTrackers, this);
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
SteamVRPipeInputBridge steamVRInput = new SteamVRPipeInputBridge(this);
tasks.add(() -> steamVRInput.startBridge());
bridges.add(steamVRInput);
//*/
NamedPipeBridge driverBridge = new NamedPipeBridge(hmdTracker, "steamvr", "SteamVR Driver Bridge", "\\\\.\\pipe\\SlimeVRDriver", shareTrackers);
tasks.add(() -> driverBridge.startBridge());
bridges.add(driverBridge);
// 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) {
} catch (UnknownHostException e) {
e.printStackTrace();
}
bvhRecorder = new BVHRecorder(this);
registerTracker(hmdTracker);
for(int i = 0; i < shareTrackers.size(); ++i)
registerTracker(shareTrackers.get(i));
for (Tracker tracker : shareTrackers) {
registerTracker(tracker);
}
}
public boolean hasBridge(Class<? extends Bridge> bridgeClass) {
for(int i = 0; i < bridges.size(); ++i) {
if(bridgeClass.isAssignableFrom(bridges.get(i).getClass()))
for (Bridge bridge : bridges) {
if (bridgeClass.isAssignableFrom(bridge.getClass())) {
return true;
}
}
return false;
}
@ThreadSafe
public <E extends Bridge> E getVRBridge(Class<E> bridgeClass) {
for(int i = 0; i < bridges.size(); ++i) {
Bridge b = bridges.get(i);
if(bridgeClass.isAssignableFrom(b.getClass()))
return bridgeClass.cast(b);
for (Bridge bridge : bridges) {
if (bridgeClass.isAssignableFrom(bridge.getClass())) {
return bridgeClass.cast(bridge);
}
}
return null;
}
@ThreadSafe
public TrackerConfig getTrackerConfig(Tracker tracker) {
synchronized(configuration) {
synchronized (configuration) {
TrackerConfig config = configuration.get(tracker.getName());
if(config == null) {
if (config == null) {
config = new TrackerConfig(tracker);
configuration.put(tracker.getName(), config);
}
return config;
}
}
private void loadConfig() {
try {
config.load(new FileInputStream(new File("vrconfig.yml")));
} catch(FileNotFoundException e) {
config.load(new FileInputStream(new File(this.configPath)));
} catch (FileNotFoundException e) {
// Config file didn't exist, is not an error
} catch(YamlException e) {
} catch (YamlException e) {
e.printStackTrace();
}
List<YamlNode> trackersConfig = config.getNodeList("trackers", null);
for(int i = 0; i < trackersConfig.size(); ++i) {
TrackerConfig cfg = new TrackerConfig(trackersConfig.get(i));
synchronized(configuration) {
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(int i = 0; i < trackers.size(); ++i)
consumer.accept(trackers.get(i));
for (Tracker tracker : trackers) {
consumer.accept(tracker);
}
});
}
@ThreadSafe
public void trackerUpdated(Tracker tracker) {
queueTask(() -> {
@@ -171,7 +194,7 @@ public class VRServer extends Thread {
}
@ThreadSafe
public void addSkeletonUpdatedCallback(Consumer<HumanSkeleton> consumer) {
public void addSkeletonUpdatedCallback(Consumer<Skeleton> consumer) {
queueTask(() -> {
humanPoseProcessor.addSkeletonUpdatedCallback(consumer);
});
@@ -181,64 +204,65 @@ public class VRServer extends Thread {
public synchronized void saveConfig() {
List<YamlNode> nodes = config.getNodeList("trackers", null);
List<Map<String, Object>> trackersConfig = new FastList<>(nodes.size());
for(int i = 0; i < nodes.size(); ++i) {
trackersConfig.add(nodes.get(i).root);
for (YamlNode node : nodes) {
trackersConfig.add(node.root);
}
config.setProperty("trackers", trackersConfig);
synchronized(configuration) {
synchronized (configuration) {
Iterator<TrackerConfig> iterator = configuration.values().iterator();
while(iterator.hasNext()) {
while (iterator.hasNext()) {
TrackerConfig tc = iterator.next();
Map<String, Object> cfg = null;
for(int i = 0; i < trackersConfig.size(); ++i) {
Map<String, Object> c = trackersConfig.get(i);
if(tc.trackerName.equals(c.get("name"))) {
for (Map<String, Object> c : trackersConfig) {
if (tc.trackerName.equals(c.get("name"))) {
cfg = c;
break;
}
}
if(cfg == null) {
if (cfg == null) {
cfg = new HashMap<>();
trackersConfig.add(cfg);
}
tc.saveConfig(new YamlNode(cfg));
}
}
File cfgFile = new File("vrconfig.yml");
File cfgFile = new File(this.configPath);
try {
config.save(new FileOutputStream(cfgFile));
} catch(IOException e) {
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
@VRServerThread
public void run() {
trackersServer.start();
while(true) {
//final long start = System.currentTimeMillis();
while (true) {
// final long start = System.currentTimeMillis();
do {
Runnable task = tasks.poll();
if(task == null)
if (task == null)
break;
task.run();
} while(true);
for(int i = 0; i < onTick.size(); ++i) {
this.onTick.get(i).run();
} while (true);
for (Runnable task : onTick) {
task.run();
}
for (Bridge bridge : bridges) {
bridge.dataRead();
}
for (Tracker tracker : trackers) {
tracker.tick();
}
for(int i = 0; i < bridges.size(); ++i)
bridges.get(i).dataRead();
for(int i = 0; i < trackers.size(); ++i)
trackers.get(i).tick();
humanPoseProcessor.update();
for(int i = 0; i < bridges.size(); ++i)
bridges.get(i).dataWrite();
//final long time = System.currentTimeMillis() - start;
for (Bridge bridge : bridges) {
bridge.dataWrite();
}
// final long time = System.currentTimeMillis() - start;
try {
Thread.sleep(1); // 1000Hz
} catch(InterruptedException e) {
}
} catch (InterruptedException e) {}
}
}
@@ -246,12 +270,12 @@ public class VRServer extends Thread {
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);
@@ -259,23 +283,40 @@ public class VRServer extends Thread {
queueTask(() -> {
trackers.add(tracker);
trackerAdded(tracker);
for(int i = 0; i < newTrackersConsumers.size(); ++i)
newTrackersConsumers.get(i).accept(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();
}
@@ -283,4 +324,48 @@ public class VRServer extends Thread {
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;
}
}

File diff suppressed because it is too large Load Diff

View 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?
}
}

View 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);
}

View 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);
}
}
}

View 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View 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);
}
}

View File

@@ -0,0 +1,8 @@
package dev.slimevr.autobone.errors;
import dev.slimevr.autobone.AutoBoneTrainingStep;
public interface IAutoBoneError {
public float getStepError(AutoBoneTrainingStep trainingStep) throws AutoBoneException;
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -3,41 +3,42 @@ package dev.slimevr.bridge;
import dev.slimevr.util.ann.VRServerThread;
import dev.slimevr.vr.trackers.ShareableTracker;
/**
* Bridge handles sending and recieving 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 recieved from the <b>remote side</b> or send shared <b>local
* trackers</b> to the other side.
* 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
public void dataRead();
void dataRead();
@VRServerThread
public void dataWrite();
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
* 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
public void addSharedTracker(ShareableTracker tracker);
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.
* 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
public void removeSharedTracker(ShareableTracker tracker);
void removeSharedTracker(ShareableTracker tracker);
@VRServerThread
public void startBridge();
void startBridge();
}

View File

@@ -3,7 +3,7 @@ package dev.slimevr.bridge;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value = RetentionPolicy.SOURCE)
public @interface BridgeThread {
}

View File

@@ -1,224 +0,0 @@
package dev.slimevr.bridge;
import java.io.IOException;
import java.util.List;
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.Pipe.PipeState;
import dev.slimevr.bridge.ProtobufMessages.ProtobufMessage;
import dev.slimevr.bridge.ProtobufMessages.TrackerAdded;
import dev.slimevr.util.ann.VRServerThread;
import dev.slimevr.vr.trackers.HMDTracker;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.TrackerPosition;
import dev.slimevr.vr.trackers.TrackerRole;
import dev.slimevr.vr.trackers.VRTracker;
import io.eiren.util.logging.LogManager;
public class NamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnable {
private final TrackerRole[] defaultRoles = new TrackerRole[] {TrackerRole.WAIST, TrackerRole.LEFT_FOOT, TrackerRole.RIGHT_FOOT};
private final byte[] buffArray = new byte[2048];
protected Pipe pipe;
protected final String pipeName;
protected final String bridgeSettingsKey;
protected final Thread runnerThread;
private final List<? extends ShareableTracker> shareableTrackers;
public NamedPipeBridge(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(int i = 0; i < shareableTrackers.size(); ++i) {
ShareableTracker tr = shareableTrackers.get(i);
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(int i = 0; i < shareableTrackers.size(); ++i) {
ShareableTracker tr = shareableTrackers.get(i);
if(tr.getTrackerRole() == role) {
return sharedTrackers.contains(tr);
}
}
return false;
}
@VRServerThread
public void changeShareSettings(TrackerRole role, boolean share) {
if(role == null)
return;
for(int i = 0; i < shareableTrackers.size(); ++i) {
ShareableTracker tr = shareableTrackers.get(i);
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.getByRole(role));
}
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.log.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.log.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);
messageRecieved(message);
readAnything = true;
} else {
pipe.state = PipeState.ERROR;
LogManager.log.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.log.severe("[" + bridgeName + "] Pipe error: " + Kernel32.INSTANCE.GetLastError());
}
return false;
}
private void resetPipe() {
Pipe.safeDisconnect(pipe);
pipe.state = PipeState.CREATED;
Main.vrServer.queueTask(this::disconnected);
}
private void createPipe() throws IOException {
try {
pipe = new Pipe(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.log.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.log.info("[" + bridgeName + "] Pipes are created");
} catch(IOException e) {
Pipe.safeDisconnect(pipe);
throw e;
}
}
private boolean tryOpeningPipe(Pipe pipe) {
if(Kernel32.INSTANCE.ConnectNamedPipe(pipe.pipeHandle, null) || Kernel32.INSTANCE.GetLastError() == WinError.ERROR_PIPE_CONNECTED) {
pipe.state = PipeState.OPEN;
LogManager.log.info("[" + bridgeName + "] Pipe " + pipe.name + " is open");
Main.vrServer.queueTask(this::reconnected);
return true;
}
LogManager.log.info("[" + bridgeName + "] Error connecting to pipe " + pipe.name + ": " + Kernel32.INSTANCE.GetLastError());
return false;
}
}

View File

@@ -1,290 +0,0 @@
package dev.slimevr.bridge;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
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.Pipe.PipeState;
import dev.slimevr.vr.trackers.ComputedTracker;
import dev.slimevr.vr.trackers.HMDTracker;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.Tracker;
import dev.slimevr.vr.trackers.TrackerStatus;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
public class NamedPipeVRBridge extends Thread implements Bridge {
private static final int MAX_COMMAND_LENGTH = 2048;
public static final String HMDPipeName = "\\\\.\\pipe\\HMDPipe";
public static final String TrackersPipeName = "\\\\.\\pipe\\TrackPipe";
public static final Charset ASCII = Charset.forName("ASCII");
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 Pipe hmdPipe;
private final HMDTracker hmd;
private final List<Pipe> trackerPipes;
private final List<? extends Tracker> shareTrackers;
private final List<ComputedTracker> internalTrackers;
private final HMDTracker internalHMDTracker = new HMDTracker("itnernal://HMD");
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
public NamedPipeVRBridge(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(int i = 0; i < shareTrackers.size(); ++i) {
Tracker t = shareTrackers.get(i);
ComputedTracker ct = new ComputedTracker(t.getTrackerId(), "internal://" + t.getName(), true, true);
ct.setStatus(TrackerStatus.OK);
this.internalTrackers.add(ct);
}
}
@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) {
Pipe 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.log.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.log.severe("[VRBridge] Short HMD data recieved: " + commandBuilder.toString());
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) {
Pipe 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(Pipe pipe) {
hmd.setStatus(TrackerStatus.OK);
}
private void initTrackerPipe(Pipe 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(Pipe pipe) {
if(Kernel32.INSTANCE.ConnectNamedPipe(pipe.pipeHandle, null)) {
pipe.state = PipeState.OPEN;
LogManager.log.info("[VRBridge] Pipe " + pipe.name + " is open");
return true;
}
LogManager.log.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(int i = 0; i < trackerPipes.size(); ++i) {
if(trackerPipes.get(i).state == PipeState.CREATED)
return false;
}
return true;
}
private void createPipes() throws IOException {
try {
hmdPipe = new Pipe(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.log.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.log.info("[VRBridge] Pipe " + pipeName + " created");
trackerPipes.add(new Pipe(pipeHandle, pipeName));
}
LogManager.log.info("[VRBridge] Pipes are open");
} catch(IOException e) {
safeDisconnect(hmdPipe);
for(int i = 0; i < trackerPipes.size(); ++i)
safeDisconnect(trackerPipes.get(i));
trackerPipes.clear();
throw e;
}
}
public static void safeDisconnect(Pipe pipe) {
try {
if(pipe != null && pipe.pipeHandle != null)
Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle);
} catch(Exception 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();
}
}

View File

@@ -3,40 +3,40 @@ 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() {
}
}

View File

@@ -0,0 +1,7 @@
package dev.slimevr.bridge;
public enum PipeState {
CREATED,
OPEN,
ERROR
}

View File

@@ -1,54 +1,40 @@
package dev.slimevr.bridge;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import dev.slimevr.Main;
import dev.slimevr.bridge.ProtobufMessages.Position;
import dev.slimevr.bridge.ProtobufMessages.ProtobufMessage;
import dev.slimevr.bridge.ProtobufMessages.TrackerAdded;
import dev.slimevr.bridge.ProtobufMessages.TrackerStatus;
import dev.slimevr.bridge.ProtobufMessages.UserAction;
import dev.slimevr.bridge.ProtobufMessages.*;
import dev.slimevr.util.ann.VRServerThread;
import dev.slimevr.vr.trackers.ComputedTracker;
import dev.slimevr.vr.trackers.HMDTracker;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.TrackerRole;
import dev.slimevr.vr.trackers.VRTracker;
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<>();
@VRServerThread
protected final List<ShareableTracker> sharedTrackers = new FastList<>();
@Synchronize("self")
private final Map<String, T> remoteTrackersBySerial = new HashMap<>();
@Synchronize("self")
private final Map<Integer, T> remoteTrackersByTrackerId = new HashMap<>();
private boolean hadNewData = false;
private T hmdTracker;
private final HMDTracker hmd;
protected final String bridgeName;
private boolean hadNewData = false;
private T hmdTracker;
public ProtobufBridge(String bridgeName, HMDTracker hmd) {
this.bridgeName = bridgeName;
this.hmd = hmd;
@@ -58,10 +44,10 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
protected abstract boolean sendMessageReal(ProtobufMessage message);
@BridgeThread
protected void messageRecieved(ProtobufMessage message) {
protected void messageReceived(ProtobufMessage message) {
inputQueue.add(message);
}
@ThreadSafe
protected void sendMessage(ProtobufMessage message) {
outputQueue.add(message);
@@ -70,22 +56,22 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
@BridgeThread
protected void updateMessageQueue() {
ProtobufMessage message = null;
while((message = outputQueue.poll()) != null) {
if(!sendMessageReal(message))
while ((message = outputQueue.poll()) != null) {
if (!sendMessageReal(message))
return;
}
}
@VRServerThread
@Override
public void dataRead() {
hadNewData = false;
ProtobufMessage message = null;
while((message = inputQueue.poll()) != null) {
processMessageRecieved(message);
while ((message = inputQueue.poll()) != null) {
processMessageReceived(message);
hadNewData = true;
}
if(hadNewData && hmdTracker != null) {
if (hadNewData && hmdTracker != null) {
trackerOverrideUpdate(hmdTracker, hmd);
}
}
@@ -101,22 +87,24 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
@VRServerThread
@Override
public void dataWrite() {
if(!hadNewData) // Don't write anything if no message were recieved, we always process at the speed of the other side
if (!hadNewData) // Don't write anything if no message were received, we
// always process at the
// speed of the other side
return;
for(int i = 0; i < sharedTrackers.size(); ++i) {
writeTrackerUpdate(sharedTrackers.get(i));
for (ShareableTracker tracker : sharedTrackers) {
writeTrackerUpdate(tracker);
}
}
@VRServerThread
protected void writeTrackerUpdate(ShareableTracker localTracker) {
Position.Builder builder = Position.newBuilder().setTrackerId(localTracker.getTrackerId());
if(localTracker.getPosition(vec1)) {
if (localTracker.getPosition(vec1)) {
builder.setX(vec1.x);
builder.setY(vec1.y);
builder.setZ(vec1.z);
}
if(localTracker.getRotation(quat1)) {
if (localTracker.getRotation(quat1)) {
builder.setQx(quat1.getX());
builder.setQy(quat1.getY());
builder.setQz(quat1.getZ());
@@ -124,51 +112,58 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
}
sendMessage(ProtobufMessage.newBuilder().setPosition(builder).build());
}
@VRServerThread
protected void processMessageRecieved(ProtobufMessage message) {
//if(!message.hasPosition())
// LogManager.log.info("[" + bridgeName + "] MSG: " + message);
if(message.hasPosition()) {
positionRecieved(message.getPosition());
} else if(message.hasUserAction()) {
userActionRecieved(message.getUserAction());
} else if(message.hasTrackerStatus()) {
trackerStatusRecieved(message.getTrackerStatus());
} else if(message.hasTrackerAdded()) {
trackerAddedRecieved(message.getTrackerAdded());
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 positionRecieved(Position positionMessage) {
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());
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 trackerAddedRecieved(TrackerAdded trackerAdded) {
protected void trackerAddedReceived(TrackerAdded trackerAdded) {
T tracker = getInternalRemoteTrackerById(trackerAdded.getTrackerId());
if(tracker != null) {
if (tracker != null) {
// TODO reinit?
return;
}
tracker = createNewTracker(trackerAdded);
synchronized(remoteTrackersBySerial) {
synchronized (remoteTrackersBySerial) {
remoteTrackersBySerial.put(tracker.getName(), tracker);
}
synchronized(remoteTrackersByTrackerId) {
synchronized (remoteTrackersByTrackerId) {
remoteTrackersByTrackerId.put(tracker.getTrackerId(), tracker);
}
if(trackerAdded.getTrackerRole() == TrackerRole.HMD.id) {
if (trackerAdded.getTrackerRole() == TrackerRole.HMD.id) {
hmdTracker = tracker;
} else {
Main.vrServer.registerTracker(tracker);
@@ -176,48 +171,58 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
}
@VRServerThread
protected void userActionRecieved(UserAction userAction) {
switch(userAction.getName()) {
case "calibrate":
// TODO : Check pose field
Main.vrServer.resetTrackers();
break;
protected void userActionReceived(UserAction userAction) {
switch (userAction.getName()) {
case "calibrate":
// TODO : Check pose field
Main.vrServer.resetTrackers();
break;
}
}
@VRServerThread
protected void trackerStatusRecieved(TrackerStatus trackerStatus) {
protected void trackerStatusReceived(TrackerStatus trackerStatus) {
T tracker = getInternalRemoteTrackerById(trackerStatus.getTrackerId());
if(tracker != null) {
tracker.setStatus(dev.slimevr.vr.trackers.TrackerStatus.getById(trackerStatus.getStatusValue()));
if (tracker != null) {
tracker
.setStatus(
dev.slimevr.vr.trackers.TrackerStatus.getById(trackerStatus.getStatusValue())
);
}
}
@ThreadSafe
protected T getInternalRemoteTrackerById(int trackerId) {
synchronized(remoteTrackersByTrackerId) {
synchronized (remoteTrackersByTrackerId) {
return remoteTrackersByTrackerId.get(trackerId);
}
}
@VRServerThread
protected void reconnected() {
for(int i = 0; i < sharedTrackers.size(); ++i) {
ShareableTracker tracker = sharedTrackers.get(i);
TrackerAdded.Builder builder = TrackerAdded.newBuilder().setTrackerId(tracker.getTrackerId()).setTrackerName(tracker.getDescriptiveName()).setTrackerSerial(tracker.getName()).setTrackerRole(tracker.getTrackerRole().id);
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) {
synchronized (remoteTrackersByTrackerId) {
Iterator<Entry<Integer, T>> iterator = remoteTrackersByTrackerId.entrySet().iterator();
while(iterator.hasNext()) {
iterator.next().getValue().setStatus(dev.slimevr.vr.trackers.TrackerStatus.DISCONNECTED);
while (iterator.hasNext()) {
iterator
.next()
.getValue()
.setStatus(dev.slimevr.vr.trackers.TrackerStatus.DISCONNECTED);
}
}
if(hmdTracker != null) {
if (hmdTracker != null) {
hmd.setStatus(dev.slimevr.vr.trackers.TrackerStatus.DISCONNECTED);
}
}
@@ -225,10 +230,15 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
@VRServerThread
@Override
public void addSharedTracker(ShareableTracker tracker) {
if(sharedTrackers.contains(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);
TrackerAdded.Builder builder = TrackerAdded
.newBuilder()
.setTrackerId(tracker.getTrackerId())
.setTrackerName(tracker.getDescriptiveName())
.setTrackerSerial(tracker.getName())
.setTrackerRole(tracker.getTrackerRole().id);
sendMessage(ProtobufMessage.newBuilder().setTrackerAdded(builder).build());
}
@@ -236,6 +246,8 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
@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)
// No message can be sent to the remote side, protocol doesn't support
// tracker
// removal (yet)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,293 +0,0 @@
package dev.slimevr.bridge;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.lang3.StringUtils;
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.WinError;
import com.sun.jna.ptr.IntByReference;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.Pipe.PipeState;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.TrackerPosition;
import dev.slimevr.vr.trackers.TrackerStatus;
import dev.slimevr.vr.trackers.VRTracker;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
public class SteamVRPipeInputBridge extends Thread implements Bridge {
private static final int MAX_COMMAND_LENGTH = 2048;
public static final String PipeName = "\\\\.\\pipe\\SlimeVRInput";
private final byte[] buffArray = new byte[1024];
private final VRServer server;
private final StringBuilder commandBuilder = new StringBuilder(1024);
private final List<VRTracker> trackers = new FastList<>();
private final Map<Integer, VRTracker> trackersInternal = new HashMap<>();
private AtomicBoolean newData = new AtomicBoolean(false);
private final Vector3f vBuffer = new Vector3f();
private final Quaternion qBuffer = new Quaternion();
private Pipe pipe;
public SteamVRPipeInputBridge(VRServer server) {
this.server = server;
}
@Override
public void run() {
try {
createPipes();
while(true) {
boolean pipesUpdated = false;
if(pipe.state == PipeState.CREATED) {
tryOpeningPipe(pipe);
}
if(pipe.state == PipeState.OPEN) {
pipesUpdated = updatePipes();
}
if(pipe.state == PipeState.ERROR) {
resetPipe();
}
if(!pipesUpdated) {
try {
Thread.sleep(5); // Up to 200Hz
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
public boolean updatePipes() throws IOException {
if(pipe.state == PipeState.OPEN) {
IntByReference bytesAvailable = new IntByReference(0);
if(Kernel32.INSTANCE.PeekNamedPipe(pipe.pipeHandle, null, 0, null, bytesAvailable, null)) {
if(bytesAvailable.getValue() > 0) {
while(Kernel32.INSTANCE.ReadFile(pipe.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') {
executeInputCommand();
commandBuilder.setLength(0);
} else {
commandBuilder.append(c);
if(commandBuilder.length() >= MAX_COMMAND_LENGTH) {
LogManager.log.severe("[SteamVRPipeInputBridge] Command from the pipe is too long, flushing buffer");
commandBuilder.setLength(0);
}
}
}
if(bytesRead < buffArray.length)
return true; // All pipe data read
}
} else {
return false; // Pipe was empty, it's okay
}
}
// PeekNamedPipe or ReadFile returned an error
pipe.state = PipeState.ERROR;
LogManager.log.severe("[SteamVRPipeInputBridge] Pipe error: " + Kernel32.INSTANCE.GetLastError());
}
return false;
}
private void executeInputCommand() throws IOException {
String[] command = commandBuilder.toString().split(" ");
switch(command[0]) {
case "ADD": // Add new tracker
if(command.length < 4) {
LogManager.log.severe("[SteamVRPipeInputBridge] Error in ADD command. Command requires at least 4 arguments. Supplied: " + commandBuilder.toString());
return;
}
VRTracker internalTracker = new VRTracker(Integer.parseInt(command[1]), StringUtils.join(command, " ", 3, command.length), true, true);
int roleId = Integer.parseInt(command[2]);
if(roleId >= 0 && roleId < SteamVRInputRoles.values.length) {
SteamVRInputRoles svrRole = SteamVRInputRoles.values[roleId];
internalTracker.bodyPosition = svrRole.bodyPosition;
}
VRTracker oldTracker;
synchronized(trackersInternal) {
oldTracker = trackersInternal.put(internalTracker.getTrackerId(), internalTracker);
}
if(oldTracker != null) {
LogManager.log.severe("[SteamVRPipeInputBridge] New tracker added with the same id. Supplied: " + commandBuilder.toString());
return;
}
newData.set(true);
break;
case "UPD": // Update tracker data
if(command.length < 9) {
LogManager.log.severe("[SteamVRPipeInputBridge] Error in UPD command. Command requires at least 9 arguments. Supplied: " + commandBuilder.toString());
return;
}
int id = Integer.parseInt(command[1]);
double x = Double.parseDouble(command[2]);
double y = Double.parseDouble(command[3]);
double z = Double.parseDouble(command[4]);
double qw = Double.parseDouble(command[5]);
double qx = Double.parseDouble(command[6]);
double qy = Double.parseDouble(command[7]);
double qz = Double.parseDouble(command[8]);
internalTracker = trackersInternal.get(id);
if(internalTracker != null) {
internalTracker.position.set((float) x, (float) y, (float) z);
internalTracker.rotation.set((float) qx, (float) qy, (float) qz, (float) qw);
internalTracker.dataTick();
newData.set(true);
}
break;
case "STA": // Update tracker status
if(command.length < 3) {
LogManager.log.severe("[SteamVRPipeInputBridge] Error in STA command. Command requires at least 3 arguments. Supplied: " + commandBuilder.toString());
return;
}
id = Integer.parseInt(command[1]);
int status = Integer.parseInt(command[2]);
TrackerStatus st = TrackerStatus.getById(status);
if(st == null) {
LogManager.log.severe("[SteamVRPipeInputBridge] Unrecognized status id. Supplied: " + commandBuilder.toString());
return;
}
internalTracker = trackersInternal.get(id);
if(internalTracker != null) {
internalTracker.setStatus(st);
newData.set(true);
}
break;
}
}
@Override
public void dataRead() {
if(newData.getAndSet(false)) {
if(trackers.size() < trackersInternal.size()) {
// Add new trackers
synchronized(trackersInternal) {
Iterator<VRTracker> iterator = trackersInternal.values().iterator();
internal: while(iterator.hasNext()) {
VRTracker internalTracker = iterator.next();
for(int i = 0; i < trackers.size(); ++i) {
VRTracker t = trackers.get(i);
if(t.getTrackerId() == internalTracker.getTrackerId())
continue internal;
}
// Tracker is not found in current trackers
VRTracker tracker = new VRTracker(internalTracker.getTrackerId(), internalTracker.getName(), true, true);
tracker.bodyPosition = internalTracker.bodyPosition;
trackers.add(tracker);
server.registerTracker(tracker);
}
}
}
for(int i = 0; i < trackers.size(); ++i) {
VRTracker tracker = trackers.get(i);
VRTracker internal = trackersInternal.get(tracker.getTrackerId());
if(internal == null)
throw new NullPointerException("Lost internal tracker somehow: " + tracker.getTrackerId()); // Shouln't really happen even, but better to catch it like this
if(internal.getPosition(vBuffer))
tracker.position.set(vBuffer);
if(internal.getRotation(qBuffer))
tracker.rotation.set(qBuffer);
tracker.setStatus(internal.getStatus());
tracker.dataTick();
}
}
}
@Override
public void dataWrite() {
// Not used, only input
}
private void resetPipe() {
Pipe.safeDisconnect(pipe);
pipe.state = PipeState.CREATED;
//Main.vrServer.queueTask(this::disconnected);
}
private boolean tryOpeningPipe(Pipe pipe) {
if(Kernel32.INSTANCE.ConnectNamedPipe(pipe.pipeHandle, null) || Kernel32.INSTANCE.GetLastError() == WinError.ERROR_PIPE_CONNECTED) {
pipe.state = PipeState.OPEN;
LogManager.log.info("[SteamVRPipeInputBridge] Pipe " + pipe.name + " is open");
return true;
}
LogManager.log.info("[SteamVRPipeInputBridge] Error connecting to pipe " + pipe.name + ": " + Kernel32.INSTANCE.GetLastError());
return false;
}
private void createPipes() throws IOException {
try {
pipe = new Pipe(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.log.info("[SteamVRPipeInputBridge] Pipe " + pipe.name + " created");
if(WinBase.INVALID_HANDLE_VALUE.equals(pipe.pipeHandle))
throw new IOException("Can't open " + PipeName + " pipe: " + Kernel32.INSTANCE.GetLastError());
LogManager.log.info("[SteamVRPipeInputBridge] Pipes are open");
} catch(IOException e) {
Pipe.safeDisconnect(pipe);
throw e;
}
}
@Override
public void addSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
@Override
public void removeSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
public enum SteamVRInputRoles {
HEAD(TrackerPosition.HMD),
LEFT_HAND(TrackerPosition.LEFT_CONTROLLER),
RIGHT_HAND(TrackerPosition.RIGHT_CONTROLLER),
LEFT_FOOT(TrackerPosition.LEFT_FOOT),
RIGHT_FOOT(TrackerPosition.RIGHT_FOOT),
LEFT_SHOULDER(TrackerPosition.NONE),
RIGHT_SHOULDER(TrackerPosition.NONE),
LEFT_ELBOW(TrackerPosition.NONE),
RIGHT_ELBOW(TrackerPosition.NONE),
LEFT_KNEE(TrackerPosition.LEFT_LEG),
RIGHT_KNEE(TrackerPosition.RIGHT_LEG),
WAIST(TrackerPosition.WAIST),
CHEST(TrackerPosition.CHEST),
;
private static final SteamVRInputRoles[] values = values();
public final TrackerPosition bodyPosition;
private SteamVRInputRoles(TrackerPosition slimeVrPosition) {
this.bodyPosition = slimeVrPosition;
}
}
@Override
public void startBridge() {
start();
}
}

View File

@@ -1,52 +1,51 @@
package dev.slimevr.bridge;
import dev.slimevr.vr.trackers.ShareableTracker;
import java.net.InetAddress;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.Tracker;
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();
}
}

View File

@@ -1,194 +0,0 @@
package dev.slimevr.bridge;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.java_websocket.WebSocket;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import org.json.JSONException;
import org.json.JSONObject;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import dev.slimevr.Main;
import dev.slimevr.VRServer;
import dev.slimevr.vr.trackers.ComputedTracker;
import dev.slimevr.vr.trackers.HMDTracker;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.Tracker;
import dev.slimevr.vr.trackers.TrackerStatus;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
public class WebSocketVRBridge extends WebSocketServer implements Bridge {
private final Vector3f vBuffer = new Vector3f();
private final Quaternion qBuffer = new Quaternion();
private final HMDTracker hmd;
private final List<? extends ShareableTracker> shareTrackers;
private final List<ComputedTracker> internalTrackers;
private final HMDTracker internalHMDTracker = new HMDTracker("itnernal://HMD");
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
public WebSocketVRBridge(HMDTracker hmd, List<? extends ShareableTracker> shareTrackers, VRServer server) {
super(new InetSocketAddress(21110), Collections.<Draft>singletonList(new Draft_6455()));
this.hmd = hmd;
this.shareTrackers = new FastList<>(shareTrackers);
this.internalTrackers = new FastList<>(shareTrackers.size());
for(int i = 0; i < shareTrackers.size(); ++i) {
Tracker t = shareTrackers.get(i);
ComputedTracker ct = new ComputedTracker(t.getTrackerId(), "internal://" + t.getName(), true, true);
ct.setStatus(TrackerStatus.OK);
ct.bodyPosition = t.getBodyPosition();
this.internalTrackers.add(ct);
}
}
@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(vBuffer))
it.position.set(vBuffer);
if(t.getRotation(qBuffer))
it.rotation.set(qBuffer);
}
}
@Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
LogManager.log.info("[WebSocket] New connection from: " + conn.getRemoteSocketAddress().getAddress().getHostAddress());
// Register trackers
for(int i = 0; i < internalTrackers.size(); ++i) {
JSONObject message = new JSONObject();
message.put("type", "config");
message.put("tracker_id", "SlimeVR Tracker " + (i + 1));
message.put("location", shareTrackers.get(i).getTrackerRole().name().toLowerCase());
message.put("tracker_type", message.optString("location"));
conn.send(message.toString());
}
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
LogManager.log.info("[WebSocket] Disconnected: " + conn.getRemoteSocketAddress().getAddress().getHostAddress() + ", (" + code + ") " + reason + ". Remote: " + remote);
}
@Override
public void onMessage(WebSocket conn, ByteBuffer message) {
StringBuilder sb = new StringBuilder(message.limit());
while(message.hasRemaining()) {
sb.append((char) message.get());
}
onMessage(conn, sb.toString());
}
@Override
public void onMessage(WebSocket conn, String message) {
//LogManager.log.info(message);
try {
JSONObject json = new JSONObject(message);
if(json.has("type")) {
switch(json.optString("type")) {
case "pos":
parsePosition(json, conn);
return;
case "action":
parseAction(json, conn);
return;
case "config": // TODO Ignore it for now, it should only register HMD in our test case with id 0
LogManager.log.info("[WebSocket] Config recieved: " + json.toString());
return;
}
}
LogManager.log.warning("[WebSocket] Unrecognized message from " + conn.getRemoteSocketAddress().getAddress().getHostAddress() + ": " + message);
} catch(Exception e) {
LogManager.log.severe("[WebSocket] Exception parsing message from " + conn.getRemoteSocketAddress().getAddress().getHostAddress() + ". Message: " + message, e);
}
}
private void parsePosition(JSONObject json, WebSocket conn) throws JSONException {
if(json.optInt("tracker_id") == 0) {
// Read HMD information
internalHMDTracker.position.set(json.optFloat("x"), json.optFloat("y") + 0.2f, json.optFloat("z")); // TODO Wtf is this hack? VRWorkout issue?
internalHMDTracker.rotation.set(json.optFloat("qx"), json.optFloat("qy"), json.optFloat("qz"), json.optFloat("qw"));
internalHMDTracker.dataTick();
newHMDData.set(true);
// Send tracker info in reply
for(int i = 0; i < internalTrackers.size(); ++i) {
JSONObject message = new JSONObject();
message.put("type", "pos");
message.put("src", "full");
message.put("tracker_id", "SlimeVR Tracker " + (i + 1));
ComputedTracker t = internalTrackers.get(i);
message.put("x", t.position.x);
message.put("y", t.position.y);
message.put("z", t.position.z);
message.put("qx", t.rotation.getX());
message.put("qy", t.rotation.getY());
message.put("qz", t.rotation.getZ());
message.put("qw", t.rotation.getW());
conn.send(message.toString());
}
}
}
private void parseAction(JSONObject json, WebSocket conn) throws JSONException {
switch(json.optString("name")) {
case "calibrate":
Main.vrServer.resetTrackersYaw();
break;
}
}
@Override
public void onError(WebSocket conn, Exception ex) {
LogManager.log.severe("[WebSocket] Exception on connection " + (conn != null ? conn.getRemoteSocketAddress().getAddress().getHostAddress() : null), ex);
}
@Override
public void onStart() {
LogManager.log.info("[WebSocket] Web Socket VR Bridge started on port " + getPort());
setConnectionLostTimeout(0);
setConnectionLostTimeout(1);
}
@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();
}
}

View File

@@ -3,20 +3,21 @@ 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) {
}

View File

@@ -3,32 +3,33 @@ 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) {
}

View File

@@ -8,144 +8,55 @@ import javax.swing.ScrollPaneConstants;
import javax.swing.JButton;
import javax.swing.border.EmptyBorder;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.List;
import java.util.concurrent.Future;
import java.util.EnumMap;
import io.eiren.util.StringUtils;
import io.eiren.util.ann.AWTThread;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
import javax.swing.event.MouseInputAdapter;
import org.apache.commons.lang3.tuple.Pair;
import dev.slimevr.VRServer;
import dev.slimevr.autobone.AutoBone;
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.poserecorder.PoseFrameIO;
import dev.slimevr.poserecorder.PoseRecorder;
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
public class AutoBoneWindow extends JFrame {
private static File saveDir = new File("Recordings");
private static File loadDir = new File("LoadRecordings");
public class AutoBoneWindow extends JFrame implements AutoBoneListener {
private EJBox pane;
private final transient VRServer server;
private final transient SkeletonConfigGUI skeletonConfig;
private final transient PoseRecorder poseRecorder;
private final transient AutoBone autoBone;
private transient Thread recordingThread = null;
private transient Thread saveRecordingThread = null;
private transient Thread autoBoneThread = null;
private JButton saveRecordingButton;
private JButton adjustButton;
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;
this.poseRecorder = new PoseRecorder(server);
this.autoBone = new AutoBone(server);
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));
add(
new JScrollPane(
pane = new EJBox(BoxLayout.PAGE_AXIS),
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED
)
);
server.getAutoBoneHandler().addListener(this);
build();
}
private String getLengthsString() {
final StringBuilder configInfo = new StringBuilder();
autoBone.configs.forEach((key, value) -> {
if(configInfo.length() > 0) {
configInfo.append(", ");
}
configInfo.append(key.stringVal + ": " + StringUtils.prettyNumber(value * 100f, 2));
});
return configInfo.toString();
}
private 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.log.info("[AutoBone] Exporting frames to \"" + saveRecording.getPath() + "\"...");
if(PoseFrameIO.writeToFile(saveRecording, frames)) {
LogManager.log.info("[AutoBone] Done exporting! Recording can be found at \"" + saveRecording.getPath() + "\".");
} else {
LogManager.log.severe("[AutoBone] Failed to export the recording to \"" + saveRecording.getPath() + "\".");
}
} else {
LogManager.log.severe("[AutoBone] Failed to create the recording directory \"" + saveDir.getPath() + "\".");
}
}
private 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.log.info("[AutoBone] Detected recording at \"" + file.getPath() + "\", loading frames...");
PoseFrames frames = PoseFrameIO.readFromFile(file);
if(frames == null) {
LogManager.log.severe("Reading frames from \"" + file.getPath() + "\" failed...");
} else {
recordings.add(Pair.of(file.getName(), frames));
}
}
}
}
}
return recordings;
}
private float processFrames(PoseFrames frames) {
autoBone.minDataDistance = server.config.getInt("autobone.minimumDataDistance", autoBone.minDataDistance);
autoBone.maxDataDistance = server.config.getInt("autobone.maximumDataDistance", autoBone.maxDataDistance);
autoBone.numEpochs = server.config.getInt("autobone.epochCount", autoBone.numEpochs);
autoBone.initialAdjustRate = server.config.getFloat("autobone.adjustRate", autoBone.initialAdjustRate);
autoBone.adjustRateDecay = server.config.getFloat("autobone.adjustRateDecay", autoBone.adjustRateDecay);
autoBone.slideErrorFactor = server.config.getFloat("autobone.slideErrorFactor", autoBone.slideErrorFactor);
autoBone.offsetSlideErrorFactor = server.config.getFloat("autobone.offsetSlideErrorFactor", autoBone.offsetSlideErrorFactor);
autoBone.offsetErrorFactor = server.config.getFloat("autobone.offsetErrorFactor", autoBone.offsetErrorFactor);
autoBone.proportionErrorFactor = server.config.getFloat("autobone.proportionErrorFactor", autoBone.proportionErrorFactor);
autoBone.heightErrorFactor = server.config.getFloat("autobone.heightErrorFactor", autoBone.heightErrorFactor);
autoBone.positionErrorFactor = server.config.getFloat("autobone.positionErrorFactor", autoBone.positionErrorFactor);
autoBone.positionOffsetErrorFactor = server.config.getFloat("autobone.positionOffsetErrorFactor", autoBone.positionOffsetErrorFactor);
boolean calcInitError = server.config.getBoolean("autobone.calculateInitialError", true);
float targetHeight = server.config.getFloat("autobone.manualTargetHeight", -1f);
return autoBone.processFrames(frames, calcInitError, targetHeight, (epoch) -> {
processLabel.setText(epoch.toString());
lengthsLabel.setText(getLengthsString());
});
}
@AWTThread
private void build() {
pane.add(new EJBox(BoxLayout.LINE_AXIS) {
@@ -156,258 +67,58 @@ public class AutoBoneWindow extends JFrame {
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// Prevent running multiple times
if(!isEnabled() || recordingThread != null) {
if (!isEnabled()) {
return;
}
Thread thread = new Thread() {
@Override
public void run() {
try {
if(poseRecorder.isReadyToRecord()) {
setText("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);
PoseFrames frames = framesFuture.get();
LogManager.log.info("[AutoBone] Done recording!");
saveRecordingButton.setEnabled(true);
adjustButton.setEnabled(true);
if(server.config.getBoolean("autobone.saveRecordings", false)) {
setText("Saving...");
saveRecording(frames);
}
} else {
setText("Not Ready...");
LogManager.log.severe("[AutoBone] Unable to record...");
Thread.sleep(3000); // Wait for 3 seconds
return;
}
} catch(Exception e) {
setText("Recording Failed...");
LogManager.log.severe("[AutoBone] Failed recording!", e);
try {
Thread.sleep(3000); // Wait for 3 seconds
} catch(Exception e1) {
// Ignore
}
} finally {
setText("Start Recording");
recordingThread = null;
}
}
};
recordingThread = thread;
thread.start();
server.getAutoBoneHandler().startRecording();
}
});
}
});
add(saveRecordingButton = new JButton("Save Recording") {
{
setEnabled(poseRecorder.hasRecording());
setEnabled(false);
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// Prevent running multiple times
if(!isEnabled() || saveRecordingThread != null) {
if (!isEnabled()) {
return;
}
Thread thread = new Thread() {
@Override
public void run() {
try {
Future<PoseFrames> framesFuture = poseRecorder.getFramesAsync();
if(framesFuture != null) {
setText("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");
}
setText("Saving...");
saveRecording(frames);
setText("Recording Saved!");
try {
Thread.sleep(3000); // Wait for 3 seconds
} catch(Exception e1) {
// Ignore
}
} else {
setText("No Recording...");
LogManager.log.severe("[AutoBone] Unable to save, no recording was done...");
try {
Thread.sleep(3000); // Wait for 3 seconds
} catch(Exception e1) {
// Ignore
}
return;
}
} catch(Exception e) {
setText("Saving Failed...");
LogManager.log.severe("[AutoBone] Failed to save recording!", e);
try {
Thread.sleep(3000); // Wait for 3 seconds
} catch(Exception e1) {
// Ignore
}
} finally {
setText("Save Recording");
saveRecordingThread = null;
}
}
};
saveRecordingThread = thread;
thread.start();
server.getAutoBoneHandler().saveRecording();
}
});
}
});
add(adjustButton = new JButton("Auto-Adjust") {
add(new JButton("Auto-Adjust") {
{
// If there are files to load, enable the button
setEnabled(poseRecorder.hasRecording() || (loadDir.isDirectory() && loadDir.list().length > 0));
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
// Prevent running multiple times
if(!isEnabled() || autoBoneThread != null) {
if (!isEnabled()) {
return;
}
Thread thread = new Thread() {
@Override
public void run() {
try {
setText("Load...");
List<Pair<String, PoseFrames>> frameRecordings = loadRecordings();
if(!frameRecordings.isEmpty()) {
LogManager.log.info("[AutoBone] Done loading frames!");
} else {
Future<PoseFrames> framesFuture = poseRecorder.getFramesAsync();
if(framesFuture != null) {
setText("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 {
setText("No Recordings...");
LogManager.log.severe("[AutoBone] No recordings found in \"" + loadDir.getPath() + "\" and no recording was done...");
try {
Thread.sleep(3000); // Wait for 3 seconds
} catch(Exception e1) {
// Ignore
}
return;
}
}
setText("Processing...");
LogManager.log.info("[AutoBone] Processing frames...");
FastList<Float> heightPercentError = new FastList<Float>(frameRecordings.size());
for(Pair<String, PoseFrames> recording : frameRecordings) {
LogManager.log.info("[AutoBone] Processing frames from \"" + recording.getKey() + "\"...");
heightPercentError.add(processFrames(recording.getValue()));
LogManager.log.info("[AutoBone] Done processing!");
applyButton.setEnabled(true);
//#region Stats/Values
Float neckLength = autoBone.getConfig(SkeletonConfigValue.NECK);
Float chestDistance = autoBone.getConfig(SkeletonConfigValue.CHEST);
Float torsoLength = autoBone.getConfig(SkeletonConfigValue.TORSO);
Float hipWidth = autoBone.getConfig(SkeletonConfigValue.HIPS_WIDTH);
Float legsLength = autoBone.getConfig(SkeletonConfigValue.LEGS_LENGTH);
Float kneeHeight = autoBone.getConfig(SkeletonConfigValue.KNEE_HEIGHT);
float neckTorso = neckLength != null && torsoLength != null ? neckLength / torsoLength : 0f;
float chestTorso = chestDistance != null && torsoLength != null ? chestDistance / torsoLength : 0f;
float torsoWaist = hipWidth != null && torsoLength != null ? hipWidth / torsoLength : 0f;
float legTorso = legsLength != null && torsoLength != null ? legsLength / torsoLength : 0f;
float legBody = legsLength != null && torsoLength != null && neckLength != null ? legsLength / (torsoLength + neckLength) : 0f;
float kneeLeg = kneeHeight != null && legsLength != null ? kneeHeight / legsLength : 0f;
LogManager.log.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) + "}]");
String lengthsString = getLengthsString();
LogManager.log.info("[AutoBone] Length values: " + lengthsString);
lengthsLabel.setText(lengthsString);
}
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.log.info("[AutoBone] Average height error: " + StringUtils.prettyNumber(mean, 6) + " (SD " + StringUtils.prettyNumber(std, 6) + ")");
}
//#endregion
} catch(Exception e) {
setText("Failed...");
LogManager.log.severe("[AutoBone] Failed adjustment!", e);
try {
Thread.sleep(3000); // Wait for 3 seconds
} catch(Exception e1) {
// Ignore
}
} finally {
setText("Auto-Adjust");
autoBoneThread = null;
}
}
};
autoBoneThread = thread;
thread.start();
server.getAutoBoneHandler().processRecording();
}
});
}
});
add(applyButton = new JButton("Apply Values") {
{
setEnabled(false);
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if(!isEnabled()) {
if (!isEnabled()) {
return;
}
autoBone.applyConfig();
server.getAutoBoneHandler().applyValues();
// Update GUI values after applying
skeletonConfig.refreshAll();
}
@@ -416,24 +127,93 @@ public class AutoBoneWindow extends JFrame {
});
}
});
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(getLengthsString()));
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);
}
}

View File

@@ -1,80 +1,82 @@
package dev.slimevr.gui;
import java.awt.Container;
import java.awt.event.MouseEvent;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MouseInputAdapter;
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 currentCalibrationRecieved(String str) {
public void currentCalibrationReceived(String str) {
java.awt.EventQueue.invokeLater(() -> {
currentCalibration.setText(str);
pack();
});
}
public void newCalibrationRecieved(String str) {
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::newCalibrationRecieved);
}
});
}});
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::currentCalibrationRecieved);
}});
pane.add(new EJBox(BoxLayout.PAGE_AXIS) {{
setBorder(new EmptyBorder(i(5)));
add(new JLabel("New calibration"));
add(newCalibration = new JTextArea(10, 25));
}});
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);

View File

@@ -1,62 +1,70 @@
package dev.slimevr.gui;
import com.melloware.jintellitype.JIntellitype;
import com.melloware.jintellitype.JIntellitypeException;
import dev.slimevr.VRServer;
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 {
public final VRServer server;
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) {
if (JIntellitype.getInstance() instanceof JIntellitype) {
JIntellitype.getInstance().addHotKeyListener(this);
String resetBinding = this.server.config.getString("keybindings.reset");
if(resetBinding == null) {
if (resetBinding == null) {
resetBinding = "CTRL+ALT+SHIFT+Y";
this.server.config.setProperty("keybindings.reset", resetBinding);
}
JIntellitype.getInstance().registerHotKey(RESET, resetBinding);
LogManager.log.info("[Keybinding] Bound reset to " + resetBinding);
LogManager.info("[Keybinding] Bound reset to " + resetBinding);
String quickResetBinding = this.server.config.getString("keybindings.quickReset");
if(quickResetBinding == null) {
if (quickResetBinding == null) {
quickResetBinding = "CTRL+ALT+SHIFT+U";
this.server.config.setProperty("keybindings.quickReset", quickResetBinding);
}
JIntellitype.getInstance().registerHotKey(QUICK_RESET, quickResetBinding);
LogManager.log.info("[Keybinding] Bound quick reset to " + quickResetBinding);
LogManager.info("[Keybinding] Bound quick reset to " + quickResetBinding);
}
} catch(JIntellitypeException je) {
LogManager.log.info("[Keybinding] JIntellitype initialization failed. Keybindings will be disabled. Try restarting your computer.");
} catch(ExceptionInInitializerError e) {
LogManager.log.info("[Keybinding] JIntellitype initialization failed. Keybindings will be disabled. Try restarting your computer.");
} 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.log.info("[Keybinding] Reset pressed");
server.resetTrackers();
break;
case QUICK_RESET:
LogManager.log.info("[Keybinding] Quick reset pressed");
server.resetTrackersYaw();
break;
switch (identifier) {
case RESET:
LogManager.info("[Keybinding] Reset pressed");
server.resetTrackers();
break;
case QUICK_RESET:
LogManager.info("[Keybinding] Quick reset pressed");
server.resetTrackersYaw();
break;
}
}
}

View File

@@ -1,32 +1,33 @@
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) {
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,56 +35,56 @@ public class ScalableFont extends Font {
this.initPointSize = this.pointSize;
}
}
public ScalableFont(Font font, float scale) {
super(font);
if(font instanceof ScalableFont) {
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.pointSize = newPointSize;
}

View File

@@ -1,27 +1,27 @@
package dev.slimevr.gui;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.event.MouseInputAdapter;
import dev.slimevr.VRServer;
import dev.slimevr.gui.swing.ButtonTimer;
import dev.slimevr.gui.swing.EJBagNoStretch;
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
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 Map<SkeletonConfigValue, SkeletonLabel> labels = new HashMap<>();
private final Map<SkeletonConfigValue, SkeletonLabel> labels = new HashMap<>();
private JCheckBox precisionCb;
public SkeletonConfigGUI(VRServer server, VRServerGUI gui) {
super(false, true);
@@ -35,90 +35,46 @@ public class SkeletonConfigGUI extends EJBagNoStretch {
}
@ThreadSafe
public void skeletonUpdated(HumanSkeleton newSkeleton) {
public void skeletonUpdated(Skeleton newSkeleton) {
java.awt.EventQueue.invokeLater(() -> {
removeAll();
int row = 0;
/**
add(new JCheckBox("Extended pelvis model") {{
addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if(e.getStateChange() == ItemEvent.SELECTED) {//checkbox has been selected
if(newSkeleton != null && newSkeleton instanceof HumanSkeletonWithLegs) {
HumanSkeletonWithLegs hswl = (HumanSkeletonWithLegs) newSkeleton;
hswl.setSkeletonConfigBoolean("Extended pelvis model", true);
}
} else {
if(newSkeleton != null && newSkeleton instanceof HumanSkeletonWithLegs) {
HumanSkeletonWithLegs hswl = (HumanSkeletonWithLegs) newSkeleton;
hswl.setSkeletonConfigBoolean("Extended pelvis model", false);
}
}
}
});
if(newSkeleton != null && newSkeleton instanceof HumanSkeletonWithLegs) {
HumanSkeletonWithLegs hswl = (HumanSkeletonWithLegs) newSkeleton;
setSelected(hswl.getSkeletonConfigBoolean("Extended pelvis model"));
}
}}, s(c(0, row, 2), 3, 1));
row++;
//*/
/*
add(new JCheckBox("Extended knee model") {{
addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if(e.getStateChange() == ItemEvent.SELECTED) {//checkbox has been selected
if(newSkeleton != null && newSkeleton instanceof HumanSkeletonWithLegs) {
HumanSkeletonWithLegs hswl = (HumanSkeletonWithLegs) newSkeleton;
hswl.setSkeletonConfigBoolean("Extended knee model", true);
}
} else {
if(newSkeleton != null && newSkeleton instanceof HumanSkeletonWithLegs) {
HumanSkeletonWithLegs hswl = (HumanSkeletonWithLegs) newSkeleton;
hswl.setSkeletonConfigBoolean("Extended knee model", false);
}
}
}
});
if(newSkeleton != null && newSkeleton instanceof HumanSkeletonWithLegs) {
HumanSkeletonWithLegs hswl = (HumanSkeletonWithLegs) newSkeleton;
setSelected(hswl.getSkeletonConfigBoolean("Extended knee model"));
}
}}, s(c(0, row, 2), 3, 1));
row++;
//*/
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(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, 0.01f), c(1, row, 2));
add(new AdjButton("+", config, false), c(1, row, 2));
add(new SkeletonLabel(config), c(2, row, 2));
add(new AdjButton("-", config, -0.01f), c(3, 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
// 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;
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++;
@@ -128,11 +84,29 @@ public class SkeletonConfigGUI extends EJBagNoStretch {
});
}
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(StringUtils.prettyNumber(server.humanPoseProcessor.getSkeletonConfig(joint) * 100, 0));
label.setText(getBoneLengthString(joint));
});
});
}
@@ -145,7 +119,7 @@ public class SkeletonConfigGUI extends EJBagNoStretch {
server.saveConfig();
// Update GUI
labels.get(joint).setText(StringUtils.prettyNumber((current + diff) * 100, 0));
labels.get(joint).setText(getBoneLengthString(joint));
}
private void reset(SkeletonConfigValue joint) {
@@ -155,8 +129,7 @@ public class SkeletonConfigGUI extends EJBagNoStretch {
server.saveConfig();
// Update GUI
float current = server.humanPoseProcessor.getSkeletonConfig(joint);
labels.get(joint).setText(StringUtils.prettyNumber((current) * 100, 0));
labels.get(joint).setText(getBoneLengthString(joint));
}
private void resetAll() {
@@ -172,19 +145,19 @@ public class SkeletonConfigGUI extends EJBagNoStretch {
private class SkeletonLabel extends JLabel {
public SkeletonLabel(SkeletonConfigValue joint) {
super(StringUtils.prettyNumber(server.humanPoseProcessor.getSkeletonConfig(joint) * 100, 0));
super(getBoneLengthString(joint));
labels.put(joint, this);
}
}
private class AdjButton extends JButton {
public AdjButton(String text, SkeletonConfigValue joint, float diff) {
public AdjButton(String text, SkeletonConfigValue joint, boolean negative) {
super(text);
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
change(joint, diff);
change(joint, proportionsIncrement(negative));
}
});
}

View File

@@ -1,35 +1,32 @@
package dev.slimevr.gui;
import java.awt.GridBagConstraints;
import java.util.List;
import javax.swing.JLabel;
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.HumanSkeleton;
import dev.slimevr.vr.processor.skeleton.Skeleton;
import io.eiren.util.StringUtils;
import io.eiren.util.ann.ThreadSafe;
import io.eiren.util.collections.FastList;
public class SkeletonList extends EJBagNoStretch {
private static final long UPDATE_DELAY = 50;
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 VRServerGUI gui;
private final List<NodeStatus> nodes = new FastList<>();
private long lastUpdate = 0;
public SkeletonList(VRServer server, VRServerGUI gui) {
super(false, true);
this.gui = gui;
@@ -37,13 +34,13 @@ public class SkeletonList extends EJBagNoStretch {
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));
@@ -51,28 +48,29 @@ public class SkeletonList extends EJBagNoStretch {
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())
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;
@@ -82,7 +80,7 @@ public class SkeletonList extends EJBagNoStretch {
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));
@@ -93,12 +91,12 @@ public class SkeletonList extends EJBagNoStretch {
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));

View 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);
}
});
}
}
}

View File

@@ -1,53 +1,36 @@
package dev.slimevr.gui;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JLabel;
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.ComputedTracker;
import dev.slimevr.vr.trackers.HMDTracker;
import dev.slimevr.vr.trackers.IMUTracker;
import dev.slimevr.vr.trackers.ReferenceAdjustedTracker;
import dev.slimevr.vr.trackers.Tracker;
import dev.slimevr.vr.trackers.TrackerConfig;
import dev.slimevr.vr.trackers.TrackerMountingRotation;
import dev.slimevr.vr.trackers.TrackerPosition;
import dev.slimevr.vr.trackers.TrackerWithBattery;
import dev.slimevr.vr.trackers.TrackerWithTPS;
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 List<TrackerPanel> trackers = new FastList<>();
private final VRServer server;
private final VRServerGUI gui;
private long lastUpdate = 0;
private boolean debug = false;
public TrackersList(VRServer server, VRServerGUI gui) {
super(BoxLayout.PAGE_AXIS, false, true);
@@ -55,29 +38,43 @@ public class TrackersList extends EJBoxNoStretch {
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(int i = 0; i < trackers.size(); ++i) {
TrackerPanel tr = trackers.get(i);
Tracker t = tr.t;
if(t instanceof ReferenceAdjustedTracker)
t = ((ReferenceAdjustedTracker<?>) t).getTracker();
if(currentClass != t.getClass()) {
for (TrackerPanel tr : trackers) {
Tracker t = tr.t.get();
if (currentClass != t.getClass()) {
currentClass = t.getClass();
if(line != null)
if (line != null)
line.add(Box.createHorizontalGlue());
line = null;
line = new EJBoxNoStretch(BoxLayout.LINE_AXIS, false, true);
@@ -89,8 +86,8 @@ public class TrackersList extends EJBoxNoStretch {
add(line);
line = null;
}
if(line == null) {
if (line == null) {
line = new EJBoxNoStretch(BoxLayout.LINE_AXIS, false, true);
add(Box.createVerticalStrut(3));
add(line);
@@ -101,24 +98,25 @@ public class TrackersList extends EJBoxNoStretch {
}
tr.build();
line.add(tr);
if(!first)
if (!first)
line = null;
}
validate();
gui.refresh();
}
@ThreadSafe
public void updateTrackers() {
if(lastUpdate + UPDATE_DELAY > System.currentTimeMillis())
if (lastUpdate + UPDATE_DELAY > System.currentTimeMillis())
return;
lastUpdate = System.currentTimeMillis();
java.awt.EventQueue.invokeLater(() -> {
for(int i = 0; i < trackers.size(); ++i)
trackers.get(i).update();
for (TrackerPanel tr : trackers) {
tr.update();
}
});
}
@ThreadSafe
public void newTrackerAdded(Tracker t) {
java.awt.EventQueue.invokeLater(() -> {
@@ -126,9 +124,9 @@ public class TrackersList extends EJBoxNoStretch {
build();
});
}
private class TrackerPanel extends EJBagNoStretch {
final Tracker t;
JLabel position;
JLabel rotation;
@@ -142,12 +140,17 @@ public class TrackersList extends EJBoxNoStretch {
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;
}
@@ -155,116 +158,188 @@ public class TrackersList extends EJBoxNoStretch {
@AWTThread
public TrackerPanel build() {
int row = 0;
Tracker realTracker = t;
if(t instanceof ReferenceAdjustedTracker)
realTracker = ((ReferenceAdjustedTracker<? extends Tracker>) t).getTracker();
Tracker tracker = t.get();
removeAll();
JLabel nameLabel;
add(nameLabel = new JLabel(t.getDescriptiveName()), s(c(0, row, 2, GridBagConstraints.FIRST_LINE_START), 4, 1));
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()) {
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));
for(TrackerPosition p : TrackerPosition.values) {
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 p = TrackerPosition.getByDesignation(cfg.designation);
if(p != null)
desSelect.setSelectedItem(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) {
TrackerPosition p = TrackerPosition.valueOf(String.valueOf(desSelect.getSelectedItem()));
t.setBodyPosition(p);
if (desSelect.getSelectedItem() == "NONE") {
t.setBodyPosition(null);
} else {
TrackerPosition p = TrackerPosition
.valueOf(String.valueOf(desSelect.getSelectedItem()));
t.setBodyPosition(p);
}
server.trackerUpdated(t);
}
});
if(realTracker instanceof IMUTracker) {
IMUTracker imu = (IMUTracker) realTracker;
TrackerMountingRotation tr = imu.getMountingRotation();
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) {
add(
mountSelect = new JComboBox<>(),
s(c(2, row, 2, GridBagConstraints.FIRST_LINE_START), 2, 1)
);
for (TrackerMountingRotation p : TrackerMountingRotation.values) {
mountSelect.addItem(p.name());
}
if(tr != null) {
mountSelect.setSelectedItem(tr.name());
} else {
mountSelect.setSelectedItem(TrackerMountingRotation.BACK.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);
TrackerMountingRotation tr = TrackerMountingRotation
.valueOf(String.valueOf(mountSelect.getSelectedItem()));
imu.setMountingRotation(tr.quaternion);
server.trackerUpdated(t);
}
});
}
row++;
}
if(t.hasRotation())
if (t.hasRotation())
add(new JLabel("Rotation"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
if(t.hasPosition())
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(realTracker instanceof IMUTracker) {
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(realTracker instanceof IMUTracker) {
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(realTracker instanceof TrackerWithTPS) {
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(realTracker instanceof TrackerWithBattery) {
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));
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(realTracker instanceof IMUTracker) {
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(
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(
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));
add(
magAccuracy = new JLabel(""),
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(
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(t instanceof ReferenceAdjustedTracker) {
add(new JLabel("Adj:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
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("AdjY:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
add(adjYaw = new JLabel("0 0 0 0"), c(3, 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);
@@ -274,80 +349,182 @@ public class TrackersList extends EJBoxNoStretch {
@SuppressWarnings("unchecked")
@AWTThread
public void update() {
if(position == null && rotation == null)
if (position == null && rotation == null)
return;
Tracker realTracker = t;
if(t instanceof ReferenceAdjustedTracker)
realTracker = ((ReferenceAdjustedTracker<? extends Tracker>) t).getTracker();
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));
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(realTracker instanceof TrackerWithTPS) {
tps.setText(StringUtils.prettyNumber(((TrackerWithTPS) realTracker).getTPS(), 1));
if (tracker instanceof TrackerWithTPS) {
tps.setText(StringUtils.prettyNumber(((TrackerWithTPS) tracker).getTPS(), 1));
}
if(realTracker instanceof TrackerWithBattery)
bat.setText(StringUtils.prettyNumber(((TrackerWithBattery) realTracker).getBatteryVoltage(), 1));
if(t instanceof ReferenceAdjustedTracker) {
((ReferenceAdjustedTracker<Tracker>) t).attachmentFix.toAngles(angles);
if(adj != null)
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));
((ReferenceAdjustedTracker<Tracker>) t).yawFix.toAngles(angles);
if(adjYaw != null)
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 (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(realTracker instanceof IMUTracker) {
if(ping != null)
ping.setText(String.valueOf(((IMUTracker) realTracker).ping));
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)
);
}
}
realTracker.getRotation(q);
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(realTracker instanceof IMUTracker) {
((IMUTracker) realTracker).rotMagQuaternion.toAngles(angles);
if(rawMag != null)
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(((IMUTracker) realTracker).calibrationStatus + " / " + ((IMUTracker) realTracker).magCalibrationStatus);
if(magAccuracy != null)
magAccuracy.setText(StringUtils.prettyNumber(((IMUTracker) realTracker).magnetometerAccuracy * FastMath.RAD_TO_DEG, 1) + "°");
((IMUTracker) realTracker).getCorrection(q);
q.toAngles(angles);
if(correction != null)
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));
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");
}
}
}
}
}
private static int getTrackerSort(Tracker t) {
if(t instanceof ReferenceAdjustedTracker)
t = ((ReferenceAdjustedTracker<?>) t).getTracker();
if(t instanceof IMUTracker)
return 0;
if(t instanceof HMDTracker)
return 100;
if(t instanceof ComputedTracker)
return 200;
return 1000;
}
}

View File

@@ -1,29 +1,23 @@
package dev.slimevr.gui;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MouseInputAdapter;
import dev.slimevr.Main;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.NamedPipeBridge;
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 java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsConfiguration;
import java.awt.Rectangle;
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;
@@ -33,31 +27,32 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static javax.swing.BoxLayout.PAGE_AXIS;
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 EJBox pane;
private float zoom = 1.5f;
private float initZoom = zoom;
@AWTThread
public VRServerGUI(VRServer server) {
super(TITLE);
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Exception e) {
} catch (Exception e) {
e.printStackTrace();
}
if(OperatingSystem.getCurrentPlatform() == OperatingSystem.OSX)
if (OperatingSystem.getCurrentPlatform() == OperatingSystem.OSX)
MacOSX.setTitle(TITLE);
try {
List<BufferedImage> images = new ArrayList<BufferedImage>(6);
@@ -68,49 +63,95 @@ public class VRServerGUI extends JFrame {
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) {
if (OperatingSystem.getCurrentPlatform() == OperatingSystem.OSX) {
MacOSX.setIcons(images);
}
} catch(IOException e1) {
} 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
// 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);
add(new JScrollPane(pane = new EJBox(PAGE_AXIS), ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED));
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
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);
@@ -119,14 +160,14 @@ public class VRServerGUI extends JFrame {
server.config.setProperty("window.posy", b.y);
server.saveConfig();
}
public float getZoom() {
return this.zoom;
}
public void refresh() {
// Pack and display
//pack();
// pack();
setVisible(true);
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
@@ -135,162 +176,299 @@ public class VRServerGUI extends JFrame {
}
});
}
@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("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);
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(NamedPipeBridge.class)) {
NamedPipeBridge br = server.getVRBridge(NamedPipeBridge.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() {
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) {
server.queueTask(() -> {
br.changeShareSettings(TrackerRole.WAIST, waistCb.isSelected());
});
trackersList.setDebug(debugCb.isSelected());
}
});
JCheckBox legsCb;
add(legsCb = new JCheckBox("Legs"), 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());
});
}
});
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);
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());
});
}
});
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 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());
});
}
});
}});
add(Box.createVerticalStrut(10));
}
add(new JLabel("Skeleton data"));
add(skeletonList);
add(Box.createVerticalGlue());
}});
}});
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
// For now only changes font size, but should change fixed components size
// in
// the future too
private void guiZoom() {
if(zoom <= 1.0f) {
if (zoom <= 1.0f) {
zoom = 1.5f;
} else if(zoom <= 1.5f) {
} else if (zoom <= 1.5f) {
zoom = 1.75f;
} else if(zoom <= 1.75f) {
} else if (zoom <= 1.75f) {
zoom = 2.0f;
} else if(zoom <= 2.0f) {
} else if (zoom <= 2.0f) {
zoom = 2.5f;
} else {
zoom = 1.0f;
@@ -300,37 +478,12 @@ public class VRServerGUI extends JFrame {
server.config.setProperty("zoom", zoom);
server.saveConfig();
}
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);
}
}
}
@AWTThread
private void resetFast() {
server.resetTrackersYaw();
}
@AWTThread
private void reset() {
ButtonTimer.runTimer(resetButton, 3, "RESET", server::resetTrackers);

View File

@@ -1,110 +1,119 @@
package dev.slimevr.gui;
import java.awt.Container;
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;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
import javax.swing.WindowConstants;
import javax.swing.event.MouseInputAdapter;
import com.fazecast.jSerialComm.SerialPort;
public class WiFiWindow extends JFrame implements SerialListener {
import dev.slimevr.gui.swing.EJBox;
import io.eiren.util.ann.AWTThread;
public class WiFiWindow extends JFrame {
private static final Timer timer = new Timer();
private static String savedSSID = "";
private static String savedPassword = "";
private final VRServerGUI gui;
JTextField ssidField;
JTextField passwdField;
SerialPort trackerPort = null;
JPasswordField passwdField;
JTextArea log;
TimerTask readTask;
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() {
Container pane = getContentPane();
SerialPort[] ports = SerialPort.getCommPorts();
for(SerialPort port : ports) {
if(port.getDescriptivePortName().toLowerCase().contains("ch340") || port.getDescriptivePortName().toLowerCase().contains("cp21") || port.getDescriptivePortName().toLowerCase().contains("ch910")) {
trackerPort = port;
break;
}
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
);
}
pane.add(new EJBox(BoxLayout.PAGE_AXIS) {{
if(trackerPort == null) {
add(new JLabel("No trackers connected, connect tracker to USB and reopen window"));
timer.schedule(new TimerTask() {
@Override
public void run() {
WiFiWindow.this.dispose();
}
}, 5000);
} else {
add(new JLabel("Tracker connected to " + trackerPort.getSystemPortName() + " (" + trackerPort.getDescriptivePortName() + ")"));
}
@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));
add(
scroll = new JScrollPane(
log = new JTextArea(10, 20),
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
)
);
log.setLineWrap(true);
scroll.setAutoscrolls(true);
if(trackerPort.openPort()) {
trackerPort.setBaudRate(115200);
log.append("[OK] Port opened\n");
readTask = new ReadTask();
timer.schedule(readTask, 500, 500);
} else {
log.append("ERROR: Can't open port");
}
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:"));
add(passwdField = new JTextField(savedPassword));
}});
add(new JButton("Send") {{
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
send(ssidField.getText(), passwdField.getText());
}
});
}});
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 and display
});
pack();
setLocationRelativeTo(null);
setVisible(true);
@@ -116,57 +125,26 @@ public class WiFiWindow extends JFrame {
}
});
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
final WiFiWindow window = this;
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent windowEvent) {
if(trackerPort != null)
trackerPort.closePort();
if(readTask != null)
readTask.cancel();
System.out.println("Port closed okay");
dispose();
}
});
}
protected void send(String ssid, String passwd) {
savedSSID = ssid;
savedPassword = passwd;
OutputStream os = trackerPort.getOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(os);
try {
writer.append("SET WIFI \"" + ssid + "\" \"" + passwd + "\"\n");
writer.flush();
} catch(IOException e) {
log.append(e.toString() + "\n");
e.printStackTrace();
}
}
private class ReadTask extends TimerTask {
final InputStream is;
final Reader reader;
StringBuffer sb = new StringBuffer();
public ReadTask() {
is = trackerPort.getInputStreamWithSuppressedTimeoutExceptions();
reader = new InputStreamReader(is);
}
@Override
public void run() {
try {
while(reader.ready())
sb.appendCodePoint(reader.read());
if(sb.length() > 0)
log.append(sb.toString());
sb.setLength(0);
} catch(Exception e) {
log.append(e.toString() + "\n");
e.printStackTrace();
@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);
}
}

View File

@@ -1,16 +1,21 @@
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);
}
}
}

View File

@@ -1,9 +1,10 @@
package dev.slimevr.gui.swing;
import java.awt.GridBagLayout;
import java.awt.*;
public class EJBag extends EJPanel {
public EJBag() {
super(new GridBagLayout());
}

View File

@@ -1,33 +1,32 @@
package dev.slimevr.gui.swing;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagLayout;
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)
if (stretchVertical)
pref.height = Integer.MAX_VALUE;
if(stretchHorizontal)
if (stretchHorizontal)
pref.width = Integer.MAX_VALUE;
return pref;
}
}
}
}

View File

@@ -1,9 +1,10 @@
package dev.slimevr.gui.swing;
import javax.swing.BoxLayout;
import javax.swing.*;
public class EJBox extends EJPanel {
public EJBox(int layout) {
super();
setLayout(new BoxLayout(this, layout));

View File

@@ -1,36 +1,40 @@
package dev.slimevr.gui.swing;
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.*;
import java.awt.*;
import javax.swing.BoxLayout;
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) {
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)
if (stretchVertical)
pref.height = Integer.MAX_VALUE;
if(stretchHorizontal)
if (stretchHorizontal)
pref.width = Integer.MAX_VALUE;
return pref;
}
}
}
}

View File

@@ -1,29 +1,24 @@
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;
}

View File

@@ -1,8 +1,8 @@
package dev.slimevr.gui.swing;
import javax.swing.JLabel;
import javax.swing.*;
public class EJlabel extends JLabel {
}

View File

@@ -3,11 +3,12 @@ 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);
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -1,30 +1,25 @@
package dev.slimevr.bridge;
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 class Pipe {
public final String name;
public final HANDLE pipeHandle;
public PipeState state = PipeState.CREATED;
public Pipe(HANDLE pipeHandle, String name) {
public WindowsPipe(HANDLE pipeHandle, String name) {
this.pipeHandle = pipeHandle;
this.name = name;
}
public static void safeDisconnect(Pipe pipe) {
public static void safeDisconnect(WindowsPipe pipe) {
try {
if(pipe != null && pipe.pipeHandle != null)
if (pipe != null && pipe.pipeHandle != null)
Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle);
} catch(Exception e) {
}
} catch (Exception e) {}
}
enum PipeState {
CREATED,
OPEN,
ERROR;
}
}
}

View 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;
}
}

View File

@@ -1,54 +1,48 @@
package dev.slimevr.poserecorder;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
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) {
if (frames != null) {
outputStream.writeInt(frames.getTrackerCount());
for(PoseFrameTracker tracker : frames.getTrackers()) {
for (PoseFrameTracker tracker : frames.getTrackers()) {
outputStream.writeUTF(tracker.name);
outputStream.writeInt(tracker.getFrameCount());
for(int i = 0; i < tracker.getFrameCount(); i++) {
for (int i = 0; i < tracker.getFrameCount(); i++) {
TrackerFrame trackerFrame = tracker.safeGetFrame(i);
if(trackerFrame == null) {
if (trackerFrame == null) {
outputStream.writeInt(0);
continue;
}
outputStream.writeInt(trackerFrame.getDataFlags());
if(trackerFrame.hasData(TrackerFrameData.DESIGNATION)) {
if (trackerFrame.hasData(TrackerFrameData.DESIGNATION)) {
outputStream.writeUTF(trackerFrame.designation.designation);
}
if(trackerFrame.hasData(TrackerFrameData.ROTATION)) {
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)) {
if (trackerFrame.hasData(TrackerFrameData.POSITION)) {
outputStream.writeFloat(trackerFrame.position.getX());
outputStream.writeFloat(trackerFrame.position.getY());
outputStream.writeFloat(trackerFrame.position.getZ());
@@ -58,81 +52,91 @@ public final class PoseFrameIO {
} else {
outputStream.writeInt(0);
}
} catch(Exception e) {
LogManager.log.severe("Error writing frame to stream", e);
} 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)))) {
try (
DataOutputStream outputStream = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(file))
)
) {
writeFrames(outputStream, frames);
} catch(Exception e) {
LogManager.log.severe("Error writing frames to file", e);
} 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++) {
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++) {
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());
if (TrackerFrameData.DESIGNATION.check(dataFlags)) {
designation = TrackerPosition
.getByDesignation(inputStream.readUTF())
.orElse(null);
}
Quaternion rotation = null;
if(TrackerFrameData.ROTATION.check(dataFlags)) {
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)) {
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.log.severe("Error reading frame from stream", e);
} 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.log.severe("Error reading frame from file", e);
return readFrames(
new DataInputStream(new BufferedInputStream(new FileInputStream(file)))
);
} catch (Exception e) {
LogManager.severe("Error reading frame from file", e);
}
return null;
}
}

View File

@@ -1,68 +1,84 @@
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;
import dev.slimevr.VRServer;
import dev.slimevr.vr.processor.ComputedHumanPoseTracker;
import dev.slimevr.vr.processor.skeleton.SimpleSkeleton;
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
import dev.slimevr.vr.trackers.Tracker;
public class PoseFrameSkeleton extends SimpleSkeleton {
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) {
public PoseFrameSkeleton(
VRServer server,
List<? extends ComputedHumanPoseTracker> computedTrackers
) {
super(server, computedTrackers);
}
public PoseFrameSkeleton(List<? extends Tracker> trackers, List<? extends ComputedHumanPoseTracker> 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) {
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) {
public PoseFrameSkeleton(
List<? extends Tracker> trackers,
List<? extends ComputedHumanPoseTracker> computedTrackers,
Map<SkeletonConfigValue, Float> configs
) {
super(trackers, computedTrackers, configs);
}
private int limitCursor() {
if(frameCursor < 0) {
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) {
if (tracker instanceof PoseFrameTracker) {
// Return frame if available, otherwise return the original tracker
TrackerFrame frame = ((PoseFrameTracker) tracker).safeGetFrame(frameCursor);
return frame == null ? tracker : frame;

View File

@@ -1,239 +1,269 @@
package dev.slimevr.poserecorder;
import java.util.Iterator;
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 int frameCursor = 0;
private final int trackerId = Tracker.getNextLocalTrackerId();
private int frameCursor = 0;
public PoseFrameTracker(String name, FastList<TrackerFrame> frames) {
if(frames == null) {
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()) {
if (frameCursor < 0 || frames.isEmpty()) {
frameCursor = 0;
} else if(frameCursor >= frames.size()) {
} 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) {
} catch (Exception e) {
return null;
}
}
public TrackerFrame safeGetFrame() {
return safeGetFrame(frameCursor);
}
//#region Tracker Interface Implementation
// #region Tracker Interface Implementation
@Override
public boolean getRotation(Quaternion store) {
TrackerFrame frame = safeGetFrame();
if(frame != null && frame.hasData(TrackerFrameData.ROTATION)) {
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)) {
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");
throw new UnsupportedOperationException(
"PoseFrameTracker does not implement configuration"
);
}
@Override
public void saveConfig(TrackerConfig config) {
throw new UnsupportedOperationException("PoseFrameTracker does not implement configuration");
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");
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
// #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;
}
}

View File

@@ -1,142 +1,289 @@
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;
import dev.slimevr.vr.trackers.Tracker;
import io.eiren.util.collections.FastList;
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;
}
public PoseFrameTracker addTracker(Tracker tracker, int initialCapacity) {
return addTracker(new PoseFrameTracker(tracker.getName(), initialCapacity));
}
public PoseFrameTracker addTracker(Tracker tracker) {
return addTracker(tracker, 5);
}
/**
* 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);
}
public PoseFrameTracker removeTracker(PoseFrameTracker tracker) {
trackers.remove(tracker);
return tracker;
/**
* 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(int i = 0; i < trackers.size(); i++) {
PoseFrameTracker tracker = trackers.get(i);
if(tracker != null && tracker.getFrameCount() > maxFrames) {
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) {
for(int i = 0; i < trackers.size(); i++) {
PoseFrameTracker tracker = trackers.get(i);
buffer[i] = tracker != null ? tracker.safeGetFrame(frameIndex) : null;
int frameCount = 0;
for (PoseFrameTracker tracker : trackers) {
if (tracker == null) {
continue;
}
TrackerFrame frame = tracker.safeGetFrame(frameIndex);
if (frame == null) {
continue;
}
buffer[frameCount++] = frame;
}
return trackers.size();
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) {
for(int i = 0; i < trackers.size(); i++) {
PoseFrameTracker tracker = trackers.get(i);
buffer.add(i, tracker != null ? tracker.safeGetFrame(frameIndex) : null);
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 trackers.size();
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()) {
if (trackers.isEmpty()) {
return false;
}
for(int i = 0; i < trackers.size(); i++) {
PoseFrameTracker tracker = trackers.get(i);
if(tracker != null && cursor < tracker.getFrameCount()) {
for (PoseFrameTracker tracker : trackers) {
if (tracker != null && cursor < tracker.getFrameCount()) {
return true;
}
}
return false;
}
@Override
public TrackerFrame[] next() {
if(!hasNext()) {
if (!hasNext()) {
throw new NoSuchElementException();
}
poseFrame.getFrames(cursor++, trackerFrameBuffer);
return trackerFrameBuffer;
}
}

View File

@@ -4,6 +4,7 @@ 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;
@@ -13,163 +14,216 @@ 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) {
if (numFrames <= 0) {
return;
}
PoseFrames poseFrame = this.poseFrame;
List<Pair<Tracker, PoseFrameTracker>> trackers = this.trackers;
if(poseFrame == null || trackers == null) {
if (poseFrame == null || trackers == null) {
return;
}
if(frameCursor >= numFrames) {
if (frameCursor >= numFrames) {
// If done and hasn't yet, send finished recording
stopFrameRecording();
return;
}
long curTime = System.currentTimeMillis();
if(curTime < nextFrameTimeMs) {
if (curTime < nextFrameTimeMs) {
return;
}
nextFrameTimeMs += frameRecordingInterval;
// To prevent duplicate frames, make sure the frame time is always in the future
if(nextFrameTimeMs <= curTime) {
// 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
// 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
synchronized (this) {
// A stopped recording will be accounted for by an empty "trackers"
// list
int cursor = frameCursor++;
for(Pair<Tracker, PoseFrameTracker> tracker : trackers) {
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) {
if (frameCursor >= numFrames) {
stopFrameRecording();
}
}
}
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long intervalMs) {
return startFrameRecording(numFrames, intervalMs, server.getAllTrackers());
return startFrameRecording(numFrames, intervalMs, server.getAllTrackers(), null);
}
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long intervalMs, List<Tracker> trackers) {
if(numFrames < 1) {
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) {
if (intervalMs < 1) {
throw new IllegalArgumentException("intervalMs must at least have a value of 1");
}
if(trackers == null) {
if (trackers == null) {
throw new IllegalArgumentException("trackers must not be null");
}
if(trackers.isEmpty()) {
if (trackers.isEmpty()) {
throw new IllegalArgumentException("trackers must have at least one entry");
}
if(!isReadyToRecord()) {
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) {
for (Tracker tracker : trackers) {
// Ignore null and computed trackers
if(tracker == null || tracker.isComputed()) {
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, poseFrame.addTracker(tracker, numFrames)));
this.trackers.add(Pair.of(tracker, poseFrameTracker));
}
this.frameCursor = 0;
this.numFrames = numFrames;
frameRecordingInterval = intervalMs;
nextFrameTimeMs = -1L;
LogManager.log.info("[PoseRecorder] Recording " + numFrames + " samples at a " + intervalMs + " ms frame interval");
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()) {
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()) {
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;

View File

@@ -2,178 +2,199 @@ 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 {
private int dataFlags = 0;
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) {
if (designation != null) {
dataFlags |= TrackerFrameData.DESIGNATION.flag;
}
this.rotation = rotation;
if(rotation != null) {
if (rotation != null) {
dataFlags |= TrackerFrameData.ROTATION.flag;
}
this.position = position;
if(position != null) {
if (position != null) {
dataFlags |= TrackerFrameData.POSITION.flag;
}
}
public static TrackerFrame fromTracker(Tracker tracker) {
if(tracker == null) {
if (tracker == null) {
return null;
}
// If the tracker is not ready
if(tracker.getStatus() != TrackerStatus.OK && tracker.getStatus() != TrackerStatus.BUSY && tracker.getStatus() != TrackerStatus.OCCLUDED) {
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()) {
if (tracker.getBodyPosition() == null && !tracker.hasRotation() && !tracker.hasPosition()) {
return null;
}
Quaternion rotation = null;
if(tracker.hasRotation()) {
if (tracker.hasRotation()) {
rotation = new Quaternion();
if(!tracker.getRotation(rotation)) {
if (!tracker.getRotation(rotation)) {
// If getting the rotation failed, set it back to null
rotation = null;
}
}
Vector3f position = null;
if(tracker.hasPosition()) {
if (tracker.hasPosition()) {
position = new Vector3f();
if(!tracker.getPosition(position)) {
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
// #region Tracker Interface Implementation
@Override
public boolean getRotation(Quaternion store) {
if(hasData(TrackerFrameData.ROTATION)) {
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)) {
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");
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
// #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;
}
}

View File

@@ -1,18 +1,15 @@
package dev.slimevr.poserecorder;
public enum TrackerFrameData {
DESIGNATION(0),
ROTATION(1),
POSITION(2),
;
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;
}

View File

@@ -1,263 +1,284 @@
package dev.slimevr.posestreamer;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
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 dev.slimevr.vr.processor.TransformNode;
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
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 long frameCount = 0;
private final BufferedWriter writer;
private long frameCount = 0;
private long frameCountOffset;
private float[] angleBuf = new float[3];
private Quaternion rotBuf = new Quaternion();
private HumanSkeleton wrappedSkeleton;
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(HumanSkeleton skeleton) {
private TransformNodeWrapper wrapSkeletonIfNew(Skeleton skeleton) {
TransformNodeWrapper wrapper = rootNode;
// If the wrapped skeleton is missing or the skeleton is updated
if(wrapper == null || skeleton != wrappedSkeleton) {
if (wrapper == null || skeleton != wrappedSkeleton) {
wrapper = wrapSkeleton(skeleton);
}
return wrapper;
}
private TransformNodeWrapper wrapSkeleton(HumanSkeleton skeleton) {
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) {
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()) {
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) {
if (level > 0 && node.wrappedNode.getParent() != null) {
Vector3f offset = node.localTransform.getTranslation();
float reverseMultiplier = node.hasReversedHierarchy() ? -1 : 1;
writer.write(nextIndentLevel + "OFFSET " + Float.toString(offset.getX() * OFFSET_SCALE * reverseMultiplier) + " " + Float.toString(offset.getY() * OFFSET_SCALE * reverseMultiplier) + " " + Float.toString(offset.getZ() * OFFSET_SCALE * reverseMultiplier) + "\n");
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()) {
if (!node.children.isEmpty()) {
// Only give position for root
if(level > 0) {
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");
writer
.write(
nextIndentLevel
+ "CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation\n"
);
}
for(TransformNodeWrapper childNode : node.children) {
for (TransformNodeWrapper childNode : node.children) {
writeNodeHierarchy(childNode, level + 1);
}
}
writer.write(indentLevel + "}\n");
}
@Override
public void writeHeader(HumanSkeleton skeleton, PoseStreamer streamer) throws IOException {
if(skeleton == null) {
public void writeHeader(Skeleton skeleton, PoseStreamer streamer) throws IOException {
if (skeleton == null) {
throw new NullPointerException("skeleton must not be null");
}
if(streamer == 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) {
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.frameRecordingInterval / 1000d) + "\n");
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
// 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) {
if (angles == null) {
angles = new float[3];
} else if(angles.length != 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);
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 {
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;
}
}
}
*/
* 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 = rotBuf.multLocal(inverseRootRot);
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(Float.toString(angleBuf[0] * FastMath.RAD_TO_DEG) + " " + Float.toString(angleBuf[1] * FastMath.RAD_TO_DEG) + " " + Float.toString(angleBuf[2] * FastMath.RAD_TO_DEG));
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()) {
if (!node.children.isEmpty()) {
Quaternion inverseRot = transform.getRotation().inverse();
for(TransformNodeWrapper childNode : node.children) {
if(childNode.children.isEmpty()) {
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(HumanSkeleton skeleton) throws IOException {
if(skeleton == null) {
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(Float.toString(rootPos.getX() * POSITION_SCALE) + " " + Float.toString(rootPos.getY() * POSITION_SCALE) + " " + Float.toString(rootPos.getZ() * POSITION_SCALE) + " ");
writer
.write(
rootPos.getX() * POSITION_SCALE
+ " "
+ rootPos.getY() * POSITION_SCALE
+ " "
+ rootPos.getZ() * POSITION_SCALE
+ " "
);
writeNodeHierarchyRotation(rootNode, null);
writer.newLine();
frameCount++;
}
@Override
public void writeFooter(HumanSkeleton skeleton) throws IOException {
public void writeFooter(Skeleton skeleton) throws IOException {
// Write the final frame count for files
if(outputStream instanceof FileOutputStream) {
if (outputStream instanceof FileOutputStream) {
FileOutputStream fileOutputStream = (FileOutputStream) outputStream;
// Flush before anything else
writer.flush();
@@ -267,7 +288,7 @@ public class BVHFileStream extends PoseDataStream {
writer.write(Long.toString(frameCount));
}
}
@Override
public void close() throws IOException {
writer.close();

View File

@@ -1,42 +1,39 @@
package dev.slimevr.posestreamer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import dev.slimevr.vr.processor.skeleton.Skeleton;
import java.io.*;
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
public abstract class PoseDataStream implements AutoCloseable {
protected boolean closed = false;
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(HumanSkeleton skeleton, PoseStreamer streamer) throws IOException {
public void writeHeader(Skeleton skeleton, PoseStreamer streamer) throws IOException {
}
abstract void writeFrame(HumanSkeleton skeleton) throws IOException;
public void writeFooter(HumanSkeleton skeleton) 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();

View File

@@ -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();
}
}
}

View File

@@ -1,114 +1,78 @@
package dev.slimevr.posestreamer;
import dev.slimevr.vr.processor.skeleton.Skeleton;
import io.eiren.util.logging.LogManager;
import java.io.IOException;
import dev.slimevr.VRServer;
import dev.slimevr.util.ann.VRServerThread;
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
import io.eiren.util.logging.LogManager;
public class PoseStreamer {
protected long frameRecordingInterval = 60L;
protected long nextFrameTimeMs = -1L;
private HumanSkeleton skeleton;
private PoseDataStream poseFileStream;
protected final VRServer server;
public PoseStreamer(VRServer server) {
this.server = server;
// Register callbacks/events
server.addSkeletonUpdatedCallback(this::onSkeletonUpdated);
server.addOnTick(this::onTick);
}
@VRServerThread
public void onSkeletonUpdated(HumanSkeleton skeleton) {
protected Skeleton skeleton;
protected PoseDataStream poseFileStream;
public PoseStreamer(Skeleton skeleton) {
this.skeleton = skeleton;
}
@VRServerThread
public void onTick() {
PoseDataStream poseFileStream = this.poseFileStream;
if(poseFileStream == null) {
public synchronized void captureFrame() {
// Make sure the stream is open before trying to write
if (poseFileStream.isClosed()) {
return;
}
HumanSkeleton 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;
}
// Make sure it's synchronized since this is the server thread interacting with
// an unknown outside thread controlling this class
synchronized(this) {
// 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.log.severe("[PoseStreamer] Exception while saving frame", e);
}
try {
poseFileStream.writeFrame(skeleton);
} catch (Exception e) {
// Handle any exceptions without crashing the program
LogManager.severe("[PoseStreamer] Exception while saving frame", e);
}
}
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 long getFrameInterval() {
return frameRecordingInterval;
}
public synchronized void setOutput(PoseDataStream poseFileStream) throws IOException {
poseFileStream.writeHeader(skeleton, this);
this.poseFileStream = poseFileStream;
nextFrameTimeMs = -1L; // Reset the frame timing
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 void setOutput(PoseDataStream poseFileStream, long intervalMs) throws IOException {
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) {
if (poseFileStream != null) {
closeOutput(poseFileStream);
this.poseFileStream = null;
}
}
public synchronized void closeOutput(PoseDataStream poseFileStream) throws IOException {
if(poseFileStream != null) {
if (poseFileStream != null) {
poseFileStream.writeFooter(skeleton);
poseFileStream.close();
}

View File

@@ -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();
}
}

View File

@@ -1,63 +1,48 @@
package dev.slimevr.posestreamer;
import dev.slimevr.vr.processor.TransformNode;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import dev.slimevr.vr.processor.TransformNode;
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) {
if (newRoot == null) {
return null;
}
TransformNodeWrapper wrappedRoot = TransformNodeWrapper.wrapHierarchyDown(newRoot);
/*
// If should wrap up hierarchy
if (newRoot.getParent() != null) {
// Create an extra node for full proper rotation
TransformNodeWrapper spineWrapper = new TransformNodeWrapper(new TransformNode("Spine", false), true, 1);
wrappedRoot.attachChild(spineWrapper);
// Wrap up on top of the spine node
TransformNodeWrapper.wrapNodeHierarchyUp(newRoot, spineWrapper);
}
*/
TransformNodeWrapper.wrapNodeHierarchyUp(wrappedRoot);
return wrappedRoot;
return TransformNodeWrapper.wrapFullHierarchy(newRoot);
}
private TransformNode getNodeFromHierarchy(TransformNode node, String name) {
if(node.getName().equalsIgnoreCase(name)) {
if (node.getName().equalsIgnoreCase(name)) {
return node;
}
for(TransformNode child : node.children) {
for (TransformNode child : node.children) {
TransformNode result = getNodeFromHierarchy(child, name);
if(result != null) {
if (result != null) {
return result;
}
}
return null;
}
}

View 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
}
}

View File

@@ -1,157 +1,195 @@
package dev.slimevr.posestreamer;
import java.util.List;
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;
protected String name;
public final Transform localTransform;
public final Transform worldTransform;
private boolean reversedHierarchy = false;
protected TransformNodeWrapper parent;
public final List<TransformNodeWrapper> children;
public TransformNodeWrapper(TransformNode nodeToWrap, String name, boolean reversedHierarchy, int initialChildCapacity) {
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) {
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(new TransformNodeWrapper(root, root.children.size()));
return wrapNodeHierarchyDown(root, new TransformNodeWrapper(root, root.children.size()));
}
public static TransformNodeWrapper wrapNodeHierarchyDown(TransformNodeWrapper root) {
for(TransformNode child : root.wrappedNode.children) {
root.attachChild(wrapHierarchyDown(child));
public static TransformNodeWrapper wrapNodeHierarchyDown(
TransformNode root,
TransformNodeWrapper target
) {
for (TransformNode child : root.children) {
target.attachChild(wrapHierarchyDown(child));
}
return root;
return target;
}
public static TransformNodeWrapper wrapHierarchyUp(TransformNode root) {
return wrapNodeHierarchyUp(new TransformNodeWrapper(root, root.getParent() != null ? 1 : 0));
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) {
public static TransformNodeWrapper wrapNodeHierarchyUp(
TransformNode root,
TransformNodeWrapper target
) {
TransformNode parent = root.getParent();
if(parent == null) {
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));
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) {
if (parent.children.size() > 1) {
for (TransformNode child : parent.children) {
// Skip the original node
if(child == target.wrappedNode) {
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) {
public Quaternion calculateLocalRotationInverse(
Quaternion inverseRelativeTo,
Quaternion result
) {
if (result == null) {
result = new Quaternion();
}
return worldTransform.getRotation().mult(inverseRelativeTo, result);
return inverseRelativeTo.mult(worldTransform.getRotation(), result);
}
public void attachChild(TransformNodeWrapper node) {
if(node.parent != null) {
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;
}

View 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;
}
}

View 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);
}
}

View 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;
}
}

Some files were not shown because too many files have changed in this diff Show More