mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c21caa76d5 | ||
|
|
4073d8fc32 | ||
|
|
f8b7be8572 | ||
|
|
d6ab811de0 | ||
|
|
9fa6722f2b | ||
|
|
c51204e9cd | ||
|
|
74794d8610 | ||
|
|
16978f5acd | ||
|
|
71e24d0cb9 | ||
|
|
9a45f99b0e | ||
|
|
7f829f56a3 | ||
|
|
347531f4fe | ||
|
|
a1c33a0852 | ||
|
|
9cd441654e | ||
|
|
88c866a735 | ||
|
|
b11492c3f3 | ||
|
|
52e30b6323 | ||
|
|
1ebad806f9 | ||
|
|
51129d3b5d | ||
|
|
379e1cdcf0 | ||
|
|
0a8f76cfd4 | ||
|
|
a382698c32 | ||
|
|
718f1d02c6 | ||
|
|
542de22550 | ||
|
|
327d458f00 | ||
|
|
5f206dd12e | ||
|
|
e18ce338e9 | ||
|
|
51e6255e9d | ||
|
|
7fa7e6c2cc | ||
|
|
710d154817 | ||
|
|
f354a10a81 | ||
|
|
8bb8135f41 | ||
|
|
e7b9968519 | ||
|
|
bfc58d51f2 | ||
|
|
c158022da5 | ||
|
|
9e010b0026 | ||
|
|
a085b09e07 | ||
|
|
37da4ab7fe | ||
|
|
c5945d784b | ||
|
|
16ca08446b | ||
|
|
b487350714 | ||
|
|
753b12b49e | ||
|
|
0d90cf9c20 | ||
|
|
658fd2916d | ||
|
|
ed4ea675fb | ||
|
|
2746fd7a67 | ||
|
|
a6b92c60b0 | ||
|
|
d4d36a65ec | ||
|
|
97df8ee12f | ||
|
|
2ab637b4e8 | ||
|
|
9a821b051f | ||
|
|
e2f09fc93d | ||
|
|
891d8e0468 | ||
|
|
494e31e41f | ||
|
|
b369ae6a2a | ||
|
|
5c22ef0192 | ||
|
|
d99cbb9c85 | ||
|
|
4f14f01830 | ||
|
|
930b5c701a | ||
|
|
bd9e2c47a3 | ||
|
|
53ca2cf881 | ||
|
|
55e17e7625 | ||
|
|
13b37aa2a9 | ||
|
|
fe4dde69ea | ||
|
|
0268a5a3ec | ||
|
|
4bddb529d4 | ||
|
|
435f5d1751 | ||
|
|
af8ce60dbe | ||
|
|
25f53232cd | ||
|
|
012cb518b3 | ||
|
|
2d1ffbc5b0 | ||
|
|
c88a6802a9 | ||
|
|
f5d608ac6a | ||
|
|
5d49bbfb29 | ||
|
|
5ce520a316 | ||
|
|
98c2c6e202 | ||
|
|
a2fc809d71 | ||
|
|
eb302aaef1 | ||
|
|
3b354f103a | ||
|
|
03c24a5d39 | ||
|
|
a8f13bb570 | ||
|
|
f8e35e0a72 | ||
|
|
27c153f5d3 | ||
|
|
82fdedfa14 | ||
|
|
f5bfbb13e2 | ||
|
|
80de578334 | ||
|
|
3b0acbe406 | ||
|
|
1062361612 | ||
|
|
7d81fe6f92 | ||
|
|
0285eca613 | ||
|
|
b0aea9ba89 | ||
|
|
b98eafb66f | ||
|
|
566df6793c | ||
|
|
4949e0a7f3 | ||
|
|
572dcdf1bb | ||
|
|
80ce825494 | ||
|
|
bdc3b1971c | ||
|
|
cee400a4c6 | ||
|
|
e58706d212 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -6,7 +6,12 @@ build
|
||||
|
||||
/bin/
|
||||
|
||||
# Ignore .idea
|
||||
.idea
|
||||
|
||||
# Syncthing ignore file
|
||||
.stignore
|
||||
|
||||
MagnetoLib.dll
|
||||
MagnetoLib.dll
|
||||
vrconfig.yml
|
||||
*.log
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
37
build.gradle
37
build.gradle
@@ -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')
|
||||
}
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
269
gradlew
vendored
269
gradlew
vendored
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -17,78 +17,113 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@@ -105,79 +140,95 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@@ -54,7 +54,7 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
@@ -64,28 +64,14 @@ echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
@@ -1,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"
|
||||
|
||||
Submodule slime-java-commons updated: 35f5a78c20...a8e49ba963
@@ -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) {
|
||||
|
||||
9
src/main/java/dev/slimevr/NetworkProtocol.java
Normal file
9
src/main/java/dev/slimevr/NetworkProtocol.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package dev.slimevr;
|
||||
|
||||
public enum NetworkProtocol {
|
||||
|
||||
OWO_LEGACY,
|
||||
SLIMEVR_RAW,
|
||||
SLIMEVR_FLATBUFFER,
|
||||
SLIMEVR_WEBSOCKET;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
7
src/main/java/dev/slimevr/bridge/PipeState.java
Normal file
7
src/main/java/dev/slimevr/bridge/PipeState.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
public enum PipeState {
|
||||
CREATED,
|
||||
OPEN,
|
||||
ERROR;
|
||||
}
|
||||
@@ -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()));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}});
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
});
|
||||
}});
|
||||
|
||||
@@ -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");
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
46
src/main/java/dev/slimevr/posestreamer/TickPoseStreamer.java
Normal file
46
src/main/java/dev/slimevr/posestreamer/TickPoseStreamer.java
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -7,5 +7,7 @@ public enum ComputedHumanPoseTrackerPosition {
|
||||
LEFT_FOOT,
|
||||
RIGHT_FOOT,
|
||||
LEFT_KNEE,
|
||||
RIGHT_KNEE;
|
||||
RIGHT_KNEE,
|
||||
LEFT_ELBOW,
|
||||
RIGHT_ELBOW;
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -11,6 +11,9 @@ public abstract class HumanSkeleton {
|
||||
|
||||
@ThreadSafe
|
||||
public abstract TransformNode getRootNode();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract TransformNode[] getAllNodes();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract SkeletonConfig getSkeletonConfig();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.";
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.jme3.math.Quaternion;
|
||||
|
||||
public enum TrackerMountingRotation {
|
||||
|
||||
FORWARD(180),
|
||||
FRONT(180),
|
||||
LEFT(90),
|
||||
BACK(0),
|
||||
RIGHT(-90);
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
434
src/main/java/dev/slimevr/vr/trackers/udp/TrackersUDPServer.java
Normal file
434
src/main/java/dev/slimevr/vr/trackers/udp/TrackersUDPServer.java
Normal 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();
|
||||
}
|
||||
}
|
||||
70
src/main/java/dev/slimevr/vr/trackers/udp/UDPPacket.java
Normal file
70
src/main/java/dev/slimevr/vr/trackers/udp/UDPPacket.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package dev.slimevr.vr.trackers.udp;
|
||||
|
||||
public class UDPPacket1Heartbeat extends UDPPacket0Heartbeat {
|
||||
|
||||
@Override
|
||||
public int getPacketId() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
120
src/main/java/dev/slimevr/vr/trackers/udp/UDPProtocolParser.java
Normal file
120
src/main/java/dev/slimevr/vr/trackers/udp/UDPProtocolParser.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user