mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f14f01830 | ||
|
|
930b5c701a | ||
|
|
bd9e2c47a3 | ||
|
|
53ca2cf881 | ||
|
|
55e17e7625 | ||
|
|
13b37aa2a9 | ||
|
|
fe4dde69ea | ||
|
|
0268a5a3ec | ||
|
|
4bddb529d4 | ||
|
|
435f5d1751 | ||
|
|
af8ce60dbe | ||
|
|
25f53232cd | ||
|
|
012cb518b3 | ||
|
|
2d1ffbc5b0 | ||
|
|
c88a6802a9 | ||
|
|
f5d608ac6a | ||
|
|
5d49bbfb29 | ||
|
|
5ce520a316 | ||
|
|
98c2c6e202 | ||
|
|
a2fc809d71 | ||
|
|
eb302aaef1 | ||
|
|
3b354f103a | ||
|
|
03c24a5d39 | ||
|
|
a8f13bb570 | ||
|
|
f8e35e0a72 | ||
|
|
27c153f5d3 | ||
|
|
82fdedfa14 | ||
|
|
f5bfbb13e2 | ||
|
|
80de578334 | ||
|
|
3b0acbe406 | ||
|
|
1062361612 | ||
|
|
7d81fe6f92 | ||
|
|
0285eca613 | ||
|
|
b0aea9ba89 | ||
|
|
b98eafb66f | ||
|
|
566df6793c | ||
|
|
4949e0a7f3 | ||
|
|
572dcdf1bb | ||
|
|
80ce825494 | ||
|
|
bdc3b1971c | ||
|
|
cee400a4c6 | ||
|
|
e58706d212 | ||
|
|
ad03caa064 | ||
|
|
e2d6189547 | ||
|
|
7b15d242f7 | ||
|
|
b81458d034 | ||
|
|
0595422f69 | ||
|
|
8e58adb279 | ||
|
|
a5e4b4d8e2 | ||
|
|
be2c010b5a | ||
|
|
e107326fee | ||
|
|
4da54f6dec | ||
|
|
1a3a955e10 | ||
|
|
3f304f7275 | ||
|
|
0690d742c7 | ||
|
|
43bbd4b4dd | ||
|
|
77fa27a698 | ||
|
|
8991e4f9f8 | ||
|
|
c9740651ba | ||
|
|
473550ba07 | ||
|
|
a7cbe91e73 | ||
|
|
40281f68b9 | ||
|
|
d3049751ba | ||
|
|
68164756c2 | ||
|
|
91c0ddef28 | ||
|
|
0641ca1b7b | ||
|
|
e3d9eb6ac9 | ||
|
|
7eec89bd53 | ||
|
|
289a7f8313 | ||
|
|
f49b2556ae | ||
|
|
318c43077c | ||
|
|
5691b68166 | ||
|
|
0ea44f988c | ||
|
|
59e2f796eb | ||
|
|
f1a75a98d0 | ||
|
|
76ac3fcf55 | ||
|
|
6cc3c8e84b | ||
|
|
36907c3244 | ||
|
|
dfeb02c1a7 | ||
|
|
6adf5f4090 | ||
|
|
e44ce3fb0b | ||
|
|
76ab69e44e | ||
|
|
57d009df5c | ||
|
|
b4d07b0b7e | ||
|
|
da3afa6f8e | ||
|
|
ec1c491e93 | ||
|
|
baccb556e8 | ||
|
|
eedfa61d74 | ||
|
|
2a9225178f | ||
|
|
259190e478 | ||
|
|
24a0c3b136 | ||
|
|
f46f2bc913 | ||
|
|
77f048c48e | ||
|
|
4055d51758 | ||
|
|
21eff5e1ba | ||
|
|
34174b442f | ||
|
|
77d37ab2a7 | ||
|
|
350fdbce9d | ||
|
|
e19cec4d3e | ||
|
|
e56d7665ed | ||
|
|
a0e23bfbe9 | ||
|
|
b7dc33f79e | ||
|
|
d8c31eec81 | ||
|
|
e84ee760b1 | ||
|
|
6ba1cc6bdb | ||
|
|
1b5e534592 | ||
|
|
1a3e21007b | ||
|
|
55e11ffb5c |
8
.github/workflows/gradle.yml
vendored
8
.github/workflows/gradle.yml
vendored
@@ -10,12 +10,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2.1.0
|
||||
uses: actions/setup-java@v2.4.0
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
@@ -31,12 +31,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
- uses: actions/checkout@v2.4.0
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2.1.0
|
||||
uses: actions/setup-java@v2.4.0
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -75,5 +75,5 @@ shadowJar {
|
||||
archiveVersion.set('')
|
||||
}
|
||||
application {
|
||||
mainClassName = 'io.eiren.vr.Main'
|
||||
mainClassName = 'dev.slimevr.Main'
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1 +1,19 @@
|
||||
@java -Xmx512M -jar slimevr.jar
|
||||
@echo off
|
||||
setlocal enableextensions
|
||||
cd /d "%~dp0"
|
||||
|
||||
where java >nul 2>&1
|
||||
if %errorlevel% EQU 0 (
|
||||
java -Xmx512M -jar slimevr.jar
|
||||
) else (
|
||||
echo Java was not found in your system.
|
||||
echo.
|
||||
echo Either use SlimeVR Installer to install the server by following this link:
|
||||
echo https://github.com/SlimeVR/SlimeVR-Installer/releases/latest/download/slimevr_web_installer.exe
|
||||
echo.
|
||||
echo Or download Java 11 by following this link:
|
||||
echo https://adoptium.net/releases.html?variant=openjdk11^&jvmVariant=hotspot
|
||||
)
|
||||
if %errorlevel% NEQ 0 (
|
||||
pause
|
||||
)
|
||||
Submodule slime-java-commons updated: 35f5a78c20...cd6fd53324
@@ -1,18 +1,21 @@
|
||||
package io.eiren.vr;
|
||||
package dev.slimevr;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import org.apache.commons.lang3.JavaVersion;
|
||||
import org.apache.commons.lang3.SystemUtils;
|
||||
|
||||
import dev.slimevr.gui.Keybinding;
|
||||
import dev.slimevr.gui.VRServerGUI;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static String VERSION = "0.1.1";
|
||||
public static String VERSION = "0.1.4";
|
||||
|
||||
public static VRServer vrServer;
|
||||
|
||||
@@ -34,6 +37,16 @@ public class Main {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new ServerSocket(6969).close();
|
||||
new ServerSocket(35903).close();
|
||||
new ServerSocket(21110).close();
|
||||
} catch (IOException e) {
|
||||
LogManager.log.severe("SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running.");
|
||||
JOptionPane.showMessageDialog(null, "SlimeVR start-up error! Required ports are busy. Make sure there is no other instance of SlimeVR Server running.", "SlimeVR: Ports are busy", JOptionPane.ERROR_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
vrServer = new VRServer();
|
||||
vrServer.start();
|
||||
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;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr;
|
||||
package dev.slimevr;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -20,21 +20,21 @@ import dev.slimevr.bridge.NamedPipeBridge;
|
||||
import dev.slimevr.bridge.SteamVRPipeInputBridge;
|
||||
import dev.slimevr.bridge.VMCBridge;
|
||||
import dev.slimevr.bridge.WebSocketVRBridge;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.HumanPoseProcessor;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.trackers.HMDTracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerConfig;
|
||||
import dev.slimevr.vr.trackers.udp.TrackersUDPServer;
|
||||
import io.eiren.util.OperatingSystem;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.ann.ThreadSecure;
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.processor.HumanPoseProcessor;
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.TrackersUDPServer;
|
||||
import io.eiren.yaml.YamlException;
|
||||
import io.eiren.yaml.YamlFile;
|
||||
import io.eiren.yaml.YamlNode;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerConfig;
|
||||
|
||||
public class VRServer extends Thread {
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
package dev.slimevr.autobone;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.poserecorder.PoseFrameSkeleton;
|
||||
import dev.slimevr.poserecorder.PoseFrameTracker;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.poserecorder.TrackerFrame;
|
||||
import dev.slimevr.poserecorder.TrackerFrameData;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import dev.slimevr.vr.processor.HumanPoseProcessor;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfig;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import dev.slimevr.vr.trackers.TrackerUtils;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
import io.eiren.vr.processor.HumanSkeletonWithLegs;
|
||||
import io.eiren.vr.processor.HumanSkeletonWithWaist;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerUtils;
|
||||
|
||||
public class AutoBone {
|
||||
|
||||
@@ -49,12 +54,17 @@ public class AutoBone {
|
||||
public float adjustRateDecay = 1.01f;
|
||||
|
||||
public float slideErrorFactor = 1.0f;
|
||||
public float offsetSlideErrorFactor = 0.0f;
|
||||
public float offsetErrorFactor = 0.0f;
|
||||
public float proportionErrorFactor = 0.2f;
|
||||
public float heightErrorFactor = 0.1f;
|
||||
public float positionErrorFactor = 0.0f;
|
||||
public float positionOffsetErrorFactor = 0.0f;
|
||||
|
||||
// TODO Needs much more work, probably going to rethink how the errors work to avoid this barely functional workaround @ButterscotchVanilla
|
||||
// For scaling distances, since smaller sizes will cause smaller distances
|
||||
//private float totalLengthBase = 2f;
|
||||
|
||||
// Human average is probably 1.1235 (SD 0.07)
|
||||
public float legBodyRatio = 1.1235f;
|
||||
// SD of 0.07, capture 68% within range
|
||||
@@ -62,63 +72,79 @@ public class AutoBone {
|
||||
|
||||
// Assume these to be approximately half
|
||||
public float kneeLegRatio = 0.5f;
|
||||
public float chestWaistRatio = 0.5f;
|
||||
public float chestTorsoRatio = 0.5f;
|
||||
|
||||
protected final VRServer server;
|
||||
|
||||
protected HumanSkeletonWithLegs skeleton = null;
|
||||
|
||||
// This is filled by reloadConfigValues()
|
||||
public final HashMap<String, Float> configs = new HashMap<String, Float>();
|
||||
public final HashMap<String, Float> staticConfigs = new HashMap<String, Float>();
|
||||
public final EnumMap<SkeletonConfigValue, Float> configs = new EnumMap<SkeletonConfigValue, Float>(SkeletonConfigValue.class);
|
||||
public final EnumMap<SkeletonConfigValue, Float> staticConfigs = new EnumMap<SkeletonConfigValue, Float>(SkeletonConfigValue.class);
|
||||
|
||||
public final FastList<String> heightConfigs = new FastList<String>(new String[]{"Neck", "Waist", "Legs length"
|
||||
});
|
||||
public final FastList<SkeletonConfigValue> heightConfigs = new FastList<SkeletonConfigValue>(new SkeletonConfigValue[]{
|
||||
SkeletonConfigValue.NECK, SkeletonConfigValue.TORSO, SkeletonConfigValue.LEGS_LENGTH});
|
||||
public final FastList<SkeletonConfigValue> lengthConfigs = new FastList<SkeletonConfigValue>(new SkeletonConfigValue[]{
|
||||
SkeletonConfigValue.HEAD, SkeletonConfigValue.NECK, SkeletonConfigValue.TORSO, SkeletonConfigValue.HIPS_WIDTH, SkeletonConfigValue.LEGS_LENGTH});
|
||||
|
||||
public AutoBone(VRServer server) {
|
||||
this.server = server;
|
||||
|
||||
reloadConfigValues();
|
||||
|
||||
server.addSkeletonUpdatedCallback(this::skeletonUpdated);
|
||||
}
|
||||
|
||||
public void reloadConfigValues() {
|
||||
reloadConfigValues(null);
|
||||
}
|
||||
|
||||
public void reloadConfigValues(TrackerFrame[] frame) {
|
||||
// Load waist configs
|
||||
staticConfigs.put("Head", server.config.getFloat("body.headShift", HumanSkeletonWithWaist.HEAD_SHIFT_DEFAULT));
|
||||
staticConfigs.put("Neck", server.config.getFloat("body.neckLength", HumanSkeletonWithWaist.NECK_LENGTH_DEFAULT));
|
||||
configs.put("Waist", server.config.getFloat("body.waistDistance", 0.85f));
|
||||
|
||||
if(server.config.getBoolean("autobone.forceChestTracker", false) || (frame != null && TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.CHEST) != null) || TrackerUtils.findTrackerForBodyPosition(server.getAllTrackers(), TrackerPosition.CHEST) != null) {
|
||||
private float readFromConfig(SkeletonConfigValue configValue) {
|
||||
return server.config.getFloat(configValue.configKey, configValue.defaultValue);
|
||||
}
|
||||
|
||||
public void reloadConfigValues(List<PoseFrameTracker> trackers) {
|
||||
// Load torso configs
|
||||
staticConfigs.put(SkeletonConfigValue.HEAD, readFromConfig(SkeletonConfigValue.HEAD));
|
||||
staticConfigs.put(SkeletonConfigValue.NECK, readFromConfig(SkeletonConfigValue.NECK));
|
||||
configs.put(SkeletonConfigValue.TORSO, readFromConfig(SkeletonConfigValue.TORSO));
|
||||
if(server.config.getBoolean("autobone.forceChestTracker", false) || (trackers != null && TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.CHEST) != null)) {
|
||||
// If force enabled or has a chest tracker
|
||||
configs.put("Chest", server.config.getFloat("body.chestDistance", 0.42f));
|
||||
staticConfigs.remove(SkeletonConfigValue.CHEST);
|
||||
configs.put(SkeletonConfigValue.CHEST, readFromConfig(SkeletonConfigValue.CHEST));
|
||||
} else {
|
||||
// Otherwise, make sure it's not used
|
||||
configs.remove("Chest");
|
||||
staticConfigs.put("Chest", server.config.getFloat("body.chestDistance", 0.42f));
|
||||
configs.remove(SkeletonConfigValue.CHEST);
|
||||
staticConfigs.put(SkeletonConfigValue.CHEST, readFromConfig(SkeletonConfigValue.CHEST));
|
||||
}
|
||||
if(server.config.getBoolean("autobone.forceHipTracker", false) || (trackers != null && TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.HIP) != null && TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.WAIST) != null)) {
|
||||
// If force enabled or has a hip tracker and waist tracker
|
||||
staticConfigs.remove(SkeletonConfigValue.WAIST);
|
||||
configs.put(SkeletonConfigValue.WAIST, readFromConfig(SkeletonConfigValue.WAIST));
|
||||
} else {
|
||||
// Otherwise, make sure it's not used
|
||||
configs.remove(SkeletonConfigValue.WAIST);
|
||||
staticConfigs.put(SkeletonConfigValue.WAIST, readFromConfig(SkeletonConfigValue.WAIST));
|
||||
}
|
||||
|
||||
// Load leg configs
|
||||
staticConfigs.put("Hips width", server.config.getFloat("body.hipsWidth", HumanSkeletonWithLegs.HIPS_WIDTH_DEFAULT));
|
||||
configs.put("Legs length", server.config.getFloat("body.legsLength", 0.84f));
|
||||
configs.put("Knee height", server.config.getFloat("body.kneeHeight", 0.42f));
|
||||
staticConfigs.put(SkeletonConfigValue.HIPS_WIDTH, readFromConfig(SkeletonConfigValue.HIPS_WIDTH));
|
||||
configs.put(SkeletonConfigValue.LEGS_LENGTH, readFromConfig(SkeletonConfigValue.LEGS_LENGTH));
|
||||
configs.put(SkeletonConfigValue.KNEE_HEIGHT, readFromConfig(SkeletonConfigValue.KNEE_HEIGHT));
|
||||
|
||||
// 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 HumanSkeletonWithLegs) {
|
||||
skeleton = (HumanSkeletonWithLegs) 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();
|
||||
}
|
||||
@@ -129,40 +155,37 @@ public class AutoBone {
|
||||
return false;
|
||||
}
|
||||
|
||||
configs.forEach(skeleton::setSkeletonConfig);
|
||||
|
||||
SkeletonConfig skeletonConfig = skeleton.getSkeletonConfig();
|
||||
skeletonConfig.setConfigs(configs, null);
|
||||
skeletonConfig.saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
|
||||
LogManager.log.info("[AutoBone] Configured skeleton bone lengths");
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setConfig(String name, String path) {
|
||||
Float value = configs.get(name);
|
||||
private void setConfig(SkeletonConfigValue config) {
|
||||
Float value = configs.get(config);
|
||||
if(value != null) {
|
||||
server.config.setProperty(path, value);
|
||||
server.config.setProperty(config.configKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
// This doesn't require a skeleton, therefore can be used if skeleton is null
|
||||
public void saveConfigs() {
|
||||
setConfig("Head", "body.headShift");
|
||||
setConfig("Neck", "body.neckLength");
|
||||
setConfig("Waist", "body.waistDistance");
|
||||
setConfig("Chest", "body.chestDistance");
|
||||
setConfig("Hips width", "body.hipsWidth");
|
||||
setConfig("Legs length", "body.legsLength");
|
||||
setConfig("Knee height", "body.kneeHeight");
|
||||
for(SkeletonConfigValue config : SkeletonConfigValue.values) {
|
||||
setConfig(config);
|
||||
}
|
||||
|
||||
server.saveConfig();
|
||||
}
|
||||
|
||||
public Float getConfig(String config) {
|
||||
public Float getConfig(SkeletonConfigValue config) {
|
||||
Float configVal = configs.get(config);
|
||||
return configVal != null ? configVal : staticConfigs.get(config);
|
||||
}
|
||||
|
||||
public Float getConfig(String config, Map<String, Float> configs, Map<String, Float> configsAlt) {
|
||||
public Float getConfig(SkeletonConfigValue config, Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigValue, Float> configsAlt) {
|
||||
if(configs == null) {
|
||||
throw new NullPointerException("Argument \"configs\" must not be null");
|
||||
}
|
||||
@@ -171,27 +194,46 @@ public class AutoBone {
|
||||
return configVal != null || configsAlt == null ? configVal : configsAlt.get(config);
|
||||
}
|
||||
|
||||
public float getHeight(Map<String, Float> configs) {
|
||||
return getHeight(configs, null);
|
||||
}
|
||||
|
||||
public float getHeight(Map<String, Float> configs, Map<String, Float> configsAlt) {
|
||||
float height = 0f;
|
||||
public float sumSelectConfigs(List<SkeletonConfigValue> selection, Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigValue, Float> configsAlt) {
|
||||
float sum = 0f;
|
||||
|
||||
for(String heightConfig : heightConfigs) {
|
||||
Float length = getConfig(heightConfig, configs, configsAlt);
|
||||
for(SkeletonConfigValue config : selection) {
|
||||
Float length = getConfig(config, configs, configsAlt);
|
||||
if(length != null) {
|
||||
height += length;
|
||||
sum += length;
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
return sum;
|
||||
}
|
||||
|
||||
public float getLengthSum(Map<String, Float> configs) {
|
||||
public float sumSelectConfigs(List<SkeletonConfigValue> selection, SkeletonConfig skeletonConfig) {
|
||||
float sum = 0f;
|
||||
|
||||
for(SkeletonConfigValue config : selection) {
|
||||
sum += skeletonConfig.getConfig(config);
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
public float getLengthSum(Map<SkeletonConfigValue, Float> configs) {
|
||||
return getLengthSum(configs, null);
|
||||
}
|
||||
|
||||
public float getLengthSum(Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigValue, Float> configsAlt) {
|
||||
float length = 0f;
|
||||
|
||||
for(float boneLength : configs.values()) {
|
||||
if(configsAlt != null) {
|
||||
for(Entry<SkeletonConfigValue, Float> config : configsAlt.entrySet()) {
|
||||
// If there isn't a duplicate config
|
||||
if(!configs.containsKey(config.getKey())) {
|
||||
length += config.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(Float boneLength : configs.values()) {
|
||||
length += boneLength;
|
||||
}
|
||||
|
||||
@@ -232,21 +274,22 @@ public class AutoBone {
|
||||
public float processFrames(PoseFrames frames, boolean calcInitError, float targetHeight, Consumer<Epoch> epochCallback) {
|
||||
final int frameCount = frames.getMaxFrameCount();
|
||||
|
||||
final SimpleSkeleton skeleton1 = new SimpleSkeleton(configs, staticConfigs);
|
||||
final TrackerFrame[] trackerBuffer1 = new TrackerFrame[frames.getTrackerCount()];
|
||||
List<PoseFrameTracker> trackers = frames.getTrackers();
|
||||
reloadConfigValues(trackers); // Reload configs and detect chest tracker from the first frame
|
||||
|
||||
frames.getFrames(0, trackerBuffer1);
|
||||
reloadConfigValues(trackerBuffer1); // Reload configs and detect chest tracker from the first frame
|
||||
|
||||
final SimpleSkeleton skeleton2 = new SimpleSkeleton(configs, staticConfigs);
|
||||
final TrackerFrame[] trackerBuffer2 = new TrackerFrame[frames.getTrackerCount()];
|
||||
final PoseFrameSkeleton skeleton1 = new PoseFrameSkeleton(trackers, null, configs, staticConfigs);
|
||||
final PoseFrameSkeleton skeleton2 = new PoseFrameSkeleton(trackers, null, configs, staticConfigs);
|
||||
|
||||
// If target height isn't specified, auto-detect
|
||||
if(targetHeight < 0f) {
|
||||
// Get the current skeleton from the server
|
||||
HumanSkeleton skeleton = getSkeleton();
|
||||
if(skeleton != null) {
|
||||
targetHeight = getHeight(skeleton.getSkeletonConfig());
|
||||
// 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);
|
||||
@@ -259,33 +302,38 @@ 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 ? (float) (initialAdjustRate / Math.pow(adjustRateDecay, epoch)) : 0f;
|
||||
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) {
|
||||
frames.getFrames(frameCursor, trackerBuffer1);
|
||||
frames.getFrames(frameCursor + cursorOffset, trackerBuffer2);
|
||||
int frameCursor2 = frameCursor + cursorOffset;
|
||||
|
||||
skeleton1.setSkeletonConfigs(configs);
|
||||
skeleton2.setSkeletonConfigs(configs);
|
||||
skeleton1.skeletonConfig.setConfigs(configs, null);
|
||||
skeleton2.skeletonConfig.setConfigs(configs, null);
|
||||
|
||||
skeleton1.setPoseFromFrame(trackerBuffer1);
|
||||
skeleton2.setPoseFromFrame(trackerBuffer2);
|
||||
skeleton1.setCursor(frameCursor);
|
||||
skeleton1.updatePose();
|
||||
|
||||
skeleton2.setCursor(frameCursor2);
|
||||
skeleton2.updatePose();
|
||||
|
||||
float totalLength = getLengthSum(configs);
|
||||
float curHeight = getHeight(configs, staticConfigs);
|
||||
float errorDeriv = getErrorDeriv(trackerBuffer1, trackerBuffer2, skeleton1, skeleton2, targetHeight - curHeight);
|
||||
float curHeight = sumSelectConfigs(heightConfigs, configs, staticConfigs);
|
||||
//float scaleLength = sumSelectConfigs(lengthConfigs, configs, staticConfigs);
|
||||
float errorDeriv = getErrorDeriv(frames, frameCursor, frameCursor2, skeleton1, skeleton2, targetHeight - curHeight, 1f);
|
||||
float error = errorFunc(errorDeriv);
|
||||
|
||||
// In case of fire
|
||||
if(Float.isNaN(error) || Float.isInfinite(error)) {
|
||||
// Extinguish
|
||||
LogManager.log.warning("[AutoBone] Error value is invalid, resetting variables to recover");
|
||||
reloadConfigValues(trackerBuffer1);
|
||||
reloadConfigValues(trackers);
|
||||
|
||||
// Reset error sum values
|
||||
sumError = 0f;
|
||||
@@ -301,7 +349,12 @@ public class AutoBone {
|
||||
|
||||
float adjustVal = error * adjustRate;
|
||||
|
||||
for(Entry<String, Float> entry : configs.entrySet()) {
|
||||
// If there is no adjustment whatsoever, skip this
|
||||
if(adjustVal == 0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(Entry<SkeletonConfigValue, Float> entry : configs.entrySet()) {
|
||||
// Skip adjustment if the epoch is before starting (for logging only)
|
||||
if(epoch < 0) {
|
||||
break;
|
||||
@@ -311,6 +364,7 @@ public class AutoBone {
|
||||
|
||||
// Try positive and negative adjustments
|
||||
boolean isHeightVar = heightConfigs.contains(entry.getKey());
|
||||
//boolean isLengthVar = lengthConfigs.contains(entry.getKey());
|
||||
float minError = errorDeriv;
|
||||
float finalNewLength = -1f;
|
||||
for(int i = 0; i < 2; i++) {
|
||||
@@ -326,7 +380,8 @@ public class AutoBone {
|
||||
updateSkeletonBoneLength(skeleton1, skeleton2, entry.getKey(), newLength);
|
||||
|
||||
float newHeight = isHeightVar ? curHeight + curAdjustVal : curHeight;
|
||||
float newErrorDeriv = getErrorDeriv(trackerBuffer1, trackerBuffer2, skeleton1, skeleton2, targetHeight - newHeight);
|
||||
//float newScaleLength = isLengthVar ? scaleLength + curAdjustVal : scaleLength;
|
||||
float newErrorDeriv = getErrorDeriv(frames, frameCursor, frameCursor2, skeleton1, skeleton2, targetHeight - newHeight, 1f);
|
||||
|
||||
if(newErrorDeriv < minError) {
|
||||
minError = newErrorDeriv;
|
||||
@@ -353,53 +408,80 @@ public class AutoBone {
|
||||
}
|
||||
}
|
||||
|
||||
float finalHeight = getHeight(configs, staticConfigs);
|
||||
float finalHeight = sumSelectConfigs(heightConfigs, configs, staticConfigs);
|
||||
LogManager.log.info("[AutoBone] Target height: " + targetHeight + " New height: " + finalHeight);
|
||||
|
||||
return Math.abs(finalHeight - targetHeight);
|
||||
return FastMath.abs(finalHeight - targetHeight);
|
||||
}
|
||||
|
||||
// The change in position of the ankle over time
|
||||
protected float getSlideErrorDeriv(SimpleSkeleton skeleton1, SimpleSkeleton skeleton2) {
|
||||
float slideLeft = skeleton1.getNodePosition(TrackerPosition.LEFT_ANKLE).distance(skeleton2.getNodePosition(TrackerPosition.LEFT_ANKLE));
|
||||
float slideRight = skeleton1.getNodePosition(TrackerPosition.RIGHT_ANKLE).distance(skeleton2.getNodePosition(TrackerPosition.RIGHT_ANKLE));
|
||||
protected float getSlideErrorDeriv(PoseFrameSkeleton skeleton1, PoseFrameSkeleton skeleton2) {
|
||||
float slideLeft = skeleton1.getComputedTracker(TrackerRole.LEFT_FOOT).position.distance(skeleton2.getComputedTracker(TrackerRole.LEFT_FOOT).position);
|
||||
float slideRight = skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT).position.distance(skeleton2.getComputedTracker(TrackerRole.RIGHT_FOOT).position);
|
||||
|
||||
// Divide by 4 to halve and average, it's halved because you want to approach a midpoint, not the other point
|
||||
return (slideLeft + slideRight) / 4f;
|
||||
}
|
||||
|
||||
// The change in distance between both of the ankles over time
|
||||
protected float getOffsetSlideErrorDeriv(PoseFrameSkeleton skeleton1, PoseFrameSkeleton skeleton2) {
|
||||
Vector3f leftFoot1 = skeleton1.getComputedTracker(TrackerRole.LEFT_FOOT).position;
|
||||
Vector3f rightFoot1 = skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT).position;
|
||||
|
||||
Vector3f leftFoot2 = skeleton2.getComputedTracker(TrackerRole.LEFT_FOOT).position;
|
||||
Vector3f rightFoot2 = skeleton2.getComputedTracker(TrackerRole.RIGHT_FOOT).position;
|
||||
|
||||
float slideDist1 = leftFoot1.distance(rightFoot1);
|
||||
float slideDist2 = leftFoot2.distance(rightFoot2);
|
||||
|
||||
float slideDist3 = leftFoot1.distance(rightFoot2);
|
||||
float slideDist4 = leftFoot2.distance(rightFoot1);
|
||||
|
||||
float dist1 = FastMath.abs(slideDist1 - slideDist2);
|
||||
float dist2 = FastMath.abs(slideDist3 - slideDist4);
|
||||
|
||||
float dist3 = FastMath.abs(slideDist1 - slideDist3);
|
||||
float dist4 = FastMath.abs(slideDist1 - slideDist4);
|
||||
|
||||
float dist5 = FastMath.abs(slideDist2 - slideDist3);
|
||||
float dist6 = FastMath.abs(slideDist2 - slideDist4);
|
||||
|
||||
// Divide by 12 to halve and average, it's halved because you want to approach a midpoint, not the other point
|
||||
return (dist1 + dist2 + dist3 + dist4 + dist5 + dist6) / 12f;
|
||||
}
|
||||
|
||||
// The offset between both feet at one instant and over time
|
||||
protected float getOffsetErrorDeriv(SimpleSkeleton skeleton1, SimpleSkeleton skeleton2) {
|
||||
float skeleton1Left = skeleton1.getNodePosition(TrackerPosition.LEFT_ANKLE).getY();
|
||||
float skeleton1Right = skeleton1.getNodePosition(TrackerPosition.RIGHT_ANKLE).getY();
|
||||
protected float getOffsetErrorDeriv(PoseFrameSkeleton skeleton1, PoseFrameSkeleton skeleton2) {
|
||||
float leftFoot1 = skeleton1.getComputedTracker(TrackerRole.LEFT_FOOT).position.getY();
|
||||
float rightFoot1 = skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT).position.getY();
|
||||
|
||||
float skeleton2Left = skeleton2.getNodePosition(TrackerPosition.LEFT_ANKLE).getY();
|
||||
float skeleton2Right = skeleton2.getNodePosition(TrackerPosition.RIGHT_ANKLE).getY();
|
||||
float leftFoot2 = skeleton2.getComputedTracker(TrackerRole.LEFT_FOOT).position.getY();
|
||||
float rightFoot2 = skeleton2.getComputedTracker(TrackerRole.RIGHT_FOOT).position.getY();
|
||||
|
||||
float dist1 = Math.abs(skeleton1Left - skeleton1Right);
|
||||
float dist2 = Math.abs(skeleton2Left - skeleton2Right);
|
||||
float dist1 = FastMath.abs(leftFoot1 - rightFoot1);
|
||||
float dist2 = FastMath.abs(leftFoot2 - rightFoot2);
|
||||
|
||||
float dist3 = Math.abs(skeleton1Left - skeleton2Right);
|
||||
float dist4 = Math.abs(skeleton2Left - skeleton1Right);
|
||||
float dist3 = FastMath.abs(leftFoot1 - rightFoot2);
|
||||
float dist4 = FastMath.abs(leftFoot2 - rightFoot1);
|
||||
|
||||
float dist5 = Math.abs(skeleton1Left - skeleton2Left);
|
||||
float dist6 = Math.abs(skeleton1Right - skeleton2Right);
|
||||
float dist5 = FastMath.abs(leftFoot1 - leftFoot2);
|
||||
float dist6 = FastMath.abs(rightFoot1 - rightFoot2);
|
||||
|
||||
// Divide by 12 to halve and average, it's halved because you want to approach a midpoint, not the other point
|
||||
return (dist1 + dist2 + dist3 + dist4 + dist5 + dist6) / 12f;
|
||||
}
|
||||
|
||||
// The distance from average human proportions
|
||||
protected float getProportionErrorDeriv(SimpleSkeleton skeleton) {
|
||||
Float neckLength = skeleton.getSkeletonConfig("Neck");
|
||||
Float chestLength = skeleton.getSkeletonConfig("Chest");
|
||||
Float waistLength = skeleton.getSkeletonConfig("Waist");
|
||||
Float legsLength = skeleton.getSkeletonConfig("Legs length");
|
||||
Float kneeHeight = skeleton.getSkeletonConfig("Knee height");
|
||||
protected float getProportionErrorDeriv(SkeletonConfig skeleton) {
|
||||
float neckLength = skeleton.getConfig(SkeletonConfigValue.NECK);
|
||||
float chestLength = skeleton.getConfig(SkeletonConfigValue.CHEST);
|
||||
float torsoLength = skeleton.getConfig(SkeletonConfigValue.TORSO);
|
||||
float legsLength = skeleton.getConfig(SkeletonConfigValue.LEGS_LENGTH);
|
||||
float kneeHeight = skeleton.getConfig(SkeletonConfigValue.KNEE_HEIGHT);
|
||||
|
||||
float chestWaist = chestLength != null && waistLength != null ? Math.abs((chestLength / waistLength) - chestWaistRatio) : 0f;
|
||||
float legBody = legsLength != null && waistLength != null && neckLength != null ? Math.abs((legsLength / (waistLength + neckLength)) - legBodyRatio) : 0f;
|
||||
float kneeLeg = kneeHeight != null && legsLength != null ? Math.abs((kneeHeight / legsLength) - kneeLegRatio) : 0f;
|
||||
float chestTorso = FastMath.abs((chestLength / torsoLength) - chestTorsoRatio);
|
||||
float legBody = FastMath.abs((legsLength / (torsoLength + neckLength)) - legBodyRatio);
|
||||
float kneeLeg = FastMath.abs((kneeHeight / legsLength) - kneeLegRatio);
|
||||
|
||||
if(legBody <= legBodyRatioRange) {
|
||||
legBody = 0f;
|
||||
@@ -407,22 +489,26 @@ public class AutoBone {
|
||||
legBody -= legBodyRatioRange;
|
||||
}
|
||||
|
||||
return (chestWaist + legBody + kneeLeg) / 3f;
|
||||
return (chestTorso + legBody + kneeLeg) / 3f;
|
||||
}
|
||||
|
||||
// The distance of any points to the corresponding absolute position
|
||||
protected float getPositionErrorDeriv(TrackerFrame[] frame, SimpleSkeleton skeleton) {
|
||||
protected float getPositionErrorDeriv(PoseFrames frames, int cursor, PoseFrameSkeleton skeleton) {
|
||||
float offset = 0f;
|
||||
int offsetCount = 0;
|
||||
|
||||
for(TrackerFrame trackerFrame : frame) {
|
||||
List<PoseFrameTracker> trackers = frames.getTrackers();
|
||||
for(int i = 0; i < trackers.size(); i++) {
|
||||
PoseFrameTracker tracker = trackers.get(i);
|
||||
|
||||
TrackerFrame trackerFrame = tracker.safeGetFrame(cursor);
|
||||
if(trackerFrame == null || !trackerFrame.hasData(TrackerFrameData.POSITION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3f nodePos = skeleton.getNodePosition(trackerFrame.designation.designation);
|
||||
Vector3f nodePos = skeleton.getComputedTracker(trackerFrame.designation.trackerRole).position;
|
||||
if(nodePos != null) {
|
||||
offset += Math.abs(nodePos.distance(trackerFrame.position));
|
||||
offset += FastMath.abs(nodePos.distance(trackerFrame.position));
|
||||
offsetCount++;
|
||||
}
|
||||
}
|
||||
@@ -431,76 +517,91 @@ public class AutoBone {
|
||||
}
|
||||
|
||||
// The difference between offset of absolute position and the corresponding point over time
|
||||
protected float getPositionOffsetErrorDeriv(TrackerFrame[] frame1, TrackerFrame[] frame2, SimpleSkeleton skeleton1, SimpleSkeleton skeleton2) {
|
||||
protected float getPositionOffsetErrorDeriv(PoseFrames frames, int cursor1, int cursor2, PoseFrameSkeleton skeleton1, PoseFrameSkeleton skeleton2) {
|
||||
float offset = 0f;
|
||||
int offsetCount = 0;
|
||||
|
||||
for(TrackerFrame trackerFrame1 : frame1) {
|
||||
List<PoseFrameTracker> trackers = frames.getTrackers();
|
||||
for(int i = 0; i < trackers.size(); i++) {
|
||||
PoseFrameTracker tracker = trackers.get(i);
|
||||
|
||||
TrackerFrame trackerFrame1 = tracker.safeGetFrame(cursor1);
|
||||
if(trackerFrame1 == null || !trackerFrame1.hasData(TrackerFrameData.POSITION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
TrackerFrame trackerFrame2 = TrackerUtils.findTrackerForBodyPosition(frame2, trackerFrame1.designation);
|
||||
TrackerFrame trackerFrame2 = tracker.safeGetFrame(cursor2);
|
||||
if(trackerFrame2 == null || !trackerFrame2.hasData(TrackerFrameData.POSITION)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3f nodePos1 = skeleton1.getNodePosition(trackerFrame1.designation);
|
||||
Vector3f nodePos1 = skeleton1.getComputedTracker(trackerFrame1.designation.trackerRole).position;
|
||||
if(nodePos1 == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3f nodePos2 = skeleton2.getNodePosition(trackerFrame2.designation);
|
||||
Vector3f nodePos2 = skeleton2.getComputedTracker(trackerFrame2.designation.trackerRole).position;
|
||||
if(nodePos2 == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float dist1 = Math.abs(nodePos1.distance(trackerFrame1.position));
|
||||
float dist2 = Math.abs(nodePos2.distance(trackerFrame2.position));
|
||||
float dist1 = FastMath.abs(nodePos1.distance(trackerFrame1.position));
|
||||
float dist2 = FastMath.abs(nodePos2.distance(trackerFrame2.position));
|
||||
|
||||
offset += Math.abs(dist2 - dist1);
|
||||
offset += FastMath.abs(dist2 - dist1);
|
||||
offsetCount++;
|
||||
}
|
||||
|
||||
return offsetCount > 0 ? offset / offsetCount : 0f;
|
||||
}
|
||||
|
||||
protected float getErrorDeriv(TrackerFrame[] frame1, TrackerFrame[] frame2, SimpleSkeleton skeleton1, SimpleSkeleton skeleton2, float heightChange) {
|
||||
protected float getErrorDeriv(PoseFrames frames, int cursor1, int cursor2, PoseFrameSkeleton skeleton1, PoseFrameSkeleton skeleton2, float heightChange, float distScale) {
|
||||
float totalError = 0f;
|
||||
float sumWeight = 0f;
|
||||
|
||||
if(slideErrorFactor > 0f) {
|
||||
totalError += getSlideErrorDeriv(skeleton1, skeleton2) * slideErrorFactor;
|
||||
// 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) {
|
||||
totalError += getOffsetErrorDeriv(skeleton1, skeleton2) * offsetErrorFactor;
|
||||
// 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) * proportionErrorFactor;
|
||||
totalError += getProportionErrorDeriv(skeleton1.skeletonConfig) * proportionErrorFactor;
|
||||
sumWeight += proportionErrorFactor;
|
||||
}
|
||||
|
||||
if(heightErrorFactor > 0f) {
|
||||
totalError += Math.abs(heightChange) * heightErrorFactor;
|
||||
// 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) {
|
||||
totalError += (getPositionErrorDeriv(frame1, skeleton1) + getPositionErrorDeriv(frame2, skeleton2) / 2f) * positionErrorFactor;
|
||||
// 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) {
|
||||
totalError += getPositionOffsetErrorDeriv(frame1, frame2, skeleton1, skeleton2) * positionOffsetErrorFactor;
|
||||
// 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;
|
||||
}
|
||||
|
||||
@@ -509,8 +610,11 @@ public class AutoBone {
|
||||
return 0.5f * (errorDeriv * errorDeriv);
|
||||
}
|
||||
|
||||
protected void updateSkeletonBoneLength(SimpleSkeleton skeleton1, SimpleSkeleton skeleton2, String joint, float newLength) {
|
||||
skeleton1.setSkeletonConfig(joint, newLength, true);
|
||||
skeleton2.setSkeletonConfig(joint, newLength, true);
|
||||
protected void updateSkeletonBoneLength(PoseFrameSkeleton skeleton1, PoseFrameSkeleton skeleton2, SkeletonConfigValue config, float newLength) {
|
||||
skeleton1.skeletonConfig.setConfig(config, newLength);
|
||||
skeleton1.updatePoseAffectedByConfig(config);
|
||||
|
||||
skeleton2.skeletonConfig.setConfig(config, newLength);
|
||||
skeleton2.updatePoseAffectedByConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
package dev.slimevr.autobone;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.poserecorder.TrackerFrame;
|
||||
import dev.slimevr.poserecorder.TrackerFrameData;
|
||||
import io.eiren.vr.processor.HumanSkeletonWithLegs;
|
||||
import io.eiren.vr.processor.HumanSkeletonWithWaist;
|
||||
import io.eiren.vr.processor.TransformNode;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerUtils;
|
||||
import io.eiren.yaml.YamlFile;
|
||||
|
||||
public class SimpleSkeleton {
|
||||
|
||||
// Waist
|
||||
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 waistNode = new TransformNode("Waist", false);
|
||||
protected final TransformNode chestNode = new TransformNode("Chest", false);
|
||||
|
||||
protected float chestDistance = 0.42f;
|
||||
/**
|
||||
* Distance from eyes to waist
|
||||
*/
|
||||
protected float waistDistance = 0.85f;
|
||||
/**
|
||||
* Distance from eyes to the base of the neck
|
||||
*/
|
||||
protected float neckLength = HumanSkeletonWithWaist.NECK_LENGTH_DEFAULT;
|
||||
/**
|
||||
* Distance from eyes to ear
|
||||
*/
|
||||
protected float headShift = HumanSkeletonWithWaist.HEAD_SHIFT_DEFAULT;
|
||||
|
||||
// Legs
|
||||
protected final TransformNode leftHipNode = new TransformNode("Left-Hip", false);
|
||||
protected final TransformNode leftKneeNode = new TransformNode("Left-Knee", false);
|
||||
protected final TransformNode leftAnkleNode = new TransformNode("Left-Ankle", false);
|
||||
protected final TransformNode rightHipNode = new TransformNode("Right-Hip", false);
|
||||
protected final TransformNode rightKneeNode = new TransformNode("Right-Knee", false);
|
||||
protected final TransformNode rightAnkleNode = new TransformNode("Right-Ankle", false);
|
||||
|
||||
/**
|
||||
* Distance between centers of both hips
|
||||
*/
|
||||
protected float hipsWidth = HumanSkeletonWithLegs.HIPS_WIDTH_DEFAULT;
|
||||
/**
|
||||
* Length from waist to knees
|
||||
*/
|
||||
protected float kneeHeight = 0.42f;
|
||||
/**
|
||||
* Distance from waist to ankle
|
||||
*/
|
||||
protected float legsLength = 0.84f;
|
||||
|
||||
protected final HashMap<String, TransformNode> nodes = new HashMap<String, TransformNode>();
|
||||
|
||||
private Quaternion rotBuf1 = new Quaternion();
|
||||
private Quaternion rotBuf2 = new Quaternion();
|
||||
|
||||
public SimpleSkeleton() {
|
||||
// Assemble skeleton to waist
|
||||
hmdNode.attachChild(headNode);
|
||||
headNode.localTransform.setTranslation(0, 0, headShift);
|
||||
|
||||
headNode.attachChild(neckNode);
|
||||
neckNode.localTransform.setTranslation(0, -neckLength, 0);
|
||||
|
||||
neckNode.attachChild(chestNode);
|
||||
chestNode.localTransform.setTranslation(0, -chestDistance, 0);
|
||||
|
||||
chestNode.attachChild(waistNode);
|
||||
waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0);
|
||||
|
||||
// Assemble skeleton to feet
|
||||
waistNode.attachChild(leftHipNode);
|
||||
leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0);
|
||||
|
||||
waistNode.attachChild(rightHipNode);
|
||||
rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0);
|
||||
|
||||
leftHipNode.attachChild(leftKneeNode);
|
||||
leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
|
||||
rightHipNode.attachChild(rightKneeNode);
|
||||
rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
|
||||
leftKneeNode.attachChild(leftAnkleNode);
|
||||
leftAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0);
|
||||
|
||||
rightKneeNode.attachChild(rightAnkleNode);
|
||||
rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0);
|
||||
|
||||
// Set up a HashMap to get nodes by name easily
|
||||
hmdNode.depthFirstTraversal(visitor -> {
|
||||
nodes.put(visitor.getName(), visitor);
|
||||
});
|
||||
}
|
||||
|
||||
public SimpleSkeleton(Map<String, Float> configs, Map<String, Float> altConfigs) {
|
||||
// Initialize
|
||||
this();
|
||||
|
||||
// Set configs
|
||||
if(altConfigs != null) {
|
||||
// Set alts first, so if there's any overlap it doesn't affect the values
|
||||
setSkeletonConfigs(altConfigs);
|
||||
}
|
||||
setSkeletonConfigs(configs);
|
||||
}
|
||||
|
||||
public SimpleSkeleton(Map<String, Float> configs) {
|
||||
this(configs, null);
|
||||
}
|
||||
|
||||
public void setPoseFromFrame(TrackerFrame[] frame) {
|
||||
|
||||
TrackerFrame hmd = TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.HMD);
|
||||
|
||||
if(hmd != null) {
|
||||
if(hmd.hasData(TrackerFrameData.ROTATION)) {
|
||||
hmdNode.localTransform.setRotation(hmd.rotation);
|
||||
headNode.localTransform.setRotation(hmd.rotation);
|
||||
}
|
||||
|
||||
if(hmd.hasData(TrackerFrameData.POSITION)) {
|
||||
hmdNode.localTransform.setTranslation(hmd.position);
|
||||
}
|
||||
}
|
||||
|
||||
TrackerFrame chest = TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.CHEST, TrackerPosition.WAIST);
|
||||
setRotation(chest, neckNode);
|
||||
|
||||
TrackerFrame waist = TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.WAIST, TrackerPosition.CHEST);
|
||||
setRotation(waist, chestNode);
|
||||
|
||||
TrackerFrame leftLeg = TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.LEFT_LEG);
|
||||
TrackerFrame rightLeg = TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.RIGHT_LEG);
|
||||
|
||||
averagePelvis(waist, leftLeg, rightLeg);
|
||||
|
||||
setRotation(leftLeg, leftHipNode);
|
||||
setRotation(rightLeg, rightHipNode);
|
||||
|
||||
TrackerFrame leftAnkle = TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.LEFT_ANKLE);
|
||||
setRotation(leftAnkle, rightKneeNode);
|
||||
|
||||
TrackerFrame rightAnkle = TrackerUtils.findTrackerForBodyPosition(frame, TrackerPosition.RIGHT_ANKLE);
|
||||
setRotation(rightAnkle, leftKneeNode);
|
||||
|
||||
updatePose();
|
||||
}
|
||||
|
||||
public void setRotation(TrackerFrame trackerFrame, TransformNode node) {
|
||||
if(trackerFrame != null && trackerFrame.hasData(TrackerFrameData.ROTATION)) {
|
||||
node.localTransform.setRotation(trackerFrame.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
public void averagePelvis(TrackerFrame waist, TrackerFrame leftLeg, TrackerFrame rightLeg) {
|
||||
if((leftLeg == null || rightLeg == null) || (!leftLeg.hasData(TrackerFrameData.ROTATION) || !rightLeg.hasData(TrackerFrameData.ROTATION))) {
|
||||
setRotation(waist, waistNode);
|
||||
return;
|
||||
}
|
||||
|
||||
if(waist == null || !waist.hasData(TrackerFrameData.ROTATION)) {
|
||||
if(leftLeg.hasData(TrackerFrameData.ROTATION) && rightLeg.hasData(TrackerFrameData.ROTATION)) {
|
||||
leftLeg.getRotation(rotBuf1);
|
||||
rightLeg.getRotation(rotBuf2);
|
||||
rotBuf1.nlerp(rotBuf2, 0.5f);
|
||||
|
||||
waistNode.localTransform.setRotation(rotBuf1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Average the pelvis with the waist rotation
|
||||
leftLeg.getRotation(rotBuf1);
|
||||
rightLeg.getRotation(rotBuf2);
|
||||
rotBuf1.nlerp(rotBuf2, 0.5f);
|
||||
|
||||
waist.getRotation(rotBuf2);
|
||||
rotBuf1.nlerp(rotBuf2, 0.3333333f);
|
||||
|
||||
waistNode.localTransform.setRotation(rotBuf1);
|
||||
}
|
||||
|
||||
public void setSkeletonConfigs(Map<String, Float> configs) {
|
||||
configs.forEach(this::setSkeletonConfig);
|
||||
}
|
||||
|
||||
public void setSkeletonConfig(String joint, float newLength) {
|
||||
setSkeletonConfig(joint, newLength, false);
|
||||
}
|
||||
|
||||
public void setSkeletonConfig(String joint, float newLength, boolean updatePose) {
|
||||
switch(joint) {
|
||||
case "Head":
|
||||
headShift = newLength;
|
||||
headNode.localTransform.setTranslation(0, 0, headShift);
|
||||
if(updatePose) {
|
||||
headNode.update();
|
||||
}
|
||||
break;
|
||||
case "Neck":
|
||||
neckLength = newLength;
|
||||
neckNode.localTransform.setTranslation(0, -neckLength, 0);
|
||||
if(updatePose) {
|
||||
neckNode.update();
|
||||
}
|
||||
break;
|
||||
case "Waist":
|
||||
waistDistance = newLength;
|
||||
waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0);
|
||||
if(updatePose) {
|
||||
waistNode.update();
|
||||
}
|
||||
break;
|
||||
case "Chest":
|
||||
chestDistance = newLength;
|
||||
chestNode.localTransform.setTranslation(0, -chestDistance, 0);
|
||||
waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0);
|
||||
if(updatePose) {
|
||||
chestNode.update();
|
||||
}
|
||||
break;
|
||||
case "Hips width":
|
||||
hipsWidth = newLength;
|
||||
leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0);
|
||||
rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0);
|
||||
if(updatePose) {
|
||||
leftHipNode.update();
|
||||
rightHipNode.update();
|
||||
}
|
||||
break;
|
||||
case "Knee height":
|
||||
kneeHeight = newLength;
|
||||
leftAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0);
|
||||
rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0);
|
||||
leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
if(updatePose) {
|
||||
leftKneeNode.update();
|
||||
rightKneeNode.update();
|
||||
}
|
||||
break;
|
||||
case "Legs length":
|
||||
legsLength = newLength;
|
||||
leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
if(updatePose) {
|
||||
leftKneeNode.update();
|
||||
rightKneeNode.update();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public Float getSkeletonConfig(String joint) {
|
||||
switch(joint) {
|
||||
case "Head":
|
||||
return headShift;
|
||||
case "Neck":
|
||||
return neckLength;
|
||||
case "Waist":
|
||||
return waistDistance;
|
||||
case "Chest":
|
||||
return chestDistance;
|
||||
case "Hips width":
|
||||
return hipsWidth;
|
||||
case "Knee height":
|
||||
return kneeHeight;
|
||||
case "Legs length":
|
||||
return legsLength;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void updatePose() {
|
||||
hmdNode.update();
|
||||
}
|
||||
|
||||
public TransformNode getNode(String node) {
|
||||
return nodes.get(node);
|
||||
}
|
||||
|
||||
public TransformNode getNode(TrackerPosition bodyPosition) {
|
||||
return getNode(bodyPosition, false);
|
||||
}
|
||||
|
||||
public TransformNode getNode(TrackerPosition bodyPosition, boolean rotationNode) {
|
||||
if(bodyPosition == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch(bodyPosition) {
|
||||
case HMD:
|
||||
return hmdNode;
|
||||
case CHEST:
|
||||
return rotationNode ? neckNode : chestNode;
|
||||
case WAIST:
|
||||
return rotationNode ? chestNode : waistNode;
|
||||
|
||||
case LEFT_LEG:
|
||||
return rotationNode ? leftHipNode : leftKneeNode;
|
||||
case RIGHT_LEG:
|
||||
return rotationNode ? rightHipNode : rightKneeNode;
|
||||
|
||||
case LEFT_ANKLE:
|
||||
return rotationNode ? leftKneeNode : leftAnkleNode;
|
||||
case RIGHT_ANKLE:
|
||||
return rotationNode ? rightKneeNode : rightAnkleNode;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Vector3f getNodePosition(String node) {
|
||||
TransformNode transformNode = getNode(node);
|
||||
return transformNode != null ? transformNode.worldTransform.getTranslation() : null;
|
||||
}
|
||||
|
||||
public Vector3f getNodePosition(TrackerPosition bodyPosition) {
|
||||
TransformNode node = getNode(bodyPosition);
|
||||
if(node == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.worldTransform.getTranslation();
|
||||
}
|
||||
|
||||
public void saveConfigs(YamlFile config) {
|
||||
// Save waist configs
|
||||
config.setProperty("body.headShift", headShift);
|
||||
config.setProperty("body.neckLength", neckLength);
|
||||
config.setProperty("body.waistDistance", waistDistance);
|
||||
config.setProperty("body.chestDistance", chestDistance);
|
||||
|
||||
// Save leg configs
|
||||
config.setProperty("body.hipsWidth", hipsWidth);
|
||||
config.setProperty("body.kneeHeight", kneeHeight);
|
||||
config.setProperty("body.legsLength", legsLength);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
|
||||
/**
|
||||
* Bridge handles sending and recieving tracker data
|
||||
|
||||
@@ -9,17 +9,17 @@ import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinError;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import dev.slimevr.Main;
|
||||
import dev.slimevr.bridge.Pipe.PipeState;
|
||||
import dev.slimevr.bridge.ProtobufMessages.ProtobufMessage;
|
||||
import dev.slimevr.bridge.ProtobufMessages.TrackerAdded;
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.HMDTracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import dev.slimevr.vr.trackers.VRTracker;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.Main;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerRole;
|
||||
import io.eiren.vr.trackers.VRTracker;
|
||||
|
||||
public class NamedPipeBridge extends ProtobufBridge<VRTracker> implements Runnable {
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinNT.HANDLE;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.bridge.Pipe.PipeState;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.HMDTracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.ComputedTracker;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
|
||||
public class NamedPipeVRBridge extends Thread implements Bridge {
|
||||
|
||||
@@ -43,7 +43,7 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
|
||||
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) {
|
||||
@@ -149,7 +149,7 @@ public class NamedPipeVRBridge extends Thread implements Bridge {
|
||||
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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.slimevr.bridge;
|
||||
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
|
||||
public class OpenVRNativeBridge implements Bridge {
|
||||
|
||||
|
||||
@@ -11,21 +11,21 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.Main;
|
||||
import dev.slimevr.bridge.ProtobufMessages.Position;
|
||||
import dev.slimevr.bridge.ProtobufMessages.ProtobufMessage;
|
||||
import dev.slimevr.bridge.ProtobufMessages.TrackerAdded;
|
||||
import dev.slimevr.bridge.ProtobufMessages.TrackerStatus;
|
||||
import dev.slimevr.bridge.ProtobufMessages.UserAction;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.HMDTracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import dev.slimevr.vr.trackers.VRTracker;
|
||||
import io.eiren.util.ann.Synchronize;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.Main;
|
||||
import io.eiren.vr.trackers.ComputedTracker;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.TrackerRole;
|
||||
import io.eiren.vr.trackers.VRTracker;
|
||||
|
||||
public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
|
||||
|
||||
@@ -189,7 +189,7 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
|
||||
protected void trackerStatusRecieved(TrackerStatus trackerStatus) {
|
||||
T tracker = getInternalRemoteTrackerById(trackerStatus.getTrackerId());
|
||||
if(tracker != null) {
|
||||
tracker.setStatus(io.eiren.vr.trackers.TrackerStatus.getById(trackerStatus.getStatusValue()));
|
||||
tracker.setStatus(dev.slimevr.vr.trackers.TrackerStatus.getById(trackerStatus.getStatusValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,11 +214,11 @@ public abstract class ProtobufBridge<T extends VRTracker> implements Bridge {
|
||||
synchronized(remoteTrackersByTrackerId) {
|
||||
Iterator<Entry<Integer, T>> iterator = remoteTrackersByTrackerId.entrySet().iterator();
|
||||
while(iterator.hasNext()) {
|
||||
iterator.next().getValue().setStatus(io.eiren.vr.trackers.TrackerStatus.DISCONNECTED);
|
||||
iterator.next().getValue().setStatus(dev.slimevr.vr.trackers.TrackerStatus.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
if(hmdTracker != null) {
|
||||
hmd.setStatus(io.eiren.vr.trackers.TrackerStatus.DISCONNECTED);
|
||||
hmd.setStatus(dev.slimevr.vr.trackers.TrackerStatus.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2506,7 +2506,7 @@ public final class ProtobufMessages {
|
||||
}
|
||||
private com.google.protobuf.MapField<java.lang.String, java.lang.String>
|
||||
internalGetMutableActionArguments() {
|
||||
onChanged();;
|
||||
onChanged();
|
||||
if (actionArguments_ == null) {
|
||||
actionArguments_ = com.google.protobuf.MapField.newMapField(
|
||||
ActionArgumentsDefaultEntryHolder.defaultEntry);
|
||||
@@ -4632,7 +4632,7 @@ public final class ProtobufMessages {
|
||||
}
|
||||
private com.google.protobuf.MapField<java.lang.String, java.lang.String>
|
||||
internalGetMutableExtra() {
|
||||
onChanged();;
|
||||
onChanged();
|
||||
if (extra_ == null) {
|
||||
extra_ = com.google.protobuf.MapField.newMapField(
|
||||
ExtraDefaultEntryHolder.defaultEntry);
|
||||
@@ -5099,7 +5099,7 @@ public final class ProtobufMessages {
|
||||
public int getNumber() {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageCase
|
||||
@@ -5791,7 +5791,7 @@ public final class ProtobufMessages {
|
||||
message_ = null;
|
||||
}
|
||||
messageCase_ = 1;
|
||||
onChanged();;
|
||||
onChanged();
|
||||
return positionBuilder_;
|
||||
}
|
||||
|
||||
@@ -5932,7 +5932,7 @@ public final class ProtobufMessages {
|
||||
message_ = null;
|
||||
}
|
||||
messageCase_ = 2;
|
||||
onChanged();;
|
||||
onChanged();
|
||||
return userActionBuilder_;
|
||||
}
|
||||
|
||||
@@ -6073,7 +6073,7 @@ public final class ProtobufMessages {
|
||||
message_ = null;
|
||||
}
|
||||
messageCase_ = 3;
|
||||
onChanged();;
|
||||
onChanged();
|
||||
return trackerAddedBuilder_;
|
||||
}
|
||||
|
||||
@@ -6214,7 +6214,7 @@ public final class ProtobufMessages {
|
||||
message_ = null;
|
||||
}
|
||||
messageCase_ = 4;
|
||||
onChanged();;
|
||||
onChanged();
|
||||
return trackerStatusBuilder_;
|
||||
}
|
||||
@java.lang.Override
|
||||
|
||||
@@ -16,14 +16,14 @@ import com.sun.jna.platform.win32.WinBase;
|
||||
import com.sun.jna.platform.win32.WinError;
|
||||
import com.sun.jna.ptr.IntByReference;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.bridge.Pipe.PipeState;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
import dev.slimevr.vr.trackers.VRTracker;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.VRTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
|
||||
public class SteamVRPipeInputBridge extends Thread implements Bridge {
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package dev.slimevr.bridge;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
|
||||
public class VMCBridge extends Thread implements Bridge {
|
||||
|
||||
|
||||
@@ -17,15 +17,15 @@ import org.json.JSONObject;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.Main;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.HMDTracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.Main;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.ComputedTracker;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
|
||||
public class WebSocketVRBridge extends WebSocketServer implements Bridge {
|
||||
|
||||
|
||||
@@ -20,5 +20,4 @@ public abstract class AbstractComponentListener implements ComponentListener {
|
||||
@Override
|
||||
public void componentHidden(ComponentEvent e) {
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,4 @@ public abstract class AbstractWindowListener implements WindowListener {
|
||||
@Override
|
||||
public void windowDeactivated(WindowEvent e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,24 +10,24 @@ import javax.swing.border.EmptyBorder;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.VRServer;
|
||||
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.autobone.AutoBone;
|
||||
import dev.slimevr.gui.swing.EJBox;
|
||||
import dev.slimevr.poserecorder.PoseFrames;
|
||||
import dev.slimevr.poserecorder.PoseFrameIO;
|
||||
import dev.slimevr.poserecorder.PoseRecorder;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
|
||||
public class AutoBoneWindow extends JFrame {
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AutoBoneWindow extends JFrame {
|
||||
private EJBox pane;
|
||||
|
||||
private final transient VRServer server;
|
||||
private final transient SkeletonConfig skeletonConfig;
|
||||
private final transient SkeletonConfigGUI skeletonConfig;
|
||||
private final transient PoseRecorder poseRecorder;
|
||||
private final transient AutoBone autoBone;
|
||||
|
||||
@@ -52,7 +52,7 @@ public class AutoBoneWindow extends JFrame {
|
||||
private JLabel processLabel;
|
||||
private JLabel lengthsLabel;
|
||||
|
||||
public AutoBoneWindow(VRServer server, SkeletonConfig skeletonConfig) {
|
||||
public AutoBoneWindow(VRServer server, SkeletonConfigGUI skeletonConfig) {
|
||||
super("Skeleton Auto-Configuration");
|
||||
|
||||
this.server = server;
|
||||
@@ -67,17 +67,14 @@ public class AutoBoneWindow extends JFrame {
|
||||
}
|
||||
|
||||
private String getLengthsString() {
|
||||
boolean first = true;
|
||||
StringBuilder configInfo = new StringBuilder("");
|
||||
for(Entry<String, Float> entry : autoBone.configs.entrySet()) {
|
||||
if(!first) {
|
||||
final StringBuilder configInfo = new StringBuilder();
|
||||
autoBone.configs.forEach((key, value) -> {
|
||||
if(configInfo.length() > 0) {
|
||||
configInfo.append(", ");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
||||
configInfo.append(entry.getKey() + ": " + StringUtils.prettyNumber(entry.getValue() * 100f, 2));
|
||||
}
|
||||
configInfo.append(key.stringVal + ": " + StringUtils.prettyNumber(value * 100f, 2));
|
||||
});
|
||||
|
||||
return configInfo.toString();
|
||||
}
|
||||
@@ -134,6 +131,7 @@ public class AutoBoneWindow extends JFrame {
|
||||
autoBone.adjustRateDecay = server.config.getFloat("autobone.adjustRateDecay", autoBone.adjustRateDecay);
|
||||
|
||||
autoBone.slideErrorFactor = server.config.getFloat("autobone.slideErrorFactor", autoBone.slideErrorFactor);
|
||||
autoBone.offsetSlideErrorFactor = server.config.getFloat("autobone.offsetSlideErrorFactor", autoBone.offsetSlideErrorFactor);
|
||||
autoBone.offsetErrorFactor = server.config.getFloat("autobone.offsetErrorFactor", autoBone.offsetErrorFactor);
|
||||
autoBone.proportionErrorFactor = server.config.getFloat("autobone.proportionErrorFactor", autoBone.proportionErrorFactor);
|
||||
autoBone.heightErrorFactor = server.config.getFloat("autobone.heightErrorFactor", autoBone.heightErrorFactor);
|
||||
@@ -339,21 +337,21 @@ public class AutoBoneWindow extends JFrame {
|
||||
applyButton.setEnabled(true);
|
||||
|
||||
//#region Stats/Values
|
||||
Float neckLength = autoBone.getConfig("Neck");
|
||||
Float chestLength = autoBone.getConfig("Chest");
|
||||
Float waistLength = autoBone.getConfig("Waist");
|
||||
Float hipWidth = autoBone.getConfig("Hips width");
|
||||
Float legsLength = autoBone.getConfig("Legs length");
|
||||
Float kneeHeight = autoBone.getConfig("Knee height");
|
||||
Float neckLength = autoBone.getConfig(SkeletonConfigValue.NECK);
|
||||
Float chestDistance = autoBone.getConfig(SkeletonConfigValue.CHEST);
|
||||
Float torsoLength = autoBone.getConfig(SkeletonConfigValue.TORSO);
|
||||
Float hipWidth = autoBone.getConfig(SkeletonConfigValue.HIPS_WIDTH);
|
||||
Float legsLength = autoBone.getConfig(SkeletonConfigValue.LEGS_LENGTH);
|
||||
Float kneeHeight = autoBone.getConfig(SkeletonConfigValue.KNEE_HEIGHT);
|
||||
|
||||
float neckWaist = neckLength != null && waistLength != null ? neckLength / waistLength : 0f;
|
||||
float chestWaist = chestLength != null && waistLength != null ? chestLength / waistLength : 0f;
|
||||
float hipWaist = hipWidth != null && waistLength != null ? hipWidth / waistLength : 0f;
|
||||
float legWaist = legsLength != null && waistLength != null ? legsLength / waistLength : 0f;
|
||||
float legBody = legsLength != null && waistLength != null && neckLength != null ? legsLength / (waistLength + neckLength) : 0f;
|
||||
float neckTorso = neckLength != null && torsoLength != null ? neckLength / torsoLength : 0f;
|
||||
float chestTorso = chestDistance != null && torsoLength != null ? chestDistance / torsoLength : 0f;
|
||||
float torsoWaist = hipWidth != null && torsoLength != null ? hipWidth / torsoLength : 0f;
|
||||
float legTorso = legsLength != null && torsoLength != null ? legsLength / torsoLength : 0f;
|
||||
float legBody = legsLength != null && torsoLength != null && neckLength != null ? legsLength / (torsoLength + neckLength) : 0f;
|
||||
float kneeLeg = kneeHeight != null && legsLength != null ? kneeHeight / legsLength : 0f;
|
||||
|
||||
LogManager.log.info("[AutoBone] Ratios: [{Neck-Waist: " + StringUtils.prettyNumber(neckWaist) + "}, {Chest-Waist: " + StringUtils.prettyNumber(chestWaist) + "}, {Hip-Waist: " + StringUtils.prettyNumber(hipWaist) + "}, {Leg-Waist: " + StringUtils.prettyNumber(legWaist) + "}, {Leg-Body: " + StringUtils.prettyNumber(legBody) + "}, {Knee-Leg: " + StringUtils.prettyNumber(kneeLeg) + "}]");
|
||||
LogManager.log.info("[AutoBone] Ratios: [{Neck-Torso: " + StringUtils.prettyNumber(neckTorso) + "}, {Chest-Torso: " + StringUtils.prettyNumber(chestTorso) + "}, {Torso-Waist: " + StringUtils.prettyNumber(torsoWaist) + "}, {Leg-Torso: " + StringUtils.prettyNumber(legTorso) + "}, {Leg-Body: " + StringUtils.prettyNumber(legBody) + "}, {Knee-Leg: " + StringUtils.prettyNumber(kneeLeg) + "}]");
|
||||
|
||||
String lengthsString = getLengthsString();
|
||||
LogManager.log.info("[AutoBone] Length values: " + lengthsString);
|
||||
|
||||
@@ -13,9 +13,9 @@ import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
|
||||
import dev.slimevr.gui.swing.EJBox;
|
||||
import dev.slimevr.vr.trackers.CalibratingTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.vr.trackers.CalibratingTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
|
||||
public class CalibrationWindow extends JFrame {
|
||||
|
||||
|
||||
64
src/main/java/dev/slimevr/gui/Keybinding.java
Normal file
64
src/main/java/dev/slimevr/gui/Keybinding.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package dev.slimevr.gui;
|
||||
|
||||
import com.melloware.jintellitype.HotkeyListener;
|
||||
import com.melloware.jintellitype.JIntellitype;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import io.eiren.util.OperatingSystem;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
public class Keybinding implements HotkeyListener {
|
||||
public final VRServer server;
|
||||
private static final int RESET = 1;
|
||||
private static final int QUICK_RESET = 2;
|
||||
|
||||
@AWTThread
|
||||
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);
|
||||
|
||||
String resetBinding = this.server.config.getString("keybindings.reset");
|
||||
if(resetBinding == null) {
|
||||
resetBinding = "CTRL+ALT+SHIFT+Y";
|
||||
this.server.config.setProperty("keybindings.reset", resetBinding);
|
||||
}
|
||||
JIntellitype.getInstance().registerHotKey(RESET, resetBinding);
|
||||
LogManager.log.info("[Keybinding] Bound reset to " + resetBinding);
|
||||
|
||||
String quickResetBinding = this.server.config.getString("keybindings.quickReset");
|
||||
if(quickResetBinding == null) {
|
||||
quickResetBinding = "CTRL+ALT+SHIFT+U";
|
||||
this.server.config.setProperty("keybindings.quickReset", quickResetBinding);
|
||||
}
|
||||
JIntellitype.getInstance().registerHotKey(QUICK_RESET, quickResetBinding);
|
||||
LogManager.log.info("[Keybinding] Bound quick reset to " + quickResetBinding);
|
||||
}
|
||||
} catch(Throwable e) {
|
||||
LogManager.log.info("[Keybinding] JIntellitype initialization failed. Keybindings will be disabled. Try restarting your computer.");
|
||||
}
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
@Override
|
||||
public void onHotKey(int identifier) {
|
||||
switch(identifier) {
|
||||
case RESET:
|
||||
LogManager.log.info("[Keybinding] Reset pressed");
|
||||
server.resetTrackers();
|
||||
break;
|
||||
case QUICK_RESET:
|
||||
LogManager.log.info("[Keybinding] Quick reset pressed");
|
||||
server.resetTrackersYaw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class ScalableFont extends Font {
|
||||
super(font);
|
||||
|
||||
if(font instanceof ScalableFont) {
|
||||
ScalableFont sourceFont = (ScalableFont)font;
|
||||
ScalableFont sourceFont = (ScalableFont) font;
|
||||
|
||||
this.initSize = sourceFont.getInitSize();
|
||||
this.initPointSize = sourceFont.getInitSize2D();
|
||||
@@ -39,7 +39,7 @@ public class ScalableFont extends Font {
|
||||
super(font);
|
||||
|
||||
if(font instanceof ScalableFont) {
|
||||
ScalableFont sourceFont = (ScalableFont)font;
|
||||
ScalableFont sourceFont = (ScalableFont) font;
|
||||
|
||||
this.initSize = sourceFont.getInitSize();
|
||||
this.initPointSize = sourceFont.getInitSize2D();
|
||||
@@ -84,8 +84,7 @@ public class ScalableFont extends Font {
|
||||
|
||||
float newPointSize = initPointSize * scale;
|
||||
|
||||
this.size = (int)(newPointSize + 0.5);
|
||||
this.size = (int) (newPointSize + 0.5);
|
||||
this.pointSize = newPointSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,21 +8,22 @@ import javax.swing.JButton;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.ButtonTimer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
|
||||
public class SkeletonConfig extends EJBagNoStretch {
|
||||
public class SkeletonConfigGUI extends EJBagNoStretch {
|
||||
|
||||
private final VRServer server;
|
||||
private final VRServerGUI gui;
|
||||
private final AutoBoneWindow autoBone;
|
||||
private Map<String, SkeletonLabel> labels = new HashMap<>();
|
||||
private Map<SkeletonConfigValue, SkeletonLabel> labels = new HashMap<>();
|
||||
|
||||
public SkeletonConfig(VRServer server, VRServerGUI gui) {
|
||||
public SkeletonConfigGUI(VRServer server, VRServerGUI gui) {
|
||||
super(false, true);
|
||||
this.server = server;
|
||||
this.gui = gui;
|
||||
@@ -91,7 +92,7 @@ public class SkeletonConfig extends EJBagNoStretch {
|
||||
row++;
|
||||
//*/
|
||||
|
||||
add(new TimedResetButton("Reset All", "All"), s(c(1, row, 2), 3, 1));
|
||||
add(new TimedResetButton("Reset All"), s(c(1, row, 2), 3, 1));
|
||||
add(new JButton("Auto") {{
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
@@ -103,75 +104,25 @@ public class SkeletonConfig extends EJBagNoStretch {
|
||||
}}, s(c(4, row, 2), 3, 1));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Chest"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Chest", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Chest"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Chest", -0.01f), c(3, row, 2));
|
||||
add(new ResetButton("Reset", "Chest"), c(4, row, 2));
|
||||
row++;
|
||||
for (SkeletonConfigValue config : SkeletonConfigValue.values) {
|
||||
add(new JLabel(config.label), c(0, row, 2));
|
||||
add(new AdjButton("+", config, 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel(config), c(2, row, 2));
|
||||
add(new AdjButton("-", config, -0.01f), c(3, row, 2));
|
||||
|
||||
add(new JLabel("Waist"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Waist", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Waist"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Waist", -0.01f), c(3, row, 2));
|
||||
add(new TimedResetButton("Reset", "Waist"), c(4, row, 2));
|
||||
row++;
|
||||
// Only use a timer on configs that need time to get into position for
|
||||
switch (config) {
|
||||
case TORSO:
|
||||
case LEGS_LENGTH:
|
||||
add(new TimedResetButton("Reset", config), c(4, row, 2));
|
||||
break;
|
||||
default:
|
||||
add(new ResetButton("Reset", config), c(4, row, 2));
|
||||
break;
|
||||
}
|
||||
|
||||
add(new JLabel("Hips width"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Hips width", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Hips width"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Hips width", -0.01f), c(3, row, 2));
|
||||
add(new ResetButton("Reset", "Hips width"), c(4, row, 2));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Legs length"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Legs length", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Legs length"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Legs length", -0.01f), c(3, row, 2));
|
||||
add(new TimedResetButton("Reset", "Legs length"), c(4, row, 2));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Knee height"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Knee height", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Knee height"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Knee height", -0.01f), c(3, row, 2));
|
||||
add(new TimedResetButton("Reset", "Knee height"), c(4, row, 2));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Foot length"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Foot length", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Foot length"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Foot length", -0.01f), c(3, row, 2));
|
||||
add(new ResetButton("Reset", "Foot length"), c(4, row, 2));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Foot offset"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Foot offset", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Foot offset"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Foot offset", -0.01f), c(3, row, 2));
|
||||
add(new ResetButton("Reset", "Foot offset"), c(4, row, 2));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Head offset"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Head", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Head"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Head", -0.01f), c(3, row, 2));
|
||||
add(new ResetButton("Reset", "Head"), c(4, row, 2));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Neck length"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Neck", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Neck"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Neck", -0.01f), c(3, row, 2));
|
||||
add(new ResetButton("Reset", "Neck"), c(4, row, 2));
|
||||
row++;
|
||||
|
||||
add(new JLabel("Virtual waist"), c(0, row, 2));
|
||||
add(new AdjButton("+", "Virtual waist", 0.01f), c(1, row, 2));
|
||||
add(new SkeletonLabel("Virtual waist"), c(2, row, 2));
|
||||
add(new AdjButton("-", "Virtual waist", -0.01f), c(3, row, 2));
|
||||
add(new ResetButton("Reset", "Virtual waist"), c(4, row, 2));
|
||||
row++;
|
||||
row++;
|
||||
}
|
||||
|
||||
gui.refresh();
|
||||
});
|
||||
@@ -186,30 +137,41 @@ public class SkeletonConfig extends EJBagNoStretch {
|
||||
});
|
||||
}
|
||||
|
||||
private void change(String joint, float diff) {
|
||||
private void change(SkeletonConfigValue joint, float diff) {
|
||||
// Update config value
|
||||
float current = server.humanPoseProcessor.getSkeletonConfig(joint);
|
||||
server.humanPoseProcessor.setSkeletonConfig(joint, current + diff);
|
||||
server.humanPoseProcessor.getSkeletonConfig().saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
|
||||
// Update GUI
|
||||
labels.get(joint).setText(StringUtils.prettyNumber((current + diff) * 100, 0));
|
||||
}
|
||||
|
||||
private void reset(String joint) {
|
||||
private void reset(SkeletonConfigValue joint) {
|
||||
// Update config value
|
||||
server.humanPoseProcessor.resetSkeletonConfig(joint);
|
||||
server.humanPoseProcessor.getSkeletonConfig().saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
if(!"All".equals(joint)) {
|
||||
float current = server.humanPoseProcessor.getSkeletonConfig(joint);
|
||||
labels.get(joint).setText(StringUtils.prettyNumber((current) * 100, 0));
|
||||
} else {
|
||||
labels.forEach((jnt, label) -> {
|
||||
float current = server.humanPoseProcessor.getSkeletonConfig(jnt);
|
||||
label.setText(StringUtils.prettyNumber((current) * 100, 0));
|
||||
});
|
||||
}
|
||||
|
||||
// Update GUI
|
||||
float current = server.humanPoseProcessor.getSkeletonConfig(joint);
|
||||
labels.get(joint).setText(StringUtils.prettyNumber((current) * 100, 0));
|
||||
}
|
||||
|
||||
private void resetAll() {
|
||||
// Update config value
|
||||
server.humanPoseProcessor.resetAllSkeletonConfigs();
|
||||
server.humanPoseProcessor.getSkeletonConfig().saveToConfig(server.config);
|
||||
server.saveConfig();
|
||||
|
||||
// Update GUI
|
||||
refreshAll();
|
||||
}
|
||||
|
||||
private class SkeletonLabel extends JLabel {
|
||||
|
||||
public SkeletonLabel(String joint) {
|
||||
public SkeletonLabel(SkeletonConfigValue joint) {
|
||||
super(StringUtils.prettyNumber(server.humanPoseProcessor.getSkeletonConfig(joint) * 100, 0));
|
||||
labels.put(joint, this);
|
||||
}
|
||||
@@ -217,7 +179,7 @@ public class SkeletonConfig extends EJBagNoStretch {
|
||||
|
||||
private class AdjButton extends JButton {
|
||||
|
||||
public AdjButton(String text, String joint, float diff) {
|
||||
public AdjButton(String text, SkeletonConfigValue joint, float diff) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
@@ -230,7 +192,7 @@ public class SkeletonConfig extends EJBagNoStretch {
|
||||
|
||||
private class ResetButton extends JButton {
|
||||
|
||||
public ResetButton(String text, String joint) {
|
||||
public ResetButton(String text, SkeletonConfigValue joint) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
@@ -243,7 +205,7 @@ public class SkeletonConfig extends EJBagNoStretch {
|
||||
|
||||
private class TimedResetButton extends JButton {
|
||||
|
||||
public TimedResetButton(String text, String joint) {
|
||||
public TimedResetButton(String text, SkeletonConfigValue joint) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
@@ -252,5 +214,15 @@ public class SkeletonConfig extends EJBagNoStretch {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public TimedResetButton(String text) {
|
||||
super(text);
|
||||
addMouseListener(new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
ButtonTimer.runTimer(TimedResetButton.this, 3, text, () -> resetAll());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,14 +9,14 @@ import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
import io.eiren.vr.processor.TransformNode;
|
||||
|
||||
public class SkeletonList extends EJBagNoStretch {
|
||||
|
||||
|
||||
@@ -17,23 +17,23 @@ import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.gui.swing.EJBoxNoStretch;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.HMDTracker;
|
||||
import dev.slimevr.vr.trackers.IMUTracker;
|
||||
import dev.slimevr.vr.trackers.ReferenceAdjustedTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerConfig;
|
||||
import dev.slimevr.vr.trackers.TrackerMountingRotation;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerWithBattery;
|
||||
import dev.slimevr.vr.trackers.TrackerWithTPS;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.ReferenceAdjustedTracker;
|
||||
import io.eiren.vr.trackers.ComputedTracker;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.IMUTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerConfig;
|
||||
import io.eiren.vr.trackers.TrackerMountingRotation;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerWithBattery;
|
||||
import io.eiren.vr.trackers.TrackerWithTPS;
|
||||
|
||||
public class TrackersList extends EJBoxNoStretch {
|
||||
|
||||
@@ -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);
|
||||
@@ -297,22 +322,42 @@ public class TrackersList extends EJBoxNoStretch {
|
||||
tps.setText(StringUtils.prettyNumber(((TrackerWithTPS) realTracker).getTPS(), 1));
|
||||
}
|
||||
if(realTracker instanceof TrackerWithBattery)
|
||||
bat.setText(StringUtils.prettyNumber(((TrackerWithBattery) realTracker).getBatteryVoltage(), 1));
|
||||
bat.setText(String.format("%d%% (%sV)", Math.round(((TrackerWithBattery) realTracker).getBatteryLevel()), StringUtils.prettyNumber(((TrackerWithBattery) realTracker).getBatteryVoltage(), 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 +365,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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,18 @@ import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
|
||||
import dev.slimevr.Main;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.bridge.NamedPipeBridge;
|
||||
import dev.slimevr.gui.swing.ButtonTimer;
|
||||
import dev.slimevr.gui.swing.EJBagNoStretch;
|
||||
import dev.slimevr.gui.swing.EJBox;
|
||||
import dev.slimevr.gui.swing.EJBoxNoStretch;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import io.eiren.util.MacOSX;
|
||||
import io.eiren.util.OperatingSystem;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.vr.Main;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.TrackerRole;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Container;
|
||||
@@ -198,11 +198,22 @@ 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 SkeletonConfig(server, VRServerGUI.this));
|
||||
add(new SkeletonConfigGUI(server, VRServerGUI.this));
|
||||
add(Box.createVerticalStrut(10));
|
||||
if(server.hasBridge(NamedPipeBridge.class)) {
|
||||
NamedPipeBridge br = server.getVRBridge(NamedPipeBridge.class);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.hardware.magentometer;
|
||||
package dev.slimevr.hardware.magentometer;
|
||||
|
||||
import com.sun.jna.Library;
|
||||
import com.sun.jna.Native;
|
||||
@@ -11,9 +11,9 @@ import java.io.FileOutputStream;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
|
||||
public final class PoseFrameIO {
|
||||
|
||||
@@ -26,7 +26,6 @@ public final class PoseFrameIO {
|
||||
if(frames != null) {
|
||||
outputStream.writeInt(frames.getTrackerCount());
|
||||
for(PoseFrameTracker tracker : frames.getTrackers()) {
|
||||
|
||||
outputStream.writeUTF(tracker.name);
|
||||
outputStream.writeInt(tracker.getFrameCount());
|
||||
for(int i = 0; i < tracker.getFrameCount(); i++) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package dev.slimevr.poserecorder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.vr.processor.ComputedHumanPoseTracker;
|
||||
import dev.slimevr.vr.processor.skeleton.SimpleSkeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.SkeletonConfigValue;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
|
||||
public class PoseFrameSkeleton extends SimpleSkeleton {
|
||||
|
||||
private int frameCursor = 0;
|
||||
|
||||
protected PoseFrameSkeleton(List<? extends ComputedHumanPoseTracker> computedTrackers) {
|
||||
super(computedTrackers);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(VRServer server, List<? extends ComputedHumanPoseTracker> computedTrackers) {
|
||||
super(server, computedTrackers);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(List<? extends Tracker> trackers, List<? extends ComputedHumanPoseTracker> computedTrackers) {
|
||||
super(trackers, computedTrackers);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(List<? extends Tracker> trackers, List<? extends ComputedHumanPoseTracker> computedTrackers, Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigValue, Float> altConfigs) {
|
||||
super(trackers, computedTrackers, configs, altConfigs);
|
||||
}
|
||||
|
||||
public PoseFrameSkeleton(List<? extends Tracker> trackers, List<? extends ComputedHumanPoseTracker> computedTrackers, Map<SkeletonConfigValue, Float> configs) {
|
||||
super(trackers, computedTrackers, configs);
|
||||
}
|
||||
|
||||
private int limitCursor() {
|
||||
if(frameCursor < 0) {
|
||||
frameCursor = 0;
|
||||
}
|
||||
|
||||
return frameCursor;
|
||||
}
|
||||
|
||||
public int setCursor(int index) {
|
||||
frameCursor = index;
|
||||
return limitCursor();
|
||||
}
|
||||
|
||||
public int incrementCursor(int increment) {
|
||||
frameCursor += increment;
|
||||
return limitCursor();
|
||||
}
|
||||
|
||||
public int incrementCursor() {
|
||||
return incrementCursor(1);
|
||||
}
|
||||
|
||||
public int getCursor() {
|
||||
return frameCursor;
|
||||
}
|
||||
|
||||
// Get tracker for specific frame
|
||||
@Override
|
||||
protected Tracker trackerPreUpdate(Tracker tracker) {
|
||||
if(tracker instanceof PoseFrameTracker) {
|
||||
// Return frame if available, otherwise return the original tracker
|
||||
TrackerFrame frame = ((PoseFrameTracker) tracker).safeGetFrame(frameCursor);
|
||||
return frame == null ? tracker : frame;
|
||||
}
|
||||
return tracker;
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import java.util.Iterator;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerConfig;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerConfig;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
|
||||
public class PoseFrameTracker implements Tracker, Iterable<TrackerFrame> {
|
||||
|
||||
@@ -137,7 +137,7 @@ public class PoseFrameTracker implements Tracker, Iterable<TrackerFrame> {
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(0, 0, 0, 1);
|
||||
store.set(Quaternion.IDENTITY);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ public class PoseFrameTracker implements Tracker, Iterable<TrackerFrame> {
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(0, 0, 0);
|
||||
store.set(Vector3f.ZERO);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ public class PoseFrameTracker implements Tracker, Iterable<TrackerFrame> {
|
||||
|
||||
@Override
|
||||
public float getConfidenceLevel() {
|
||||
return 0;
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -231,7 +231,7 @@ public class PoseFrameTracker implements Tracker, Iterable<TrackerFrame> {
|
||||
public Iterator<TrackerFrame> iterator() {
|
||||
return frames.iterator();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getTrackerId() {
|
||||
return this.trackerId;
|
||||
|
||||
@@ -4,8 +4,8 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
|
||||
public final class PoseFrames implements Iterable<TrackerFrame[]> {
|
||||
|
||||
|
||||
@@ -7,82 +7,82 @@ import java.util.concurrent.Future;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
|
||||
public class PoseRecorder {
|
||||
|
||||
|
||||
protected PoseFrames poseFrame = null;
|
||||
|
||||
|
||||
protected int numFrames = -1;
|
||||
protected int frameCursor = 0;
|
||||
protected long frameRecordingInterval = 60L;
|
||||
protected long nextFrameTimeMs = -1L;
|
||||
|
||||
|
||||
protected CompletableFuture<PoseFrames> currentRecording;
|
||||
|
||||
|
||||
protected final VRServer server;
|
||||
FastList<Pair<Tracker, PoseFrameTracker>> trackers = new FastList<Pair<Tracker, PoseFrameTracker>>();
|
||||
|
||||
|
||||
public PoseRecorder(VRServer server) {
|
||||
this.server = server;
|
||||
server.addOnTick(this::onTick);
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void onTick() {
|
||||
if (numFrames <= 0) {
|
||||
if(numFrames <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
PoseFrames poseFrame = this.poseFrame;
|
||||
List<Pair<Tracker, PoseFrameTracker>> trackers = this.trackers;
|
||||
if (poseFrame == null || trackers == null) {
|
||||
if(poseFrame == null || trackers == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (frameCursor >= numFrames) {
|
||||
|
||||
if(frameCursor >= numFrames) {
|
||||
// If done and hasn't yet, send finished recording
|
||||
stopFrameRecording();
|
||||
return;
|
||||
}
|
||||
|
||||
long curTime = System.currentTimeMillis();
|
||||
if (curTime < nextFrameTimeMs) {
|
||||
if(curTime < nextFrameTimeMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
nextFrameTimeMs += frameRecordingInterval;
|
||||
|
||||
|
||||
// To prevent duplicate frames, make sure the frame time is always in the future
|
||||
if (nextFrameTimeMs <= curTime) {
|
||||
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) {
|
||||
synchronized(this) {
|
||||
// A stopped recording will be accounted for by an empty "trackers" list
|
||||
int cursor = frameCursor++;
|
||||
for(Pair<Tracker, PoseFrameTracker> tracker : trackers) {
|
||||
// Add a frame for each tracker
|
||||
tracker.getRight().addFrame(cursor, tracker.getLeft());
|
||||
}
|
||||
|
||||
|
||||
// If done, send finished recording
|
||||
if(frameCursor >= numFrames) {
|
||||
stopFrameRecording();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long intervalMs) {
|
||||
return startFrameRecording(numFrames, intervalMs, server.getAllTrackers());
|
||||
}
|
||||
|
||||
|
||||
public synchronized Future<PoseFrames> startFrameRecording(int numFrames, long intervalMs, List<Tracker> trackers) {
|
||||
if(numFrames < 1) {
|
||||
throw new IllegalArgumentException("numFrames must at least have a value of 1");
|
||||
@@ -99,11 +99,11 @@ public class PoseRecorder {
|
||||
if(!isReadyToRecord()) {
|
||||
throw new IllegalStateException("PoseRecorder isn't ready to record!");
|
||||
}
|
||||
|
||||
|
||||
cancelFrameRecording();
|
||||
|
||||
|
||||
poseFrame = new PoseFrames(trackers.size());
|
||||
|
||||
|
||||
// Update tracker list
|
||||
this.trackers.ensureCapacity(trackers.size());
|
||||
for(Tracker tracker : trackers) {
|
||||
@@ -111,65 +111,65 @@ public class PoseRecorder {
|
||||
if(tracker == null || tracker.isComputed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// Pair tracker with recording
|
||||
this.trackers.add(Pair.of(tracker, poseFrame.addTracker(tracker, numFrames)));
|
||||
}
|
||||
|
||||
|
||||
this.frameCursor = 0;
|
||||
this.numFrames = numFrames;
|
||||
|
||||
|
||||
frameRecordingInterval = intervalMs;
|
||||
nextFrameTimeMs = -1L;
|
||||
|
||||
|
||||
LogManager.log.info("[PoseRecorder] Recording " + numFrames + " samples at a " + intervalMs + " ms frame interval");
|
||||
|
||||
|
||||
currentRecording = new CompletableFuture<PoseFrames>();
|
||||
return currentRecording;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void stopFrameRecording() {
|
||||
CompletableFuture<PoseFrames> currentRecording = this.currentRecording;
|
||||
if(currentRecording != null && !currentRecording.isDone()) {
|
||||
// Stop the recording, returning the frames recorded
|
||||
currentRecording.complete(poseFrame);
|
||||
}
|
||||
|
||||
|
||||
numFrames = -1;
|
||||
frameCursor = 0;
|
||||
trackers.clear();
|
||||
poseFrame = null;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void cancelFrameRecording() {
|
||||
CompletableFuture<PoseFrames> currentRecording = this.currentRecording;
|
||||
if(currentRecording != null && !currentRecording.isDone()) {
|
||||
// Cancel the current recording and return nothing
|
||||
currentRecording.cancel(true);
|
||||
}
|
||||
|
||||
|
||||
numFrames = -1;
|
||||
frameCursor = 0;
|
||||
trackers.clear();
|
||||
poseFrame = null;
|
||||
}
|
||||
|
||||
|
||||
public synchronized boolean isReadyToRecord() {
|
||||
return server.getTrackersCount() > 0;
|
||||
}
|
||||
|
||||
|
||||
public synchronized boolean isRecording() {
|
||||
return numFrames > frameCursor;
|
||||
}
|
||||
|
||||
|
||||
public synchronized boolean hasRecording() {
|
||||
return currentRecording != null;
|
||||
}
|
||||
|
||||
|
||||
public synchronized Future<PoseFrames> getFramesAsync() {
|
||||
return currentRecording;
|
||||
}
|
||||
|
||||
|
||||
public synchronized PoseFrames getFrames() throws ExecutionException, InterruptedException {
|
||||
CompletableFuture<PoseFrames> currentRecording = this.currentRecording;
|
||||
return currentRecording != null ? currentRecording.get() : null;
|
||||
|
||||
@@ -3,10 +3,10 @@ package dev.slimevr.poserecorder;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerConfig;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerConfig;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
|
||||
public final class TrackerFrame implements Tracker {
|
||||
|
||||
@@ -86,7 +86,7 @@ public final class TrackerFrame implements Tracker {
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(0, 0, 0, 1);
|
||||
store.set(Quaternion.IDENTITY);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public final class TrackerFrame implements Tracker {
|
||||
return true;
|
||||
}
|
||||
|
||||
store.set(0, 0, 0);
|
||||
store.set(Vector3f.ZERO);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public final class TrackerFrame implements Tracker {
|
||||
|
||||
@Override
|
||||
public float getConfidenceLevel() {
|
||||
return 0;
|
||||
return 1f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -171,7 +171,7 @@ public final class TrackerFrame implements Tracker {
|
||||
return true;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
|
||||
@Override
|
||||
public int getTrackerId() {
|
||||
return this.trackerId;
|
||||
|
||||
@@ -8,166 +8,257 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
import io.eiren.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
|
||||
public class BVHFileStream extends PoseDataStream {
|
||||
|
||||
|
||||
private static final int LONG_MAX_VALUE_DIGITS = Long.toString(Long.MAX_VALUE).length();
|
||||
private static final float POS_SCALE = 10f;
|
||||
|
||||
private static final float OFFSET_SCALE = 100f;
|
||||
private static final float POSITION_SCALE = 100f;
|
||||
|
||||
private long frameCount = 0;
|
||||
private final BufferedWriter writer;
|
||||
|
||||
|
||||
private long frameCountOffset;
|
||||
|
||||
|
||||
private float[] angleBuf = new float[3];
|
||||
private Quaternion rotBuf = new Quaternion();
|
||||
|
||||
|
||||
private HumanSkeleton wrappedSkeleton;
|
||||
private TransformNodeWrapper rootNode;
|
||||
|
||||
public BVHFileStream(OutputStream outputStream) {
|
||||
super(outputStream);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 4096);
|
||||
}
|
||||
|
||||
|
||||
public BVHFileStream(File file) throws FileNotFoundException {
|
||||
super(file);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 4096);
|
||||
}
|
||||
|
||||
|
||||
public BVHFileStream(String file) throws FileNotFoundException {
|
||||
super(file);
|
||||
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 4096);
|
||||
}
|
||||
|
||||
|
||||
private String getBufferedFrameCount(long frameCount) {
|
||||
String frameString = Long.toString(frameCount);
|
||||
int bufferCount = LONG_MAX_VALUE_DIGITS - frameString.length();
|
||||
|
||||
|
||||
return bufferCount > 0 ? frameString + StringUtils.repeat(' ', bufferCount) : frameString;
|
||||
}
|
||||
|
||||
private void writeTransformNodeHierarchy(TransformNode node) throws IOException {
|
||||
writeTransformNodeHierarchy(node, 0);
|
||||
|
||||
private TransformNodeWrapper wrapSkeletonIfNew(HumanSkeleton skeleton) {
|
||||
TransformNodeWrapper wrapper = rootNode;
|
||||
|
||||
// If the wrapped skeleton is missing or the skeleton is updated
|
||||
if(wrapper == null || skeleton != wrappedSkeleton) {
|
||||
wrapper = wrapSkeleton(skeleton);
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private void writeTransformNodeHierarchy(TransformNode node, int level) throws IOException {
|
||||
|
||||
private TransformNodeWrapper wrapSkeleton(HumanSkeleton skeleton) {
|
||||
TransformNodeWrapper wrapper = wrapSkeletonNodes(skeleton.getRootNode());
|
||||
|
||||
wrappedSkeleton = skeleton;
|
||||
rootNode = wrapper;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
protected TransformNodeWrapper wrapSkeletonNodes(TransformNode rootNode) {
|
||||
return TransformNodeWrapper.wrapFullHierarchy(rootNode);
|
||||
}
|
||||
|
||||
private void writeNodeHierarchy(TransformNodeWrapper node) throws IOException {
|
||||
writeNodeHierarchy(node, 0);
|
||||
}
|
||||
|
||||
private void writeNodeHierarchy(TransformNodeWrapper node, int level) throws IOException {
|
||||
// Don't write end sites at populated nodes
|
||||
if(node.children.isEmpty() && node.getParent().children.size() > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
String indentLevel = StringUtils.repeat("\t", level);
|
||||
String nextIndentLevel = indentLevel + "\t";
|
||||
|
||||
|
||||
// Handle ends
|
||||
if (node.children.isEmpty()) {
|
||||
if(node.children.isEmpty()) {
|
||||
writer.write(indentLevel + "End Site\n");
|
||||
} else {
|
||||
writer.write((level > 0 ? indentLevel + "JOINT " : "ROOT ") + node.getName() + "\n");
|
||||
}
|
||||
writer.write(indentLevel + "{\n");
|
||||
|
||||
if (level > 0) {
|
||||
|
||||
// Ignore the root offset and original root offset
|
||||
if(level > 0 && node.wrappedNode.getParent() != null) {
|
||||
Vector3f offset = node.localTransform.getTranslation();
|
||||
writer.write(nextIndentLevel + "OFFSET " + Float.toString(offset.getX() * POS_SCALE) + " " + Float.toString(offset.getY() * POS_SCALE) + " " + Float.toString(offset.getZ() * POS_SCALE) + "\n");
|
||||
float reverseMultiplier = node.hasReversedHierarchy() ? -1 : 1;
|
||||
writer.write(nextIndentLevel + "OFFSET " + Float.toString(offset.getX() * OFFSET_SCALE * reverseMultiplier) + " " + Float.toString(offset.getY() * OFFSET_SCALE * reverseMultiplier) + " " + Float.toString(offset.getZ() * OFFSET_SCALE * reverseMultiplier) + "\n");
|
||||
} else {
|
||||
writer.write(nextIndentLevel + "OFFSET 0.0 0.0 0.0\n");
|
||||
}
|
||||
|
||||
|
||||
// Handle ends
|
||||
if (!node.children.isEmpty()) {
|
||||
if(!node.children.isEmpty()) {
|
||||
// Only give position for root
|
||||
if (level > 0) {
|
||||
if(level > 0) {
|
||||
writer.write(nextIndentLevel + "CHANNELS 3 Zrotation Xrotation Yrotation\n");
|
||||
} else {
|
||||
writer.write(nextIndentLevel + "CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation\n");
|
||||
}
|
||||
|
||||
for (TransformNode childNode : node.children) {
|
||||
writeTransformNodeHierarchy(childNode, level + 1);
|
||||
|
||||
for(TransformNodeWrapper childNode : node.children) {
|
||||
writeNodeHierarchy(childNode, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
writer.write(indentLevel + "}\n");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeHeader(HumanSkeleton skeleton, PoseStreamer streamer) throws IOException {
|
||||
if (skeleton == null) {
|
||||
if(skeleton == null) {
|
||||
throw new NullPointerException("skeleton must not be null");
|
||||
}
|
||||
if (streamer == null) {
|
||||
if(streamer == null) {
|
||||
throw new NullPointerException("streamer must not be null");
|
||||
}
|
||||
|
||||
|
||||
writer.write("HIERARCHY\n");
|
||||
writeTransformNodeHierarchy(skeleton.getRootNode());
|
||||
|
||||
writeNodeHierarchy(wrapSkeletonIfNew(skeleton));
|
||||
|
||||
writer.write("MOTION\n");
|
||||
writer.write("Frames: ");
|
||||
|
||||
|
||||
// Get frame offset for finishing writing the file
|
||||
if (outputStream instanceof FileOutputStream) {
|
||||
FileOutputStream fileOutputStream = (FileOutputStream)outputStream;
|
||||
if(outputStream instanceof FileOutputStream) {
|
||||
FileOutputStream fileOutputStream = (FileOutputStream) outputStream;
|
||||
// Flush buffer to get proper offset
|
||||
writer.flush();
|
||||
frameCountOffset = fileOutputStream.getChannel().position();
|
||||
}
|
||||
|
||||
|
||||
writer.write(getBufferedFrameCount(frameCount) + "\n");
|
||||
|
||||
|
||||
// Frame time in seconds
|
||||
writer.write("Frame Time: " + (streamer.frameRecordingInterval / 1000d) + "\n");
|
||||
}
|
||||
|
||||
private void writeTransformHierarchyRotation(TransformNode node, Quaternion inverseRootRot) throws IOException {
|
||||
rotBuf = node.localTransform.getRotation(rotBuf);
|
||||
|
||||
// Adjust to local rotation
|
||||
if (inverseRootRot != null) {
|
||||
rotBuf = inverseRootRot.mult(rotBuf, rotBuf);
|
||||
|
||||
// Roughly based off code from https://github.com/TrackLab/ViRe/blob/50a987eff4db31036b2ebaeb5a28983cd473f267/Assets/Scripts/BVH/BVHRecorder.cs
|
||||
private float[] quatToXyzAngles(Quaternion q, float[] angles) {
|
||||
if(angles == null) {
|
||||
angles = new float[3];
|
||||
} else if(angles.length != 3) {
|
||||
throw new IllegalArgumentException("Angles array must have three elements");
|
||||
}
|
||||
|
||||
angleBuf = rotBuf.toAngles(angleBuf);
|
||||
writer.write(Float.toString((float)Math.toDegrees(angleBuf[2])) + " " + Float.toString((float)Math.toDegrees(angleBuf[0])) + " " + Float.toString((float)Math.toDegrees(angleBuf[1])));
|
||||
|
||||
// Get inverse rotation for child local rotations
|
||||
Quaternion inverseRot = node.localTransform.getRotation().inverse();
|
||||
for (TransformNode childNode : node.children) {
|
||||
if (childNode.children.isEmpty()) {
|
||||
// If it's an end node, skip
|
||||
continue;
|
||||
|
||||
float x = q.getX();
|
||||
float y = q.getY();
|
||||
float z = q.getZ();
|
||||
float w = q.getW();
|
||||
|
||||
// Roll (X)
|
||||
float sinrCosp = -2f * (x * y - w * z);
|
||||
float cosrCosp = w * w - x * x + y * y - z * z;
|
||||
angles[0] = FastMath.atan2(sinrCosp, cosrCosp);
|
||||
|
||||
// Pitch (Y)
|
||||
float sinp = 2f * (y * z + w * x);
|
||||
// Use 90 degrees if out of range
|
||||
angles[1] = FastMath.abs(sinp) >= 1f ? FastMath.copysign(FastMath.PI / 2f, sinp) : FastMath.asin(sinp);
|
||||
|
||||
// Yaw (Z)
|
||||
float sinyCosp = -2f * (x * z - w * y);
|
||||
float cosyCosp = w * w - x * x - y * y + z * z;
|
||||
angles[2] = FastMath.atan2(sinyCosp, cosyCosp);
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
private void writeNodeHierarchyRotation(TransformNodeWrapper node, Quaternion inverseRootRot) throws IOException {
|
||||
Transform transform = node.worldTransform;
|
||||
|
||||
/*
|
||||
if (node.hasReversedHierarchy()) {
|
||||
for (TransformNodeWrapper childNode : node.children) {
|
||||
// If the hierarchy is fully reversed, set the rotation for the upper bone
|
||||
if (childNode.hasReversedHierarchy()) {
|
||||
transform = childNode.worldTransform;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
rotBuf = transform.getRotation(rotBuf);
|
||||
|
||||
// Adjust to local rotation
|
||||
if(inverseRootRot != null) {
|
||||
rotBuf = rotBuf.multLocal(inverseRootRot);
|
||||
}
|
||||
|
||||
// Yaw (Z), roll (X), pitch (Y) (intrinsic)
|
||||
// angleBuf = rotBuf.toAngles(angleBuf);
|
||||
|
||||
// Roll (X), pitch (Y), yaw (Z) (intrinsic)
|
||||
angleBuf = quatToXyzAngles(rotBuf.normalizeLocal(), angleBuf);
|
||||
|
||||
// Output in order of roll (Z), pitch (X), yaw (Y) (extrinsic)
|
||||
writer.write(Float.toString(angleBuf[0] * FastMath.RAD_TO_DEG) + " " + Float.toString(angleBuf[1] * FastMath.RAD_TO_DEG) + " " + Float.toString(angleBuf[2] * FastMath.RAD_TO_DEG));
|
||||
|
||||
// Get inverse rotation for child local rotations
|
||||
if(!node.children.isEmpty()) {
|
||||
Quaternion inverseRot = transform.getRotation().inverse();
|
||||
for(TransformNodeWrapper childNode : node.children) {
|
||||
if(childNode.children.isEmpty()) {
|
||||
// If it's an end node, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add spacing
|
||||
writer.write(" ");
|
||||
writeNodeHierarchyRotation(childNode, inverseRot);
|
||||
}
|
||||
|
||||
// Add spacing
|
||||
writer.write(" ");
|
||||
writeTransformHierarchyRotation(childNode, inverseRot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeFrame(HumanSkeleton skeleton) throws IOException {
|
||||
if (skeleton == null) {
|
||||
if(skeleton == null) {
|
||||
throw new NullPointerException("skeleton must not be null");
|
||||
}
|
||||
|
||||
TransformNode root = skeleton.getRootNode();
|
||||
Vector3f rootPos = root.localTransform.getTranslation();
|
||||
|
||||
|
||||
TransformNodeWrapper rootNode = wrapSkeletonIfNew(skeleton);
|
||||
|
||||
Vector3f rootPos = rootNode.worldTransform.getTranslation();
|
||||
|
||||
// Write root position
|
||||
writer.write(Float.toString(rootPos.getX() * POS_SCALE) + " " + Float.toString(rootPos.getY() * POS_SCALE) + " " + Float.toString(rootPos.getZ() * POS_SCALE) + " ");
|
||||
writeTransformHierarchyRotation(root, null);
|
||||
|
||||
writer.write(Float.toString(rootPos.getX() * POSITION_SCALE) + " " + Float.toString(rootPos.getY() * POSITION_SCALE) + " " + Float.toString(rootPos.getZ() * POSITION_SCALE) + " ");
|
||||
writeNodeHierarchyRotation(rootNode, null);
|
||||
|
||||
writer.newLine();
|
||||
|
||||
|
||||
frameCount++;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeFooter(HumanSkeleton skeleton) throws IOException {
|
||||
// Write the final frame count for files
|
||||
if (outputStream instanceof FileOutputStream) {
|
||||
FileOutputStream fileOutputStream = (FileOutputStream)outputStream;
|
||||
if(outputStream instanceof FileOutputStream) {
|
||||
FileOutputStream fileOutputStream = (FileOutputStream) outputStream;
|
||||
// Flush before anything else
|
||||
writer.flush();
|
||||
// Seek to the count offset
|
||||
@@ -176,7 +267,7 @@ public class BVHFileStream extends PoseDataStream {
|
||||
writer.write(Long.toString(frameCount));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
writer.close();
|
||||
|
||||
@@ -6,37 +6,37 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
|
||||
public abstract class PoseDataStream implements AutoCloseable {
|
||||
|
||||
|
||||
protected boolean closed = false;
|
||||
protected final OutputStream outputStream;
|
||||
|
||||
|
||||
protected PoseDataStream(OutputStream outputStream) {
|
||||
this.outputStream = outputStream;
|
||||
}
|
||||
|
||||
|
||||
protected PoseDataStream(File file) throws FileNotFoundException {
|
||||
this(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
|
||||
protected PoseDataStream(String file) throws FileNotFoundException {
|
||||
this(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
|
||||
public void writeHeader(HumanSkeleton skeleton, PoseStreamer streamer) throws IOException {
|
||||
}
|
||||
|
||||
|
||||
abstract void writeFrame(HumanSkeleton skeleton) throws IOException;
|
||||
|
||||
|
||||
public void writeFooter(HumanSkeleton skeleton) throws IOException {
|
||||
}
|
||||
|
||||
|
||||
public boolean isClosed() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
outputStream.close();
|
||||
|
||||
@@ -2,109 +2,115 @@ package dev.slimevr.posestreamer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.skeleton.HumanSkeleton;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.processor.HumanSkeleton;
|
||||
|
||||
public class PoseStreamer {
|
||||
|
||||
|
||||
protected long frameRecordingInterval = 60L;
|
||||
protected long nextFrameTimeMs = -1L;
|
||||
|
||||
|
||||
private HumanSkeleton skeleton;
|
||||
private PoseDataStream poseFileStream;
|
||||
|
||||
|
||||
protected final VRServer server;
|
||||
|
||||
|
||||
public PoseStreamer(VRServer server) {
|
||||
this.server = server;
|
||||
|
||||
|
||||
// Register callbacks/events
|
||||
server.addSkeletonUpdatedCallback(this::onSkeletonUpdated);
|
||||
server.addOnTick(this::onTick);
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void onSkeletonUpdated(HumanSkeleton skeleton) {
|
||||
this.skeleton = skeleton;
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void onTick() {
|
||||
PoseDataStream poseFileStream = this.poseFileStream;
|
||||
if (poseFileStream == null) {
|
||||
if(poseFileStream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
HumanSkeleton skeleton = this.skeleton;
|
||||
if (skeleton == null) {
|
||||
if(skeleton == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
long curTime = System.currentTimeMillis();
|
||||
if (curTime < nextFrameTimeMs) {
|
||||
if(curTime < nextFrameTimeMs) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
nextFrameTimeMs += frameRecordingInterval;
|
||||
|
||||
|
||||
// To prevent duplicate frames, make sure the frame time is always in the future
|
||||
if (nextFrameTimeMs <= curTime) {
|
||||
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) {
|
||||
synchronized(this) {
|
||||
// Make sure the stream is open before trying to write
|
||||
if (poseFileStream.isClosed()) {
|
||||
if(poseFileStream.isClosed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
poseFileStream.writeFrame(skeleton);
|
||||
} catch (Exception e) {
|
||||
} catch(Exception e) {
|
||||
// Handle any exceptions without crashing the program
|
||||
LogManager.log.severe("[PoseStreamer] Exception while saving frame", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public synchronized void setFrameInterval(long intervalMs) {
|
||||
if(intervalMs < 1) {
|
||||
throw new IllegalArgumentException("intervalMs must at least have a value of 1");
|
||||
}
|
||||
|
||||
|
||||
this.frameRecordingInterval = intervalMs;
|
||||
}
|
||||
|
||||
|
||||
public synchronized long getFrameInterval() {
|
||||
return frameRecordingInterval;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void setOutput(PoseDataStream poseFileStream) throws IOException {
|
||||
poseFileStream.writeHeader(skeleton, this);
|
||||
this.poseFileStream = poseFileStream;
|
||||
nextFrameTimeMs = -1L; // Reset the frame timing
|
||||
}
|
||||
|
||||
|
||||
public synchronized void setOutput(PoseDataStream poseFileStream, long intervalMs) throws IOException {
|
||||
setFrameInterval(intervalMs);
|
||||
setOutput(poseFileStream);
|
||||
}
|
||||
|
||||
|
||||
public synchronized PoseDataStream getOutput() {
|
||||
return poseFileStream;
|
||||
}
|
||||
|
||||
|
||||
public synchronized void closeOutput() throws IOException {
|
||||
PoseDataStream poseFileStream = this.poseFileStream;
|
||||
|
||||
if (poseFileStream != null) {
|
||||
poseFileStream.writeFooter(skeleton);
|
||||
poseFileStream.close();
|
||||
|
||||
if(poseFileStream != null) {
|
||||
closeOutput(poseFileStream);
|
||||
this.poseFileStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void closeOutput(PoseDataStream poseFileStream) throws IOException {
|
||||
if(poseFileStream != null) {
|
||||
poseFileStream.writeFooter(skeleton);
|
||||
poseFileStream.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
63
src/main/java/dev/slimevr/posestreamer/StdBVHFileStream.java
Normal file
63
src/main/java/dev/slimevr/posestreamer/StdBVHFileStream.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
|
||||
public class StdBVHFileStream extends BVHFileStream {
|
||||
|
||||
public StdBVHFileStream(OutputStream outputStream) {
|
||||
super(outputStream);
|
||||
}
|
||||
|
||||
public StdBVHFileStream(File file) throws FileNotFoundException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
public StdBVHFileStream(String file) throws FileNotFoundException {
|
||||
super(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransformNodeWrapper wrapSkeletonNodes(TransformNode rootNode) {
|
||||
TransformNode newRoot = getNodeFromHierarchy(rootNode, "Hip");
|
||||
if(newRoot == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
private TransformNode getNodeFromHierarchy(TransformNode node, String name) {
|
||||
if(node.getName().equalsIgnoreCase(name)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
for(TransformNode child : node.children) {
|
||||
TransformNode result = getNodeFromHierarchy(child, name);
|
||||
if(result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
158
src/main/java/dev/slimevr/posestreamer/TransformNodeWrapper.java
Normal file
158
src/main/java/dev/slimevr/posestreamer/TransformNodeWrapper.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package dev.slimevr.posestreamer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import io.eiren.util.collections.FastList;
|
||||
|
||||
public class TransformNodeWrapper {
|
||||
|
||||
public final TransformNode wrappedNode;
|
||||
|
||||
protected String name;
|
||||
|
||||
public final Transform localTransform;
|
||||
public final Transform worldTransform;
|
||||
|
||||
private boolean reversedHierarchy = false;
|
||||
|
||||
protected TransformNodeWrapper parent;
|
||||
public final List<TransformNodeWrapper> children;
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, String name, boolean reversedHierarchy, int initialChildCapacity) {
|
||||
this.wrappedNode = nodeToWrap;
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.localTransform = nodeToWrap.localTransform;
|
||||
this.worldTransform = nodeToWrap.worldTransform;
|
||||
|
||||
this.reversedHierarchy = reversedHierarchy;
|
||||
|
||||
this.children = new FastList<>(initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, String name, int initialChildCapacity) {
|
||||
this(nodeToWrap, name, false, initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, String name) {
|
||||
this(nodeToWrap, name, false, 5);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, boolean reversedHierarchy, int initialChildCapacity) {
|
||||
this(nodeToWrap, nodeToWrap.getName(), reversedHierarchy, initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, boolean reversedHierarchy) {
|
||||
this(nodeToWrap, nodeToWrap.getName(), reversedHierarchy, 5);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap, int initialChildCapacity) {
|
||||
this(nodeToWrap, nodeToWrap.getName(), initialChildCapacity);
|
||||
}
|
||||
|
||||
public TransformNodeWrapper(TransformNode nodeToWrap) {
|
||||
this(nodeToWrap, nodeToWrap.getName());
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapFullHierarchy(TransformNode root) {
|
||||
return wrapNodeHierarchyUp(wrapHierarchyDown(root));
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapHierarchyDown(TransformNode root) {
|
||||
return wrapNodeHierarchyDown(new TransformNodeWrapper(root, root.children.size()));
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapNodeHierarchyDown(TransformNodeWrapper root) {
|
||||
for(TransformNode child : root.wrappedNode.children) {
|
||||
root.attachChild(wrapHierarchyDown(child));
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapHierarchyUp(TransformNode root) {
|
||||
return wrapNodeHierarchyUp(new TransformNodeWrapper(root, root.getParent() != null ? 1 : 0));
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapNodeHierarchyUp(TransformNodeWrapper root) {
|
||||
return wrapNodeHierarchyUp(root.wrappedNode, root);
|
||||
}
|
||||
|
||||
public static TransformNodeWrapper wrapNodeHierarchyUp(TransformNode root, TransformNodeWrapper target) {
|
||||
TransformNode parent = root.getParent();
|
||||
if(parent == null) {
|
||||
return target;
|
||||
}
|
||||
|
||||
// Flip the offset for these reversed nodes
|
||||
TransformNodeWrapper wrapper = new TransformNodeWrapper(parent, true, (parent.getParent() != null ? 1 : 0) + Math.max(0, parent.children.size() - 1));
|
||||
target.attachChild(wrapper);
|
||||
|
||||
// Re-attach other children
|
||||
if(parent.children.size() > 1) {
|
||||
for(TransformNode child : parent.children) {
|
||||
// Skip the original node
|
||||
if(child == target.wrappedNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wrapper.attachChild(wrapHierarchyDown(child));
|
||||
}
|
||||
}
|
||||
|
||||
// Continue up the hierarchy
|
||||
wrapNodeHierarchyUp(wrapper);
|
||||
// Return original node
|
||||
return target;
|
||||
}
|
||||
|
||||
public boolean hasReversedHierarchy() {
|
||||
return reversedHierarchy;
|
||||
}
|
||||
|
||||
public void setReversedHierarchy(boolean reversedHierarchy) {
|
||||
this.reversedHierarchy = reversedHierarchy;
|
||||
}
|
||||
|
||||
public boolean hasLocalRotation() {
|
||||
return wrappedNode.localRotation;
|
||||
}
|
||||
|
||||
public Quaternion calculateLocalRotation(Quaternion relativeTo, Quaternion result) {
|
||||
return calculateLocalRotationInverse(relativeTo.inverse(), result);
|
||||
}
|
||||
|
||||
public Quaternion calculateLocalRotationInverse(Quaternion inverseRelativeTo, Quaternion result) {
|
||||
if(result == null) {
|
||||
result = new Quaternion();
|
||||
}
|
||||
|
||||
return worldTransform.getRotation().mult(inverseRelativeTo, result);
|
||||
}
|
||||
|
||||
public void attachChild(TransformNodeWrapper node) {
|
||||
if(node.parent != null) {
|
||||
throw new IllegalArgumentException("The child node must not already have a parent");
|
||||
}
|
||||
|
||||
this.children.add(node);
|
||||
node.parent = this;
|
||||
}
|
||||
|
||||
public TransformNodeWrapper getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.util.ann;
|
||||
package dev.slimevr.util.ann;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
@@ -1,17 +1,17 @@
|
||||
package io.eiren.vr.processor;
|
||||
package dev.slimevr.vr.processor;
|
||||
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import dev.slimevr.vr.trackers.TrackerWithTPS;
|
||||
import io.eiren.util.BufferedTimer;
|
||||
import io.eiren.vr.trackers.ComputedTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.TrackerRole;
|
||||
import io.eiren.vr.trackers.TrackerWithTPS;
|
||||
|
||||
public class ComputedHumanPoseTracker extends ComputedTracker implements TrackerWithTPS, ShareableTracker {
|
||||
|
||||
public final ComputedHumanPoseTrackerPosition skeletonPosition;
|
||||
protected final TrackerRole trackerRole;
|
||||
protected BufferedTimer timer = new BufferedTimer(1f);
|
||||
|
||||
|
||||
public ComputedHumanPoseTracker(int trackerId, ComputedHumanPoseTrackerPosition skeletonPosition, TrackerRole role) {
|
||||
super(trackerId, "human://" + skeletonPosition.name(), true, true);
|
||||
this.skeletonPosition = skeletonPosition;
|
||||
@@ -27,7 +27,7 @@ public class ComputedHumanPoseTracker extends ComputedTracker implements Tracker
|
||||
public void dataTick() {
|
||||
timer.update();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TrackerRole getTrackerRole() {
|
||||
return trackerRole;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.processor;
|
||||
package dev.slimevr.vr.processor;
|
||||
|
||||
public enum ComputedHumanPoseTrackerPosition {
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package io.eiren.vr.processor;
|
||||
package dev.slimevr.vr.processor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
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.HMDTracker;
|
||||
import dev.slimevr.vr.trackers.ShareableTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.util.collections.FastList;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.ShareableTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerRole;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
|
||||
public class HumanPoseProcessor {
|
||||
|
||||
@@ -19,7 +23,7 @@ public class HumanPoseProcessor {
|
||||
private final List<ComputedHumanPoseTracker> computedTrackers = new FastList<>();
|
||||
private final List<Consumer<HumanSkeleton>> onSkeletonUpdated = new FastList<>();
|
||||
private HumanSkeleton skeleton;
|
||||
|
||||
|
||||
public HumanPoseProcessor(VRServer server, HMDTracker hmd) {
|
||||
this.server = server;
|
||||
computedTrackers.add(new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.WAIST, TrackerRole.WAIST));
|
||||
@@ -33,7 +37,7 @@ public class HumanPoseProcessor {
|
||||
public HumanSkeleton getSkeleton() {
|
||||
return skeleton;
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void addSkeletonUpdatedCallback(Consumer<HumanSkeleton> consumer) {
|
||||
onSkeletonUpdated.add(consumer);
|
||||
@@ -42,23 +46,32 @@ public class HumanPoseProcessor {
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public void setSkeletonConfig(String key, float newLength) {
|
||||
public void setSkeletonConfig(SkeletonConfigValue key, float newLength) {
|
||||
if(skeleton != null)
|
||||
skeleton.setSkeletonConfig(key, newLength);
|
||||
skeleton.getSkeletonConfig().setConfig(key, newLength);
|
||||
}
|
||||
|
||||
|
||||
@ThreadSafe
|
||||
public void resetSkeletonConfig(String key) {
|
||||
public void resetSkeletonConfig(SkeletonConfigValue key) {
|
||||
if(skeleton != null)
|
||||
skeleton.resetSkeletonConfig(key);
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public float getSkeletonConfig(String key) {
|
||||
public void resetAllSkeletonConfigs() {
|
||||
if(skeleton != null)
|
||||
skeleton.resetAllSkeletonConfigs();
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public SkeletonConfig getSkeletonConfig() {
|
||||
return skeleton.getSkeletonConfig();
|
||||
}
|
||||
|
||||
@ThreadSafe
|
||||
public float getSkeletonConfig(SkeletonConfigValue key) {
|
||||
if(skeleton != null) {
|
||||
Number f = skeleton.getSkeletonConfig().get(key);
|
||||
if(f != null)
|
||||
return f.floatValue();
|
||||
return skeleton.getSkeletonConfig().getConfig(key);
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
@@ -67,44 +80,44 @@ public class HumanPoseProcessor {
|
||||
public List<? extends ShareableTracker> getComputedTrackers() {
|
||||
return computedTrackers;
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void trackerAdded(Tracker tracker) {
|
||||
updateSekeltonModel();
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void trackerUpdated(Tracker tracker) {
|
||||
updateSekeltonModel();
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
private void updateSekeltonModel() {
|
||||
disconnectAllTrackers();
|
||||
skeleton = new HumanSkeletonWithLegs(server, computedTrackers);
|
||||
skeleton = new SimpleSkeleton(server, computedTrackers);
|
||||
for(int i = 0; i < onSkeletonUpdated.size(); ++i)
|
||||
onSkeletonUpdated.get(i).accept(skeleton);
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
private void disconnectAllTrackers() {
|
||||
for(int i = 0; i < computedTrackers.size(); ++i) {
|
||||
computedTrackers.get(i).setStatus(TrackerStatus.DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void update() {
|
||||
if(skeleton != null)
|
||||
skeleton.updatePose();
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void resetTrackers() {
|
||||
if(skeleton != null)
|
||||
skeleton.resetTrackersFull();
|
||||
}
|
||||
|
||||
|
||||
@VRServerThread
|
||||
public void resetTrackersYaw() {
|
||||
if(skeleton != null)
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.processor;
|
||||
package dev.slimevr.vr.processor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
@@ -26,10 +26,18 @@ public class TransformNode {
|
||||
}
|
||||
|
||||
public void attachChild(TransformNode node) {
|
||||
if(node.parent != null) {
|
||||
throw new IllegalArgumentException("The child node must not already have a parent");
|
||||
}
|
||||
|
||||
this.children.add(node);
|
||||
node.parent = this;
|
||||
}
|
||||
|
||||
public TransformNode getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
updateWorldTransforms(); // Call update on each frame because we have relatively few nodes
|
||||
for(int i = 0; i < children.size(); ++i)
|
||||
@@ -58,14 +66,11 @@ public class TransformNode {
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void combineWithParentGlobalRotation(Transform parent) {
|
||||
worldTransform.getScale().multLocal(parent.getScale());
|
||||
worldTransform.getTranslation().multLocal(parent.getScale());
|
||||
|
||||
parent
|
||||
.getRotation()
|
||||
.multLocal(worldTransform.getTranslation())
|
||||
.addLocal(parent.getTranslation());
|
||||
}
|
||||
|
||||
public void combineWithParentGlobalRotation(Transform parent) {
|
||||
worldTransform.getScale().multLocal(parent.getScale());
|
||||
worldTransform.getTranslation().multLocal(parent.getScale());
|
||||
|
||||
parent.getRotation().multLocal(worldTransform.getTranslation()).addLocal(parent.getTranslation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
|
||||
public abstract class HumanSkeleton {
|
||||
|
||||
@VRServerThread
|
||||
public abstract void updatePose();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract TransformNode getRootNode();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract SkeletonConfig getSkeletonConfig();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract void resetSkeletonConfig(SkeletonConfigValue config);
|
||||
|
||||
@ThreadSafe
|
||||
public void resetAllSkeletonConfigs() {
|
||||
for(SkeletonConfigValue config : SkeletonConfigValue.values) {
|
||||
resetSkeletonConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
@VRServerThread
|
||||
public abstract void resetTrackersFull();
|
||||
|
||||
@VRServerThread
|
||||
public abstract void resetTrackersYaw();
|
||||
}
|
||||
@@ -0,0 +1,826 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import dev.slimevr.VRServer;
|
||||
import dev.slimevr.util.ann.VRServerThread;
|
||||
import dev.slimevr.vr.processor.ComputedHumanPoseTracker;
|
||||
import dev.slimevr.vr.processor.ComputedHumanPoseTrackerPosition;
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import dev.slimevr.vr.trackers.TrackerPosition;
|
||||
import dev.slimevr.vr.trackers.TrackerRole;
|
||||
import dev.slimevr.vr.trackers.TrackerStatus;
|
||||
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);
|
||||
//#endregion
|
||||
|
||||
//#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 Tracker Input
|
||||
protected Tracker hmdTracker;
|
||||
protected Tracker chestTracker;
|
||||
protected Tracker waistTracker;
|
||||
protected Tracker hipTracker;
|
||||
|
||||
protected Tracker leftLegTracker;
|
||||
protected Tracker leftAnkleTracker;
|
||||
protected Tracker leftFootTracker;
|
||||
|
||||
protected Tracker rightLegTracker;
|
||||
protected Tracker rightAnkleTracker;
|
||||
protected Tracker rightFootTracker;
|
||||
//#endregion
|
||||
|
||||
//#region Tracker Output
|
||||
protected ComputedHumanPoseTracker computedChestTracker;
|
||||
protected ComputedHumanPoseTracker computedWaistTracker;
|
||||
|
||||
protected ComputedHumanPoseTracker computedLeftKneeTracker;
|
||||
protected ComputedHumanPoseTracker computedLeftFootTracker;
|
||||
|
||||
protected ComputedHumanPoseTracker computedRightKneeTracker;
|
||||
protected ComputedHumanPoseTracker computedRightFootTracker;
|
||||
//#endregion
|
||||
|
||||
protected boolean extendedPelvisModel = true;
|
||||
protected boolean extendedKneeModel = false;
|
||||
|
||||
public final SkeletonConfig skeletonConfig;
|
||||
|
||||
//#region Buffers
|
||||
private Vector3f posBuf = new Vector3f();
|
||||
|
||||
private Quaternion rotBuf1 = new Quaternion();
|
||||
private Quaternion rotBuf2 = new Quaternion();
|
||||
|
||||
protected final Vector3f hipVector = new Vector3f();
|
||||
protected final Vector3f ankleVector = new Vector3f();
|
||||
|
||||
protected final Quaternion kneeRotation = new Quaternion();
|
||||
//#endregion
|
||||
|
||||
//#region Constructors
|
||||
protected SimpleSkeleton(List<? extends ComputedHumanPoseTracker> computedTrackers) {
|
||||
//#region Assemble skeleton to hip
|
||||
hmdNode.attachChild(headNode);
|
||||
headNode.attachChild(neckNode);
|
||||
neckNode.attachChild(chestNode);
|
||||
chestNode.attachChild(waistNode);
|
||||
waistNode.attachChild(hipNode);
|
||||
//#endregion
|
||||
|
||||
//#region Assemble skeleton to feet
|
||||
hipNode.attachChild(leftHipNode);
|
||||
hipNode.attachChild(rightHipNode);
|
||||
|
||||
leftHipNode.attachChild(leftKneeNode);
|
||||
rightHipNode.attachChild(rightKneeNode);
|
||||
|
||||
leftKneeNode.attachChild(leftAnkleNode);
|
||||
rightKneeNode.attachChild(rightAnkleNode);
|
||||
|
||||
leftAnkleNode.attachChild(leftFootNode);
|
||||
rightAnkleNode.attachChild(rightFootNode);
|
||||
//#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);
|
||||
//#endregion
|
||||
|
||||
// Set default skeleton configuration (callback automatically sets initial offsets)
|
||||
skeletonConfig = new SkeletonConfig(true, this);
|
||||
|
||||
if(computedTrackers != null) {
|
||||
setComputedTrackers(computedTrackers);
|
||||
}
|
||||
fillNullComputedTrackers(true);
|
||||
}
|
||||
|
||||
public SimpleSkeleton(VRServer server, List<? extends ComputedHumanPoseTracker> computedTrackers) {
|
||||
this(computedTrackers);
|
||||
setTrackersFromServer(server);
|
||||
skeletonConfig.loadFromConfig(server.config);
|
||||
}
|
||||
|
||||
public SimpleSkeleton(List<? extends Tracker> trackers, List<? extends ComputedHumanPoseTracker> computedTrackers) {
|
||||
this(computedTrackers);
|
||||
|
||||
if(trackers != null) {
|
||||
setTrackersFromList(trackers);
|
||||
} else {
|
||||
setTrackersFromList(new FastList<Tracker>(0));
|
||||
}
|
||||
}
|
||||
|
||||
public SimpleSkeleton(List<? extends Tracker> trackers, List<? extends ComputedHumanPoseTracker> computedTrackers, Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigValue, Float> altConfigs) {
|
||||
// Initialize
|
||||
this(trackers, computedTrackers);
|
||||
|
||||
// Set configs
|
||||
if(altConfigs != null) {
|
||||
// Set alts first, so if there's any overlap it doesn't affect the values
|
||||
skeletonConfig.setConfigs(altConfigs, null);
|
||||
}
|
||||
skeletonConfig.setConfigs(configs, null);
|
||||
}
|
||||
|
||||
public SimpleSkeleton(List<? extends Tracker> trackers, List<? extends ComputedHumanPoseTracker> computedTrackers, Map<SkeletonConfigValue, Float> configs) {
|
||||
this(trackers, computedTrackers, configs, null);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Set Trackers
|
||||
public void setTrackersFromList(List<? extends Tracker> trackers, boolean setHmd) {
|
||||
if(setHmd) {
|
||||
this.hmdTracker = TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.HMD);
|
||||
}
|
||||
|
||||
this.chestTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.CHEST, TrackerPosition.WAIST, TrackerPosition.HIP);
|
||||
this.waistTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.WAIST, TrackerPosition.CHEST, TrackerPosition.HIP);
|
||||
this.hipTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.HIP, TrackerPosition.WAIST, TrackerPosition.CHEST);
|
||||
|
||||
this.leftLegTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.LEFT_LEG, TrackerPosition.LEFT_ANKLE, null);
|
||||
this.leftAnkleTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(trackers, TrackerPosition.LEFT_ANKLE, TrackerPosition.LEFT_LEG, null);
|
||||
this.leftFootTracker = TrackerUtils.findTrackerForBodyPosition(trackers, TrackerPosition.LEFT_FOOT);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void setTrackersFromList(List<? extends Tracker> trackers) {
|
||||
setTrackersFromList(trackers, true);
|
||||
}
|
||||
|
||||
public void setTrackersFromServer(VRServer server) {
|
||||
this.hmdTracker = server.hmdTracker;
|
||||
setTrackersFromList(server.getAllTrackers(), false);
|
||||
}
|
||||
|
||||
public void setComputedTracker(ComputedHumanPoseTracker tracker) {
|
||||
switch(tracker.getTrackerRole()) {
|
||||
case CHEST:
|
||||
computedChestTracker = tracker;
|
||||
break;
|
||||
case WAIST:
|
||||
computedWaistTracker = tracker;
|
||||
break;
|
||||
|
||||
case LEFT_KNEE:
|
||||
computedLeftKneeTracker = tracker;
|
||||
break;
|
||||
case LEFT_FOOT:
|
||||
computedLeftFootTracker = tracker;
|
||||
break;
|
||||
|
||||
case RIGHT_KNEE:
|
||||
computedRightKneeTracker = tracker;
|
||||
break;
|
||||
case RIGHT_FOOT:
|
||||
computedRightFootTracker = tracker;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setComputedTrackers(List<? extends ComputedHumanPoseTracker> trackers) {
|
||||
for(int i = 0; i < trackers.size(); ++i) {
|
||||
setComputedTracker(trackers.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void setComputedTrackersAndFillNull(List<? extends ComputedHumanPoseTracker> trackers, boolean onlyFillWaistAndFeet) {
|
||||
setComputedTrackers(trackers);
|
||||
fillNullComputedTrackers(onlyFillWaistAndFeet);
|
||||
}
|
||||
|
||||
public void fillNullComputedTrackers(boolean onlyFillWaistAndFeet) {
|
||||
if(computedWaistTracker == null) {
|
||||
computedWaistTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.WAIST, TrackerRole.WAIST);
|
||||
computedWaistTracker.setStatus(TrackerStatus.OK);
|
||||
}
|
||||
|
||||
if(computedLeftFootTracker == null) {
|
||||
computedLeftFootTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.LEFT_FOOT, TrackerRole.LEFT_FOOT);
|
||||
computedLeftFootTracker.setStatus(TrackerStatus.OK);
|
||||
}
|
||||
|
||||
if(computedRightFootTracker == null) {
|
||||
computedRightFootTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.RIGHT_FOOT, TrackerRole.RIGHT_FOOT);
|
||||
computedRightFootTracker.setStatus(TrackerStatus.OK);
|
||||
}
|
||||
|
||||
if(!onlyFillWaistAndFeet) {
|
||||
if(computedChestTracker == null) {
|
||||
computedChestTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.CHEST, TrackerRole.CHEST);
|
||||
computedChestTracker.setStatus(TrackerStatus.OK);
|
||||
}
|
||||
|
||||
if(computedLeftKneeTracker == null) {
|
||||
computedLeftKneeTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.LEFT_KNEE, TrackerRole.LEFT_KNEE);
|
||||
computedLeftKneeTracker.setStatus(TrackerStatus.OK);
|
||||
}
|
||||
|
||||
if(computedRightKneeTracker == null) {
|
||||
computedRightKneeTracker = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.RIGHT_KNEE, TrackerRole.RIGHT_KNEE);
|
||||
computedRightKneeTracker.setStatus(TrackerStatus.OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Get Trackers
|
||||
public ComputedHumanPoseTracker getComputedTracker(TrackerRole trackerRole) {
|
||||
switch(trackerRole) {
|
||||
case CHEST:
|
||||
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;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Processing
|
||||
// Useful for sub-classes that need to return a sub-tracker (like PoseFrameTracker -> TrackerFrame)
|
||||
protected Tracker trackerPreUpdate(Tracker tracker) {
|
||||
return tracker;
|
||||
}
|
||||
|
||||
// Updates the pose from tracker positions
|
||||
@VRServerThread
|
||||
@Override
|
||||
public void updatePose() {
|
||||
updateLocalTransforms();
|
||||
hmdNode.update();
|
||||
updateComputedTrackers();
|
||||
}
|
||||
|
||||
//#region Update the node transforms from the trackers
|
||||
protected void updateLocalTransforms() {
|
||||
//#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);
|
||||
//#endregion
|
||||
|
||||
if(hmdTracker != null) {
|
||||
if(hmdTracker.getPosition(posBuf)) {
|
||||
hmdNode.localTransform.setTranslation(posBuf);
|
||||
}
|
||||
if(hmdTracker.getRotation(rotBuf1)) {
|
||||
hmdNode.localTransform.setRotation(rotBuf1);
|
||||
headNode.localTransform.setRotation(rotBuf1);
|
||||
}
|
||||
} else {
|
||||
// Set to zero
|
||||
hmdNode.localTransform.setTranslation(Vector3f.ZERO);
|
||||
hmdNode.localTransform.setRotation(Quaternion.IDENTITY);
|
||||
headNode.localTransform.setRotation(Quaternion.IDENTITY);
|
||||
}
|
||||
|
||||
if(chestTracker.getRotation(rotBuf1)) {
|
||||
neckNode.localTransform.setRotation(rotBuf1);
|
||||
}
|
||||
if(waistTracker.getRotation(rotBuf1)) {
|
||||
chestNode.localTransform.setRotation(rotBuf1);
|
||||
trackerChestNode.localTransform.setRotation(rotBuf1);
|
||||
}
|
||||
if(hipTracker.getRotation(rotBuf1)) {
|
||||
waistNode.localTransform.setRotation(rotBuf1);
|
||||
trackerWaistNode.localTransform.setRotation(rotBuf1);
|
||||
hipNode.localTransform.setRotation(rotBuf1);
|
||||
}
|
||||
|
||||
// Left Leg
|
||||
leftLegTracker.getRotation(rotBuf1);
|
||||
leftAnkleTracker.getRotation(rotBuf2);
|
||||
|
||||
if(extendedKneeModel)
|
||||
calculateKneeLimits(rotBuf1, rotBuf2, leftLegTracker.getConfidenceLevel(), leftAnkleTracker.getConfidenceLevel());
|
||||
|
||||
leftHipNode.localTransform.setRotation(rotBuf1);
|
||||
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
|
||||
rightLegTracker.getRotation(rotBuf1);
|
||||
rightAnkleTracker.getRotation(rotBuf2);
|
||||
|
||||
if(extendedKneeModel)
|
||||
calculateKneeLimits(rotBuf1, rotBuf2, rightLegTracker.getConfidenceLevel(), rightAnkleTracker.getConfidenceLevel());
|
||||
|
||||
rightHipNode.localTransform.setRotation(rotBuf1);
|
||||
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) {
|
||||
// Average pelvis between two legs
|
||||
leftHipNode.localTransform.getRotation(rotBuf1);
|
||||
rightHipNode.localTransform.getRotation(rotBuf2);
|
||||
rotBuf2.nlerp(rotBuf1, 0.5f);
|
||||
chestNode.localTransform.getRotation(rotBuf1);
|
||||
rotBuf2.nlerp(rotBuf1, 0.3333333f);
|
||||
hipNode.localTransform.setRotation(rotBuf2);
|
||||
//trackerWaistNode.localTransform.setRotation(rotBuf2); // <== Provides cursed results from my test in VRChat when sitting or laying down -Erimel
|
||||
// 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
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Knee Model
|
||||
// Knee basically has only 1 DoF (pitch), average yaw and roll between knee and hip
|
||||
protected void calculateKneeLimits(Quaternion hipBuf, Quaternion kneeBuf, float hipConfidence, float kneeConfidence) {
|
||||
ankleVector.set(0, -1, 0);
|
||||
hipVector.set(0, -1, 0);
|
||||
hipBuf.multLocal(hipVector);
|
||||
kneeBuf.multLocal(ankleVector);
|
||||
kneeRotation.angleBetweenVectors(hipVector, ankleVector); // Find knee angle
|
||||
|
||||
// Substract knee angle from knee rotation. With perfect leg and perfect
|
||||
// sensors result should match hip rotation perfectly
|
||||
kneeBuf.multLocal(kneeRotation.inverse());
|
||||
|
||||
// Average knee and hip with a slerp
|
||||
hipBuf.slerp(kneeBuf, 0.5f); // TODO : Use confidence to calculate changeAmt
|
||||
kneeBuf.set(hipBuf);
|
||||
|
||||
// Return knee angle into knee rotation
|
||||
kneeBuf.multLocal(kneeRotation);
|
||||
}
|
||||
|
||||
public static float normalizeRad(float angle) {
|
||||
return FastMath.normalize(angle, -FastMath.PI, FastMath.PI);
|
||||
}
|
||||
|
||||
public static float interpolateRadians(float factor, float start, float end) {
|
||||
float angle = FastMath.abs(end - start);
|
||||
if(angle > FastMath.PI) {
|
||||
if(end > start) {
|
||||
start += FastMath.TWO_PI;
|
||||
} else {
|
||||
end += FastMath.TWO_PI;
|
||||
}
|
||||
}
|
||||
float val = start + (end - start) * factor;
|
||||
return normalizeRad(val);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Update the output trackers
|
||||
protected void updateComputedTrackers() {
|
||||
if(computedChestTracker != null) {
|
||||
computedChestTracker.position.set(trackerChestNode.worldTransform.getTranslation());
|
||||
computedChestTracker.rotation.set(neckNode.worldTransform.getRotation());
|
||||
computedChestTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedWaistTracker != null) {
|
||||
computedWaistTracker.position.set(trackerWaistNode.worldTransform.getTranslation());
|
||||
computedWaistTracker.rotation.set(trackerWaistNode.worldTransform.getRotation());
|
||||
computedWaistTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedLeftKneeTracker != null) {
|
||||
computedLeftKneeTracker.position.set(trackerLeftKneeNode.worldTransform.getTranslation());
|
||||
computedLeftKneeTracker.rotation.set(leftHipNode.worldTransform.getRotation());
|
||||
computedLeftKneeTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedLeftFootTracker != null) {
|
||||
computedLeftFootTracker.position.set(trackerLeftFootNode.worldTransform.getTranslation());
|
||||
computedLeftFootTracker.rotation.set(trackerLeftFootNode.worldTransform.getRotation());
|
||||
computedLeftFootTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedRightKneeTracker != null) {
|
||||
computedRightKneeTracker.position.set(trackerRightKneeNode.worldTransform.getTranslation());
|
||||
computedRightKneeTracker.rotation.set(rightHipNode.worldTransform.getRotation());
|
||||
computedRightKneeTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedRightFootTracker != null) {
|
||||
computedRightFootTracker.position.set(trackerRightFootNode.worldTransform.getTranslation());
|
||||
computedRightFootTracker.rotation.set(trackerRightFootNode.worldTransform.getRotation());
|
||||
computedRightFootTracker.dataTick();
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
//#endregion
|
||||
|
||||
//#region Skeleton Config
|
||||
@Override
|
||||
public void updateConfigState(SkeletonConfigValue config, float newValue) {
|
||||
// Do nothing, the node offset callback handles all that's needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateToggleState(SkeletonConfigToggle configToggle, boolean newValue) {
|
||||
if(configToggle == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache the values of these configs
|
||||
switch(configToggle) {
|
||||
case EXTENDED_PELVIS_MODEL:
|
||||
extendedPelvisModel = newValue;
|
||||
break;
|
||||
case EXTENDED_KNEE_MODEL:
|
||||
extendedKneeModel = newValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateNodeOffset(SkeletonNodeOffset nodeOffset, Vector3f offset) {
|
||||
if(nodeOffset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch(nodeOffset) {
|
||||
case HEAD:
|
||||
headNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case NECK:
|
||||
neckNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case CHEST:
|
||||
chestNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case CHEST_TRACKER:
|
||||
trackerChestNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case WAIST:
|
||||
waistNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case HIP:
|
||||
hipNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case HIP_TRACKER:
|
||||
trackerWaistNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
|
||||
case LEFT_HIP:
|
||||
leftHipNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case RIGHT_HIP:
|
||||
rightHipNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
|
||||
case KNEE:
|
||||
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);
|
||||
break;
|
||||
case FOOT:
|
||||
leftFootNode.localTransform.setTranslation(offset);
|
||||
rightFootNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
case FOOT_TRACKER:
|
||||
trackerLeftFootNode.localTransform.setTranslation(offset);
|
||||
trackerRightFootNode.localTransform.setTranslation(offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePoseAffectedByConfig(SkeletonConfigValue config) {
|
||||
switch(config) {
|
||||
case HEAD:
|
||||
headNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case NECK:
|
||||
neckNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case TORSO:
|
||||
hipNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case CHEST:
|
||||
chestNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case WAIST:
|
||||
waistNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case HIP_OFFSET:
|
||||
trackerWaistNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case HIPS_WIDTH:
|
||||
leftHipNode.update();
|
||||
rightHipNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case KNEE_HEIGHT:
|
||||
leftKneeNode.update();
|
||||
rightKneeNode.update();
|
||||
break;
|
||||
case LEGS_LENGTH:
|
||||
leftKneeNode.update();
|
||||
rightKneeNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case FOOT_LENGTH:
|
||||
leftFootNode.update();
|
||||
rightFootNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case FOOT_OFFSET:
|
||||
leftAnkleNode.update();
|
||||
rightAnkleNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
case SKELETON_OFFSET:
|
||||
trackerChestNode.update();
|
||||
trackerWaistNode.update();
|
||||
trackerLeftKneeNode.update();
|
||||
trackerRightKneeNode.update();
|
||||
trackerLeftFootNode.update();
|
||||
trackerRightFootNode.update();
|
||||
updateComputedTrackers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
@Override
|
||||
public TransformNode getRootNode() {
|
||||
return hmdNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SkeletonConfig getSkeletonConfig() {
|
||||
return skeletonConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetSkeletonConfig(SkeletonConfigValue config) {
|
||||
if(config == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3f vec;
|
||||
float height;
|
||||
switch(config) {
|
||||
case HEAD:
|
||||
skeletonConfig.setConfig(SkeletonConfigValue.HEAD, null);
|
||||
break;
|
||||
case NECK:
|
||||
skeletonConfig.setConfig(SkeletonConfigValue.NECK, null);
|
||||
break;
|
||||
case TORSO: // Distance from shoulders to hip (full torso length)
|
||||
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.TORSO, ((height) / 2.0f) - 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);
|
||||
break;
|
||||
case WAIST: // waist length is from hips to waist
|
||||
skeletonConfig.setConfig(SkeletonConfigValue.WAIST, null);
|
||||
break;
|
||||
case HIP_OFFSET:
|
||||
skeletonConfig.setConfig(SkeletonConfigValue.HIP_OFFSET, null);
|
||||
break;
|
||||
case HIPS_WIDTH:
|
||||
skeletonConfig.setConfig(SkeletonConfigValue.HIPS_WIDTH, null);
|
||||
break;
|
||||
case FOOT_LENGTH:
|
||||
skeletonConfig.setConfig(SkeletonConfigValue.FOOT_LENGTH, null);
|
||||
break;
|
||||
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);
|
||||
} 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
//#endregion
|
||||
|
||||
// Each tracker uses the tracker before it to adjust itself,
|
||||
// so trackers that don't need adjustments could be used too
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
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);
|
||||
//#endregion
|
||||
|
||||
// Each tracker uses the tracker before it to adjust itself,
|
||||
// so trackers that don't need adjustments could be used too
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import io.eiren.yaml.YamlFile;
|
||||
|
||||
public class SkeletonConfig {
|
||||
|
||||
protected final EnumMap<SkeletonConfigValue, Float> configs = new EnumMap<SkeletonConfigValue, Float>(SkeletonConfigValue.class);
|
||||
protected final EnumMap<SkeletonConfigToggle, Boolean> toggles = new EnumMap<SkeletonConfigToggle, Boolean>(SkeletonConfigToggle.class);
|
||||
protected final EnumMap<SkeletonNodeOffset, Vector3f> nodeOffsets = new EnumMap<SkeletonNodeOffset, Vector3f>(SkeletonNodeOffset.class);
|
||||
|
||||
protected final boolean autoUpdateOffsets;
|
||||
protected final SkeletonConfigCallback callback;
|
||||
|
||||
public SkeletonConfig(boolean autoUpdateOffsets) {
|
||||
this.autoUpdateOffsets = autoUpdateOffsets;
|
||||
this.callback = null;
|
||||
|
||||
callCallbackOnAll(true);
|
||||
|
||||
if(autoUpdateOffsets) {
|
||||
computeAllNodeOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletonConfig(boolean autoUpdateOffsets, SkeletonConfigCallback callback) {
|
||||
this.autoUpdateOffsets = autoUpdateOffsets;
|
||||
this.callback = callback;
|
||||
|
||||
callCallbackOnAll(true);
|
||||
|
||||
if(autoUpdateOffsets) {
|
||||
computeAllNodeOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletonConfig(Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigToggle, Boolean> toggles, boolean autoUpdateOffsets, SkeletonConfigCallback callback) {
|
||||
this.autoUpdateOffsets = autoUpdateOffsets;
|
||||
this.callback = callback;
|
||||
setConfigs(configs, toggles);
|
||||
|
||||
callCallbackOnAll(true);
|
||||
}
|
||||
|
||||
public SkeletonConfig(Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigToggle, Boolean> toggles, boolean autoUpdateOffsets) {
|
||||
this(configs, toggles, autoUpdateOffsets, null);
|
||||
}
|
||||
|
||||
public SkeletonConfig(SkeletonConfig skeletonConfig, boolean autoUpdateOffsets, SkeletonConfigCallback callback) {
|
||||
this.autoUpdateOffsets = autoUpdateOffsets;
|
||||
this.callback = callback;
|
||||
setConfigs(skeletonConfig);
|
||||
|
||||
callCallbackOnAll(true);
|
||||
}
|
||||
|
||||
public SkeletonConfig(SkeletonConfig skeletonConfig, boolean autoUpdateOffsets) {
|
||||
this(skeletonConfig, autoUpdateOffsets, null);
|
||||
}
|
||||
|
||||
private void callCallbackOnAll(boolean defaultOnly) {
|
||||
if(callback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for(SkeletonConfigValue config : SkeletonConfigValue.values) {
|
||||
try {
|
||||
Float val = configs.get(config);
|
||||
if(!defaultOnly || val == null) {
|
||||
callback.updateConfigState(config, val == null ? config.defaultValue : val);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
LogManager.log.severe("[SkeletonConfig] Exception while calling callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
for(SkeletonConfigToggle config : SkeletonConfigToggle.values) {
|
||||
try {
|
||||
Boolean val = toggles.get(config);
|
||||
if(!defaultOnly || val == null) {
|
||||
callback.updateToggleState(config, val == null ? config.defaultValue : val);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
LogManager.log.severe("[SkeletonConfig] Exception while calling callback", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Float setConfig(SkeletonConfigValue config, Float newValue, boolean computeOffsets) {
|
||||
Float origVal = newValue != null ? configs.put(config, newValue) : configs.remove(config);
|
||||
|
||||
// Re-compute the affected offsets
|
||||
if(computeOffsets && autoUpdateOffsets && config.affectedOffsets != null) {
|
||||
for(SkeletonNodeOffset offset : config.affectedOffsets) {
|
||||
computeNodeOffset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
if(callback != null) {
|
||||
try {
|
||||
callback.updateConfigState(config, newValue != null ? newValue : config.defaultValue);
|
||||
} catch(Exception e) {
|
||||
LogManager.log.severe("[SkeletonConfig] Exception while calling callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
return origVal;
|
||||
}
|
||||
|
||||
public Float setConfig(SkeletonConfigValue config, Float newValue) {
|
||||
return setConfig(config, newValue, true);
|
||||
}
|
||||
|
||||
public Float setConfig(String config, Float newValue) {
|
||||
return setConfig(SkeletonConfigValue.getByStringValue(config), newValue);
|
||||
}
|
||||
|
||||
public float getConfig(SkeletonConfigValue config) {
|
||||
if(config == null) {
|
||||
return 0f;
|
||||
}
|
||||
|
||||
// IMPORTANT!! This null check is necessary, getOrDefault seems to randomly decide to return null at times, so this is a secondary check
|
||||
Float val = configs.getOrDefault(config, config.defaultValue);
|
||||
return val != null ? val : config.defaultValue;
|
||||
}
|
||||
|
||||
public float getConfig(String config) {
|
||||
if(config == null) {
|
||||
return 0f;
|
||||
}
|
||||
return getConfig(SkeletonConfigValue.getByStringValue(config));
|
||||
}
|
||||
|
||||
public Boolean setToggle(SkeletonConfigToggle config, Boolean newValue) {
|
||||
Boolean origVal = newValue != null ? toggles.put(config, newValue) : toggles.remove(config);
|
||||
|
||||
if(callback != null) {
|
||||
try {
|
||||
callback.updateToggleState(config, newValue != null ? newValue : config.defaultValue);
|
||||
} catch(Exception e) {
|
||||
LogManager.log.severe("[SkeletonConfig] Exception while calling callback", e);
|
||||
}
|
||||
}
|
||||
|
||||
return origVal;
|
||||
}
|
||||
|
||||
public Boolean setToggle(String config, Boolean newValue) {
|
||||
return setToggle(SkeletonConfigToggle.getByStringValue(config), newValue);
|
||||
}
|
||||
|
||||
public boolean getToggle(SkeletonConfigToggle config) {
|
||||
if(config == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// IMPORTANT!! This null check is necessary, getOrDefault seems to randomly decide to return null at times, so this is a secondary check
|
||||
Boolean val = toggles.getOrDefault(config, config.defaultValue);
|
||||
return val != null ? val : config.defaultValue;
|
||||
}
|
||||
|
||||
public boolean getToggle(String config) {
|
||||
if(config == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return getToggle(SkeletonConfigToggle.getByStringValue(config));
|
||||
}
|
||||
|
||||
protected void setNodeOffset(SkeletonNodeOffset nodeOffset, float x, float y, float z) {
|
||||
Vector3f offset = nodeOffsets.get(nodeOffset);
|
||||
|
||||
if(offset == null) {
|
||||
offset = new Vector3f(x, y, z);
|
||||
nodeOffsets.put(nodeOffset, offset);
|
||||
} else {
|
||||
offset.set(x, y, z);
|
||||
}
|
||||
|
||||
if(callback != null) {
|
||||
try {
|
||||
callback.updateNodeOffset(nodeOffset, offset);
|
||||
} catch(Exception e) {
|
||||
LogManager.log.severe("[SkeletonConfig] Exception while calling callback", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setNodeOffset(SkeletonNodeOffset nodeOffset, Vector3f offset) {
|
||||
if(offset == null) {
|
||||
setNodeOffset(nodeOffset, 0f, 0f, 0f);
|
||||
return;
|
||||
}
|
||||
|
||||
setNodeOffset(nodeOffset, offset.x, offset.y, offset.z);
|
||||
}
|
||||
|
||||
public Vector3f getNodeOffset(SkeletonNodeOffset nodeOffset) {
|
||||
return nodeOffsets.getOrDefault(nodeOffset, Vector3f.ZERO);
|
||||
}
|
||||
|
||||
public void computeNodeOffset(SkeletonNodeOffset nodeOffset) {
|
||||
switch(nodeOffset) {
|
||||
case HEAD:
|
||||
setNodeOffset(nodeOffset, 0, 0, getConfig(SkeletonConfigValue.HEAD));
|
||||
break;
|
||||
case NECK:
|
||||
setNodeOffset(nodeOffset, 0, -getConfig(SkeletonConfigValue.NECK), 0);
|
||||
break;
|
||||
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;
|
||||
case HIP:
|
||||
setNodeOffset(nodeOffset, 0, -getConfig(SkeletonConfigValue.WAIST), 0);
|
||||
break;
|
||||
case HIP_TRACKER:
|
||||
setNodeOffset(nodeOffset, 0, getConfig(SkeletonConfigValue.HIP_OFFSET), -getConfig(SkeletonConfigValue.SKELETON_OFFSET));
|
||||
break;
|
||||
|
||||
case LEFT_HIP:
|
||||
setNodeOffset(nodeOffset, -getConfig(SkeletonConfigValue.HIPS_WIDTH) / 2f, 0, 0);
|
||||
break;
|
||||
case RIGHT_HIP:
|
||||
setNodeOffset(nodeOffset, getConfig(SkeletonConfigValue.HIPS_WIDTH) / 2f, 0, 0);
|
||||
break;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public void computeAllNodeOffsets() {
|
||||
for(SkeletonNodeOffset offset : SkeletonNodeOffset.values) {
|
||||
computeNodeOffset(offset);
|
||||
}
|
||||
}
|
||||
|
||||
public void setConfigs(Map<SkeletonConfigValue, Float> configs, Map<SkeletonConfigToggle, Boolean> toggles) {
|
||||
if(configs != null) {
|
||||
configs.forEach((key, value) -> {
|
||||
// Do not recalculate the offsets, these are done in bulk at the end
|
||||
setConfig(key, value, false);
|
||||
});
|
||||
}
|
||||
|
||||
if(toggles != null) {
|
||||
toggles.forEach(this::setToggle);
|
||||
}
|
||||
|
||||
if(autoUpdateOffsets) {
|
||||
computeAllNodeOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
public void setStringConfigs(Map<String, Float> configs, Map<String, Boolean> toggles) {
|
||||
if(configs != null) {
|
||||
configs.forEach((key, value) -> {
|
||||
// Do not recalculate the offsets, these are done in bulk at the end
|
||||
setConfig(SkeletonConfigValue.getByStringValue(key), value, false);
|
||||
});
|
||||
}
|
||||
|
||||
if(toggles != null) {
|
||||
toggles.forEach((key, value) -> {
|
||||
setToggle(SkeletonConfigToggle.getByStringValue(key), value);
|
||||
});
|
||||
}
|
||||
|
||||
if(autoUpdateOffsets) {
|
||||
computeAllNodeOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
public void setConfigs(SkeletonConfig skeletonConfig) {
|
||||
setConfigs(skeletonConfig.configs, skeletonConfig.toggles);
|
||||
}
|
||||
|
||||
//#region Cast utilities for config reading
|
||||
private static Float castFloat(Object o) {
|
||||
if(o == null) {
|
||||
return null;
|
||||
} else if(o instanceof Float) {
|
||||
return (Float) o;
|
||||
} else if(o instanceof Double) {
|
||||
return ((Double) o).floatValue();
|
||||
} else if(o instanceof Byte) {
|
||||
return (float) (Byte) o;
|
||||
} else if(o instanceof Integer) {
|
||||
return (float) (Integer) o;
|
||||
} else if(o instanceof Long) {
|
||||
return (float) (Long) o;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Boolean castBoolean(Object o) {
|
||||
if(o == null) {
|
||||
return null;
|
||||
} else if(o instanceof Boolean) {
|
||||
return (Boolean) o;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
public void loadFromConfig(YamlFile config) {
|
||||
for(SkeletonConfigValue configValue : SkeletonConfigValue.values) {
|
||||
Float val = castFloat(config.getProperty(configValue.configKey));
|
||||
if(val != null) {
|
||||
// Do not recalculate the offsets, these are done in bulk at the end
|
||||
setConfig(configValue, val, false);
|
||||
}
|
||||
}
|
||||
|
||||
for(SkeletonConfigToggle configValue : SkeletonConfigToggle.values) {
|
||||
Boolean val = castBoolean(config.getProperty(configValue.configKey));
|
||||
if(val != null) {
|
||||
setToggle(configValue, val);
|
||||
}
|
||||
}
|
||||
|
||||
if(autoUpdateOffsets) {
|
||||
computeAllNodeOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveToConfig(YamlFile config) {
|
||||
// Write all possible values, this keeps configs consistent even if defaults were changed
|
||||
for(SkeletonConfigValue value : SkeletonConfigValue.values) {
|
||||
config.setProperty(value.configKey, getConfig(value));
|
||||
}
|
||||
|
||||
for(SkeletonConfigToggle value : SkeletonConfigToggle.values) {
|
||||
config.setProperty(value.configKey, getToggle(value));
|
||||
}
|
||||
}
|
||||
|
||||
public void resetConfigs() {
|
||||
configs.clear();
|
||||
toggles.clear();
|
||||
|
||||
callCallbackOnAll(false);
|
||||
|
||||
if(autoUpdateOffsets) {
|
||||
computeAllNodeOffsets();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
public interface SkeletonConfigCallback {
|
||||
|
||||
public void updateConfigState(SkeletonConfigValue config, float newValue);
|
||||
|
||||
public void updateToggleState(SkeletonConfigToggle configToggle, boolean newValue);
|
||||
|
||||
public void updateNodeOffset(SkeletonNodeOffset nodeOffset, Vector3f offset);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum SkeletonConfigToggle {
|
||||
|
||||
EXTENDED_PELVIS_MODEL("Extended pelvis model", "extendedPelvis", true),
|
||||
EXTENDED_KNEE_MODEL("Extended knee model", "extendedKnee", false),
|
||||
;
|
||||
|
||||
private static final String CONFIG_PREFIX = "body.model.";
|
||||
|
||||
public final String stringVal;
|
||||
public final String configKey;
|
||||
|
||||
public final boolean defaultValue;
|
||||
|
||||
public static final SkeletonConfigToggle[] values = values();
|
||||
private static final Map<String, SkeletonConfigToggle> byStringVal = new HashMap<>();
|
||||
|
||||
private SkeletonConfigToggle(String stringVal, String configKey, boolean defaultValue) {
|
||||
this.stringVal = stringVal;
|
||||
this.configKey = CONFIG_PREFIX + configKey;
|
||||
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public static SkeletonConfigToggle getByStringValue(String stringVal) {
|
||||
return stringVal == null ? null : byStringVal.get(stringVal.toLowerCase());
|
||||
}
|
||||
|
||||
static {
|
||||
for(SkeletonConfigToggle configVal : values()) {
|
||||
byStringVal.put(configVal.stringVal.toLowerCase(), configVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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.6f, new SkeletonNodeOffset[]{SkeletonNodeOffset.WAIST}),
|
||||
CHEST("Chest", "chestDistance", "Chest distance", 0.3f, new SkeletonNodeOffset[]{SkeletonNodeOffset.CHEST, SkeletonNodeOffset.WAIST}),
|
||||
WAIST("Waist", "waistDistance", "Waist distance", 0.05f, 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.28f, new SkeletonNodeOffset[]{SkeletonNodeOffset.LEFT_HIP, SkeletonNodeOffset.RIGHT_HIP}),
|
||||
LEGS_LENGTH("Legs length", "legsLength", "Legs length", 0.88f, new SkeletonNodeOffset[]{SkeletonNodeOffset.KNEE}),
|
||||
KNEE_HEIGHT("Knee height", "kneeHeight", "Knee height", 0.44f, 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}),
|
||||
SKELETON_OFFSET("Skeleton offset", "skeletonOffset", "Skeleton offset", 0.0f, new SkeletonNodeOffset[]{SkeletonNodeOffset.CHEST_TRACKER, SkeletonNodeOffset.HIP_TRACKER, SkeletonNodeOffset.KNEE_TRACKER, SkeletonNodeOffset.FOOT_TRACKER}),
|
||||
;
|
||||
|
||||
private static final String CONFIG_PREFIX = "body.";
|
||||
|
||||
public final String stringVal;
|
||||
public final String configKey;
|
||||
public final String label;
|
||||
|
||||
public final float defaultValue;
|
||||
|
||||
public final SkeletonNodeOffset[] affectedOffsets;
|
||||
|
||||
public static final SkeletonConfigValue[] values = values();
|
||||
private static final Map<String, SkeletonConfigValue> byStringVal = new HashMap<>();
|
||||
|
||||
private SkeletonConfigValue(String stringVal, String configKey, String label, float defaultValue, SkeletonNodeOffset[] affectedOffsets) {
|
||||
this.stringVal = stringVal;
|
||||
this.configKey = CONFIG_PREFIX + configKey;
|
||||
this.label = label;
|
||||
|
||||
this.defaultValue = defaultValue;
|
||||
|
||||
this.affectedOffsets = affectedOffsets == null ? new SkeletonNodeOffset[0] : affectedOffsets;
|
||||
}
|
||||
|
||||
public static SkeletonConfigValue getByStringValue(String stringVal) {
|
||||
return stringVal == null ? null : byStringVal.get(stringVal.toLowerCase());
|
||||
}
|
||||
|
||||
static {
|
||||
for(SkeletonConfigValue configVal : values()) {
|
||||
byStringVal.put(configVal.stringVal.toLowerCase(), configVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package dev.slimevr.vr.processor.skeleton;
|
||||
|
||||
public enum SkeletonNodeOffset {
|
||||
|
||||
HEAD,
|
||||
NECK,
|
||||
CHEST,
|
||||
CHEST_TRACKER,
|
||||
WAIST,
|
||||
HIP,
|
||||
HIP_TRACKER,
|
||||
LEFT_HIP,
|
||||
RIGHT_HIP,
|
||||
KNEE,
|
||||
KNEE_TRACKER,
|
||||
ANKLE,
|
||||
FOOT,
|
||||
FOOT_TRACKER,
|
||||
;
|
||||
|
||||
public static final SkeletonNodeOffset[] values = values();
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
public enum DeviceType {
|
||||
HMD,
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import io.eiren.util.BufferedTimer;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
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) {
|
||||
@@ -149,7 +150,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 +158,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;
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import dev.slimevr.vr.trackers.udp.TrackersUDPServer;
|
||||
|
||||
public class MPUTracker extends IMUTracker {
|
||||
|
||||
public ConfigurationData newCalibrationData;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
@@ -1,10 +1,10 @@
|
||||
package io.eiren.vr.trackers;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
public interface ShareableTracker extends Tracker {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
@@ -10,6 +10,7 @@ public enum TrackerPosition {
|
||||
HMD("HMD", TrackerRole.HMD),
|
||||
CHEST("body:chest", TrackerRole.CHEST),
|
||||
WAIST("body:waist", TrackerRole.WAIST),
|
||||
HIP("body:hip", null),
|
||||
LEFT_LEG("body:left_leg", TrackerRole.LEFT_KNEE),
|
||||
RIGHT_LEG("body:right_leg", TrackerRole.RIGHT_KNEE),
|
||||
LEFT_ANKLE("body:left_ankle", null),
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
public enum TrackerRole {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
public enum TrackerStatus {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -8,6 +8,8 @@ public class TrackerUtils {
|
||||
}
|
||||
|
||||
public static <T extends Tracker> T findTrackerForBodyPosition(T[] allTrackers, TrackerPosition position) {
|
||||
if(position == null)
|
||||
return null;
|
||||
for(int i = 0; i < allTrackers.length; ++i) {
|
||||
T t = allTrackers[i];
|
||||
if(t != null && t.getBodyPosition() == position)
|
||||
@@ -17,6 +19,8 @@ public class TrackerUtils {
|
||||
}
|
||||
|
||||
public static <T extends Tracker> T findTrackerForBodyPosition(List<T> allTrackers, TrackerPosition position) {
|
||||
if(position == null)
|
||||
return null;
|
||||
for(int i = 0; i < allTrackers.size(); ++i) {
|
||||
T t = allTrackers.get(i);
|
||||
if(t != null && t.getBodyPosition() == position)
|
||||
@@ -32,18 +36,24 @@ public class TrackerUtils {
|
||||
return findTrackerForBodyPosition(allTrackers, altPosition);
|
||||
}
|
||||
|
||||
public static <T extends Tracker> T findTrackerForBodyPosition(T[] allTrackers, TrackerPosition position, TrackerPosition altPosition) {
|
||||
public static <T extends Tracker> T findTrackerForBodyPosition(T[] allTrackers, TrackerPosition position, TrackerPosition altPosition, TrackerPosition secondAltPosition) {
|
||||
T t = findTrackerForBodyPosition(allTrackers, position);
|
||||
if(t != null)
|
||||
return t;
|
||||
return findTrackerForBodyPosition(allTrackers, altPosition);
|
||||
t = findTrackerForBodyPosition(allTrackers, altPosition);
|
||||
if(t != null)
|
||||
return t;
|
||||
return findTrackerForBodyPosition(allTrackers, secondAltPosition);
|
||||
}
|
||||
|
||||
public static Tracker findTrackerForBodyPositionOrEmpty(List<? extends Tracker> allTrackers, TrackerPosition position, TrackerPosition altPosition) {
|
||||
public static Tracker findTrackerForBodyPositionOrEmpty(List<? extends Tracker> allTrackers, TrackerPosition position, TrackerPosition altPosition, TrackerPosition secondAltPosition) {
|
||||
Tracker t = findTrackerForBodyPosition(allTrackers, position);
|
||||
if(t != null)
|
||||
return t;
|
||||
t = findTrackerForBodyPosition(allTrackers, altPosition);
|
||||
if(t != null)
|
||||
return t;
|
||||
t = findTrackerForBodyPosition(allTrackers, secondAltPosition);
|
||||
if(t != null)
|
||||
return t;
|
||||
return new ComputedTracker(Tracker.getNextLocalTrackerId(), "Empty tracker", false, false);
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
public interface TrackerWithBattery {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
public interface TrackerWithTPS {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package io.eiren.vr.trackers;
|
||||
package dev.slimevr.vr.trackers;
|
||||
|
||||
import io.eiren.util.BufferedTimer;
|
||||
|
||||
@@ -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,43 @@
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
429
src/main/java/dev/slimevr/vr/trackers/udp/TrackersUDPServer.java
Normal file
429
src/main/java/dev/slimevr/vr/trackers/udp/TrackersUDPServer.java
Normal file
@@ -0,0 +1,429 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 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 recieved 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 differenet 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 < 8) {
|
||||
// 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("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);
|
||||
}
|
||||
} else {
|
||||
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] Wrog 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 recieved 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,30 @@
|
||||
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();
|
||||
}
|
||||
|
||||
@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,51 @@
|
||||
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;
|
||||
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 2;
|
||||
}
|
||||
}
|
||||
@@ -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("[UDPPorotocolParser] 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package io.eiren.vr;
|
||||
|
||||
import com.melloware.jintellitype.JIntellitype;
|
||||
import com.melloware.jintellitype.HotkeyListener;
|
||||
import io.eiren.util.ann.AWTThread;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
|
||||
public class Keybinding implements HotkeyListener {
|
||||
public final VRServer server;
|
||||
private static final int RESET = 1;
|
||||
private static final int QUICK_RESET = 2;
|
||||
|
||||
@AWTThread
|
||||
public Keybinding(VRServer server) {
|
||||
this.server = server;
|
||||
|
||||
if(JIntellitype.isJIntellitypeSupported()) {
|
||||
JIntellitype.getInstance().addHotKeyListener(this);
|
||||
|
||||
String resetBinding = this.server.config.getString("keybindings.reset");
|
||||
if(resetBinding == null) {
|
||||
resetBinding = "CTRL+ALT+SHIFT+Y";
|
||||
this.server.config.setProperty("keybindings.reset", resetBinding);
|
||||
}
|
||||
JIntellitype.getInstance().registerHotKey(RESET, resetBinding);
|
||||
LogManager.log.info("[Keybinding] Bound reset to " + resetBinding);
|
||||
|
||||
String quickResetBinding = this.server.config.getString("keybindings.quickReset");
|
||||
if(quickResetBinding == null) {
|
||||
quickResetBinding = "CTRL+ALT+SHIFT+U";
|
||||
this.server.config.setProperty("keybindings.quickReset", quickResetBinding);
|
||||
}
|
||||
JIntellitype.getInstance().registerHotKey(QUICK_RESET, quickResetBinding);
|
||||
LogManager.log.info("[Keybinding] Bound quick reset to " + quickResetBinding);
|
||||
}
|
||||
}
|
||||
|
||||
@AWTThread
|
||||
@Override
|
||||
public void onHotKey(int identifier) {
|
||||
switch(identifier) {
|
||||
case RESET:
|
||||
LogManager.log.info("[Keybinding] Reset pressed");
|
||||
server.resetTrackers();
|
||||
break;
|
||||
case QUICK_RESET:
|
||||
LogManager.log.info("[Keybinding] Quick reset pressed");
|
||||
server.resetTrackersYaw();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package io.eiren.vr.processor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.eiren.util.ann.ThreadSafe;
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
|
||||
public abstract class HumanSkeleton {
|
||||
|
||||
@VRServerThread
|
||||
public abstract void updatePose();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract TransformNode getRootNode();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract Map<String, Float> getSkeletonConfig();
|
||||
|
||||
@ThreadSafe
|
||||
public abstract void setSkeletonConfig(String key, float newLength);
|
||||
|
||||
@ThreadSafe
|
||||
public abstract void resetSkeletonConfig(String joint);
|
||||
|
||||
@VRServerThread
|
||||
public abstract void resetTrackersFull();
|
||||
|
||||
@VRServerThread
|
||||
public abstract void resetTrackersYaw();
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
package io.eiren.vr.processor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerRole;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
import io.eiren.vr.trackers.TrackerUtils;
|
||||
|
||||
public class HumanSkeletonWithLegs extends HumanSkeletonWithWaist {
|
||||
|
||||
public static final float HIPS_WIDTH_DEFAULT = 0.3f;
|
||||
public static final float FOOT_LENGTH_DEFAULT = 0.05f;
|
||||
public static final float DEFAULT_FLOOR_OFFSET = 0.05f;
|
||||
|
||||
protected final Quaternion hipBuf = new Quaternion();
|
||||
protected final Quaternion kneeBuf = new Quaternion();
|
||||
protected final Vector3f hipVector = new Vector3f();
|
||||
protected final Vector3f ankleVector = new Vector3f();
|
||||
protected final Quaternion kneeRotation = new Quaternion();
|
||||
|
||||
protected final Tracker leftLegTracker;
|
||||
protected final Tracker leftAnkleTracker;
|
||||
protected final Tracker leftFootTracker;
|
||||
protected final ComputedHumanPoseTracker computedLeftFootTracker;
|
||||
protected final ComputedHumanPoseTracker computedLeftKneeTracker;
|
||||
protected final Tracker rightLegTracker;
|
||||
protected final Tracker rightAnkleTracker;
|
||||
protected final Tracker rightFootTracker;
|
||||
protected final ComputedHumanPoseTracker computedRightFootTracker;
|
||||
protected final ComputedHumanPoseTracker computedRightKneeTracker;
|
||||
|
||||
protected final TransformNode leftHipNode = new TransformNode("Left-Hip", false);
|
||||
protected final TransformNode leftKneeNode = new TransformNode("Left-Knee", false);
|
||||
protected final TransformNode leftAnkleNode = new TransformNode("Left-Ankle", false);
|
||||
protected final TransformNode leftFootNode = new TransformNode("Left-Foot", false);
|
||||
protected final TransformNode rightHipNode = new TransformNode("Right-Hip", false);
|
||||
protected final TransformNode rightKneeNode = new TransformNode("Right-Knee", false);
|
||||
protected final TransformNode rightAnkleNode = new TransformNode("Right-Ankle", false);
|
||||
protected final TransformNode rightFootNode = new TransformNode("Right-Foot", false);
|
||||
|
||||
/**
|
||||
* Distance between centers of both hips
|
||||
*/
|
||||
protected float hipsWidth = HIPS_WIDTH_DEFAULT;
|
||||
/**
|
||||
* Length from waist to knees
|
||||
*/
|
||||
protected float kneeHeight = 0.42f;
|
||||
/**
|
||||
* Distance from waist to ankle
|
||||
*/
|
||||
protected float legsLength = 0.84f;
|
||||
protected float footLength = FOOT_LENGTH_DEFAULT;
|
||||
protected float footOffset = 0f; //horizontal forward/backwards translation feet offset for avatars with bent knees
|
||||
|
||||
protected float minKneePitch = 0f * FastMath.DEG_TO_RAD;
|
||||
protected float maxKneePitch = 90f * FastMath.DEG_TO_RAD;
|
||||
|
||||
protected float kneeLerpFactor = 0.5f;
|
||||
|
||||
protected boolean extendedPelvisModel = true;
|
||||
protected boolean extendedKneeModel = false;
|
||||
|
||||
public HumanSkeletonWithLegs(VRServer server, List<ComputedHumanPoseTracker> computedTrackers) {
|
||||
super(server, computedTrackers);
|
||||
List<Tracker> allTracekrs = server.getAllTrackers();
|
||||
this.leftLegTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(allTracekrs, TrackerPosition.LEFT_LEG, TrackerPosition.LEFT_ANKLE);
|
||||
this.leftAnkleTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(allTracekrs, TrackerPosition.LEFT_ANKLE, TrackerPosition.LEFT_LEG);
|
||||
this.leftFootTracker = TrackerUtils.findTrackerForBodyPosition(allTracekrs, TrackerPosition.LEFT_FOOT);
|
||||
this.rightLegTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(allTracekrs, TrackerPosition.RIGHT_LEG, TrackerPosition.RIGHT_ANKLE);
|
||||
this.rightAnkleTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(allTracekrs, TrackerPosition.RIGHT_ANKLE, TrackerPosition.RIGHT_LEG);
|
||||
this.rightFootTracker = TrackerUtils.findTrackerForBodyPosition(allTracekrs, TrackerPosition.RIGHT_FOOT);
|
||||
ComputedHumanPoseTracker lat = null;
|
||||
ComputedHumanPoseTracker rat = null;
|
||||
ComputedHumanPoseTracker rkt = null;
|
||||
ComputedHumanPoseTracker lkt = null;
|
||||
for(int i = 0; i < computedTrackers.size(); ++i) {
|
||||
ComputedHumanPoseTracker t = computedTrackers.get(i);
|
||||
if(t.skeletonPosition == ComputedHumanPoseTrackerPosition.LEFT_FOOT)
|
||||
lat = t;
|
||||
if(t.skeletonPosition == ComputedHumanPoseTrackerPosition.RIGHT_FOOT)
|
||||
rat = t;
|
||||
if(t.skeletonPosition == ComputedHumanPoseTrackerPosition.LEFT_KNEE)
|
||||
lkt = t;
|
||||
if(t.skeletonPosition == ComputedHumanPoseTrackerPosition.RIGHT_KNEE)
|
||||
rkt = t;
|
||||
}
|
||||
if(lat == null)
|
||||
lat = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.LEFT_FOOT, TrackerRole.LEFT_FOOT);
|
||||
if(rat == null)
|
||||
rat = new ComputedHumanPoseTracker(Tracker.getNextLocalTrackerId(), ComputedHumanPoseTrackerPosition.RIGHT_FOOT, TrackerRole.RIGHT_FOOT);
|
||||
computedLeftFootTracker = lat;
|
||||
computedRightFootTracker = rat;
|
||||
computedLeftKneeTracker = lkt;
|
||||
computedRightKneeTracker = rkt;
|
||||
lat.setStatus(TrackerStatus.OK);
|
||||
rat.setStatus(TrackerStatus.OK);
|
||||
hipsWidth = server.config.getFloat("body.hipsWidth", hipsWidth);
|
||||
kneeHeight = server.config.getFloat("body.kneeHeight", kneeHeight);
|
||||
legsLength = server.config.getFloat("body.legsLength", legsLength);
|
||||
footLength = server.config.getFloat("body.footLength", footLength);
|
||||
footOffset = server.config.getFloat("body.footOffset", footOffset);
|
||||
//extendedPelvisModel = server.config.getBoolean("body.model.extendedPelvis", extendedPelvisModel);
|
||||
extendedKneeModel = server.config.getBoolean("body.model.extendedKnee", extendedKneeModel);
|
||||
|
||||
waistNode.attachChild(leftHipNode);
|
||||
leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0);
|
||||
|
||||
waistNode.attachChild(rightHipNode);
|
||||
rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0);
|
||||
|
||||
leftHipNode.attachChild(leftKneeNode);
|
||||
leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
|
||||
rightHipNode.attachChild(rightKneeNode);
|
||||
rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
|
||||
leftKneeNode.attachChild(leftAnkleNode);
|
||||
leftAnkleNode.localTransform.setTranslation(0, -kneeHeight, -footOffset);
|
||||
|
||||
rightKneeNode.attachChild(rightAnkleNode);
|
||||
rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, -footOffset);
|
||||
|
||||
leftAnkleNode.attachChild(leftFootNode);
|
||||
leftFootNode.localTransform.setTranslation(0, 0, -footLength);
|
||||
|
||||
rightAnkleNode.attachChild(rightFootNode);
|
||||
rightFootNode.localTransform.setTranslation(0, 0, -footLength);
|
||||
|
||||
configMap.put("Hips width", hipsWidth);
|
||||
configMap.put("Legs length", legsLength);
|
||||
configMap.put("Knee height", kneeHeight);
|
||||
configMap.put("Foot length", footLength);
|
||||
configMap.put("Foot offset", footOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetSkeletonConfig(String joint) {
|
||||
super.resetSkeletonConfig(joint);
|
||||
switch(joint) {
|
||||
case "All":
|
||||
// Resets from the parent already performed
|
||||
resetSkeletonConfig("Hips width");
|
||||
resetSkeletonConfig("Foot length");
|
||||
resetSkeletonConfig("Foot offset");
|
||||
resetSkeletonConfig("Legs length");
|
||||
break;
|
||||
case "Hips width":
|
||||
setSkeletonConfig(joint, HIPS_WIDTH_DEFAULT);
|
||||
break;
|
||||
case "Foot length":
|
||||
setSkeletonConfig(joint, FOOT_LENGTH_DEFAULT);
|
||||
break;
|
||||
case "Foot offset":
|
||||
setSkeletonConfig(joint, 0f);
|
||||
break;
|
||||
case "Legs length": // Set legs length to be 5cm above floor level
|
||||
Vector3f vec = new Vector3f();
|
||||
hmdTracker.getPosition(vec);
|
||||
float 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
|
||||
setSkeletonConfig(joint, height - neckLength - waistDistance - DEFAULT_FLOOR_OFFSET);
|
||||
}
|
||||
resetSkeletonConfig("Knee height");
|
||||
break;
|
||||
case "Knee height": // Knees are at 50% of the legs by default
|
||||
setSkeletonConfig(joint, legsLength / 2.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkeletonConfig(String joint, float newLength) {
|
||||
super.setSkeletonConfig(joint, newLength);
|
||||
switch(joint) {
|
||||
case "Hips width":
|
||||
hipsWidth = newLength;
|
||||
server.config.setProperty("body.hipsWidth", hipsWidth);
|
||||
leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0);
|
||||
rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0);
|
||||
break;
|
||||
case "Knee height":
|
||||
kneeHeight = newLength;
|
||||
server.config.setProperty("body.kneeHeight", kneeHeight);
|
||||
leftAnkleNode.localTransform.setTranslation(0, -kneeHeight, -footOffset);
|
||||
rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, -footOffset);
|
||||
leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
break;
|
||||
case "Legs length":
|
||||
legsLength = newLength;
|
||||
server.config.setProperty("body.legsLength", legsLength);
|
||||
leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
|
||||
break;
|
||||
case "Foot length":
|
||||
footLength = newLength;
|
||||
server.config.setProperty("body.footLength", footLength);
|
||||
leftFootNode.localTransform.setTranslation(0, 0, -footLength);
|
||||
rightFootNode.localTransform.setTranslation(0, 0, -footLength);
|
||||
break;
|
||||
case "Foot offset":
|
||||
footOffset = newLength;
|
||||
server.config.setProperty("body.footOffset", footOffset);
|
||||
leftAnkleNode.localTransform.setTranslation(0, -kneeHeight, -footOffset);
|
||||
rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, -footOffset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getSkeletonConfigBoolean(String config) {
|
||||
switch(config) {
|
||||
case "Extended pelvis model":
|
||||
return extendedPelvisModel;
|
||||
case "Extended knee model":
|
||||
return extendedKneeModel;
|
||||
}
|
||||
return super.getSkeletonConfigBoolean(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkeletonConfigBoolean(String config, boolean newState) {
|
||||
switch(config) {
|
||||
case "Extended pelvis model":
|
||||
extendedPelvisModel = newState;
|
||||
server.config.setProperty("body.model.extendedPelvis", newState);
|
||||
break;
|
||||
case "Extended knee model":
|
||||
extendedKneeModel = newState;
|
||||
server.config.setProperty("body.model.extendedKnee", newState);
|
||||
break;
|
||||
default:
|
||||
super.setSkeletonConfigBoolean(config, newState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLocalTransforms() {
|
||||
super.updateLocalTransforms();
|
||||
// Left Leg
|
||||
leftLegTracker.getRotation(hipBuf);
|
||||
leftAnkleTracker.getRotation(kneeBuf);
|
||||
|
||||
if(extendedKneeModel)
|
||||
calculateKneeLimits(hipBuf, kneeBuf, leftLegTracker.getConfidenceLevel(), leftAnkleTracker.getConfidenceLevel());
|
||||
|
||||
leftHipNode.localTransform.setRotation(hipBuf);
|
||||
leftKneeNode.localTransform.setRotation(kneeBuf);
|
||||
leftAnkleNode.localTransform.setRotation(kneeBuf);
|
||||
leftFootNode.localTransform.setRotation(kneeBuf);
|
||||
|
||||
if(leftFootTracker != null) {
|
||||
leftFootTracker.getRotation(kneeBuf);
|
||||
leftAnkleNode.localTransform.setRotation(kneeBuf);
|
||||
leftFootNode.localTransform.setRotation(kneeBuf);
|
||||
}
|
||||
|
||||
// Right Leg
|
||||
rightLegTracker.getRotation(hipBuf);
|
||||
rightAnkleTracker.getRotation(kneeBuf);
|
||||
|
||||
if(extendedKneeModel)
|
||||
calculateKneeLimits(hipBuf, kneeBuf, rightLegTracker.getConfidenceLevel(), rightAnkleTracker.getConfidenceLevel());
|
||||
|
||||
rightHipNode.localTransform.setRotation(hipBuf);
|
||||
rightKneeNode.localTransform.setRotation(kneeBuf);
|
||||
rightAnkleNode.localTransform.setRotation(kneeBuf);
|
||||
rightFootNode.localTransform.setRotation(kneeBuf);
|
||||
|
||||
if(rightFootTracker != null) {
|
||||
rightFootTracker.getRotation(kneeBuf);
|
||||
rightAnkleNode.localTransform.setRotation(kneeBuf);
|
||||
rightFootNode.localTransform.setRotation(kneeBuf);
|
||||
}
|
||||
|
||||
if(extendedPelvisModel) {
|
||||
// Average pelvis between two legs
|
||||
leftHipNode.localTransform.getRotation(hipBuf);
|
||||
rightHipNode.localTransform.getRotation(kneeBuf);
|
||||
kneeBuf.nlerp(hipBuf, 0.5f);
|
||||
chestNode.localTransform.getRotation(hipBuf);
|
||||
kneeBuf.nlerp(hipBuf, 0.3333333f);
|
||||
waistNode.localTransform.setRotation(kneeBuf);
|
||||
// TODO : Use vectors to add like 50% of wasit tracker yaw to waist node to reduce drift and let user take weird poses
|
||||
// TODO Set virtual waist node yaw to that of waist node
|
||||
}
|
||||
}
|
||||
|
||||
// Knee basically has only 1 DoF (pitch), average yaw and roll between knee and hip
|
||||
protected void calculateKneeLimits(Quaternion hipBuf, Quaternion kneeBuf, float hipConfidense, float kneeConfidense) {
|
||||
ankleVector.set(0, -1, 0);
|
||||
hipVector.set(0, -1, 0);
|
||||
hipBuf.multLocal(hipVector);
|
||||
kneeBuf.multLocal(ankleVector);
|
||||
kneeRotation.angleBetweenVectors(hipVector, ankleVector); // Find knee angle
|
||||
|
||||
// Substract knee angle from knee rotation. With perfect leg and perfect
|
||||
// sensors result should match hip rotation perfectly
|
||||
kneeBuf.multLocal(kneeRotation.inverse());
|
||||
|
||||
// Average knee and hip with a slerp
|
||||
hipBuf.slerp(kneeBuf, 0.5f); // TODO : Use confidence to calculate changeAmt
|
||||
kneeBuf.set(hipBuf);
|
||||
|
||||
// Return knee angle into knee rotation
|
||||
kneeBuf.multLocal(kneeRotation);
|
||||
}
|
||||
|
||||
public static float normalizeRad(float angle) {
|
||||
return FastMath.normalize(angle, -FastMath.PI, FastMath.PI);
|
||||
}
|
||||
|
||||
public static float interpolateRadians(float factor, float start, float end) {
|
||||
float angle = Math.abs(end - start);
|
||||
if(angle > FastMath.PI) {
|
||||
if(end > start) {
|
||||
start += FastMath.TWO_PI;
|
||||
} else {
|
||||
end += FastMath.TWO_PI;
|
||||
}
|
||||
}
|
||||
float val = start + (end - start) * factor;
|
||||
return normalizeRad(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateComputedTrackers() {
|
||||
super.updateComputedTrackers();
|
||||
|
||||
if(computedLeftFootTracker != null) {
|
||||
computedLeftFootTracker.position.set(leftFootNode.worldTransform.getTranslation());
|
||||
computedLeftFootTracker.rotation.set(leftFootNode.worldTransform.getRotation());
|
||||
computedLeftFootTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedLeftKneeTracker != null) {
|
||||
computedLeftKneeTracker.position.set(leftKneeNode.worldTransform.getTranslation());
|
||||
computedLeftKneeTracker.rotation.set(leftHipNode.worldTransform.getRotation());
|
||||
computedLeftKneeTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedRightFootTracker != null) {
|
||||
computedRightFootTracker.position.set(rightFootNode.worldTransform.getTranslation());
|
||||
computedRightFootTracker.rotation.set(rightFootNode.worldTransform.getRotation());
|
||||
computedRightFootTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedRightKneeTracker != null) {
|
||||
computedRightKneeTracker.position.set(rightKneeNode.worldTransform.getTranslation());
|
||||
computedRightKneeTracker.rotation.set(rightHipNode.worldTransform.getRotation());
|
||||
computedRightKneeTracker.dataTick();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void resetTrackersFull() {
|
||||
// Each tracker uses the tracker before it to adjust iteself,
|
||||
// so trackers that don't need adjustments could be used too
|
||||
super.resetTrackersFull();
|
||||
// Start with waist, it was reset in the parent
|
||||
Quaternion referenceRotation = new Quaternion();
|
||||
this.waistTracker.getRotation(referenceRotation);
|
||||
|
||||
this.leftLegTracker.resetFull(referenceRotation);
|
||||
this.rightLegTracker.resetFull(referenceRotation);
|
||||
this.leftLegTracker.getRotation(referenceRotation);
|
||||
|
||||
this.leftAnkleTracker.resetFull(referenceRotation);
|
||||
this.leftAnkleTracker.getRotation(referenceRotation);
|
||||
|
||||
if(this.leftFootTracker != null) {
|
||||
this.leftFootTracker.resetFull(referenceRotation);
|
||||
}
|
||||
|
||||
this.rightLegTracker.getRotation(referenceRotation);
|
||||
|
||||
this.rightAnkleTracker.resetFull(referenceRotation);
|
||||
this.rightAnkleTracker.getRotation(referenceRotation);
|
||||
|
||||
if(this.rightFootTracker != null) {
|
||||
this.rightFootTracker.resetFull(referenceRotation);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void resetTrackersYaw() {
|
||||
// Each tracker uses the tracker before it to adjust iteself,
|
||||
// so trackers that don't need adjustments could be used too
|
||||
super.resetTrackersYaw();
|
||||
// Start with waist, it was reset in the parent
|
||||
Quaternion referenceRotation = new Quaternion();
|
||||
this.waistTracker.getRotation(referenceRotation);
|
||||
|
||||
this.leftLegTracker.resetYaw(referenceRotation);
|
||||
this.rightLegTracker.resetYaw(referenceRotation);
|
||||
this.leftLegTracker.getRotation(referenceRotation);
|
||||
|
||||
this.leftAnkleTracker.resetYaw(referenceRotation);
|
||||
this.leftAnkleTracker.getRotation(referenceRotation);
|
||||
|
||||
if(this.leftFootTracker != null) {
|
||||
this.leftFootTracker.resetYaw(referenceRotation);
|
||||
}
|
||||
|
||||
this.rightLegTracker.getRotation(referenceRotation);
|
||||
|
||||
this.rightAnkleTracker.resetYaw(referenceRotation);
|
||||
this.rightAnkleTracker.getRotation(referenceRotation);
|
||||
|
||||
if(this.rightFootTracker != null) {
|
||||
this.rightFootTracker.resetYaw(referenceRotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
package io.eiren.vr.processor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import io.eiren.util.ann.VRServerThread;
|
||||
import io.eiren.vr.VRServer;
|
||||
import io.eiren.vr.trackers.HMDTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
import io.eiren.vr.trackers.TrackerPosition;
|
||||
import io.eiren.vr.trackers.TrackerStatus;
|
||||
import io.eiren.vr.trackers.TrackerUtils;
|
||||
|
||||
public class HumanSkeletonWithWaist extends HumanSkeleton {
|
||||
|
||||
public static final float HEAD_SHIFT_DEFAULT = 0.1f;
|
||||
public static final float NECK_LENGTH_DEFAULT = 0.1f;
|
||||
|
||||
protected final Map<String, Float> configMap = new HashMap<>();
|
||||
protected final VRServer server;
|
||||
|
||||
protected final float[] waistAngles = new float[3];
|
||||
protected final Quaternion qBuf = new Quaternion();
|
||||
protected final Vector3f vBuf = new Vector3f();
|
||||
|
||||
protected final Tracker waistTracker;
|
||||
protected final Tracker chestTracker;
|
||||
protected final HMDTracker hmdTracker;
|
||||
protected final ComputedHumanPoseTracker computedWaistTracker;
|
||||
protected final ComputedHumanPoseTracker computedChestTracker;
|
||||
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 waistNode = new TransformNode("Waist", false);
|
||||
protected final TransformNode chestNode = new TransformNode("Chest", false);
|
||||
protected final TransformNode trackerWaistNode = new TransformNode("Waist-Tracker", false);
|
||||
|
||||
protected float chestDistance = 0.42f;
|
||||
/**
|
||||
* Distance from eyes to waist
|
||||
*/
|
||||
protected float waistDistance = 0.85f;
|
||||
/**
|
||||
* Distance from eyes to waist, defines reported
|
||||
* tracker position, if you want to move resulting
|
||||
* tracker up or down from actual waist
|
||||
*/
|
||||
protected float trackerWaistDistance = 0.0f;
|
||||
/**
|
||||
* Distance from eyes to the base of the neck
|
||||
*/
|
||||
protected float neckLength = NECK_LENGTH_DEFAULT;
|
||||
/**
|
||||
* Distance from eyes to ear
|
||||
*/
|
||||
protected float headShift = HEAD_SHIFT_DEFAULT;
|
||||
|
||||
public HumanSkeletonWithWaist(VRServer server, List<ComputedHumanPoseTracker> computedTrackers) {
|
||||
List<Tracker> allTracekrs = server.getAllTrackers();
|
||||
this.waistTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(allTracekrs, TrackerPosition.WAIST, TrackerPosition.CHEST);
|
||||
this.chestTracker = TrackerUtils.findTrackerForBodyPositionOrEmpty(allTracekrs, TrackerPosition.CHEST, TrackerPosition.WAIST);
|
||||
this.hmdTracker = server.hmdTracker;
|
||||
this.server = server;
|
||||
ComputedHumanPoseTracker cwt = null;
|
||||
ComputedHumanPoseTracker cct = null;
|
||||
for(int i = 0; i < computedTrackers.size(); ++i) {
|
||||
ComputedHumanPoseTracker t = computedTrackers.get(i);
|
||||
if(t.skeletonPosition == ComputedHumanPoseTrackerPosition.WAIST)
|
||||
cwt = t;
|
||||
if(t.skeletonPosition == ComputedHumanPoseTrackerPosition.CHEST)
|
||||
cct = t;
|
||||
}
|
||||
computedWaistTracker = cwt;
|
||||
computedChestTracker = cct;
|
||||
cwt.setStatus(TrackerStatus.OK);
|
||||
headShift = server.config.getFloat("body.headShift", headShift);
|
||||
neckLength = server.config.getFloat("body.neckLength", neckLength);
|
||||
chestDistance = server.config.getFloat("body.chestDistance", chestDistance);
|
||||
waistDistance = server.config.getFloat("body.waistDistance", waistDistance);
|
||||
trackerWaistDistance = server.config.getFloat("body.trackerWaistDistance", trackerWaistDistance);
|
||||
// Build skeleton
|
||||
hmdNode.attachChild(headNode);
|
||||
headNode.localTransform.setTranslation(0, 0, headShift);
|
||||
|
||||
headNode.attachChild(neckNode);
|
||||
neckNode.localTransform.setTranslation(0, -neckLength, 0);
|
||||
|
||||
neckNode.attachChild(chestNode);
|
||||
chestNode.localTransform.setTranslation(0, -chestDistance, 0);
|
||||
|
||||
chestNode.attachChild(waistNode);
|
||||
waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0);
|
||||
|
||||
chestNode.attachChild(trackerWaistNode);
|
||||
trackerWaistNode.localTransform.setTranslation(0, -(waistDistance + trackerWaistDistance - chestDistance), 0);
|
||||
|
||||
configMap.put("Head", headShift);
|
||||
configMap.put("Neck", neckLength);
|
||||
configMap.put("Chest", chestDistance);
|
||||
configMap.put("Waist", waistDistance);
|
||||
configMap.put("Virtual waist", trackerWaistDistance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetSkeletonConfig(String joint) {
|
||||
switch(joint) {
|
||||
case "All": // Reset all joints according to height
|
||||
resetSkeletonConfig("Head");
|
||||
resetSkeletonConfig("Neck");
|
||||
resetSkeletonConfig("Virtual waist");
|
||||
resetSkeletonConfig("Waist");
|
||||
resetSkeletonConfig("Chest");
|
||||
break;
|
||||
case "Head":
|
||||
setSkeletonConfig(joint, HEAD_SHIFT_DEFAULT);
|
||||
break;
|
||||
case "Neck":
|
||||
setSkeletonConfig(joint, NECK_LENGTH_DEFAULT);
|
||||
break;
|
||||
case "Virtual waist":
|
||||
setSkeletonConfig(joint, 0.0f);
|
||||
break;
|
||||
case "Chest":
|
||||
setSkeletonConfig(joint, waistDistance / 2.0f);
|
||||
break;
|
||||
case "Waist": // Puts Waist in the middle of the height
|
||||
Vector3f vec = new Vector3f();
|
||||
hmdTracker.getPosition(vec);
|
||||
float 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
|
||||
setSkeletonConfig(joint, (height) / 2.0f);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Float> getSkeletonConfig() {
|
||||
return configMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkeletonConfig(String joint, float newLength) {
|
||||
configMap.put(joint, newLength);
|
||||
switch(joint) {
|
||||
case "Head":
|
||||
headShift = newLength;
|
||||
server.config.setProperty("body.headShift", headShift);
|
||||
headNode.localTransform.setTranslation(0, 0, headShift);
|
||||
break;
|
||||
case "Neck":
|
||||
neckLength = newLength;
|
||||
server.config.setProperty("body.neckLength", neckLength);
|
||||
neckNode.localTransform.setTranslation(0, -neckLength, 0);
|
||||
break;
|
||||
case "Waist":
|
||||
waistDistance = newLength;
|
||||
server.config.setProperty("body.waistDistance", waistDistance);
|
||||
waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0);
|
||||
trackerWaistNode.localTransform.setTranslation(0, -(waistDistance + trackerWaistDistance - chestDistance), 0);
|
||||
break;
|
||||
case "Chest":
|
||||
chestDistance = newLength;
|
||||
server.config.setProperty("body.chestDistance", chestDistance);
|
||||
chestNode.localTransform.setTranslation(0, -chestDistance, 0);
|
||||
waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0);
|
||||
trackerWaistNode.localTransform.setTranslation(0, -(waistDistance + trackerWaistDistance - chestDistance), 0);
|
||||
break;
|
||||
case "Virtual waist":
|
||||
trackerWaistDistance = newLength;
|
||||
server.config.setProperty("body.trackerWaistDistance", trackerWaistDistance);
|
||||
trackerWaistNode.localTransform.setTranslation(0, -(waistDistance + trackerWaistDistance - chestDistance), 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getSkeletonConfigBoolean(String config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSkeletonConfigBoolean(String config, boolean newState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransformNode getRootNode() {
|
||||
return hmdNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void updatePose() {
|
||||
updateLocalTransforms();
|
||||
hmdNode.update();
|
||||
updateComputedTrackers();
|
||||
}
|
||||
|
||||
protected void updateLocalTransforms() {
|
||||
if(hmdTracker.getPosition(vBuf)) {
|
||||
hmdNode.localTransform.setTranslation(vBuf);
|
||||
}
|
||||
if(hmdTracker.getRotation(qBuf)) {
|
||||
hmdNode.localTransform.setRotation(qBuf);
|
||||
headNode.localTransform.setRotation(qBuf);
|
||||
}
|
||||
|
||||
if(chestTracker.getRotation(qBuf))
|
||||
neckNode.localTransform.setRotation(qBuf);
|
||||
|
||||
if(waistTracker.getRotation(qBuf)) {
|
||||
trackerWaistNode.localTransform.setRotation(qBuf);
|
||||
chestNode.localTransform.setRotation(qBuf);
|
||||
waistNode.localTransform.setRotation(qBuf);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateComputedTrackers() {
|
||||
if(computedWaistTracker != null) {
|
||||
computedWaistTracker.position.set(trackerWaistNode.worldTransform.getTranslation());
|
||||
computedWaistTracker.rotation.set(trackerWaistNode.worldTransform.getRotation());
|
||||
computedWaistTracker.dataTick();
|
||||
}
|
||||
|
||||
if(computedChestTracker != null) {
|
||||
computedChestTracker.position.set(chestNode.worldTransform.getTranslation());
|
||||
computedChestTracker.rotation.set(neckNode.worldTransform.getRotation());
|
||||
computedChestTracker.dataTick();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void resetTrackersFull() {
|
||||
// Each tracker uses the tracker before it to adjust iteself,
|
||||
// so trackers that don't need adjustments could be used too
|
||||
Quaternion referenceRotation = new Quaternion();
|
||||
server.hmdTracker.getRotation(referenceRotation);
|
||||
|
||||
this.chestTracker.resetFull(referenceRotation);
|
||||
this.chestTracker.getRotation(referenceRotation);
|
||||
|
||||
this.waistTracker.resetFull(referenceRotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@VRServerThread
|
||||
public void resetTrackersYaw() {
|
||||
// Each tracker uses the tracker before it to adjust iteself,
|
||||
// so trackers that don't need adjustments could be used too
|
||||
Quaternion referenceRotation = new Quaternion();
|
||||
server.hmdTracker.getRotation(referenceRotation);
|
||||
|
||||
this.chestTracker.resetYaw(referenceRotation);
|
||||
this.chestTracker.getRotation(referenceRotation);
|
||||
|
||||
this.waistTracker.resetYaw(referenceRotation);
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
package io.eiren.vr.trackers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
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);
|
||||
socket.setSoTimeout(250);
|
||||
while(true) {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
package io.eiren.unit;
|
||||
package dev.slimevr.unit;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Quaternion;
|
||||
|
||||
import dev.slimevr.vr.processor.TransformNode;
|
||||
import dev.slimevr.vr.trackers.ComputedTracker;
|
||||
import dev.slimevr.vr.trackers.ReferenceAdjustedTracker;
|
||||
import dev.slimevr.vr.trackers.Tracker;
|
||||
import io.eiren.math.FloatMath;
|
||||
import io.eiren.util.StringUtils;
|
||||
import io.eiren.vr.processor.TransformNode;
|
||||
import io.eiren.vr.trackers.ComputedTracker;
|
||||
import io.eiren.vr.trackers.ReferenceAdjustedTracker;
|
||||
import io.eiren.vr.trackers.Tracker;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
@@ -57,7 +57,9 @@ public class ReferenceAdjustmentsTests {
|
||||
));
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
// TODO : Test is not passing because the test is wrong
|
||||
// See issue https://github.com/SlimeVR/SlimeVR-Server/issues/55
|
||||
//@TestFactory
|
||||
Stream<DynamicTest> getTestsForRotation() {
|
||||
return getAnglesSet().map((p) ->
|
||||
IntStream.of(yaws).mapToObj((refYaw) ->
|
||||
Reference in New Issue
Block a user