Add basic BVH file streamer

This commit is contained in:
ButterscotchVanilla
2021-09-24 05:10:08 -04:00
parent 472fcab821
commit a326d76f6a
4 changed files with 208 additions and 26 deletions

View File

@@ -7,5 +7,5 @@ root = true
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
#trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -0,0 +1,184 @@
package dev.slimevr.poserecorder;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import org.apache.commons.lang3.StringUtils;
import io.eiren.vr.processor.HumanSkeleton;
import io.eiren.vr.processor.TransformNode;
public class BVHFileStream extends PoseFileStream {
private static final int LONG_MAX_VALUE_DIGITS = Long.toString(Long.MAX_VALUE).length();
private static final float POS_SCALE = 10f;
private long frameCount = 0;
private final BufferedWriter writer;
private long frameCountOffset;
private float[] angleBuf = new float[3];
private Quaternion rotBuf = new Quaternion();
public BVHFileStream(OutputStream outputStream) {
super(outputStream);
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 128);
}
public BVHFileStream(File file) throws FileNotFoundException {
super(file);
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 128);
}
public BVHFileStream(String file) throws FileNotFoundException {
super(file);
writer = new BufferedWriter(new OutputStreamWriter(outputStream), 128);
}
private String getBufferedFrameCount(long frameCount) {
String frameString = Long.toString(frameCount);
int bufferCount = LONG_MAX_VALUE_DIGITS - frameString.length();
return bufferCount > 0 ? frameString + StringUtils.repeat(' ', bufferCount) : frameString;
}
private void writeTransformNodeHierarchy(TransformNode node) throws IOException {
writeTransformNodeHierarchy(node, 0);
}
private void writeTransformNodeHierarchy(TransformNode node, int level) throws IOException {
String indentLevel = StringUtils.repeat("\t", level);
String nextIndentLevel = indentLevel + "\t";
// Handle ends
if (node.children.isEmpty()) {
writer.write(indentLevel + "End Site\n");
} else {
writer.write((level > 0 ? indentLevel + "JOINT " : "ROOT ") + node.getName() + "\n");
}
writer.write(indentLevel + "{\n");
if (level > 0) {
Vector3f offset = node.localTransform.getTranslation();
writer.write(nextIndentLevel + "OFFSET " + Float.toString(offset.getX() * POS_SCALE) + " " + Float.toString(offset.getY() * POS_SCALE) + " " + Float.toString(offset.getZ() * POS_SCALE) + "\n");
} else {
writer.write(nextIndentLevel + "OFFSET 0.0 0.0 0.0\n");
}
// Handle ends
if (!node.children.isEmpty()) {
// Only give position for root
if (level > 0) {
writer.write(nextIndentLevel + "CHANNELS 3 Zrotation Xrotation Yrotation\n");
} else {
writer.write(nextIndentLevel + "CHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation\n");
}
for (TransformNode childNode : node.children) {
writeTransformNodeHierarchy(childNode, level + 1);
}
}
writer.write(indentLevel + "}\n");
}
@Override
public void writeHeader(HumanSkeleton skeleton, PoseStreamer streamer) throws IOException {
if (skeleton == null) {
throw new NullPointerException("skeleton must not be null");
}
if (streamer == null) {
throw new NullPointerException("streamer must not be null");
}
writer.write("HIERARCHY\n");
writeTransformNodeHierarchy(skeleton.getRootNode());
writer.write("MOTION\n");
writer.write("Frames: ");
// Get frame offset for finishing writing the file
if (parentStream instanceof FileOutputStream) {
FileOutputStream fileOutputStream = (FileOutputStream)parentStream;
// Flush buffer to get proper offset
writer.flush();
frameCountOffset = fileOutputStream.getChannel().position();
}
writer.write(getBufferedFrameCount(frameCount) + "\n");
// Frame time in seconds
writer.write("Frame Time: " + (streamer.frameRecordingInterval / 1000d) + "\n");
}
private void writeTransformHierarchyRotation(TransformNode node, Quaternion inverseRootRot) throws IOException {
rotBuf = node.localTransform.getRotation(rotBuf);
// Adjust to local rotation
if (inverseRootRot != null) {
rotBuf = inverseRootRot.mult(rotBuf, rotBuf);
}
angleBuf = rotBuf.toAngles(angleBuf);
writer.write(Float.toString((float)Math.toDegrees(angleBuf[2])) + " " + Float.toString((float)Math.toDegrees(angleBuf[0])) + " " + Float.toString((float)Math.toDegrees(angleBuf[1])));
// Get inverse rotation for child local rotations
Quaternion inverseRot = node.localTransform.getRotation().inverse();
for (TransformNode childNode : node.children) {
if (childNode.children.isEmpty()) {
// If it's an end node, skip
continue;
}
// Add spacing
writer.write(" ");
writeTransformHierarchyRotation(childNode, inverseRot);
}
}
@Override
public void writeFrame(HumanSkeleton skeleton) throws IOException {
if (skeleton == null) {
throw new NullPointerException("skeleton must not be null");
}
TransformNode root = skeleton.getRootNode();
Vector3f rootPos = root.localTransform.getTranslation();
// Write root position
writer.write(Float.toString(rootPos.getX() * POS_SCALE) + " " + Float.toString(rootPos.getY() * POS_SCALE) + " " + Float.toString(rootPos.getZ() * POS_SCALE) + " ");
writeTransformHierarchyRotation(root, null);
writer.newLine();
frameCount++;
}
@Override
public void writeFooter(HumanSkeleton skeleton) throws IOException {
// Write the final frame count for files
if (parentStream instanceof FileOutputStream) {
FileOutputStream fileOutputStream = (FileOutputStream)parentStream;
// Flush before anything else
writer.flush();
// Seek to the count offset
fileOutputStream.getChannel().position(frameCountOffset);
// Overwrite the count with a new value
writer.write(Long.toString(frameCount));
}
}
@Override
public void close() throws IOException {
writer.close();
}
}

