Compare commits

...

22 Commits

Author SHA1 Message Date
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
Eiren Rain
43bc140d42 Fix hips switching place when changing hips width 2021-07-05 17:00:33 +03:00
Eiren Rain
143cf49ce7 Added a button to switch between 3 and 1 vive trackers mode 2021-07-05 02:32:30 +03:00
Eiren Rain
a7aec873e6 Added scroll bar to the main window 2021-07-05 02:32:10 +03:00
19 changed files with 829 additions and 325 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,4 @@ 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.

View File

@@ -19,6 +19,8 @@ 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'
@@ -33,3 +35,15 @@ dependencies {
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
}
subprojects.each { subproject -> evaluationDependsOn(subproject.path) }
task serverJar (type: Jar, dependsOn: subprojects.tasks['build']) {
manifest {
attributes 'Main-Class': 'io.eiren.vr.Main'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar
}

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

@@ -11,7 +11,6 @@ import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.border.BevelBorder;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
@@ -20,7 +19,6 @@ import com.jme3.math.Vector3f;
import io.eiren.util.StringUtils;
import io.eiren.util.ann.AWTThread;
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.TrackerBodyPosition;
@@ -103,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) {
@@ -177,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 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)
@@ -204,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).adjustmentAttachment.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).adjustmentYaw.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);
raw.setText(StringUtils.prettyNumber(q.getX(), 4)
+ " " + StringUtils.prettyNumber(q.getY(), 4)
+ " " + StringUtils.prettyNumber(q.getZ(), 4)
+ " " + StringUtils.prettyNumber(q.getW(), 4));
}
}

View File

