Compare commits

...

99 Commits

Author SHA1 Message Date
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
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
71 changed files with 2370 additions and 1108 deletions

7
.gitignore vendored
View File

@@ -6,7 +6,12 @@ build
/bin/
# Ignore .idea
.idea
# Syncthing ignore file
.stignore
MagnetoLib.dll
MagnetoLib.dll
vrconfig.yml
*.log

View File

@@ -10,9 +10,9 @@ 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.targetPlatform=1.11
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.compliance=1.11
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -108,7 +108,7 @@ 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.compiler.source=1.11
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

View File

@@ -15,7 +15,7 @@ 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
@@ -45,3 +45,7 @@ run gradle command `shadowJar` to build a runnable server JAR
* 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
## 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.

View File

@@ -8,11 +8,11 @@
plugins {
id 'application'
id "com.github.johnrengelman.shadow" version "6.1.0"
id "com.github.johnrengelman.shadow" version "7.1.2"
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
sourceCompatibility = 1.11
targetCompatibility = 1.11
// Set compiler to use UTF-8
compileJava.options.encoding = 'UTF-8'
@@ -22,9 +22,7 @@ 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'])
options.release = 8
}
}
tasks.withType(Test) {
@@ -38,30 +36,25 @@ allprojects {
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
}
}
dependencies {
compile project(':slime-java-commons')
// 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 project(':slime-java-commons')
// 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 '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 +68,5 @@ shadowJar {
archiveVersion.set('')
}
application {
mainClassName = 'io.eiren.vr.Main'
getMainClass().set('dev.slimevr.Main')
}

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