View File

@@ -1,7 +1,6 @@
package dev.slimevr.poserecorder;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@@ -11,11 +10,13 @@ import java.io.OutputStream;
import io.eiren.vr.processor.HumanSkeleton;
public abstract class PoseFileStream implements AutoCloseable {
protected DataOutputStream dataStream;
protected final OutputStream parentStream;
protected final BufferedOutputStream outputStream;
protected PoseFileStream(OutputStream outputStream) {
this.dataStream = new DataOutputStream(new BufferedOutputStream(outputStream));
this.parentStream = outputStream;
this.outputStream = new BufferedOutputStream(outputStream);
}
protected PoseFileStream(File file) throws FileNotFoundException {
@@ -26,14 +27,16 @@ public abstract class PoseFileStream implements AutoCloseable {
this(new FileOutputStream(file));
}
abstract boolean writeHeader(HumanSkeleton skeleton) throws IOException;
public void writeHeader(HumanSkeleton skeleton, PoseStreamer streamer) throws IOException {
}
abstract boolean writeFrame(HumanSkeleton skeleton) throws IOException;
abstract void writeFrame(HumanSkeleton skeleton) throws IOException;
abstract boolean writeFooter(HumanSkeleton skeleton) throws IOException;
public void writeFooter(HumanSkeleton skeleton) throws IOException {
}
@Override
public void close() throws IOException {
dataStream.close();
outputStream.close();
}
}

View File

@@ -63,32 +63,27 @@ public class PoseStreamer {
}
public void setOutput(PoseFileStream poseFileStream) throws IOException {
poseFileStream.writeHeader(skeleton);
poseFileStream.writeHeader(skeleton, this);
this.poseFileStream = poseFileStream;
nextFrameTimeMs = -1L; // Reset the frame timing
}
public void setOutput(PoseFileStream poseFileStream, long intervalMs) throws IOException {
setFrameInterval(intervalMs);
setOutput(poseFileStream);
}
public PoseFileStream getOutput() {
return poseFileStream;
}
public void closeOutput() {
public void closeOutput() throws IOException {
PoseFileStream poseFileStream = this.poseFileStream;
if (poseFileStream != null) {
try {
poseFileStream.writeFooter(skeleton);
} catch (Exception e) {
// Ignore
LogManager.log.severe("[PoseStreamer] Exception while writing file footer", e);
}
try {
poseFileStream.close();
} catch (Exception e) {
// Ignore
LogManager.log.severe("[PoseStreamer] Exception while closing file stream", e);
}
if (poseFileStream != null) {
poseFileStream.writeFooter(skeleton);
poseFileStream.close();
this.poseFileStream = null;
}
}
}