Skip to content

Commit

Permalink
Various geometry bugs are finally fixed
Browse files Browse the repository at this point in the history
  • Loading branch information
emilianavt committed Apr 18, 2019
1 parent 900775f commit 8e60113
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 30 deletions.
18 changes: 13 additions & 5 deletions Plugins/BVH Tools/BVHAnimationLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public class BVHAnimationLoader : MonoBehaviour {
public Animator targetAvatar;
[Tooltip("This is the path to the BVH file that should be loaded. Bone offsets are currently being ignored by this loader.")]
public string bvhFile;
[Tooltip("When this option is set, the BVH file will be assumed to have the Z axis as up and the Y axis as forward instead of the normal BVH conventions.")]
public bool blender = true;
[Tooltip("The frame time is the number of milliseconds per frame.")]
public float frameTime = 1000f / 24f;
[Tooltip("This is the name that will be set on the animation clip. Leaving this empty is also okay.")]
Expand Down Expand Up @@ -49,8 +51,8 @@ public struct FakeDictionary {
}

// BVH to Unity
Quaternion fromEulerYXZ(Vector3 euler) {
return Quaternion.AngleAxis(euler.y, Vector3.up) * Quaternion.AngleAxis(euler.x, Vector3.right) * Quaternion.AngleAxis(euler.z, Vector3.forward);
Quaternion fromEulerZXY(Vector3 euler) {
return Quaternion.AngleAxis(euler.z, Vector3.forward) * Quaternion.AngleAxis(euler.x, Vector3.right) * Quaternion.AngleAxis(euler.y, Vector3.up);
}

private float wrapAngle(float a) {
Expand Down Expand Up @@ -176,7 +178,10 @@ private void getCurves(string path, BvhNode node, Transform bone, bool first) {
if (posX && posY && posZ) {
for (int i = 0; i < frames; i++) {
Vector3 posBVH = new Vector3(keyframes[0][i].value, keyframes[1][i].value, keyframes[2][i].value);
Vector3 pos = new Vector3(-posBVH.y, posBVH.z, posBVH.x);
Vector3 pos = new Vector3(-posBVH.x, posBVH.y, posBVH.z);
if (blender) {
pos = new Vector3(-posBVH.x, posBVH.z, -posBVH.y);
}
keyframes[0][i].value = pos.x;
keyframes[1][i].value = pos.y;
keyframes[2][i].value = pos.z;
Expand All @@ -189,8 +194,11 @@ private void getCurves(string path, BvhNode node, Transform bone, bool first) {
if (rotX && rotY && rotZ) {
for (int i = 0; i < frames; i++) {
Vector3 eulerBVH = new Vector3(keyframes[3][i].value, keyframes[4][i].value, keyframes[5][i].value);
Quaternion rot = fromEulerYXZ(eulerBVH);
Quaternion rot2 = new Quaternion(rot.y, -rot.z, -rot.x, rot.w);
Quaternion rot = fromEulerZXY(eulerBVH);
Quaternion rot2 = new Quaternion(rot.x, -rot.y, -rot.z, rot.w);
if (blender) {
rot2 = new Quaternion(rot.x, -rot.z, rot.y, rot.w);
}
Vector3 euler = rot2.eulerAngles;
keyframes[3][i].value = wrapAngle(euler.x);
keyframes[4][i].value = wrapAngle(euler.y);
Expand Down
52 changes: 31 additions & 21 deletions Plugins/BVH Tools/BVHRecorder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ public class BVHRecorder : MonoBehaviour {
[Header("Recorder settings")]
[Tooltip("The bone rotations will be recorded every frame time milliseconds. Bone locations are recorded when this script starts running or genHierarchy() is called.")]
public float frameTime = 1000.0f / 24.0f;
[Tooltip("This is the filename to which the BVH file will be saved. If no filename is given, a random one will be generated when the script starts running. When importing this file into Blender, use the following settings: \"- Y Forward\", \"Z Up\", and \"Euler(YXZ)\"")]
[Tooltip("This is the filename to which the BVH file will be saved. If no filename is given, a random one will be generated when the script starts running.")]
public string filename;
[Tooltip("When this option is set, the BVH file will have the Z axis as up and the Y axis as forward instead of the normal BVH conventions.")]
public bool blender = true;
[Tooltip("When this box is checked, motion data will be recorded. It is possible to uncheck and check this box to pause and resume the capturing process.")]
public bool capturing = false;
[Header("Advanced settings")]
Expand Down Expand Up @@ -60,7 +62,7 @@ public SkelTree(Transform bone, Dictionary<Transform, string> boneMap) {
}
}
if (bone.localRotation.eulerAngles.x < 0 || bone.localRotation.eulerAngles.x > 0 || bone.localRotation.eulerAngles.y < 0 || bone.localRotation.eulerAngles.y > 0 || bone.localRotation.eulerAngles.z < 0 || bone.localRotation.eulerAngles.z > 0) {
throw new InvalidOperationException("Bone " + bone.name + " with non-zero rotation not supported.");
//throw new InvalidOperationException("Bone " + bone.name + " with non-zero rotation not supported.");
}
transform = bone;
children = new List<SkelTree>();
Expand Down Expand Up @@ -234,7 +236,10 @@ private static string tabs(int n) {

// This formats local translation vectors
private string getOffset(Vector3 offset) {
Vector3 offset2 = new Vector3(offset.z, -offset.x, offset.y);
Vector3 offset2 = new Vector3(-offset.x, offset.y, offset.z);
if (blender) {
offset2 = new Vector3(-offset.x, -offset.z, offset.y);
}
if (lowPrecision) {
return string.Format(CultureInfo.InvariantCulture, "{0: 0.00;-0.00}\t{1: 0.00;-0.00}\t{2: 0.00;-0.00}", offset2.x, offset2.y, offset2.z);
} else {
Expand All @@ -245,25 +250,30 @@ private string getOffset(Vector3 offset) {
// From: http://bediyap.com/programming/convert-quaternion-to-euler-rotations/
Vector3 manualEuler(float a, float b, float c, float d, float e) {
Vector3 euler = new Vector3();
euler.x = Mathf.Asin(c) * Mathf.Rad2Deg;
euler.y = Mathf.Atan2(a, b) * Mathf.Rad2Deg;
euler.z = Mathf.Atan2(d, e) * Mathf.Rad2Deg;
euler.z = Mathf.Atan2(a, b) * Mathf.Rad2Deg; // Z
euler.x = Mathf.Asin(c) * Mathf.Rad2Deg; // Y
euler.y = Mathf.Atan2(d, e) * Mathf.Rad2Deg; // X
return euler;
}

// Unity to BVH
Vector3 eulerYXZ(Quaternion q) {
return manualEuler(2 * (q.x * q.z + q.w * q.y),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z,
-2 * (q.y * q.z - q.w * q.x),
2 * (q.x * q.y + q.w * q.z),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z);
Vector3 eulerZXY(Quaternion q) {
return manualEuler(-2 * (q.x * q.y - q.w * q.z),
q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z,
2 * (q.y * q.z + q.w * q.x),
-2 * (q.x * q.z - q.w * q.y),
q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z); // ZXY
}

// This formats local Euler rotation vectors. BVH files expect rotations to be given in the order Z, X, Y.

private string getRotation(Quaternion rot) {
Quaternion rot2 = new Quaternion(-rot.z, rot.x, -rot.y, rot.w);
Vector3 angles = eulerYXZ(rot2);
Quaternion rot2 = new Quaternion(rot.x, -rot.y, -rot.z, rot.w);
if (blender) {
rot2 = new Quaternion(rot.x, rot.z, -rot.y, rot.w);
}
Vector3 angles = eulerZXY(rot2);
// This does convert to XZY order, but it should be ZXY?

if (lowPrecision) {
return string.Format(CultureInfo.InvariantCulture, "{0: 0.00;-0.00}\t{1: 0.00;-0.00}\t{2: 0.00;-0.00}", wrapAngle(angles.z), wrapAngle(angles.x), wrapAngle(angles.y));
} else {
Expand All @@ -285,8 +295,8 @@ private float wrapAngle(float a) {
// This function recursively generates JOINT entries for the hierarchy section of the BVH file
private string genJoint(int level, SkelTree bone) {
// I thought I'd pretend to be a bone with zero rotation, but it did nothing.
//Quaternion rot = bone.transform.localRotation;
//bone.transform.localRotation = Quaternion.identity;
Quaternion rot = bone.transform.localRotation;
bone.transform.localRotation = Quaternion.identity;

string result = tabs(level) + "JOINT " + bone.name + "\n" + tabs(level) + "{\n" + tabs(level) + "\tOFFSET\t" + getOffset(bone.transform.position - bone.transform.parent.position) + "\n" + tabs(level) + "\tCHANNELS 3 Zrotation Xrotation Yrotation\n";
boneOrder.Add(bone);
Expand All @@ -301,7 +311,7 @@ private string genJoint(int level, SkelTree bone) {
}

result += tabs(level) + "}\n";
//bone.transform.localRotation = rot;
bone.transform.localRotation = rot;

return result;
}
Expand All @@ -312,8 +322,8 @@ public void genHierarchy() {
throw new InvalidOperationException("Skeleton not initialized. You can initialize the skeleton by calling buildSkeleton().");
}

//Quaternion rot = skel.transform.localRotation;
//skel.transform.localRotation = Quaternion.identity;
Quaternion rot = skel.transform.localRotation;
skel.transform.localRotation = Quaternion.identity;
boneOrder = new List<SkelTree>() { skel };
hierarchy = "HIERARCHY\nROOT " + skel.name + "\n{\n\tOFFSET\t0.00\t0.00\t0.00\n\tCHANNELS 6 Xposition Yposition Zposition Zrotation Xrotation Yrotation\n";

Expand All @@ -327,7 +337,7 @@ public void genHierarchy() {
}

hierarchy += "}\n";
//skel.transform.localRotation = rot;
skel.transform.localRotation = rot;

frames = new List<string>();
lastFrame = Time.time;
Expand Down
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ This package provides a component to record motion data of VRM avatars in BVH
format files. A second component allows loading such files as (legacy)
animation clips back into Unity.

Other than depending on one script from UniVRM, this should actually work
with non-VRM avatars too.

## Requirements

The animation loader depends on the Bvh.cs file from UniVRM, so make sure you
Expand All @@ -25,12 +28,21 @@ Editor script should give something of an overview.

## Editing

You can load and edit your BVH file in Blender. When importing it, please use
the following settings:
If you want to edit your file in Blender, please enable the "Blender" checkbox
for both recording and loading and use the following settings during import
into Blender:

Forward: -Y Forward
Forward: Y Forward
Up: Z Up
Rotation: Euler (YXZ)

When importing files into Blender that were recorded with Blender mode disabled,
please use the following settings instead:

Forward: -Z Forward
Up: Y Up

When loading files that were exported from Blender, the Blender checkbox always
has to be enabled.

## Loading

Expand Down

0 comments on commit 8e60113

Please sign in to comment.