Compare commits

...

47 Commits

Author SHA1 Message Date
Eiren Rain
2b4ce4b920 Fix version number 2021-08-02 23:08:22 +03:00
Eiren Rain
4e7585b87e Added support for different SteamVR trackers configuration 2021-08-02 23:07:25 +03:00
Eiren Rain
de13db4627 Fix right foot wasn't resetting 2021-07-31 03:05:08 +03:00
Eiren Rain
ca8ceb428b Set version to 0.0.12 2021-07-26 02:01:18 +03:00
Eiren Rain
c18597387a Merge pull request #20 from adigyran/patch-5
Update build.gradle
2021-07-26 02:00:25 +03:00
Yury
962504b788 Update build.gradle
useJUnitPlatform
2021-07-26 01:27:15 +03:00
Eiren Rain
8d1886d045 Display raw tracker data in degrees not in quats 2021-07-26 00:55:01 +03:00
Eiren Rain
1c5167bb7c Another tracker adjustment fix, doesn't pass all tests, but works better 2021-07-26 00:48:34 +03:00
Eiren Rain
e248cca4e7 Adjustmed trackers pass all tests 2021-07-25 23:04:34 +03:00
Eiren Rain
89ee97872d Streams go brrrr in unit tests 2021-07-25 22:45:54 +03:00
Eiren Rain
b22a2368d4 Refactor tests, generate tests for each angle dynamically, separate 3 test types 2021-07-25 22:19:04 +03:00
Eiren Rain
9ecfc57e44 Use JUnit 5 framework for testing 2021-07-25 20:57:11 +03:00
Eiren Rain
cd141258c5 Merge pull request #19 from adigyran/patch-4
Update build.gradle
2021-07-23 17:51:47 +03:00
Yury
5dc027a9e2 Update build.gradle
fix gradle dependencies
2021-07-23 17:46:29 +03:00
Eiren Rain
3e55b0e417 Merge pull request #18 from adigyran/patch-3
Update README.md
2021-07-22 12:36:20 +03:00
Eiren Rain
9ca6b21c61 Merge pull request #17 from ButterscotchVanilla/main
Automatically detect and set the Slime Java Commons subproject location
2021-07-22 12:36:01 +03:00
Yury
8ec528d4a0 Update README.md
formatting, thanks Butterscotch for some changes
2021-07-22 02:56:25 +03:00
Yury
961946bd29 Update README.md
formatting
2021-07-22 02:53:35 +03:00
Yury
da5fc860cf Update README.md
formatting
2021-07-22 02:50:54 +03:00
Yury
fdd39c4010 Update README.md
How to build instructions, this is for ButterscotchVanilla's PR  #17
2021-07-22 02:46:39 +03:00
ButterscotchVanilla
900e96a3a6 Announce subproject location before setting it 2021-07-21 17:39:45 -04:00
ButterscotchVanilla
6a9f42f126 Auto-detect Slime Java Commons subproject location 2021-07-21 17:35:09 -04:00
ButterscotchVanilla
72ea196359 Update gradle.yml 2021-07-21 17:11:01 -04:00
ButterscotchVanilla
90a8abeed2 Add comments to build.gradle and add path to subproject 2021-07-21 17:07:16 -04:00
Eiren Rain
34fcbfa96f Minor fixes 2021-07-21 22:21:39 +03:00
Eiren Rain
0f360cf892 WiFi window should be able to use CP2102 usb to uart too 2021-07-21 22:15:03 +03:00
Eiren Rain
22d4196bed Implement setting WiFi credentials via GUI 2021-07-21 22:06:35 +03:00
Eiren Rain
fb9860d51d Improve unit testing for adjusted trackers, not properly tests reference yaw != 0 2021-07-21 18:13:00 +03:00
Eiren Rain
c8ba9d62aa Added unit tests for adjusted trackers rotation, another rotation correction alghorithm
Added version number to the title to reduce confusion
2021-07-21 17:38:43 +03:00
Eiren Rain
4a66128af5 Merge pull request #15 from adigyran/patch-1
Update README.md
2021-07-20 15:54:27 +03:00
Yury
f061410205 Update README.md 2021-07-20 15:51:53 +03:00
Eiren Rain
1250e77771 More correct (but still not completely correct) tracker adjustments 2021-07-19 18:35:08 +03:00
Eiren Rain
a20334d026 Fixed trackers ping not being displayed 2021-07-13 17:25:15 +03:00
Eiren Rain
c2b4d30047 Removed leg yaw averaging, it was causing leg gimbal lock 2021-07-08 16:46:55 +03:00
Eiren Rain
755ab592f1 Rework legs proportions to legs length and knee height 2021-07-08 06:36:23 +03:00
Eiren Rain
9e0147ed27 Implemented body proportions reset buttons to reset things to their defaults based on user's height 2021-07-08 06:16:02 +03:00
Eiren Rain
0ab92322a8 Minor GUI fix 2021-07-08 06:15:21 +03:00
Eiren Rain
4e58df76fb Merge pull request #6 from ButterscotchVanilla/main
Add Slime Java Commons to Gradle build and create a GitHub Actions workflow
2021-07-08 02:50:33 +03:00
Butterscotch!
d3f81625ce Add testing to workflow 2021-07-07 19:39:08 -04:00
Butterscotch!
b97a92b682 Create GitHub Actions build script (#1)
* Create gradle.yml
2021-07-07 19:29:54 -04:00
ButterscotchVanilla
5b2918acb2 Build dependencies when packaging a server jar 2021-07-07 19:19:46 -04:00
ButterscotchVanilla
cbf37a7c9c Add Slime Java Commons as a dependency and add runnable jar task 2021-07-07 18:48:24 -04:00
Eiren Rain
f169cfd0c7 Merge pull request #5 from ButterscotchVanilla/main
Retain initial font size while zooming and display zoom level
2021-07-08 00:39:33 +03:00
ButterscotchVanilla
0e51b79775 Fix formatting 2021-07-07 10:27:42 -04:00
ButterscotchVanilla
1ee13c02d9 Retain initial font size while zooming and display zoom level 2021-07-07 10:19:55 -04:00
Eiren Rain
94829060a3 Added GUI zoom button, made default zoom level 150% instead of 200%
Minor gui refactoring
2021-07-06 23:00:10 +03:00
Eiren Rain
b790cfbd7d Fix updating waist doesn't move tracker right away 2021-07-06 22:59:15 +03:00
22 changed files with 1140 additions and 571 deletions

48
.github/workflows/gradle.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: Build SlimeVR Server with Gradle
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- name: Clone Slime Java Commons
uses: actions/checkout@v2.3.4
with:
repository: Eirenliel/slime-java-commons
# Relative path under $GITHUB_WORKSPACE to place the repository
path: Slime Java Commons
- name: Set up JDK 11
uses: actions/setup-java@v2.1.0
with:
java-version: '11'
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Test with Gradle
run: ./gradlew clean test
- name: Build with Gradle
run: ./gradlew clean serverJar
- name: Upload the Server JAR as a Build Artifact
uses: actions/upload-artifact@v2.2.4
with:
# Artifact name
name: "SlimeVR-Server" # optional, default is artifact
# A file, directory or wildcard pattern that describes what to upload
path: build/libs/*

View File

@@ -5,7 +5,7 @@ Server orchestrates communication between multiple sensors and integrations, lik
Sensors implementations:
* [SlimeVR Tracker for ESP](https://github.com/SlimeVR/SlimeVR-Tracker-ESP) - ESP microcontrollers and multiple IMUs are supported
* [owoTrack Mobile App](https://github.com/abb128/owoTrackVRSyncMobile) - use phone as a tracker (limited functionality and copmatibility)
* [owoTrack Mobile App](https://github.com/abb128/owoTrackVRSyncMobile) - use phone as a tracker (limited functionality and compatibility)
Integrations:
* Use [SlimeVR OpenVR Driver](https://github.com/SlimeVR/SlimeVR-OpenVR-Driver) as a driver for SteamVR
@@ -13,4 +13,22 @@ Integrations:
## How to use
Latest instructions are currently [here](https://gist.github.com/Eirenliel/8c0eefcdbda1076d5c2e1bf634831d20). Will be updated and republished as time goes on.
Latest instructions are currently [here](https://gist.github.com/Eirenliel/8c0eefcdbda1076d5c2e1bf634831d20). Will be updated and republished as time goes on.
## How to build
You need to execute these commands in the folder where you want this project.
```bash
# Clone repositories
git clone https://github.com/SlimeVR/SlimeVR-Server.git
git clone https://github.com/Eirenliel/slime-java-commons.git
# Enter the directory and build the runnable server JAR
cd SlimeVR-Server
gradlew serverJar
```
Open Slime VR Server project in Eclipse or Intellij Idea
run gradle command `serverJar` to build a runnable server JAR

View File

@@ -19,17 +19,41 @@ repositories {
}
dependencies {
compile project(':Slime Java Commons')
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
api 'org.yaml:snakeyaml:1.25'
api 'net.java.dev.jna:jna:5.6.0'
api 'net.java.dev.jna:jna-platform:5.6.0'
api 'com.illposed.osc:javaosc-core:0.8'
compile 'org.apache.commons:commons-math3:3.6.1'
compile 'org.yaml:snakeyaml:1.25'
compile 'net.java.dev.jna:jna:5.6.0'
compile 'net.java.dev.jna:jna-platform:5.6.0'
compile 'com.illposed.osc:javaosc-core:0.8'
compile 'com.fazecast:jSerialComm:[2.0.0,3.0.0)'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:28.2-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
testImplementation platform('org.junit:junit-bom:5.7.2')
testImplementation 'org.junit.jupiter:junit-jupiter'
testImplementation 'org.junit.platform:junit-platform-launcher'
}
test {
useJUnitPlatform()
}
subprojects.each { subproject -> evaluationDependsOn(subproject.path) }
task serverJar (type: Jar, dependsOn: subprojects.tasks['build']) {
// Make the JAR runnable
manifest {
attributes 'Main-Class': 'io.eiren.vr.Main'
}
// Pack all dependencies within the JAR
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
// Add this project's classes in the JAR
with jar
}

View File

@@ -8,4 +8,19 @@
*/
rootProject.name = 'SlimeVR Server'
include('Slime Java Commons')
include('Slime Java Commons')
def commonsDirs = [
new File(settingsDir, 'Slime Java Commons'),
new File(settingsDir, 'slime-java-commons'),
new File(settingsDir, '../Slime Java Commons'),
new File(settingsDir, '../slime-java-commons')
]
for (commonsDir in commonsDirs) {
if (commonsDir.isDirectory()) {
logger.info('\"Slime Java Commons\" subproject detected at \"{}\"', commonsDir.getCanonicalPath())
project(':Slime Java Commons').projectDir = commonsDir
break
}
}

View File

@@ -0,0 +1,42 @@
package io.eiren.gui;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.AbstractButton;
public class ButtonTimer {
private static Timer timer = new Timer();
public static void runTimer(AbstractButton button, int seconds, String defaultText, Runnable runnable) {
if(seconds <= 0) {
button.setText(defaultText);
runnable.run();
} else {
button.setText(String.valueOf(seconds));
timer.schedule(new ButtonTimerTask(button, seconds - 1, defaultText, runnable), 1000);
}
}
private static class ButtonTimerTask extends TimerTask {
private final AbstractButton button;
private final int seconds;
private final String defaultText;
private final Runnable runnable;
private ButtonTimerTask(AbstractButton button, int seconds, String defaultText, Runnable runnable) {
this.button = button;
this.seconds = seconds;
this.defaultText = defaultText;
this.runnable = runnable;
}
@Override
public void run() {
runTimer(button, seconds, defaultText, runnable);
}
}
}

View File

@@ -0,0 +1,91 @@
package io.eiren.gui;
import java.awt.Font;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.Map;
public class ScalableFont extends Font {
protected float scale = 1.0f;
protected int initSize;
protected float initPointSize;
public ScalableFont(Map<? extends Attribute, ?> attributes) {
super(attributes);
this.initSize = this.size;
this.initPointSize = this.pointSize;
}
public ScalableFont(Font font) {
super(font);
if(font instanceof ScalableFont) {
ScalableFont sourceFont = (ScalableFont)font;
this.initSize = sourceFont.getInitSize();
this.initPointSize = sourceFont.getInitSize2D();
this.size = this.initSize;
this.pointSize = this.initPointSize;
} else {
this.initSize = this.size;
this.initPointSize = this.pointSize;
}
}
public ScalableFont(Font font, float scale) {
super(font);
if(font instanceof ScalableFont) {
ScalableFont sourceFont = (ScalableFont)font;
this.initSize = sourceFont.getInitSize();
this.initPointSize = sourceFont.getInitSize2D();
} else {
this.initSize = this.size;
this.initPointSize = this.pointSize;
}
setScale(scale);
}
public ScalableFont(String name, int style, int size) {
super(name, style, size);
this.initSize = this.size;
this.initPointSize = this.pointSize;
}
public ScalableFont(String name, int style, int size, float scale) {
super(name, style, size);
this.initSize = this.size;
this.initPointSize = this.pointSize;
setScale(scale);
}
public int getInitSize() {
return initSize;
}
public float getInitSize2D() {
return initPointSize;
}
public float getScale() {
return scale;
}
private void setScale(float scale) {
this.scale = scale;
float newPointSize = initPointSize * scale;
this.size = (int)(newPointSize + 0.5);
this.pointSize = newPointSize;
}
}

View File

@@ -34,53 +34,71 @@ public class SkeletonConfig extends EJBag {
removeAll();
int row = 0;
add(new TimedResetButton("Reset All", "All"), s(c(1, row, 1), 3, 1));
row++;
add(new JLabel("Chest"), c(0, row, 1));
add(new AdjButton("+", "Chest", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Chest"), c(2, row, 1));
add(new AdjButton("-", "Chest", -0.01f), c(3, row, 1));
add(new ResetButton("Reset", "Chest"), c(4, row, 1));
row++;
add(new JLabel("Waist"), c(0, row, 1));
add(new AdjButton("+", "Waist", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Waist"), c(2, row, 1));
add(new AdjButton("-", "Waist", -0.01f), c(3, row, 1));
row++;
add(new JLabel("Virtual waist"), c(0, row, 1));
add(new AdjButton("+", "Virtual waist", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Virtual waist"), c(2, row, 1));
add(new AdjButton("-", "Virtual waist", -0.01f), c(3, row, 1));
add(new TimedResetButton("Reset", "Waist"), c(4, row, 1));
row++;
add(new JLabel("Hips width"), c(0, row, 1));
add(new AdjButton("+", "Hips width", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Hips width"), c(2, row, 1));
add(new AdjButton("-", "Hips width", -0.01f), c(3, row, 1));
add(new ResetButton("Reset", "Hips width"), c(4, row, 1));
row++;
add(new JLabel("Hip length"), c(0, row, 1));
add(new AdjButton("+", "Hip length", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Hip length"), c(2, row, 1));
add(new AdjButton("-", "Hip length", -0.01f), c(3, row, 1));
add(new JLabel("Legs length"), c(0, row, 1));
add(new AdjButton("+", "Legs length", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Legs length"), c(2, row, 1));
add(new AdjButton("-", "Legs length", -0.01f), c(3, row, 1));
add(new TimedResetButton("Reset", "Legs length"), c(4, row, 1));
row++;
add(new JLabel("Ankle length"), c(0, row, 1));
add(new AdjButton("+", "Ankle length", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Ankle length"), c(2, row, 1));
add(new AdjButton("-", "Ankle length", -0.01f), c(3, row, 1));
add(new JLabel("Knee height"), c(0, row, 1));
add(new AdjButton("+", "Knee height", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Knee height"), c(2, row, 1));
add(new AdjButton("-", "Knee height", -0.01f), c(3, row, 1));
add(new TimedResetButton("Reset", "Knee height"), c(4, row, 1));
row++;
add(new JLabel("Foot length"), c(0, row, 1));
add(new AdjButton("+", "Foot length", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Foot length"), c(2, row, 1));
add(new AdjButton("-", "Foot length", -0.01f), c(3, row, 1));
add(new ResetButton("Reset", "Foot length"), c(4, row, 1));
row++;
add(new JLabel("Head offset"), c(0, row, 1));
add(new AdjButton("+", "Head", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Head"), c(2, row, 1));
add(new AdjButton("-", "Head", -0.01f), c(3, row, 1));
add(new ResetButton("Reset", "Head"), c(4, row, 1));
row++;
add(new JLabel("Neck length"), c(0, row, 1));
add(new AdjButton("+", "Neck", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Neck"), c(2, row, 1));
add(new AdjButton("-", "Neck", -0.01f), c(3, row, 1));
add(new ResetButton("Reset", "Neck"), c(4, row, 1));
row++;
add(new JLabel("Virtual waist"), c(0, row, 1));
add(new AdjButton("+", "Virtual waist", 0.01f), c(1, row, 1));
add(new SkeletonLabel("Virtual waist"), c(2, row, 1));
add(new AdjButton("-", "Virtual waist", -0.01f), c(3, row, 1));
add(new ResetButton("Reset", "Virtual waist"), c(4, row, 1));
row++;
gui.refresh();
@@ -91,13 +109,27 @@ public class SkeletonConfig extends EJBag {
float current = server.humanPoseProcessor.getSkeletonConfig(joint);
server.humanPoseProcessor.setSkeletonConfig(joint, current + diff);
server.saveConfig();
labels.get(joint).setText(StringUtils.prettyNumber(current + diff, 2));
labels.get(joint).setText(StringUtils.prettyNumber((current + diff) * 100, 0));
}
private void reset(String joint) {
server.humanPoseProcessor.resetSkeletonConfig(joint);
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));
});
}
}
private class SkeletonLabel extends JLabel {
public SkeletonLabel(String joint) {
super(StringUtils.prettyNumber(server.humanPoseProcessor.getSkeletonConfig(joint), 2));
super(StringUtils.prettyNumber(server.humanPoseProcessor.getSkeletonConfig(joint) * 100, 0));
labels.put(joint, this);
}
}
@@ -114,4 +146,30 @@ public class SkeletonConfig extends EJBag {
});
}
}
private class ResetButton extends JButton {
public ResetButton(String text, String joint) {
super(text);
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
reset(joint);
}
});
}
}
private class TimedResetButton extends JButton {
public TimedResetButton(String text, String joint) {
super(text);
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
ButtonTimer.runTimer(TimedResetButton.this, 3, text, () -> reset(joint));
}
});
}
}
}

View File

@@ -101,6 +101,9 @@ public class TrackersList extends EJBox {
JLabel tps;
JLabel bat;
JLabel ping;
JLabel raw;
JLabel adj;
JLabel adjYaw;
@AWTThread
public TrackerRow(Tracker t) {
@@ -175,12 +178,22 @@ public class TrackersList extends EJBox {
add(status = new JLabel(t.getStatus().toString().toLowerCase()), c(1, 4, 0, GridBagConstraints.FIRST_LINE_START));
add(new JLabel("Battery:"), c(2, 4, 0, GridBagConstraints.FIRST_LINE_START));
add(bat = new JLabel("0"), c(3, 4, 0, GridBagConstraints.FIRST_LINE_START));
add(new JLabel("Raw:"), c(0, 5, 0, GridBagConstraints.FIRST_LINE_START));
add(raw = new JLabel("0 0 0"), s(c(1, 5, 0, GridBagConstraints.FIRST_LINE_START), 3, 1));
if(t instanceof ReferenceAdjustedTracker) {
add(new JLabel("Adj:"), c(0, 6, 0, GridBagConstraints.FIRST_LINE_START));
add(adj = new JLabel("0 0 0 0"), c(1, 6, 0, GridBagConstraints.FIRST_LINE_START));
add(new JLabel("AdjY:"), c(2, 6, 0, GridBagConstraints.FIRST_LINE_START));
add(adjYaw = new JLabel("0 0 0 0"), c(3, 6, 0, GridBagConstraints.FIRST_LINE_START));
}
setBorder(BorderFactory.createLineBorder(new Color(0x663399), 4, true));
TrackersList.this.add(this);
return this;
}
@SuppressWarnings("unchecked")
@AWTThread
public void update() {
if(position == null)
@@ -202,8 +215,25 @@ public class TrackersList extends EJBox {
}
if(t instanceof TrackerWithBattery)
bat.setText(StringUtils.prettyNumber(((TrackerWithBattery) t).getBatteryVoltage(), 1));
if(t instanceof IMUTracker)
ping.setText(String.valueOf(((IMUTracker) t).ping));
Tracker t2 = t;
if(t instanceof ReferenceAdjustedTracker) {
t2 = ((ReferenceAdjustedTracker<Tracker>) t).getTracker();
((ReferenceAdjustedTracker<Tracker>) t).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);
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(t2 instanceof IMUTracker)
ping.setText(String.valueOf(((IMUTracker) t2).ping));
t2.getRotation(q);
q.toAngles(angles);
raw.setText(StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 0)
+ " " + StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 0)
+ " " + StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 0));
}
}

View File

@@ -4,12 +4,17 @@ import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MouseInputAdapter;
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.bridge.NamedPipeVRBridge;
import java.awt.Component;
import java.awt.Container;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.TimerTask;
import static javax.swing.BoxLayout.PAGE_AXIS;
import static javax.swing.BoxLayout.LINE_AXIS;
@@ -19,17 +24,30 @@ public class VRServerGUI extends JFrame {
public final VRServer server;
private final TrackersList trackersList;
private final SkeletonList skeletonList;
private java.util.Timer timer = new java.util.Timer();
private JButton resetButton;
private JScrollPane scroll;
private EJBox pane;
private float zoom = 1.5f;
private float initZoom = zoom;
@AWTThread
public VRServerGUI(VRServer server) {
super("SlimeVR Server");
increaseFontSize();
super("SlimeVR Server (" + Main.VERSION + ")");
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Exception e) {
e.printStackTrace();
}
//increaseFontSize();
this.server = server;
this.zoom = server.config.getFloat("zoom", zoom);
this.initZoom = zoom;
setDefaultFontSize(zoom);
// All components should be constructed to the current zoom level by default
setDefaultCloseOperation(EXIT_ON_CLOSE);
getContentPane().setLayout(new BoxLayout(getContentPane(), PAGE_AXIS));
@@ -41,6 +59,10 @@ public class VRServerGUI extends JFrame {
build();
}
public float getZoom() {
return this.zoom;
}
public void refresh() {
// Pack and display
pack();
@@ -48,7 +70,6 @@ public class VRServerGUI extends JFrame {
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
toFront();
repaint();
}
});
@@ -58,8 +79,6 @@ public class VRServerGUI extends JFrame {
private void build() {
pane.removeAll();
NamedPipeVRBridge npvb = server.getVRBridge(NamedPipeVRBridge.class);
pane.add(new EJBox(LINE_AXIS) {{
setBorder(new EmptyBorder(i(5)));
@@ -73,36 +92,93 @@ public class VRServerGUI extends JFrame {
});
}});
add(Box.createHorizontalGlue());
if(npvb != null) {
add(new JButton(npvb.isOneTrackerMode() ? "1" : "3") {{
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
npvb.setSpawnOneTracker(!npvb.isOneTrackerMode());
setText(npvb.isOneTrackerMode() ? "1" : "3");
}
});
}});
add(Box.createHorizontalStrut(10));
}
add(new JButton("GUI Zoom (x" + StringUtils.prettyNumber(zoom, 2) + ")") {{
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
guiZoom();
setText("GUI Zoom (x" + StringUtils.prettyNumber(zoom, 2) + ")");
}
});
}});
add(Box.createHorizontalStrut(10));
add(new JButton("WiFi") {{
addMouseListener(new MouseInputAdapter() {
@SuppressWarnings("unused")
@Override
public void mouseClicked(MouseEvent e) {
new WiFiWindow(VRServerGUI.this);
}
});
}});
add(Box.createHorizontalStrut(10));
}});
pane.add(new EJBox(LINE_AXIS) {{
setBorder(new EmptyBorder(i(5)));
add(new EJBox(PAGE_AXIS) {{
setAlignmentY(TOP_ALIGNMENT);
add(new JLabel("SteamVR Trackers:"));
JComboBox<String> trackersSelect;
add(trackersSelect = new JComboBox<>());
trackersSelect.addItem("Waist");
trackersSelect.addItem("Waist + Feet");
trackersSelect.addItem("Waist + Feet + Chest");
trackersSelect.addItem("Waist + Feet + Knees");
trackersSelect.addItem("Waist + Feet + Chest + Knees");
switch(server.config.getInt("vitrualtrackers", 3)) {
case 1:
trackersSelect.setSelectedIndex(0);
break;
case 3:
trackersSelect.setSelectedIndex(1);
break;
case 4:
trackersSelect.setSelectedIndex(2);
break;
case 5:
trackersSelect.setSelectedIndex(3);
break;
case 6:
trackersSelect.setSelectedIndex(4);
break;
}
trackersSelect.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
switch(trackersSelect.getSelectedIndex()) {
case 0:
server.config.setProperty("vitrualtrackers", 1);
break;
case 1:
server.config.setProperty("vitrualtrackers", 3);
break;
case 2:
server.config.setProperty("vitrualtrackers", 4);
break;
case 3:
server.config.setProperty("vitrualtrackers", 5);
break;
case 4:
server.config.setProperty("vitrualtrackers", 6);
break;
}
server.saveConfig();
}
});
add(Box.createHorizontalStrut(10));
add(new JLabel("Trackers"));
add(new JLabel("Trackers list"));
add(trackersList);
add(Box.createVerticalGlue());
}});
add(new EJBox(PAGE_AXIS) {{
setAlignmentY(TOP_ALIGNMENT);
add(new JLabel("Skeleton"));
add(skeletonList);
add(new JLabel("Skeleton config"));
add(new JLabel("Body proportions"));
add(new SkeletonConfig(server, VRServerGUI.this));
add(new JLabel("Skeleton data"));
add(skeletonList);
add(Box.createVerticalGlue());
}});
}});
@@ -114,14 +190,45 @@ public class VRServerGUI extends JFrame {
server.addOnTick(skeletonList::updateBones);
}
private static void increaseFontSize() {
// For now only changes font size, but should change fixed components size in the future too
private void guiZoom() {
if(zoom <= 1.0f) {
zoom = 1.5f;
} else if(zoom <= 1.5f) {
zoom = 1.75f;
} else if(zoom <= 1.75f) {
zoom = 2.0f;
} else if(zoom <= 2.0f) {
zoom = 2.5f;
} else {
zoom = 1.0f;
}
processNewZoom(zoom / initZoom, pane);
refresh();
server.config.setProperty("zoom", zoom);
server.saveConfig();
}
private static void processNewZoom(float zoom, Component comp) {
if(comp.isFontSet()) {
Font newFont = new ScalableFont(comp.getFont(), zoom);
comp.setFont(newFont);
}
if(comp instanceof Container) {
Container cont = (Container) comp;
for(Component child : cont.getComponents())
processNewZoom(zoom, child);
}
}
private static void setDefaultFontSize(float zoom) {
java.util.Enumeration<Object> keys = UIManager.getDefaults().keys();
while(keys.hasMoreElements()) {
Object key = keys.nextElement();
Object value = UIManager.get(key);
if(value instanceof javax.swing.plaf.FontUIResource) {
javax.swing.plaf.FontUIResource f = (javax.swing.plaf.FontUIResource) value;
javax.swing.plaf.FontUIResource f2 = new javax.swing.plaf.FontUIResource(f.deriveFont(f.getSize() * 2f));
javax.swing.plaf.FontUIResource f2 = new javax.swing.plaf.FontUIResource(f.deriveFont(f.getSize() * zoom));
UIManager.put(key, f2);
}
}
@@ -129,37 +236,6 @@ public class VRServerGUI extends JFrame {
@AWTThread
private void reset() {
resetButton.setText("5");
timer.schedule(new TimerTask() {
@Override
public void run() {
resetButton.setText("4");
timer.schedule(new TimerTask() {
@Override
public void run() {
resetButton.setText("3");
timer.schedule(new TimerTask() {
@Override
public void run() {
resetButton.setText("2");
timer.schedule(new TimerTask() {
@Override
public void run() {
resetButton.setText("1");
timer.schedule(new TimerTask() {
@Override
public void run() {
server.resetTrackers();
resetButton.setText("RESET");
}
}, 1000);
}
}, 1000);
}
}, 1000);
}
}, 1000);
}
}, 1000);
ButtonTimer.runTimer(resetButton, 3, "RESET", server::resetTrackers);
}
}

View File

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

View File

@@ -5,9 +5,12 @@ import java.io.File;
import io.eiren.gui.VRServerGUI;
import io.eiren.util.logging.LogManager;
public class Main {
public static String VERSION = "0.0.13";
public static VRServer vrServer;
@SuppressWarnings("unused")
public static void main(String[] args) {
System.setProperty("awt.useSystemAAFontSettings", "on");
@@ -21,7 +24,7 @@ public class Main {
}
try {
VRServer vrServer = new VRServer();
vrServer = new VRServer();
vrServer.start();
new VRServerGUI(vrServer);
} catch(Throwable e) {

View File

@@ -50,7 +50,7 @@ public class VRServer extends Thread {
hmdTracker = new HMDTracker("HMD");
hmdTracker.position.set(0, 1.8f, 0); // Set starting position for easier debugging
// TODO Multiple processors
humanPoseProcessor = new HumanPoseProcessor(this, hmdTracker);
humanPoseProcessor = new HumanPoseProcessor(this, hmdTracker, config.getInt("vitrualtrackers", 3));
List<? extends Tracker> shareTrackers = humanPoseProcessor.getComputedTrackers();
// Create named pipe bridge for SteamVR driver

View File

@@ -43,8 +43,6 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
private final HMDTracker internalHMDTracker = new HMDTracker("itnernal://HMD");
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
private boolean spawnOneTracker = false;
public NamedPipeVRBridge(HMDTracker hmd, List<? extends Tracker> shareTrackers, VRServer server) {
super("Named Pipe VR Bridge");
this.server = server;
@@ -58,26 +56,6 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
ct.setStatus(TrackerStatus.OK);
this.internalTrackers.add(ct);
}
this.spawnOneTracker = server.config.getBoolean("openvr.onetracker", spawnOneTracker);
}
public boolean isOneTrackerMode() {
return this.spawnOneTracker;
}
/**
* Makes OpenVR bridge spawn only 1 tracker instead of 3, for
* use with only waist/chest tracking. Requires restart.
*/
public void setSpawnOneTracker(boolean spawnOneTracker) {
if(spawnOneTracker == this.spawnOneTracker)
return;
this.spawnOneTracker = spawnOneTracker;
if(this.spawnOneTracker)
this.server.config.setProperty("openvr.onetracker", true);
else
this.server.config.removeProperty("openvr.onetracker");
this.server.saveConfig();
}
@Override
@@ -133,8 +111,6 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
if(tryOpeningPipe(trackerPipe))
initTrackerPipe(trackerPipe, i);
}
if(spawnOneTracker)
break;
}
}
@@ -194,7 +170,7 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
}
private void initTrackerPipe(Pipe pipe, int trackerId) {
String trackerHello = (spawnOneTracker ? "1" : this.shareTrackers.size()) + " 0";
String trackerHello = this.shareTrackers.size() + " 0";
System.arraycopy(trackerHello.getBytes(ASCII), 0, buffer, 0, trackerHello.length());
buffer[trackerHello.length()] = '\0';
IntByReference lpNumberOfBytesWritten = new IntByReference(0);
@@ -252,8 +228,6 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
throw new IOException("Can't open " + pipeName + " pipe: " + Kernel32.INSTANCE.GetLastError());
LogManager.log.info("[VRBridge] Pipe " + pipeName + " created");
trackerPipes.add(new Pipe(pipeHandle, pipeName));
if(spawnOneTracker)
break;
}
LogManager.log.info("[VRBridge] Pipes are open");
} catch(IOException e) {

View File

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

View File

@@ -19,11 +19,20 @@ public class HumanPoseProcessor {
private final List<Consumer<HumanSkeleton>> onSkeletonUpdated = new FastList<>();
private HumanSkeleton skeleton;
public HumanPoseProcessor(VRServer server, HMDTracker hmd) {
public HumanPoseProcessor(VRServer server, HMDTracker hmd, int trackersAmount) {
this.server = server;
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.WAIST));
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.LEFT_FOOT));
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.RIGHT_FOOT));
if(trackersAmount > 2) {
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.LEFT_FOOT));
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.RIGHT_FOOT));
if(trackersAmount == 4 || trackersAmount >= 6) {
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.CHEST));
}
if(trackersAmount >= 5) {
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.LEFT_KNEE));
computedTrackers.add(new ComputedHumanPoseTracker(ComputedHumanPoseTrackerPosition.RIGHT_KNEE));
}
}
}
@VRServerThread
@@ -38,6 +47,12 @@ public class HumanPoseProcessor {
if(skeleton != null)
skeleton.setSkeletonConfig(key, newLength);
}
@ThreadSafe
public void resetSkeletonConfig(String key) {
if(skeleton != null)
skeleton.resetSkeletonConfig(key);
}
@ThreadSafe
public float getSkeletonConfig(String key) {

View File

@@ -4,6 +4,7 @@ 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;
@@ -13,6 +14,10 @@ import io.eiren.vr.trackers.TrackerUtils;
public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
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 float[] kneeAngles = new float[3];
protected final float[] hipAngles = new float[3];
protected final Quaternion hipBuf = new Quaternion();
@@ -22,10 +27,12 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
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);
@@ -39,15 +46,16 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
/**
* Distance between centers of both hips
*/
protected float hipsWidth = 0.30f;
protected float hipsWidth = HIPS_WIDTH_DEFAULT;
/**
* Length from waist to knees
*/
protected float hipsLength = 0.51f;
protected float kneeHeight = 0.42f;
/**
* Distance from waist to ankle
*/
protected float ankleLength = 0.55f;
protected float legsLength = 0.84f;
protected float footLength = FOOT_LENGTH_DEFAULT;
protected float minKneePitch = 0f * FastMath.DEG_TO_RAD;
protected float maxKneePitch = 90f * FastMath.DEG_TO_RAD;
@@ -65,20 +73,29 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
this.rightFootTracker = TrackerUtils.findTrackerForBodyPosition(allTracekrs, TrackerBodyPosition.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;
}
computedLeftFootTracker = lat;
computedRightFootTracker = rat;
computedLeftKneeTracker = lkt;
computedRightKneeTracker = rkt;
lat.setStatus(TrackerStatus.OK);
rat.setStatus(TrackerStatus.OK);
hipsWidth = server.config.getFloat("body.hipsWidth", hipsWidth);
hipsLength = server.config.getFloat("body.hipLength", hipsLength);
ankleLength = server.config.getFloat("body.ankleLength", ankleLength);
kneeHeight = server.config.getFloat("body.kneeHeight", kneeHeight);
legsLength = server.config.getFloat("body.legsLength", legsLength);
footLength = server.config.getFloat("body.footLength", footLength);
waistNode.attachChild(leftHipNode);
leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0);
@@ -87,26 +104,58 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0);
leftHipNode.attachChild(leftKneeNode);
leftKneeNode.localTransform.setTranslation(0, -hipsLength, 0);
leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
rightHipNode.attachChild(rightKneeNode);
rightKneeNode.localTransform.setTranslation(0, -hipsLength, 0);
rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0);
leftKneeNode.attachChild(leftAnkleNode);
leftAnkleNode.localTransform.setTranslation(0, -ankleLength, 0);
leftAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0);
rightKneeNode.attachChild(rightAnkleNode);
rightAnkleNode.localTransform.setTranslation(0, -ankleLength, 0);
rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0);
leftAnkleNode.attachChild(leftFootNode);
leftFootNode.localTransform.setTranslation(0, 0, -0.05f);
leftFootNode.localTransform.setTranslation(0, 0, -footLength);
rightAnkleNode.attachChild(rightFootNode);
rightFootNode.localTransform.setTranslation(0, 0, -0.05f);
rightFootNode.localTransform.setTranslation(0, 0, -footLength);
configMap.put("Hips width", hipsWidth);
configMap.put("Hip length", hipsLength);
configMap.put("Ankle length", ankleLength);
configMap.put("Legs length", legsLength);
configMap.put("Knee height", kneeHeight);
configMap.put("Foot length", footLength);
}
@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("Legs length");
break;
case "Hips width":
setSkeletonConfig(joint, HIPS_WIDTH_DEFAULT);
break;
case "Foot length":
setSkeletonConfig(joint, FOOT_LENGTH_DEFAULT);
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
@@ -119,17 +168,25 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0);
rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0);
break;
case "Hip length":
hipsLength = newLength;
server.config.setProperty("body.hipLength", hipsLength);
leftKneeNode.localTransform.setTranslation(0, -hipsLength, 0);
rightKneeNode.localTransform.setTranslation(0, -hipsLength, 0);
case "Knee height":
kneeHeight = newLength;
server.config.setProperty("body.kneeHeight", kneeHeight);
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);
break;
case "Ankle length":
ankleLength = newLength;
server.config.setProperty("body.ankleLength", ankleLength);
leftAnkleNode.localTransform.setTranslation(0, -ankleLength, 0);
rightAnkleNode.localTransform.setTranslation(0, -ankleLength, 0);
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;
}
}
@@ -141,7 +198,7 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
leftLegTracker.getRotation(hipBuf);
leftAnkleTracker.getRotation(kneeBuf);
calculateKneeLimits(hipBuf, kneeBuf, leftLegTracker.getConfidenceLevel(), leftAnkleTracker.getConfidenceLevel());
//calculateKneeLimits(hipBuf, kneeBuf, leftLegTracker.getConfidenceLevel(), leftAnkleTracker.getConfidenceLevel());
leftHipNode.localTransform.setRotation(hipBuf);
leftKneeNode.localTransform.setRotation(kneeBuf);
@@ -158,7 +215,7 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
rightLegTracker.getRotation(hipBuf);
rightAnkleTracker.getRotation(kneeBuf);
calculateKneeLimits(hipBuf, kneeBuf, rightLegTracker.getConfidenceLevel(), rightAnkleTracker.getConfidenceLevel());
//calculateKneeLimits(hipBuf, kneeBuf, rightLegTracker.getConfidenceLevel(), rightAnkleTracker.getConfidenceLevel());
rightHipNode.localTransform.setRotation(hipBuf);
rightKneeNode.localTransform.setRotation(kneeBuf);
@@ -207,13 +264,29 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
protected void updateComputedTrackers() {
super.updateComputedTrackers();
computedLeftFootTracker.position.set(leftFootNode.worldTransform.getTranslation());
computedLeftFootTracker.rotation.set(leftFootNode.worldTransform.getRotation());
computedLeftFootTracker.dataTick();
if(computedLeftFootTracker != null) {
computedLeftFootTracker.position.set(leftFootNode.worldTransform.getTranslation());
computedLeftFootTracker.rotation.set(leftFootNode.worldTransform.getRotation());
computedLeftFootTracker.dataTick();
}
computedRightFootTracker.position.set(rightFootNode.worldTransform.getTranslation());
computedRightFootTracker.rotation.set(rightFootNode.worldTransform.getRotation());
computedRightFootTracker.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
@@ -242,8 +315,8 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
this.rightAnkleTracker.resetFull(referenceRotation);
this.rightAnkleTracker.getRotation(referenceRotation);
if(this.rightAnkleTracker != null) {
this.rightAnkleTracker.resetFull(referenceRotation);
if(this.rightFootTracker != null) {
this.rightFootTracker.resetFull(referenceRotation);
}
}
}

