mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-06-10 09:50:37 -05:00
remove non used
This commit is contained in:
parent
f8745ee17f
commit
75a12cbe87
|
|
@ -6,7 +6,7 @@ namespace UniVRM10
|
|||
{
|
||||
public static class ModelLoader
|
||||
{
|
||||
public static Model Load(Vrm10Storage storage, string rootName, bool estimateHumanoid = false)
|
||||
public static Model Load(Vrm10Storage storage, string rootName)
|
||||
{
|
||||
if (storage == null)
|
||||
{
|
||||
|
|
@ -81,11 +81,7 @@ namespace UniVRM10
|
|||
model.Animations.AddRange(Enumerable.Range(0, storage.AnimationCount).Select(x => storage.CreateAnimation(x, model.Nodes)));
|
||||
|
||||
// VRM
|
||||
if (!LoadVrm(model, storage) && estimateHumanoid)
|
||||
{
|
||||
// VRMでないときにボーン推定する
|
||||
model.HumanoidBoneEstimate();
|
||||
}
|
||||
LoadVrm(model, storage);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using UnityEngine;
|
|||
using UnityEngine.TestTools;
|
||||
using UniVRM10;
|
||||
using VrmLib;
|
||||
using VrmLib.Diff;
|
||||
|
||||
namespace UniVRM10.Test
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ using UniGLTF;
|
|||
using UniJSON;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VrmLib.Diff;
|
||||
|
||||
|
||||
namespace UniVRM10
|
||||
{
|
||||
|
|
@ -206,16 +206,16 @@ namespace UniVRM10
|
|||
|
||||
// vrmlib <= gltf
|
||||
var loaded = deserialized.FromGltf(textures);
|
||||
var context = ModelDiffContext.Create();
|
||||
ModelDiffExtensions.MaterialEquals(context, vrmLibMaterial, loaded);
|
||||
var diff = context.List
|
||||
.Where(x => !s_ignoreKeys.Contains(x.Context))
|
||||
.ToArray();
|
||||
if (diff.Length > 0)
|
||||
{
|
||||
Debug.LogWarning(string.Join("\n", diff.Select(x => $"{x.Context}: {x.Message}")));
|
||||
}
|
||||
Assert.AreEqual(0, diff.Length);
|
||||
// var context = ModelDiffContext.Create();
|
||||
// ModelDiffExtensions.MaterialEquals(context, vrmLibMaterial, loaded);
|
||||
// var diff = context.List
|
||||
// .Where(x => !s_ignoreKeys.Contains(x.Context))
|
||||
// .ToArray();
|
||||
// if (diff.Length > 0)
|
||||
// {
|
||||
// Debug.LogWarning(string.Join("\n", diff.Select(x => $"{x.Context}: {x.Message}")));
|
||||
// }
|
||||
// Assert.AreEqual(0, diff.Length);
|
||||
|
||||
// <= vrmlib
|
||||
var map = new Dictionary<VrmLib.Texture, Texture2D>();
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ac2c48555929afa47903182006ab82d2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib.Bvh
|
||||
{
|
||||
public class BvhException : Exception
|
||||
{
|
||||
public BvhException(string msg) : base(msg) { }
|
||||
}
|
||||
|
||||
public enum Channel
|
||||
{
|
||||
Xposition,
|
||||
Yposition,
|
||||
Zposition,
|
||||
Xrotation,
|
||||
Yrotation,
|
||||
Zrotation,
|
||||
}
|
||||
public static class ChannelExtensions
|
||||
{
|
||||
public static string ToProperty(this Channel ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case Channel.Xposition: return "localPosition.x";
|
||||
case Channel.Yposition: return "localPosition.y";
|
||||
case Channel.Zposition: return "localPosition.z";
|
||||
case Channel.Xrotation: return "localEulerAnglesBaked.x";
|
||||
case Channel.Yrotation: return "localEulerAnglesBaked.y";
|
||||
case Channel.Zrotation: return "localEulerAnglesBaked.z";
|
||||
}
|
||||
|
||||
throw new BvhException("no property for " + ch);
|
||||
}
|
||||
|
||||
public static bool IsLocation(this Channel ch)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case Channel.Xposition:
|
||||
case Channel.Yposition:
|
||||
case Channel.Zposition: return true;
|
||||
case Channel.Xrotation:
|
||||
case Channel.Yrotation:
|
||||
case Channel.Zrotation: return false;
|
||||
}
|
||||
|
||||
throw new BvhException("no property for " + ch);
|
||||
}
|
||||
}
|
||||
|
||||
public class EndSite : BvhNode
|
||||
{
|
||||
public EndSite() : base("")
|
||||
{
|
||||
}
|
||||
|
||||
public override void Parse(StringReader r)
|
||||
{
|
||||
r.ReadLine(); // offset
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelCurve
|
||||
{
|
||||
public float[] Keys
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ChannelCurve(int frameCount)
|
||||
{
|
||||
Keys = new float[frameCount];
|
||||
}
|
||||
|
||||
public void SetKey(int frame, float value)
|
||||
{
|
||||
Keys[frame] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class Bvh
|
||||
{
|
||||
public BvhNode Root
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public TimeSpan FrameTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public ChannelCurve[] Channels
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
int m_frames;
|
||||
public int FrameCount
|
||||
{
|
||||
get { return m_frames; }
|
||||
}
|
||||
|
||||
public struct PathWithProperty
|
||||
{
|
||||
public string Path;
|
||||
public string Property;
|
||||
public bool IsLocation;
|
||||
}
|
||||
|
||||
public bool TryGetPathWithPropertyFromChannel(ChannelCurve channel, out PathWithProperty pathWithProp)
|
||||
{
|
||||
var index = Channels.ToList().IndexOf(channel);
|
||||
if (index == -1)
|
||||
{
|
||||
pathWithProp = default(PathWithProperty);
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var node in Root.Traverse())
|
||||
{
|
||||
for (int i = 0; i < node.Channels.Length; ++i, --index)
|
||||
{
|
||||
if (index == 0)
|
||||
{
|
||||
pathWithProp = new PathWithProperty
|
||||
{
|
||||
Path = GetPath(node),
|
||||
Property = node.Channels[i].ToProperty(),
|
||||
IsLocation = node.Channels[i].IsLocation(),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new BvhException("channel is not found");
|
||||
}
|
||||
|
||||
public string GetPath(BvhNode node)
|
||||
{
|
||||
var list = new List<string>() { node.Name };
|
||||
|
||||
var current = node;
|
||||
while (current != null)
|
||||
{
|
||||
current = GetParent(current);
|
||||
if (current != null)
|
||||
{
|
||||
list.Insert(0, current.Name);
|
||||
}
|
||||
}
|
||||
|
||||
return String.Join("/", list.ToArray());
|
||||
}
|
||||
|
||||
BvhNode GetParent(BvhNode node)
|
||||
{
|
||||
foreach (var x in Root.Traverse())
|
||||
{
|
||||
if (x.Children.Contains(node))
|
||||
{
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ChannelCurve GetChannel(BvhNode target, Channel channel)
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var node in Root.Traverse())
|
||||
{
|
||||
for (int i = 0; i < node.Channels.Length; ++i, ++index)
|
||||
{
|
||||
if (node == target && node.Channels[i] == channel)
|
||||
{
|
||||
return Channels[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new BvhException("channel is not found");
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}nodes, {1}channels, {2}frames, {3:0.00}seconds"
|
||||
, Root.Traverse().Count()
|
||||
, Channels.Length
|
||||
, m_frames
|
||||
, m_frames * FrameTime.TotalSeconds);
|
||||
}
|
||||
|
||||
public Bvh(BvhNode root, int frames, float seconds)
|
||||
{
|
||||
Root = root;
|
||||
FrameTime = TimeSpan.FromSeconds(seconds);
|
||||
m_frames = frames;
|
||||
var channelCount = Root.Traverse()
|
||||
.Where(x => x.Channels != null)
|
||||
.Select(x => x.Channels.Length)
|
||||
.Sum();
|
||||
Channels = Enumerable.Range(0, channelCount)
|
||||
.Select(x => new ChannelCurve(frames))
|
||||
.ToArray()
|
||||
;
|
||||
}
|
||||
|
||||
public void ParseFrame(int frame, string line)
|
||||
{
|
||||
var splitted = line.Trim().Split().Where(x => !string.IsNullOrEmpty(x)).ToArray();
|
||||
if (splitted.Length != Channels.Length)
|
||||
{
|
||||
throw new BvhException("frame key count is not match channel count");
|
||||
}
|
||||
for (int i = 0; i < Channels.Length; ++i)
|
||||
{
|
||||
Channels[i].SetKey(frame, float.Parse(splitted[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class BvhParser
|
||||
{
|
||||
static BvhNode ParseNode(StringReader r, int level = 0)
|
||||
{
|
||||
var firstline = r.ReadLine().Trim();
|
||||
var splitted = firstline.Split();
|
||||
if (splitted.Length != 2)
|
||||
{
|
||||
if (splitted.Length == 1)
|
||||
{
|
||||
if (splitted[0] == "}")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
throw new BvhException(String.Format("splitted to {0}({1})", splitted.Length, firstline));
|
||||
}
|
||||
|
||||
BvhNode node = null;
|
||||
if (splitted[0] == "ROOT")
|
||||
{
|
||||
if (level != 0)
|
||||
{
|
||||
throw new BvhException("nested ROOT");
|
||||
}
|
||||
node = new BvhNode(splitted[1]);
|
||||
}
|
||||
else if (splitted[0] == "JOINT")
|
||||
{
|
||||
if (level == 0)
|
||||
{
|
||||
throw new BvhException("should ROOT, but JOINT");
|
||||
}
|
||||
node = new BvhNode(splitted[1]);
|
||||
}
|
||||
else if (splitted[0] == "End")
|
||||
{
|
||||
if (level == 0)
|
||||
{
|
||||
throw new BvhException("End in level 0");
|
||||
}
|
||||
node = new EndSite();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new BvhException("unknown type: " + splitted[0]);
|
||||
}
|
||||
|
||||
if (r.ReadLine().Trim() != "{")
|
||||
{
|
||||
throw new BvhException("'{' is not found");
|
||||
}
|
||||
|
||||
node.Parse(r);
|
||||
|
||||
// child nodes
|
||||
while (true)
|
||||
{
|
||||
var child = ParseNode(r, level + 1);
|
||||
if (child == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(child is EndSite))
|
||||
{
|
||||
node.AddChid(child);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public static Bvh FromPath(string path)
|
||||
{
|
||||
return Parse(File.ReadAllText(path));
|
||||
}
|
||||
|
||||
public static Bvh Parse(string src)
|
||||
{
|
||||
using (var r = new StringReader(src))
|
||||
{
|
||||
if (r.ReadLine() != "HIERARCHY")
|
||||
{
|
||||
throw new BvhException("not start with HIERARCHY");
|
||||
}
|
||||
|
||||
var root = ParseNode(r);
|
||||
if (root == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var frames = 0;
|
||||
var frameTime = 0.0f;
|
||||
if (r.ReadLine() == "MOTION")
|
||||
{
|
||||
var frameSplitted = r.ReadLine().Split(':');
|
||||
if (frameSplitted[0] != "Frames")
|
||||
{
|
||||
throw new BvhException("Frames is not found");
|
||||
}
|
||||
frames = int.Parse(frameSplitted[1]);
|
||||
|
||||
var frameTimeSplitted = r.ReadLine().Split(':');
|
||||
if (frameTimeSplitted[0] != "Frame Time")
|
||||
{
|
||||
throw new BvhException("Frame Time is not found");
|
||||
}
|
||||
frameTime = float.Parse(frameTimeSplitted[1]);
|
||||
}
|
||||
|
||||
var bvh = new Bvh(root, frames, frameTime);
|
||||
|
||||
for (int i = 0; i < frames; ++i)
|
||||
{
|
||||
var line = r.ReadLine();
|
||||
bvh.ParseFrame(i, line);
|
||||
}
|
||||
|
||||
bvh.Root.UpdatePosition(Vector3.Zero);
|
||||
|
||||
return bvh;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3876c0b36fb1fc54e9f7a5fc412967ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib.Bvh
|
||||
{
|
||||
public static class BvhAnimation
|
||||
{
|
||||
class CurveSet
|
||||
{
|
||||
BvhNode Node = default;
|
||||
Func<float, float, float, Quaternion> EulerToRotation = default;
|
||||
public CurveSet(BvhNode node)
|
||||
{
|
||||
Node = node;
|
||||
}
|
||||
|
||||
public ChannelCurve PositionX = default;
|
||||
public ChannelCurve PositionY = default;
|
||||
public ChannelCurve PositionZ = default;
|
||||
public Vector3 GetPosition(int i)
|
||||
{
|
||||
return new Vector3(
|
||||
PositionX.Keys[i],
|
||||
PositionY.Keys[i],
|
||||
PositionZ.Keys[i]);
|
||||
}
|
||||
|
||||
public ChannelCurve RotationX = default;
|
||||
public ChannelCurve RotationY = default;
|
||||
public ChannelCurve RotationZ = default;
|
||||
public Quaternion GetRotation(int i)
|
||||
{
|
||||
if (EulerToRotation == null)
|
||||
{
|
||||
EulerToRotation = Node.GetEulerToRotation();
|
||||
}
|
||||
return EulerToRotation(
|
||||
RotationX.Keys[i],
|
||||
RotationY.Keys[i],
|
||||
RotationZ.Keys[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 556644864ce505b43b0f967425a25c0c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib.Bvh
|
||||
{
|
||||
public static class BvhExtensions
|
||||
{
|
||||
public static Func<float, float, float, Quaternion> GetEulerToRotation(this BvhNode bvh)
|
||||
{
|
||||
var order = bvh.Channels.Where(x => x == Channel.Xrotation || x == Channel.Yrotation || x == Channel.Zrotation).ToArray();
|
||||
|
||||
return (x, y, z) =>
|
||||
{
|
||||
var xRot = Quaternion.CreateFromYawPitchRoll(x, 0, 0);
|
||||
var yRot = Quaternion.CreateFromYawPitchRoll(0, y, 0);
|
||||
var zRot = Quaternion.CreateFromYawPitchRoll(0, 0, z);
|
||||
|
||||
var r = Quaternion.Identity;
|
||||
foreach (var ch in order)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case Channel.Xrotation: r = r * xRot; break;
|
||||
case Channel.Yrotation: r = r * yRot; break;
|
||||
case Channel.Zrotation: r = r * zRot; break;
|
||||
default: throw new BvhException("no rotation");
|
||||
}
|
||||
}
|
||||
return r;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: be2bfe50d0adaf24185001d94f5ff8e0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib.Bvh
|
||||
{
|
||||
public class BvhNode
|
||||
{
|
||||
public String Name
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name}";
|
||||
}
|
||||
|
||||
public HumanoidBones Bone
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
// world position
|
||||
public Vector3 SkeletonLocalPosition
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public void UpdatePosition(Vector3 parentPosition)
|
||||
{
|
||||
SkeletonLocalPosition = parentPosition + Offset;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.UpdatePosition(SkeletonLocalPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Offset;
|
||||
|
||||
public Channel[] Channels
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
List<BvhNode> m_children = new List<BvhNode>();
|
||||
|
||||
|
||||
public IReadOnlyList<BvhNode> Children => m_children;
|
||||
|
||||
|
||||
public void AddChid(BvhNode child)
|
||||
{
|
||||
child.Parent = this;
|
||||
m_children.Add(child);
|
||||
}
|
||||
|
||||
|
||||
public BvhNode Parent
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public BvhNode(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public virtual void Parse(StringReader r)
|
||||
{
|
||||
Offset = ParseOffset(r.ReadLine());
|
||||
|
||||
Channels = ParseChannel(r.ReadLine());
|
||||
}
|
||||
|
||||
static Vector3 ParseOffset(string line)
|
||||
{
|
||||
var splited = line.Trim().Split();
|
||||
if (splited[0] != "OFFSET")
|
||||
{
|
||||
throw new BvhException("OFFSET is not found");
|
||||
}
|
||||
|
||||
var offset = splited.Skip(1).Where(x => !string.IsNullOrEmpty(x)).Select(x => float.Parse(x)).ToArray();
|
||||
return new Vector3(offset[0], offset[1], offset[2]);
|
||||
}
|
||||
|
||||
static Channel[] ParseChannel(string line)
|
||||
{
|
||||
var splited = line.Trim().Split();
|
||||
if (splited[0] != "CHANNELS")
|
||||
{
|
||||
throw new BvhException("CHANNELS is not found");
|
||||
}
|
||||
var count = int.Parse(splited[1]);
|
||||
if (count + 2 != splited.Length)
|
||||
{
|
||||
throw new BvhException("channel count is not match with splited count");
|
||||
}
|
||||
return splited.Skip(2).Select(x => (Channel)Enum.Parse(typeof(Channel), x)).ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<BvhNode> Traverse()
|
||||
{
|
||||
yield return this;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
foreach (var descentant in child.Traverse())
|
||||
{
|
||||
yield return descentant;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ac87eb7c7e9da9c458c0601587ed0bc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace VrmLib.Diff
|
||||
{
|
||||
static class ObjectExtensions
|
||||
{
|
||||
public static bool IsNull<T>(this T self)
|
||||
{
|
||||
if (typeof(T).IsClass)
|
||||
{
|
||||
return self == null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ModelDiff
|
||||
{
|
||||
public string Context;
|
||||
public string Message;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Context}: {Message}";
|
||||
}
|
||||
}
|
||||
|
||||
public struct ModelDiffContext
|
||||
{
|
||||
public readonly string Path;
|
||||
|
||||
public readonly List<ModelDiff> List;
|
||||
|
||||
ModelDiffContext(string path, List<ModelDiff> list)
|
||||
{
|
||||
Path = path;
|
||||
List = list;
|
||||
}
|
||||
|
||||
public bool Push<T>(T lhs, T rhs, Func<ModelDiffContext, T, T, bool> pred = null)
|
||||
{
|
||||
if (pred != null)
|
||||
{
|
||||
if (!pred(this, lhs, rhs))
|
||||
{
|
||||
List.Add(new ModelDiff
|
||||
{
|
||||
Context = Path,
|
||||
Message = $"{lhs} != {rhs}",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!RequireComapre(lhs, rhs, out bool equals))
|
||||
{
|
||||
return equals;
|
||||
}
|
||||
|
||||
if (lhs.Equals(rhs))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
List.Add(new ModelDiff
|
||||
{
|
||||
Context = Path,
|
||||
Message = $"{lhs} != {rhs}",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public ModelDiffContext Enter(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
return new ModelDiffContext(key, List);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ModelDiffContext(Path + "." + key, List);
|
||||
}
|
||||
}
|
||||
|
||||
public static ModelDiffContext Create()
|
||||
{
|
||||
return new ModelDiffContext("", new List<ModelDiff>());
|
||||
}
|
||||
|
||||
public bool RequireComapre(object lhs, object rhs, out bool equals)
|
||||
{
|
||||
if (lhs is null)
|
||||
{
|
||||
if (rhs is null)
|
||||
{
|
||||
equals = true;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
equals = false;
|
||||
List.Add(new ModelDiff
|
||||
{
|
||||
Context = Path,
|
||||
Message = "lhs is null"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rhs is null)
|
||||
{
|
||||
equals = false;
|
||||
List.Add(new ModelDiff
|
||||
{
|
||||
Context = Path,
|
||||
Message = "rhs is null"
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
equals = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1cef47aa218fe78498b3bc0e698b817a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,540 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using VrmLib.MToon;
|
||||
|
||||
namespace VrmLib.Diff
|
||||
{
|
||||
public static class ModelDiffExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 違うところを集める(debug用)
|
||||
/// </summary>
|
||||
public static List<ModelDiff> Diff(this Model lhs, Model rhs)
|
||||
{
|
||||
var context = ModelDiffContext.Create();
|
||||
context.Enter(nameof(lhs.AssetGenerator)).Push(lhs.AssetGenerator, rhs.AssetGenerator, StringEquals);
|
||||
context.Enter(nameof(lhs.AssetVersion)).Push(lhs.AssetVersion, rhs.AssetVersion, StringEquals);
|
||||
context.Enter(nameof(lhs.AssetMinVersion)).Push(lhs.AssetMinVersion, rhs.AssetMinVersion, StringEquals);
|
||||
context.Enter(nameof(lhs.AssetCopyright)).Push(lhs.AssetCopyright, rhs.AssetCopyright, StringEquals);
|
||||
|
||||
// Materialの参照で比較する
|
||||
ListDiff(context.Enter("Materials"), lhs.Materials, rhs.Materials, MaterialEquals);
|
||||
ListDiff(context.Enter("Meshes"), lhs.MeshGroups, rhs.MeshGroups, MeshGroupEquals);
|
||||
ListDiff(context.Enter("Nodes"), lhs.Nodes, rhs.Nodes, NodeEquals);
|
||||
ListDiff(context.Enter("Skins"), lhs.Skins, rhs.Skins, SkinEquals);
|
||||
Vrm(context.Enter("Vrm"), lhs, rhs);
|
||||
|
||||
return context.List;
|
||||
}
|
||||
|
||||
#region Private
|
||||
static bool ListDiff<T>(ModelDiffContext context, List<T> lhs, List<T> rhs, Func<ModelDiffContext, T, T, bool> pred, Func<T, int> order = null)
|
||||
{
|
||||
var equals = true;
|
||||
if (lhs.Count != rhs.Count)
|
||||
{
|
||||
equals = false;
|
||||
context.List.Add(new ModelDiff
|
||||
{
|
||||
Context = context.Path,
|
||||
Message = $"{lhs.Count} != {rhs.Count}",
|
||||
});
|
||||
}
|
||||
|
||||
var l = order != null ? lhs.OrderBy(order).GetEnumerator() : lhs.GetEnumerator();
|
||||
var r = order != null ? rhs.OrderBy(order).GetEnumerator() : rhs.GetEnumerator();
|
||||
for (int i = 0; i < lhs.Count; ++i)
|
||||
{
|
||||
l.MoveNext();
|
||||
r.MoveNext();
|
||||
if (!pred(context.Enter($"{i}"), l.Current, r.Current))
|
||||
equals = false;
|
||||
}
|
||||
return equals;
|
||||
}
|
||||
|
||||
const float EPSILON = 1e-5f;
|
||||
|
||||
static bool Vector3NearlyEquals(ModelDiffContext _, Vector3 l, Vector3 r)
|
||||
{
|
||||
if (Math.Abs(l.X - r.X) > EPSILON) return false;
|
||||
if (Math.Abs(l.Y - r.Y) > EPSILON) return false;
|
||||
if (Math.Abs(l.Z - r.Z) > EPSILON) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool QuaternionNearlyEquals(ModelDiffContext _, Quaternion l, Quaternion r)
|
||||
{
|
||||
if (Math.Abs(l.X - r.X) > EPSILON) return false;
|
||||
if (Math.Abs(l.Y - r.Y) > EPSILON) return false;
|
||||
if (Math.Abs(l.Z - r.Z) > EPSILON) return false;
|
||||
if (Math.Abs(l.W - r.W) > EPSILON) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool StringEquals(ModelDiffContext _, string l, string r)
|
||||
{
|
||||
if (string.IsNullOrEmpty(l))
|
||||
{
|
||||
return string.IsNullOrEmpty(r);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(r))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return l == r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ImageBytesEquals(ModelDiffContext context, Image lhs, Image rhs)
|
||||
{
|
||||
if (lhs is null)
|
||||
{
|
||||
if (rhs is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (rhs is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return lhs.Bytes.SequenceEqual(rhs.Bytes);
|
||||
}
|
||||
|
||||
static void Image(ModelDiffContext context, Image lhs, Image rhs)
|
||||
{
|
||||
context.Enter($"{lhs.Name}:{rhs.Name}").Push(lhs, rhs, ImageBytesEquals);
|
||||
}
|
||||
|
||||
static bool TextureInfoEquals(ModelDiffContext context, TextureInfo lhs, TextureInfo rhs)
|
||||
{
|
||||
if (!context.RequireComapre(lhs, rhs, out bool equals))
|
||||
{
|
||||
return equals;
|
||||
}
|
||||
|
||||
if (lhs.Offset != rhs.Offset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (lhs.Scaling != rhs.Scaling)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return TextureEquals(context.Enter("Texture"), lhs.Texture, rhs.Texture);
|
||||
}
|
||||
|
||||
static bool TextureEquals(ModelDiffContext context, Texture lhs, Texture rhs)
|
||||
{
|
||||
if (!context.RequireComapre(lhs, rhs, out bool equals))
|
||||
{
|
||||
if (!equals)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
equals = true;
|
||||
if (!context.Enter("Name").Push(lhs.Name, rhs.Name, StringEquals))
|
||||
equals = false;
|
||||
if (!context.Enter("MagFilter").Push(lhs.Sampler.MagFilter, rhs.Sampler.MagFilter))
|
||||
equals = false;
|
||||
if (!context.Enter("MinFilter").Push(lhs.Sampler.MinFilter, rhs.Sampler.MinFilter))
|
||||
equals = false;
|
||||
if (!context.Enter("WrapS").Push(lhs.Sampler.WrapS, rhs.Sampler.WrapS))
|
||||
equals = false;
|
||||
if (!context.Enter("WrapT").Push(lhs.Sampler.WrapT, rhs.Sampler.WrapT))
|
||||
equals = false;
|
||||
if (lhs is ImageTexture l && rhs is ImageTexture r)
|
||||
{
|
||||
if (!ImageBytesEquals(context, l.Image, r.Image))
|
||||
equals = false;
|
||||
return equals;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void Texture(ModelDiffContext context, Texture lhs, Texture rhs)
|
||||
{
|
||||
context.Enter($"{lhs.Name}:{rhs.Name}").Push(lhs, rhs, TextureEquals);
|
||||
}
|
||||
|
||||
static bool BaseMaterialEquals(ModelDiffContext context, Material lhs, Material rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter(nameof(lhs.AlphaCutoff)).Push(lhs.AlphaCutoff, rhs.AlphaCutoff)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.AlphaMode)).Push(lhs.AlphaMode, rhs.AlphaMode)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.BaseColorFactor)).Push(lhs.BaseColorFactor, rhs.BaseColorFactor)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.BaseColorTexture)).Push(lhs.BaseColorTexture?.Texture, rhs.BaseColorTexture?.Texture, TextureEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.DoubleSided)).Push(lhs.DoubleSided, rhs.DoubleSided)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool PBRMaterialEquals(ModelDiffContext context, PBRMaterial lhs, PBRMaterial rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!BaseMaterialEquals(context, lhs, rhs)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.EmissiveFactor)).Push(lhs.EmissiveFactor, rhs.EmissiveFactor)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.EmissiveTexture)).Push(lhs.EmissiveTexture, rhs.EmissiveTexture, TextureEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.MetallicFactor)).Push(lhs.MetallicFactor, rhs.MetallicFactor)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.MetallicRoughnessTexture)).Push(lhs.MetallicRoughnessTexture, rhs.MetallicRoughnessTexture, TextureEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.NormalTexture)).Push(lhs.NormalTexture, rhs.NormalTexture, TextureEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.OcclusionTexture)).Push(lhs.OcclusionTexture, rhs.OcclusionTexture, TextureEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.RoughnessFactor)).Push(lhs.RoughnessFactor, rhs.RoughnessFactor)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool MToonDefinitionEquals(ModelDiffContext context, object lhs, object rhs, Type t)
|
||||
{
|
||||
if (!context.RequireComapre(lhs, rhs, out bool equals))
|
||||
{
|
||||
return equals;
|
||||
}
|
||||
|
||||
equals = true;
|
||||
foreach (var fi in t.GetFields())
|
||||
{
|
||||
if (fi.FieldType == typeof(TextureInfo))
|
||||
{
|
||||
if (!context.Enter(fi.Name).Push(fi.GetValue(lhs) as TextureInfo, fi.GetValue(rhs) as TextureInfo, TextureInfoEquals))
|
||||
equals = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!context.Enter(fi.Name).Push(fi.GetValue(lhs), fi.GetValue(rhs)))
|
||||
equals = false;
|
||||
}
|
||||
}
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool MToonDefinitionEquals(ModelDiffContext context, MToonDefinition lhs, MToonDefinition rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(MetaDefinition)), lhs?.Meta, rhs?.Meta, typeof(MetaDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(ColorDefinition)), lhs?.Color, rhs?.Color, typeof(ColorDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(OutlineDefinition)), lhs?.Outline, rhs?.Outline, typeof(OutlineDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(LightingInfluenceDefinition)), lhs?.Lighting.LightingInfluence, rhs?.Lighting.LightingInfluence, typeof(LightingInfluenceDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(LitAndShadeMixingDefinition)), lhs?.Lighting.LitAndShadeMixing, rhs?.Lighting.LitAndShadeMixing, typeof(LitAndShadeMixingDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(EmissionDefinition)), lhs?.Emission, rhs?.Emission, typeof(EmissionDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(MatCapDefinition)), lhs?.MatCap, rhs?.MatCap, typeof(MatCapDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(RimDefinition)), lhs?.Rim, rhs?.Rim, typeof(RimDefinition)))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(TextureUvCoordsDefinition)), lhs?.TextureOption, rhs?.TextureOption, typeof(TextureUvCoordsDefinition)))
|
||||
equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool MToonMaterialEquals(ModelDiffContext context, MToonMaterial lhs, MToonMaterial rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!BaseMaterialEquals(context, lhs, rhs))
|
||||
equals = false;
|
||||
if (!context.Enter(nameof(lhs._DebugMode)).Push(lhs._DebugMode, rhs._DebugMode))
|
||||
equals = false;
|
||||
if (!context.Enter(nameof(lhs._DstBlend)).Push(lhs._DstBlend, rhs._DstBlend))
|
||||
equals = false;
|
||||
if (!context.Enter(nameof(lhs._SrcBlend)).Push(lhs._SrcBlend, rhs._SrcBlend))
|
||||
equals = false;
|
||||
if (!context.Enter(nameof(lhs._ZWrite)).Push(lhs._ZWrite, rhs._ZWrite))
|
||||
equals = false;
|
||||
if (!MToonDefinitionEquals(context.Enter(nameof(lhs.Definition)), lhs.Definition, rhs.Definition))
|
||||
equals = false;
|
||||
// context.Enter(nameof(lhs.KeyWords)).Push( lhs.KeyWords, rhs.KeyWords);
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool UnlitMaterialEquals(ModelDiffContext context, UnlitMaterial lhs, UnlitMaterial rhs)
|
||||
{
|
||||
return BaseMaterialEquals(context, lhs, rhs);
|
||||
}
|
||||
|
||||
public static bool MaterialEquals(ModelDiffContext context, Material l, Material r)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Name").Push(l.Name, r.Name, StringEquals))
|
||||
equals = false;
|
||||
|
||||
// context.Enter($"{i}:{lhs[i].Name}:{rhs[i].Name}").Push( lhs[i], rhs[i], MaterialEquals);
|
||||
if (l.GetType() != r.GetType())
|
||||
{
|
||||
context.Enter($"Type").Push(l.GetType(), r.GetType());
|
||||
equals = false;
|
||||
}
|
||||
else if (l is PBRMaterial lp && r is PBRMaterial rp)
|
||||
{
|
||||
if (!PBRMaterialEquals(context.Enter($"(PBRMaterial)"), lp, rp))
|
||||
equals = false;
|
||||
}
|
||||
else if (l is MToonMaterial lm && r is MToonMaterial rm)
|
||||
{
|
||||
if (!MToonMaterialEquals(context.Enter($"(MToonMaterial)"), lm, rm))
|
||||
equals = false;
|
||||
}
|
||||
else if (l is UnlitMaterial lu && r is UnlitMaterial ru)
|
||||
{
|
||||
if (!UnlitMaterialEquals(context.Enter($"(UnlitMaterial)"), lu, ru))
|
||||
equals = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool AccessorEquals(ModelDiffContext context, BufferAccessor lhs, BufferAccessor rhs)
|
||||
{
|
||||
if (!context.RequireComapre(lhs, rhs, out bool equals))
|
||||
{
|
||||
return equals;
|
||||
}
|
||||
|
||||
return lhs.Bytes.SequenceEqual(rhs.Bytes);
|
||||
}
|
||||
|
||||
static bool VertexBufferEquals(ModelDiffContext context, VertexBuffer lhs, VertexBuffer rhs)
|
||||
{
|
||||
var equals = true;
|
||||
foreach (var kv in lhs)
|
||||
{
|
||||
rhs.TryGetValue(kv.Key, out BufferAccessor accessor);
|
||||
if (!context.Enter(kv.Key).Push(kv.Value, accessor, AccessorEquals)) equals = false;
|
||||
}
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool MeshEquals(ModelDiffContext context, Mesh lhs, Mesh rhs)
|
||||
{
|
||||
return VertexBufferEquals(context.Enter(nameof(lhs.VertexBuffer)), lhs.VertexBuffer, rhs.VertexBuffer);
|
||||
}
|
||||
|
||||
static bool MeshGroupEquals(ModelDiffContext context, MeshGroup lhs, MeshGroup rhs)
|
||||
{
|
||||
return ListDiff(context.Enter("Meshes"), lhs.Meshes, rhs.Meshes, MeshEquals);
|
||||
}
|
||||
|
||||
static bool NodeEquals(ModelDiffContext context, Node lhs, Node rhs)
|
||||
{
|
||||
if (lhs is null)
|
||||
{
|
||||
if (rhs is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rhs is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var equals = true;
|
||||
if (!context.Enter(nameof(lhs.Name)).Push(lhs.Name, rhs.Name, StringEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.LocalTranslation)).Push(lhs.LocalTranslation, rhs.LocalTranslation, Vector3NearlyEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.LocalRotation)).Push(lhs.LocalRotation, rhs.LocalRotation, QuaternionNearlyEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.LocalScaling)).Push(lhs.LocalScaling, rhs.LocalScaling, Vector3NearlyEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.Parent)).Push(lhs.Parent?.Name, rhs.Parent?.Name)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.HumanoidBone)).Push(lhs.HumanoidBone, rhs.HumanoidBone)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool SkinEquals(ModelDiffContext context, Skin lhs, Skin rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Root").Push(lhs.Root, rhs.Root, NodeEquals)) equals = false;
|
||||
if (!ListDiff(context.Enter("Joints"), lhs.Joints, rhs.Joints, NodeEquals)) equals = false;
|
||||
if (!context.Enter("InverseMatrices").Push(lhs.InverseMatrices, rhs.InverseMatrices, AccessorEquals)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static void Vrm(ModelDiffContext context, Model lhs, Model rhs)
|
||||
{
|
||||
context.Enter(nameof(lhs.Vrm.SpecVersion)).Push(lhs.Vrm.SpecVersion, rhs.Vrm.SpecVersion);
|
||||
context.Enter(nameof(lhs.Vrm.ExporterVersion)).Push(lhs.Vrm.ExporterVersion, rhs.Vrm.ExporterVersion);
|
||||
VrmMeta(context.Enter(nameof(lhs.Vrm.Meta)), lhs.Vrm.Meta, rhs.Vrm.Meta);
|
||||
ListDiff(context.Enter(nameof(lhs.Vrm.ExpressionManager)), lhs.Vrm.ExpressionManager.ExpressionList, rhs.Vrm.ExpressionManager.ExpressionList, VrmExpressionEquals, x => (int)x.Preset);
|
||||
VrmFirstPerson(context.Enter(nameof(lhs.Vrm.FirstPerson)), lhs.Vrm.FirstPerson, rhs.Vrm.FirstPerson);
|
||||
VrmLookAt(context.Enter(nameof(lhs.Vrm.LookAt)), lhs.Vrm.LookAt, rhs.Vrm.LookAt);
|
||||
ListDiff(context.Enter("SpringBone.Springs"), lhs.Vrm.SpringBone.Springs, rhs.Vrm.SpringBone.Springs, VrmSpringBoneEquals);
|
||||
}
|
||||
|
||||
static void VrmMeta(ModelDiffContext context, Meta lhs, Meta rhs)
|
||||
{
|
||||
context.Enter(nameof(lhs.Name)).Push(lhs.Name, rhs.Name);
|
||||
context.Enter(nameof(lhs.Version)).Push(lhs.Version, rhs.Version);
|
||||
context.Enter(nameof(lhs.CopyrightInformation)).Push(lhs.CopyrightInformation, rhs.CopyrightInformation);
|
||||
context.Enter(nameof(lhs.Author)).Push(lhs.Author, rhs.Author);
|
||||
context.Enter(nameof(lhs.ContactInformation)).Push(lhs.ContactInformation, rhs.ContactInformation);
|
||||
context.Enter(nameof(lhs.Reference)).Push(lhs.Reference, rhs.Reference);
|
||||
context.Enter(nameof(lhs.Thumbnail)).Push(lhs.Thumbnail, rhs.Thumbnail, ImageBytesEquals);
|
||||
// AvatarPermission
|
||||
context.Enter(nameof(lhs.AvatarPermission.AvatarUsage)).Push(lhs.AvatarPermission.AvatarUsage, rhs.AvatarPermission.AvatarUsage);
|
||||
context.Enter(nameof(lhs.AvatarPermission.IsAllowedViolentUsage)).Push(lhs.AvatarPermission.IsAllowedViolentUsage, rhs.AvatarPermission.IsAllowedViolentUsage);
|
||||
context.Enter(nameof(lhs.AvatarPermission.IsAllowedSexualUsage)).Push(lhs.AvatarPermission.IsAllowedSexualUsage, rhs.AvatarPermission.IsAllowedSexualUsage);
|
||||
context.Enter(nameof(lhs.AvatarPermission.IsAllowedCommercialUsage)).Push(lhs.AvatarPermission.IsAllowedCommercialUsage, rhs.AvatarPermission.IsAllowedCommercialUsage);
|
||||
context.Enter(nameof(lhs.AvatarPermission.CommercialUsage)).Push(lhs.AvatarPermission.CommercialUsage, rhs.AvatarPermission.CommercialUsage);
|
||||
context.Enter(nameof(lhs.AvatarPermission.IsAllowedCommercialUsage)).Push(lhs.AvatarPermission.IsAllowedCommercialUsage, rhs.AvatarPermission.IsAllowedCommercialUsage);
|
||||
context.Enter(nameof(lhs.AvatarPermission.IsAllowedCommercialUsage)).Push(lhs.AvatarPermission.IsAllowedCommercialUsage, rhs.AvatarPermission.IsAllowedCommercialUsage);
|
||||
context.Enter(nameof(lhs.AvatarPermission.OtherPermissionUrl)).Push(lhs.AvatarPermission.OtherPermissionUrl, rhs.AvatarPermission.OtherPermissionUrl);
|
||||
// RedistributionLicense
|
||||
context.Enter(nameof(lhs.RedistributionLicense.License)).Push(lhs.RedistributionLicense.License, rhs.RedistributionLicense.License);
|
||||
context.Enter(nameof(lhs.RedistributionLicense.OtherLicenseUrl)).Push(lhs.RedistributionLicense.OtherLicenseUrl, rhs.RedistributionLicense.OtherLicenseUrl);
|
||||
}
|
||||
|
||||
static bool VrmExpressionEquals(ModelDiffContext context, Expression lhs, Expression rhs)
|
||||
{
|
||||
if (lhs.IsNull())
|
||||
{
|
||||
if (rhs.IsNull())
|
||||
{
|
||||
// ok
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.List.Add(new ModelDiff
|
||||
{
|
||||
Context = context.Path,
|
||||
Message = "lhs is null",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (rhs.IsNull())
|
||||
{
|
||||
context.List.Add(new ModelDiff
|
||||
{
|
||||
Context = context.Path,
|
||||
Message = "rhs is null",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var equals = true;
|
||||
if (!context.Enter(nameof(lhs.Preset)).Push(lhs.Preset, rhs.Preset)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.Name)).Push(lhs.Name, rhs.Name, StringEquals)) equals = false;
|
||||
if (!context.Enter(nameof(lhs.IsBinary)).Push(lhs.IsBinary, rhs.IsBinary)) equals = false;
|
||||
if (!ListDiff(context.Enter(nameof(lhs.MorphTargetBinds)), lhs.MorphTargetBinds, rhs.MorphTargetBinds, VrmExpressionBindValueEquals)) equals = false;
|
||||
if (!ListDiff(context.Enter(nameof(lhs.MaterialColorBinds)), lhs.MaterialColorBinds, rhs.MaterialColorBinds, VrmMaterialBindValueEquals)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool VrmExpressionBindValueEquals(ModelDiffContext context, MorphTargetBind lhs, MorphTargetBind rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Node").Push(lhs.Node, rhs.Node, NodeEquals)) equals = false;
|
||||
if (!context.Enter("Name").Push(lhs.Name, rhs.Name)) equals = false;
|
||||
if (!context.Enter("Value").Push(lhs.Value, rhs.Value)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool VrmMaterialBindValueEquals(ModelDiffContext context, MaterialColorBind lhs, MaterialColorBind rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Material.Name").Push(lhs.Material.Name, rhs.Material.Name)) equals = false;
|
||||
if (!context.Enter("Property").Push(lhs.Property, rhs.Property)) equals = false;
|
||||
// if (!context.Enter("Value").Push(lhs.m_value, rhs.m_value)) equals = false;
|
||||
if (!context.Enter("BindType").Push(lhs.BindType, rhs.BindType)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool FirstPersonMeshAnnotationEquals(ModelDiffContext context, FirstPersonMeshAnnotation lhs, FirstPersonMeshAnnotation rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Node").Push(lhs.Node, rhs.Node, NodeEquals)) equals = false;
|
||||
if (!context.Enter("Flag").Push(lhs.FirstPersonFlag, rhs.FirstPersonFlag)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static void VrmFirstPerson(ModelDiffContext context, FirstPerson lhs, FirstPerson rhs)
|
||||
{
|
||||
// context.Enter("HeadNode").Push(lhs.m_fp, rhs.m_fp, NodeEquals);
|
||||
ListDiff(context.Enter("Annotations"), lhs.Annotations, rhs.Annotations, FirstPersonMeshAnnotationEquals);
|
||||
}
|
||||
|
||||
static void VrmLookAt(ModelDiffContext context, LookAt lhs, LookAt rhs)
|
||||
{
|
||||
context.Enter("Offset").Push(lhs.OffsetFromHeadBone, rhs.OffsetFromHeadBone, Vector3NearlyEquals);
|
||||
context.Enter(nameof(lhs.LookAtType)).Push(lhs.LookAtType, rhs.LookAtType);
|
||||
VrmLookAtRangeMap(context.Enter(nameof(lhs.HorizontalInner)), lhs.HorizontalInner, rhs.HorizontalInner);
|
||||
VrmLookAtRangeMap(context.Enter(nameof(lhs.HorizontalOuter)), lhs.HorizontalOuter, rhs.HorizontalOuter);
|
||||
VrmLookAtRangeMap(context.Enter(nameof(lhs.VerticalUp)), lhs.VerticalUp, rhs.VerticalUp);
|
||||
VrmLookAtRangeMap(context.Enter(nameof(lhs.VerticalDown)), lhs.VerticalDown, rhs.VerticalDown);
|
||||
}
|
||||
|
||||
static void VrmLookAtRangeMap(ModelDiffContext context, LookAtRangeMap lhs, LookAtRangeMap rhs)
|
||||
{
|
||||
context.Enter(nameof(lhs.InputMaxValue)).Push(lhs.InputMaxValue, rhs.InputMaxValue);
|
||||
context.Enter(nameof(lhs.OutputScaling)).Push(lhs.OutputScaling, rhs.OutputScaling);
|
||||
context.Enter("Curve").Push(lhs.Curve, rhs.Curve);
|
||||
}
|
||||
|
||||
static bool VrmSpringBoneJointEquals(ModelDiffContext context, SpringJoint lhs, SpringJoint rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("DragForce").Push(lhs.DragForce, rhs.DragForce)) equals = false;
|
||||
if (!context.Enter("GravityDir").Push(lhs.GravityDir, rhs.GravityDir)) equals = false;
|
||||
if (!context.Enter("GravityPower").Push(lhs.GravityPower, rhs.GravityPower)) equals = false;
|
||||
if (!context.Enter("HitRadius").Push(lhs.HitRadius, rhs.HitRadius)) equals = false;
|
||||
if (!context.Enter("Stiffness").Push(lhs.Stiffness, rhs.Stiffness)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool VrmSpringBoneEquals(ModelDiffContext context, SpringBone lhs, SpringBone rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Comment").Push(lhs.Comment, rhs.Comment)) equals = false;
|
||||
if (!context.Enter("Origin").Push(lhs.Origin, rhs.Origin)) equals = false;
|
||||
if (!ListDiff(context.Enter("Joint"), lhs.Joints, rhs.Joints, VrmSpringBoneJointEquals)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool VrmSpringBoneColliderEquals(ModelDiffContext context, VrmSpringBoneCollider lhs, VrmSpringBoneCollider rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Offset").Push(lhs.Offset, rhs.Offset)) equals = false;
|
||||
if (!context.Enter("Radius").Push(lhs.Radius, rhs.Radius)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
|
||||
static bool VrmSpringBoneColliderEquals(ModelDiffContext context, SpringBoneColliderGroup lhs, SpringBoneColliderGroup rhs)
|
||||
{
|
||||
var equals = true;
|
||||
if (!context.Enter("Node").Push(lhs.Node, rhs.Node, NodeEquals)) equals = false;
|
||||
if (!ListDiff(context.Enter("Colliders"), lhs.Colliders, rhs.Colliders, VrmSpringBoneColliderEquals)) equals = false;
|
||||
return equals;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 93cf70a1afdd7784e8c964726d98edd6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,303 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 13b0cacd23e61e1468b9b9891e0e6656
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class ModelExtensionsForHumanoid
|
||||
{
|
||||
static ValueTuple<Node, Node> GetUpperLower(Node root)
|
||||
{
|
||||
var legL = root.Traverse().FirstOrDefault(x => x.HumanoidBone == HumanoidBones.leftUpperLeg);
|
||||
var legR = root.Traverse().FirstOrDefault(x => x.HumanoidBone == HumanoidBones.rightUpperLeg);
|
||||
var head = root.Traverse().FirstOrDefault(x => x.HumanoidBone == HumanoidBones.head);
|
||||
|
||||
var parentL = legL.Parent;
|
||||
var parentR = legR.Parent;
|
||||
if (parentL != parentR)
|
||||
{
|
||||
throw new Exception("different leftLeg parent and rightLeg parent");
|
||||
}
|
||||
var lower = parentL;
|
||||
|
||||
var upperAncestors = head.Ancestors().ToList();
|
||||
if (upperAncestors.Any(x => x == lower))
|
||||
{
|
||||
throw new Exception("lower is ancestor of head");
|
||||
}
|
||||
|
||||
var lowerAncestors = legL.Ancestors().ToList();
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (upperAncestors.Last() != lowerAncestors.Last())
|
||||
{
|
||||
break;
|
||||
}
|
||||
upperAncestors.RemoveAt(upperAncestors.Count - 1);
|
||||
lowerAncestors.RemoveAt(lowerAncestors.Count - 1);
|
||||
}
|
||||
|
||||
return (upperAncestors.Last(), lowerAncestors.Last());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// root
|
||||
/// upper
|
||||
/// lower
|
||||
/// legL
|
||||
/// legR
|
||||
///
|
||||
/// ↓
|
||||
///
|
||||
/// ①上半身をchestにする例
|
||||
///
|
||||
/// root(hips)
|
||||
/// legL
|
||||
/// legR
|
||||
/// lower(spine: 上下反転するため頭の位置が変わる)
|
||||
/// upper(chest)
|
||||
///
|
||||
/// ②上半身をspineにする例もありえる。その場合は、下半身とその親を近接させて
|
||||
/// 下半身にはhumanoidボーンを割り当てない(hipsとみなす)
|
||||
///
|
||||
/// root(hips)
|
||||
/// legL
|
||||
/// legR
|
||||
/// lower(上下反転するため頭の位置が変わる)
|
||||
/// upper(spine)
|
||||
///
|
||||
/// ③もしくは下半身をhipsに繰り上げる
|
||||
///
|
||||
/// lower(hips: 上下反転するため頭の位置が変わる)
|
||||
/// legL
|
||||
/// legR
|
||||
/// upper(spine)
|
||||
///
|
||||
/// </summary>
|
||||
public static string FixInvertedPelvis(this Model model)
|
||||
{
|
||||
var (upper, lower) = GetUpperLower(model.Root);
|
||||
if (upper == null)
|
||||
{
|
||||
return "FixInvertedPelvis: upper not found. this is not humanoid ? do nothing";
|
||||
}
|
||||
if (lower == null)
|
||||
{
|
||||
return "FixInvertedPelvis: lower not found. this is model's pelvis is not inverted. do nothing";
|
||||
}
|
||||
|
||||
// found lower. fix inverted pelvis...
|
||||
|
||||
var hips = model.Root.FindBone(HumanoidBones.hips);
|
||||
{
|
||||
hips.HumanoidBone = null;
|
||||
}
|
||||
var spine = model.Root.FindBone(HumanoidBones.spine);
|
||||
{
|
||||
spine.HumanoidBone = null;
|
||||
}
|
||||
var chest = model.Root.FindBone(HumanoidBones.chest);
|
||||
if (chest != null)
|
||||
{
|
||||
chest.HumanoidBone = null;
|
||||
}
|
||||
var legL = model.Root.FindBone(HumanoidBones.leftUpperLeg);
|
||||
var legR = model.Root.FindBone(HumanoidBones.rightUpperLeg);
|
||||
|
||||
// [chest]
|
||||
// 上半身を下半身の子にしてchestとなす
|
||||
lower.Add(upper);
|
||||
upper.HumanoidBone = HumanoidBones.chest;
|
||||
|
||||
// [hips]
|
||||
// lowerの親をhipsとして両足の間に配置する
|
||||
var newHips = lower.Parent;
|
||||
newHips.Translation = new Vector3(0, legL.Translation.Y, legL.Translation.Z);
|
||||
newHips.HumanoidBone = HumanoidBones.hips;
|
||||
|
||||
// [spine]
|
||||
// 下半身をspineとして
|
||||
lower.HumanoidBone = HumanoidBones.spine;
|
||||
// hips と chest の中間に配置する
|
||||
lower.Translation = (upper.Translation + hips.Translation) * 0.5f;
|
||||
|
||||
// [legs]
|
||||
// 足の親を下半身からrootに変える
|
||||
hips.Add(legL);
|
||||
hips.Add(legR);
|
||||
|
||||
return $"FixInvertedPelvis: lower: {lower.Name}";
|
||||
}
|
||||
|
||||
static void StringBuilder(System.Text.StringBuilder sb, Node n, string indent = "")
|
||||
{
|
||||
sb.Append($"{indent}{n}\n");
|
||||
|
||||
foreach (var child in n.Children)
|
||||
{
|
||||
StringBuilder(sb, child, indent + " ");
|
||||
}
|
||||
}
|
||||
|
||||
public static string HumanoidBoneEstimate(this Model model)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.Append("HumanoidBoneEstimate: ");
|
||||
|
||||
// estimate skeleton
|
||||
var skeleton = SkeletonEstimator.Detect(model.Root);
|
||||
if (skeleton == null)
|
||||
{
|
||||
return "fail to estimate skeleton";
|
||||
}
|
||||
|
||||
// rename bone
|
||||
foreach (var kv in skeleton)
|
||||
{
|
||||
kv.Value.Name = kv.Key.ToString();
|
||||
}
|
||||
|
||||
if (model.Vrm == null)
|
||||
{
|
||||
sb.Append("add vrm humanoid");
|
||||
model.Vrm = new Vrm(new Meta
|
||||
{
|
||||
}, "UniVRM-0.51.0", "0.0");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
StringBuilder(sb, skeleton[HumanoidBones.hips]);
|
||||
|
||||
foreach (var skin in model.Skins)
|
||||
{
|
||||
if (skin.Root == null)
|
||||
{
|
||||
skin.Root = (Node)skeleton[HumanoidBones.hips].Parent;
|
||||
sb.Append($"{skin}: set {skin.Root}\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append($"{skin}: {skin.Root}\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString(); ;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e290d6e2d1cfe684488ad5504f0ce8dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
using System.Linq;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class ModelExtensionsForSingleMesh
|
||||
{
|
||||
///
|
||||
/// 各ノードのスキニングで使用されている回数
|
||||
///
|
||||
public static int[] GetNodeSkinUseCount(Model model)
|
||||
{
|
||||
// create new skin
|
||||
var useCountList = new int[model.Nodes.Count];
|
||||
foreach (var n in model.Root.Traverse().Skip(1))
|
||||
{
|
||||
var g = n.MeshGroup;
|
||||
if (g == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g.Skin == null)
|
||||
{
|
||||
// Skin無し。そのMeshに乗る
|
||||
var index = model.Nodes.IndexOf(n);
|
||||
++useCountList[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skinあり。VertexBufferの JOINT_0 と WEIGHT_0 を見る
|
||||
var skinJoints = g.Skin.Joints;
|
||||
foreach (var m in g.Meshes)
|
||||
{
|
||||
var joints = m.VertexBuffer.GetOrCreateJoints();
|
||||
var weights = m.VertexBuffer.GetOrCreateWeights();
|
||||
for (int i = 0; i < joints.Length; ++i)
|
||||
{
|
||||
var j = joints[i];
|
||||
var w = weights[i];
|
||||
if (w.X > 0)
|
||||
{
|
||||
var node = skinJoints[j.Joint0];
|
||||
var index = model.Nodes.IndexOf(node);
|
||||
++useCountList[index];
|
||||
}
|
||||
if (w.Y > 0)
|
||||
{
|
||||
var node = skinJoints[j.Joint1];
|
||||
var index = model.Nodes.IndexOf(node);
|
||||
++useCountList[index];
|
||||
}
|
||||
if (w.Z > 0)
|
||||
{
|
||||
var node = skinJoints[j.Joint2];
|
||||
var index = model.Nodes.IndexOf(node);
|
||||
++useCountList[index];
|
||||
}
|
||||
if (w.W > 0)
|
||||
{
|
||||
var node = skinJoints[j.Joint3];
|
||||
var index = model.Nodes.IndexOf(node);
|
||||
++useCountList[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return useCountList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Integrate meshes to a single mesh
|
||||
/// </summary>
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public static MeshGroup CreateSingleMesh(this Model model, string name)
|
||||
{
|
||||
// new mesh to store result
|
||||
var meshGroup = new MeshGroup(name);
|
||||
var mesh = new Mesh
|
||||
{
|
||||
VertexBuffer = new VertexBuffer()
|
||||
};
|
||||
meshGroup.Meshes.Add(mesh);
|
||||
|
||||
var useCountList = GetNodeSkinUseCount(model);
|
||||
|
||||
// new Skin.
|
||||
// Joints has include all joint
|
||||
meshGroup.Skin = new Skin();
|
||||
for (int i = 0; i < useCountList.Length; ++i)
|
||||
{
|
||||
if (useCountList[i] > 0)
|
||||
{
|
||||
// add joint that has bone weight
|
||||
meshGroup.Skin.Joints.Add(model.Nodes[i]);
|
||||
}
|
||||
}
|
||||
model.Skins.Clear();
|
||||
model.Skins.Add(meshGroup.Skin);
|
||||
|
||||
// concatenate all mesh
|
||||
foreach (var node in model.Root.Traverse().Skip(1))
|
||||
{
|
||||
var g = node.MeshGroup;
|
||||
if (g != null)
|
||||
{
|
||||
foreach (var m in g.Meshes)
|
||||
{
|
||||
if (g.Skin != null && m.VertexBuffer.Joints != null && m.VertexBuffer.Weights != null)
|
||||
{
|
||||
var jointIndexMap = g.Skin.Joints.Select(x => meshGroup.Skin.Joints.IndexOf(x)).ToArray();
|
||||
mesh.Append(m.VertexBuffer, m.IndexBuffer, m.Submeshes, m.MorphTargets, jointIndexMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rootIndex = meshGroup.Skin.Joints.IndexOf(node);
|
||||
mesh.Append(m.VertexBuffer, m.IndexBuffer, m.Submeshes, m.MorphTargets, null, rootIndex, node.Matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var target in mesh.MorphTargets)
|
||||
{
|
||||
target.VertexBuffer.Resize(mesh.VertexBuffer.Count);
|
||||
}
|
||||
|
||||
return meshGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ff27373084460f24b8866d4853a9acda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class ModelExtensionsForValidation
|
||||
{
|
||||
public static void Validate(this Model model, Node node, string message)
|
||||
{
|
||||
if (node is null)
|
||||
{
|
||||
throw new ArgumentNullException(message);
|
||||
}
|
||||
if (!model.Nodes.Contains(node))
|
||||
{
|
||||
throw new ArgumentException($"{message}: node found in nodes");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Validate(this Model model)
|
||||
{
|
||||
foreach (var node in model.Root.Traverse().Skip(1))
|
||||
{
|
||||
model.Validate(node, "nodes must Contains node");
|
||||
}
|
||||
|
||||
foreach (var skin in model.Skins)
|
||||
{
|
||||
foreach (var joint in skin.Joints)
|
||||
{
|
||||
model.Validate(joint, "nodes must Contatins joint");
|
||||
}
|
||||
}
|
||||
|
||||
if (model.Vrm != null)
|
||||
{
|
||||
if (model.Vrm.ExpressionManager != null)
|
||||
{
|
||||
foreach (var b in model.Vrm.ExpressionManager.ExpressionList)
|
||||
{
|
||||
foreach (var v in b.MorphTargetBinds)
|
||||
{
|
||||
model.Validate(v.Node, "MorphTargetBindValue.Node is null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (model.Vrm.FirstPerson != null)
|
||||
{
|
||||
foreach (var a in model.Vrm.FirstPerson.Annotations)
|
||||
{
|
||||
model.Validate(a.Node, "FirstPersonMeshAnnotation.Node is null");
|
||||
}
|
||||
}
|
||||
|
||||
var humanDict = model.Root.Traverse()
|
||||
.Where(x => x.HumanoidBone.HasValue)
|
||||
.ToDictionary(x => x.HumanoidBone.Value, x => x);
|
||||
|
||||
foreach (var required in new[]{
|
||||
HumanoidBones.hips,
|
||||
})
|
||||
{
|
||||
if (!humanDict.ContainsKey(required))
|
||||
{
|
||||
throw new Exception($"no {required}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e7f73e8a96ec0774dadaa1f526c80210
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,365 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class ModelModifierExtensions
|
||||
{
|
||||
public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key, TValue addValue)
|
||||
{
|
||||
bool canAdd = !dict.ContainsKey(key);
|
||||
|
||||
if (canAdd)
|
||||
dict.Add(key, addValue);
|
||||
|
||||
return canAdd;
|
||||
}
|
||||
|
||||
public static TValue GetValueOrDefault<TKey, TValue>(this System.Collections.Generic.IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out TValue value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
static void ReplaceMorphTargetAnimationNode(IEnumerable<Animation> animations, Node dst)
|
||||
{
|
||||
foreach (var animation in animations)
|
||||
{
|
||||
var dstAnimation = animation.GetOrCreateNodeAnimation(dst);
|
||||
foreach (var (node, nodeAnimation) in animation.NodeMap)
|
||||
{
|
||||
if (nodeAnimation.Curves.TryGetValue(AnimationPathType.Weights, out CurveSampler curve))
|
||||
{
|
||||
// remove
|
||||
nodeAnimation.Curves.Remove(AnimationPathType.Weights);
|
||||
|
||||
// add
|
||||
if (!dstAnimation.Curves.TryAdd(AnimationPathType.Weights, curve))
|
||||
{
|
||||
Console.Error.WriteLine($"already exists. skip: {node.Name}: {nodeAnimation}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expression
|
||||
/// FirstPersonの置き換え
|
||||
public static void MeshNodeReplace(this ModelModifier modifier, Node src, Node dst)
|
||||
{
|
||||
var vrm = modifier.Model.Vrm;
|
||||
if (vrm is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (vrm.ExpressionManager != null)
|
||||
{
|
||||
foreach (var b in vrm.ExpressionManager.ExpressionList)
|
||||
{
|
||||
foreach (var v in b.MorphTargetBinds)
|
||||
{
|
||||
if (v.Node == src)
|
||||
{
|
||||
v.Node = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (vrm.FirstPerson != null)
|
||||
{
|
||||
foreach (var a in vrm.FirstPerson.Annotations)
|
||||
{
|
||||
if (a.Node == src)
|
||||
{
|
||||
a.Node = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string SingleMesh(this ModelModifier modifier, string name)
|
||||
{
|
||||
var count = modifier.Model.MeshGroups.Sum(x => x.Meshes.Count);
|
||||
var meshes = modifier.Model.Root.Traverse()
|
||||
.Select(x => x.MeshGroup)
|
||||
.Where(x => x != null)
|
||||
.Select(x => $"[{x.Name}]")
|
||||
.ToArray();
|
||||
if (meshes.Length == 0)
|
||||
{
|
||||
return "SingleMesh: no mesh. do nothing";
|
||||
}
|
||||
if (meshes.Length <= 1)
|
||||
{
|
||||
return "SingleMesh: one mesh. do nothing";
|
||||
}
|
||||
|
||||
var mesh = modifier.Model.CreateSingleMesh(name);
|
||||
var meshNode = new Node(mesh.Name)
|
||||
{
|
||||
MeshGroup = mesh,
|
||||
};
|
||||
mesh.Skin.Root = meshNode;
|
||||
|
||||
// fix bone weight (0, x, 0, 0) => (x, 0, 0, 0)
|
||||
// mesh.Meshes[0].VertexBuffer.FixBoneWeight();
|
||||
|
||||
// replace morphAnimation reference
|
||||
ReplaceMorphTargetAnimationNode(modifier.Model.Animations, meshNode);
|
||||
|
||||
// update Model
|
||||
foreach (var x in modifier.Model.MeshGroups.ToArray())
|
||||
{
|
||||
modifier.MeshReplace(x, mesh);
|
||||
}
|
||||
foreach (var node in modifier.Model.Nodes)
|
||||
{
|
||||
if (node.MeshGroup != null)
|
||||
{
|
||||
node.MeshGroup = null;
|
||||
modifier.MeshNodeReplace(node, meshNode);
|
||||
}
|
||||
|
||||
}
|
||||
modifier.NodeAdd(meshNode);
|
||||
|
||||
var names = string.Join("", meshes);
|
||||
// return $"SingleMesh: {names}";
|
||||
return $"SingleMesh: {count} => {modifier.Model.MeshGroups.Sum(x => x.Meshes.Count)}";
|
||||
}
|
||||
|
||||
public static void SepareteByMorphTarget(this ModelModifier modifier, MeshGroup mesh)
|
||||
{
|
||||
var (with, without) = mesh.SepareteByMorphTarget();
|
||||
var list = new List<MeshGroup>();
|
||||
if (with != null) list.Add(with);
|
||||
if (without != null) list.Add(without);
|
||||
|
||||
// 分割モデルで置き換え
|
||||
if (list.Any())
|
||||
{
|
||||
modifier.MeshReplace(mesh, list[0]);
|
||||
// rename node
|
||||
modifier.Model.Nodes.Find(x => x.MeshGroup == list[0]).Name = list[0].Name;
|
||||
}
|
||||
|
||||
if (list.Count > 1)
|
||||
{
|
||||
// morph無しと有り両方存在する場合に2つ目を追加する
|
||||
modifier.MeshReplace(null, list[1]);
|
||||
modifier.NodeAdd(new Node(list[1].Name)
|
||||
{
|
||||
MeshGroup = list[1]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void SepareteByHeadBone(this ModelModifier modifier, MeshGroup mesh, HashSet<int> boneIndices)
|
||||
{
|
||||
var (with, without) = mesh.SepareteByHeadBone(boneIndices);
|
||||
var list = new List<MeshGroup>();
|
||||
if (with != null) list.Add(with);
|
||||
if (without != null) list.Add(without);
|
||||
|
||||
// 分割モデルで置き換え
|
||||
if (list.Any())
|
||||
{
|
||||
modifier.MeshReplace(mesh, list[0]);
|
||||
// rename node
|
||||
modifier.Model.Nodes.Find(x => x.MeshGroup == list[0]).Name = list[0].Name;
|
||||
}
|
||||
|
||||
if (list.Count > 1)
|
||||
{
|
||||
// 頭と胴体で分割後2つ以上ある場合、2つ目を追加する
|
||||
modifier.MeshReplace(null, list[1]);
|
||||
modifier.NodeAdd(new Node(list[1].Name)
|
||||
{
|
||||
MeshGroup = list[1]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static string NodeReduce(this ModelModifier modifier)
|
||||
{
|
||||
var count = modifier.Model.Nodes.Count;
|
||||
var removeNames = new List<string>();
|
||||
|
||||
// ノードを削除する
|
||||
foreach (var node in modifier.Model.GetRemoveNodes())
|
||||
{
|
||||
modifier.NodeRemove(node);
|
||||
removeNames.Add($"[{node.Name}]");
|
||||
foreach (var skin in modifier.Model.Skins)
|
||||
{
|
||||
var index = skin.Joints.IndexOf(node);
|
||||
if (index != -1)
|
||||
{
|
||||
// remove
|
||||
skin.Joints[index] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 削除されたノードを参照する頂点バッファを修正する
|
||||
foreach (var meshGroup in modifier.Model.MeshGroups)
|
||||
{
|
||||
var skin = meshGroup.Skin;
|
||||
if (skin != null && skin.Joints.Contains(null))
|
||||
{
|
||||
foreach (var mesh in meshGroup.Meshes)
|
||||
{
|
||||
skin.FixBoneWeight(mesh.VertexBuffer.Joints, mesh.VertexBuffer.Weights);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var joined = string.Join("", removeNames);
|
||||
|
||||
return $"NodeReduce: {count} => {modifier.Model.Nodes.Count}";
|
||||
// return $"NodeReduce: {joined}";
|
||||
}
|
||||
|
||||
public static string SkinningBake(this ModelModifier modifier)
|
||||
{
|
||||
foreach (var node in modifier.Model.Nodes)
|
||||
{
|
||||
var meshGroup = node.MeshGroup;
|
||||
if (meshGroup == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (meshGroup.Skin != null)
|
||||
{
|
||||
// 正規化されていれば1つしかない
|
||||
// されていないと Primitive の数だけある
|
||||
foreach (var mesh in meshGroup.Meshes)
|
||||
{
|
||||
{
|
||||
// Skinningの出力先を自身にすることでBakeする
|
||||
meshGroup.Skin.Skinning(mesh.VertexBuffer);
|
||||
}
|
||||
|
||||
// morphのPositionは相対値が入っているはずなので、手を加えない(正規化されていない場合、二重に補正が掛かる)
|
||||
/*
|
||||
foreach (var morph in mesh.MorphTargets)
|
||||
{
|
||||
if (morph.VertexBuffer.Positions != null)
|
||||
{
|
||||
meshGroup.Skin.Skinning(morph.VertexBuffer);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
meshGroup.Skin.Root = null;
|
||||
meshGroup.Skin.InverseMatrices = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var mesh in meshGroup.Meshes)
|
||||
{
|
||||
// nodeに対して疑似的にSkinningする
|
||||
// 回転と拡縮を適用し位置は適用しない
|
||||
mesh.ApplyRotationAndScaling(node.Matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 回転・拡縮を除去する
|
||||
modifier.Model.ApplyRotationAndScale();
|
||||
|
||||
// inverse matrix の再計算
|
||||
foreach (var node in modifier.Model.Nodes)
|
||||
{
|
||||
var meshGroup = node.MeshGroup;
|
||||
if (meshGroup == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var mesh in meshGroup.Meshes)
|
||||
{
|
||||
if (meshGroup.Skin != null)
|
||||
{
|
||||
meshGroup.Skin.CalcInverseMatrices();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "SkinningBake";
|
||||
}
|
||||
|
||||
public static string CloneSharedMesh(this ModelModifier modifier)
|
||||
{
|
||||
Dictionary<MeshGroup, int> m_useMap = new Dictionary<MeshGroup, int>();
|
||||
|
||||
var cloned = new List<string>();
|
||||
|
||||
foreach (var node in modifier.Model.Nodes)
|
||||
{
|
||||
if (node.MeshGroup == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var n = m_useMap.GetValueOrDefault(node.MeshGroup);
|
||||
if (n > 0)
|
||||
{
|
||||
// copy
|
||||
node.MeshGroup = node.MeshGroup.Clone();
|
||||
cloned.Add($"[{node.MeshGroup.Name}]");
|
||||
}
|
||||
m_useMap[node.MeshGroup] = n + 1;
|
||||
}
|
||||
|
||||
if (!cloned.Any())
|
||||
{
|
||||
return "CloneSharedMesh: no shared mesh. do nothing";
|
||||
}
|
||||
else
|
||||
{
|
||||
var joined = string.Join("", cloned);
|
||||
return $"CloneSharedMesh: copy {joined}";
|
||||
}
|
||||
}
|
||||
|
||||
public static string MaterialIntegrate(this ModelModifier modifier)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
var materials = new List<Material>();
|
||||
|
||||
foreach (var material in modifier.Model.Materials.ToArray())
|
||||
{
|
||||
var found = materials.FirstOrDefault(x => x.CanIntegrate(material));
|
||||
if (found != null)
|
||||
{
|
||||
// merge
|
||||
modifier.MaterialReplace(material, found);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add
|
||||
materials.Add(material);
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append($"MaterialIntegrate: {modifier.Model.Materials.Count} => {materials.Count}");
|
||||
|
||||
modifier.Model.Materials.Clear();
|
||||
modifier.Model.Materials.AddRange(materials);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 54fd566dc84b51643886ce8858a671db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using VrmLib;
|
||||
using VrmLib.Bvh;
|
||||
|
||||
namespace VrmLibTests
|
||||
{
|
||||
public class BvhTests
|
||||
{
|
||||
DirectoryInfo RootPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return new FileInfo(GetType().Assembly.Location).Directory.Parent.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("Assets/StreamingAssets/VRM.Samples/Motions/test.txt")]
|
||||
public void BvhTest(string filename)
|
||||
{
|
||||
var path = Path.Combine(RootPath.FullName, filename);
|
||||
var text = File.ReadAllText(path, Encoding.UTF8);
|
||||
var bvh = BvhParser.Parse(text);
|
||||
Assert.AreEqual(4007, bvh.FrameCount);
|
||||
|
||||
var model = ModelExtensionsForBvh.Load(Path.GetFileName(path), bvh);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("Assets/StreamingAssets/VRM.Samples/Motions/test.txt")]
|
||||
public void SkeletonEstimatorTest(string filename)
|
||||
{
|
||||
var path = Path.Combine(RootPath.FullName, filename);
|
||||
var text = File.ReadAllText(path, Encoding.UTF8);
|
||||
var bvh = BvhParser.Parse(text);
|
||||
|
||||
var model = ModelExtensionsForBvh.CreateFromBvh(bvh.Root);
|
||||
|
||||
var estimated = SkeletonEstimator.Detect(model.Root);
|
||||
Assert.AreEqual(estimated[HumanoidBones.hips].Name, "Hips");
|
||||
Assert.AreEqual(estimated[HumanoidBones.spine].Name, "Spine");
|
||||
Assert.AreEqual(estimated[HumanoidBones.chest].Name, "Spine1");
|
||||
Assert.AreEqual(estimated[HumanoidBones.neck].Name, "Neck");
|
||||
Assert.AreEqual(estimated[HumanoidBones.head].Name, "Head");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftShoulder].Name, "LeftShoulder");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftUpperArm].Name, "LeftArm");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftLowerArm].Name, "LeftForeArm");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftHand].Name, "LeftHand");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightShoulder].Name, "RightShoulder");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightUpperArm].Name, "RightArm");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightLowerArm].Name, "RightForeArm");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightHand].Name, "RightHand");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftUpperLeg].Name, "LeftUpLeg");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftLowerLeg].Name, "LeftLeg");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftFoot].Name, "LeftFoot");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftToes].Name, "LeftToeBase");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightUpperLeg].Name, "RightUpLeg");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightLowerLeg].Name, "RightLeg");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightFoot].Name, "RightFoot");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightToes].Name, "RightToeBase");
|
||||
}
|
||||
|
||||
DirectoryInfo TestModelPath
|
||||
{
|
||||
get
|
||||
{
|
||||
var env = Environment.GetEnvironmentVariable("VRM_TEST_MODELS");
|
||||
return new DirectoryInfo(env);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample00.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample01.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample02.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample03.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample04.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample05.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample06.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample07.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample08.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample09.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample10.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample11.bvh")]
|
||||
[TestCase("Motions/bvh/liveanimation/la_bvh_sample12.bvh")]
|
||||
public void SkeletonEstimatorTestLA(string filename)
|
||||
{
|
||||
var path = Path.Combine(TestModelPath.FullName, filename);
|
||||
var text = File.ReadAllText(path, Encoding.UTF8);
|
||||
var bvh = BvhParser.Parse(text);
|
||||
var model = ModelExtensionsForBvh.CreateFromBvh(bvh.Root);
|
||||
var estimated = SkeletonEstimator.Detect(model.Root);
|
||||
Assert.AreEqual(estimated[HumanoidBones.hips].Name, "Hips");
|
||||
Assert.AreEqual(estimated[HumanoidBones.spine].Name, "Chest");
|
||||
Assert.AreEqual(estimated[HumanoidBones.chest].Name, "Chest2");
|
||||
Assert.AreEqual(estimated[HumanoidBones.neck].Name, "Neck");
|
||||
Assert.AreEqual(estimated[HumanoidBones.head].Name, "Head");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftShoulder].Name, "LeftCollar");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftUpperArm].Name, "LeftShoulder");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftLowerArm].Name, "LeftElbow");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftHand].Name, "LeftWrist");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightShoulder].Name, "RightCollar");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightUpperArm].Name, "RightShoulder");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightLowerArm].Name, "RightElbow");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightHand].Name, "RightWrist");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftUpperLeg].Name, "LeftHip");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftLowerLeg].Name, "LeftKnee");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftFoot].Name, "LeftAnkle");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightUpperLeg].Name, "RightHip");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightLowerLeg].Name, "RightKnee");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightFoot].Name, "RightAnkle");
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase("Motions/bvh/accad/eric1.bvh")]
|
||||
[TestCase("Motions/bvh/accad/ericdog.bvh")]
|
||||
[TestCase("Motions/bvh/accad/ericrun.bvh")]
|
||||
[TestCase("Motions/bvh/accad/flip.bvh")]
|
||||
[TestCase("Motions/bvh/accad/swagger.bvh")]
|
||||
public void SkeletonEstimatorTestAccad(string filename)
|
||||
{
|
||||
var path = Path.Combine(TestModelPath.FullName, filename);
|
||||
var text = File.ReadAllText(path, Encoding.UTF8);
|
||||
var bvh = BvhParser.Parse(text);
|
||||
var bones = bvh.Root.Traverse().ToArray();
|
||||
foreach (var bone in bones)
|
||||
{
|
||||
Console.WriteLine(bone.Name);
|
||||
}
|
||||
var model = ModelExtensionsForBvh.CreateFromBvh(bvh.Root);
|
||||
var estimated = SkeletonEstimator.Detect(model.Root);
|
||||
|
||||
Assert.AreEqual(estimated[HumanoidBones.hips].Name, "root");
|
||||
Assert.AreEqual(estimated[HumanoidBones.spine].Name, "lowerback");
|
||||
Assert.AreEqual(estimated[HumanoidBones.chest].Name, "upperback");
|
||||
Assert.AreEqual(estimated[HumanoidBones.upperChest].Name, "thorax");
|
||||
Assert.AreEqual(estimated[HumanoidBones.neck].Name, "neck");
|
||||
Assert.AreEqual(estimated[HumanoidBones.head].Name, "head");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftShoulder].Name, "lshoulderjoint");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftUpperArm].Name, "lhumerus");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftLowerArm].Name, "lradius");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftHand].Name, "lhand");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightShoulder].Name, "rshoulderjoint");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightUpperArm].Name, "rhumerus");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightLowerArm].Name, "rradius");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightHand].Name, "rhand");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightUpperLeg].Name, "rfemur");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightLowerLeg].Name, "rtibia");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightFoot].Name, "rfoot");
|
||||
Assert.AreEqual(estimated[HumanoidBones.rightToes].Name, "rtoes");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftUpperLeg].Name, "lfemur");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftLowerLeg].Name, "ltibia");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftFoot].Name, "lfoot");
|
||||
Assert.AreEqual(estimated[HumanoidBones.leftToes].Name, "ltoes");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c6a1d4e939982f84b9f3d94de009eb0e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue
Block a user