/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.IO;
using OpenMetaverse;
namespace OpenSim.Region.Framework.Scenes.Animation
{
///
/// Written to decode and encode a binary animation asset.
/// The SecondLife Client reads in a BVH file and converts
/// it to the format described here. This isn't
///
public class BinBVHAnimation
{
///
/// Rotation Keyframe count (used internally)
/// Don't use this, use the rotationkeys.Length on each joint
///
private int rotationkeys;
///
/// Position Keyframe count (used internally)
/// Don't use this, use the positionkeys.Length on each joint
///
private int positionkeys;
public UInt16 unknown0; // Always 1
public UInt16 unknown1; // Always 0
///
/// Animation Priority
///
public int Priority;
///
/// The animation length in seconds.
///
public Single Length;
///
/// Expression set in the client. Null if [None] is selected
///
public string ExpressionName; // "" (null)
///
/// The time in seconds to start the animation
///
public Single InPoint;
///
/// The time in seconds to end the animation
///
public Single OutPoint;
///
/// Loop the animation
///
public bool Loop;
///
/// Meta data. Ease in Seconds.
///
public Single EaseInTime;
///
/// Meta data. Ease out seconds.
///
public Single EaseOutTime;
///
/// Meta Data for the Hand Pose
///
public uint HandPose;
///
/// Number of joints defined in the animation
/// Don't use this.. use joints.Length
///
private uint m_jointCount;
///
/// Contains an array of joints
///
public binBVHJoint[] Joints;
public byte[] ToBytes()
{
byte[] outputbytes;
using (MemoryStream ms = new MemoryStream())
using (BinaryWriter iostream = new BinaryWriter(ms))
{
iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(unknown0)));
iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(unknown1)));
iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(Priority)));
iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(Length)));
iostream.Write(BinBVHUtil.WriteNullTerminatedString(ExpressionName));
iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(InPoint)));
iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(OutPoint)));
iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(Loop ? 1 : 0)));
iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(EaseInTime)));
iostream.Write(BinBVHUtil.ES(Utils.FloatToBytes(EaseOutTime)));
iostream.Write(BinBVHUtil.ES(Utils.UIntToBytes(HandPose)));
iostream.Write(BinBVHUtil.ES(Utils.UIntToBytes((uint)(Joints.Length))));
for (int i = 0; i < Joints.Length; i++)
{
Joints[i].WriteBytesToStream(iostream, InPoint, OutPoint);
}
iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(0)));
using (MemoryStream ms2 = (MemoryStream)iostream.BaseStream)
outputbytes = ms2.ToArray();
}
return outputbytes;
}
public BinBVHAnimation()
{
rotationkeys = 0;
positionkeys = 0;
unknown0 = 1;
unknown1 = 0;
Priority = 1;
Length = 0;
ExpressionName = string.Empty;
InPoint = 0;
OutPoint = 0;
Loop = false;
EaseInTime = 0;
EaseOutTime = 0;
HandPose = 1;
m_jointCount = 0;
Joints = new binBVHJoint[1];
Joints[0] = new binBVHJoint();
Joints[0].Name = "mPelvis";
Joints[0].Priority = 7;
Joints[0].positionkeys = new binBVHJointKey[1];
Joints[0].rotationkeys = new binBVHJointKey[1];
Joints[0].rotationkeys[0] = new binBVHJointKey();
Joints[0].rotationkeys[0].time = (0f);
Joints[0].rotationkeys[0].key_element.X = ((float)Random.Shared.NextDouble() * 2 - 1);
Joints[0].rotationkeys[0].key_element.Y = ((float)Random.Shared.NextDouble() * 2 - 1);
Joints[0].rotationkeys[0].key_element.Z = ((float)Random.Shared.NextDouble() * 2 - 1);
Joints[0].positionkeys[0] = new binBVHJointKey();
Joints[0].positionkeys[0].time = (0f);
Joints[0].positionkeys[0].key_element.X = ((float)Random.Shared.NextDouble() * 2 - 1);
Joints[0].positionkeys[0].key_element.Y = ((float)Random.Shared.NextDouble() * 2 - 1);
Joints[0].positionkeys[0].key_element.Z = ((float)Random.Shared.NextDouble() * 2 - 1);
}
public BinBVHAnimation(byte[] animationdata)
{
int i = 0;
if (!BitConverter.IsLittleEndian)
{
unknown0 = Utils.BytesToUInt16(BinBVHUtil.EndianSwap(animationdata,i,2)); i += 2; // Always 1
unknown1 = Utils.BytesToUInt16(BinBVHUtil.EndianSwap(animationdata, i, 2)); i += 2; // Always 0
Priority = Utils.BytesToInt(BinBVHUtil.EndianSwap(animationdata, i, 4)); i += 4;
Length = Utils.BytesToFloat(BinBVHUtil.EndianSwap(animationdata, i, 4), 0); i += 4;
}
else
{
unknown0 = Utils.BytesToUInt16(animationdata, i); i += 2; // Always 1
unknown1 = Utils.BytesToUInt16(animationdata, i); i += 2; // Always 0
Priority = Utils.BytesToInt(animationdata, i); i += 4;
Length = Utils.BytesToFloat(animationdata, i); i += 4;
}
ExpressionName = ReadBytesUntilNull(animationdata, ref i);
if (!BitConverter.IsLittleEndian)
{
InPoint = Utils.BytesToFloat(BinBVHUtil.EndianSwap(animationdata, i, 4), 0); i += 4;
OutPoint = Utils.BytesToFloat(BinBVHUtil.EndianSwap(animationdata, i, 4), 0); i += 4;
Loop = (Utils.BytesToInt(BinBVHUtil.EndianSwap(animationdata, i, 4)) != 0); i += 4;
EaseInTime = Utils.BytesToFloat(BinBVHUtil.EndianSwap(animationdata, i, 4), 0); i += 4;
EaseOutTime = Utils.BytesToFloat(BinBVHUtil.EndianSwap(animationdata, i, 4), 0); i += 4;
HandPose = Utils.BytesToUInt(BinBVHUtil.EndianSwap(animationdata, i, 4)); i += 4; // Handpose?
m_jointCount = Utils.BytesToUInt(animationdata, i); i += 4; // Get Joint count
}
else
{
InPoint = Utils.BytesToFloat(animationdata, i); i += 4;
OutPoint = Utils.BytesToFloat(animationdata, i); i += 4;
Loop = (Utils.BytesToInt(animationdata, i) != 0); i += 4;
EaseInTime = Utils.BytesToFloat(animationdata, i); i += 4;
EaseOutTime = Utils.BytesToFloat(animationdata, i); i += 4;
HandPose = Utils.BytesToUInt(animationdata, i); i += 4; // Handpose?
m_jointCount = Utils.BytesToUInt(animationdata, i); i += 4; // Get Joint count
}
Joints = new binBVHJoint[m_jointCount];
// deserialize the number of joints in the animation.
// Joints are variable length blocks of binary data consisting of joint data and keyframes
for (int iter = 0; iter < m_jointCount; iter++)
{
binBVHJoint joint = readJoint(animationdata, ref i);
Joints[iter] = joint;
}
}
///
/// Variable length strings seem to be null terminated in the animation asset.. but..
/// use with caution, home grown.
/// advances the index.
///
/// The animation asset byte array
/// The offset to start reading
/// a string
private static string ReadBytesUntilNull(byte[] data, ref int i)
{
char nterm = '\0'; // Null terminator
int endpos = i;
int startpos = i;
// Find the null character
for (int j = i; j < data.Length; j++)
{
char spot = Convert.ToChar(data[j]);
if (spot == nterm)
{
endpos = j;
break;
}
}
// if we got to the end, then it's a zero length string
if (i == endpos)
{
// advance the 1 null character
i++;
return string.Empty;
}
else
{
// We found the end of the string
// append the bytes from the beginning of the string to the end of the string
// advance i
byte[] interm = new byte[endpos-i];
for (; i
/// Read in a Joint from an animation asset byte array
/// Variable length Joint fields, yay!
/// Advances the index
///
/// animation asset byte array
/// Byte Offset of the start of the joint
/// The Joint data serialized into the binBVHJoint structure
private binBVHJoint readJoint(byte[] data, ref int i)
{
binBVHJointKey[] positions;
binBVHJointKey[] rotations;
binBVHJoint pJoint = new binBVHJoint();
/*
109
84
111
114
114
111
0 <--- Null terminator
*/
pJoint.Name = ReadBytesUntilNull(data, ref i); // Joint name
/*
2 <- Priority Revisited
0
0
0
*/
/*
5 <-- 5 keyframes
0
0
0
... 5 Keyframe data blocks
*/
/*
2 <-- 2 keyframes
0
0
0
.. 2 Keyframe data blocks
*/
if (!BitConverter.IsLittleEndian)
{
pJoint.Priority = Utils.BytesToInt(BinBVHUtil.EndianSwap(data, i, 4)); i += 4; // Joint Priority override?
rotationkeys = Utils.BytesToInt(BinBVHUtil.EndianSwap(data, i, 4)); i += 4; // How many rotation keyframes
}
else
{
pJoint.Priority = Utils.BytesToInt(data, i); i += 4; // Joint Priority override?
rotationkeys = Utils.BytesToInt(data, i); i += 4; // How many rotation keyframes
}
// argh! floats into two bytes!.. bad bad bad bad
// After fighting with it for a while.. -1, to 1 seems to give the best results
rotations = readKeys(data, ref i, rotationkeys, -1f, 1f);
for (int iter = 0; iter < rotations.Length; iter++)
{
rotations[iter].W = 1f -
(rotations[iter].key_element.X + rotations[iter].key_element.Y +
rotations[iter].key_element.Z);
}
if (!BitConverter.IsLittleEndian)
{
positionkeys = Utils.BytesToInt(BinBVHUtil.EndianSwap(data, i, 4)); i += 4; // How many position keyframes
}
else
{
positionkeys = Utils.BytesToInt(data, i); i += 4; // How many position keyframes
}
// Read in position keyframes
// argh! more floats into two bytes!.. *head desk*
// After fighting with it for a while.. -5, to 5 seems to give the best results
positions = readKeys(data, ref i, positionkeys, -5f, 5f);
pJoint.rotationkeys = rotations;
pJoint.positionkeys = positions;
return pJoint;
}
///
/// Read Keyframes of a certain type
/// advance i
///
/// Animation Byte array
/// Offset in the Byte Array. Will be advanced
/// Number of Keyframes
/// Scaling Min to pass to the Uint16ToFloat method
/// Scaling Max to pass to the Uint16ToFloat method
///
private binBVHJointKey[] readKeys(byte[] data, ref int i, int keycount, float min, float max)
{
float x;
float y;
float z;
/*
0.o, Float values in Two bytes.. this is just wrong >:(
17 255 <-- Time Code
17 255 <-- Time Code
255 255 <-- X
127 127 <-- X
255 255 <-- Y
127 127 <-- Y
213 213 <-- Z
142 142 <---Z
*/
binBVHJointKey[] m_keys = new binBVHJointKey[keycount];
for (int j = 0; j < keycount; j++)
{
binBVHJointKey pJKey = new binBVHJointKey();
pJKey.time = Utils.BytesUInt16ToFloat(data, i, InPoint, OutPoint); i += 2;
x = Utils.BytesUInt16ToFloat(data, i, min, max); i += 2;
y = Utils.BytesUInt16ToFloat(data, i, min, max); i += 2;
z = Utils.BytesUInt16ToFloat(data, i, min, max); i += 2;
pJKey.key_element = new Vector3(x, y, z);
m_keys[j] = pJKey;
}
return m_keys;
}
}
///
/// A Joint and it's associated meta data and keyframes
///
public struct binBVHJoint
{
///
/// Name of the Joint. Matches the avatar_skeleton.xml in client distros
///
public string Name;
///
/// Joint Animation Override? Was the same as the Priority in testing..
///
public int Priority;
///
/// Array of Rotation Keyframes in order from earliest to latest
///
public binBVHJointKey[] rotationkeys;
///
/// Array of Position Keyframes in order from earliest to latest
/// This seems to only be for the Pelvis?
///
public binBVHJointKey[] positionkeys;
public void WriteBytesToStream(BinaryWriter iostream, float InPoint, float OutPoint)
{
iostream.Write(BinBVHUtil.WriteNullTerminatedString(Name));
iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(Priority)));
iostream.Write(BinBVHUtil.ES(Utils.IntToBytes(rotationkeys.Length)));
for (int i=0;i
/// A Joint Keyframe. This is either a position or a rotation.
///
public struct binBVHJointKey
{
// Time in seconds for this keyframe.
public float time;
///
/// Either a Vector3 position or a Vector3 Euler rotation
///
public Vector3 key_element;
public float W;
public void WriteBytesToStream(BinaryWriter iostream, float InPoint, float OutPoint, float min, float max)
{
iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(BinBVHUtil.FloatToUInt16(time, InPoint, OutPoint))));
iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(BinBVHUtil.FloatToUInt16(key_element.X, min, max))));
iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(BinBVHUtil.FloatToUInt16(key_element.Y, min, max))));
iostream.Write(BinBVHUtil.ES(Utils.UInt16ToBytes(BinBVHUtil.FloatToUInt16(key_element.Z, min, max))));
}
}
///
/// Poses set in the animation metadata for the hands.
///
public enum HandPose : uint
{
Spread = 0,
Relaxed = 1,
Point_Both = 2,
Fist = 3,
Relaxed_Left = 4,
Point_Left = 5,
Fist_Left = 6,
Relaxed_Right = 7,
Point_Right = 8,
Fist_Right = 9,
Salute_Right = 10,
Typing = 11,
Peace_Right = 12
}
public static class BinBVHUtil
{
public const float ONE_OVER_U16_MAX = 1.0f / UInt16.MaxValue;
public static UInt16 FloatToUInt16(float val, float lower, float upper)
{
UInt16 uival = 0;
//m_parentGroup.GetTimeDilation() * (float)ushort.MaxValue
//0-1
// float difference = upper - lower;
// we're trying to get a zero lower and modify all values equally so we get a percentage position
if (lower > 0)
{
upper -= lower;
val = val - lower;
// start with 500 upper and 200 lower.. subtract 200 from the upper and the value
}
else //if (lower < 0 && upper > 0)
{
// double negative, 0 minus negative 5 is 5.
upper += 0 - lower;
lower += 0 - lower;
val += 0 - lower;
}
if (upper == 0)
val = 0;
else
{
val /= upper;
}
uival = (UInt16)(val * UInt16.MaxValue);
return uival;
}
///
/// Endian Swap
/// Swaps endianness if necessary
///
/// Input array
///
public static byte[] ES(byte[] arr)
{
if (!BitConverter.IsLittleEndian)
Array.Reverse(arr);
return arr;
}
public static byte[] EndianSwap(byte[] arr, int offset, int len)
{
byte[] bendian = new byte[offset + len];
Buffer.BlockCopy(arr, offset, bendian, 0, len);
Array.Reverse(bendian);
return bendian;
}
public static byte[] WriteNullTerminatedString(string str)
{
byte[] output = new byte[str.Length + 1];
Char[] chr = str.ToCharArray();
int i = 0;
for (i = 0; i < chr.Length; i++)
{
output[i] = Convert.ToByte(chr[i]);
}
output[i] = Convert.ToByte('\0');
return output;
}
}
}
/*
switch (jointname)
{
case "mPelvis":
case "mTorso":
case "mNeck":
case "mHead":
case "mChest":
case "mHipLeft":
case "mHipRight":
case "mKneeLeft":
case "mKneeRight":
// XYZ->ZXY
t = x;
x = y;
y = t;
break;
case "mCollarLeft":
case "mCollarRight":
case "mElbowLeft":
case "mElbowRight":
// YZX ->ZXY
t = z;
z = x;
x = y;
y = t;
break;
case "mWristLeft":
case "mWristRight":
case "mShoulderLeft":
case "mShoulderRight":
// ZYX->ZXY
t = y;
y = z;
z = t;
break;
case "mAnkleLeft":
case "mAnkleRight":
// XYZ ->ZXY
t = x;
x = z;
z = y;
y = t;
break;
}
*/