View File

@@ -16,6 +16,9 @@ import io.eiren.vr.trackers.TrackerUtils;
public class HumanSkeleonWithWaist 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;
@@ -27,6 +30,7 @@ public class HumanSkeleonWithWaist extends HumanSkeleton {
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);
@@ -48,11 +52,11 @@ public class HumanSkeleonWithWaist extends HumanSkeleton {
/**
* Distacne from eyes to the base of the neck
*/
protected float neckLength = 0.1f;
protected float neckLength = NECK_LENGTH_DEFAULT;
/**
* Distance from eyes to ear
*/
protected float headShift = 0.1f;
protected float headShift = HEAD_SHIFT_DEFAULT;
public HumanSkeleonWithWaist(VRServer server, List<ComputedHumanPoseTracker> computedTrackers) {
List<Tracker> allTracekrs = server.getAllTrackers();
@@ -61,12 +65,16 @@ public class HumanSkeleonWithWaist extends HumanSkeleton {
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);
@@ -96,6 +104,39 @@ public class HumanSkeleonWithWaist extends HumanSkeleton {
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;
@@ -119,6 +160,7 @@ public class HumanSkeleonWithWaist extends HumanSkeleton {
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;
@@ -168,9 +210,17 @@ public class HumanSkeleonWithWaist extends HumanSkeleton {
}
protected void updateComputedTrackers() {
computedWaistTracker.position.set(trackerWaistNode.worldTransform.getTranslation());
computedWaistTracker.rotation.set(trackerWaistNode.worldTransform.getRotation());
computedWaistTracker.dataTick();
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

View File

@@ -18,6 +18,9 @@ public abstract class HumanSkeleton {
@ThreadSafe
public abstract void setSkeletonConfig(String key, float newLength);
@ThreadSafe
public abstract void resetSkeletonConfig(String joint);
@VRServerThread
public abstract void resetTrackersFull();

View File

@@ -1,6 +1,5 @@
package io.eiren.vr.trackers;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
@@ -9,12 +8,9 @@ import io.eiren.vr.processor.TrackerBodyPosition;
public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
public final E tracker;
private final Quaternion smoothedQuaternion = new Quaternion();
public final Quaternion adjustmentYaw = new Quaternion();
public final Quaternion adjustmentAttachment = new Quaternion();
protected float[] lastAngles = new float[3];
public float smooth = 0 * FastMath.DEG_TO_RAD;
private final float[] angles = new float[3];
public final Quaternion yawFix = new Quaternion();
public final Quaternion gyroFix = new Quaternion();
public final Quaternion attachmentFix = new Quaternion();
protected float confidenceMultiplier = 1.0f;
public ReferenceAdjustedTracker(E tracker) {
@@ -49,19 +45,14 @@ public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
*/
@Override
public void resetFull(Quaternion reference) {
resetYaw(reference);
fixGyroscope();
Quaternion sensorRotation = new Quaternion();
tracker.getRotation(sensorRotation);
adjustmentYaw.mult(sensorRotation, sensorRotation);
// Use only yaw HMD rotation
Quaternion targetTrackerRotation = new Quaternion(reference);
float[] angles = new float[3];
targetTrackerRotation.toAngles(angles);
targetTrackerRotation.fromAngles(0, angles[1], 0);
gyroFix.mult(sensorRotation, sensorRotation);
attachmentFix.set(sensorRotation).inverseLocal();
adjustmentAttachment.set(sensorRotation).inverseLocal().multLocal(targetTrackerRotation);
resetYaw(reference);
}
/**
@@ -81,33 +72,36 @@ public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
Quaternion sensorRotation = new Quaternion();
tracker.getRotation(sensorRotation);
gyroFix.mult(sensorRotation, sensorRotation);
sensorRotation.multLocal(attachmentFix);
sensorRotation.toAngles(angles);
sensorRotation.fromAngles(0, angles[1], 0);
adjustmentYaw.set(sensorRotation).inverseLocal().multLocal(targetTrackerRotation);
yawFix.set(sensorRotation).inverseLocal().multLocal(targetTrackerRotation);
}
private void fixGyroscope() {
float[] angles = new float[3];
confidenceMultiplier = 1.0f / tracker.getConfidenceLevel();
lastAngles[0] = 1000;
Quaternion sensorRotation = new Quaternion();
tracker.getRotation(sensorRotation);
sensorRotation.toAngles(angles);
sensorRotation.fromAngles(0, angles[1], 0);
gyroFix.set(sensorRotation).inverseLocal();
}
protected void adjustInternal(Quaternion store) {
store.multLocal(adjustmentAttachment);
adjustmentYaw.mult(store, store);
gyroFix.mult(store, store);
store.multLocal(attachmentFix);
yawFix.mult(store, store);
}
@Override
public boolean getRotation(Quaternion store) {
tracker.getRotation(store);
if(smooth > 0) {
store.toAngles(angles);
if(Math.abs(angles[0] - lastAngles[0]) > smooth || Math.abs(angles[1] - lastAngles[1]) > smooth || Math.abs(angles[2] - lastAngles[2]) > smooth) {
smoothedQuaternion.set(store);
store.toAngles(lastAngles);
} else {
store.set(smoothedQuaternion);
}
}
adjustInternal(store);
return true;
}

View File

@@ -0,0 +1,265 @@
package io.eiren.unit;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
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 static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
/**
* Tests {@link ReferenceAdjustedTracker#resetFull(Quaternion)}
*/
public class ReferenceAdjustmentsTests {
private static final int[] yaws = {0, 45, 90, 180, 270};
private static final int[] pitches = {0, 15, 35, -15, -35};
private static final int[] rolls = {0, 15, 35, -15, -35};
private static final boolean PRINT_TEST_RESULTS = false;
private static int errors = 0;
private static int successes = 0;
public static Stream<AnglesSet> getAnglesSet() {
return IntStream.of(yaws).mapToObj((yaw) ->
IntStream.of(pitches).mapToObj((pitch) ->
IntStream.of(rolls).mapToObj((roll) -> new AnglesSet(pitch, yaw, roll)
))).flatMap(Function.identity()).flatMap(Function.identity());
}
@TestFactory
Stream<DynamicTest> getTestsYaw() {
return getAnglesSet().map((p) ->
dynamicTest("Adjustment Yaw Test of Tracker(" + p.pitch + "," + p.yaw + "," + p.roll + ")",
() -> IntStream.of(yaws).forEach((refYaw) ->
checkReferenceAdjustmentYaw(q(p.pitch, p.yaw, p.roll), 0, refYaw, 0))
));
}
@TestFactory
Stream<DynamicTest> getTestsFull() {
return getAnglesSet().map((p) ->
dynamicTest("Adjustment Full Test of Tracker(" + p.pitch + "," + p.yaw + "," + p.roll + ")",
() -> getAnglesSet().forEach((ref) ->
checkReferenceAdjustmentFull(q(p.pitch, p.yaw, p.roll), ref.pitch, ref.yaw, ref.roll))
));
}
@TestFactory
Stream<DynamicTest> getTestsForRotation() {
return getAnglesSet().map((p) ->
IntStream.of(yaws).mapToObj((refYaw) ->
dynamicTest("Adjustment Rotation Test of Tracker(" + p.pitch + "," + p.yaw + "," + p.roll + "), Ref " + refYaw,
() -> testAdjustedTrackerRotation(q(p.pitch, p.yaw, p.roll), 0, refYaw, 0)
))).flatMap(Function.identity());
}
public void checkReferenceAdjustmentFull(Quaternion trackerQuat, int refPitch, int refYaw, int refRoll) {
Quaternion referenceQuat = q(refPitch, refYaw, refRoll);
ComputedTracker tracker = new ComputedTracker("test");
tracker.rotation.set(trackerQuat);
ReferenceAdjustedTracker<ComputedTracker> adj = new ReferenceAdjustedTracker<>(tracker);
adj.resetFull(referenceQuat);
Quaternion read = new Quaternion();
assertTrue(adj.getRotation(read), "Adjusted tracker didn't return rotation");
// Use only yaw HMD rotation
Quaternion targetTrackerRotation = new Quaternion(referenceQuat);
float[] angles = new float[3];
targetTrackerRotation.toAngles(angles);
targetTrackerRotation.fromAngles(0, angles[1], 0);
assertEquals(new QuatEqualFullWithEpsilon(read), new QuatEqualFullWithEpsilon(targetTrackerRotation),
"Adjusted quat is not equal to reference quat (" + toDegs(targetTrackerRotation) + " vs " + toDegs(read) + ")");
}
public void checkReferenceAdjustmentYaw(Quaternion trackerQuat, int refPitch, int refYaw, int refRoll) {
Quaternion referenceQuat = q(refPitch, refYaw, refRoll);
ComputedTracker tracker = new ComputedTracker("test");
tracker.rotation.set(trackerQuat);
ReferenceAdjustedTracker<ComputedTracker> adj = new ReferenceAdjustedTracker<>(tracker);
adj.resetYaw(referenceQuat);
Quaternion read = new Quaternion();
assertTrue(adj.getRotation(read), "Adjusted tracker didn't return rotation");
assertEquals(new QuatEqualYawWithEpsilon(referenceQuat), new QuatEqualYawWithEpsilon(read),
"Adjusted quat is not equal to reference quat (" + toDegs(referenceQuat) + " vs " + toDegs(read) + ")");
}
private void testAdjustedTrackerRotation(Quaternion trackerQuat, int refPitch, int refYaw, int refRoll) {
Quaternion referenceQuat = q(refPitch, refYaw, refRoll);
ComputedTracker tracker = new ComputedTracker("test");
tracker.rotation.set(trackerQuat);
ReferenceAdjustedTracker<ComputedTracker> adj = new ReferenceAdjustedTracker<>(tracker);
adj.resetFull(referenceQuat);
// Use only yaw HMD rotation
Quaternion targetTrackerRotation = new Quaternion(referenceQuat);
float[] angles = new float[3];
targetTrackerRotation.toAngles(angles);
targetTrackerRotation.fromAngles(0, angles[1], 0);
Quaternion read = new Quaternion();
Quaternion rotation = new Quaternion();
Quaternion rotationCompare = new Quaternion();
Quaternion diff = new Quaternion();
float[] anglesAdj = new float[3];
float[] anglesDiff = new float[3];
TransformNode trackerNode = new TransformNode("Tracker", true);
TransformNode rotationNode = new TransformNode("Rot", true);
rotationNode.attachChild(trackerNode);
trackerNode.localTransform.setRotation(tracker.rotation);
for(int yaw = 0; yaw <= 360; yaw += 30) {
for(int pitch = -90; pitch <= 90; pitch += 15) {
for(int roll = -90; roll <= 90; roll += 15) {
rotation.fromAngles(pitch * FastMath.DEG_TO_RAD, yaw * FastMath.DEG_TO_RAD, roll * FastMath.DEG_TO_RAD);
rotationCompare.fromAngles(pitch * FastMath.DEG_TO_RAD, (yaw + refYaw) * FastMath.DEG_TO_RAD, roll * FastMath.DEG_TO_RAD);
rotationNode.localTransform.setRotation(rotation);
rotationNode.update();
tracker.rotation.set(trackerNode.worldTransform.getRotation());
tracker.rotation.toAngles(angles);
adj.getRotation(read);
read.toAngles(anglesAdj);
diff.set(read).inverseLocal().multLocal(rotationCompare);
diff.toAngles(anglesDiff);
if(!PRINT_TEST_RESULTS) {
assertTrue(FloatMath.equalsToZero(anglesDiff[0]) && FloatMath.equalsToZero(anglesDiff[1]) && FloatMath.equalsToZero(anglesDiff[2]),
name(yaw, pitch, roll, angles, anglesAdj, anglesDiff));
} else {
if(FloatMath.equalsToZero(anglesDiff[0]) && FloatMath.equalsToZero(anglesDiff[1]) && FloatMath.equalsToZero(anglesDiff[2]))
successes++;
else
errors++;
System.out.println(name(yaw, pitch, roll, angles, anglesAdj, anglesDiff));
}
}
}
}
if(PRINT_TEST_RESULTS)
System.out.println("Errors: " + errors + ", successes: " + successes);
}
private static String name(int yaw, int pitch, int roll, float[] angles, float[] anglesAdj, float[] anglesDiff) {
return "Rot: " + yaw + "/" + pitch + "/" + roll + ". "
+ "Angles: " + StringUtils.prettyNumber(angles[0] * FastMath.RAD_TO_DEG, 1) + "/" + StringUtils.prettyNumber(anglesAdj[0] * FastMath.RAD_TO_DEG, 1) + ", "
+ StringUtils.prettyNumber(angles[1] * FastMath.RAD_TO_DEG, 1) + "/" + StringUtils.prettyNumber(anglesAdj[1] * FastMath.RAD_TO_DEG, 1) + ", "
+ StringUtils.prettyNumber(angles[2] * FastMath.RAD_TO_DEG, 1) + "/" + StringUtils.prettyNumber(anglesAdj[2] * FastMath.RAD_TO_DEG, 1) + ". Diff: "
+ StringUtils.prettyNumber(anglesDiff[0] * FastMath.RAD_TO_DEG, 1) + ", "
+ StringUtils.prettyNumber(anglesDiff[1] * FastMath.RAD_TO_DEG, 1) + ", "
+ StringUtils.prettyNumber(anglesDiff[2] * FastMath.RAD_TO_DEG, 1);
}
public static Quaternion q(float pitch, float yaw, float roll) {
return new Quaternion().fromAngles(pitch * FastMath.DEG_TO_RAD, yaw * FastMath.DEG_TO_RAD, roll * FastMath.DEG_TO_RAD);
}
public static String toDegs(Quaternion q) {
float[] degs = new float[3];
q.toAngles(degs);
return StringUtils.prettyNumber(degs[0] * FastMath.RAD_TO_DEG, 0) + "," + StringUtils.prettyNumber(degs[1] * FastMath.RAD_TO_DEG, 0) + "," + StringUtils.prettyNumber(degs[2] * FastMath.RAD_TO_DEG, 0);
}
private static class QuatEqualYawWithEpsilon {
private final Quaternion q;
public QuatEqualYawWithEpsilon(Quaternion q) {
this.q = q;
}
@Override
public String toString() {
return String.valueOf(q);
}
@Override
public int hashCode() {
return q.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Quaternion)
obj = new QuatEqualYawWithEpsilon((Quaternion) obj);
if(!(obj instanceof QuatEqualYawWithEpsilon))
return false;
Quaternion q2 = ((QuatEqualYawWithEpsilon) obj).q;
float[] degs1 = new float[3];
q.toAngles(degs1);
float[] degs2 = new float[3];
q2.toAngles(degs2);
if(degs1[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs1[1] += FastMath.TWO_PI;
if(degs2[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs2[1] += FastMath.TWO_PI;
return FloatMath.equalsWithEpsilon(degs1[1], degs2[1]);
}
}
public static class QuatEqualFullWithEpsilon {
private final Quaternion q;
public QuatEqualFullWithEpsilon(Quaternion q) {
this.q = q;
}
@Override
public String toString() {
return String.valueOf(q);
}
@Override
public int hashCode() {
return q.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Quaternion)
obj = new QuatEqualFullWithEpsilon((Quaternion) obj);
if(!(obj instanceof QuatEqualFullWithEpsilon))
return false;
Quaternion q2 = ((QuatEqualFullWithEpsilon) obj).q;
float[] degs1 = new float[3];
q.toAngles(degs1);
float[] degs2 = new float[3];
q2.toAngles(degs2);
if(degs1[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs1[1] += FastMath.TWO_PI;
if(degs2[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs2[1] += FastMath.TWO_PI;
return FloatMath.equalsWithEpsilon(degs1[0], degs2[0])
&& FloatMath.equalsWithEpsilon(degs1[1], degs2[1])
&& FloatMath.equalsWithEpsilon(degs1[2], degs2[2]);
}
}
public static class AnglesSet {
public final int pitch;
public final int yaw;
public final int roll;
public AnglesSet(int pitch, int yaw, int roll) {
this.pitch = pitch;
this.yaw = yaw;
this.roll = roll;
}
}
}

View File

@@ -1,196 +0,0 @@
package io.eiren.unity;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import io.eiren.math.FloatMath;
import io.eiren.util.StringUtils;
import io.eiren.vr.trackers.ComputedTracker;
import io.eiren.vr.trackers.ReferenceAdjustedTracker;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Tests {@link ReferenceAdjustedTracker#resetFull(Quaternion)}
*/
public class ReferenceAdjustmentsFullTests {
@Test
public void check0to0() {
yawTest(0, 0);
}
@Test
public void check45to0() {
yawTest(0, 45);
}
@Test
public void check90to0() {
yawTest(0, 90);
}
@Test
public void check180to0() {
yawTest(0, 180);
}
@Test
public void check270to0() {
yawTest(0, 270);
}
@Test
public void check0to45() {
yawTest(45, 0);
}
@Test
public void check45to45() {
yawTest(45, 45);
}
@Test
public void check90to45() {
yawTest(45, 90);
}
@Test
public void check180to45() {
yawTest(45, 180);
}
@Test
public void check270to45() {
yawTest(45, 270);
}
@Test
public void check0to90() {
yawTest(90, 0);
}
@Test
public void check45to90() {
yawTest(90, 45);
}
@Test
public void check90to90() {
yawTest(90, 90);
}
@Test
public void check180to90() {
yawTest(90, 180);
}
@Test
public void check270to90() {
yawTest(90, 270);
}
@Test
public void check0to180() {
yawTest(180, 0);
}
@Test
public void check45to180() {
yawTest(180, 45);
}
@Test
public void check90to180() {
yawTest(180, 90);
}
@Test
public void check180to180() {
yawTest(180, 180);
}
@Test
public void check270to180() {
yawTest(180, 270);
}
private void yawTest(float refYaw, float trackerTaw) {
checkReferenceAdjustmentFull(q(0, refYaw, 0), q(0, trackerTaw, 0));
checkReferenceAdjustmentFull(q(0, refYaw, 15), q(0, trackerTaw, 0));
checkReferenceAdjustmentFull(q(15, refYaw, 0), q(0, trackerTaw, 0));
checkReferenceAdjustmentFull(q(15, refYaw, 15), q(0, trackerTaw, 0));
checkReferenceAdjustmentFull(q(0, refYaw, 0), q(15, trackerTaw, 0));
checkReferenceAdjustmentFull(q(0, refYaw, 15), q(0, trackerTaw, 15));
checkReferenceAdjustmentFull(q(15, refYaw, 0), q(15, trackerTaw, 15));
checkReferenceAdjustmentFull(q(15, refYaw, 15), q(0, trackerTaw, 15));
}
public static void checkReferenceAdjustmentFull(Quaternion referenceQuat, Quaternion trackerQuat) {
ComputedTracker tracker = new ComputedTracker("test");
tracker.rotation.set(trackerQuat);
ReferenceAdjustedTracker adj = new ReferenceAdjustedTracker(tracker);
adj.resetFull(referenceQuat);
Quaternion read = new Quaternion();
assertTrue("Adjusted tracker didn't return rotation", adj.getRotation(read));
// Use only yaw HMD rotation
Quaternion targetTrackerRotation = new Quaternion(referenceQuat);
float[] angles = new float[3];
targetTrackerRotation.toAngles(angles);
targetTrackerRotation.fromAngles(0, angles[1], 0);
assertEquals("Adjusted quat is not equal to reference quat (" + toDegs(targetTrackerRotation) + " vs " + toDegs(read) + ")", new QuatEqualFullWithEpsilon(targetTrackerRotation), new QuatEqualFullWithEpsilon(read));
}
public static String toDegs(Quaternion q) {
float[] degs = new float[3];
q.toAngles(degs);
return StringUtils.prettyNumber(degs[0] * FastMath.RAD_TO_DEG, 0) + "," + StringUtils.prettyNumber(degs[1] * FastMath.RAD_TO_DEG, 0) + "," + StringUtils.prettyNumber(degs[2] * FastMath.RAD_TO_DEG, 0);
}
public static Quaternion q(float pitch, float yaw, float roll) {
return new Quaternion().fromAngles(pitch * FastMath.DEG_TO_RAD, yaw * FastMath.DEG_TO_RAD, roll * FastMath.DEG_TO_RAD);
}
public static class QuatEqualFullWithEpsilon {
private final Quaternion q;
public QuatEqualFullWithEpsilon(Quaternion q) {
this.q = q;
}
@Override
public String toString() {
return String.valueOf(q);
}
@Override
public int hashCode() {
return q.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Quaternion)
obj = new QuatEqualFullWithEpsilon((Quaternion) obj);
if(!(obj instanceof QuatEqualFullWithEpsilon))
return false;
Quaternion q2 = ((QuatEqualFullWithEpsilon) obj).q;
float[] degs1 = new float[3];
q.toAngles(degs1);
float[] degs2 = new float[3];
q2.toAngles(degs2);
if(degs1[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs1[1] += FastMath.TWO_PI;
if(degs2[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs2[1] += FastMath.TWO_PI;
return FloatMath.equalsWithEpsilon(degs1[0], degs2[0])
&& FloatMath.equalsWithEpsilon(degs1[1], degs2[1])
&& FloatMath.equalsWithEpsilon(degs1[2], degs2[2]);
}
}
}

View File

@@ -1,187 +0,0 @@
package io.eiren.unity;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import io.eiren.math.FloatMath;
import io.eiren.util.StringUtils;
import io.eiren.vr.trackers.ComputedTracker;
import io.eiren.vr.trackers.ReferenceAdjustedTracker;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Tests {@link ReferenceAdjustedTracker#resetYaw(Quaternion)}
*/
public class ReferenceAdjustmentsYawTests {
@Test
public void check0to0() {
yawTest(0, 0);
}
@Test
public void check45to0() {
yawTest(0, 45);
}
@Test
public void check90to0() {
yawTest(0, 90);
}
@Test
public void check180to0() {
yawTest(0, 180);
}
@Test
public void check270to0() {
yawTest(0, 270);
}
@Test
public void check0to45() {
yawTest(45, 0);
}
@Test
public void check45to45() {
yawTest(45, 45);
}
@Test
public void check90to45() {
yawTest(45, 90);
}
@Test
public void check180to45() {
yawTest(45, 180);
}
@Test
public void check270to45() {
yawTest(45, 270);
}
@Test
public void check0to90() {
yawTest(90, 0);
}
@Test
public void check45to90() {
yawTest(90, 45);
}
@Test
public void check90to90() {
yawTest(90, 90);
}
@Test
public void check180to90() {
yawTest(90, 180);
}
@Test
public void check270to90() {
yawTest(90, 270);
}
@Test
public void check0to180() {
yawTest(180, 0);
}
@Test
public void check45to180() {
yawTest(180, 45);
}
@Test
public void check90to180() {
yawTest(180, 90);
}
@Test
public void check180to180() {
yawTest(180, 180);
}
@Test
public void check270to180() {
yawTest(180, 270);
}
private void yawTest(float refYaw, float trackerTaw) {
checkReferenceAdjustmentYaw(q(0, refYaw, 0), q(0, trackerTaw, 0));
checkReferenceAdjustmentYaw(q(0, refYaw, 15), q(0, trackerTaw, 0));
checkReferenceAdjustmentYaw(q(15, refYaw, 0), q(0, trackerTaw, 0));
checkReferenceAdjustmentYaw(q(15, refYaw, 15), q(0, trackerTaw, 0));
checkReferenceAdjustmentYaw(q(0, refYaw, 0), q(15, trackerTaw, 0));
checkReferenceAdjustmentYaw(q(0, refYaw, 15), q(0, trackerTaw, 15));
checkReferenceAdjustmentYaw(q(15, refYaw, 0), q(15, trackerTaw, 15));
checkReferenceAdjustmentYaw(q(15, refYaw, 15), q(0, trackerTaw, 15));
}
public static void checkReferenceAdjustmentYaw(Quaternion referenceQuat, Quaternion trackerQuat) {
ComputedTracker tracker = new ComputedTracker("test");
tracker.rotation.set(trackerQuat);
ReferenceAdjustedTracker adj = new ReferenceAdjustedTracker(tracker);
adj.resetYaw(referenceQuat);
Quaternion read = new Quaternion();
assertTrue("Adjusted tracker didn't return rotation", adj.getRotation(read));
assertEquals("Adjusted quat is not equal to reference quat (" + toDegs(referenceQuat) + " vs " + toDegs(read) + ")", new QuatEqualYawWithEpsilon(referenceQuat), new QuatEqualYawWithEpsilon(read));
}
public static String toDegs(Quaternion q) {
float[] degs = new float[3];
q.toAngles(degs);
return StringUtils.prettyNumber(degs[0] * FastMath.RAD_TO_DEG, 0) + "," + StringUtils.prettyNumber(degs[1] * FastMath.RAD_TO_DEG, 0) + "," + StringUtils.prettyNumber(degs[2] * FastMath.RAD_TO_DEG, 0);
}
public static Quaternion q(float pitch, float yaw, float roll) {
return new Quaternion().fromAngles(pitch * FastMath.DEG_TO_RAD, yaw * FastMath.DEG_TO_RAD, roll * FastMath.DEG_TO_RAD);
}
private static class QuatEqualYawWithEpsilon {
private final Quaternion q;
public QuatEqualYawWithEpsilon(Quaternion q) {
this.q = q;
}
@Override
public String toString() {
return String.valueOf(q);
}
@Override
public int hashCode() {
return q.hashCode();
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Quaternion)
obj = new QuatEqualYawWithEpsilon((Quaternion) obj);
if(!(obj instanceof QuatEqualYawWithEpsilon))
return false;
Quaternion q2 = ((QuatEqualYawWithEpsilon) obj).q;
float[] degs1 = new float[3];
q.toAngles(degs1);
float[] degs2 = new float[3];
q2.toAngles(degs2);
if(degs1[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs1[1] += FastMath.TWO_PI;
if(degs2[1] < -FloatMath.ANGLE_EPSILON_RAD)
degs2[1] += FastMath.TWO_PI;
return FloatMath.equalsWithEpsilon(degs1[1], degs2[1]);
}
}
}