mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-06 05:07:47 -05:00
303 lines
10 KiB
C#
303 lines
10 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.InteropServices;
|
|
using VrmLib.Bvh;
|
|
|
|
namespace VrmLib
|
|
{
|
|
public static class ModelExtensionsForBvh
|
|
{
|
|
static float ToRad(float src)
|
|
{
|
|
return src / 180.0f * MathFWrap.PI;
|
|
}
|
|
|
|
static BvhNode GetNode(BvhNode root, string path)
|
|
{
|
|
var splitted = path.Split('/');
|
|
|
|
var it = splitted.Select(x => x).GetEnumerator();
|
|
var current = root;
|
|
if (splitted[0] == path)
|
|
{
|
|
return current;
|
|
}
|
|
it.MoveNext();
|
|
while (it.MoveNext())
|
|
{
|
|
current = current.Children.First(x => x.Name == it.Current);
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
public static Model CreateFromBvh(BvhNode node)
|
|
{
|
|
// add nodes
|
|
var model = new Model(Coordinates.Vrm1);
|
|
model.Root.Name = "__bvh_root__";
|
|
|
|
AddBvhNodeRecursive(model, model.Root, node);
|
|
|
|
return model;
|
|
}
|
|
|
|
static void AddBvhNodeRecursive(Model model, Node parent, BvhNode node)
|
|
{
|
|
var newNode = new Node(node.Name)
|
|
{
|
|
HumanoidBone = node.Bone,
|
|
};
|
|
|
|
model.Nodes.Add(newNode);
|
|
parent.Add(newNode);
|
|
newNode.Translation = node.SkeletonLocalPosition;
|
|
|
|
foreach (var child in node.Children)
|
|
{
|
|
AddBvhNodeRecursive(model, newNode, child);
|
|
}
|
|
}
|
|
|
|
class BvhNodeCurves
|
|
{
|
|
public Bvh.ChannelCurve LocalPositionX;
|
|
public Bvh.ChannelCurve LocalPositionY;
|
|
public Bvh.ChannelCurve LocalPositionZ;
|
|
|
|
public Bvh.ChannelCurve EulerX;
|
|
public Bvh.ChannelCurve EulerY;
|
|
public Bvh.ChannelCurve EulerZ;
|
|
|
|
public void Set(string prop, Bvh.ChannelCurve curve)
|
|
{
|
|
switch (prop)
|
|
{
|
|
case "localPosition.x":
|
|
LocalPositionX = curve;
|
|
break;
|
|
|
|
case "localPosition.y":
|
|
LocalPositionY = curve;
|
|
break;
|
|
|
|
case "localPosition.z":
|
|
LocalPositionZ = curve;
|
|
break;
|
|
|
|
case "localEulerAnglesBaked.x":
|
|
EulerX = curve;
|
|
break;
|
|
|
|
case "localEulerAnglesBaked.y":
|
|
EulerY = curve;
|
|
break;
|
|
|
|
case "localEulerAnglesBaked.z":
|
|
EulerZ = curve;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static Animation LoadAnimation(string name, Bvh.Bvh bvh, Model model, float scalingFactor)
|
|
{
|
|
var animation = new Animation(name);
|
|
|
|
Dictionary<string, BvhNodeCurves> pathMap = new Dictionary<string, BvhNodeCurves>();
|
|
|
|
for (int i = 0; i < bvh.Channels.Length; ++i)
|
|
{
|
|
var channel = bvh.Channels[i];
|
|
|
|
if (!bvh.TryGetPathWithPropertyFromChannel(channel, out Bvh.Bvh.PathWithProperty prop))
|
|
{
|
|
throw new Exception();
|
|
}
|
|
|
|
if (!pathMap.TryGetValue(prop.Path, out BvhNodeCurves curves))
|
|
{
|
|
curves = new BvhNodeCurves();
|
|
pathMap.Add(prop.Path, curves);
|
|
}
|
|
|
|
curves.Set(prop.Property, channel);
|
|
}
|
|
|
|
// setup time
|
|
var timeBytes = new byte[Marshal.SizeOf(typeof(float)) * bvh.FrameCount];
|
|
var timeSpan = SpanLike.Wrap<Single>(new ArraySegment<byte>(timeBytes));
|
|
var now = 0.0;
|
|
for (int i = 0; i < timeSpan.Length; ++i, now += bvh.FrameTime.TotalSeconds)
|
|
{
|
|
timeSpan[i] = (float)now;
|
|
}
|
|
var times = new BufferAccessor(new ArraySegment<byte>(timeBytes), AccessorValueType.FLOAT, AccessorVectorType.SCALAR, bvh.FrameCount);
|
|
|
|
foreach (var (key, nodeCurve) in pathMap)
|
|
{
|
|
var node = Model.GetNode(model.Root, key);
|
|
var bvhNode = GetNode(bvh.Root, key);
|
|
var curve = new NodeAnimation();
|
|
|
|
if (nodeCurve.LocalPositionX != null)
|
|
{
|
|
var values = new byte[Marshal.SizeOf(typeof(Vector3))
|
|
* nodeCurve.LocalPositionX.Keys.Length];
|
|
var span = SpanLike.Wrap<Vector3>(new ArraySegment<byte>(values));
|
|
for (int i = 0; i < nodeCurve.LocalPositionX.Keys.Length; ++i)
|
|
{
|
|
span[i] = new Vector3
|
|
{
|
|
X = nodeCurve.LocalPositionX.Keys[i] * scalingFactor,
|
|
Y = nodeCurve.LocalPositionY.Keys[i] * scalingFactor,
|
|
Z = nodeCurve.LocalPositionZ.Keys[i] * scalingFactor,
|
|
};
|
|
}
|
|
var sampler = new CurveSampler
|
|
{
|
|
In = times,
|
|
Out = new BufferAccessor(new ArraySegment<byte>(values),
|
|
AccessorValueType.FLOAT, AccessorVectorType.VEC3, span.Length)
|
|
};
|
|
curve.Curves.Add(AnimationPathType.Translation, sampler);
|
|
}
|
|
|
|
if (nodeCurve.EulerX != null)
|
|
{
|
|
var values = new byte[Marshal.SizeOf(typeof(Quaternion))
|
|
* nodeCurve.EulerX.Keys.Length];
|
|
var span = SpanLike.Wrap<Quaternion>(new ArraySegment<byte>(values));
|
|
|
|
Func<Quaternion, BvhNodeCurves, int, Quaternion> getRot = (q, c, i) => q;
|
|
|
|
foreach (var ch in bvhNode.Channels)
|
|
{
|
|
var tmp = getRot;
|
|
switch (ch)
|
|
{
|
|
case Channel.Xrotation:
|
|
getRot = (_, c, i) =>
|
|
{
|
|
return tmp(_, c, i) *
|
|
Quaternion.CreateFromAxisAngle(Vector3.UnitX, ToRad(c.EulerX.Keys[i]));
|
|
};
|
|
break;
|
|
case Channel.Yrotation:
|
|
getRot = (_, c, i) =>
|
|
{
|
|
return tmp(_, c, i) *
|
|
Quaternion.CreateFromAxisAngle(Vector3.UnitY, ToRad(c.EulerY.Keys[i]));
|
|
};
|
|
break;
|
|
case Channel.Zrotation:
|
|
getRot = (_, c, i) =>
|
|
{
|
|
return tmp(_, c, i) *
|
|
Quaternion.CreateFromAxisAngle(Vector3.UnitZ, ToRad(c.EulerZ.Keys[i]));
|
|
};
|
|
break;
|
|
default:
|
|
// throw new NotImplementedException();
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < nodeCurve.EulerX.Keys.Length; ++i)
|
|
{
|
|
span[i] = getRot(Quaternion.Identity, nodeCurve, i);
|
|
}
|
|
var sampler = new CurveSampler
|
|
{
|
|
In = times,
|
|
Out = new BufferAccessor(new ArraySegment<byte>(values),
|
|
AccessorValueType.FLOAT, AccessorVectorType.VEC4, span.Length)
|
|
};
|
|
curve.Curves.Add(AnimationPathType.Rotation, sampler);
|
|
}
|
|
|
|
animation.AddCurve(node, curve);
|
|
}
|
|
|
|
return animation;
|
|
}
|
|
|
|
public static Model Load(string name, Bvh.Bvh bvh)
|
|
{
|
|
var model = CreateFromBvh(bvh.Root);
|
|
|
|
// estimate skeleton
|
|
var skeleton = SkeletonEstimator.Detect(model.Root);
|
|
if (skeleton == null)
|
|
{
|
|
throw new Exception("fail to estimate skeleton");
|
|
}
|
|
|
|
// foot to zero
|
|
var minY = model.Nodes.Min(x => x.Translation.Y);
|
|
var hips = model.Nodes.First(x => x.HumanoidBone == HumanoidBones.hips);
|
|
if (model.Root.Children.Count != 1)
|
|
{
|
|
throw new Exception();
|
|
}
|
|
if (model.Root.Children[0] != hips)
|
|
{
|
|
throw new Exception();
|
|
}
|
|
hips.Translation -= new Vector3(0, minY, 0);
|
|
|
|
// normalize scale
|
|
var pos = hips.Translation;
|
|
var factor = 1.0f;
|
|
if (pos.Y != 0)
|
|
{
|
|
factor = 1.0f / pos.Y;
|
|
foreach (var x in hips.Traverse())
|
|
{
|
|
x.LocalTranslation *= factor;
|
|
}
|
|
hips.Translation = new Vector3(pos.X, 1.0f, pos.Z);
|
|
}
|
|
|
|
// animation
|
|
model.Animations.Add(LoadAnimation(name, bvh, model, factor));
|
|
|
|
// add origin
|
|
var origin = new Node("origin");
|
|
origin.Add(model.Root.Children[0]);
|
|
model.Nodes.Add(origin);
|
|
model.Root.Add(origin);
|
|
|
|
return model;
|
|
}
|
|
|
|
public static void CreateBoxMan(this Model model)
|
|
{
|
|
// skin
|
|
var skin = new Skin();
|
|
skin.Joints.AddRange(model.Nodes);
|
|
skin.CalcInverseMatrices();
|
|
|
|
// mesh
|
|
var group = new MeshGroup("box-man")
|
|
{
|
|
Skin = skin,
|
|
};
|
|
var builder = new MeshBuilder();
|
|
builder.Build(model.Nodes);
|
|
group.Meshes.Add(builder.CreateMesh());
|
|
model.MeshGroups.Add(group);
|
|
|
|
// node
|
|
var meshNode = new Node("mesh");
|
|
meshNode.MeshGroup = group;
|
|
model.Nodes.Add(meshNode);
|
|
model.Root.Add(meshNode);
|
|
}
|
|
}
|
|
} |