@@ -4,12 +4,16 @@ 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.MouseEvent;
import java.util.TimerTask;
import static javax.swing.BoxLayout.PAGE_AXIS;
import static javax.swing.BoxLayout.LINE_AXIS;
@@ -19,24 +23,40 @@ 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 + ")");
//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));
this.trackersList = new TrackersList(server, this);
this.skeletonList = new SkeletonList(server, this);
add(scroll = new JScrollPane(pane = new EJBox(PAGE_AXIS), ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED));
build();
}
public float getZoom() {
return this.zoom;
}
public void refresh() {
// Pack and display
pack();
@@ -44,7 +64,6 @@ public class VRServerGUI extends JFrame {
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
toFront();
repaint();
}
});
@@ -52,12 +71,14 @@ public class VRServerGUI extends JFrame {
@AWTThread
private void build() {
Container pane = getContentPane();
pane.removeAll();
NamedPipeVRBridge npvb = server.getVRBridge(NamedPipeVRBridge.class);
pane.add(new EJBox(LINE_AXIS) {{
setBorder(new EmptyBorder(i(5)));
add(Box.createHorizontalGlue());
add(resetButton = new JButton("RESET") {{
addMouseListener(new MouseInputAdapter() {
@Override
@@ -66,6 +87,29 @@ public class VRServerGUI extends JFrame {
}
});
}});
add(Box.createHorizontalGlue());
if(npvb != null) {
add(new JButton(npvb.isOneTrackerMode() ? "Trackers: 1" : "Trackers: 3") {{
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
npvb.setSpawnOneTracker(!npvb.isOneTrackerMode());
setText(npvb.isOneTrackerMode() ? "Trackers: 1" : "Trackers: 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));
}});
pane.add(new EJBox(LINE_AXIS) {{
@@ -80,10 +124,10 @@ public class VRServerGUI extends JFrame {
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());
}});
}});
@@ -95,14 +139,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);
}
}
@@ -110,37 +185,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

@@ -8,7 +8,10 @@ import io.eiren.util.logging.LogManager;
public class Main {
@SuppressWarnings("unused")
public static String VERSION = "0.0.7 DEV 1";
public static VRServer vrServer;
public static void main(String[] args) {
System.setProperty("awt.useSystemAAFontSettings", "on");
System.setProperty("swing.aatext", "true");
@@ -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

@@ -46,6 +46,7 @@ public class VRServer extends Thread {
public VRServer() {
super("VRServer");
loadConfig();
hmdTracker = new HMDTracker("HMD");
hmdTracker.position.set(0, 1.8f, 0); // Set starting position for easier debugging
// TODO Multiple processors
@@ -53,7 +54,7 @@ public class VRServer extends Thread {
List<? extends Tracker> shareTrackers = humanPoseProcessor.getComputedTrackers();
// Create named pipe bridge for SteamVR driver
NamedPipeVRBridge driverBridge = new NamedPipeVRBridge(hmdTracker, shareTrackers);
NamedPipeVRBridge driverBridge = new NamedPipeVRBridge(hmdTracker, shareTrackers, this);
tasks.add(() -> driverBridge.start());
bridges.add(driverBridge);
@@ -71,6 +72,16 @@ public class VRServer extends Thread {
for(int i = 0; i < shareTrackers.size(); ++i)
registerTracker(shareTrackers.get(i));
}
@ThreadSafe
public <E extends VRBridge> E getVRBridge(Class<E> cls) {
for(int i = 0; i < bridges.size(); ++i) {
VRBridge b = bridges.get(i);
if(cls.isInstance(b))
return cls.cast(b);
}
return null;
}
@ThreadSafe
public TrackerConfig getTrackerConfig(Tracker tracker) {
@@ -169,7 +180,6 @@ public class VRServer extends Thread {
@Override
@VRServerThread
public void run() {
loadConfig();
trackersServer.start();
while(true) {
//final long start = System.currentTimeMillis();

View File

@@ -14,6 +14,7 @@ import com.sun.jna.ptr.IntByReference;
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.Tracker;
@@ -32,6 +33,7 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
private final Quaternion qBuffer = new Quaternion();
private final Quaternion qBuffer2 = new Quaternion();
private final VRServer server;
private Pipe hmdPipe;
private final HMDTracker hmd;
private final List<Pipe> trackerPipes;
@@ -41,8 +43,11 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
private final HMDTracker internalHMDTracker = new HMDTracker("itnernal://HMD");
private final AtomicBoolean newHMDData = new AtomicBoolean(false);
public NamedPipeVRBridge(HMDTracker hmd, List<? extends Tracker> shareTrackers) {
private boolean spawnOneTracker = false;
public NamedPipeVRBridge(HMDTracker hmd, List<? extends Tracker> shareTrackers, VRServer server) {
super("Named Pipe VR Bridge");
this.server = server;
this.hmd = hmd;
this.shareTrackers = new FastList<>(shareTrackers);
this.trackerPipes = new FastList<>(shareTrackers.size());
@@ -53,6 +58,26 @@ 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
@@ -108,6 +133,8 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
if(tryOpeningPipe(trackerPipe))
initTrackerPipe(trackerPipe, i);
}
if(spawnOneTracker)
break;
}
}
@@ -167,7 +194,7 @@ public class NamedPipeVRBridge extends Thread implements VRBridge {
}
private void initTrackerPipe(Pipe pipe, int trackerId) {
String trackerHello = this.shareTrackers.size() + " 0";
String trackerHello = (spawnOneTracker ? "1" : this.shareTrackers.size()) + " 0";
System.arraycopy(trackerHello.getBytes(ASCII), 0, buffer, 0, trackerHello.length());
buffer[trackerHello.length()] = '\0';
IntByReference lpNumberOfBytesWritten = new IntByReference(0);
@@ -225,6 +252,8 @@ 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

@@ -38,6 +38,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();
@@ -39,15 +44,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;
@@ -77,8 +83,9 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
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 +94,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
@@ -116,20 +155,28 @@ public class HumanSekeletonWithLegs extends HumanSkeleonWithWaist {
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);
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 +188,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 +205,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);

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;
@@ -48,11 +51,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();
@@ -96,6 +99,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 +155,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;

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,8 @@ 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];
protected float confidenceMultiplier = 1.0f;
public ReferenceAdjustedTracker(E tracker) {
@@ -49,19 +44,14 @@ public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
*/
@Override
public void resetFull(Quaternion reference) {
resetYaw(reference);
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);
//float[] angles = new float[3];
//sensorRotation.toAngles(angles);
//sensorRotation.fromAngles(angles[0], 0, angles[2]);
adjustmentAttachment.set(sensorRotation).inverseLocal();
adjustmentAttachment.set(sensorRotation).inverseLocal().multLocal(targetTrackerRotation);
resetYaw(reference);
}
/**
@@ -81,6 +71,8 @@ public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
Quaternion sensorRotation = new Quaternion();
tracker.getRotation(sensorRotation);
adjustmentAttachment.mult(sensorRotation, sensorRotation);
//sensorRotation.multLocal(adjustmentAttachment);
sensorRotation.toAngles(angles);
sensorRotation.fromAngles(0, angles[1], 0);
@@ -88,26 +80,17 @@ public class ReferenceAdjustedTracker<E extends Tracker> implements Tracker {
adjustmentYaw.set(sensorRotation).inverseLocal().multLocal(targetTrackerRotation);
confidenceMultiplier = 1.0f / tracker.getConfidenceLevel();
lastAngles[0] = 1000;
}
protected void adjustInternal(Quaternion store) {
store.multLocal(adjustmentAttachment);
//store.multLocal(adjustmentAttachment);
adjustmentAttachment.mult(store, store);
adjustmentYaw.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,257 @@
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.Assert.*;
import java.util.HashSet;
import java.util.Set;
import org.junit.Test;
/**
* Tests {@link ReferenceAdjustedTracker#resetFull(Quaternion)}
*/
public class ReferenceAdjustmentsFullTests {
private Set<String> testedTrackerNames = new HashSet<>();
@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(int refYaw, int trackerYaw) {
checkReferenceAdjustmentFull(q(0, refYaw, 0), q(0, trackerYaw, 0), refYaw, "Tracker(0," + trackerYaw + ",0/" + refYaw + ")");
checkReferenceAdjustmentFull(q(0, refYaw, 15), q(0, trackerYaw, 0), refYaw, "Tracker(0," + trackerYaw + ",0/" + refYaw + ")");
checkReferenceAdjustmentFull(q(15, refYaw, 0), q(0, trackerYaw, 0), refYaw, "Tracker(0," + trackerYaw + ",0/" + refYaw + ")");
checkReferenceAdjustmentFull(q(15, refYaw, 15), q(0, trackerYaw, 0), refYaw, "Tracker(0," + trackerYaw + ",0/" + refYaw + ")");
checkReferenceAdjustmentFull(q(0, refYaw, 0), q(15, trackerYaw, 0), refYaw, "Tracker(15," + trackerYaw + ",0/" + refYaw + ")");
checkReferenceAdjustmentFull(q(0, refYaw, 15), q(0, trackerYaw, 15), refYaw, "Tracker(0," + trackerYaw + ",0/" + refYaw + ")");
checkReferenceAdjustmentFull(q(15, refYaw, 0), q(15, trackerYaw, 15), refYaw, "Tracker(15," + trackerYaw + ",0/" + refYaw + ")");
checkReferenceAdjustmentFull(q(15, refYaw, 15), q(0, trackerYaw, 15), refYaw, "Tracker(0," + trackerYaw + ",0/" + refYaw + ")");
}
public void checkReferenceAdjustmentFull(Quaternion referenceQuat, Quaternion trackerQuat, int refYaw, String name) {
ComputedTracker tracker = new ComputedTracker("test");
tracker.rotation.set(trackerQuat);
ReferenceAdjustedTracker<ComputedTracker> 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));
if(refYaw == 0)
testAdjustedTracker(tracker, adj, name, refYaw);
}
//private static int errors = 0;
//private static int successes = 0;
private void testAdjustedTracker(ComputedTracker tracker, ReferenceAdjustedTracker<ComputedTracker> adj, String name, int refYaw) {
if(!testedTrackerNames.add(name))
return;
final Quaternion trackerBase = new Quaternion();
trackerBase.set(tracker.rotation);
Quaternion rotation = new Quaternion();
Quaternion read = new Quaternion();
Quaternion diff = new Quaternion();
float[] angles = new float[3];
float[] anglesAdj = new float[3];
float[] anglesDiff = new float[3];
TransformNode trackerNode = new TransformNode(name, true);
TransformNode rotationNode = new TransformNode("Rot", true);
trackerNode.attachChild(rotationNode);
trackerNode.localTransform.setRotation(trackerBase);
for(int yaw = 0; yaw <= 360; yaw += 90) {
for(int pitch = -90; pitch <= 90; pitch += 30) {
for(int roll = -90; roll <= 90; roll += 30) {
rotation.fromAngles(pitch, yaw, roll);
rotationNode.localTransform.setRotation(rotation);
trackerNode.update();
rotationNode.update();
tracker.rotation.set(rotationNode.worldTransform.getRotation());
tracker.rotation.toAngles(angles);
adj.getRotation(read);
read.toAngles(anglesAdj);
diff.set(read).inverseLocal().multLocal(rotation);
diff.toAngles(anglesDiff);
assertTrue(name + ". Rot: " + yaw + "/" + pitch + ". "
+ "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),
FloatMath.equalsToZero(anglesDiff[0]) && FloatMath.equalsToZero(anglesDiff[1]) && FloatMath.equalsToZero(anglesDiff[2]));
}
}
}
//System.out.println("Errors: " + errors + ", successes: " + successes);
}
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,4 +1,4 @@
package io.eiren.unity;
package io.eiren.unit;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;

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