diff --git a/src/main/java/dev/slimevr/vr/autobone/AutoBone.java b/src/main/java/dev/slimevr/autobone/AutoBone.java similarity index 84% rename from src/main/java/dev/slimevr/vr/autobone/AutoBone.java rename to src/main/java/dev/slimevr/autobone/AutoBone.java index ccb6649992..179ccbbdc9 100644 --- a/src/main/java/dev/slimevr/vr/autobone/AutoBone.java +++ b/src/main/java/dev/slimevr/autobone/AutoBone.java @@ -1,20 +1,19 @@ -package dev.slimevr.vr.autobone; +package dev.slimevr.autobone; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.function.Consumer; import com.jme3.math.Vector3f; -import dev.slimevr.vr.poserecorder.PoseFrame; -import dev.slimevr.vr.poserecorder.TrackerFrameData; +import dev.slimevr.poserecorder.PoseFrame; +import dev.slimevr.poserecorder.TrackerFrame; +import dev.slimevr.poserecorder.TrackerFrameData; import io.eiren.util.ann.ThreadSafe; import io.eiren.util.logging.LogManager; import io.eiren.util.collections.FastList; import io.eiren.vr.VRServer; -import dev.slimevr.vr.poserecorder.TrackerFrame; import io.eiren.vr.processor.HumanSkeleton; import io.eiren.vr.processor.HumanSkeletonWithLegs; import io.eiren.vr.processor.HumanSkeletonWithWaist; @@ -22,84 +21,79 @@ import io.eiren.vr.trackers.TrackerUtils; public class AutoBone { - + public class Epoch { - + public final int epoch; public final float epochError; - + public Epoch(int epoch, float epochError) { this.epoch = epoch; this.epochError = epochError; } - + @Override public String toString() { return "Epoch: " + epoch + ", Epoch Error: " + epochError; } } - + public int cursorIncrement = 1; - + public int minDataDistance = 2; public int maxDataDistance = 32; - + public int numEpochs = 5; - + public float initialAdjustRate = 2.5f; public float adjustRateDecay = 1.01f; - + public float slideErrorFactor = 1.0f; public float offsetErrorFactor = 0.0f; public float proportionErrorFactor = 0.2f; public float heightErrorFactor = 0.1f; public float positionErrorFactor = 0.0f; public float positionOffsetErrorFactor = 0.0f; - + // Human average is probably 1.1235 (SD 0.07) public float legBodyRatio = 1.1235f; // SD of 0.07, capture 68% within range public float legBodyRatioRange = 0.07f; - + // Assume these to be approximately half public float kneeLegRatio = 0.5f; public float chestWaistRatio = 0.5f; - + protected final VRServer server; - + protected HumanSkeletonWithLegs skeleton = null; - + // This is filled by reloadConfigValues() public final HashMap configs = new HashMap(); public final HashMap staticConfigs = new HashMap(); - - public final FastList heightConfigs = new FastList(new String[] { - "Neck", - "Waist", - "Legs length" + + public final FastList heightConfigs = new FastList(new String[]{"Neck", "Waist", "Legs length" }); - + public AutoBone(VRServer server) { this.server = server; - + reloadConfigValues(); - + server.addSkeletonUpdatedCallback(this::skeletonUpdated); } - + public void reloadConfigValues() { reloadConfigValues(null); } - + public void reloadConfigValues(TrackerFrame[] frame) { // Load waist configs staticConfigs.put("Head", server.config.getFloat("body.headShift", HumanSkeletonWithWaist.HEAD_SHIFT_DEFAULT)); staticConfigs.put("Neck", server.config.getFloat("body.neckLength", HumanSkeletonWithWaist.NECK_LENGTH_DEFAULT)); configs.put("Waist", server.config.getFloat("body.waistDistance", 0.85f)); - - if (server.config.getBoolean("autobone.forceChestTracker", false) || - (frame != null && TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.CHEST) != null) || - TrackerUtils.findTrackerForBodyPosition(server.getAllTrackers(), TrackerBodyPosition.CHEST) != null) { + + if(server.config.getBoolean("autobone.forceChestTracker", false) || (frame != null && TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.CHEST) != null) || TrackerUtils.findTrackerForBodyPosition(server.getAllTrackers(), TrackerBodyPosition.CHEST) != null) { // If force enabled or has a chest tracker configs.put("Chest", server.config.getFloat("body.chestDistance", 0.42f)); } else { @@ -107,49 +101,49 @@ public void reloadConfigValues(TrackerFrame[] frame) { configs.remove("Chest"); staticConfigs.put("Chest", server.config.getFloat("body.chestDistance", 0.42f)); } - + // Load leg configs staticConfigs.put("Hips width", server.config.getFloat("body.hipsWidth", HumanSkeletonWithLegs.HIPS_WIDTH_DEFAULT)); configs.put("Legs length", server.config.getFloat("body.legsLength", 0.84f)); configs.put("Knee height", server.config.getFloat("body.kneeHeight", 0.42f)); } - + @ThreadSafe public void skeletonUpdated(HumanSkeleton newSkeleton) { - if (newSkeleton instanceof HumanSkeletonWithLegs) { - skeleton = (HumanSkeletonWithLegs)newSkeleton; + if(newSkeleton instanceof HumanSkeletonWithLegs) { + skeleton = (HumanSkeletonWithLegs) newSkeleton; applyConfigToSkeleton(newSkeleton); LogManager.log.info("[AutoBone] Received updated skeleton"); } } - + public void applyConfig() { - if (!applyConfigToSkeleton(skeleton)) { + if(!applyConfigToSkeleton(skeleton)) { // Unable to apply to skeleton, save directly saveConfigs(); } } - + public boolean applyConfigToSkeleton(HumanSkeleton skeleton) { - if (skeleton == null) { + if(skeleton == null) { return false; } - + configs.forEach(skeleton::setSkeletonConfig); - + server.saveConfig(); - + LogManager.log.info("[AutoBone] Configured skeleton bone lengths"); return true; } - + private void setConfig(String name, String path) { Float value = configs.get(name); - if (value != null) { + if(value != null) { server.config.setProperty(path, value); } } - + // This doesn't require a skeleton, therefore can be used if skeleton is null public void saveConfigs() { setConfig("Head", "body.headShift"); @@ -159,242 +153,242 @@ public void saveConfigs() { setConfig("Hips width", "body.hipsWidth"); setConfig("Legs length", "body.legsLength"); setConfig("Knee height", "body.kneeHeight"); - + server.saveConfig(); } - + public Float getConfig(String config) { Float configVal = configs.get(config); return configVal != null ? configVal : staticConfigs.get(config); } - + public Float getConfig(String config, Map configs, Map configsAlt) { - if (configs == null) { + if(configs == null) { throw new NullPointerException("Argument \"configs\" must not be null"); } - + Float configVal = configs.get(config); return configVal != null || configsAlt == null ? configVal : configsAlt.get(config); } - + public float getHeight(Map configs) { return getHeight(configs, null); } - + public float getHeight(Map configs, Map configsAlt) { float height = 0f; - - for (String heightConfig : heightConfigs) { + + for(String heightConfig : heightConfigs) { Float length = getConfig(heightConfig, configs, configsAlt); - if (length != null) { + if(length != null) { height += length; } } - + return height; } - + public float getLengthSum(Map configs) { float length = 0f; - - for (float boneLength : configs.values()) { + + for(float boneLength : configs.values()) { length += boneLength; } - + return length; } - + public float getMaxHmdHeight(PoseFrame frames) { float maxHeight = 0f; - for (TrackerFrame[] frame : frames) { + for(TrackerFrame[] frame : frames) { TrackerFrame hmd = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.HMD); - if (hmd != null && hmd.hasData(TrackerFrameData.POSITION) && hmd.position.y > maxHeight) { + if(hmd != null && hmd.hasData(TrackerFrameData.POSITION) && hmd.position.y > maxHeight) { maxHeight = hmd.position.y; } } return maxHeight; } - + public void processFrames(PoseFrame frames) { processFrames(frames, -1f); } - + public void processFrames(PoseFrame frames, Consumer epochCallback) { processFrames(frames, -1f, epochCallback); } - + public void processFrames(PoseFrame frames, float targetHeight) { processFrames(frames, true, targetHeight); } - + public void processFrames(PoseFrame frames, float targetHeight, Consumer epochCallback) { processFrames(frames, true, targetHeight, epochCallback); } - + public float processFrames(PoseFrame frames, boolean calcInitError, float targetHeight) { return processFrames(frames, calcInitError, targetHeight, null); } - + public float processFrames(PoseFrame frames, boolean calcInitError, float targetHeight, Consumer epochCallback) { final int frameCount = frames.getMaxFrameCount(); - + final SimpleSkeleton skeleton1 = new SimpleSkeleton(configs, staticConfigs); final TrackerFrame[] trackerBuffer1 = new TrackerFrame[frames.getTrackerCount()]; - + frames.getFrames(0, trackerBuffer1); reloadConfigValues(trackerBuffer1); // Reload configs and detect chest tracker from the first frame - + final SimpleSkeleton skeleton2 = new SimpleSkeleton(configs, staticConfigs); final TrackerFrame[] trackerBuffer2 = new TrackerFrame[frames.getTrackerCount()]; - + // If target height isn't specified, auto-detect - if (targetHeight < 0f) { - if (skeleton != null) { + if(targetHeight < 0f) { + if(skeleton != null) { targetHeight = getHeight(skeleton.getSkeletonConfig()); LogManager.log.warning("[AutoBone] Target height loaded from skeleton (Make sure you reset before running!): " + targetHeight); } else { float hmdHeight = getMaxHmdHeight(frames); - if (hmdHeight <= 0.50f) { + if(hmdHeight <= 0.50f) { LogManager.log.warning("[AutoBone] Max headset height detected (Value seems too low, did you not stand up straight while measuring?): " + hmdHeight); } else { LogManager.log.info("[AutoBone] Max headset height detected: " + hmdHeight); } - + // Estimate target height from HMD height targetHeight = hmdHeight; } } - - for (int epoch = calcInitError ? -1 : 0; epoch < numEpochs; epoch++) { + + for(int epoch = calcInitError ? -1 : 0; epoch < numEpochs; epoch++) { float sumError = 0f; int errorCount = 0; - - float adjustRate = epoch >= 0 ? (float)(initialAdjustRate / Math.pow(adjustRateDecay, epoch)) : 0f; - - for (int cursorOffset = minDataDistance; cursorOffset <= maxDataDistance && cursorOffset < frameCount; cursorOffset++) { - for (int frameCursor = 0; frameCursor < frameCount - cursorOffset; frameCursor += cursorIncrement) { + + float adjustRate = epoch >= 0 ? (float) (initialAdjustRate / Math.pow(adjustRateDecay, epoch)) : 0f; + + for(int cursorOffset = minDataDistance; cursorOffset <= maxDataDistance && cursorOffset < frameCount; cursorOffset++) { + for(int frameCursor = 0; frameCursor < frameCount - cursorOffset; frameCursor += cursorIncrement) { frames.getFrames(frameCursor, trackerBuffer1); frames.getFrames(frameCursor + cursorOffset, trackerBuffer2); - + skeleton1.setSkeletonConfigs(configs); skeleton2.setSkeletonConfigs(configs); - + skeleton1.setPoseFromFrame(trackerBuffer1); skeleton2.setPoseFromFrame(trackerBuffer2); - + float totalLength = getLengthSum(configs); float curHeight = getHeight(configs, staticConfigs); float errorDeriv = getErrorDeriv(trackerBuffer1, trackerBuffer2, skeleton1, skeleton2, targetHeight - curHeight); float error = errorFunc(errorDeriv); - + // In case of fire - if (Float.isNaN(error) || Float.isInfinite(error)) { + if(Float.isNaN(error) || Float.isInfinite(error)) { // Extinguish LogManager.log.warning("[AutoBone] Error value is invalid, resetting variables to recover"); reloadConfigValues(trackerBuffer1); - + // Reset error sum values sumError = 0f; errorCount = 0; - + // Continue on new data continue; } - + // Store the error count for logging purposes sumError += errorDeriv; errorCount++; - + float adjustVal = error * adjustRate; - - for (Entry entry : configs.entrySet()) { + + for(Entry entry : configs.entrySet()) { // Skip adjustment if the epoch is before starting (for logging only) - if (epoch < 0) { + if(epoch < 0) { break; } - + float originalLength = entry.getValue(); - + // Try positive and negative adjustments boolean isHeightVar = heightConfigs.contains(entry.getKey()); float minError = errorDeriv; float finalNewLength = -1f; - for (int i = 0; i < 2; i++) { + for(int i = 0; i < 2; i++) { // Scale by the ratio for smooth adjustment and more stable results float curAdjustVal = ((i == 0 ? adjustVal : -adjustVal) * originalLength) / totalLength; float newLength = originalLength + curAdjustVal; - + // No small or negative numbers!!! Bad algorithm! - if (newLength < 0.01f) { + if(newLength < 0.01f) { continue; } - + updateSkeletonBoneLength(skeleton1, skeleton2, entry.getKey(), newLength); - + float newHeight = isHeightVar ? curHeight + curAdjustVal : curHeight; float newErrorDeriv = getErrorDeriv(trackerBuffer1, trackerBuffer2, skeleton1, skeleton2, targetHeight - newHeight); - - if (newErrorDeriv < minError) { + + if(newErrorDeriv < minError) { minError = newErrorDeriv; finalNewLength = newLength; } } - - if (finalNewLength > 0f) { + + if(finalNewLength > 0f) { entry.setValue(finalNewLength); } - + // Reset the length to minimize bias in other variables, it's applied later updateSkeletonBoneLength(skeleton1, skeleton2, entry.getKey(), originalLength); } } } - + // Calculate average error over the epoch float avgError = errorCount > 0 ? sumError / errorCount : -1f; LogManager.log.info("[AutoBone] Epoch " + (epoch + 1) + " average error: " + avgError); - - if (epochCallback != null) { + + if(epochCallback != null) { epochCallback.accept(new Epoch(epoch + 1, avgError)); } } - + float finalHeight = getHeight(configs, staticConfigs); LogManager.log.info("[AutoBone] Target height: " + targetHeight + " New height: " + finalHeight); - + return Math.abs(finalHeight - targetHeight); } - + // The change in position of the ankle over time protected float getSlideErrorDeriv(SimpleSkeleton skeleton1, SimpleSkeleton skeleton2) { float slideLeft = skeleton1.getNodePosition(TrackerBodyPosition.LEFT_ANKLE).distance(skeleton2.getNodePosition(TrackerBodyPosition.LEFT_ANKLE)); float slideRight = skeleton1.getNodePosition(TrackerBodyPosition.RIGHT_ANKLE).distance(skeleton2.getNodePosition(TrackerBodyPosition.RIGHT_ANKLE)); - + // Divide by 4 to halve and average, it's halved because you want to approach a midpoint, not the other point return (slideLeft + slideRight) / 4f; } - + // The offset between both feet at one instant and over time protected float getOffsetErrorDeriv(SimpleSkeleton skeleton1, SimpleSkeleton skeleton2) { float skeleton1Left = skeleton1.getNodePosition(TrackerBodyPosition.LEFT_ANKLE).getY(); float skeleton1Right = skeleton1.getNodePosition(TrackerBodyPosition.RIGHT_ANKLE).getY(); - + float skeleton2Left = skeleton2.getNodePosition(TrackerBodyPosition.LEFT_ANKLE).getY(); float skeleton2Right = skeleton2.getNodePosition(TrackerBodyPosition.RIGHT_ANKLE).getY(); - + float dist1 = Math.abs(skeleton1Left - skeleton1Right); float dist2 = Math.abs(skeleton2Left - skeleton2Right); - + float dist3 = Math.abs(skeleton1Left - skeleton2Right); float dist4 = Math.abs(skeleton2Left - skeleton1Right); - + float dist5 = Math.abs(skeleton1Left - skeleton2Left); float dist6 = Math.abs(skeleton1Right - skeleton2Right); - + // Divide by 12 to halve and average, it's halved because you want to approach a midpoint, not the other point return (dist1 + dist2 + dist3 + dist4 + dist5 + dist6) / 12f; } - + // The distance from average human proportions protected float getProportionErrorDeriv(SimpleSkeleton skeleton) { Float neckLength = skeleton.getSkeletonConfig("Neck"); @@ -402,119 +396,119 @@ protected float getProportionErrorDeriv(SimpleSkeleton skeleton) { Float waistLength = skeleton.getSkeletonConfig("Waist"); Float legsLength = skeleton.getSkeletonConfig("Legs length"); Float kneeHeight = skeleton.getSkeletonConfig("Knee height"); - + float chestWaist = chestLength != null && waistLength != null ? Math.abs((chestLength / waistLength) - chestWaistRatio) : 0f; float legBody = legsLength != null && waistLength != null && neckLength != null ? Math.abs((legsLength / (waistLength + neckLength)) - legBodyRatio) : 0f; float kneeLeg = kneeHeight != null && legsLength != null ? Math.abs((kneeHeight / legsLength) - kneeLegRatio) : 0f; - - if (legBody <= legBodyRatioRange) { + + if(legBody <= legBodyRatioRange) { legBody = 0f; } else { legBody -= legBodyRatioRange; } - + return (chestWaist + legBody + kneeLeg) / 3f; } - + // The distance of any points to the corresponding absolute position protected float getPositionErrorDeriv(TrackerFrame[] frame, SimpleSkeleton skeleton) { float offset = 0f; int offsetCount = 0; - - for (TrackerFrame trackerFrame : frame) { - if (trackerFrame == null || !trackerFrame.hasData(TrackerFrameData.POSITION)) { + + for(TrackerFrame trackerFrame : frame) { + if(trackerFrame == null || !trackerFrame.hasData(TrackerFrameData.POSITION)) { continue; } - + Vector3f nodePos = skeleton.getNodePosition(trackerFrame.designation.designation); - if (nodePos != null) { + if(nodePos != null) { offset += Math.abs(nodePos.distance(trackerFrame.position)); offsetCount++; } } - + return offsetCount > 0 ? offset / offsetCount : 0f; } - + // The difference between offset of absolute position and the corresponding point over time protected float getPositionOffsetErrorDeriv(TrackerFrame[] frame1, TrackerFrame[] frame2, SimpleSkeleton skeleton1, SimpleSkeleton skeleton2) { float offset = 0f; int offsetCount = 0; - - for (TrackerFrame trackerFrame1 : frame1) { - if (trackerFrame1 == null || !trackerFrame1.hasData(TrackerFrameData.POSITION)) { + + for(TrackerFrame trackerFrame1 : frame1) { + if(trackerFrame1 == null || !trackerFrame1.hasData(TrackerFrameData.POSITION)) { continue; } - + TrackerFrame trackerFrame2 = TrackerUtils.findTrackerForBodyPosition(frame2, trackerFrame1.designation); - if (trackerFrame2 == null || !trackerFrame2.hasData(TrackerFrameData.POSITION)) { + if(trackerFrame2 == null || !trackerFrame2.hasData(TrackerFrameData.POSITION)) { continue; } - + Vector3f nodePos1 = skeleton1.getNodePosition(trackerFrame1.designation); - if (nodePos1 == null) { + if(nodePos1 == null) { continue; } - + Vector3f nodePos2 = skeleton2.getNodePosition(trackerFrame2.designation); - if (nodePos2 == null) { + if(nodePos2 == null) { continue; } - + float dist1 = Math.abs(nodePos1.distance(trackerFrame1.position)); float dist2 = Math.abs(nodePos2.distance(trackerFrame2.position)); - + offset += Math.abs(dist2 - dist1); offsetCount++; } - + return offsetCount > 0 ? offset / offsetCount : 0f; } - + protected float getErrorDeriv(TrackerFrame[] frame1, TrackerFrame[] frame2, SimpleSkeleton skeleton1, SimpleSkeleton skeleton2, float heightChange) { float totalError = 0f; float sumWeight = 0f; - - if (slideErrorFactor > 0f) { + + if(slideErrorFactor > 0f) { totalError += getSlideErrorDeriv(skeleton1, skeleton2) * slideErrorFactor; sumWeight += slideErrorFactor; } - - if (offsetErrorFactor > 0f) { + + if(offsetErrorFactor > 0f) { totalError += getOffsetErrorDeriv(skeleton1, skeleton2) * offsetErrorFactor; sumWeight += offsetErrorFactor; } - - if (proportionErrorFactor > 0f) { + + if(proportionErrorFactor > 0f) { // Either skeleton will work fine, skeleton1 is used as a default totalError += getProportionErrorDeriv(skeleton1) * proportionErrorFactor; sumWeight += proportionErrorFactor; } - - if (heightErrorFactor > 0f) { + + if(heightErrorFactor > 0f) { totalError += Math.abs(heightChange) * heightErrorFactor; sumWeight += heightErrorFactor; } - - if (positionErrorFactor > 0f) { + + if(positionErrorFactor > 0f) { totalError += (getPositionErrorDeriv(frame1, skeleton1) + getPositionErrorDeriv(frame2, skeleton2) / 2f) * positionErrorFactor; sumWeight += positionErrorFactor; } - - if (positionOffsetErrorFactor > 0f) { + + if(positionOffsetErrorFactor > 0f) { totalError += getPositionOffsetErrorDeriv(frame1, frame2, skeleton1, skeleton2) * positionOffsetErrorFactor; sumWeight += positionOffsetErrorFactor; } - + // Minimize sliding, minimize foot height offset, minimize change in total height return sumWeight > 0f ? totalError / sumWeight : 0f; } - + // Mean square error function protected static float errorFunc(float errorDeriv) { return 0.5f * (errorDeriv * errorDeriv); } - + protected void updateSkeletonBoneLength(SimpleSkeleton skeleton1, SimpleSkeleton skeleton2, String joint, float newLength) { skeleton1.setSkeletonConfig(joint, newLength, true); skeleton2.setSkeletonConfig(joint, newLength, true); diff --git a/src/main/java/dev/slimevr/vr/autobone/SimpleSkeleton.java b/src/main/java/dev/slimevr/autobone/SimpleSkeleton.java similarity index 90% rename from src/main/java/dev/slimevr/vr/autobone/SimpleSkeleton.java rename to src/main/java/dev/slimevr/autobone/SimpleSkeleton.java index 2f6457a277..52be559261 100644 --- a/src/main/java/dev/slimevr/vr/autobone/SimpleSkeleton.java +++ b/src/main/java/dev/slimevr/autobone/SimpleSkeleton.java @@ -1,4 +1,4 @@ -package dev.slimevr.vr.autobone; +package dev.slimevr.autobone; import java.util.HashMap; import java.util.Map; @@ -6,8 +6,8 @@ import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; -import dev.slimevr.vr.poserecorder.TrackerFrameData; -import dev.slimevr.vr.poserecorder.TrackerFrame; +import dev.slimevr.poserecorder.TrackerFrame; +import dev.slimevr.poserecorder.TrackerFrameData; import io.eiren.vr.processor.HumanSkeletonWithLegs; import io.eiren.vr.processor.HumanSkeletonWithWaist; import io.eiren.vr.processor.TrackerBodyPosition; @@ -16,14 +16,14 @@ import io.eiren.yaml.YamlFile; public class SimpleSkeleton { - + // Waist protected final TransformNode hmdNode = new TransformNode("HMD", false); protected final TransformNode headNode = new TransformNode("Head", false); protected final TransformNode neckNode = new TransformNode("Neck", false); protected final TransformNode waistNode = new TransformNode("Waist", false); protected final TransformNode chestNode = new TransformNode("Chest", false); - + protected float chestDistance = 0.42f; /** * Distance from eyes to waist @@ -37,7 +37,7 @@ public class SimpleSkeleton { * Distance from eyes to ear */ protected float headShift = HumanSkeletonWithWaist.HEAD_SHIFT_DEFAULT; - + // Legs protected final TransformNode leftHipNode = new TransformNode("Left-Hip", false); protected final TransformNode leftKneeNode = new TransformNode("Left-Knee", false); @@ -45,7 +45,7 @@ public class SimpleSkeleton { protected final TransformNode rightHipNode = new TransformNode("Right-Hip", false); protected final TransformNode rightKneeNode = new TransformNode("Right-Knee", false); protected final TransformNode rightAnkleNode = new TransformNode("Right-Ankle", false); - + /** * Distance between centers of both hips */ @@ -58,175 +58,168 @@ public class SimpleSkeleton { * Distance from waist to ankle */ protected float legsLength = 0.84f; - + protected final HashMap nodes = new HashMap(); - + private Quaternion rotBuf1 = new Quaternion(); private Quaternion rotBuf2 = new Quaternion(); - + public SimpleSkeleton() { // Assemble skeleton to waist hmdNode.attachChild(headNode); headNode.localTransform.setTranslation(0, 0, headShift); - + headNode.attachChild(neckNode); neckNode.localTransform.setTranslation(0, -neckLength, 0); - + neckNode.attachChild(chestNode); chestNode.localTransform.setTranslation(0, -chestDistance, 0); - + chestNode.attachChild(waistNode); waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0); - + // Assemble skeleton to feet waistNode.attachChild(leftHipNode); leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0); - + waistNode.attachChild(rightHipNode); rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0); - + leftHipNode.attachChild(leftKneeNode); leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0); - + rightHipNode.attachChild(rightKneeNode); rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0); - + leftKneeNode.attachChild(leftAnkleNode); leftAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0); - + rightKneeNode.attachChild(rightAnkleNode); rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0); - + // Set up a HashMap to get nodes by name easily hmdNode.depthFirstTraversal(visitor -> { nodes.put(visitor.getName(), visitor); }); } - + public SimpleSkeleton(Map configs, Map altConfigs) { // Initialize this(); - + // Set configs - if (altConfigs != null) { + if(altConfigs != null) { // Set alts first, so if there's any overlap it doesn't affect the values setSkeletonConfigs(altConfigs); } setSkeletonConfigs(configs); } - + public SimpleSkeleton(Map configs) { this(configs, null); } - + public void setPoseFromFrame(TrackerFrame[] frame) { - + TrackerFrame hmd = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.HMD); - - if (hmd != null) - { - if (hmd.hasData(TrackerFrameData.ROTATION)) - { + + if(hmd != null) { + if(hmd.hasData(TrackerFrameData.ROTATION)) { hmdNode.localTransform.setRotation(hmd.rotation); headNode.localTransform.setRotation(hmd.rotation); } - - if (hmd.hasData(TrackerFrameData.POSITION)) - { + + if(hmd.hasData(TrackerFrameData.POSITION)) { hmdNode.localTransform.setTranslation(hmd.position); } } - + TrackerFrame chest = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.CHEST, TrackerBodyPosition.WAIST); setRotation(chest, neckNode); - + TrackerFrame waist = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.WAIST, TrackerBodyPosition.CHEST); setRotation(waist, chestNode); - + TrackerFrame leftLeg = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.LEFT_LEG); TrackerFrame rightLeg = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.RIGHT_LEG); - + averagePelvis(waist, leftLeg, rightLeg); - + setRotation(leftLeg, leftHipNode); setRotation(rightLeg, rightHipNode); - + TrackerFrame leftAnkle = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.LEFT_ANKLE); setRotation(leftAnkle, rightKneeNode); - + TrackerFrame rightAnkle = TrackerUtils.findTrackerForBodyPosition(frame, TrackerBodyPosition.RIGHT_ANKLE); setRotation(rightAnkle, leftKneeNode); - + updatePose(); } - + public void setRotation(TrackerFrame trackerFrame, TransformNode node) { - if (trackerFrame != null && trackerFrame.hasData(TrackerFrameData.ROTATION)) { + if(trackerFrame != null && trackerFrame.hasData(TrackerFrameData.ROTATION)) { node.localTransform.setRotation(trackerFrame.rotation); } } - - public void averagePelvis(TrackerFrame waist, TrackerFrame leftLeg, TrackerFrame rightLeg) - { - if ((leftLeg == null || rightLeg == null) || (!leftLeg.hasData(TrackerFrameData.ROTATION) || !rightLeg.hasData(TrackerFrameData.ROTATION))) - { + + public void averagePelvis(TrackerFrame waist, TrackerFrame leftLeg, TrackerFrame rightLeg) { + if((leftLeg == null || rightLeg == null) || (!leftLeg.hasData(TrackerFrameData.ROTATION) || !rightLeg.hasData(TrackerFrameData.ROTATION))) { setRotation(waist, waistNode); return; } - - if (waist == null || !waist.hasData(TrackerFrameData.ROTATION)) - { - if ((leftLeg != null && rightLeg != null) && (leftLeg.hasData(TrackerFrameData.ROTATION) && rightLeg.hasData(TrackerFrameData.ROTATION))) - { + + if(waist == null || !waist.hasData(TrackerFrameData.ROTATION)) { + if(leftLeg.hasData(TrackerFrameData.ROTATION) && rightLeg.hasData(TrackerFrameData.ROTATION)) { leftLeg.getRotation(rotBuf1); rightLeg.getRotation(rotBuf2); rotBuf1.nlerp(rotBuf2, 0.5f); - + waistNode.localTransform.setRotation(rotBuf1); } - + return; } - + // Average the pelvis with the waist rotation leftLeg.getRotation(rotBuf1); rightLeg.getRotation(rotBuf2); rotBuf1.nlerp(rotBuf2, 0.5f); - + waist.getRotation(rotBuf2); rotBuf1.nlerp(rotBuf2, 0.3333333f); - + waistNode.localTransform.setRotation(rotBuf1); } - + public void setSkeletonConfigs(Map configs) { configs.forEach(this::setSkeletonConfig); } - + public void setSkeletonConfig(String joint, float newLength) { setSkeletonConfig(joint, newLength, false); } - + public void setSkeletonConfig(String joint, float newLength, boolean updatePose) { switch(joint) { case "Head": headShift = newLength; headNode.localTransform.setTranslation(0, 0, headShift); - if (updatePose) { + if(updatePose) { headNode.update(); } break; case "Neck": neckLength = newLength; neckNode.localTransform.setTranslation(0, -neckLength, 0); - if (updatePose) { + if(updatePose) { neckNode.update(); } break; case "Waist": waistDistance = newLength; waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0); - if (updatePose) { + if(updatePose) { waistNode.update(); } break; @@ -234,7 +227,7 @@ public void setSkeletonConfig(String joint, float newLength, boolean updatePose) chestDistance = newLength; chestNode.localTransform.setTranslation(0, -chestDistance, 0); waistNode.localTransform.setTranslation(0, -(waistDistance - chestDistance), 0); - if (updatePose) { + if(updatePose) { chestNode.update(); } break; @@ -242,7 +235,7 @@ public void setSkeletonConfig(String joint, float newLength, boolean updatePose) hipsWidth = newLength; leftHipNode.localTransform.setTranslation(-hipsWidth / 2, 0, 0); rightHipNode.localTransform.setTranslation(hipsWidth / 2, 0, 0); - if (updatePose) { + if(updatePose) { leftHipNode.update(); rightHipNode.update(); } @@ -253,7 +246,7 @@ public void setSkeletonConfig(String joint, float newLength, boolean updatePose) rightAnkleNode.localTransform.setTranslation(0, -kneeHeight, 0); leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0); rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0); - if (updatePose) { + if(updatePose) { leftKneeNode.update(); rightKneeNode.update(); } @@ -262,14 +255,14 @@ public void setSkeletonConfig(String joint, float newLength, boolean updatePose) legsLength = newLength; leftKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0); rightKneeNode.localTransform.setTranslation(0, -(legsLength - kneeHeight), 0); - if (updatePose) { + if(updatePose) { leftKneeNode.update(); rightKneeNode.update(); } break; } } - + public Float getSkeletonConfig(String joint) { switch(joint) { case "Head": @@ -287,70 +280,70 @@ public Float getSkeletonConfig(String joint) { case "Legs length": return legsLength; } - + return null; } - + public void updatePose() { hmdNode.update(); } - + public TransformNode getNode(String node) { return nodes.get(node); } - + public TransformNode getNode(TrackerBodyPosition bodyPosition) { return getNode(bodyPosition, false); } - + public TransformNode getNode(TrackerBodyPosition bodyPosition, boolean rotationNode) { - if (bodyPosition == null) { + if(bodyPosition == null) { return null; } - - switch (bodyPosition) { + + switch(bodyPosition) { case HMD: return hmdNode; case CHEST: return rotationNode ? neckNode : chestNode; case WAIST: return rotationNode ? chestNode : waistNode; - + case LEFT_LEG: return rotationNode ? leftHipNode : leftKneeNode; case RIGHT_LEG: return rotationNode ? rightHipNode : rightKneeNode; - + case LEFT_ANKLE: return rotationNode ? leftKneeNode : leftAnkleNode; case RIGHT_ANKLE: return rotationNode ? rightKneeNode : rightAnkleNode; } - + return null; } - + public Vector3f getNodePosition(String node) { TransformNode transformNode = getNode(node); return transformNode != null ? transformNode.worldTransform.getTranslation() : null; } - + public Vector3f getNodePosition(TrackerBodyPosition bodyPosition) { TransformNode node = getNode(bodyPosition); - if (node == null) { + if(node == null) { return null; } - + return node.worldTransform.getTranslation(); } - + public void saveConfigs(YamlFile config) { // Save waist configs config.setProperty("body.headShift", headShift); config.setProperty("body.neckLength", neckLength); config.setProperty("body.waistDistance", waistDistance); config.setProperty("body.chestDistance", chestDistance); - + // Save leg configs config.setProperty("body.hipsWidth", hipsWidth); config.setProperty("body.kneeHeight", kneeHeight); diff --git a/src/main/java/dev/slimevr/gui/AutoBoneWindow.java b/src/main/java/dev/slimevr/gui/AutoBoneWindow.java index c2017a64d8..8089f9c88c 100644 --- a/src/main/java/dev/slimevr/gui/AutoBoneWindow.java +++ b/src/main/java/dev/slimevr/gui/AutoBoneWindow.java @@ -20,79 +20,79 @@ import io.eiren.util.collections.FastList; import io.eiren.util.logging.LogManager; import io.eiren.vr.VRServer; -import dev.slimevr.vr.autobone.AutoBone; import javax.swing.event.MouseInputAdapter; import org.apache.commons.lang3.tuple.Pair; -import dev.slimevr.vr.poserecorder.PoseFrame; -import dev.slimevr.vr.poserecorder.PoseFrameIO; -import dev.slimevr.vr.poserecorder.PoseRecorder; +import dev.slimevr.autobone.AutoBone; +import dev.slimevr.poserecorder.PoseFrame; +import dev.slimevr.poserecorder.PoseFrameIO; +import dev.slimevr.poserecorder.PoseRecorder; public class AutoBoneWindow extends JFrame { - + private static File saveDir = new File("Recordings"); private static File loadDir = new File("LoadRecordings"); - + private EJBox pane; - + private final transient VRServer server; private final transient SkeletonConfig skeletonConfig; private final transient PoseRecorder poseRecorder; private final transient AutoBone autoBone; - + private transient Thread recordingThread = null; private transient Thread saveRecordingThread = null; private transient Thread autoBoneThread = null; - + private JButton saveRecordingButton; private JButton adjustButton; private JButton applyButton; - + private JLabel processLabel; private JLabel lengthsLabel; - + public AutoBoneWindow(VRServer server, SkeletonConfig skeletonConfig) { super("Skeleton Auto-Configuration"); - + this.server = server; this.skeletonConfig = skeletonConfig; this.poseRecorder = new PoseRecorder(server); this.autoBone = new AutoBone(server); - + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS)); add(new JScrollPane(pane = new EJBox(BoxLayout.PAGE_AXIS), ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED)); - + build(); } - + private String getLengthsString() { boolean first = true; StringBuilder configInfo = new StringBuilder(""); - for (Entry entry : autoBone.configs.entrySet()) { - if (!first) { + for(Entry entry : autoBone.configs.entrySet()) { + if(!first) { configInfo.append(", "); } else { first = false; } - + configInfo.append(entry.getKey() + ": " + StringUtils.prettyNumber(entry.getValue() * 100f, 2)); } - + return configInfo.toString(); } - + private void saveRecording(PoseFrame frames) { - if (saveDir.isDirectory() || saveDir.mkdirs()) { + if(saveDir.isDirectory() || saveDir.mkdirs()) { File saveRecording; int recordingIndex = 1; do { saveRecording = new File(saveDir, "ABRecording" + recordingIndex++ + ".pfr"); - } while (saveRecording.exists()); - + } while(saveRecording.exists()); + LogManager.log.info("[AutoBone] Exporting frames to \"" + saveRecording.getPath() + "\"..."); - if (PoseFrameIO.writeToFile(saveRecording, frames)) { + if(PoseFrameIO.writeToFile(saveRecording, frames)) { LogManager.log.info("[AutoBone] Done exporting! Recording can be found at \"" + saveRecording.getPath() + "\"."); } else { LogManager.log.severe("[AutoBone] Failed to export the recording to \"" + saveRecording.getPath() + "\"."); @@ -101,18 +101,18 @@ private void saveRecording(PoseFrame frames) { LogManager.log.severe("[AutoBone] Failed to create the recording directory \"" + saveDir.getPath() + "\"."); } } - + private List> loadRecordings() { List> recordings = new FastList>(); - if (loadDir.isDirectory()) { + if(loadDir.isDirectory()) { File[] files = loadDir.listFiles(); - if (files != null) { - for (File file : files) { - if (file.isFile() && org.apache.commons.lang3.StringUtils.endsWithIgnoreCase(file.getName(), ".pfr")) { + if(files != null) { + for(File file : files) { + if(file.isFile() && org.apache.commons.lang3.StringUtils.endsWithIgnoreCase(file.getName(), ".pfr")) { LogManager.log.info("[AutoBone] Detected recording at \"" + file.getPath() + "\", loading frames..."); PoseFrame frames = PoseFrameIO.readFromFile(file); - - if (frames == null) { + + if(frames == null) { LogManager.log.severe("Reading frames from \"" + file.getPath() + "\" failed..."); } else { recordings.add(Pair.of(file.getName(), frames)); @@ -121,26 +121,26 @@ private List> loadRecordings() { } } } - + return recordings; } - + private float processFrames(PoseFrame frames) { autoBone.minDataDistance = server.config.getInt("autobone.minimumDataDistance", autoBone.minDataDistance); autoBone.maxDataDistance = server.config.getInt("autobone.maximumDataDistance", autoBone.maxDataDistance); - + autoBone.numEpochs = server.config.getInt("autobone.epochCount", autoBone.numEpochs); - + autoBone.initialAdjustRate = server.config.getFloat("autobone.adjustRate", autoBone.initialAdjustRate); autoBone.adjustRateDecay = server.config.getFloat("autobone.adjustRateDecay", autoBone.adjustRateDecay); - + autoBone.slideErrorFactor = server.config.getFloat("autobone.slideErrorFactor", autoBone.slideErrorFactor); autoBone.offsetErrorFactor = server.config.getFloat("autobone.offsetErrorFactor", autoBone.offsetErrorFactor); autoBone.proportionErrorFactor = server.config.getFloat("autobone.proportionErrorFactor", autoBone.proportionErrorFactor); autoBone.heightErrorFactor = server.config.getFloat("autobone.heightErrorFactor", autoBone.heightErrorFactor); autoBone.positionErrorFactor = server.config.getFloat("autobone.positionErrorFactor", autoBone.positionErrorFactor); autoBone.positionOffsetErrorFactor = server.config.getFloat("autobone.positionOffsetErrorFactor", autoBone.positionOffsetErrorFactor); - + boolean calcInitError = server.config.getBoolean("autobone.calculateInitialError", true); float targetHeight = server.config.getFloat("autobone.manualTargetHeight", -1f); return autoBone.processFrames(frames, calcInitError, targetHeight, (epoch) -> { @@ -148,283 +148,292 @@ private float processFrames(PoseFrame frames) { lengthsLabel.setText(getLengthsString()); }); } - + @AWTThread private void build() { - pane.add(new EJBox(BoxLayout.LINE_AXIS) {{ - setBorder(new EmptyBorder(i(5))); - add(new JButton("Start Recording") {{ - addMouseListener(new MouseInputAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - // Prevent running multiple times - if (!isEnabled() || recordingThread != null) { - return; - } - - Thread thread = new Thread() { + pane.add(new EJBox(BoxLayout.LINE_AXIS) { + { + setBorder(new EmptyBorder(i(5))); + add(new JButton("Start Recording") { + { + addMouseListener(new MouseInputAdapter() { @Override - public void run() { - try { - if (poseRecorder.isReadyToRecord()) { - setText("Recording..."); - // 1000 samples at 20 ms per sample is 20 seconds - int sampleCount = server.config.getInt("autobone.sampleCount", 1000); - long sampleRate = server.config.getLong("autobone.sampleRateMs", 20L); - Future framesFuture = poseRecorder.startFrameRecording(sampleCount, sampleRate); - PoseFrame frames = framesFuture.get(); - LogManager.log.info("[AutoBone] Done recording!"); - - saveRecordingButton.setEnabled(true); - adjustButton.setEnabled(true); - - if (server.config.getBoolean("autobone.saveRecordings", false)) { - setText("Saving..."); - saveRecording(frames); + public void mouseClicked(MouseEvent e) { + // Prevent running multiple times + if(!isEnabled() || recordingThread != null) { + return; + } + + Thread thread = new Thread() { + @Override + public void run() { + try { + if(poseRecorder.isReadyToRecord()) { + setText("Recording..."); + // 1000 samples at 20 ms per sample is 20 seconds + int sampleCount = server.config.getInt("autobone.sampleCount", 1000); + long sampleRate = server.config.getLong("autobone.sampleRateMs", 20L); + Future framesFuture = poseRecorder.startFrameRecording(sampleCount, sampleRate); + PoseFrame frames = framesFuture.get(); + LogManager.log.info("[AutoBone] Done recording!"); + + saveRecordingButton.setEnabled(true); + adjustButton.setEnabled(true); + + if(server.config.getBoolean("autobone.saveRecordings", false)) { + setText("Saving..."); + saveRecording(frames); + } + } else { + setText("Not Ready..."); + LogManager.log.severe("[AutoBone] Unable to record..."); + Thread.sleep(3000); // Wait for 3 seconds + return; + } + } catch(Exception e) { + setText("Recording Failed..."); + LogManager.log.severe("[AutoBone] Failed recording!", e); + try { + Thread.sleep(3000); // Wait for 3 seconds + } catch(Exception e1) { + // Ignore + } + } finally { + setText("Start Recording"); + recordingThread = null; } - } else { - setText("Not Ready..."); - LogManager.log.severe("[AutoBone] Unable to record..."); - Thread.sleep(3000); // Wait for 3 seconds - return; - } - } catch (Exception e) { - setText("Recording Failed..."); - LogManager.log.severe("[AutoBone] Failed recording!", e); - try { - Thread.sleep(3000); // Wait for 3 seconds - } catch (Exception e1) { - // Ignore } - } finally { - setText("Start Recording"); - recordingThread = null; - } + }; + + recordingThread = thread; + thread.start(); } - }; - - recordingThread = thread; - thread.start(); + }); } }); - }}); - - add(saveRecordingButton = new JButton("Save Recording") {{ - setEnabled(poseRecorder.hasRecording()); - addMouseListener(new MouseInputAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - // Prevent running multiple times - if (!isEnabled() || saveRecordingThread != null) { - return; - } - - Thread thread = new Thread() { + + add(saveRecordingButton = new JButton("Save Recording") { + { + setEnabled(poseRecorder.hasRecording()); + addMouseListener(new MouseInputAdapter() { @Override - public void run() { - try { - Future framesFuture = poseRecorder.getFramesAsync(); - if (framesFuture != null) { - setText("Waiting for Recording..."); - PoseFrame frames = framesFuture.get(); - - if (frames.getTrackerCount() <= 0) { - throw new IllegalStateException("Recording has no trackers"); - } - - if (frames.getMaxFrameCount() <= 0) { - throw new IllegalStateException("Recording has no frames"); - } - - setText("Saving..."); - saveRecording(frames); - - setText("Recording Saved!"); - try { - Thread.sleep(3000); // Wait for 3 seconds - } catch (Exception e1) { - // Ignore - } - } else { - setText("No Recording..."); - LogManager.log.severe("[AutoBone] Unable to save, no recording was done..."); + public void mouseClicked(MouseEvent e) { + // Prevent running multiple times + if(!isEnabled() || saveRecordingThread != null) { + return; + } + + Thread thread = new Thread() { + @Override + public void run() { try { - Thread.sleep(3000); // Wait for 3 seconds - } catch (Exception e1) { - // Ignore + Future framesFuture = poseRecorder.getFramesAsync(); + if(framesFuture != null) { + setText("Waiting for Recording..."); + PoseFrame frames = framesFuture.get(); + + if(frames.getTrackerCount() <= 0) { + throw new IllegalStateException("Recording has no trackers"); + } + + if(frames.getMaxFrameCount() <= 0) { + throw new IllegalStateException("Recording has no frames"); + } + + setText("Saving..."); + saveRecording(frames); + + setText("Recording Saved!"); + try { + Thread.sleep(3000); // Wait for 3 seconds + } catch(Exception e1) { + // Ignore + } + } else { + setText("No Recording..."); + LogManager.log.severe("[AutoBone] Unable to save, no recording was done..."); + try { + Thread.sleep(3000); // Wait for 3 seconds + } catch(Exception e1) { + // Ignore + } + return; + } + } catch(Exception e) { + setText("Saving Failed..."); + LogManager.log.severe("[AutoBone] Failed to save recording!", e); + try { + Thread.sleep(3000); // Wait for 3 seconds + } catch(Exception e1) { + // Ignore + } + } finally { + setText("Save Recording"); + saveRecordingThread = null; } - return; - } - } catch (Exception e) { - setText("Saving Failed..."); - LogManager.log.severe("[AutoBone] Failed to save recording!", e); - try { - Thread.sleep(3000); // Wait for 3 seconds - } catch (Exception e1) { - // Ignore } - } finally { - setText("Save Recording"); - saveRecordingThread = null; - } + }; + + saveRecordingThread = thread; + thread.start(); } - }; - - saveRecordingThread = thread; - thread.start(); + }); } }); - }}); - - add(adjustButton = new JButton("Auto-Adjust") {{ - // If there are files to load, enable the button - setEnabled(poseRecorder.hasRecording() || (loadDir.isDirectory() && loadDir.list().length > 0)); - addMouseListener(new MouseInputAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - // Prevent running multiple times - if (!isEnabled() || autoBoneThread != null) { - return; - } - - Thread thread = new Thread() { + + add(adjustButton = new JButton("Auto-Adjust") { + { + // If there are files to load, enable the button + setEnabled(poseRecorder.hasRecording() || (loadDir.isDirectory() && loadDir.list().length > 0)); + addMouseListener(new MouseInputAdapter() { @Override - public void run() { - try { - setText("Load..."); - List> frameRecordings = loadRecordings(); - - if (!frameRecordings.isEmpty()) { - LogManager.log.info("[AutoBone] Done loading frames!"); - } else { - Future framesFuture = poseRecorder.getFramesAsync(); - if (framesFuture != null) { - setText("Waiting for Recording..."); - PoseFrame frames = framesFuture.get(); - - if (frames.getTrackerCount() <= 0) { - throw new IllegalStateException("Recording has no trackers"); + public void mouseClicked(MouseEvent e) { + // Prevent running multiple times + if(!isEnabled() || autoBoneThread != null) { + return; + } + + Thread thread = new Thread() { + @Override + public void run() { + try { + setText("Load..."); + List> frameRecordings = loadRecordings(); + + if(!frameRecordings.isEmpty()) { + LogManager.log.info("[AutoBone] Done loading frames!"); + } else { + Future framesFuture = poseRecorder.getFramesAsync(); + if(framesFuture != null) { + setText("Waiting for Recording..."); + PoseFrame frames = framesFuture.get(); + + if(frames.getTrackerCount() <= 0) { + throw new IllegalStateException("Recording has no trackers"); + } + + if(frames.getMaxFrameCount() <= 0) { + throw new IllegalStateException("Recording has no frames"); + } + + frameRecordings.add(Pair.of("", frames)); + } else { + setText("No Recordings..."); + LogManager.log.severe("[AutoBone] No recordings found in \"" + loadDir.getPath() + "\" and no recording was done..."); + try { + Thread.sleep(3000); // Wait for 3 seconds + } catch(Exception e1) { + // Ignore + } + return; + } } - - if (frames.getMaxFrameCount() <= 0) { - throw new IllegalStateException("Recording has no frames"); + + setText("Processing..."); + LogManager.log.info("[AutoBone] Processing frames..."); + FastList heightPercentError = new FastList(frameRecordings.size()); + for(Pair recording : frameRecordings) { + LogManager.log.info("[AutoBone] Processing frames from \"" + recording.getKey() + "\"..."); + + heightPercentError.add(processFrames(recording.getValue())); + LogManager.log.info("[AutoBone] Done processing!"); + applyButton.setEnabled(true); + + //#region Stats/Values + Float neckLength = autoBone.getConfig("Neck"); + Float chestLength = autoBone.getConfig("Chest"); + Float waistLength = autoBone.getConfig("Waist"); + Float hipWidth = autoBone.getConfig("Hips width"); + Float legsLength = autoBone.getConfig("Legs length"); + Float kneeHeight = autoBone.getConfig("Knee height"); + + float neckWaist = neckLength != null && waistLength != null ? neckLength / waistLength : 0f; + float chestWaist = chestLength != null && waistLength != null ? chestLength / waistLength : 0f; + float hipWaist = hipWidth != null && waistLength != null ? hipWidth / waistLength : 0f; + float legWaist = legsLength != null && waistLength != null ? legsLength / waistLength : 0f; + float legBody = legsLength != null && waistLength != null && neckLength != null ? legsLength / (waistLength + neckLength) : 0f; + float kneeLeg = kneeHeight != null && legsLength != null ? kneeHeight / legsLength : 0f; + + LogManager.log.info("[AutoBone] Ratios: [{Neck-Waist: " + StringUtils.prettyNumber(neckWaist) + "}, {Chest-Waist: " + StringUtils.prettyNumber(chestWaist) + "}, {Hip-Waist: " + StringUtils.prettyNumber(hipWaist) + "}, {Leg-Waist: " + StringUtils.prettyNumber(legWaist) + "}, {Leg-Body: " + StringUtils.prettyNumber(legBody) + "}, {Knee-Leg: " + StringUtils.prettyNumber(kneeLeg) + "}]"); + + String lengthsString = getLengthsString(); + LogManager.log.info("[AutoBone] Length values: " + lengthsString); + lengthsLabel.setText(lengthsString); } - - frameRecordings.add(Pair.of("", frames)); - } else { - setText("No Recordings..."); - LogManager.log.severe("[AutoBone] No recordings found in \"" + loadDir.getPath() + "\" and no recording was done..."); + + if(!heightPercentError.isEmpty()) { + float mean = 0f; + for(float val : heightPercentError) { + mean += val; + } + mean /= heightPercentError.size(); + + float std = 0f; + for(float val : heightPercentError) { + float stdVal = val - mean; + std += stdVal * stdVal; + } + std = (float) Math.sqrt(std / heightPercentError.size()); + + LogManager.log.info("[AutoBone] Average height error: " + StringUtils.prettyNumber(mean, 6) + " (SD " + StringUtils.prettyNumber(std, 6) + ")"); + } + //#endregion + } catch(Exception e) { + setText("Failed..."); + LogManager.log.severe("[AutoBone] Failed adjustment!", e); try { Thread.sleep(3000); // Wait for 3 seconds - } catch (Exception e1) { + } catch(Exception e1) { // Ignore } - return; - } - } - - setText("Processing..."); - LogManager.log.info("[AutoBone] Processing frames..."); - FastList heightPercentError = new FastList(frameRecordings.size()); - for (Pair recording : frameRecordings) { - LogManager.log.info("[AutoBone] Processing frames from \"" + recording.getKey() + "\"..."); - - heightPercentError.add(processFrames(recording.getValue())); - LogManager.log.info("[AutoBone] Done processing!"); - applyButton.setEnabled(true); - - //#region Stats/Values - Float neckLength = autoBone.getConfig("Neck"); - Float chestLength = autoBone.getConfig("Chest"); - Float waistLength = autoBone.getConfig("Waist"); - Float hipWidth = autoBone.getConfig("Hips width"); - Float legsLength = autoBone.getConfig("Legs length"); - Float kneeHeight = autoBone.getConfig("Knee height"); - - float neckWaist = neckLength != null && waistLength != null ? neckLength / waistLength : 0f; - float chestWaist = chestLength != null && waistLength != null ? chestLength / waistLength : 0f; - float hipWaist = hipWidth != null && waistLength != null ? hipWidth / waistLength : 0f; - float legWaist = legsLength != null && waistLength != null ? legsLength / waistLength : 0f; - float legBody = legsLength != null && waistLength != null && neckLength != null ? legsLength / (waistLength + neckLength) : 0f; - float kneeLeg = kneeHeight != null && legsLength != null ? kneeHeight / legsLength : 0f; - - LogManager.log.info("[AutoBone] Ratios: [{Neck-Waist: " + StringUtils.prettyNumber(neckWaist) + - "}, {Chest-Waist: " + StringUtils.prettyNumber(chestWaist) + - "}, {Hip-Waist: " + StringUtils.prettyNumber(hipWaist) + - "}, {Leg-Waist: " + StringUtils.prettyNumber(legWaist) + - "}, {Leg-Body: " + StringUtils.prettyNumber(legBody) + - "}, {Knee-Leg: " + StringUtils.prettyNumber(kneeLeg) + "}]"); - - String lengthsString = getLengthsString(); - LogManager.log.info("[AutoBone] Length values: " + lengthsString); - lengthsLabel.setText(lengthsString); - } - - if (!heightPercentError.isEmpty()) { - float mean = 0f; - for (float val : heightPercentError) { - mean += val; + } finally { + setText("Auto-Adjust"); + autoBoneThread = null; } - mean /= heightPercentError.size(); - - float std = 0f; - for (float val : heightPercentError) { - float stdVal = val - mean; - std += stdVal * stdVal; - } - std = (float)Math.sqrt(std / heightPercentError.size()); - - LogManager.log.info("[AutoBone] Average height error: " + StringUtils.prettyNumber(mean, 6) + " (SD " + StringUtils.prettyNumber(std, 6) + ")"); } - //#endregion - } catch (Exception e) { - setText("Failed..."); - LogManager.log.severe("[AutoBone] Failed adjustment!", e); - try { - Thread.sleep(3000); // Wait for 3 seconds - } catch (Exception e1) { - // Ignore - } - } finally { - setText("Auto-Adjust"); - autoBoneThread = null; - } + }; + + autoBoneThread = thread; + thread.start(); } - }; - - autoBoneThread = thread; - thread.start(); + }); } }); - }}); - - add(applyButton = new JButton("Apply Values") {{ - setEnabled(false); - addMouseListener(new MouseInputAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (!isEnabled()) { - return; - } - - autoBone.applyConfig(); - // Update GUI values after applying - skeletonConfig.refreshAll(); + + add(applyButton = new JButton("Apply Values") { + { + setEnabled(false); + addMouseListener(new MouseInputAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if(!isEnabled()) { + return; + } + + autoBone.applyConfig(); + // Update GUI values after applying + skeletonConfig.refreshAll(); + } + }); } }); - }}); - }}); - - pane.add(new EJBox(BoxLayout.LINE_AXIS) {{ - setBorder(new EmptyBorder(i(5))); - add(processLabel = new JLabel("Processing has not been started...")); - }}); - - pane.add(new EJBox(BoxLayout.LINE_AXIS) {{ - setBorder(new EmptyBorder(i(5))); - add(lengthsLabel = new JLabel(getLengthsString())); - }}); - + } + }); + + pane.add(new EJBox(BoxLayout.LINE_AXIS) { + { + setBorder(new EmptyBorder(i(5))); + add(processLabel = new JLabel("Processing has not been started...")); + } + }); + + pane.add(new EJBox(BoxLayout.LINE_AXIS) { + { + setBorder(new EmptyBorder(i(5))); + add(lengthsLabel = new JLabel(getLengthsString())); + } + }); + // Pack and display pack(); setLocationRelativeTo(null); diff --git a/src/main/java/dev/slimevr/vr/poserecorder/PoseFrame.java b/src/main/java/dev/slimevr/poserecorder/PoseFrame.java similarity index 86% rename from src/main/java/dev/slimevr/vr/poserecorder/PoseFrame.java rename to src/main/java/dev/slimevr/poserecorder/PoseFrame.java index 1d9105a358..0f8cba4ea3 100644 --- a/src/main/java/dev/slimevr/vr/poserecorder/PoseFrame.java +++ b/src/main/java/dev/slimevr/poserecorder/PoseFrame.java @@ -1,4 +1,4 @@ -package dev.slimevr.vr.poserecorder; +package dev.slimevr.poserecorder; import java.util.Iterator; import java.util.List; @@ -8,135 +8,135 @@ import io.eiren.vr.trackers.Tracker; public final class PoseFrame implements Iterable { - + private final FastList trackers; - + public PoseFrame(FastList trackers) { this.trackers = trackers; } - + public PoseFrame(int initialCapacity) { this.trackers = new FastList(initialCapacity); } - + public PoseFrame() { this(5); } - + public PoseFrameTracker addTracker(PoseFrameTracker tracker) { trackers.add(tracker); return tracker; } - + public PoseFrameTracker addTracker(Tracker tracker, int initialCapacity) { return addTracker(new PoseFrameTracker(tracker.getName(), initialCapacity)); } - + public PoseFrameTracker addTracker(Tracker tracker) { return addTracker(tracker, 5); } - + public PoseFrameTracker removeTracker(int index) { return trackers.remove(index); } - + public PoseFrameTracker removeTracker(PoseFrameTracker tracker) { trackers.remove(tracker); return tracker; } - + public void clearTrackers() { trackers.clear(); } - + public void fakeClearTrackers() { trackers.fakeClear(); } - + public int getTrackerCount() { return trackers.size(); } - + public List getTrackers() { return trackers; } - + public int getMaxFrameCount() { int maxFrames = 0; - - for (int i = 0; i < trackers.size(); i++) { + + for(int i = 0; i < trackers.size(); i++) { PoseFrameTracker tracker = trackers.get(i); - if (tracker != null && tracker.getFrameCount() > maxFrames) { + if(tracker != null && tracker.getFrameCount() > maxFrames) { maxFrames = tracker.getFrameCount(); } } - + return maxFrames; } - + public int getFrames(int frameIndex, TrackerFrame[] buffer) { - for (int i = 0; i < trackers.size(); i++) { + for(int i = 0; i < trackers.size(); i++) { PoseFrameTracker tracker = trackers.get(i); buffer[i] = tracker != null ? tracker.safeGetFrame(frameIndex) : null; } return trackers.size(); } - + public int getFrames(int frameIndex, List buffer) { - for (int i = 0; i < trackers.size(); i++) { + for(int i = 0; i < trackers.size(); i++) { PoseFrameTracker tracker = trackers.get(i); buffer.add(i, tracker != null ? tracker.safeGetFrame(frameIndex) : null); } return trackers.size(); } - + public TrackerFrame[] getFrames(int frameIndex) { TrackerFrame[] trackerFrames = new TrackerFrame[trackers.size()]; getFrames(frameIndex, trackerFrames); return trackerFrames; } - + @Override public Iterator iterator() { return new PoseFrameIterator(this); } - + public class PoseFrameIterator implements Iterator { - + private final PoseFrame poseFrame; private final TrackerFrame[] trackerFrameBuffer; - + private int cursor = 0; - + public PoseFrameIterator(PoseFrame poseFrame) { this.poseFrame = poseFrame; trackerFrameBuffer = new TrackerFrame[poseFrame.getTrackerCount()]; } - + @Override public boolean hasNext() { - if (trackers.isEmpty()) { + if(trackers.isEmpty()) { return false; } - - for (int i = 0; i < trackers.size(); i++) { + + for(int i = 0; i < trackers.size(); i++) { PoseFrameTracker tracker = trackers.get(i); - if (tracker != null && cursor < tracker.getFrameCount()) { + if(tracker != null && cursor < tracker.getFrameCount()) { return true; } } - + return false; } - + @Override public TrackerFrame[] next() { - if (!hasNext()) { + if(!hasNext()) { throw new NoSuchElementException(); } - + poseFrame.getFrames(cursor++, trackerFrameBuffer); - + return trackerFrameBuffer; } } diff --git a/src/main/java/dev/slimevr/vr/poserecorder/PoseFrameIO.java b/src/main/java/dev/slimevr/poserecorder/PoseFrameIO.java similarity index 77% rename from src/main/java/dev/slimevr/vr/poserecorder/PoseFrameIO.java rename to src/main/java/dev/slimevr/poserecorder/PoseFrameIO.java index 3c7fb460a3..74af74cc41 100644 --- a/src/main/java/dev/slimevr/vr/poserecorder/PoseFrameIO.java +++ b/src/main/java/dev/slimevr/poserecorder/PoseFrameIO.java @@ -1,4 +1,4 @@ -package dev.slimevr.vr.poserecorder; +package dev.slimevr.poserecorder; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -7,7 +7,6 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.util.List; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -17,41 +16,40 @@ import io.eiren.vr.processor.TrackerBodyPosition; public final class PoseFrameIO { - + private PoseFrameIO() { // Do not allow instantiating } - + public static boolean writeFrames(DataOutputStream outputStream, PoseFrame frames) { try { - if (frames != null) { - + if(frames != null) { outputStream.writeInt(frames.getTrackerCount()); - for (PoseFrameTracker tracker : frames.getTrackers()) { - + for(PoseFrameTracker tracker : frames.getTrackers()) { + outputStream.writeUTF(tracker.name); outputStream.writeInt(tracker.getFrameCount()); - for (int i = 0; i < tracker.getFrameCount(); i++) { + for(int i = 0; i < tracker.getFrameCount(); i++) { TrackerFrame trackerFrame = tracker.safeGetFrame(i); - if (trackerFrame == null) { + if(trackerFrame == null) { outputStream.writeInt(0); continue; } - + outputStream.writeInt(trackerFrame.getDataFlags()); - - if (trackerFrame.hasData(TrackerFrameData.DESIGNATION)) { + + if(trackerFrame.hasData(TrackerFrameData.DESIGNATION)) { outputStream.writeUTF(trackerFrame.designation.designation); } - - if (trackerFrame.hasData(TrackerFrameData.ROTATION)) { + + if(trackerFrame.hasData(TrackerFrameData.ROTATION)) { outputStream.writeFloat(trackerFrame.rotation.getX()); outputStream.writeFloat(trackerFrame.rotation.getY()); outputStream.writeFloat(trackerFrame.rotation.getZ()); outputStream.writeFloat(trackerFrame.rotation.getW()); } - - if (trackerFrame.hasData(TrackerFrameData.POSITION)) { + + if(trackerFrame.hasData(TrackerFrameData.POSITION)) { outputStream.writeFloat(trackerFrame.position.getX()); outputStream.writeFloat(trackerFrame.position.getY()); outputStream.writeFloat(trackerFrame.position.getZ()); @@ -61,81 +59,81 @@ public static boolean writeFrames(DataOutputStream outputStream, PoseFrame frame } else { outputStream.writeInt(0); } - } catch (Exception e) { + } catch(Exception e) { LogManager.log.severe("Error writing frame to stream", e); return false; } - + return true; } - + public static boolean writeToFile(File file, PoseFrame frames) { - try (DataOutputStream outputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { + try(DataOutputStream outputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { writeFrames(outputStream, frames); - } catch (Exception e) { + } catch(Exception e) { LogManager.log.severe("Error writing frames to file", e); return false; } - + return true; } - + public static PoseFrame readFrames(DataInputStream inputStream) { try { - + int trackerCount = inputStream.readInt(); FastList trackers = new FastList(trackerCount); - for (int i = 0; i < trackerCount; i++) { - + for(int i = 0; i < trackerCount; i++) { + String name = inputStream.readUTF(); int trackerFrameCount = inputStream.readInt(); FastList trackerFrames = new FastList(trackerFrameCount); - for (int j = 0; j < trackerFrameCount; j++) { + for(int j = 0; j < trackerFrameCount; j++) { int dataFlags = inputStream.readInt(); - + TrackerBodyPosition designation = null; - if (TrackerFrameData.DESIGNATION.check(dataFlags)) { + if(TrackerFrameData.DESIGNATION.check(dataFlags)) { designation = TrackerBodyPosition.getByDesignation(inputStream.readUTF()); } - + Quaternion rotation = null; - if (TrackerFrameData.ROTATION.check(dataFlags)) { + if(TrackerFrameData.ROTATION.check(dataFlags)) { float quatX = inputStream.readFloat(); float quatY = inputStream.readFloat(); float quatZ = inputStream.readFloat(); float quatW = inputStream.readFloat(); rotation = new Quaternion(quatX, quatY, quatZ, quatW); } - + Vector3f position = null; - if (TrackerFrameData.POSITION.check(dataFlags)) { + if(TrackerFrameData.POSITION.check(dataFlags)) { float posX = inputStream.readFloat(); float posY = inputStream.readFloat(); float posZ = inputStream.readFloat(); position = new Vector3f(posX, posY, posZ); } - + trackerFrames.add(new TrackerFrame(designation, rotation, position)); } - + trackers.add(new PoseFrameTracker(name, trackerFrames)); } - + return new PoseFrame(trackers); - } catch (Exception e) { + } catch(Exception e) { LogManager.log.severe("Error reading frame from stream", e); } - + return null; } - + public static PoseFrame readFromFile(File file) { try { return readFrames(new DataInputStream(new BufferedInputStream(new FileInputStream(file)))); - } catch (Exception e) { + } catch(Exception e) { LogManager.log.severe("Error reading frame from file", e); } - + return null; } } diff --git a/src/main/java/dev/slimevr/vr/poserecorder/PoseFrameTracker.java b/src/main/java/dev/slimevr/poserecorder/PoseFrameTracker.java similarity index 92% rename from src/main/java/dev/slimevr/vr/poserecorder/PoseFrameTracker.java rename to src/main/java/dev/slimevr/poserecorder/PoseFrameTracker.java index 812b70dca3..7970b833d1 100644 --- a/src/main/java/dev/slimevr/vr/poserecorder/PoseFrameTracker.java +++ b/src/main/java/dev/slimevr/poserecorder/PoseFrameTracker.java @@ -1,4 +1,4 @@ -package dev.slimevr.vr.poserecorder; +package dev.slimevr.poserecorder; import java.util.Iterator; @@ -12,220 +12,220 @@ import io.eiren.vr.trackers.TrackerStatus; public class PoseFrameTracker implements Tracker, Iterable { - + public final String name; - + private final FastList frames; private int frameCursor = 0; - + public PoseFrameTracker(String name, FastList frames) { - if (frames == null) { + if(frames == null) { throw new NullPointerException("frames must not be null"); } - + this.name = name != null ? name : ""; this.frames = frames; } - + public PoseFrameTracker(String name, int initialCapacity) { this(name, new FastList(initialCapacity)); } - + public PoseFrameTracker(String name) { this(name, 5); } - + private int limitCursor() { - if (frameCursor < 0 || frames.isEmpty()) { + if(frameCursor < 0 || frames.isEmpty()) { frameCursor = 0; - } else if (frameCursor >= frames.size()) { + } else if(frameCursor >= frames.size()) { frameCursor = frames.size() - 1; } - + return frameCursor; } - + public int setCursor(int index) { frameCursor = index; return limitCursor(); } - + public int incrementCursor(int increment) { frameCursor += increment; return limitCursor(); } - + public int incrementCursor() { return incrementCursor(1); } - + public int getCursor() { return frameCursor; } - + public int getFrameCount() { return frames.size(); } - + public TrackerFrame addFrame(int index, TrackerFrame trackerFrame) { frames.add(index, trackerFrame); return trackerFrame; } - + public TrackerFrame addFrame(int index, Tracker tracker) { return addFrame(index, TrackerFrame.fromTracker(tracker)); } - + public TrackerFrame addFrame(TrackerFrame trackerFrame) { frames.add(trackerFrame); return trackerFrame; } - + public TrackerFrame addFrame(Tracker tracker) { return addFrame(TrackerFrame.fromTracker(tracker)); } - + public TrackerFrame removeFrame(int index) { TrackerFrame trackerFrame = frames.remove(index); limitCursor(); return trackerFrame; } - + public TrackerFrame removeFrame(TrackerFrame trackerFrame) { frames.remove(trackerFrame); limitCursor(); return trackerFrame; } - + public void clearFrames() { frames.clear(); limitCursor(); } - + public void fakeClearFrames() { frames.fakeClear(); limitCursor(); } - + public TrackerFrame getFrame(int index) { return frames.get(index); } - + public TrackerFrame getFrame() { return getFrame(frameCursor); } - + public TrackerFrame safeGetFrame(int index) { try { return getFrame(index); - } catch (Exception e) { + } catch(Exception e) { return null; } } - + public TrackerFrame safeGetFrame() { return safeGetFrame(frameCursor); } - + //#region Tracker Interface Implementation @Override public boolean getRotation(Quaternion store) { TrackerFrame frame = safeGetFrame(); - if (frame != null && frame.hasData(TrackerFrameData.ROTATION)) { + if(frame != null && frame.hasData(TrackerFrameData.ROTATION)) { store.set(frame.rotation); return true; } - + store.set(0, 0, 0, 1); return false; } - + @Override public boolean getPosition(Vector3f store) { TrackerFrame frame = safeGetFrame(); - if (frame != null && frame.hasData(TrackerFrameData.POSITION)) { + if(frame != null && frame.hasData(TrackerFrameData.POSITION)) { store.set(frame.position); return true; } - + store.set(0, 0, 0); return false; } - + @Override public String getName() { return name; } - + @Override public TrackerStatus getStatus() { return TrackerStatus.OK; } - + @Override public void loadConfig(TrackerConfig config) { throw new UnsupportedOperationException("PoseFrameTracker does not implement configuration"); } - + @Override public void saveConfig(TrackerConfig config) { throw new UnsupportedOperationException("PoseFrameTracker does not implement configuration"); } - + @Override public float getConfidenceLevel() { return 0; } - + @Override public void resetFull(Quaternion reference) { throw new UnsupportedOperationException("PoseFrameTracker does not implement calibration"); } - + @Override public void resetYaw(Quaternion reference) { throw new UnsupportedOperationException("PoseFrameTracker does not implement calibration"); } - + @Override public void tick() { throw new UnsupportedOperationException("PoseFrameTracker does not implement this method"); } - + @Override public TrackerBodyPosition getBodyPosition() { TrackerFrame frame = safeGetFrame(); return frame == null ? null : frame.designation; } - + @Override public void setBodyPosition(TrackerBodyPosition position) { throw new UnsupportedOperationException("PoseFrameTracker does not allow setting the body position"); } - + @Override public boolean userEditable() { return false; } - + @Override public boolean hasRotation() { TrackerFrame frame = safeGetFrame(); return frame != null && frame.hasData(TrackerFrameData.ROTATION); } - + @Override public boolean hasPosition() { TrackerFrame frame = safeGetFrame(); return frame != null && frame.hasData(TrackerFrameData.POSITION); } - + @Override public boolean isComputed() { return true; } //#endregion - + @Override public Iterator iterator() { return frames.iterator(); diff --git a/src/main/java/dev/slimevr/vr/poserecorder/PoseRecorder.java b/src/main/java/dev/slimevr/poserecorder/PoseRecorder.java similarity index 85% rename from src/main/java/dev/slimevr/vr/poserecorder/PoseRecorder.java rename to src/main/java/dev/slimevr/poserecorder/PoseRecorder.java index 14150b09cc..9e72e57033 100644 --- a/src/main/java/dev/slimevr/vr/poserecorder/PoseRecorder.java +++ b/src/main/java/dev/slimevr/poserecorder/PoseRecorder.java @@ -1,4 +1,4 @@ -package dev.slimevr.vr.poserecorder; +package dev.slimevr.poserecorder; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -14,42 +14,42 @@ import io.eiren.vr.trackers.Tracker; public class PoseRecorder { - + protected PoseFrame poseFrame = null; - + protected int numFrames = -1; protected int frameCursor = 0; protected long frameRecordingInterval = 60L; protected long nextFrameTimeMs = -1L; - + protected CompletableFuture currentRecording; - + protected final VRServer server; FastList> trackers = new FastList>(); - + public PoseRecorder(VRServer server) { this.server = server; server.addOnTick(this::onTick); } - + @VRServerThread public void onTick() { - if (numFrames > 0) { + if(numFrames > 0) { PoseFrame poseFrame = this.poseFrame; List> trackers = this.trackers; - if (poseFrame != null && trackers != null) { - if (frameCursor < numFrames) { - if (System.currentTimeMillis() >= nextFrameTimeMs) { + if(poseFrame != null && trackers != null) { + if(frameCursor < numFrames) { + if(System.currentTimeMillis() >= nextFrameTimeMs) { nextFrameTimeMs = System.currentTimeMillis() + frameRecordingInterval; - + int cursor = frameCursor++; - for (Pair tracker : trackers) { + for(Pair tracker : trackers) { // Add a frame for each tracker tracker.getRight().addFrame(cursor, tracker.getLeft()); } - + // If done, send finished recording - if (frameCursor >= numFrames) { + if(frameCursor >= numFrames) { internalStopRecording(); } } @@ -60,102 +60,102 @@ public void onTick() { } } } - + public synchronized Future startFrameRecording(int numFrames, long interval) { return startFrameRecording(numFrames, interval, server.getAllTrackers()); } - + public synchronized Future startFrameRecording(int numFrames, long interval, List trackers) { - if (numFrames < 1) { + if(numFrames < 1) { throw new IllegalArgumentException("numFrames must at least have a value of 1"); } - if (interval < 1) { + if(interval < 1) { throw new IllegalArgumentException("interval must at least have a value of 1"); } - if (trackers == null) { + if(trackers == null) { throw new IllegalArgumentException("trackers must not be null"); } - if (trackers.isEmpty()) { + if(trackers.isEmpty()) { throw new IllegalArgumentException("trackers must have at least one entry"); } - if (!isReadyToRecord()) { + if(!isReadyToRecord()) { throw new IllegalStateException("PoseRecorder isn't ready to record!"); } - + cancelFrameRecording(); - + poseFrame = new PoseFrame(trackers.size()); - + // Update tracker list this.trackers.ensureCapacity(trackers.size()); - for (Tracker tracker : trackers) { + for(Tracker tracker : trackers) { // Ignore null and computed trackers - if (tracker == null || tracker.isComputed()) { + if(tracker == null || tracker.isComputed()) { continue; } - + // Pair tracker with recording this.trackers.add(Pair.of(tracker, poseFrame.addTracker(tracker, numFrames))); } - + this.frameCursor = 0; this.numFrames = numFrames; - + frameRecordingInterval = interval; nextFrameTimeMs = -1L; - + LogManager.log.info("[PoseRecorder] Recording " + numFrames + " samples at a " + interval + " ms frame interval"); - + currentRecording = new CompletableFuture(); return currentRecording; } - + private void internalStopRecording() { CompletableFuture currentRecording = this.currentRecording; - if (currentRecording != null && !currentRecording.isDone()) { + if(currentRecording != null && !currentRecording.isDone()) { // Stop the recording, returning the frames recorded currentRecording.complete(poseFrame); } - + numFrames = -1; frameCursor = 0; trackers.clear(); poseFrame = null; } - + public synchronized void stopFrameRecording() { internalStopRecording(); } - + public synchronized void cancelFrameRecording() { CompletableFuture currentRecording = this.currentRecording; - if (currentRecording != null && !currentRecording.isDone()) { + if(currentRecording != null && !currentRecording.isDone()) { // Cancel the current recording and return nothing currentRecording.cancel(true); } - + numFrames = -1; frameCursor = 0; trackers.clear(); poseFrame = null; } - + public boolean isReadyToRecord() { return server.getTrackersCount() > 0; } - + public boolean isRecording() { return numFrames > frameCursor; } - + public boolean hasRecording() { return currentRecording != null; } - + public Future getFramesAsync() { return currentRecording; } - + public PoseFrame getFrames() throws ExecutionException, InterruptedException { CompletableFuture currentRecording = this.currentRecording; return currentRecording != null ? currentRecording.get() : null; diff --git a/src/main/java/dev/slimevr/vr/poserecorder/TrackerFrame.java b/src/main/java/dev/slimevr/poserecorder/TrackerFrame.java similarity index 83% rename from src/main/java/dev/slimevr/vr/poserecorder/TrackerFrame.java rename to src/main/java/dev/slimevr/poserecorder/TrackerFrame.java index fc9a606aff..a1a132493f 100644 --- a/src/main/java/dev/slimevr/vr/poserecorder/TrackerFrame.java +++ b/src/main/java/dev/slimevr/poserecorder/TrackerFrame.java @@ -1,4 +1,4 @@ -package dev.slimevr.vr.poserecorder; +package dev.slimevr.poserecorder; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -9,162 +9,162 @@ import io.eiren.vr.trackers.TrackerStatus; public final class TrackerFrame implements Tracker { - + private int dataFlags = 0; - + public final TrackerBodyPosition designation; public final Quaternion rotation; public final Vector3f position; - + public TrackerFrame(TrackerBodyPosition designation, Quaternion rotation, Vector3f position) { this.designation = designation; - if (designation != null) { + if(designation != null) { dataFlags |= TrackerFrameData.DESIGNATION.flag; } - + this.rotation = rotation; - if (rotation != null) { + if(rotation != null) { dataFlags |= TrackerFrameData.ROTATION.flag; } - + this.position = position; - if (position != null) { + if(position != null) { dataFlags |= TrackerFrameData.POSITION.flag; } } - + public static TrackerFrame fromTracker(Tracker tracker) { - if (tracker == null) { + if(tracker == null) { return null; } - + // If the tracker is not ready - if (tracker.getStatus() != TrackerStatus.OK && tracker.getStatus() != TrackerStatus.BUSY && tracker.getStatus() != TrackerStatus.OCCLUDED) { + if(tracker.getStatus() != TrackerStatus.OK && tracker.getStatus() != TrackerStatus.BUSY && tracker.getStatus() != TrackerStatus.OCCLUDED) { return null; } - + // If tracker has no data - if (tracker.getBodyPosition() == null && !tracker.hasRotation() && !tracker.hasPosition()) { + if(tracker.getBodyPosition() == null && !tracker.hasRotation() && !tracker.hasPosition()) { return null; } - + Quaternion rotation = null; - if (tracker.hasRotation()) { + if(tracker.hasRotation()) { rotation = new Quaternion(); - if (!tracker.getRotation(rotation)) { + if(!tracker.getRotation(rotation)) { // If getting the rotation failed, set it back to null rotation = null; } } - + Vector3f position = null; - if (tracker.hasPosition()) { + if(tracker.hasPosition()) { position = new Vector3f(); - if (!tracker.getPosition(position)) { + if(!tracker.getPosition(position)) { // If getting the position failed, set it back to null position = null; } } - + return new TrackerFrame(tracker.getBodyPosition(), rotation, position); } - + public int getDataFlags() { return dataFlags; } - + public boolean hasData(TrackerFrameData flag) { return flag.check(dataFlags); } - + //#region Tracker Interface Implementation @Override public boolean getRotation(Quaternion store) { - if (hasData(TrackerFrameData.ROTATION)) { + if(hasData(TrackerFrameData.ROTATION)) { store.set(rotation); return true; } - + store.set(0, 0, 0, 1); return false; } - + @Override public boolean getPosition(Vector3f store) { - if (hasData(TrackerFrameData.POSITION)) { + if(hasData(TrackerFrameData.POSITION)) { store.set(position); return true; } - + store.set(0, 0, 0); return false; } - + @Override public String getName() { return "TrackerFrame:/" + (designation != null ? designation.designation : "null"); } - + @Override public TrackerStatus getStatus() { return TrackerStatus.OK; } - + @Override public void loadConfig(TrackerConfig config) { throw new UnsupportedOperationException("TrackerFrame does not implement configuration"); } - + @Override public void saveConfig(TrackerConfig config) { throw new UnsupportedOperationException("TrackerFrame does not implement configuration"); } - + @Override public float getConfidenceLevel() { return 0; } - + @Override public void resetFull(Quaternion reference) { throw new UnsupportedOperationException("TrackerFrame does not implement calibration"); } - + @Override public void resetYaw(Quaternion reference) { throw new UnsupportedOperationException("TrackerFrame does not implement calibration"); } - + @Override public void tick() { throw new UnsupportedOperationException("TrackerFrame does not implement this method"); } - + @Override public TrackerBodyPosition getBodyPosition() { return designation; } - + @Override public void setBodyPosition(TrackerBodyPosition position) { throw new UnsupportedOperationException("TrackerFrame does not allow setting the body position"); } - + @Override public boolean userEditable() { return false; } - + @Override public boolean hasRotation() { return hasData(TrackerFrameData.ROTATION); } - + @Override public boolean hasPosition() { return hasData(TrackerFrameData.POSITION); } - + @Override public boolean isComputed() { return true; diff --git a/src/main/java/dev/slimevr/vr/poserecorder/TrackerFrameData.java b/src/main/java/dev/slimevr/poserecorder/TrackerFrameData.java similarity index 71% rename from src/main/java/dev/slimevr/vr/poserecorder/TrackerFrameData.java rename to src/main/java/dev/slimevr/poserecorder/TrackerFrameData.java index 7282b973e1..e7d61983b1 100644 --- a/src/main/java/dev/slimevr/vr/poserecorder/TrackerFrameData.java +++ b/src/main/java/dev/slimevr/poserecorder/TrackerFrameData.java @@ -1,16 +1,18 @@ -package dev.slimevr.vr.poserecorder; +package dev.slimevr.poserecorder; public enum TrackerFrameData { + DESIGNATION(0), ROTATION(1), - POSITION(2); - + POSITION(2), + ; + public final int flag; - + TrackerFrameData(int id) { - this.flag = 1 << id; + this.flag = 1 << id; } - + public boolean check(int dataFlags) { return (dataFlags & this.flag) != 0; } diff --git a/src/main/java/io/eiren/gui/SkeletonConfig.java b/src/main/java/io/eiren/gui/SkeletonConfig.java index 17b61d1e9e..0edb5174c4 100644 --- a/src/main/java/io/eiren/gui/SkeletonConfig.java +++ b/src/main/java/io/eiren/gui/SkeletonConfig.java @@ -1,13 +1,10 @@ package io.eiren.gui; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.util.HashMap; import java.util.Map; import javax.swing.JButton; -import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.event.MouseInputAdapter; @@ -15,7 +12,6 @@ import io.eiren.util.StringUtils; import io.eiren.util.ann.ThreadSafe; import io.eiren.vr.VRServer; -import io.eiren.vr.processor.HumanSkeletonWithLegs; import io.eiren.vr.processor.HumanSkeleton; public class SkeletonConfig extends EJBag { diff --git a/src/main/java/io/eiren/gui/VRServerGUI.java b/src/main/java/io/eiren/gui/VRServerGUI.java index 97fa954e86..d6b3fdb511 100644 --- a/src/main/java/io/eiren/gui/VRServerGUI.java +++ b/src/main/java/io/eiren/gui/VRServerGUI.java @@ -38,7 +38,6 @@ public class VRServerGUI extends JFrame { private final TrackersList trackersList; private final SkeletonList skeletonList; private JButton resetButton; - private JScrollPane scroll; private EJBox pane; private float zoom = 1.5f; @@ -83,7 +82,7 @@ public VRServerGUI(VRServer server) { 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)); + add(new JScrollPane(pane = new EJBox(PAGE_AXIS), ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED)); GraphicsConfiguration gc = getGraphicsConfiguration(); Rectangle screenBounds = gc.getBounds(); setMinimumSize(new Dimension(100, 100)); diff --git a/src/main/java/io/eiren/vr/processor/HumanPoseProcessor.java b/src/main/java/io/eiren/vr/processor/HumanPoseProcessor.java index 45dfd6bfb8..e80381d144 100644 --- a/src/main/java/io/eiren/vr/processor/HumanPoseProcessor.java +++ b/src/main/java/io/eiren/vr/processor/HumanPoseProcessor.java @@ -10,7 +10,6 @@ import io.eiren.vr.trackers.HMDTracker; import io.eiren.vr.trackers.Tracker; import io.eiren.vr.trackers.TrackerStatus; -import io.eiren.vr.trackers.TrackerUtils; public class HumanPoseProcessor {