@@ -15,7 +15,7 @@ import io.eiren.util.logging.LogManager;
public class Main {
public static String VERSION = "0.1.3";
public static String VERSION = "0.1.6";
public static VRServer vrServer;
@@ -49,7 +49,7 @@ public class Main {
try {
vrServer = new VRServer();
vrServer.start();
vrServer.start();
new Keybinding(vrServer);
new VRServerGUI(vrServer);
} catch(Throwable e) {

View File

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

View File

@@ -16,8 +16,8 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Consumer;
import dev.slimevr.bridge.Bridge;
import dev.slimevr.bridge.NamedPipeBridge;
import dev.slimevr.bridge.SteamVRPipeInputBridge;
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
import dev.slimevr.platform.windows.WindowsSteamVRPipeInputBridge;
import dev.slimevr.bridge.VMCBridge;
import dev.slimevr.bridge.WebSocketVRBridge;
import dev.slimevr.util.ann.VRServerThread;
@@ -27,7 +27,7 @@ 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.trackers.udp.TrackersUDPServer;
import io.eiren.util.OperatingSystem;
import io.eiren.util.ann.ThreadSafe;
import io.eiren.util.ann.ThreadSecure;
@@ -64,20 +64,18 @@ public class VRServer extends Thread {
// OpenVR bridge currently only supports 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

View File

@@ -15,14 +15,13 @@ import dev.slimevr.poserecorder.PoseFrameTracker;
import dev.slimevr.poserecorder.PoseFrames;
import dev.slimevr.poserecorder.TrackerFrame;
import dev.slimevr.poserecorder.TrackerFrameData;
import dev.slimevr.vr.processor.HumanPoseProcessor;
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
import dev.slimevr.vr.processor.skeleton.SimpleSkeleton;
import dev.slimevr.vr.processor.skeleton.SkeletonConfig;
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
import dev.slimevr.vr.trackers.TrackerPosition;
import dev.slimevr.vr.trackers.TrackerRole;
import dev.slimevr.vr.trackers.TrackerUtils;
import io.eiren.util.ann.ThreadSafe;
import io.eiren.util.logging.LogManager;
import io.eiren.util.collections.FastList;
@@ -71,14 +70,15 @@ public class AutoBone {
// SD of 0.07, capture 68% within range
public float legBodyRatioRange = 0.07f;
// Assume these to be approximately half
public float kneeLegRatio = 0.5f;
public float chestTorsoRatio = 0.5f;
// 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;
// TODO hip tracker stuff... Hip tracker should be around 3 to 5 centimeters.
protected final VRServer server;
protected SimpleSkeleton skeleton = null;
// This is filled by reloadConfigValues()
public final EnumMap<SkeletonConfigValue, Float> configs = new EnumMap<SkeletonConfigValue, Float>(SkeletonConfigValue.class);
public final EnumMap<SkeletonConfigValue, Float> staticConfigs = new EnumMap<SkeletonConfigValue, Float>(SkeletonConfigValue.class);
@@ -90,10 +90,7 @@ public class AutoBone {
public AutoBone(VRServer server) {
this.server = server;
reloadConfigValues();
server.addSkeletonUpdatedCallback(this::skeletonUpdated);
}
public void reloadConfigValues() {
@@ -136,19 +133,21 @@ public class AutoBone {
// Keep "feet" at ankles
staticConfigs.put(SkeletonConfigValue.FOOT_LENGTH, 0f);
staticConfigs.put(SkeletonConfigValue.FOOT_OFFSET, 0f);
staticConfigs.put(SkeletonConfigValue.SKELETON_OFFSET, 0f);
}
@ThreadSafe
public void skeletonUpdated(HumanSkeleton newSkeleton) {
if(newSkeleton instanceof SimpleSkeleton) {
skeleton = (SimpleSkeleton) newSkeleton;
applyConfigToSkeleton(newSkeleton);
LogManager.log.info("[AutoBone] Received updated skeleton");
}
/**
* A simple utility method to get the {@link HumanSkeleton} from the {@link VRServer}
* @return The {@link HumanSkeleton} associated with the {@link VRServer}, or null if there is none available
* @see {@link VRServer}, {@link HumanSkeleton}
*/
private HumanSkeleton getSkeleton() {
HumanPoseProcessor humanPoseProcessor = server != null ? server.humanPoseProcessor : null;
return humanPoseProcessor != null ? humanPoseProcessor.getSkeleton() : null;
}
public void applyConfig() {
if(!applyConfigToSkeleton(skeleton)) {
if(!applyConfigToSkeleton(getSkeleton())) {
// Unable to apply to skeleton, save directly
saveConfigs();
}
@@ -286,10 +285,14 @@ public class AutoBone {
// If target height isn't specified, auto-detect
if(targetHeight < 0f) {
// Get the current skeleton from the server
HumanSkeleton skeleton = getSkeleton();
if(skeleton != null) {
// If there is a skeleton available, calculate the target height from its configs
targetHeight = sumSelectConfigs(heightConfigs, skeleton.getSkeletonConfig());
LogManager.log.warning("[AutoBone] Target height loaded from skeleton (Make sure you reset before running!): " + targetHeight);
} else {
// Otherwise if there is no skeleton available, attempt to get the max HMD height from the recording
float hmdHeight = getMaxHmdHeight(frames);
if(hmdHeight <= 0.50f) {
LogManager.log.warning("[AutoBone] Max headset height detected (Value seems too low, did you not stand up straight while measuring?): " + hmdHeight);
@@ -302,12 +305,14 @@ public class AutoBone {
}
}
// Epoch loop, each epoch is one full iteration over the full dataset
for(int epoch = calcInitError ? -1 : 0; epoch < numEpochs; epoch++) {
float sumError = 0f;
int errorCount = 0;
float adjustRate = epoch >= 0 ? (initialAdjustRate / FastMath.pow(adjustRateDecay, epoch)) : 0f;
// Iterate over the frames using a cursor and an offset for comparing frames a certain number of frames apart
for(int cursorOffset = minDataDistance; cursorOffset <= maxDataDistance && cursorOffset < frameCount; cursorOffset++) {
for(int frameCursor = 0; frameCursor < frameCount - cursorOffset; frameCursor += cursorIncrement) {
int frameCursor2 = frameCursor + cursorOffset;
@@ -558,42 +563,48 @@ public class AutoBone {
float sumWeight = 0f;
if(slideErrorFactor > 0f) {
// This is the main error function, this calculates the distance between the foot positions on both frames
totalError += getSlideErrorDeriv(skeleton1, skeleton2) * distScale * slideErrorFactor;
sumWeight += slideErrorFactor;
}
if(offsetSlideErrorFactor > 0f) {
// This error function compares the distance between the feet on each frame and returns the offset between them
totalError += getOffsetSlideErrorDeriv(skeleton1, skeleton2) * distScale * offsetSlideErrorFactor;
sumWeight += offsetSlideErrorFactor;
}
if(offsetErrorFactor > 0f) {
// This error function compares the height of each foot in each frame
totalError += getOffsetErrorDeriv(skeleton1, skeleton2) * distScale * offsetErrorFactor;
sumWeight += offsetErrorFactor;
}
if(proportionErrorFactor > 0f) {
// This error function compares the current values to general expected proportions to keep measurements in line
// Either skeleton will work fine, skeleton1 is used as a default
totalError += getProportionErrorDeriv(skeleton1.skeletonConfig) * proportionErrorFactor;
sumWeight += proportionErrorFactor;
}
if(heightErrorFactor > 0f) {
// This error function compares the height change to the actual measured height of the headset
totalError += FastMath.abs(heightChange) * heightErrorFactor;
sumWeight += heightErrorFactor;
}
if(positionErrorFactor > 0f) {
// This error function compares the position of an assigned tracker with the position on the skeleton
totalError += (getPositionErrorDeriv(frames, cursor1, skeleton1) + getPositionErrorDeriv(frames, cursor2, skeleton2) / 2f) * distScale * positionErrorFactor;
sumWeight += positionErrorFactor;
}
if(positionOffsetErrorFactor > 0f) {
// This error function compares the offset of the position of an assigned tracker with the position on the skeleton
totalError += getPositionOffsetErrorDeriv(frames, cursor1, cursor2, skeleton1, skeleton2) * distScale * positionOffsetErrorFactor;
sumWeight += positionOffsetErrorFactor;
}
// Minimize sliding, minimize foot height offset, minimize change in total height
return sumWeight > 0f ? totalError / sumWeight : 0f;
}

View File

@@ -4,10 +4,10 @@ import dev.slimevr.util.ann.VRServerThread;
import dev.slimevr.vr.trackers.ShareableTracker;
/**
* Bridge handles sending and recieving tracker data
* 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 recieved from the <b>remote side</b> or send shared <b>local
* tracker received from the <b>remote side</b> or send shared <b>local
* trackers</b> to the other side.
*/
public interface Bridge {

View File

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

View File

@@ -58,7 +58,7 @@ 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);
}
@@ -82,7 +82,7 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
hadNewData = false;
ProtobufMessage message = null;
while((message = inputQueue.poll()) != null) {
processMessageRecieved(message);
processMessageReceived(message);
hadNewData = true;
}
if(hadNewData && hmdTracker != null) {
@@ -101,7 +101,7 @@ 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));
@@ -126,22 +126,22 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
}
@VRServerThread
protected void processMessageRecieved(ProtobufMessage message) {
protected void processMessageReceived(ProtobufMessage message) {
//if(!message.hasPosition())
// LogManager.log.info("[" + bridgeName + "] MSG: " + message);
if(message.hasPosition()) {
positionRecieved(message.getPosition());
positionReceived(message.getPosition());
} else if(message.hasUserAction()) {
userActionRecieved(message.getUserAction());
userActionReceived(message.getUserAction());
} else if(message.hasTrackerStatus()) {
trackerStatusRecieved(message.getTrackerStatus());
trackerStatusReceived(message.getTrackerStatus());
} else if(message.hasTrackerAdded()) {
trackerAddedRecieved(message.getTrackerAdded());
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())
@@ -155,7 +155,7 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
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) {
// TODO reinit?
@@ -176,7 +176,7 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
}
@VRServerThread
protected void userActionRecieved(UserAction userAction) {
protected void userActionReceived(UserAction userAction) {
switch(userAction.getName()) {
case "calibrate":
// TODO : Check pose field
@@ -186,7 +186,7 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
}
@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()));

View File

@@ -36,7 +36,7 @@ public class WebSocketVRBridge extends WebSocketServer implements Bridge {
private final List<? extends ShareableTracker> shareTrackers;
private final List<ComputedTracker> internalTrackers;
private final HMDTracker internalHMDTracker = new HMDTracker("itnernal://HMD");
private final HMDTracker internalHMDTracker = new HMDTracker("internal://HMD");
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
public WebSocketVRBridge(HMDTracker hmd, List<? extends ShareableTracker> shareTrackers, VRServer server) {
@@ -116,7 +116,7 @@ public class WebSocketVRBridge extends WebSocketServer implements Bridge {
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());
LogManager.log.info("[WebSocket] Config received: " + json.toString());
return;
}
}

View File

@@ -32,14 +32,14 @@ public class CalibrationWindow extends JFrame {
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);
@@ -56,7 +56,7 @@ public class CalibrationWindow extends JFrame {
@Override
public void mouseClicked(MouseEvent e) {
calibrateButton.setText("Calibrating...");
((CalibratingTracker) tracker).startCalibration(CalibrationWindow.this::newCalibrationRecieved);
((CalibratingTracker) tracker).startCalibration(CalibrationWindow.this::newCalibrationReceived);
}
});
}});
@@ -66,7 +66,7 @@ public class CalibrationWindow extends JFrame {
add(new JLabel("Current calibration"));
add(currentCalibration = new JTextArea(10, 25));
((CalibratingTracker) tracker).requestCalibrationData(CalibrationWindow.this::currentCalibrationRecieved);
((CalibratingTracker) tracker).requestCalibrationData(CalibrationWindow.this::currentCalibrationReceived);
}});
pane.add(new EJBox(BoxLayout.PAGE_AXIS) {{
setBorder(new EmptyBorder(i(5)));

View File

@@ -1,11 +1,10 @@
package dev.slimevr.gui;
import com.melloware.jintellitype.HotkeyListener;
import com.melloware.jintellitype.JIntellitype;
import com.melloware.jintellitype.JIntellitypeException;
import dev.slimevr.VRServer;
import com.melloware.jintellitype.HotkeyListener;
import io.eiren.util.OperatingSystem;
import io.eiren.util.ann.AWTThread;
import io.eiren.util.logging.LogManager;
@@ -18,6 +17,11 @@ public class Keybinding implements HotkeyListener {
public Keybinding(VRServer server) {
this.server = server;
if (OperatingSystem.getCurrentPlatform() != OperatingSystem.WINDOWS) {
LogManager.log.info("[Keybinding] Currently only supported on Windows. Keybindings will be disabled.");
return;
}
try {
if(JIntellitype.getInstance() instanceof JIntellitype) {
JIntellitype.getInstance().addHotKeyListener(this);
@@ -38,9 +42,7 @@ public class Keybinding implements HotkeyListener {
JIntellitype.getInstance().registerHotKey(QUICK_RESET, quickResetBinding);
LogManager.log.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) {
} catch(Throwable e) {
LogManager.log.info("[Keybinding] JIntellitype initialization failed. Keybindings will be disabled. Try restarting your computer.");
}
}

View File

@@ -13,7 +13,6 @@ 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.SkeletonConfigValue;
import io.eiren.util.StringUtils;
import io.eiren.util.ann.ThreadSafe;
public class SkeletonConfigGUI extends EJBagNoStretch {
@@ -106,9 +105,9 @@ public class SkeletonConfigGUI extends EJBagNoStretch {
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, 0.005f), 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, -0.005f), c(3, row, 2));
// Only use a timer on configs that need time to get into position for
switch (config) {
@@ -128,11 +127,15 @@ public class SkeletonConfigGUI extends EJBagNoStretch {
});
}
String getBoneLengthString(SkeletonConfigValue joint){ // Rounded to the nearest 0.5
return ("" + Math.round(server.humanPoseProcessor.getSkeletonConfig(joint) * 200) / 2.0f);
}
@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 +148,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 +158,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,7 +174,7 @@ 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);
}
}

View File

@@ -52,11 +52,11 @@ public class SkeletonList extends EJBagNoStretch {
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();
});

View File

@@ -48,6 +48,7 @@ public class TrackersList extends EJBoxNoStretch {
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);
@@ -59,6 +60,12 @@ public class TrackersList extends EJBoxNoStretch {
server.addNewTrackerConsumer(this::newTrackerAdded);
}
@AWTThread
public void setDebug(boolean debug) {
this.debug = debug;
build();
}
@AWTThread
private void build() {
removeAll();
@@ -142,7 +149,12 @@ 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) {
@@ -216,6 +228,7 @@ public class TrackersList extends EJBoxNoStretch {
add(new JLabel("TPS"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
if(realTracker 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())
@@ -224,6 +237,7 @@ public class TrackersList extends EJBoxNoStretch {
add(position = new JLabel("0 0 0"), c(1, row, 2, GridBagConstraints.FIRST_LINE_START));
if(realTracker 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) {
add(tps = new JLabel("0"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
@@ -240,11 +254,18 @@ public class TrackersList extends EJBoxNoStretch {
row++;
add(new JLabel("Raw:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
add(raw = new JLabel("0 0 0"), s(c(1, row, 2, GridBagConstraints.FIRST_LINE_START), 3, 1));
if(debug && realTracker 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 && realTracker instanceof IMUTracker) {
add(new JLabel("Raw mag:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
add(rawMag = new JLabel("0 0 0"), s(c(1, row, 2, GridBagConstraints.FIRST_LINE_START), 3, 1));
add(new JLabel("Gyro fix:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
add(new JLabel(String.format("0x%8x", realTracker.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));
@@ -253,18 +274,22 @@ public class TrackersList extends EJBoxNoStretch {
row++;
add(new JLabel("Correction:"), c(0, row, 2, GridBagConstraints.FIRST_LINE_START));
add(correction = new JLabel("0 0 0"), s(c(1, row, 2, GridBagConstraints.FIRST_LINE_START), 3, 1));
add(new JLabel("RotAdj:"), c(2, row, 2, GridBagConstraints.FIRST_LINE_START));
add(rotAdj = new JLabel("0"), c(3, row, 2, GridBagConstraints.FIRST_LINE_START));
row++;
}
//*/
/*
if(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(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);
@@ -296,23 +321,53 @@ public class TrackersList extends EJBoxNoStretch {
if(realTracker instanceof TrackerWithTPS) {
tps.setText(StringUtils.prettyNumber(((TrackerWithTPS) realTracker).getTPS(), 1));
}
if(realTracker instanceof TrackerWithBattery)
bat.setText(StringUtils.prettyNumber(((TrackerWithBattery) realTracker).getBatteryVoltage(), 1));
if(realTracker instanceof TrackerWithBattery) {
TrackerWithBattery twb = (TrackerWithBattery) realTracker;
float level = twb.getBatteryLevel();
float voltage = twb.getBatteryVoltage();
if(level == 0.0f) {
bat.setText(String.format("%sV", StringUtils.prettyNumber(voltage, 2)));
} else if(voltage == 0.0f) {
bat.setText(String.format("%d%%", Math.round(level)));
} else {
bat.setText(String.format("%d%% (%sV)", Math.round(level), StringUtils.prettyNumber(voltage, 2)));
}
}
if(t instanceof ReferenceAdjustedTracker) {
((ReferenceAdjustedTracker<Tracker>) t).attachmentFix.toAngles(angles);
if(adj != null)
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));
((ReferenceAdjustedTracker<Tracker>) t).yawFix.toAngles(angles);
if(adjYaw != null)
}
if(adjYaw != null) {
rat.yawFix.toAngles(angles);
adjYaw.setText(StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
+ " " + StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
+ " " + StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0));
}
if(adjGyro != null) {
rat.gyroFix.toAngles(angles);
adjGyro.setText(StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
+ " " + StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
+ " " + StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0));
}
}
if(realTracker instanceof IMUTracker) {
if(ping != null)
ping.setText(String.valueOf(((IMUTracker) realTracker).ping));
if(signalStrength != null) {
int signal = ((IMUTracker) realTracker).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(String.valueOf(percentage) + "% " + "(" + String.valueOf(signal) + " dBm" + ")");
}
}
}
realTracker.getRotation(q);
q.toAngles(angles);
@@ -320,21 +375,44 @@ public class TrackersList extends EJBoxNoStretch {
+ " " + 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)
IMUTracker imu = (IMUTracker) realTracker;
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(((IMUTracker) realTracker).calibrationStatus + " / " + ((IMUTracker) realTracker).magCalibrationStatus);
calibration.setText(imu.calibrationStatus + " / " + imu.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)
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");
}
}
}
}
}

View File

@@ -7,16 +7,20 @@ import javax.swing.event.MouseInputAdapter;
import dev.slimevr.Main;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.NamedPipeBridge;
import dev.slimevr.platform.windows.WindowsNamedPipeBridge;
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.vr.trackers.TrackerRole;
import dev.slimevr.posestreamer.BVHFileStream;
import dev.slimevr.posestreamer.PoseDataStream;
import dev.slimevr.posestreamer.ServerPoseStreamer;
import io.eiren.util.MacOSX;
import io.eiren.util.OperatingSystem;
import io.eiren.util.StringUtils;
import io.eiren.util.ann.AWTThread;
import io.eiren.util.logging.LogManager;
import java.awt.Component;
import java.awt.Container;
@@ -29,6 +33,7 @@ import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -45,6 +50,10 @@ public class VRServerGUI extends JFrame {
private final SkeletonList skeletonList;
private JButton resetButton;
private EJBox pane;
private static File bvhSaveDir = new File("BVH Recordings");
private final ServerPoseStreamer poseStreamer;
private PoseDataStream poseDataStream = null;
private float zoom = 1.5f;
private float initZoom = zoom;
@@ -88,7 +97,11 @@ public class VRServerGUI extends JFrame {
this.trackersList = new TrackersList(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));
this.poseStreamer = new ServerPoseStreamer(server);
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));
@@ -136,6 +149,22 @@ public class VRServerGUI extends JFrame {
});
}
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.log.severe("[BVH] Failed to create the recording directory \"" + bvhSaveDir.getPath() + "\".");
}
return null;
}
@AWTThread
private void build() {
pane.removeAll();
@@ -162,6 +191,37 @@ public class VRServerGUI extends JFrame {
});
}});
add(Box.createHorizontalGlue());
add(new JButton("Record BVH") {{
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (poseDataStream == null) {
File bvhFile = getBvhFile();
if (bvhFile != null) {
try {
poseDataStream = new BVHFileStream(bvhFile);
setText("Stop Recording BVH...");
poseStreamer.setOutput(poseDataStream, 1000L / 100L);
} catch (IOException e1) {
LogManager.log.severe("[BVH] Failed to create the recording file \"" + bvhFile.getPath() + "\".");
}
} else {
LogManager.log.severe("[BVH] Unable to get file to save to");
}
} else {
try {
poseStreamer.closeOutput(poseDataStream);
} catch (Exception e1) {
LogManager.log.severe("[BVH] Exception while closing poseDataStream", e1);
} finally {
poseDataStream = null;
setText("Record BVH");
}
}
}
});
}});
add(Box.createHorizontalGlue());
add(new JButton("GUI Zoom (x" + StringUtils.prettyNumber(zoom, 2) + ")") {{
addMouseListener(new MouseInputAdapter() {
@Override
@@ -198,14 +258,25 @@ public class VRServerGUI extends JFrame {
add(new EJBoxNoStretch(PAGE_AXIS, false, true) {{
setAlignmentY(TOP_ALIGNMENT);
JCheckBox debugCb;
add(debugCb = new JCheckBox("Show debug information"));
debugCb.setSelected(false);
debugCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
trackersList.setDebug(debugCb.isSelected());
}
});
JLabel l;
add(l = new JLabel("Body proportions"));
l.setFont(l.getFont().deriveFont(Font.BOLD));
l.setAlignmentX(0.5f);
add(new SkeletonConfigGUI(server, VRServerGUI.this));
add(Box.createVerticalStrut(10));
if(server.hasBridge(NamedPipeBridge.class)) {
NamedPipeBridge br = server.getVRBridge(NamedPipeBridge.class);
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);
@@ -263,6 +334,19 @@ public class VRServerGUI extends JFrame {
});
}
});
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());
});
}
});
}});

View File

@@ -15,8 +15,10 @@ import java.util.TimerTask;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
@@ -35,7 +37,7 @@ public class WiFiWindow extends JFrame {
private static String savedSSID = "";
private static String savedPassword = "";
JTextField ssidField;
JTextField passwdField;
JPasswordField passwdField;
SerialPort trackerPort = null;
JTextArea log;
TimerTask readTask;
@@ -90,13 +92,26 @@ public class WiFiWindow extends JFrame {
}});
add(new EJBox(BoxLayout.LINE_AXIS) {{
add(new JLabel("Network password:"));
add(passwdField = new JTextField(savedPassword));
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) {
send(ssidField.getText(), passwdField.getText());
send(ssidField.getText(), new String(passwdField.getPassword()));
}
});
}});

View File

@@ -1,4 +1,4 @@
package dev.slimevr.bridge;
package dev.slimevr.platform.windows;
import java.io.IOException;
import java.util.List;
@@ -10,7 +10,9 @@ 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.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;
@@ -21,19 +23,19 @@ 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 {
public class WindowsNamedPipeBridge 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 WindowsPipe 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) {
public WindowsNamedPipeBridge(HMDTracker hmd, String bridgeSettingsKey, String bridgeName, String pipeName, List<? extends ShareableTracker> shareableTrackers) {
super(bridgeName, hmd);
this.pipeName = pipeName;
this.bridgeSettingsKey = bridgeSettingsKey;
@@ -131,26 +133,26 @@ public class NamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnab
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());
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;
@@ -166,7 +168,7 @@ public class NamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnab
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);
messageReceived(message);
readAnything = true;
} else {
pipe.state = PipeState.ERROR;
@@ -185,16 +187,16 @@ public class NamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnab
}
return false;
}
private void resetPipe() {
Pipe.safeDisconnect(pipe);
WindowsPipe.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
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,
@@ -206,12 +208,12 @@ public class NamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnab
throw new IOException("Can't open " + pipeName + " pipe: " + Kernel32.INSTANCE.GetLastError());
LogManager.log.info("[" + bridgeName + "] Pipes are created");
} catch(IOException e) {
Pipe.safeDisconnect(pipe);
WindowsPipe.safeDisconnect(pipe);
throw e;
}
}
private boolean tryOpeningPipe(Pipe pipe) {
private boolean tryOpeningPipe(WindowsPipe 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");

View File

@@ -1,4 +1,4 @@
package dev.slimevr.bridge;
package dev.slimevr.platform.windows;
import java.io.IOException;
import java.nio.charset.Charset;
@@ -13,7 +13,8 @@ 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.bridge.Bridge;
import dev.slimevr.bridge.PipeState;
import dev.slimevr.vr.trackers.ComputedTracker;
import dev.slimevr.vr.trackers.HMDTracker;
import dev.slimevr.vr.trackers.ShareableTracker;
@@ -22,7 +23,7 @@ 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 {
public class WindowsNamedPipeVRBridge extends Thread implements Bridge {
private static final int MAX_COMMAND_LENGTH = 2048;
public static final String HMDPipeName = "\\\\.\\pipe\\HMDPipe";
@@ -36,17 +37,17 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
private final Vector3f vBuffer2 = new Vector3f();
private final Quaternion qBuffer = new Quaternion();
private final Quaternion qBuffer2 = new Quaternion();
private Pipe hmdPipe;
private WindowsPipe hmdPipe;
private final HMDTracker hmd;
private final List<Pipe> trackerPipes;
private final List<WindowsPipe> trackerPipes;
private final List<? extends Tracker> shareTrackers;
private final List<ComputedTracker> internalTrackers;
private final HMDTracker internalHMDTracker = new HMDTracker("itnernal://HMD");
private final HMDTracker internalHMDTracker = new HMDTracker("internal://HMD");
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
public NamedPipeVRBridge(HMDTracker hmd, List<? extends Tracker> shareTrackers, VRServer server) {
public WindowsNamedPipeVRBridge(HMDTracker hmd, List<? extends Tracker> shareTrackers, VRServer server) {
super("Named Pipe VR Bridge");
this.hmd = hmd;
this.shareTrackers = new FastList<>(shareTrackers);
@@ -59,7 +60,7 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
this.internalTrackers.add(ct);
}
}
@Override
public void run() {
try {
@@ -101,21 +102,21 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
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);
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);
@@ -145,11 +146,11 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
}
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());
LogManager.log.severe("[VRBridge] Short HMD data received: " + commandBuilder.toString());
return;
}
try {
@@ -160,7 +161,7 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
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();
@@ -169,11 +170,11 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
e.printStackTrace();
}
}
public void updateTracker(int trackerId, boolean hmdUpdated) {
Tracker sensor = internalTrackers.get(trackerId);
if(sensor.getStatus().sendData) {
Pipe trackerPipe = trackerPipes.get(trackerId);
WindowsPipe trackerPipe = trackerPipes.get(trackerId);
if(hmdUpdated && trackerPipe.state == PipeState.OPEN) {
sbBuffer.setLength(0);
sensor.getPosition(vBuffer);
@@ -188,34 +189,34 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
}
}
}
private void initHMDPipe(Pipe pipe) {
private void initHMDPipe(WindowsPipe pipe) {
hmd.setStatus(TrackerStatus.OK);
}
private void initTrackerPipe(Pipe pipe, int trackerId) {
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);
buffArray,
trackerHello.length() + 1,
lpNumberOfBytesWritten,
null);
}
private boolean tryOpeningPipe(Pipe pipe) {
private boolean tryOpeningPipe(WindowsPipe 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;
@@ -226,10 +227,10 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
}
return true;
}
private void createPipes() throws IOException {
try {
hmdPipe = new Pipe(Kernel32.INSTANCE.CreateNamedPipe(HMDPipeName, WinBase.PIPE_ACCESS_DUPLEX, // dwOpenMode
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,
@@ -242,16 +243,16 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
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
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));
trackerPipes.add(new WindowsPipe(pipeHandle, pipeName));
}
LogManager.log.info("[VRBridge] Pipes are open");
} catch(IOException e) {
@@ -262,8 +263,8 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
throw e;
}
}
public static void safeDisconnect(Pipe pipe) {
public static void safeDisconnect(WindowsPipe pipe) {
try {
if(pipe != null && pipe.pipeHandle != null)
Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle);
@@ -274,13 +275,13 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
@Override
public void addSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
@Override
public void removeSharedTracker(ShareableTracker tracker) {
// TODO Auto-generated method stub
}
@Override

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)
Kernel32.INSTANCE.DisconnectNamedPipe(pipe.pipeHandle);
} catch(Exception e) {
}
}
enum PipeState {
CREATED,
OPEN,
ERROR;
}
}
}

View File

@@ -1,4 +1,4 @@
package dev.slimevr.bridge;
package dev.slimevr.platform.windows;
import java.io.IOException;
import java.util.HashMap;
@@ -7,6 +7,8 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import dev.slimevr.VRServer;
import dev.slimevr.bridge.Bridge;
import org.apache.commons.lang3.StringUtils;
import com.jme3.math.Quaternion;
@@ -16,8 +18,7 @@ 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.bridge.PipeState;
import dev.slimevr.vr.trackers.ShareableTracker;
import dev.slimevr.vr.trackers.TrackerPosition;
import dev.slimevr.vr.trackers.TrackerStatus;
@@ -25,7 +26,7 @@ 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 {
public class WindowsSteamVRPipeInputBridge extends Thread implements Bridge {
private static final int MAX_COMMAND_LENGTH = 2048;
public static final String PipeName = "\\\\.\\pipe\\SlimeVRInput";
@@ -38,12 +39,12 @@ public class SteamVRPipeInputBridge extends Thread implements Bridge {
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) {
private WindowsPipe pipe;
public WindowsSteamVRPipeInputBridge(VRServer server) {
this.server = server;
}
@Override
public void run() {
try {
@@ -71,7 +72,7 @@ public class SteamVRPipeInputBridge extends Thread implements Bridge {
e.printStackTrace();
}
}
public boolean updatePipes() throws IOException {
if(pipe.state == PipeState.OPEN) {
IntByReference bytesAvailable = new IntByReference(0);
@@ -105,73 +106,73 @@ public class SteamVRPipeInputBridge extends Thread implements Bridge {
}
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();
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 "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;
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)) {
@@ -208,32 +209,32 @@ public class SteamVRPipeInputBridge extends Thread implements Bridge {
}
}
}
@Override
public void dataWrite() {
// Not used, only input
}
private void resetPipe() {
Pipe.safeDisconnect(pipe);
WindowsPipe.safeDisconnect(pipe);
pipe.state = PipeState.CREATED;
//Main.vrServer.queueTask(this::disconnected);
}
private boolean tryOpeningPipe(Pipe pipe) {
private boolean tryOpeningPipe(WindowsPipe 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
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,
@@ -245,7 +246,7 @@ public class SteamVRPipeInputBridge extends Thread implements Bridge {
throw new IOException("Can't open " + PipeName + " pipe: " + Kernel32.INSTANCE.GetLastError());
LogManager.log.info("[SteamVRPipeInputBridge] Pipes are open");
} catch(IOException e) {
Pipe.safeDisconnect(pipe);
WindowsPipe.safeDisconnect(pipe);
throw e;
}
}
@@ -253,15 +254,15 @@ public class SteamVRPipeInputBridge extends Thread implements Bridge {
@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),
@@ -270,17 +271,17 @@ public class SteamVRPipeInputBridge extends Thread implements Bridge {
RIGHT_FOOT(TrackerPosition.RIGHT_FOOT),
LEFT_SHOULDER(TrackerPosition.NONE),
RIGHT_SHOULDER(TrackerPosition.NONE),
LEFT_ELBOW(TrackerPosition.NONE),
RIGHT_ELBOW(TrackerPosition.NONE),
LEFT_ELBOW(TrackerPosition.LEFT_ELBOW),
RIGHT_ELBOW(TrackerPosition.RIGHT_ELBOW),
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;
}

View File

@@ -154,7 +154,7 @@ public class BVHFileStream extends PoseDataStream {
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
@@ -207,7 +207,7 @@ public class BVHFileStream extends PoseDataStream {
// Adjust to local rotation
if(inverseRootRot != null) {
rotBuf = rotBuf.multLocal(inverseRootRot);
rotBuf = inverseRootRot.mult(rotBuf, rotBuf);
}
// Yaw (Z), roll (X), pitch (Y) (intrinsic)

View File

@@ -0,0 +1,38 @@
package dev.slimevr.posestreamer;
import java.io.File;
import dev.slimevr.poserecorder.PoseFrameIO;
import dev.slimevr.poserecorder.PoseFrameSkeleton;
import dev.slimevr.poserecorder.PoseFrames;
public class PoseFrameStreamer extends PoseStreamer {
private 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

@@ -2,72 +2,31 @@ package dev.slimevr.posestreamer;
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;
protected HumanSkeleton skeleton;
protected PoseDataStream poseFileStream;
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) {
public PoseStreamer(HumanSkeleton 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.log.severe("[PoseStreamer] Exception while saving frame", e);
}
}
@@ -82,13 +41,16 @@ public class PoseStreamer {
public synchronized long getFrameInterval() {
return frameRecordingInterval;
}
public synchronized HumanSkeleton getSkeleton() {
return skeleton;
}
public synchronized void setOutput(PoseDataStream poseFileStream) throws IOException {
poseFileStream.writeHeader(skeleton, this);
this.poseFileStream = poseFileStream;
nextFrameTimeMs = -1L; // Reset the frame timing
}
public synchronized void setOutput(PoseDataStream poseFileStream, long intervalMs) throws IOException {
setFrameInterval(intervalMs);
setOutput(poseFileStream);

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.HumanSkeleton;
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(HumanSkeleton skeleton) {
this.skeleton = skeleton;
}
@VRServerThread
public void onTick() {
super.doTick();
}
}

View File

@@ -27,23 +27,7 @@ public class StdBVHFileStream extends BVHFileStream {
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) {

View File

@@ -0,0 +1,46 @@
package dev.slimevr.posestreamer;
import java.io.IOException;
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
public class TickPoseStreamer extends PoseStreamer {
protected long nextFrameTimeMs = -1L;
public TickPoseStreamer(HumanSkeleton skeleton) {
super(skeleton);
}
public void doTick() {
PoseDataStream poseFileStream = this.poseFileStream;
if(poseFileStream == null) {
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;
}
captureFrame();
}
@Override
public synchronized void setOutput(PoseDataStream poseFileStream) throws IOException {
super.setOutput(poseFileStream);
nextFrameTimeMs = -1L; // Reset the frame timing
}
}

View File

@@ -58,25 +58,38 @@ public class TransformNodeWrapper {
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) {
@@ -132,7 +145,7 @@ public class TransformNodeWrapper {
result = new Quaternion();
}
return worldTransform.getRotation().mult(inverseRelativeTo, result);
return inverseRelativeTo.mult(worldTransform.getRotation(), result);
}
public void attachChild(TransformNodeWrapper node) {

View File

@@ -7,5 +7,7 @@ public enum ComputedHumanPoseTrackerPosition {
LEFT_FOOT,
RIGHT_FOOT,
LEFT_KNEE,
RIGHT_KNEE;
RIGHT_KNEE,
LEFT_ELBOW,
RIGHT_ELBOW;
}

View File

@@ -32,6 +32,8 @@ public class HumanPoseProcessor {
computedTrackers.add(new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.CHEST, TrackerRole.CHEST));
computedTrackers.add(new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.LEFT_KNEE, TrackerRole.LEFT_KNEE));
computedTrackers.add(new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.RIGHT_KNEE, TrackerRole.RIGHT_KNEE));
computedTrackers.add(new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.LEFT_ELBOW, TrackerRole.LEFT_ELBOW));
computedTrackers.add(new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.RIGHT_ELBOW, TrackerRole.RIGHT_ELBOW));
}
public HumanSkeleton getSkeleton() {

View File

@@ -11,6 +11,9 @@ public abstract class HumanSkeleton {
@ThreadSafe
public abstract TransformNode getRootNode();
@ThreadSafe
public abstract TransformNode[] getAllNodes();
@ThreadSafe
public abstract SkeletonConfig getSkeletonConfig();

View File

@@ -20,14 +20,13 @@ import dev.slimevr.vr.trackers.TrackerUtils;
import io.eiren.util.collections.FastList;
public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallback {
public static final float DEFAULT_FLOOR_OFFSET = 0.05f;
//#region Upper body nodes (torso)
protected final TransformNode hmdNode = new TransformNode("HMD", false);
protected final TransformNode headNode = new TransformNode("Head", false);
protected final TransformNode neckNode = new TransformNode("Neck", false);
protected final TransformNode chestNode = new TransformNode("Chest", false);
protected final TransformNode trackerChestNode = new TransformNode("Chest-Tracker", false);
protected final TransformNode waistNode = new TransformNode("Waist", false);
protected final TransformNode hipNode = new TransformNode("Hip", false);
protected final TransformNode trackerWaistNode = new TransformNode("Waist-Tracker", false);
@@ -36,19 +35,34 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
//#region Lower body nodes (legs)
protected final TransformNode leftHipNode = new TransformNode("Left-Hip", false);
protected final TransformNode leftKneeNode = new TransformNode("Left-Knee", false);
protected final TransformNode trackerLeftKneeNode = new TransformNode("Left-Knee-Tracker", false);
protected final TransformNode leftAnkleNode = new TransformNode("Left-Ankle", false);
protected final TransformNode leftFootNode = new TransformNode("Left-Foot", false);
protected final TransformNode trackerLeftFootNode = new TransformNode("Left-Foot-Tracker", false);
protected final TransformNode rightHipNode = new TransformNode("Right-Hip", false);
protected final TransformNode rightKneeNode = new TransformNode("Right-Knee", false);
protected final TransformNode trackerRightKneeNode = new TransformNode("Right-Knee-Tracker", false);
protected final TransformNode rightAnkleNode = new TransformNode("Right-Ankle", false);
protected final TransformNode rightFootNode = new TransformNode("Right-Foot", false);
protected final TransformNode trackerRightFootNode = new TransformNode("Right-Foot-Tracker", false);
protected float minKneePitch = 0f * FastMath.DEG_TO_RAD;
protected float maxKneePitch = 90f * FastMath.DEG_TO_RAD;
protected float kneeLerpFactor = 0.5f;
//#endregion
//#region Arms (elbows)
protected final TransformNode leftHandNode = new TransformNode("Left-Hand", false);
protected final TransformNode rightHandNode = new TransformNode("Right-Hand", false);
protected final TransformNode leftWristNode = new TransformNode("Left-Wrist", false);
protected final TransformNode rightWristNode = new TransformNode("Right-Wrist", false);
protected final TransformNode leftElbowNode = new TransformNode("Left-Elbow", false);
protected final TransformNode rightElbowNode = new TransformNode("Right-Elbow", false);
protected final TransformNode trackerLeftElbowNode = new TransformNode("Left-Elbow-Tracker", false);
protected final TransformNode trackerRightElbowNode = new TransformNode("Right-Elbow-Tracker", false);
//#endregion
//#region Tracker Input
protected Tracker hmdTracker;
@@ -63,6 +77,11 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
protected Tracker rightLegTracker;
protected Tracker rightAnkleTracker;
protected Tracker rightFootTracker;
protected Tracker leftHandTracker;
protected Tracker rightHandTracker;
protected Tracker leftElbowTracker;
protected Tracker rightElbowTracker;
//#endregion
//#region Tracker Output
@@ -74,6 +93,9 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
protected ComputedHumanPoseTracker computedRightKneeTracker;
protected ComputedHumanPoseTracker computedRightFootTracker;
protected ComputedHumanPoseTracker computedLeftElbowTracker;
protected ComputedHumanPoseTracker computedRightElbowTracker;
//#endregion
protected boolean extendedPelvisModel = true;
@@ -101,7 +123,6 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
neckNode.attachChild(chestNode);
chestNode.attachChild(waistNode);
waistNode.attachChild(hipNode);
hipNode.attachChild(trackerWaistNode);
//#endregion
//#region Assemble skeleton to feet
@@ -117,6 +138,27 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
leftAnkleNode.attachChild(leftFootNode);
rightAnkleNode.attachChild(rightFootNode);
//#endregion
//#region Assemble skeleton arms
leftHandNode.attachChild(leftWristNode);
rightHandNode.attachChild(rightWristNode);
leftWristNode.attachChild(leftElbowNode);
rightWristNode.attachChild(rightElbowNode);
//#endregion
//#region Attach tracker nodes for offsets
chestNode.attachChild(trackerChestNode);
hipNode.attachChild(trackerWaistNode);
leftKneeNode.attachChild(trackerLeftKneeNode);
rightKneeNode.attachChild(trackerRightKneeNode);
leftFootNode.attachChild(trackerLeftFootNode);
rightFootNode.attachChild(trackerRightFootNode);
leftElbowNode.attachChild(trackerLeftElbowNode);
rightElbowNode.attachChild(trackerRightElbowNode);
//#endregion
// Set default skeleton configuration (callback automatically sets initial offsets)
skeletonConfig = new SkeletonConfig(true, this);
@@ -167,7 +209,7 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
}
this.chestTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.CHEST, TrackerPosition.WAIST, TrackerPosition.HIP);
this.waistTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.WAIST, TrackerPosition.CHEST, TrackerPosition.HIP);
this.waistTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.WAIST, TrackerPosition.HIP, TrackerPosition.CHEST);
this.hipTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.HIP, TrackerPosition.WAIST, TrackerPosition.CHEST);
this.leftLegTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.LEFT_LEG, TrackerPosition.LEFT_ANKLE, null);
@@ -177,6 +219,11 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
this.rightLegTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.RIGHT_LEG, TrackerPosition.RIGHT_ANKLE, null);
this.rightAnkleTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.RIGHT_ANKLE, TrackerPosition.RIGHT_LEG, null);
this.rightFootTracker = TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.RIGHT_FOOT);
this.leftHandTracker = TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.LEFT_CONTROLLER);
this.rightHandTracker = TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.RIGHT_CONTROLLER);
this.leftElbowTracker = TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.LEFT_ELBOW);
this.rightElbowTracker = TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.RIGHT_ELBOW);
}
public void setTrackersFromList(List<? extends Tracker> trackers) {
@@ -210,6 +257,13 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
case RIGHT_FOOT:
computedRightFootTracker = tracker;
break;
case LEFT_ELBOW:
computedLeftElbowTracker = tracker;
break;
case RIGHT_ELBOW:
computedRightElbowTracker = tracker;
break;
}
}
@@ -255,6 +309,15 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
computedRightKneeTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.RIGHT_KNEE, TrackerRole.RIGHT_KNEE);
computedRightKneeTracker.setStatus(TrackerStatus.OK);
}
if(computedLeftElbowTracker == null) {
computedLeftElbowTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.LEFT_ELBOW, TrackerRole.LEFT_ELBOW);
computedLeftElbowTracker.setStatus(TrackerStatus.OK);
}
if(computedRightElbowTracker == null) {
computedRightElbowTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.RIGHT_ELBOW, TrackerRole.RIGHT_ELBOW);
computedRightElbowTracker.setStatus(TrackerStatus.OK);
}
}
}
//#endregion
@@ -266,16 +329,20 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
return computedChestTracker;
case WAIST:
return computedWaistTracker;
case LEFT_KNEE:
return computedLeftKneeTracker;
case LEFT_FOOT:
return computedLeftFootTracker;
case RIGHT_KNEE:
return computedRightKneeTracker;
case RIGHT_FOOT:
return computedRightFootTracker;
case LEFT_ELBOW:
return computedLeftElbowTracker;
case RIGHT_ELBOW:
return computedRightElbowTracker;
}
return null;
@@ -293,9 +360,14 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
@Override
public void updatePose() {
updateLocalTransforms();
hmdNode.update();
updateRootTrackers();
updateComputedTrackers();
}
void updateRootTrackers(){
hmdNode.update();
leftHandNode.update();
rightHandNode.update();
}
//#region Update the node transforms from the trackers
protected void updateLocalTransforms() {
@@ -313,6 +385,11 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
Tracker rightLegTracker = trackerPreUpdate(this.rightLegTracker);
Tracker rightAnkleTracker = trackerPreUpdate(this.rightAnkleTracker);
Tracker rightFootTracker = trackerPreUpdate(this.rightFootTracker);
Tracker leftHandTracker = trackerPreUpdate(this.leftHandTracker);
Tracker rightHandTracker = trackerPreUpdate(this.rightHandTracker);
Tracker rightElbowTracker = trackerPreUpdate(this.rightElbowTracker);
Tracker leftElbowTracker = trackerPreUpdate(this.leftElbowTracker);
//#endregion
if(hmdTracker != null) {
@@ -335,6 +412,7 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
}
if(waistTracker.getRotation(rotBuf1)) {
chestNode.localTransform.setRotation(rotBuf1);
trackerChestNode.localTransform.setRotation(rotBuf1);
}
if(hipTracker.getRotation(rotBuf1)) {
waistNode.localTransform.setRotation(rotBuf1);
@@ -353,11 +431,15 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
leftKneeNode.localTransform.setRotation(rotBuf2);
leftAnkleNode.localTransform.setRotation(rotBuf2);
leftFootNode.localTransform.setRotation(rotBuf2);
trackerLeftKneeNode.localTransform.setRotation(rotBuf2);
trackerLeftFootNode.localTransform.setRotation(rotBuf2);
if(leftFootTracker != null) {
leftFootTracker.getRotation(rotBuf2);
leftAnkleNode.localTransform.setRotation(rotBuf2);
leftFootNode.localTransform.setRotation(rotBuf2);
trackerLeftFootNode.localTransform.setRotation(rotBuf2);
}
// Right Leg
@@ -371,11 +453,15 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
rightKneeNode.localTransform.setRotation(rotBuf2);
rightAnkleNode.localTransform.setRotation(rotBuf2);
rightFootNode.localTransform.setRotation(rotBuf2);
trackerRightKneeNode.localTransform.setRotation(rotBuf2);
trackerRightFootNode.localTransform.setRotation(rotBuf2);
if(rightFootTracker != null) {
rightFootTracker.getRotation(rotBuf2);
rightAnkleNode.localTransform.setRotation(rotBuf2);
rightFootNode.localTransform.setRotation(rotBuf2);
trackerRightFootNode.localTransform.setRotation(rotBuf2);
}
if(extendedPelvisModel) {
@@ -390,6 +476,35 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
// TODO : Correct the trackerWaistNode without getting cursed results (only correct yaw?)
// TODO : Use vectors to add like 50% of waist tracker yaw to waist node to reduce drift and let user take weird poses
}
// Left arm
if(leftHandTracker != null){
if(leftHandTracker.getPosition(posBuf))
leftHandNode.localTransform.setTranslation(posBuf);
if(leftHandTracker.getRotation(rotBuf1))
leftHandNode.localTransform.setRotation(rotBuf1);
}
if(leftElbowTracker != null){
if(leftElbowTracker.getRotation(rotBuf1)){
leftWristNode.localTransform.setRotation(rotBuf1);
trackerLeftElbowNode.localTransform.setRotation(rotBuf1);
}
}
// Right arm
if(rightHandTracker != null){
if(rightHandTracker.getPosition(posBuf))
rightHandNode.localTransform.setTranslation(posBuf);
if(rightHandTracker.getRotation(rotBuf1))
rightHandNode.localTransform.setRotation(rotBuf1);
}
if(rightElbowTracker != null){
if(rightElbowTracker.getRotation(rotBuf1)){
rightWristNode.localTransform.setRotation(rotBuf1);
trackerRightElbowNode.localTransform.setRotation(rotBuf1);
}
}
}
//#endregion
@@ -435,7 +550,7 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
//#region Update the output trackers
protected void updateComputedTrackers() {
if(computedChestTracker != null) {
computedChestTracker.position.set(chestNode.worldTransform.getTranslation());
computedChestTracker.position.set(trackerChestNode.worldTransform.getTranslation());
computedChestTracker.rotation.set(neckNode.worldTransform.getRotation());
computedChestTracker.dataTick();
}
@@ -447,28 +562,40 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
}
if(computedLeftKneeTracker != null) {
computedLeftKneeTracker.position.set(leftKneeNode.worldTransform.getTranslation());
computedLeftKneeTracker.position.set(trackerLeftKneeNode.worldTransform.getTranslation());
computedLeftKneeTracker.rotation.set(leftHipNode.worldTransform.getRotation());
computedLeftKneeTracker.dataTick();
}
if(computedLeftFootTracker != null) {
computedLeftFootTracker.position.set(leftFootNode.worldTransform.getTranslation());
computedLeftFootTracker.rotation.set(leftFootNode.worldTransform.getRotation());
computedLeftFootTracker.position.set(trackerLeftFootNode.worldTransform.getTranslation());
computedLeftFootTracker.rotation.set(trackerLeftFootNode.worldTransform.getRotation());
computedLeftFootTracker.dataTick();
}
if(computedRightKneeTracker != null) {
computedRightKneeTracker.position.set(rightKneeNode.worldTransform.getTranslation());
computedRightKneeTracker.position.set(trackerRightKneeNode.worldTransform.getTranslation());
computedRightKneeTracker.rotation.set(rightHipNode.worldTransform.getRotation());
computedRightKneeTracker.dataTick();
}
if(computedRightFootTracker != null) {
computedRightFootTracker.position.set(rightFootNode.worldTransform.getTranslation());
computedRightFootTracker.rotation.set(rightFootNode.worldTransform.getRotation());
computedRightFootTracker.position.set(trackerRightFootNode.worldTransform.getTranslation());
computedRightFootTracker.rotation.set(trackerRightFootNode.worldTransform.getRotation());
computedRightFootTracker.dataTick();
}
if(computedLeftElbowTracker != null) {
computedLeftElbowTracker.position.set(trackerLeftElbowNode.worldTransform.getTranslation());
computedLeftElbowTracker.rotation.set(trackerLeftElbowNode.worldTransform.getRotation());
computedLeftElbowTracker.dataTick();
}
if(computedRightElbowTracker != null) {
computedRightElbowTracker.position.set(trackerRightElbowNode.worldTransform.getTranslation());
computedRightElbowTracker.rotation.set(trackerRightElbowNode.worldTransform.getRotation());
computedRightElbowTracker.dataTick();
}
}
//#endregion
//#endregion
@@ -512,6 +639,9 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
case CHEST:
chestNode.localTransform.setTranslation(offset);
break;
case CHEST_TRACKER:
trackerChestNode.localTransform.setTranslation(offset);
break;
case WAIST:
waistNode.localTransform.setTranslation(offset);
break;
@@ -533,6 +663,10 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
leftKneeNode.localTransform.setTranslation(offset);
rightKneeNode.localTransform.setTranslation(offset);
break;
case KNEE_TRACKER:
trackerLeftKneeNode.localTransform.setTranslation(offset);
trackerRightKneeNode.localTransform.setTranslation(offset);
break;
case ANKLE:
leftAnkleNode.localTransform.setTranslation(offset);
rightAnkleNode.localTransform.setTranslation(offset);
@@ -541,6 +675,19 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
leftFootNode.localTransform.setTranslation(offset);
rightFootNode.localTransform.setTranslation(offset);
break;
case FOOT_TRACKER:
trackerLeftFootNode.localTransform.setTranslation(offset);
trackerRightFootNode.localTransform.setTranslation(offset);
break;
case HAND:
leftWristNode.localTransform.setTranslation(offset);
rightWristNode.localTransform.setTranslation(offset);
break;
case ELBOW:
leftElbowNode.localTransform.setTranslation(offset);
rightElbowNode.localTransform.setTranslation(offset);
break;
}
}
@@ -594,6 +741,28 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
rightAnkleNode.update();
updateComputedTrackers();
break;
case SKELETON_OFFSET:
trackerChestNode.update();
trackerWaistNode.update();
trackerLeftKneeNode.update();
trackerRightKneeNode.update();
trackerLeftFootNode.update();
trackerRightFootNode.update();
trackerLeftElbowNode.update();
trackerRightElbowNode.update();
updateComputedTrackers();
break;
case CONTROLLER_DISTANCE_Z:
case CONTROLLER_DISTANCE_Y:
leftWristNode.update();
rightWristNode.update();
updateComputedTrackers();
break;
case ELBOW_DISTANCE:
leftElbowNode.update();
rightElbowNode.update();
updateComputedTrackers();
break;
}
}
//#endregion
@@ -602,6 +771,17 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
public TransformNode getRootNode() {
return hmdNode;
}
@Override
public TransformNode[] getAllNodes() {
List<TransformNode> nodesList = new FastList<>();
hmdNode.depthFirstTraversal((node) -> {nodesList.add(node);});
leftHandNode.depthFirstTraversal((node) -> {nodesList.add(node);});
rightHandNode.depthFirstTraversal((node) -> {nodesList.add(node);});
return nodesList.toArray(new TransformNode[0]);
}
@Override
public SkeletonConfig getSkeletonConfig() {
@@ -628,14 +808,14 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
hmdTracker.getPosition(vec);
height = vec.y;
if(height > 0.5f) { // Reset only if floor level is right, TODO: read floor level from SteamVR if it's not 0
skeletonConfig.setConfig(SkeletonConfigValue.TORSO, ((height) / 2.0f) - skeletonConfig.getConfig(SkeletonConfigValue.NECK));
skeletonConfig.setConfig(SkeletonConfigValue.TORSO, ((height) * 0.42f) - skeletonConfig.getConfig(SkeletonConfigValue.NECK));
} else// if floor level is incorrect
{
skeletonConfig.setConfig(SkeletonConfigValue.TORSO, null);
}
break;
case CHEST: //Chest is roughly half of the upper body (shoulders to chest)
skeletonConfig.setConfig(SkeletonConfigValue.CHEST, skeletonConfig.getConfig(SkeletonConfigValue.TORSO) / 2.0f);
case CHEST: //Chest is 57% of the upper body by default (shoulders to chest)
skeletonConfig.setConfig(SkeletonConfigValue.CHEST, skeletonConfig.getConfig(SkeletonConfigValue.TORSO) * 0.57f);
break;
case WAIST: // waist length is from hips to waist
skeletonConfig.setConfig(SkeletonConfigValue.WAIST, null);
@@ -652,74 +832,60 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
case FOOT_OFFSET:
skeletonConfig.setConfig(SkeletonConfigValue.FOOT_OFFSET, null);
break;
case SKELETON_OFFSET:
skeletonConfig.setConfig(SkeletonConfigValue.SKELETON_OFFSET, null);
break;
case LEGS_LENGTH: // Set legs length to be 5cm above floor level
vec = new Vector3f();
hmdTracker.getPosition(vec);
height = vec.y;
if(height > 0.5f) { // Reset only if floor level is right, todo: read floor level from SteamVR if it's not 0
skeletonConfig.setConfig(SkeletonConfigValue.LEGS_LENGTH, height - skeletonConfig.getConfig(SkeletonConfigValue.NECK) - skeletonConfig.getConfig(SkeletonConfigValue.TORSO) - DEFAULT_FLOOR_OFFSET);
skeletonConfig.setConfig(SkeletonConfigValue.LEGS_LENGTH, height - skeletonConfig.getConfig(SkeletonConfigValue.NECK) - skeletonConfig.getConfig(SkeletonConfigValue.TORSO) - 0.05f);
} else //if floor level is incorrect
{
skeletonConfig.setConfig(SkeletonConfigValue.LEGS_LENGTH, null);
}
resetSkeletonConfig(SkeletonConfigValue.KNEE_HEIGHT);
break;
case KNEE_HEIGHT: // Knees are at 50% of the legs by default
skeletonConfig.setConfig(SkeletonConfigValue.KNEE_HEIGHT, skeletonConfig.getConfig(SkeletonConfigValue.LEGS_LENGTH) / 2.0f);
case KNEE_HEIGHT: // Knees are at 55% of the legs by default
skeletonConfig.setConfig(SkeletonConfigValue.KNEE_HEIGHT, skeletonConfig.getConfig(SkeletonConfigValue.LEGS_LENGTH) * 0.55f);
break;
case CONTROLLER_DISTANCE_Z:
skeletonConfig.setConfig(SkeletonConfigValue.CONTROLLER_DISTANCE_Z, null);
break;
case CONTROLLER_DISTANCE_Y:
skeletonConfig.setConfig(SkeletonConfigValue.CONTROLLER_DISTANCE_Y, null);
break;
case ELBOW_DISTANCE:
skeletonConfig.setConfig(SkeletonConfigValue.ELBOW_DISTANCE, null);
break;
}
}
Tracker[] getTrackerToReset(){
return new Tracker[] {trackerPreUpdate(this.chestTracker), trackerPreUpdate(this.waistTracker),
trackerPreUpdate(this.hipTracker), trackerPreUpdate(this.leftLegTracker),
trackerPreUpdate(this.leftAnkleTracker), trackerPreUpdate(this.leftFootTracker),
trackerPreUpdate(this.rightLegTracker), trackerPreUpdate(this.rightAnkleTracker),
trackerPreUpdate(this.rightFootTracker), trackerPreUpdate(this.rightElbowTracker),
trackerPreUpdate(this.leftElbowTracker)};
}
@Override
public void resetTrackersFull() {
//#region Pass all trackers through trackerPreUpdate
Tracker hmdTracker = trackerPreUpdate(this.hmdTracker);
Tracker chestTracker = trackerPreUpdate(this.chestTracker);
Tracker waistTracker = trackerPreUpdate(this.waistTracker);
Tracker hipTracker = trackerPreUpdate(this.hipTracker);
Tracker leftLegTracker = trackerPreUpdate(this.leftLegTracker);
Tracker leftAnkleTracker = trackerPreUpdate(this.leftAnkleTracker);
Tracker leftFootTracker = trackerPreUpdate(this.leftFootTracker);
Tracker rightLegTracker = trackerPreUpdate(this.rightLegTracker);
Tracker rightAnkleTracker = trackerPreUpdate(this.rightAnkleTracker);
Tracker rightFootTracker = trackerPreUpdate(this.rightFootTracker);
Tracker[] trackersToReset = getTrackerToReset();
//#endregion
// Each tracker uses the tracker before it to adjust itself,
// so trackers that don't need adjustments could be used too
// Resets all axis of the trackers with the HMD as reference.
Quaternion referenceRotation = new Quaternion();
hmdTracker.getRotation(referenceRotation);
chestTracker.resetFull(referenceRotation);
chestTracker.getRotation(referenceRotation);
waistTracker.resetFull(referenceRotation);
waistTracker.getRotation(referenceRotation);
hipTracker.resetFull(referenceRotation);
hipTracker.getRotation(referenceRotation);
leftLegTracker.resetFull(referenceRotation);
rightLegTracker.resetFull(referenceRotation);
leftLegTracker.getRotation(referenceRotation);
leftAnkleTracker.resetFull(referenceRotation);
leftAnkleTracker.getRotation(referenceRotation);
if(leftFootTracker != null) {
leftFootTracker.resetFull(referenceRotation);
}
rightLegTracker.getRotation(referenceRotation);
rightAnkleTracker.resetFull(referenceRotation);
rightAnkleTracker.getRotation(referenceRotation);
if(rightFootTracker != null) {
rightFootTracker.resetFull(referenceRotation);
for (Tracker tracker : trackersToReset) {
if(tracker != null){
tracker.resetFull(referenceRotation);
}
}
}
@@ -728,52 +894,17 @@ public class SimpleSkeleton extends HumanSkeleton implements SkeletonConfigCallb
public void resetTrackersYaw() {
//#region Pass all trackers through trackerPreUpdate
Tracker hmdTracker = trackerPreUpdate(this.hmdTracker);
Tracker chestTracker = trackerPreUpdate(this.chestTracker);
Tracker waistTracker = trackerPreUpdate(this.waistTracker);
Tracker hipTracker = trackerPreUpdate(this.hipTracker);
Tracker leftLegTracker = trackerPreUpdate(this.leftLegTracker);
Tracker leftAnkleTracker = trackerPreUpdate(this.leftAnkleTracker);
Tracker leftFootTracker = trackerPreUpdate(this.leftFootTracker);
Tracker rightLegTracker = trackerPreUpdate(this.rightLegTracker);
Tracker rightAnkleTracker = trackerPreUpdate(this.rightAnkleTracker);
Tracker rightFootTracker = trackerPreUpdate(this.rightFootTracker);
Tracker[] trackersToReset = getTrackerToReset();
//#endregion
// Each tracker uses the tracker before it to adjust itself,
// so trackers that don't need adjustments could be used too
// Resets the yaw of the trackers with the HMD as reference.
Quaternion referenceRotation = new Quaternion();
hmdTracker.getRotation(referenceRotation);
chestTracker.resetYaw(referenceRotation);
chestTracker.getRotation(referenceRotation);
waistTracker.resetYaw(referenceRotation);
waistTracker.getRotation(referenceRotation);
hipTracker.resetYaw(referenceRotation);
hipTracker.getRotation(referenceRotation);
leftLegTracker.resetYaw(referenceRotation);
rightLegTracker.resetYaw(referenceRotation);
leftLegTracker.getRotation(referenceRotation);
leftAnkleTracker.resetYaw(referenceRotation);
leftAnkleTracker.getRotation(referenceRotation);
if(leftFootTracker != null) {
leftFootTracker.resetYaw(referenceRotation);
}
rightLegTracker.getRotation(referenceRotation);
rightAnkleTracker.resetYaw(referenceRotation);
rightAnkleTracker.getRotation(referenceRotation);
if(rightFootTracker != null) {
rightFootTracker.resetYaw(referenceRotation);
for (Tracker tracker : trackersToReset) {
if(tracker != null){
tracker.resetYaw(referenceRotation);
}
}
}
}

View File

@@ -216,6 +216,9 @@ public class SkeletonConfig {
case CHEST:
setNodeOffset(nodeOffset, 0, -getConfig(SkeletonConfigValue.CHEST), 0);
break;
case CHEST_TRACKER:
setNodeOffset(nodeOffset, 0, 0, -getConfig(SkeletonConfigValue.SKELETON_OFFSET));
break;
case WAIST:
setNodeOffset(nodeOffset, 0, (getConfig(SkeletonConfigValue.CHEST) - getConfig(SkeletonConfigValue.TORSO) + getConfig(SkeletonConfigValue.WAIST)), 0);
break;
@@ -223,7 +226,7 @@ public class SkeletonConfig {
setNodeOffset(nodeOffset, 0, -getConfig(SkeletonConfigValue.WAIST), 0);
break;
case HIP_TRACKER:
setNodeOffset(nodeOffset, 0, getConfig(SkeletonConfigValue.HIP_OFFSET), 0);
setNodeOffset(nodeOffset, 0, getConfig(SkeletonConfigValue.HIP_OFFSET), -getConfig(SkeletonConfigValue.SKELETON_OFFSET));
break;
case LEFT_HIP:
@@ -236,12 +239,28 @@ public class SkeletonConfig {
case KNEE:
setNodeOffset(nodeOffset, 0, -(getConfig(SkeletonConfigValue.LEGS_LENGTH) - getConfig(SkeletonConfigValue.KNEE_HEIGHT)), 0);
break;
case KNEE_TRACKER:
setNodeOffset(nodeOffset, 0, 0, -getConfig(SkeletonConfigValue.SKELETON_OFFSET));
break;
case ANKLE:
setNodeOffset(nodeOffset, 0, -getConfig(SkeletonConfigValue.KNEE_HEIGHT), -getConfig(SkeletonConfigValue.FOOT_OFFSET));
break;
case FOOT:
setNodeOffset(nodeOffset, 0, 0, -getConfig(SkeletonConfigValue.FOOT_LENGTH));
break;
case FOOT_TRACKER:
setNodeOffset(nodeOffset, 0, 0, -getConfig(SkeletonConfigValue.SKELETON_OFFSET));
break;
case HAND:
setNodeOffset(nodeOffset, 0, getConfig(SkeletonConfigValue.CONTROLLER_DISTANCE_Y), getConfig(SkeletonConfigValue.CONTROLLER_DISTANCE_Z));
break;
case ELBOW:
setNodeOffset(nodeOffset, 0, getConfig(SkeletonConfigValue.ELBOW_DISTANCE), 0);
break;
case ELBOW_TRACKER:
setNodeOffset(nodeOffset, 0, 0, 0);
break;
}
}

View File

@@ -7,15 +7,19 @@ public enum SkeletonConfigValue {
HEAD("Head", "headShift", "Head shift", 0.1f, new SkeletonNodeOffset[]{SkeletonNodeOffset.HEAD}),
NECK("Neck", "neckLength", "Neck length", 0.1f, new SkeletonNodeOffset[]{SkeletonNodeOffset.NECK}),
TORSO("Torso", "torsoLength", "Torso length", 0.64f, new SkeletonNodeOffset[]{SkeletonNodeOffset.WAIST}),
TORSO("Torso", "torsoLength", "Torso length", 0.56f, new SkeletonNodeOffset[]{SkeletonNodeOffset.WAIST}),
CHEST("Chest", "chestDistance", "Chest distance", 0.32f, new SkeletonNodeOffset[]{SkeletonNodeOffset.CHEST, SkeletonNodeOffset.WAIST}),
WAIST("Waist", "waistDistance", "Waist distance", 0.05f, new SkeletonNodeOffset[]{SkeletonNodeOffset.WAIST, SkeletonNodeOffset.HIP}),
WAIST("Waist", "waistDistance", "Waist distance", 0.04f, new SkeletonNodeOffset[]{SkeletonNodeOffset.WAIST, SkeletonNodeOffset.HIP}),
HIP_OFFSET("Hip offset", "hipOffset", "Hip offset", 0.0f, new SkeletonNodeOffset[]{SkeletonNodeOffset.HIP_TRACKER}),
HIPS_WIDTH("Hips width", "hipsWidth", "Hips width", 0.3f, new SkeletonNodeOffset[]{SkeletonNodeOffset.LEFT_HIP, SkeletonNodeOffset.RIGHT_HIP}),
LEGS_LENGTH("Legs length", "legsLength", "Legs length", 0.86f, new SkeletonNodeOffset[]{SkeletonNodeOffset.KNEE}),
KNEE_HEIGHT("Knee height", "kneeHeight", "Knee height", 0.43f, new SkeletonNodeOffset[]{SkeletonNodeOffset.KNEE, SkeletonNodeOffset.ANKLE}),
HIPS_WIDTH("Hips width", "hipsWidth", "Hips width", 0.26f, new SkeletonNodeOffset[]{SkeletonNodeOffset.LEFT_HIP, SkeletonNodeOffset.RIGHT_HIP}),
LEGS_LENGTH("Legs length", "legsLength", "Legs length", 0.92f, new SkeletonNodeOffset[]{SkeletonNodeOffset.KNEE}),
KNEE_HEIGHT("Knee height", "kneeHeight", "Knee height", 0.50f, new SkeletonNodeOffset[]{SkeletonNodeOffset.KNEE, SkeletonNodeOffset.ANKLE}),
FOOT_LENGTH("Foot length", "footLength", "Foot length", 0.05f, new SkeletonNodeOffset[]{SkeletonNodeOffset.FOOT}),
FOOT_OFFSET("Foot offset", "footOffset", "Foot offset", 0.0f, new SkeletonNodeOffset[]{SkeletonNodeOffset.ANKLE}),
FOOT_OFFSET("Foot offset", "footOffset", "Foot offset", -0.05f, new SkeletonNodeOffset[]{SkeletonNodeOffset.ANKLE}),
SKELETON_OFFSET("Skeleton offset", "skeletonOffset", "Skeleton offset", 0.0f, new SkeletonNodeOffset[]{SkeletonNodeOffset.CHEST_TRACKER, SkeletonNodeOffset.HIP_TRACKER, SkeletonNodeOffset.KNEE_TRACKER, SkeletonNodeOffset.FOOT_TRACKER}),
CONTROLLER_DISTANCE_Z("Controller distance z", "controllerDistanceZ", "Controller distance z", 0.15f, new SkeletonNodeOffset[]{SkeletonNodeOffset.HAND}),
CONTROLLER_DISTANCE_Y("Controller distance y", "controllerDistanceY", "Controller distance y", 0.05f, new SkeletonNodeOffset[]{SkeletonNodeOffset.HAND}),
ELBOW_DISTANCE("Elbow distance", "elbowDistance", "Elbow distance", 0.24f, new SkeletonNodeOffset[]{SkeletonNodeOffset.ELBOW}),
;
private static final String CONFIG_PREFIX = "body.";

View File

@@ -5,14 +5,20 @@ public enum SkeletonNodeOffset {
HEAD,
NECK,
CHEST,
CHEST_TRACKER,
WAIST,
HIP,
HIP_TRACKER,
LEFT_HIP,
RIGHT_HIP,
KNEE,
KNEE_TRACKER,
ANKLE,
FOOT,
FOOT_TRACKER,
HAND,
ELBOW,
ELBOW_TRACKER
;
public static final SkeletonNodeOffset[] values = values();

View File

@@ -4,19 +4,19 @@ import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import io.eiren.math.FloatMath;
import dev.slimevr.vr.trackers.udp.TrackersUDPServer;
import io.eiren.util.BufferedTimer;
public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
public static final float MAX_MAG_CORRECTION_ACCURACY = 5 * FastMath.RAD_TO_DEG;
public final Vector3f gyroVector = new Vector3f();
public final Vector3f accelVector = new Vector3f();
//public final Vector3f gyroVector = new Vector3f();
//public final Vector3f accelVector = new Vector3f();
public final Vector3f magVector = new Vector3f();
public final Quaternion rotQuaternion = new Quaternion();
public final Quaternion rotMagQuaternion = new Quaternion();
protected final Quaternion rotAdjust = new Quaternion();
public final Quaternion rotAdjust = new Quaternion();
protected final Quaternion correction = new Quaternion();
protected TrackerMountingRotation mounting = null;
protected TrackerStatus status = TrackerStatus.OK;
@@ -27,6 +27,7 @@ public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
protected final TrackersUDPServer server;
protected float confidence = 0;
protected float batteryVoltage = 0;
protected float batteryLevel = 0;
public int calibrationStatus = 0;
public int magCalibrationStatus = 0;
public float magnetometerAccuracy = 0;
@@ -35,9 +36,9 @@ public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
protected BufferedTimer timer = new BufferedTimer(1f);
public int ping = -1;
public int signalStrength = -1;
public float temperature = 0;
public StringBuilder serialBuffer = new StringBuilder();
long lastSerialUpdate = 0;
public TrackerPosition bodyPosition = null;
public IMUTracker(int trackerId, String name, String descriptiveName, TrackersUDPServer server) {
@@ -58,7 +59,13 @@ public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
// Loading a config is an act of user editing, therefore it shouldn't not be allowed if editing is not allowed
if (userEditable()) {
if(config.mountingRotation != null) {
mounting = TrackerMountingRotation.valueOf(config.mountingRotation);
try{
mounting = TrackerMountingRotation.valueOf(config.mountingRotation);
}
catch (Exception e){ // FORWARD was renamed to FRONT
mounting = TrackerMountingRotation.FRONT;
config.mountingRotation = "FRONT";
}
if(mounting != null) {
rotAdjust.set(mounting.quaternion);
} else {
@@ -110,7 +117,7 @@ public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
@Override
public boolean getRotation(Quaternion store) {
store.set(rotQuaternion);
//correction.mult(store, store); // Correction is not used now to preven accidental errors while debugging other things
//correction.mult(store, store); // Correction is not used now to prevent accidental errors while debugging other things
store.multLocal(rotAdjust);
return true;
}
@@ -149,7 +156,7 @@ public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
@Override
public float getBatteryLevel() {
return FloatMath.mapValue(getBatteryVoltage(), 3.6f, 4.2f, 0f, 1f);
return batteryLevel;
}
@Override
@@ -157,6 +164,10 @@ public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
return batteryVoltage;
}
public void setBatteryLevel(float level) {
this.batteryLevel = level;
}
public void setBatteryVoltage(float voltage) {
this.batteryVoltage = voltage;
}
@@ -187,7 +198,7 @@ public class IMUTracker implements Tracker, TrackerWithTPS, TrackerWithBattery {
*/
protected void calculateLiveMagnetometerCorrection() {
// TODO Magic, correct only yaw
// TODO Print "jump" length when correcing if it's more than 1 degree
// TODO Print "jump" length when correcting if it's more than 1 degree
}
@Override

View File

@@ -2,6 +2,8 @@ package dev.slimevr.vr.trackers;
import java.nio.ByteBuffer;
import dev.slimevr.vr.trackers.udp.TrackersUDPServer;
public class MPUTracker extends IMUTracker {
public ConfigurationData newCalibrationData;
@@ -12,7 +14,7 @@ public class MPUTracker extends IMUTracker {
public static class ConfigurationData {
//acel offsets and correction matrix
//accel offsets and correction matrix
float[] A_B = new float[3];
float[][] A_Ainv = new float[3][3];
// mag offsets and correction matrix

View File

@@ -57,7 +57,7 @@ public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
/**
* Reset the tracker so that it's current yaw rotation
* is counted as <HMD Yaw>. This allows the tracker
* to have yaw independant of the HMD. Tracker should
* to have yaw independent of the HMD. Tracker should
* still report yaw as if it was mounted facing HMD,
* mounting position should be corrected in the source.
*/

View File

@@ -1,10 +1,10 @@
package dev.slimevr.vr.trackers;
public class BnoTap {
public class SensorTap {
public final boolean doubleTap;
public BnoTap(int tapBits) {
public SensorTap(int tapBits) {
doubleTap = (tapBits & 0x40) > 0;
}

View File

@@ -5,7 +5,7 @@ import com.jme3.math.Quaternion;
public enum TrackerMountingRotation {
FORWARD(180),
FRONT(180),
LEFT(90),
BACK(0),
RIGHT(-90);

View File

@@ -18,7 +18,9 @@ public enum TrackerPosition {
LEFT_FOOT("body:left_foot", TrackerRole.LEFT_FOOT),
RIGHT_FOOT("body:right_foot", TrackerRole.RIGHT_FOOT),
LEFT_CONTROLLER("body:left_controller", TrackerRole.LEFT_CONTROLLER),
RIGHT_CONTROLLER("body:right_conroller", TrackerRole.RIGHT_CONTROLLER),
RIGHT_CONTROLLER("body:right_controller", TrackerRole.RIGHT_CONTROLLER),
LEFT_ELBOW("body:left_elbow", TrackerRole.LEFT_ELBOW),
RIGHT_ELBOW("body:right_elbow", TrackerRole.RIGHT_ELBOW),
;
public final String designation;
@@ -52,4 +54,4 @@ public enum TrackerPosition {
}
}
}
}
}

View File

@@ -1,464 +0,0 @@
package dev.slimevr.vr.trackers;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Consumer;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import io.eiren.util.Util;
import io.eiren.util.collections.FastList;
/**
* Recieves trackers data by UDP using extended owoTrack protocol.
*/
public class TrackersUDPServer extends Thread {
/**
* Change between IMU axises and OpenGL/SteamVR axises
*/
private static final Quaternion offset = new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X);
private static final byte[] HANDSHAKE_BUFFER = new byte[64];
private static final byte[] KEEPUP_BUFFER = new byte[64];
private static final byte[] CALIBRATION_BUFFER = new byte[64];
private static final byte[] CALIBRATION_REQUEST_BUFFER = new byte[64];
private final Quaternion buf = new Quaternion();
private final Random random = new Random();
private final List<TrackerConnection> trackers = new FastList<>();
private final Map<InetAddress, TrackerConnection> trackersMap = new HashMap<>();
private final Map<Tracker, Consumer<String>> calibrationDataRequests = new HashMap<>();
private final Consumer<Tracker> trackersConsumer;
private final int port;
protected DatagramSocket socket = null;
protected long lastKeepup = System.currentTimeMillis();
public TrackersUDPServer(int port, String name, Consumer<Tracker> trackersConsumer) {
super(name);
this.port = port;
this.trackersConsumer = trackersConsumer;
}
private void setUpNewSensor(DatagramPacket handshakePacket, ByteBuffer data) throws IOException {
System.out.println("[TrackerServer] Handshake recieved from " + handshakePacket.getAddress() + ":" + handshakePacket.getPort());
InetAddress addr = handshakePacket.getAddress();
TrackerConnection sensor;
synchronized(trackers) {
sensor = trackersMap.get(addr);
}
if(sensor == null) {
boolean isOwo = false;
data.getLong(); // Skip packet number
int boardType = -1;
int imuType = -1;
int firmwareBuild = -1;
StringBuilder firmware = new StringBuilder();
byte[] mac = new byte[6];
String macString = null;
if(data.remaining() > 0) {
if(data.remaining() > 3)
boardType = data.getInt();
if(data.remaining() > 3)
imuType = data.getInt();
if(data.remaining() > 3)
data.getInt(); // MCU TYPE
if(data.remaining() > 11) {
data.getInt(); // IMU info
data.getInt();
data.getInt();
}
if(data.remaining() > 3)
firmwareBuild = data.getInt();
int length = 0;
if(data.remaining() > 0)
length = data.get() & 0xFF; // firmware version length is 1 longer than that because it's nul-terminated
while(length > 0 && data.remaining() != 0) {
char c = (char) data.get();
if(c == 0)
break;
firmware.append(c);
length--;
}
if(data.remaining() > mac.length) {
data.get(mac);
macString = String.format("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
if(macString.equals("00:00:00:00:00:00"))
macString = null;
}
}
if(firmware.length() == 0) {
firmware.append("owoTrack");
isOwo = true;
}
String trackerName = macString != null ? "udp://" + macString : "udp:/" + handshakePacket.getAddress().toString();
String descriptiveName = "udp:/" + handshakePacket.getAddress().toString();
IMUTracker imu = new IMUTracker(Tracker.getNextLocalTrackerId(), trackerName, descriptiveName, this);
ReferenceAdjustedTracker<IMUTracker> adjustedTracker = new ReferenceAdjustedTracker<>(imu);
trackersConsumer.accept(adjustedTracker);
sensor = new TrackerConnection(imu, handshakePacket.getSocketAddress());
sensor.isOwoTrack = isOwo;
int i = 0;
synchronized(trackers) {
i = trackers.size();
trackers.add(sensor);
trackersMap.put(addr, sensor);
}
System.out.println("[TrackerServer] Sensor " + i + " added with address " + handshakePacket.getSocketAddress() + ". Board type: " + boardType + ", imu type: " + imuType + ", firmware: " + firmware + " (" + firmwareBuild + "), mac: " + macString + ", name: " + trackerName);
}
sensor.sensors.get(0).setStatus(TrackerStatus.OK);
socket.send(new DatagramPacket(HANDSHAKE_BUFFER, HANDSHAKE_BUFFER.length, handshakePacket.getAddress(), handshakePacket.getPort()));
}
private void setUpAuxilarySensor(TrackerConnection connection, int trackerId) throws IOException {
System.out.println("[TrackerServer] Setting up auxilary sensor for " + connection.sensors.get(0).getName());
IMUTracker imu = connection.sensors.get(trackerId);
if(imu == null) {
imu = new IMUTracker(Tracker.getNextLocalTrackerId(), connection.sensors.get(0).getName() + "/" + trackerId, connection.sensors.get(0).getDescriptiveName() + "/" + trackerId, this);
connection.sensors.put(trackerId, imu);
ReferenceAdjustedTracker<IMUTracker> adjustedTracker = new ReferenceAdjustedTracker<>(imu);
trackersConsumer.accept(adjustedTracker);
System.out.println("[TrackerServer] Sensor added with address " + imu.getName());
}
imu.setStatus(TrackerStatus.OK);
}
@Override
public void run() {
byte[] rcvBuffer = new byte[512];
ByteBuffer bb = ByteBuffer.wrap(rcvBuffer).order(ByteOrder.BIG_ENDIAN);
StringBuilder serialBuffer2 = new StringBuilder();
try {
socket = new DatagramSocket(port);
// Why not just 255.255.255.255? Because Windows.
// https://social.technet.microsoft.com/Forums/windows/en-US/72e7387a-9f2c-4bf4-a004-c89ddde1c8aa/how-to-fix-the-global-broadcast-address-255255255255-behavior-on-windows
ArrayList<SocketAddress> addresses = new ArrayList<SocketAddress>();
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
// Ignore loopback, PPP, virtual and disabled devices
if (iface.isLoopback() || !iface.isUp() || iface.isPointToPoint() || iface.isVirtual()) {
continue;
}
Enumeration<InetAddress> iaddrs = iface.getInetAddresses();
while (iaddrs.hasMoreElements()) {
InetAddress iaddr = iaddrs.nextElement();
// Ignore IPv6 addresses
if (iaddr instanceof Inet6Address) {
continue;
}
String[] iaddrParts = iaddr.getHostAddress().split("\\.");
addresses.add(new InetSocketAddress(String.format("%s.%s.%s.255", iaddrParts[0], iaddrParts[1], iaddrParts[2]), port));
}
}
byte[] dummyPacket = new byte[] {0x0};
long prevPacketTime = System.currentTimeMillis();
socket.setSoTimeout(250);
while(true) {
try {
boolean hasActiveTrackers = false;
for (TrackerConnection tracker: trackers) {
if (tracker.sensors.get(0).getStatus() == TrackerStatus.OK) {
hasActiveTrackers = true;
break;
}
}
if (!hasActiveTrackers) {
long discoveryPacketTime = System.currentTimeMillis();
if ((discoveryPacketTime - prevPacketTime) >= 2000) {
for (SocketAddress addr: addresses) {
socket.send(new DatagramPacket(dummyPacket, dummyPacket.length, addr));
}
prevPacketTime = discoveryPacketTime;
}
}
DatagramPacket recieve = new DatagramPacket(rcvBuffer, rcvBuffer.length);
socket.receive(recieve);
bb.rewind();
TrackerConnection connection;
IMUTracker tracker = null;
synchronized(trackers) {
connection = trackersMap.get(recieve.getAddress());
}
if(connection != null)
connection.lastPacket = System.currentTimeMillis();
int packetId;
switch(packetId = bb.getInt()) {
case 0:
break;
case 3:
setUpNewSensor(recieve, bb);
break;
case 1: // PACKET_ROTATION
case 16: // PACKET_ROTATION_2
if(connection == null)
break;
bb.getLong();
buf.set(bb.getFloat(), bb.getFloat(), bb.getFloat(), bb.getFloat());
offset.mult(buf, buf);
if(packetId == 1) {
tracker = connection.sensors.get(0);
} else {
tracker = connection.sensors.get(1);
}
if(tracker == null)
break;
tracker.rotQuaternion.set(buf);
tracker.dataTick();
break;
case 17: // PACKET_ROTATION_DATA
if(connection == null)
break;
if(connection.isOwoTrack)
break;
bb.getLong();
int sensorId = bb.get() & 0xFF;
tracker = connection.sensors.get(sensorId);
if(tracker == null)
break;
int dataType = bb.get() & 0xFF;
buf.set(bb.getFloat(), bb.getFloat(), bb.getFloat(), bb.getFloat());
offset.mult(buf, buf);
int calibrationInfo = bb.get() & 0xFF;
switch(dataType) {
case 1: // DATA_TYPE_NORMAL
tracker.rotQuaternion.set(buf);
tracker.calibrationStatus = calibrationInfo;
tracker.dataTick();
break;
case 2: // DATA_TYPE_CORRECTION
tracker.rotMagQuaternion.set(buf);
tracker.magCalibrationStatus = calibrationInfo;
tracker.hasNewCorrectionData = true;
break;
}
break;
case 18: // PACKET_MAGENTOMETER_ACCURACY
if(connection == null)
break;
if(connection.isOwoTrack)
break;
bb.getLong();
sensorId = bb.get() & 0xFF;
tracker = connection.sensors.get(sensorId);
if(tracker == null)
break;
float accuracyInfo = bb.getFloat();
tracker.magnetometerAccuracy = accuracyInfo;
break;
case 2: // PACKET_GYRO
case 4: // PACKET_ACCEL
case 5: // PACKET_MAG
case 9: // PACKET_RAW_MAGENTOMETER
break; // None of these packets are used by SlimeVR trackers and are deprecated, use more generic PACKET_ROTATION_DATA
case 8: // PACKET_CONFIG
if(connection == null)
break;
if(connection.isOwoTrack)
break;
bb.getLong();
MPUTracker.ConfigurationData data = new MPUTracker.ConfigurationData(bb);
Consumer<String> dataConsumer = calibrationDataRequests.remove(connection.sensors.get(0));
if(dataConsumer != null) {
dataConsumer.accept(data.toTextMatrix());
}
break;
case 10: // PACKET_PING_PONG:
if(connection == null)
break;
if(connection.isOwoTrack)
break;
int pingId = bb.getInt();
if(connection.lastPingPacketId == pingId) {
for(int i = 0; i < connection.sensors.size(); ++i) {
tracker = connection.sensors.get(i);
tracker.ping = (int) (System.currentTimeMillis() - connection.lastPingPacketTime) / 2;
tracker.dataTick();
}
}
break;
case 11: // PACKET_SERIAL
if(connection == null)
break;
if(connection.isOwoTrack)
break;
tracker = connection.sensors.get(0);
bb.getLong();
int length = bb.getInt();
for(int i = 0; i < length; ++i) {
char ch = (char) bb.get();
if(ch == '\n') {
serialBuffer2.append('[').append(tracker.getName()).append("] ").append(tracker.serialBuffer);
System.out.println(serialBuffer2.toString());
serialBuffer2.setLength(0);
tracker.serialBuffer.setLength(0);
} else {
tracker.serialBuffer.append(ch);
}
}
break;
case 12: // PACKET_BATTERY_VOLTAGE
if(connection == null)
break;
tracker = connection.sensors.get(0);
bb.getLong();
tracker.setBatteryVoltage(bb.getFloat());
break;
case 13: // PACKET_TAP
if(connection == null)
break;
if(connection.isOwoTrack)
break;
bb.getLong();
sensorId = bb.get() & 0xFF;
tracker = connection.sensors.get(sensorId);
if(tracker == null)
break;
int tap = bb.get() & 0xFF;
BnoTap tapObj = new BnoTap(tap);
System.out.println("[TrackerServer] Tap packet received from " + tracker.getName() + "/" + sensorId + ": " + tapObj + " (b" + Integer.toBinaryString(tap) + ")");
break;
case 14: // PACKET_RESET_REASON
bb.getLong();
byte reason = bb.get();
System.out.println("[TrackerServer] Reset recieved from " + recieve.getSocketAddress() + ": " + reason);
if(connection == null)
break;
sensorId = bb.get() & 0xFF;
tracker = connection.sensors.get(sensorId);
if(tracker == null)
break;
tracker.setStatus(TrackerStatus.ERROR);
break;
case 15: // PACKET_SENSOR_INFO
if(connection == null)
break;
bb.getLong();
sensorId = bb.get() & 0xFF;
int sensorStatus = bb.get() & 0xFF;
if(sensorId > 0 && sensorStatus == 1) {
setUpAuxilarySensor(connection, sensorId);
}
bb.rewind();
bb.putInt(15);
bb.put((byte) sensorId);
bb.put((byte) sensorStatus);
socket.send(new DatagramPacket(rcvBuffer, bb.position(), connection.address));
System.out.println("[TrackerServer] Sensor info for " + connection.sensors.get(0).getName() + "/" + sensorId + ": " + sensorStatus);
break;
default:
System.out.println("[TrackerServer] Unknown data received: " + packetId + " from " + recieve.getSocketAddress());
break;
}
} catch(SocketTimeoutException e) {
} catch(Exception e) {
e.printStackTrace();
}
if(lastKeepup + 500 < System.currentTimeMillis()) {
lastKeepup = System.currentTimeMillis();
synchronized(trackers) {
for(int i = 0; i < trackers.size(); ++i) {
TrackerConnection conn = trackers.get(i);
socket.send(new DatagramPacket(KEEPUP_BUFFER, KEEPUP_BUFFER.length, conn.address));
if(conn.lastPacket + 1000 < System.currentTimeMillis()) {
Iterator<IMUTracker> iterator = conn.sensors.values().iterator();
while(iterator.hasNext()) {
IMUTracker tracker = iterator.next();
if(tracker.getStatus() == TrackerStatus.OK)
tracker.setStatus(TrackerStatus.DISCONNECTED);
}
} else {
Iterator<IMUTracker> iterator = conn.sensors.values().iterator();
while(iterator.hasNext()) {
IMUTracker tracker = iterator.next();
if(tracker.getStatus() == TrackerStatus.DISCONNECTED)
tracker.setStatus(TrackerStatus.OK);
}
}
IMUTracker tracker = conn.sensors.get(0);
if(tracker == null)
continue;
if(tracker.serialBuffer.length() > 0) {
if(tracker.lastSerialUpdate + 500L < System.currentTimeMillis()) {
serialBuffer2.append('[').append(tracker.getName()).append("] ").append(tracker.serialBuffer);
System.out.println(serialBuffer2.toString());
serialBuffer2.setLength(0);
tracker.serialBuffer.setLength(0);
}
}
if(conn.lastPingPacketTime + 500 < System.currentTimeMillis()) {
conn.lastPingPacketId = random.nextInt();
conn.lastPingPacketTime = System.currentTimeMillis();
bb.rewind();
bb.putInt(10);
bb.putInt(conn.lastPingPacketId);
socket.send(new DatagramPacket(rcvBuffer, bb.position(), conn.address));
}
}
}
}
}
} catch(Exception e) {
e.printStackTrace();
} finally {
Util.close(socket);
}
}
private class TrackerConnection {
Map<Integer, IMUTracker> sensors = new HashMap<>();
SocketAddress address;
public long lastPacket = System.currentTimeMillis();
public int lastPingPacketId = -1;
public long lastPingPacketTime = 0;
public boolean isOwoTrack = false;
public TrackerConnection(IMUTracker tracker, SocketAddress address) {
this.sensors.put(0, tracker);
this.address = address;
}
}
static {
try {
HANDSHAKE_BUFFER[0] = 3;
byte[] str = "Hey OVR =D 5".getBytes("ASCII");
System.arraycopy(str, 0, HANDSHAKE_BUFFER, 1, str.length);
} catch(UnsupportedEncodingException e) {
throw new AssertionError(e);
}
KEEPUP_BUFFER[3] = 1;
CALIBRATION_BUFFER[3] = 4;
CALIBRATION_BUFFER[4] = 1;
CALIBRATION_REQUEST_BUFFER[3] = 4;
CALIBRATION_REQUEST_BUFFER[4] = 2;
}
}

View File

@@ -0,0 +1,15 @@
package dev.slimevr.vr.trackers.udp;
public interface SensorSpecificPacket {
public int getSensorId();
/**
* Sensor with id 255 is "global" representing a whole device
* @param sensorId
* @return
*/
public static boolean isGlobal(int sensorId) {
return sensorId == 255;
}
}

View File

@@ -0,0 +1,44 @@
package dev.slimevr.vr.trackers.udp;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.Map;
import dev.slimevr.NetworkProtocol;
import dev.slimevr.vr.trackers.IMUTracker;
public class TrackerUDPConnection {
public Map<Integer, IMUTracker> sensors = new HashMap<>();
public SocketAddress address;
public InetAddress ipAddress;
public long lastPacket = System.currentTimeMillis();
public int lastPingPacketId = -1;
public long lastPingPacketTime = 0;
public String name;
public String descriptiveName;
public StringBuilder serialBuffer = new StringBuilder();
public long lastSerialUpdate = 0;
public long lastPacketNumber = -1;
public NetworkProtocol protocol = null;
public int firmwareBuild = 0;
public boolean timedOut = false;
public TrackerUDPConnection(SocketAddress address, InetAddress ipAddress) {
this.address = address;
this.ipAddress = ipAddress;
}
public boolean isNextPacket(long packetId) {
if(packetId != 0 && packetId <= lastPacketNumber)
return false;
lastPacketNumber = packetId;
return true;
}
@Override
public String toString() {
return "udp:/" + ipAddress;
}
}

View File

@@ -0,0 +1,434 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Consumer;
import org.apache.commons.lang3.ArrayUtils;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import dev.slimevr.NetworkProtocol;
import dev.slimevr.vr.trackers.IMUTracker;
import dev.slimevr.vr.trackers.ReferenceAdjustedTracker;
import dev.slimevr.vr.trackers.Tracker;
import dev.slimevr.vr.trackers.TrackerStatus;
import io.eiren.util.Util;
import io.eiren.util.collections.FastList;
import io.eiren.util.logging.LogManager;
/**
* Receives trackers data by UDP using extended owoTrack protocol.
*/
public class TrackersUDPServer extends Thread {
/**
* Change between IMU axes and OpenGL/SteamVR axes
*/
private static final Quaternion offset = new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X);
private final Quaternion buf = new Quaternion();
private final Random random = new Random();
private final List<TrackerUDPConnection> connections = new FastList<>();
private final Map<InetAddress, TrackerUDPConnection> connectionsByAddress = new HashMap<>();
private final Map<String, TrackerUDPConnection> connectionsByMAC = new HashMap<>();
private final Consumer<Tracker> trackersConsumer;
private final int port;
private final ArrayList<SocketAddress> broadcastAddresses = new ArrayList<>();
private final UDPProtocolParser parser = new UDPProtocolParser();
private final byte[] rcvBuffer = new byte[512];
private final ByteBuffer bb = ByteBuffer.wrap(rcvBuffer).order(ByteOrder.BIG_ENDIAN);
protected DatagramSocket socket = null;
protected long lastKeepup = System.currentTimeMillis();
public TrackersUDPServer(int port, String name, Consumer<Tracker> trackersConsumer) {
super(name);
this.port = port;
this.trackersConsumer = trackersConsumer;
try {
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
while(ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
// Ignore loopback, PPP, virtual and disabled devices
if(iface.isLoopback() || !iface.isUp() || iface.isPointToPoint() || iface.isVirtual()) {
continue;
}
Enumeration<InetAddress> iaddrs = iface.getInetAddresses();
while(iaddrs.hasMoreElements()) {
InetAddress iaddr = iaddrs.nextElement();
// Ignore IPv6 addresses
if(iaddr instanceof Inet6Address) {
continue;
}
String[] iaddrParts = iaddr.getHostAddress().split("\\.");
broadcastAddresses.add(new InetSocketAddress(String.format("%s.%s.%s.255", iaddrParts[0], iaddrParts[1], iaddrParts[2]), port));
}
}
} catch(Exception e) {
LogManager.log.severe("[TrackerServer] Can't enumerate network interfaces", e);
}
}
private void setUpNewConnection(DatagramPacket handshakePacket, UDPPacket3Handshake handshake) throws IOException {
LogManager.log.info("[TrackerServer] Handshake received from " + handshakePacket.getAddress() + ":" + handshakePacket.getPort());
InetAddress addr = handshakePacket.getAddress();
TrackerUDPConnection connection;
synchronized(connections) {
connection = connectionsByAddress.get(addr);
}
if(connection == null) {
connection = new TrackerUDPConnection(handshakePacket.getSocketAddress(), addr);
connection.firmwareBuild = handshake.firmwareBuild;
if(handshake.firmware == null || handshake.firmware.length() == 0) {
// Only old owoTrack doesn't report firmware and have different packet IDs with SlimeVR
connection.protocol = NetworkProtocol.OWO_LEGACY;
} else {
connection.protocol = NetworkProtocol.SLIMEVR_RAW;
}
connection.name = handshake.macString != null ? "udp://" + handshake.macString : "udp:/" + handshakePacket.getAddress().toString();
connection.descriptiveName = "udp:/" + handshakePacket.getAddress().toString();
int i = 0;
synchronized(connections) {
if(handshake.macString != null && connectionsByMAC.containsKey(handshake.macString)) {
TrackerUDPConnection previousConnection = connectionsByMAC.get(handshake.macString);
i = connections.indexOf(previousConnection);
connectionsByAddress.remove(previousConnection.ipAddress);
previousConnection.lastPacketNumber = 0;
previousConnection.ipAddress = addr;
previousConnection.address = handshakePacket.getSocketAddress();
previousConnection.name = connection.name;
previousConnection.descriptiveName = connection.descriptiveName;
connectionsByAddress.put(addr, previousConnection);
LogManager.log.info("[TrackerServer] Tracker " + i + " handed over to address " + handshakePacket.getSocketAddress() + ". Board type: " + handshake.boardType + ", imu type: " + handshake.imuType + ", firmware: " + handshake.firmware + " (" + connection.firmwareBuild + "), mac: " + handshake.macString + ", name: " + previousConnection.name);
} else {
i = connections.size();
connections.add(connection);
connectionsByAddress.put(addr, connection);
if(handshake.macString != null) {
connectionsByMAC.put(handshake.macString, connection);
}
LogManager.log.info("[TrackerServer] Tracker " + i + " added with address " + handshakePacket.getSocketAddress() + ". Board type: " + handshake.boardType + ", imu type: " + handshake.imuType + ", firmware: " + handshake.firmware + " (" + connection.firmwareBuild + "), mac: " + handshake.macString + ", name: " + connection.name);
}
}
if(connection.protocol == NetworkProtocol.OWO_LEGACY || connection.firmwareBuild < 9) {
// Set up new sensor for older firmware
// Firmware after 7 should send sensor status packet and sensor will be created when it's received
setUpSensor(connection, 0, handshake.imuType, 1);
}
}
bb.limit(bb.capacity());
bb.rewind();
parser.writeHandshakeResponse(bb, connection);
socket.send(new DatagramPacket(rcvBuffer, bb.position(), connection.address));
}
private void setUpSensor(TrackerUDPConnection connection, int trackerId, int sensorType, int sensorStatus) throws IOException {
LogManager.log.info("[TrackerServer] Sensor " + trackerId + " for " + connection.name + " status: " + sensorStatus);
IMUTracker imu = connection.sensors.get(trackerId);
if(imu == null) {
imu = new IMUTracker(Tracker.getNextLocalTrackerId(), connection.name + "/" + trackerId, connection.descriptiveName + "/" + trackerId, this);
connection.sensors.put(trackerId, imu);
ReferenceAdjustedTracker<IMUTracker> adjustedTracker = new ReferenceAdjustedTracker<>(imu);
trackersConsumer.accept(adjustedTracker);
LogManager.log.info("[TrackerServer] Added sensor " + trackerId + " for " + connection.name + ", type " + sensorType);
}
TrackerStatus status = UDPPacket15SensorInfo.getStatus(sensorStatus);
if(status != null)
imu.setStatus(status);
}
@Override
public void run() {
StringBuilder serialBuffer2 = new StringBuilder();
try {
socket = new DatagramSocket(port);
long prevPacketTime = System.currentTimeMillis();
socket.setSoTimeout(250);
while(true) {
DatagramPacket received = null;
try {
boolean hasActiveTrackers = false;
for(TrackerUDPConnection tracker : connections) {
if(tracker.sensors.size() > 0) {
hasActiveTrackers = true;
break;
}
}
if(!hasActiveTrackers) {
long discoveryPacketTime = System.currentTimeMillis();
if((discoveryPacketTime - prevPacketTime) >= 2000) {
for(SocketAddress addr : broadcastAddresses) {
bb.limit(bb.capacity());
bb.rewind();
parser.write(bb, null, new UDPPacket0Heartbeat());
socket.send(new DatagramPacket(rcvBuffer, bb.position(), addr));
}
prevPacketTime = discoveryPacketTime;
}
}
received = new DatagramPacket(rcvBuffer, rcvBuffer.length);
socket.receive(received);
bb.limit(received.getLength());
bb.rewind();
TrackerUDPConnection connection;
synchronized(connections) {
connection = connectionsByAddress.get(received.getAddress());
}
UDPPacket packet = parser.parse(bb, connection);
if(packet != null) {
processPacket(received, packet, connection);
}
} catch(SocketTimeoutException e) {
} catch(Exception e) {
LogManager.log.warning("[TrackerServer] Error parsing packet " + packetToString(received), e);
}
if(lastKeepup + 500 < System.currentTimeMillis()) {
lastKeepup = System.currentTimeMillis();
synchronized(connections) {
for(int i = 0; i < connections.size(); ++i) {
TrackerUDPConnection conn = connections.get(i);
bb.limit(bb.capacity());
bb.rewind();
parser.write(bb, conn, new UDPPacket1Heartbeat());
socket.send(new DatagramPacket(rcvBuffer, bb.position(), conn.address));
if(conn.lastPacket + 1000 < System.currentTimeMillis()) {
Iterator<IMUTracker> iterator = conn.sensors.values().iterator();
while(iterator.hasNext()) {
IMUTracker tracker = iterator.next();
if(tracker.getStatus() == TrackerStatus.OK)
tracker.setStatus(TrackerStatus.DISCONNECTED);
}
if(!conn.timedOut) {
conn.timedOut = true;
LogManager.log.info("[TrackerServer] Tracker timed out: " + conn);
}
} else {
conn.timedOut = false;
Iterator<IMUTracker> iterator = conn.sensors.values().iterator();
while(iterator.hasNext()) {
IMUTracker tracker = iterator.next();
if(tracker.getStatus() == TrackerStatus.DISCONNECTED)
tracker.setStatus(TrackerStatus.OK);
}
}
if(conn.serialBuffer.length() > 0) {
if(conn.lastSerialUpdate + 500L < System.currentTimeMillis()) {
serialBuffer2.append('[').append(conn.name).append("] ").append(conn.serialBuffer);
System.out.println(serialBuffer2.toString());
serialBuffer2.setLength(0);
conn.serialBuffer.setLength(0);
}
}
if(conn.lastPingPacketTime + 500 < System.currentTimeMillis()) {
conn.lastPingPacketId = random.nextInt();
conn.lastPingPacketTime = System.currentTimeMillis();
bb.limit(bb.capacity());
bb.rewind();
bb.putInt(10);
bb.putLong(0);
bb.putInt(conn.lastPingPacketId);
socket.send(new DatagramPacket(rcvBuffer, bb.position(), conn.address));
}
}
}
}
}
} catch(Exception e) {
e.printStackTrace();
} finally {
Util.close(socket);
}
}
protected void processPacket(DatagramPacket received, UDPPacket packet, TrackerUDPConnection connection) throws IOException {
IMUTracker tracker = null;
switch(packet.getPacketId()) {
case UDPProtocolParser.PACKET_HEARTBEAT:
break;
case UDPProtocolParser.PACKET_HANDSHAKE:
setUpNewConnection(received, (UDPPacket3Handshake) packet);
break;
case UDPProtocolParser.PACKET_ROTATION:
case UDPProtocolParser.PACKET_ROTATION_2:
if(connection == null)
break;
UDPPacket1Rotation rotationPacket = (UDPPacket1Rotation) packet;
buf.set(rotationPacket.rotation);
offset.mult(buf, buf);
tracker = connection.sensors.get(rotationPacket.getSensorId());
if(tracker == null)
break;
tracker.rotQuaternion.set(buf);
tracker.dataTick();
break;
case UDPProtocolParser.PACKET_ROTATION_DATA:
if(connection == null)
break;
UDPPacket17RotationData rotationData = (UDPPacket17RotationData) packet;
tracker = connection.sensors.get(rotationData.getSensorId());
if(tracker == null)
break;
buf.set(rotationData.rotation);
offset.mult(buf, buf);
switch(rotationData.dataType) {
case UDPPacket17RotationData.DATA_TYPE_NORMAL:
tracker.rotQuaternion.set(buf);
tracker.calibrationStatus = rotationData.calibrationInfo;
tracker.dataTick();
break;
case UDPPacket17RotationData.DATA_TYPE_CORRECTION:
tracker.rotMagQuaternion.set(buf);
tracker.magCalibrationStatus = rotationData.calibrationInfo;
tracker.hasNewCorrectionData = true;
break;
}
break;
case UDPProtocolParser.PACKET_MAGNETOMETER_ACCURACY:
if(connection == null)
break;
UDPPacket18MagnetometerAccuracy magAccuracy = (UDPPacket18MagnetometerAccuracy) packet;
tracker = connection.sensors.get(magAccuracy.getSensorId());
if(tracker == null)
break;
tracker.magnetometerAccuracy = magAccuracy.accuracyInfo;
break;
case 2: // PACKET_GYRO
case 4: // PACKET_ACCEL
case 5: // PACKET_MAG
case 9: // PACKET_RAW_MAGENTOMETER
break; // None of these packets are used by SlimeVR trackers and are deprecated, use more generic PACKET_ROTATION_DATA
case 8: // PACKET_CONFIG
if(connection == null)
break;
break;
case UDPProtocolParser.PACKET_PING_PONG: // PACKET_PING_PONG:
if(connection == null)
break;
UDPPacket10PingPong ping = (UDPPacket10PingPong) packet;
if(connection.lastPingPacketId == ping.pingId) {
for(int i = 0; i < connection.sensors.size(); ++i) {
tracker = connection.sensors.get(i);
tracker.ping = (int) (System.currentTimeMillis() - connection.lastPingPacketTime) / 2;
tracker.dataTick();
}
} else {
LogManager.log.debug("[TrackerServer] Wrong ping id " + ping.pingId + " != " + connection.lastPingPacketId);
}
break;
case UDPProtocolParser.PACKET_SERIAL:
if(connection == null)
break;
UDPPacket11Serial serial = (UDPPacket11Serial) packet;
System.out.println("[" + connection.name + "] " + serial.serial);
break;
case UDPProtocolParser.PACKET_BATTERY_LEVEL:
if(connection == null)
break;
UDPPacket12BatteryLevel battery = (UDPPacket12BatteryLevel) packet;
if(connection.sensors.size() > 0) {
Collection<IMUTracker> trackers = connection.sensors.values();
Iterator<IMUTracker> iterator = trackers.iterator();
while(iterator.hasNext()) {
IMUTracker tr = iterator.next();
tr.setBatteryVoltage(battery.voltage);
tr.setBatteryLevel(battery.level * 100);
}
}
break;
case UDPProtocolParser.PACKET_TAP:
if(connection == null)
break;
UDPPacket13Tap tap = (UDPPacket13Tap) packet;
tracker = connection.sensors.get(tap.getSensorId());
if(tracker == null)
break;
LogManager.log.info("[TrackerServer] Tap packet received from " + tracker.getName() + ": " + tap.tap);
break;
case UDPProtocolParser.PACKET_ERROR:
UDPPacket14Error error = (UDPPacket14Error) packet;
LogManager.log.severe("[TrackerServer] Error received from " + received.getSocketAddress() + ": " + error.errorNumber);
if(connection == null)
break;
tracker = connection.sensors.get(error.getSensorId());
if(tracker == null)
break;
tracker.setStatus(TrackerStatus.ERROR);
break;
case UDPProtocolParser.PACKET_SENSOR_INFO:
if(connection == null)
break;
UDPPacket15SensorInfo info = (UDPPacket15SensorInfo) packet;
setUpSensor(connection, info.getSensorId(), info.sensorType, info.sensorStatus);
// Send ack
bb.limit(bb.capacity());
bb.rewind();
parser.writeSensorInfoResponse(bb, connection, info);
socket.send(new DatagramPacket(rcvBuffer, bb.position(), connection.address));
LogManager.log.info("[TrackerServer] Sensor info for " + connection.descriptiveName + "/" + info.getSensorId() + ": " + info.sensorStatus);
break;
case UDPProtocolParser.PACKET_SIGNAL_STRENGTH:
if(connection == null)
break;
UDPPacket19SignalStrength signalStrength = (UDPPacket19SignalStrength) packet;
if(connection.sensors.size() > 0) {
Collection<IMUTracker> trackers = connection.sensors.values();
Iterator<IMUTracker> iterator = trackers.iterator();
while(iterator.hasNext()) {
IMUTracker tr = iterator.next();
tr.signalStrength = signalStrength.signalStrength;
}
}
break;
case UDPProtocolParser.PACKET_TEMPERATURE:
if(connection == null)
break;
UDPPacket20Temperature temp = (UDPPacket20Temperature) packet;
tracker = connection.sensors.get(temp.getSensorId());
if(tracker == null)
break;
tracker.temperature = temp.temperature;
break;
default:
LogManager.log.warning("[TrackerServer] Skipped packet " + packet);
break;
}
}
private static String packetToString(DatagramPacket packet) {
StringBuilder sb = new StringBuilder();
sb.append("DatagramPacket{");
sb.append(packet.getAddress().toString());
sb.append(packet.getPort());
sb.append(',');
sb.append(packet.getLength());
sb.append(',');
sb.append(ArrayUtils.toString(packet.getData()));
sb.append('}');
return sb.toString();
}
}

View File

@@ -0,0 +1,70 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public abstract class UDPPacket {
public abstract int getPacketId();
public abstract void readData(ByteBuffer buf) throws IOException;
public abstract void writeData(ByteBuffer buf) throws IOException;
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append('{');
sb.append(getPacketId());
if(this instanceof SensorSpecificPacket) {
sb.append(",sensor:");
sb.append(((SensorSpecificPacket) this).getSensorId());
}
sb.append('}');
return sb.toString();
}
/**
* Naively read null-terminated ASCII string from the byte buffer
* @param buf
* @return
* @throws IOException
*/
public static String readASCIIString(ByteBuffer buf) throws IOException {
StringBuilder sb = new StringBuilder();
while(true) {
char c = (char) (buf.get() & 0xFF);
if(c == 0)
break;
sb.append(c);
}
return sb.toString();
}
public static String readASCIIString(ByteBuffer buf, int length) throws IOException {
StringBuilder sb = new StringBuilder();
while(length-- > 0) {
char c = (char) (buf.get() & 0xFF);
if(c == 0)
break;
sb.append(c);
}
return sb.toString();
}
/**
* Naively write null-terminated ASCII string to byte buffer
* @param str
* @param buf
* @throws IOException
*/
public static void writeASCIIString(String str, ByteBuffer buf) throws IOException {
for(int i = 0; i < str.length(); ++i) {
char c = str.charAt(i);
buf.put((byte) (c & 0xFF));
}
buf.put((byte) 0);
}
}

View File

@@ -0,0 +1,25 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket0Heartbeat extends UDPPacket {
public UDPPacket0Heartbeat() {
}
@Override
public int getPacketId() {
return 0;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
// Empty packet
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Empty packet
}
}

View File

@@ -0,0 +1,32 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket10PingPong extends UDPPacket {
public int pingId;
public UDPPacket10PingPong() {
}
public UDPPacket10PingPong(int pingId) {
this.pingId = pingId;
}
@Override
public int getPacketId() {
return 10;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
pingId = buf.getInt();
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
buf.putInt(pingId);
}
}

View File

@@ -0,0 +1,34 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket11Serial extends UDPPacket {
public String serial;
public UDPPacket11Serial() {
}
@Override
public int getPacketId() {
return 11;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
int length = buf.getInt();
StringBuilder sb = new StringBuilder(length);
for(int i = 0; i < length; ++i) {
char ch = (char) buf.get();
sb.append(ch);
}
serial = sb.toString();
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
}

View File

@@ -0,0 +1,34 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket12BatteryLevel extends UDPPacket {
public float voltage;
public float level;
public UDPPacket12BatteryLevel() {
}
@Override
public int getPacketId() {
return 12;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
voltage = buf.getFloat();
if(buf.remaining() > 3) {
level = buf.getFloat();
} else {
level = voltage;
voltage = 0.0f;
}
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
}

View File

@@ -0,0 +1,36 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
import dev.slimevr.vr.trackers.SensorTap;
public class UDPPacket13Tap extends UDPPacket implements SensorSpecificPacket {
public int sensorId;
public SensorTap tap;
public UDPPacket13Tap() {
}
@Override
public int getPacketId() {
return 13;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
sensorId = buf.get() & 0xFF;
tap = new SensorTap(buf.get() & 0xFF);
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
}

View File

@@ -0,0 +1,35 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket14Error extends UDPPacket implements SensorSpecificPacket {
public int sensorId;
public int errorNumber;
public UDPPacket14Error() {
}
@Override
public int getPacketId() {
return 14;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
sensorId = buf.get() & 0xFF;
errorNumber = buf.get() & 0xFF;
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
}

View File

@@ -0,0 +1,52 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
import dev.slimevr.vr.trackers.TrackerStatus;
public class UDPPacket15SensorInfo extends UDPPacket implements SensorSpecificPacket {
public int sensorId;
public int sensorStatus;
public int sensorType;
public UDPPacket15SensorInfo() {
}
@Override
public int getPacketId() {
return 15;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
sensorId = buf.get() & 0xFF;
sensorStatus = buf.get() & 0xFF;
if(buf.remaining() > 0)
sensorType = buf.get() & 0xFF;
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
public static TrackerStatus getStatus(int sensorStatus) {
switch(sensorStatus) {
case 0:
return TrackerStatus.DISCONNECTED;
case 1:
return TrackerStatus.OK;
case 2:
return TrackerStatus.ERROR;
}
return null;
}
}

View File

@@ -0,0 +1,17 @@
package dev.slimevr.vr.trackers.udp;
public class UDPPacket16Rotation2 extends UDPPacket1Rotation {
public UDPPacket16Rotation2() {
}
@Override
public int getPacketId() {
return 16;
}
@Override
public int getSensorId() {
return 1;
}
}

View File

@@ -0,0 +1,43 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.jme3.math.Quaternion;
public class UDPPacket17RotationData extends UDPPacket implements SensorSpecificPacket {
public static final int DATA_TYPE_NORMAL = 1;
public static final int DATA_TYPE_CORRECTION = 2;
public int sensorId;
public int dataType;
public final Quaternion rotation = new Quaternion();
public int calibrationInfo;
public UDPPacket17RotationData() {
}
@Override
public int getPacketId() {
return 17;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
sensorId = buf.get() & 0xFF;
dataType = buf.get() & 0xFF;
rotation.set(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat());
calibrationInfo = buf.get() & 0xFF;
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
}

View File

@@ -0,0 +1,34 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket18MagnetometerAccuracy extends UDPPacket implements SensorSpecificPacket {
public int sensorId;
public float accuracyInfo;
public UDPPacket18MagnetometerAccuracy() {
}
@Override
public int getPacketId() {
return 18;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
sensorId = buf.get() & 0xFF;
accuracyInfo = buf.getFloat();
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
}

View File

@@ -0,0 +1,34 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket19SignalStrength extends UDPPacket implements SensorSpecificPacket {
public int sensorId;
public int signalStrength;
public UDPPacket19SignalStrength() {
}
@Override
public int getPacketId() {
return 19;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
sensorId = buf.get() & 0xFF;
signalStrength = buf.get();
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
}

View File

@@ -0,0 +1,9 @@
package dev.slimevr.vr.trackers.udp;
public class UDPPacket1Heartbeat extends UDPPacket0Heartbeat {
@Override
public int getPacketId() {
return 1;
}
}

View File

@@ -0,0 +1,34 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
import com.jme3.math.Quaternion;
public class UDPPacket1Rotation extends UDPPacket implements SensorSpecificPacket {
public final Quaternion rotation = new Quaternion();
public UDPPacket1Rotation() {
}
@Override
public int getPacketId() {
return 1;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
rotation.set(buf.getFloat(), buf.getFloat(), buf.getFloat(), buf.getFloat());
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return 0;
}
}

View File

@@ -0,0 +1,31 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket200ProtocolChange extends UDPPacket {
public int targetProtocol;
public int targetProtocolVersion;
public UDPPacket200ProtocolChange() {
}
@Override
public int getPacketId() {
return 200;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
targetProtocol = buf.get() & 0xFF;
targetProtocolVersion = buf.get() & 0xFF;
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
buf.put((byte) targetProtocol);
buf.put((byte) targetProtocolVersion);
}
}

View File

@@ -0,0 +1,35 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket20Temperature extends UDPPacket implements SensorSpecificPacket {
public int sensorId;
public float temperature;
public UDPPacket20Temperature() {
}
@Override
public int getPacketId() {
return 20;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
sensorId = buf.get() & 0xFF;
temperature = buf.getFloat();
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
}
@Override
public int getSensorId() {
return sensorId;
}
}

View File

@@ -0,0 +1,59 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.nio.ByteBuffer;
public class UDPPacket3Handshake extends UDPPacket {
public int boardType;
public int imuType;
public int mcuType;
public int firmwareBuild;
public String firmware;
public String macString;
public UDPPacket3Handshake() {
}
@Override
public int getPacketId() {
return 3;
}
@Override
public void readData(ByteBuffer buf) throws IOException {
if(buf.remaining() > 0) {
byte[] mac = new byte[6];
if(buf.remaining() > 3)
boardType = buf.getInt();
if(buf.remaining() > 3)
imuType = buf.getInt();
if(buf.remaining() > 3)
mcuType = buf.getInt(); // MCU TYPE
if(buf.remaining() > 11) {
buf.getInt(); // IMU info
buf.getInt();
buf.getInt();
}
if(buf.remaining() > 3)
firmwareBuild = buf.getInt();
int length = 0;
if(buf.remaining() > 0)
length = buf.get(); // firmware version length is 1 longer than that because it's nul-terminated
firmware = readASCIIString(buf, length);
if(buf.remaining() >= mac.length) {
buf.get(mac);
macString = String.format("%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
if(macString.equals("00:00:00:00:00:00"))
macString = null;
}
}
}
@Override
public void writeData(ByteBuffer buf) throws IOException {
// Never sent back in current protocol
// Handshake for RAW SlimeVR and legacy owoTrack has different packet id byte order from normal packets
// So it's handled by raw protocol call
}
}

View File

@@ -0,0 +1,120 @@
package dev.slimevr.vr.trackers.udp;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import io.eiren.util.logging.LogManager;
public class UDPProtocolParser {
public static final int PACKET_HEARTBEAT = 0;
public static final int PACKET_ROTATION = 1; // Deprecated
//public static final int PACKET_GYRO = 2; // Deprecated
public static final int PACKET_HANDSHAKE = 3;
//public static final int PACKET_ACCEL = 4; // Not parsed by server
//public static final int PACKET_MAG = 5; // Deprecated
//public static final int PACKET_RAW_CALIBRATION_DATA = 6; // Not parsed by server
//public static final int PACKET_CALIBRATION_FINISHED = 7; // Not parsed by server
//public static final int PACKET_CONFIG = 8; // Not parsed by server
//public static final int PACKET_RAW_MAGNETOMETER = 9 // Deprecated
public static final int PACKET_PING_PONG = 10;
public static final int PACKET_SERIAL = 11;
public static final int PACKET_BATTERY_LEVEL = 12;
public static final int PACKET_TAP = 13;
public static final int PACKET_ERROR = 14;
public static final int PACKET_SENSOR_INFO = 15;
public static final int PACKET_ROTATION_2 = 16; // Deprecated
public static final int PACKET_ROTATION_DATA = 17;
public static final int PACKET_MAGNETOMETER_ACCURACY = 18;
public static final int PACKET_SIGNAL_STRENGTH = 19;
public static final int PACKET_TEMPERATURE = 20;
public static final int PACKET_PROTOCOL_CHANGE = 200;
private static final byte[] HANDSHAKE_BUFFER = new byte[64];
public UDPProtocolParser() {
}
public UDPPacket parse(ByteBuffer buf, TrackerUDPConnection connection) throws IOException {
int packetId = buf.getInt();
long packetNumber = buf.getLong();
if(connection != null) {
if(!connection.isNextPacket(packetNumber)) {
// Skip packet because it's not next
throw new IOException("Out of order packet received: id " + packetId + ", number " + packetNumber + ", last " + connection.lastPacketNumber + ", from " + connection);
}
connection.lastPacket = System.currentTimeMillis();
}
UDPPacket newPacket = getNewPacket(packetId);
if(newPacket != null) {
newPacket.readData(buf);
} else {
//LogManager.log.debug("[UDPProtocolParser] Skipped packet id " + packetId + " from " + connection);
}
return newPacket;
}
public void write(ByteBuffer buf, TrackerUDPConnection connection, UDPPacket packet) throws IOException {
buf.putInt(packet.getPacketId());
buf.putLong(0); // Packet number is always 0 when sending data to trackers
packet.writeData(buf);
}
public void writeHandshakeResponse(ByteBuffer buf, TrackerUDPConnection connection) throws IOException {
buf.put(HANDSHAKE_BUFFER);
}
public void writeSensorInfoResponse(ByteBuffer buf, TrackerUDPConnection connection, UDPPacket15SensorInfo packet) throws IOException {
buf.putInt(packet.getPacketId());
buf.put((byte) packet.sensorId);
buf.put((byte) packet.sensorStatus);
}
protected UDPPacket getNewPacket(int packetId) {
switch(packetId) {
case PACKET_HEARTBEAT:
return new UDPPacket0Heartbeat();
case PACKET_ROTATION:
return new UDPPacket1Rotation();
case PACKET_HANDSHAKE:
return new UDPPacket3Handshake();
case PACKET_PING_PONG:
return new UDPPacket10PingPong();
case PACKET_SERIAL:
return new UDPPacket11Serial();
case PACKET_BATTERY_LEVEL:
return new UDPPacket12BatteryLevel();
case PACKET_TAP:
return new UDPPacket13Tap();
case PACKET_ERROR:
return new UDPPacket14Error();
case PACKET_SENSOR_INFO:
return new UDPPacket15SensorInfo();
case PACKET_ROTATION_2:
return new UDPPacket16Rotation2();
case PACKET_ROTATION_DATA:
return new UDPPacket17RotationData();
case PACKET_MAGNETOMETER_ACCURACY:
return new UDPPacket18MagnetometerAccuracy();
case PACKET_SIGNAL_STRENGTH:
return new UDPPacket19SignalStrength();
case PACKET_TEMPERATURE:
return new UDPPacket20Temperature();
case PACKET_PROTOCOL_CHANGE:
return new UDPPacket200ProtocolChange();
}
return null;
}
static {
try {
HANDSHAKE_BUFFER[0] = 3;
byte[] str = "Hey OVR =D 5".getBytes("ASCII");
System.arraycopy(str, 0, HANDSHAKE_BUFFER, 1, str.length);
} catch(UnsupportedEncodingException e) {
throw new AssertionError(e);
}
}
}