mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-20 17:57:57 -05:00
remove unused
This commit is contained in:
parent
b48c8707d8
commit
4bb2cfaf59
|
|
@ -794,7 +794,7 @@ namespace UniVRM10
|
|||
var model = converter.Export(go);
|
||||
|
||||
// 右手系に変換
|
||||
VrmLib.ModelExtensionsForCoordinates.ConvertCoordinate(model, VrmLib.Coordinates.Vrm1);
|
||||
model.ConvertCoordinate(VrmLib.Coordinates.Vrm1);
|
||||
|
||||
// Model と go から VRM-1.0 にExport
|
||||
var exporter10 = new Vrm10Exporter(textureSerializer ?? new RuntimeTextureSerializer(), new GltfExportSettings());
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ namespace UniVRM10
|
|||
|
||||
if (m_doNormalize)
|
||||
{
|
||||
var result = VrmLib.ModelModifierExtensions.SkinningBake(new VrmLib.ModelModifier(m_model));
|
||||
var result = m_model.SkinningBake();
|
||||
Debug.Log($"SkinningBake: {result}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace UniVRM10
|
|||
// VRM0 -> Unity
|
||||
var model = ModelReader.Read(data, VrmLib.Coordinates.Vrm0);
|
||||
// Unity -> VRM1
|
||||
VrmLib.ModelExtensionsForCoordinates.ConvertCoordinate(model, VrmLib.Coordinates.Vrm1);
|
||||
model.ConvertCoordinate(VrmLib.Coordinates.Vrm1);
|
||||
|
||||
var (gltf, bin) = new MeshUpdater(data).Update(model);
|
||||
gltf.extensions = null;
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
namespace VrmLib
|
||||
{
|
||||
public static class IndexExtensions
|
||||
{
|
||||
public static bool TryGetValidIndex(this int? index, int count, out int outValue)
|
||||
{
|
||||
outValue = -1;
|
||||
if (!index.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var value = index.Value;
|
||||
if (value < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (value >= count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
outValue = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ad2e7d02ba5894344a2f58d9d2157f98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using UniGLTF;
|
||||
|
||||
|
|
@ -240,43 +241,26 @@ namespace VrmLib
|
|||
{
|
||||
Submeshes = Submeshes.Where(x => x.DrawCount != 0).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public class MeshGroup: GltfId
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public readonly List<Mesh> Meshes = new List<Mesh>();
|
||||
|
||||
public Skin Skin;
|
||||
|
||||
public override string ToString()
|
||||
// Skin.Normalize
|
||||
public void ApplyRotationAndScaling(Matrix4x4 m)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(Name);
|
||||
if (Skin != null)
|
||||
{
|
||||
sb.Append("(skinned)");
|
||||
}
|
||||
var isFirst = true;
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
sb.Append(mesh);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
m.Translation = Vector3.Zero;
|
||||
|
||||
public MeshGroup(string name)
|
||||
{
|
||||
Name = name;
|
||||
var position = SpanLike.Wrap<Vector3>(VertexBuffer.Positions.Bytes);
|
||||
var normal = SpanLike.Wrap<Vector3>(VertexBuffer.Normals.Bytes);
|
||||
|
||||
for (int i = 0; i < position.Length; ++i)
|
||||
{
|
||||
{
|
||||
var dst = Vector4.Transform(new Vector4(position[i], 1), m);
|
||||
position[i] = new Vector3(dst.X, dst.Y, dst.Z);
|
||||
}
|
||||
{
|
||||
var dst = Vector4.Transform(new Vector4(normal[i], 0), m);
|
||||
normal[i] = new Vector3(dst.X, dst.Y, dst.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,673 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using UniGLTF;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class MeshExtensions
|
||||
{
|
||||
// Skin.Normalize
|
||||
public static void ApplyRotationAndScaling(this Mesh mesh, Matrix4x4 m)
|
||||
{
|
||||
m.Translation = Vector3.Zero;
|
||||
|
||||
var position = SpanLike.Wrap<Vector3>(mesh.VertexBuffer.Positions.Bytes);
|
||||
var normal = SpanLike.Wrap<Vector3>(mesh.VertexBuffer.Normals.Bytes);
|
||||
|
||||
for (int i = 0; i < position.Length; ++i)
|
||||
{
|
||||
{
|
||||
var dst = Vector4.Transform(new Vector4(position[i], 1), m);
|
||||
position[i] = new Vector3(dst.X, dst.Y, dst.Z);
|
||||
}
|
||||
{
|
||||
var dst = Vector4.Transform(new Vector4(normal[i], 0), m);
|
||||
normal[i] = new Vector3(dst.X, dst.Y, dst.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// joint index の調整
|
||||
static void FixSkinJoints(SpanLike<SkinJoints> joints, SpanLike<Vector4> weights, int[] jointIndexMap)
|
||||
{
|
||||
for (int i = 0; i < joints.Length; ++i)
|
||||
{
|
||||
var j = joints[i];
|
||||
var w = weights[i];
|
||||
|
||||
var sum = w.X + w.Y + w.Z + w.W;
|
||||
if (sum == 0)
|
||||
{
|
||||
throw new Exception("zero weight");
|
||||
}
|
||||
var factor = 1.0f / sum;
|
||||
|
||||
{
|
||||
var index = jointIndexMap[j.Joint0];
|
||||
j.Joint0 = (ushort)(index >= 0 ? index : 0);
|
||||
w.X *= factor;
|
||||
}
|
||||
|
||||
{
|
||||
var index = jointIndexMap[j.Joint1];
|
||||
j.Joint1 = (ushort)(index >= 0 ? index : 0);
|
||||
w.X *= factor;
|
||||
}
|
||||
|
||||
{
|
||||
var index = jointIndexMap[j.Joint2];
|
||||
j.Joint2 = (ushort)(index >= 0 ? index : 0);
|
||||
w.X *= factor;
|
||||
}
|
||||
|
||||
{
|
||||
var index = jointIndexMap[j.Joint3];
|
||||
j.Joint3 = (ushort)(index >= 0 ? index : 0);
|
||||
w.X *= factor;
|
||||
}
|
||||
|
||||
weights[i] = w;
|
||||
joints[i] = j;
|
||||
}
|
||||
}
|
||||
|
||||
/// weightを付与して、matrixを適用する
|
||||
static void FixNoSkinJoints(SpanLike<SkinJoints> joints, SpanLike<Vector4> weights, int jointIndex,
|
||||
SpanLike<Vector3> positions, SpanLike<Vector3> normals, Matrix4x4 matrix)
|
||||
{
|
||||
for (int i = 0; i < joints.Length; ++i)
|
||||
{
|
||||
var j = new SkinJoints
|
||||
{
|
||||
Joint0 = (ushort)jointIndex
|
||||
};
|
||||
joints[i] = j;
|
||||
|
||||
var w = new Vector4
|
||||
{
|
||||
X = 1.0f,
|
||||
};
|
||||
weights[i] = w;
|
||||
|
||||
{
|
||||
// position
|
||||
var src = positions[i];
|
||||
var dst = Vector4.Transform(new Vector4(src, 1), matrix);
|
||||
positions[i] = new Vector3(dst.X, dst.Y, dst.Z);
|
||||
}
|
||||
{
|
||||
// normal
|
||||
var src = normals[i];
|
||||
var dst = Vector4.Transform(new Vector4(src, 0), matrix);
|
||||
normals[i] = new Vector3(dst.X, dst.Y, dst.Z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Meshを連結する。SingleMesh で使う
|
||||
public static void Append(this Mesh mesh, VertexBuffer vertices, BufferAccessor indices,
|
||||
List<Submesh> submeshes,
|
||||
List<MorphTarget> targets,
|
||||
int[] jointIndexMap,
|
||||
int rootIndex = -1,
|
||||
Matrix4x4 matrix = default(Matrix4x4))
|
||||
{
|
||||
var lastCount = mesh.VertexBuffer != null ? mesh.VertexBuffer.Count : 0;
|
||||
|
||||
// index buffer
|
||||
if (mesh.IndexBuffer == null)
|
||||
{
|
||||
var accessor = new BufferAccessor(new ArraySegment<byte>(new byte[0]), AccessorValueType.UNSIGNED_INT, indices.AccessorType, 0);
|
||||
mesh.IndexBuffer = accessor;
|
||||
|
||||
mesh.IndexBuffer.Append(indices, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh.IndexBuffer.Append(indices, lastCount);
|
||||
}
|
||||
|
||||
{
|
||||
var submeshOffset = mesh.SubmeshTotalDrawCount;
|
||||
for (int i = 0; i < submeshes.Count; ++i)
|
||||
{
|
||||
var submesh = submeshes[i];
|
||||
mesh.Submeshes.Add(new Submesh(submeshOffset, submesh.DrawCount, submesh.Material));
|
||||
submeshOffset += submesh.DrawCount;
|
||||
}
|
||||
}
|
||||
|
||||
// vertex buffer
|
||||
var vertexOffset = 0;
|
||||
if (mesh.VertexBuffer == null)
|
||||
{
|
||||
mesh.VertexBuffer = vertices;
|
||||
}
|
||||
else
|
||||
{
|
||||
vertexOffset = mesh.VertexBuffer.Count;
|
||||
mesh.VertexBuffer.Append(vertices);
|
||||
}
|
||||
|
||||
if (jointIndexMap != null && mesh.VertexBuffer.Joints != null && mesh.VertexBuffer.Weights != null)
|
||||
{
|
||||
// JOINT index 参照の修正
|
||||
var joints = SpanLike.Wrap<SkinJoints>(mesh.VertexBuffer.Joints.Bytes).Slice(vertexOffset);
|
||||
var weights = SpanLike.Wrap<Vector4>(mesh.VertexBuffer.Weights.Bytes).Slice(vertexOffset);
|
||||
FixSkinJoints(joints, weights, jointIndexMap);
|
||||
}
|
||||
else
|
||||
{
|
||||
var position = SpanLike.Wrap<Vector3>(mesh.VertexBuffer.Positions.Bytes).Slice(vertexOffset);
|
||||
var normal = SpanLike.Wrap<Vector3>(mesh.VertexBuffer.Normals.Bytes).Slice(vertexOffset);
|
||||
var joints = mesh.VertexBuffer.GetOrCreateJoints().Slice(vertexOffset);
|
||||
var weights = mesh.VertexBuffer.GetOrCreateWeights().Slice(vertexOffset);
|
||||
// Nodeの姿勢を反映して
|
||||
// JOINT と WEIGHT を追加する
|
||||
FixNoSkinJoints(joints, weights, rootIndex, position, normal, matrix);
|
||||
}
|
||||
|
||||
// morph target
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (string.IsNullOrEmpty(target.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var kv in target.VertexBuffer)
|
||||
{
|
||||
if (kv.Value.Count != target.VertexBuffer.Count)
|
||||
{
|
||||
throw new Exception("different length");
|
||||
}
|
||||
}
|
||||
|
||||
var found = mesh.MorphTargets.FirstOrDefault(x => x.Name == target.Name);
|
||||
if (found == null)
|
||||
{
|
||||
// targetの前に0を足す
|
||||
found = new MorphTarget(target.Name);
|
||||
found.VertexBuffer = target.VertexBuffer.CloneWithOffset(lastCount);
|
||||
mesh.MorphTargets.Add(found);
|
||||
|
||||
foreach (var kv in found.VertexBuffer)
|
||||
{
|
||||
if (kv.Value.Count != mesh.VertexBuffer.Count)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
found.VertexBuffer.Resize(lastCount);
|
||||
|
||||
// foundの後ろにtargetを足す
|
||||
found.VertexBuffer.Append(target.VertexBuffer);
|
||||
|
||||
foreach (var kv in found.VertexBuffer)
|
||||
{
|
||||
if (kv.Value.Count != mesh.VertexBuffer.Count)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubmeshReorderMapper
|
||||
{
|
||||
public readonly List<int> Indices = new List<int>();
|
||||
|
||||
public ArraySegment<byte> IndexBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
var bytes = new byte[Indices.Count * 4];
|
||||
var span = SpanLike.Wrap<Int32>(new ArraySegment<byte>(bytes));
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = Indices[i];
|
||||
}
|
||||
return new ArraySegment<byte>(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public BufferAccessor CreateIndexBuffer()
|
||||
{
|
||||
return new BufferAccessor(IndexBytes,
|
||||
AccessorValueType.UNSIGNED_INT, AccessorVectorType.SCALAR, Indices.Count);
|
||||
}
|
||||
|
||||
public SubmeshReorderMapper(IEnumerable<ValueTuple<int, Triangle>> submeshTriangles)
|
||||
{
|
||||
foreach (var t in submeshTriangles)
|
||||
{
|
||||
Indices.Add(t.Item2.Vertex0);
|
||||
Indices.Add(t.Item2.Vertex1);
|
||||
Indices.Add(t.Item2.Vertex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string IntegrateSubmeshes(this Mesh mesh)
|
||||
{
|
||||
var before = mesh.Submeshes.Count;
|
||||
|
||||
// 同じMaterialのMeshが連続するようにIndexを並べ替える
|
||||
var sorted = new List<Submesh>();
|
||||
var source = mesh.Submeshes.ToList(); // copy
|
||||
while (source.Any())
|
||||
{
|
||||
var first = source[0];
|
||||
source.RemoveAt(0);
|
||||
|
||||
sorted.Add(first);
|
||||
sorted.AddRange(source.Where(x => x.Material == first.Material)); // 同じMaterialを追加
|
||||
source.RemoveAll(x => x.Material == first.Material); // 同じMaterialを削除
|
||||
}
|
||||
// Submeshを並べ替えて連続するものを連結する
|
||||
mesh.ReorderSubmeshes(sorted);
|
||||
|
||||
return $"({before} => {mesh.Submeshes.Count})";
|
||||
}
|
||||
|
||||
struct Vertex : IComparable<Vertex>, IEquatable<Vertex>
|
||||
{
|
||||
public static float PositionThreshold = float.Epsilon;
|
||||
|
||||
public static float NormalAngleThreshold = 100.0f;
|
||||
|
||||
public static float UVThreshold = 0.0001f;
|
||||
|
||||
/// <summary>
|
||||
/// 他の頂点の統合先となって、残るフラグ
|
||||
/// </summary>
|
||||
public bool IsMergeTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
return Index == targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 統合先の頂点Index
|
||||
/// 統合後も残るはtargetIndex == Index
|
||||
/// </summary>
|
||||
public int targetIndex;
|
||||
|
||||
public int Index;
|
||||
|
||||
public Vector3 Position;
|
||||
|
||||
public Vector3? Normal;
|
||||
|
||||
public Vector2? UV;
|
||||
|
||||
public SkinJoints? Joints;
|
||||
|
||||
public Vector4? Weight;
|
||||
|
||||
public Vector4? Color;
|
||||
|
||||
// 必要に応じて同一判定に使う頂点パラメータを追加する
|
||||
|
||||
public Vertex(int index, Vector3 position, Vector3? normal, Vector2? uv, SkinJoints? joints, Vector4? weight, Vector4? color)
|
||||
{
|
||||
Index = index;
|
||||
Position = position;
|
||||
Normal = normal;
|
||||
UV = uv;
|
||||
Joints = joints;
|
||||
Weight = weight;
|
||||
Color = color;
|
||||
targetIndex = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 座標ソートのときの比較基準を記述
|
||||
/// 必ずしもX軸基準である必要は特にない
|
||||
/// </summary>
|
||||
public int CompareTo(Vertex other)
|
||||
{
|
||||
if (this.Position.X == other.Position.X) return 0;
|
||||
else if (this.Position.X > other.Position.X) return 1;
|
||||
else return -1;
|
||||
}
|
||||
|
||||
public bool Equals(Vertex other)
|
||||
{
|
||||
if (Vector3.Distance(this.Position, other.Position) > PositionThreshold) return false;
|
||||
|
||||
if (this.Normal.HasValue && other.Normal.HasValue)
|
||||
{
|
||||
var v = this.Normal.Value;
|
||||
var u = other.Normal.Value;
|
||||
var angle = (Math.Acos(Vector3.Dot(v, u) / (v.Length() * u.Length()))) * (180 / Math.PI);
|
||||
if (angle > NormalAngleThreshold) return false;
|
||||
}
|
||||
|
||||
if ((this.UV.HasValue && other.UV.HasValue) && Vector2.Distance(this.UV.Value, other.UV.Value) > UVThreshold) return false;
|
||||
|
||||
if ((this.Joints.HasValue && other.Joints.HasValue) && !(this.Joints.Value.Equals(other.Joints.Value))) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// 同一位置にある頂点を統合する
|
||||
/// </summary>
|
||||
public static string MergeSameVertices(this Mesh mesh)
|
||||
{
|
||||
var indices = mesh.IndexBuffer.GetAsIntArray();
|
||||
|
||||
var positions = SpanLike.CreateVector3(mesh.VertexBuffer.Positions.Bytes);
|
||||
var normals = mesh.VertexBuffer.Normals != null ? SpanLike.CreateVector3(mesh.VertexBuffer.Normals.Bytes) : default;
|
||||
var UVs = mesh.VertexBuffer.TexCoords != null ? SpanLike.CreateVector2(mesh.VertexBuffer.TexCoords.Bytes) : default;
|
||||
var joints = mesh.VertexBuffer.Joints != null ? SpanLike.CreateSkinJoints(mesh.VertexBuffer.Joints.Bytes) : default;
|
||||
var weights = mesh.VertexBuffer.Weights != null ? SpanLike.CreateVector4(mesh.VertexBuffer.Weights.Bytes) : default;
|
||||
var colors = mesh.VertexBuffer.Colors != null ? SpanLike.CreateVector4(mesh.VertexBuffer.Colors.Bytes) : default;
|
||||
|
||||
int before = positions.Length;
|
||||
|
||||
var verts = new Vertex[positions.Length];
|
||||
for (int i = 0; i < positions.Length; i++)
|
||||
{
|
||||
var pos = positions[i];
|
||||
|
||||
Vector3? normal = null;
|
||||
if (normals.Length > 0) normal = normals[i];
|
||||
|
||||
Vector2? uv = null;
|
||||
if (UVs.Length > 0) uv = UVs[i];
|
||||
|
||||
SkinJoints? joint = null;
|
||||
if (joints.Length > 0) joint = joints[i];
|
||||
|
||||
Vector4? weight = null;
|
||||
if (weights.Length > 0) weight = weights[i];
|
||||
|
||||
Vector4? color = null;
|
||||
if (colors.Length > 0) color = colors[i];
|
||||
verts[i] = new Vertex(i, pos, normal, uv, joint, weight, color);
|
||||
}
|
||||
|
||||
Array.Sort(verts);
|
||||
|
||||
// var vertices = new Span<Vertex>(verts);
|
||||
var vertices = new ArraySegment<Vertex>(verts);
|
||||
|
||||
var stride = 200; // 総当り比較するブロック数。小さいほど計算は早いが余裕を持たせてある程度の値を確保している
|
||||
|
||||
for (int i = 0; i < vertices.Count; i += (stride))
|
||||
{
|
||||
stride = Math.Min(stride, vertices.Count - i);
|
||||
var dividedIndices = vertices.Slice(i, stride);
|
||||
FindMergeTargetVertices(ref dividedIndices);
|
||||
}
|
||||
|
||||
// 統合する頂点の組み合わせを作成
|
||||
var mergePairVatexDictionary = new Dictionary<int, int>(); // key: remove vertex index , value : merge target vertex index
|
||||
foreach (var v in vertices)
|
||||
{
|
||||
if (v.IsMergeTarget) continue;
|
||||
if (v.targetIndex > 0)
|
||||
{
|
||||
if (mergePairVatexDictionary.ContainsKey(v.Index)) continue;
|
||||
mergePairVatexDictionary.Add(v.Index, v.targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// 統合されて消える頂点ID群を作成
|
||||
var removeIndexList = new List<int>(); // index補正用
|
||||
var removeIndexHashSet = new HashSet<int>();
|
||||
foreach (var v in vertices)
|
||||
{
|
||||
if (v.IsMergeTarget) continue;
|
||||
if (v.targetIndex > 0)
|
||||
{
|
||||
removeIndexList.Add(v.Index);
|
||||
removeIndexHashSet.Add(v.Index);
|
||||
}
|
||||
}
|
||||
removeIndexList.Sort();
|
||||
|
||||
int resizedLength = positions.Length - removeIndexHashSet.Count();
|
||||
|
||||
var indexOffsetArray = new int[indices.Length];
|
||||
|
||||
int offset = 0;
|
||||
int currentIndex = 0;
|
||||
|
||||
// 統合後の頂点IDの補正値を計算
|
||||
foreach (var index in removeIndexList)
|
||||
{
|
||||
for (int i = currentIndex; i <= index; i++)
|
||||
{
|
||||
indexOffsetArray[i] = offset;
|
||||
}
|
||||
currentIndex = index + 1;
|
||||
offset++;
|
||||
}
|
||||
for (int i = currentIndex; i < indices.Length; i++)
|
||||
{
|
||||
indexOffsetArray[i] = offset;
|
||||
}
|
||||
|
||||
// merge vertex index
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
if (mergePairVatexDictionary.ContainsKey(indices[i]))
|
||||
{
|
||||
var newIndex = mergePairVatexDictionary[indices[i]];
|
||||
var correctedIndex = newIndex - indexOffsetArray[newIndex];
|
||||
|
||||
if (correctedIndex < 0 || correctedIndex >= resizedLength)
|
||||
{
|
||||
throw new IndexOutOfRangeException("recalculated vertex index are invalid after vertex merged");
|
||||
}
|
||||
indices[i] = correctedIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
var correctedIndex = indices[i] - indexOffsetArray[indices[i]];
|
||||
|
||||
if (correctedIndex < 0 || correctedIndex > resizedLength)
|
||||
{
|
||||
throw new IndexOutOfRangeException("recalculated vertex index are invalid after vertex merged");
|
||||
}
|
||||
|
||||
indices[i] = correctedIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (positions.Length > ushort.MaxValue)
|
||||
{
|
||||
mesh.IndexBuffer.Assign(indices.AsSpan());
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh.IndexBuffer.AssignAsShort(indices.AsSpan());
|
||||
}
|
||||
|
||||
// 統合後、平均値を割り当てるパラメータは更新する
|
||||
foreach (var v in vertices)
|
||||
{
|
||||
if (v.IsMergeTarget)
|
||||
{
|
||||
if (normals != null) normals[v.Index] = v.Normal.Value;
|
||||
if (colors != null) colors[v.Index] = v.Color.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// merge vertex
|
||||
var newPositions = new Vector3[resizedLength];
|
||||
var newNormals = normals != null ? new Vector3[resizedLength] : null;
|
||||
var newJoints = joints != null ? new SkinJoints[resizedLength] : null;
|
||||
var newWeights = weights != null ? new Vector4[resizedLength] : null;
|
||||
var newUVs = UVs != null ? new Vector2[resizedLength] : null;
|
||||
var newColors = colors != null ? new Vector4[resizedLength] : null;
|
||||
|
||||
for (int i = 0, targetIndex = 0; i < positions.Length; i++)
|
||||
{
|
||||
if (removeIndexHashSet.Contains(i)) continue;
|
||||
newPositions[targetIndex] = positions[i];
|
||||
if (normals != null) newNormals[targetIndex] = normals[i];
|
||||
if (joints != null) newJoints[targetIndex] = joints[i];
|
||||
if (weights != null) newWeights[targetIndex] = weights[i];
|
||||
if (UVs != null) newUVs[targetIndex] = UVs[i];
|
||||
if (colors != null) newColors[targetIndex] = colors[i];
|
||||
|
||||
targetIndex++;
|
||||
}
|
||||
|
||||
mesh.VertexBuffer.Positions.Assign(newPositions.AsSpan());
|
||||
if (normals != null) mesh.VertexBuffer.Normals.Assign(newNormals.AsSpan());
|
||||
if (joints != null) mesh.VertexBuffer.Joints.Assign(newJoints.AsSpan());
|
||||
if (weights != null) mesh.VertexBuffer.Weights.Assign(newWeights.AsSpan());
|
||||
if (UVs != null) mesh.VertexBuffer.TexCoords.Assign(newUVs.AsSpan());
|
||||
if (colors != null) mesh.VertexBuffer.Colors.Assign(newColors.AsSpan());
|
||||
mesh.VertexBuffer.RemoveTangent();
|
||||
mesh.VertexBuffer.ValidateLength();
|
||||
|
||||
//
|
||||
// merge morph vertex buffer
|
||||
//
|
||||
Span<Vector3> morphPositions = null;
|
||||
Span<Vector3> morphNormals = null;
|
||||
Span<Vector2> morphUVs = null;
|
||||
foreach (var morph in mesh.MorphTargets)
|
||||
{
|
||||
morphPositions = morph.VertexBuffer.Positions != null ? morph.VertexBuffer.Positions.GetSpan<Vector3>() : null;
|
||||
morphNormals = morph.VertexBuffer.Normals != null ? morph.VertexBuffer.Normals.GetSpan<Vector3>() : null;
|
||||
morphUVs = morph.VertexBuffer.TexCoords != null ? morph.VertexBuffer.TexCoords.GetSpan<Vector2>() : null;
|
||||
|
||||
for (int i = 0, targetIndex = 0; i < positions.Length; i++)
|
||||
{
|
||||
if (removeIndexHashSet.Contains(i)) continue;
|
||||
|
||||
if (morphPositions != null) newPositions[targetIndex] = morphPositions[i];
|
||||
if (morphNormals != null) newNormals[targetIndex] = morphNormals[i];
|
||||
if (morphUVs != null) newUVs[targetIndex] = morphUVs[i];
|
||||
targetIndex++;
|
||||
}
|
||||
|
||||
if (morphPositions != null) morph.VertexBuffer.Positions.Assign(newPositions.AsSpan());
|
||||
if (morphNormals != null) morph.VertexBuffer.Normals.Assign(newNormals.AsSpan());
|
||||
if (morphUVs != null) morph.VertexBuffer.Normals.Assign(newUVs.AsSpan());
|
||||
morph.VertexBuffer.RemoveTangent();
|
||||
morph.VertexBuffer.ValidateLength(morph.Name);
|
||||
}
|
||||
|
||||
return $"({before} => {resizedLength})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 統合できる同一とみなせる頂点を探す
|
||||
/// </summary>
|
||||
private static void FindMergeTargetVertices(SpanLike<Vertex> vertices)
|
||||
{
|
||||
var sameVertices = new HashSet<Vertex>();
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
sameVertices.Clear();
|
||||
var v0 = vertices[i];
|
||||
int minIndex = v0.Index;
|
||||
if (v0.targetIndex > 0) continue;
|
||||
for (int j = i + 1; j < vertices.Length; j++)
|
||||
{
|
||||
var v1 = vertices[j];
|
||||
|
||||
if (v1.targetIndex > 0) continue;
|
||||
|
||||
if (!v0.Equals(v1)) continue;
|
||||
|
||||
if (!sameVertices.Contains(v0)) sameVertices.Add(v0);
|
||||
if (!sameVertices.Contains(v1))
|
||||
{
|
||||
sameVertices.Add(v1);
|
||||
if (v1.Index < minIndex) minIndex = v1.Index;
|
||||
}
|
||||
}
|
||||
|
||||
if (sameVertices.Count() >= 2)
|
||||
{
|
||||
foreach (var v in sameVertices)
|
||||
{
|
||||
v.targetIndex = minIndex;
|
||||
|
||||
if (v.IsMergeTarget)
|
||||
{
|
||||
Vector3? n = Vector3.Zero;
|
||||
Vector4? c = Vector4.Zero;
|
||||
|
||||
foreach (var vert in sameVertices)
|
||||
{
|
||||
if (vert.Normal.HasValue) n += vert.Normal;
|
||||
if (vert.Color.HasValue) c += vert.Color;
|
||||
|
||||
}
|
||||
if (n.HasValue) Vector3.Normalize(n.Value);
|
||||
if (c.HasValue) c = c.Value / sameVertices.Count();
|
||||
|
||||
|
||||
v.Normal = n;
|
||||
v.Color = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void ReorderSubmeshes(this Mesh mesh, List<Submesh> order)
|
||||
{
|
||||
if (mesh.Submeshes.Count != order.Count)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
mesh.Submeshes.Clear();
|
||||
mesh.Submeshes.AddRange(order);
|
||||
|
||||
// index buffer を作り直す
|
||||
mesh.IndexBuffer = new SubmeshReorderMapper(mesh.Triangles).CreateIndexBuffer();
|
||||
|
||||
// submesh の offset を正規化する
|
||||
var offset = 0;
|
||||
for (int i = 0; i < mesh.Submeshes.Count; ++i)
|
||||
{
|
||||
var submesh = mesh.Submeshes[i];
|
||||
submesh.Offset = offset;
|
||||
offset += submesh.DrawCount;
|
||||
}
|
||||
|
||||
{
|
||||
// 連続して同じMaterialのSubMeshを連結する
|
||||
mesh.Submeshes.Clear();
|
||||
int current = -1;
|
||||
foreach (var submesh in order)
|
||||
{
|
||||
if (current != submesh.Material)
|
||||
{
|
||||
current = submesh.Material;
|
||||
mesh.Submeshes.Add(submesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh.Submeshes.Last().DrawCount += submesh.DrawCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1d56612ad117ef64fb1e374d054f449b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
43
Assets/VRM10/vrmlib/Runtime/MeshGroup.cs
Normal file
43
Assets/VRM10/vrmlib/Runtime/MeshGroup.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public class MeshGroup : GltfId
|
||||
{
|
||||
public readonly string Name;
|
||||
|
||||
public readonly List<Mesh> Meshes = new List<Mesh>();
|
||||
|
||||
public Skin Skin;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(Name);
|
||||
if (Skin != null)
|
||||
{
|
||||
sb.Append("(skinned)");
|
||||
}
|
||||
var isFirst = true;
|
||||
foreach (var mesh in Meshes)
|
||||
{
|
||||
if (isFirst)
|
||||
{
|
||||
isFirst = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
sb.Append(mesh);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public MeshGroup(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 66fe20a63e11a3b4fb734a57c5a71839
|
||||
guid: 8d578a2f92d3c57439dc109ec5986c13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class MeshGroupExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// MorphTarget が有る Mesh と無い Mesh に分ける
|
||||
/// </summary>
|
||||
/// <param name="g"></param>
|
||||
/// <returns></returns>
|
||||
public static ValueTuple<MeshGroup, MeshGroup> SepareteByMorphTarget(this MeshGroup g)
|
||||
{
|
||||
if (g.Meshes.Count > 1)
|
||||
{
|
||||
throw new NotImplementedException("MeshGroup.Meshes.Count must 1");
|
||||
}
|
||||
|
||||
var src = g.Meshes[0];
|
||||
if (src.Topology != TopologyType.Triangles)
|
||||
{
|
||||
throw new InvalidOperationException("not GltfPrimitiveMode.Triangles");
|
||||
}
|
||||
|
||||
var (withTriangles, withoutTriangles) = MeshSplitter.SplitTriangles(src);
|
||||
|
||||
MeshGroup with = default(MeshGroup);
|
||||
if (withTriangles.Any())
|
||||
{
|
||||
var mesh = MeshSplitter.SeparateMesh(src, withTriangles, true);
|
||||
with = new MeshGroup(g.Name + ".morphtarget")
|
||||
{
|
||||
Skin = g.Skin,
|
||||
};
|
||||
with.Meshes.Add(mesh);
|
||||
}
|
||||
|
||||
MeshGroup without = default(MeshGroup);
|
||||
if (withoutTriangles.Any())
|
||||
{
|
||||
var mesh = MeshSplitter.SeparateMesh(src, withoutTriangles);
|
||||
without = new MeshGroup(g.Name)
|
||||
{
|
||||
Skin = g.Skin,
|
||||
};
|
||||
without.Meshes.Add(mesh);
|
||||
}
|
||||
|
||||
return (with, without);
|
||||
}
|
||||
|
||||
public static ValueTuple<MeshGroup, MeshGroup> SepareteByHeadBone(this MeshGroup g, HashSet<int> boneIndices)
|
||||
{
|
||||
if (g.Meshes.Count > 1)
|
||||
{
|
||||
throw new NotImplementedException("MeshGroup.Meshes.Count must 1");
|
||||
}
|
||||
|
||||
var src = g.Meshes[0];
|
||||
if (src.Topology != TopologyType.Triangles)
|
||||
{
|
||||
throw new InvalidOperationException("not GltfPrimitiveMode.Triangles");
|
||||
}
|
||||
|
||||
var (headTriangles, bodyTriangles) = MeshSplitter.SplitTrianglesByBoneIndices(src, boneIndices);
|
||||
|
||||
MeshGroup head = default(MeshGroup);
|
||||
if (headTriangles.Any())
|
||||
{
|
||||
var mesh = MeshSplitter.SeparateMesh(src, headTriangles, true);
|
||||
head = new MeshGroup(g.Name + ".headMesh")
|
||||
{
|
||||
Skin = g.Skin,
|
||||
};
|
||||
head.Meshes.Add(mesh);
|
||||
}
|
||||
|
||||
MeshGroup body = default(MeshGroup);
|
||||
if (bodyTriangles.Any())
|
||||
{
|
||||
var mesh = MeshSplitter.SeparateMesh(src, bodyTriangles);
|
||||
body = new MeshGroup(g.Name)
|
||||
{
|
||||
Skin = g.Skin,
|
||||
};
|
||||
body.Meshes.Add(mesh);
|
||||
}
|
||||
|
||||
return (head, body);
|
||||
}
|
||||
|
||||
public static MeshGroup Clone(this MeshGroup src)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
// var dst = new MeshGroup(src.Name);
|
||||
|
||||
// return dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,267 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using UniGLTF;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
class MeshSplitter
|
||||
{
|
||||
public static ValueTuple<
|
||||
List<ValueTuple<int, Triangle>>,
|
||||
List<ValueTuple<int, Triangle>>> SplitTriangles(Mesh src)
|
||||
{
|
||||
var triangles = src.Triangles.ToArray();
|
||||
var morphUseCount = new int[triangles.Length];
|
||||
|
||||
// 各モーフ
|
||||
foreach (var morph in src.MorphTargets)
|
||||
{
|
||||
if (morph.VertexBuffer.Count == 0)
|
||||
{
|
||||
// 無効
|
||||
continue;
|
||||
}
|
||||
// POSITIONが使っているtriangleのカウンターをアップさせる
|
||||
var positions = SpanLike.Wrap<Vector3>(morph.VertexBuffer.Positions.Bytes);
|
||||
for (int i = 0; i < triangles.Length; ++i)
|
||||
{
|
||||
ref var triangle = ref triangles[i];
|
||||
if (positions[triangle.Item2.Vertex0] != Vector3.Zero)
|
||||
{
|
||||
++morphUseCount[i];
|
||||
}
|
||||
if (positions[triangle.Item2.Vertex1] != Vector3.Zero)
|
||||
{
|
||||
++morphUseCount[i];
|
||||
}
|
||||
if (positions[triangle.Item2.Vertex2] != Vector3.Zero)
|
||||
{
|
||||
++morphUseCount[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var withTriangles = new List<ValueTuple<int, Triangle>>();
|
||||
var withoutTriangles = new List<ValueTuple<int, Triangle>>();
|
||||
for (int i = 0; i < triangles.Length; ++i)
|
||||
{
|
||||
if (morphUseCount[i] > 0)
|
||||
{
|
||||
// モーフで使われている
|
||||
withTriangles.Add(triangles[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// モーフで使われない
|
||||
withoutTriangles.Add(triangles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return (withTriangles, withoutTriangles);
|
||||
}
|
||||
|
||||
public static ValueTuple<
|
||||
List<ValueTuple<int, Triangle>>,
|
||||
List<ValueTuple<int, Triangle>>> SplitTrianglesByBoneIndices(Mesh src, HashSet<int> targetBoneIndices)
|
||||
{
|
||||
var triangles = src.Triangles.ToArray();
|
||||
var isBodyMeshTriangle = new bool[triangles.Length];
|
||||
|
||||
var skinJointsSpan = SpanLike.Wrap<SkinJoints>(src.VertexBuffer.Joints.Bytes);
|
||||
var boneWeightSpan = SpanLike.Wrap<Vector4>(src.VertexBuffer.Weights.Bytes);
|
||||
|
||||
for (int i = 0; i < triangles.Length; ++i)
|
||||
{
|
||||
ref var triangle = ref triangles[i];
|
||||
|
||||
var skinJoints0 = skinJointsSpan[triangle.Item2.Vertex0];
|
||||
var boneWeights0 = boneWeightSpan[triangle.Item2.Vertex0];
|
||||
|
||||
if (boneWeights0.X > 0 && targetBoneIndices.Contains(skinJoints0.Joint0)) continue;
|
||||
if (boneWeights0.Y > 0 && targetBoneIndices.Contains(skinJoints0.Joint1)) continue;
|
||||
if (boneWeights0.Z > 0 && targetBoneIndices.Contains(skinJoints0.Joint2)) continue;
|
||||
if (boneWeights0.W > 0 && targetBoneIndices.Contains(skinJoints0.Joint3)) continue;
|
||||
|
||||
var skinJoints1 = skinJointsSpan[triangle.Item2.Vertex1];
|
||||
var boneWeights1 = boneWeightSpan[triangle.Item2.Vertex1];
|
||||
|
||||
if (boneWeights1.X > 0 && targetBoneIndices.Contains(skinJoints1.Joint0)) continue;
|
||||
if (boneWeights1.Y > 0 && targetBoneIndices.Contains(skinJoints1.Joint1)) continue;
|
||||
if (boneWeights1.Z > 0 && targetBoneIndices.Contains(skinJoints1.Joint2)) continue;
|
||||
if (boneWeights1.W > 0 && targetBoneIndices.Contains(skinJoints1.Joint3)) continue;
|
||||
|
||||
var skinJoints2 = skinJointsSpan[triangle.Item2.Vertex2];
|
||||
var boneWeights2 = boneWeightSpan[triangle.Item2.Vertex2];
|
||||
|
||||
if (boneWeights2.X > 0 && targetBoneIndices.Contains(skinJoints2.Joint0)) continue;
|
||||
if (boneWeights2.Y > 0 && targetBoneIndices.Contains(skinJoints2.Joint1)) continue;
|
||||
if (boneWeights2.Z > 0 && targetBoneIndices.Contains(skinJoints2.Joint2)) continue;
|
||||
if (boneWeights2.W > 0 && targetBoneIndices.Contains(skinJoints2.Joint3)) continue;
|
||||
|
||||
isBodyMeshTriangle[i] = true;
|
||||
}
|
||||
|
||||
var bodyTriangles = new List<ValueTuple<int, Triangle>>();
|
||||
var headTriangles = new List<ValueTuple<int, Triangle>>();
|
||||
|
||||
for (int i = 0; i < triangles.Length; ++i)
|
||||
{
|
||||
if (isBodyMeshTriangle[i])
|
||||
{
|
||||
// 胴体メッシュ
|
||||
bodyTriangles.Add(triangles[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 頭メッシュ
|
||||
headTriangles.Add(triangles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return (headTriangles, bodyTriangles);
|
||||
}
|
||||
|
||||
class VertexReorderMapper
|
||||
{
|
||||
public readonly List<int> IndexMap = new List<int>();
|
||||
|
||||
public BufferAccessor MapBuffer<T>(BufferAccessor srcBuffer) where T : struct
|
||||
{
|
||||
var src = SpanLike.Wrap<T>(srcBuffer.Bytes);
|
||||
var dstBytes = new byte[srcBuffer.Stride * IndexMap.Count];
|
||||
var dst = SpanLike.Wrap<T>(new ArraySegment<byte>(dstBytes));
|
||||
for (int i = 0; i < IndexMap.Count; ++i)
|
||||
{
|
||||
dst[i] = src[IndexMap[i]];
|
||||
}
|
||||
return new BufferAccessor(new ArraySegment<byte>(dstBytes), srcBuffer.ComponentType, srcBuffer.AccessorType, IndexMap.Count);
|
||||
}
|
||||
|
||||
public VertexBuffer Map(VertexBuffer src)
|
||||
{
|
||||
var dst = new VertexBuffer();
|
||||
foreach (var (semantic, buffer) in src)
|
||||
{
|
||||
BufferAccessor mapped = null;
|
||||
if (buffer.ComponentType == AccessorValueType.FLOAT)
|
||||
{
|
||||
switch (buffer.AccessorType)
|
||||
{
|
||||
case AccessorVectorType.VEC2:
|
||||
mapped = MapBuffer<Vector2>(buffer);
|
||||
break;
|
||||
|
||||
case AccessorVectorType.VEC3:
|
||||
mapped = MapBuffer<Vector3>(buffer);
|
||||
break;
|
||||
|
||||
case AccessorVectorType.VEC4:
|
||||
mapped = MapBuffer<Vector4>(buffer);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
else if (buffer.ComponentType == AccessorValueType.UNSIGNED_SHORT)
|
||||
{
|
||||
if (buffer.AccessorType == AccessorVectorType.VEC4)
|
||||
{
|
||||
mapped = MapBuffer<SkinJoints>(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
dst.Add(semantic, mapped);
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
public readonly List<int> Indices = new List<int>();
|
||||
|
||||
public ArraySegment<byte> IndexBytes
|
||||
{
|
||||
get
|
||||
{
|
||||
var bytes = new byte[Indices.Count * 4];
|
||||
var span = SpanLike.Wrap<Int32>(new ArraySegment<byte>(bytes));
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = Indices[i];
|
||||
}
|
||||
return new ArraySegment<byte>(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
public BufferAccessor CreateIndexBuffer()
|
||||
{
|
||||
return new BufferAccessor(IndexBytes,
|
||||
AccessorValueType.UNSIGNED_INT, AccessorVectorType.SCALAR, Indices.Count);
|
||||
}
|
||||
|
||||
void PushIndex(int src)
|
||||
{
|
||||
int index = IndexMap.IndexOf(src);
|
||||
if (index == -1)
|
||||
{
|
||||
// index to src map
|
||||
index = IndexMap.Count;
|
||||
IndexMap.Add(src);
|
||||
}
|
||||
Indices.Add(index);
|
||||
}
|
||||
|
||||
public VertexReorderMapper(IEnumerable<ValueTuple<int, Triangle>> submeshTriangles)
|
||||
{
|
||||
foreach (var t in submeshTriangles)
|
||||
{
|
||||
PushIndex(t.Item2.Vertex0);
|
||||
PushIndex(t.Item2.Vertex1);
|
||||
PushIndex(t.Item2.Vertex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static Mesh SeparateMesh(Mesh src, IEnumerable<ValueTuple<int, Triangle>> submeshTriangles,
|
||||
bool includeMorphTarget = false)
|
||||
{
|
||||
var mapper = new VertexReorderMapper(submeshTriangles);
|
||||
|
||||
var mesh = new Mesh
|
||||
{
|
||||
Topology = TopologyType.Triangles,
|
||||
IndexBuffer = mapper.CreateIndexBuffer(),
|
||||
VertexBuffer = mapper.Map(src.VertexBuffer),
|
||||
};
|
||||
|
||||
var offset = 0;
|
||||
foreach (var triangles in submeshTriangles.GroupBy(x => x.Item1, x => x).OrderBy(x => x.Key))
|
||||
{
|
||||
var count = triangles.Count() * 3;
|
||||
mesh.Submeshes.Add(new Submesh(offset, count, src.Submeshes[triangles.Key].Material));
|
||||
offset += count;
|
||||
}
|
||||
|
||||
if (includeMorphTarget)
|
||||
{
|
||||
foreach (var target in src.MorphTargets)
|
||||
{
|
||||
mesh.MorphTargets.Add(new MorphTarget(target.Name)
|
||||
{
|
||||
VertexBuffer = mapper.Map(target.VertexBuffer)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f3f90205124143446bab06f56827a6ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using UniGLTF;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
|
|
@ -165,5 +167,451 @@ namespace VrmLib
|
|||
|
||||
return current;
|
||||
}
|
||||
|
||||
public void ApplyRotationAndScale()
|
||||
{
|
||||
// worldPositionを記録する
|
||||
var m_positionMap = Nodes.ToDictionary(x => x, x => x.Translation);
|
||||
|
||||
// 回転・拡縮を除去する
|
||||
// 木構造の根元から実行する
|
||||
// Rootは編集対象外
|
||||
foreach (var node in Root.Traverse().Skip(1))
|
||||
{
|
||||
// 回転・拡縮を除去
|
||||
if (m_positionMap.TryGetValue(node, out Vector3 pos))
|
||||
{
|
||||
var t = Matrix4x4.CreateTranslation(pos);
|
||||
node.SetMatrix(t, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Node
|
||||
public void NodeAdd(Node node, Node parent = null)
|
||||
{
|
||||
if (parent is null)
|
||||
{
|
||||
parent = this.Root;
|
||||
}
|
||||
parent.Add(node);
|
||||
if (this.Nodes.Contains(node))
|
||||
{
|
||||
throw new ArgumentException($"Nodes contain {node}");
|
||||
}
|
||||
this.Nodes.Add(node);
|
||||
}
|
||||
|
||||
public void NodeRemove(Node remove)
|
||||
{
|
||||
foreach (var node in this.Nodes)
|
||||
{
|
||||
if (node.Parent == remove)
|
||||
{
|
||||
remove.Remove(node);
|
||||
}
|
||||
if (remove.Parent == node)
|
||||
{
|
||||
node.Remove(remove);
|
||||
}
|
||||
}
|
||||
if (this.Root.Children.Contains(remove))
|
||||
{
|
||||
this.Root.Remove(remove);
|
||||
}
|
||||
|
||||
this.Nodes.Remove(remove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nodeを置き換える。参照を置換する。
|
||||
/// </summary>
|
||||
public void NodeReplace(Node src, Node dst)
|
||||
{
|
||||
if (src == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (dst == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
// add dst same parent
|
||||
src.Parent.Add(dst, ChildMatrixMode.KeepWorld);
|
||||
|
||||
// remove all child
|
||||
foreach (var child in src.Children.ToArray())
|
||||
{
|
||||
dst.Add(child, ChildMatrixMode.KeepWorld);
|
||||
}
|
||||
|
||||
// remove from parent
|
||||
src.Parent.Remove(src);
|
||||
this.Nodes.Remove(src);
|
||||
|
||||
// remove from skinning
|
||||
foreach (var skin in this.Skins)
|
||||
{
|
||||
skin.Replace(src, dst);
|
||||
}
|
||||
|
||||
// fix animation reference
|
||||
foreach (var animation in this.Animations)
|
||||
{
|
||||
if (animation.NodeMap.TryGetValue(src, out NodeAnimation nodeAnimation))
|
||||
{
|
||||
animation.NodeMap.Remove(src);
|
||||
animation.NodeMap.Add(dst, nodeAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.Nodes.Contains(dst))
|
||||
{
|
||||
throw new Exception("already exists");
|
||||
}
|
||||
this.Nodes.Add(dst);
|
||||
|
||||
// TODO: SpringBone
|
||||
}
|
||||
#endregion
|
||||
|
||||
public string SkinningBake()
|
||||
{
|
||||
foreach (var node in this.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 回転・拡縮を除去する
|
||||
this.ApplyRotationAndScale();
|
||||
|
||||
// inverse matrix の再計算
|
||||
foreach (var node in this.Nodes)
|
||||
{
|
||||
var meshGroup = node.MeshGroup;
|
||||
if (meshGroup == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var mesh in meshGroup.Meshes)
|
||||
{
|
||||
if (meshGroup.Skin != null)
|
||||
{
|
||||
meshGroup.Skin.CalcInverseMatrices();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "SkinningBake";
|
||||
}
|
||||
|
||||
static void ReverseX(BufferAccessor ba)
|
||||
{
|
||||
if (ba.ComponentType != AccessorValueType.FLOAT)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (ba.AccessorType == AccessorVectorType.VEC3)
|
||||
{
|
||||
var span = SpanLike.Wrap<Vector3>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseX();
|
||||
}
|
||||
}
|
||||
else if (ba.AccessorType == AccessorVectorType.MAT4)
|
||||
{
|
||||
var span = SpanLike.Wrap<Matrix4x4>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseX();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
static void ReverseZ(BufferAccessor ba)
|
||||
{
|
||||
if (ba.ComponentType != AccessorValueType.FLOAT)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (ba.AccessorType == AccessorVectorType.VEC3)
|
||||
{
|
||||
var span = SpanLike.Wrap<Vector3>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseZ();
|
||||
}
|
||||
}
|
||||
else if (ba.AccessorType == AccessorVectorType.MAT4)
|
||||
{
|
||||
var span = SpanLike.Wrap<Matrix4x4>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseZ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
struct Reverser
|
||||
{
|
||||
public Action<BufferAccessor> ReverseBuffer;
|
||||
public Func<Vector3, Vector3> ReverseVector3;
|
||||
public Func<Matrix4x4, Matrix4x4> ReverseMatrix;
|
||||
}
|
||||
|
||||
static Reverser ZReverser => new Reverser
|
||||
{
|
||||
ReverseBuffer = ReverseZ,
|
||||
ReverseVector3 = v => v.ReverseZ(),
|
||||
ReverseMatrix = m => m.ReverseZ(),
|
||||
};
|
||||
|
||||
static Reverser XReverser => new Reverser
|
||||
{
|
||||
ReverseBuffer = ReverseX,
|
||||
ReverseVector3 = v => v.ReverseX(),
|
||||
ReverseMatrix = m => m.ReverseX(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// ignoreVrm: VRM-0.XX では無変換で入出力してた。VRM-1.0 では変換する。
|
||||
/// </summary>
|
||||
public void ConvertCoordinate(Coordinates coordinates, bool ignoreVrm = false)
|
||||
{
|
||||
if (Coordinates.Equals(coordinates))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Coordinates.IsVrm0 && coordinates.IsUnity)
|
||||
{
|
||||
ReverseAxisAndFlipTriangle(ZReverser, ignoreVrm);
|
||||
UVVerticalFlip();
|
||||
Coordinates = coordinates;
|
||||
}
|
||||
else if (Coordinates.IsUnity && coordinates.IsVrm0)
|
||||
{
|
||||
ReverseAxisAndFlipTriangle(ZReverser, ignoreVrm);
|
||||
UVVerticalFlip();
|
||||
Coordinates = coordinates;
|
||||
}
|
||||
else if (Coordinates.IsVrm1 && coordinates.IsUnity)
|
||||
{
|
||||
ReverseAxisAndFlipTriangle(XReverser, ignoreVrm);
|
||||
UVVerticalFlip();
|
||||
Coordinates = coordinates;
|
||||
}
|
||||
else if (Coordinates.IsUnity && coordinates.IsVrm1)
|
||||
{
|
||||
ReverseAxisAndFlipTriangle(XReverser, ignoreVrm);
|
||||
UVVerticalFlip();
|
||||
Coordinates = coordinates;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UVのVを反転する。 => V = 1.0 - V
|
||||
/// </summary>
|
||||
void UVVerticalFlip()
|
||||
{
|
||||
foreach (var g in MeshGroups)
|
||||
{
|
||||
foreach (var m in g.Meshes)
|
||||
{
|
||||
var uv = m.VertexBuffer.TexCoords;
|
||||
if (uv != null)
|
||||
{
|
||||
var span = SpanLike.Wrap<Vector2>(uv.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].UVVerticalFlip();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// * Position, Normal の Z座標に -1 を乗算する
|
||||
/// * Rotation => Axis Angle に分解 => Axis の Z座標に -1 を乗算。Angle に -1 を乗算
|
||||
/// * Triangle の index を 0, 1, 2 から 2, 1, 0 に反転する
|
||||
/// </summary>
|
||||
void ReverseAxisAndFlipTriangle(Reverser reverser, bool ignoreVrm)
|
||||
{
|
||||
// 複数の gltf.accessor が別の要素間で共有されている場合に、2回処理されることを防ぐ
|
||||
// edgecase: InverseBindMatrices で遭遇
|
||||
var unique = new HashSet<ArraySegment<byte>>();
|
||||
|
||||
foreach (var g in MeshGroups)
|
||||
{
|
||||
foreach (var m in g.Meshes)
|
||||
{
|
||||
foreach (var (k, v) in m.VertexBuffer)
|
||||
{
|
||||
if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
|
||||
{
|
||||
if (unique.Add(v.Bytes))
|
||||
{
|
||||
reverser.ReverseBuffer(v);
|
||||
}
|
||||
}
|
||||
else if (k == VertexBuffer.TangentKey)
|
||||
{
|
||||
// I don't know
|
||||
}
|
||||
}
|
||||
|
||||
if (unique.Add(m.IndexBuffer.Bytes))
|
||||
{
|
||||
switch (m.IndexBuffer.ComponentType)
|
||||
{
|
||||
case AccessorValueType.UNSIGNED_BYTE:
|
||||
FlipTriangle(SpanLike.Wrap<Byte>(m.IndexBuffer.Bytes));
|
||||
break;
|
||||
case AccessorValueType.UNSIGNED_SHORT:
|
||||
FlipTriangle(SpanLike.Wrap<UInt16>(m.IndexBuffer.Bytes));
|
||||
break;
|
||||
case AccessorValueType.UNSIGNED_INT:
|
||||
FlipTriangle(SpanLike.Wrap<UInt32>(m.IndexBuffer.Bytes));
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mt in m.MorphTargets)
|
||||
{
|
||||
foreach (var (k, v) in mt.VertexBuffer)
|
||||
{
|
||||
if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
|
||||
{
|
||||
if (unique.Add(v.Bytes))
|
||||
{
|
||||
reverser.ReverseBuffer(v);
|
||||
}
|
||||
}
|
||||
if (k == VertexBuffer.TangentKey)
|
||||
{
|
||||
// I don't know
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 親から順に処理する
|
||||
// Rootは原点決め打ちのノード(GLTFに含まれない)
|
||||
foreach (var n in Root.Traverse().Skip(1))
|
||||
{
|
||||
n.SetMatrix(reverser.ReverseMatrix(n.Matrix), false);
|
||||
}
|
||||
// 親から順に処理したので不要
|
||||
// Root.CalcWorldMatrix();
|
||||
|
||||
foreach (var s in Skins)
|
||||
{
|
||||
if (s.InverseMatrices != null)
|
||||
{
|
||||
if (unique.Add(s.InverseMatrices.Bytes))
|
||||
{
|
||||
reverser.ReverseBuffer(s.InverseMatrices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var a in Animations)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
}
|
||||
|
||||
static void FlipTriangle(SpanLike<byte> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
// 0, 1, 2 to 2, 1, 0
|
||||
var tmp = indices[i + 2];
|
||||
indices[i + 2] = indices[i];
|
||||
indices[i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void FlipTriangle(SpanLike<ushort> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
// 0, 1, 2 to 2, 1, 0
|
||||
var tmp = indices[i + 2];
|
||||
indices[i + 2] = indices[i];
|
||||
indices[i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void FlipTriangle(SpanLike<uint> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
// 0, 1, 2 to 2, 1, 0
|
||||
var tmp = indices[i + 2];
|
||||
indices[i + 2] = indices[i];
|
||||
indices[i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class ModelExtensions
|
||||
{
|
||||
public static void ApplyRotationAndScale(this Model model)
|
||||
{
|
||||
// worldPositionを記録する
|
||||
var m_positionMap = model.Nodes.ToDictionary(x => x, x => x.Translation);
|
||||
|
||||
// 回転・拡縮を除去する
|
||||
// 木構造の根元から実行する
|
||||
// Rootは編集対象外
|
||||
foreach (var node in model.Root.Traverse().Skip(1))
|
||||
{
|
||||
// 回転・拡縮を除去
|
||||
if (m_positionMap.TryGetValue(node, out Vector3 pos))
|
||||
{
|
||||
var t = Matrix4x4.CreateTranslation(pos);
|
||||
node.SetMatrix(t, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
// [Debug向け]secondaryを除去
|
||||
/// </summary>
|
||||
public static void RemoveSecondary(this Model model)
|
||||
{
|
||||
var secondary = model.Nodes
|
||||
.FirstOrDefault(x =>
|
||||
(x.Name == "secondary" || x.Name == "SpringBone")
|
||||
&& x.Parent == model.Root
|
||||
&& x.Children.Count == 0)
|
||||
;
|
||||
if (secondary != null)
|
||||
{
|
||||
var mod = new ModelModifier(model);
|
||||
mod.NodeRemove(secondary);
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckIndex<T>(List<T> list, string name) where T : GltfId
|
||||
{
|
||||
for (int i = 0; i < list.Count; ++i)
|
||||
{
|
||||
if (list[i].GltfIndex.HasValue)
|
||||
{
|
||||
if (list[i].GltfIndex.Value == i)
|
||||
{
|
||||
Console.WriteLine($"{name}[{i}] => OK");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{name}[{i}] => {list[i].GltfIndex}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"{name}[{i}] => null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CheckIndex(this Model model)
|
||||
{
|
||||
CheckIndex(model.Nodes, nameof(model.Nodes));
|
||||
CheckIndex(model.Skins, nameof(model.Skins));
|
||||
CheckIndex(model.MeshGroups, nameof(model.MeshGroups));
|
||||
CheckIndex(model.Animations, nameof(model.Animations));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: abaf884e0faa5d040ad4eae16120cb46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using UniGLTF;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class ModelExtensionsForCoordinates
|
||||
{
|
||||
static void ReverseX(BufferAccessor ba)
|
||||
{
|
||||
if (ba.ComponentType != AccessorValueType.FLOAT)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (ba.AccessorType == AccessorVectorType.VEC3)
|
||||
{
|
||||
var span = SpanLike.Wrap<Vector3>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseX();
|
||||
}
|
||||
}
|
||||
else if (ba.AccessorType == AccessorVectorType.MAT4)
|
||||
{
|
||||
var span = SpanLike.Wrap<Matrix4x4>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseX();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
static void ReverseZ(BufferAccessor ba)
|
||||
{
|
||||
if (ba.ComponentType != AccessorValueType.FLOAT)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (ba.AccessorType == AccessorVectorType.VEC3)
|
||||
{
|
||||
var span = SpanLike.Wrap<Vector3>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseZ();
|
||||
}
|
||||
}
|
||||
else if (ba.AccessorType == AccessorVectorType.MAT4)
|
||||
{
|
||||
var span = SpanLike.Wrap<Matrix4x4>(ba.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].ReverseZ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
struct Reverser
|
||||
{
|
||||
public Action<BufferAccessor> ReverseBuffer;
|
||||
public Func<Vector3, Vector3> ReverseVector3;
|
||||
public Func<Matrix4x4, Matrix4x4> ReverseMatrix;
|
||||
}
|
||||
|
||||
static Reverser ZReverser => new Reverser
|
||||
{
|
||||
ReverseBuffer = ReverseZ,
|
||||
ReverseVector3 = v => v.ReverseZ(),
|
||||
ReverseMatrix = m => m.ReverseZ(),
|
||||
};
|
||||
|
||||
static Reverser XReverser => new Reverser
|
||||
{
|
||||
ReverseBuffer = ReverseX,
|
||||
ReverseVector3 = v => v.ReverseX(),
|
||||
ReverseMatrix = m => m.ReverseX(),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// ignoreVrm: VRM-0.XX では無変換で入出力してた。VRM-1.0 では変換する。
|
||||
/// </summary>
|
||||
public static void ConvertCoordinate(this Model model, Coordinates coordinates, bool ignoreVrm = false)
|
||||
{
|
||||
if (model.Coordinates.Equals(coordinates))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.Coordinates.IsVrm0 && coordinates.IsUnity)
|
||||
{
|
||||
model.ReverseAxisAndFlipTriangle(ZReverser, ignoreVrm);
|
||||
model.UVVerticalFlip();
|
||||
model.Coordinates = coordinates;
|
||||
}
|
||||
else if (model.Coordinates.IsUnity && coordinates.IsVrm0)
|
||||
{
|
||||
model.ReverseAxisAndFlipTriangle(ZReverser, ignoreVrm);
|
||||
model.UVVerticalFlip();
|
||||
model.Coordinates = coordinates;
|
||||
}
|
||||
else if (model.Coordinates.IsVrm1 && coordinates.IsUnity)
|
||||
{
|
||||
model.ReverseAxisAndFlipTriangle(XReverser, ignoreVrm);
|
||||
model.UVVerticalFlip();
|
||||
model.Coordinates = coordinates;
|
||||
}
|
||||
else if (model.Coordinates.IsUnity && coordinates.IsVrm1)
|
||||
{
|
||||
model.ReverseAxisAndFlipTriangle(XReverser, ignoreVrm);
|
||||
model.UVVerticalFlip();
|
||||
model.Coordinates = coordinates;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UVのVを反転する。 => V = 1.0 - V
|
||||
/// </summary>
|
||||
static void UVVerticalFlip(this Model model)
|
||||
{
|
||||
foreach (var g in model.MeshGroups)
|
||||
{
|
||||
foreach (var m in g.Meshes)
|
||||
{
|
||||
var uv = m.VertexBuffer.TexCoords;
|
||||
if (uv != null)
|
||||
{
|
||||
var span = SpanLike.Wrap<Vector2>(uv.Bytes);
|
||||
for (int i = 0; i < span.Length; ++i)
|
||||
{
|
||||
span[i] = span[i].UVVerticalFlip();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// * Position, Normal の Z座標に -1 を乗算する
|
||||
/// * Rotation => Axis Angle に分解 => Axis の Z座標に -1 を乗算。Angle に -1 を乗算
|
||||
/// * Triangle の index を 0, 1, 2 から 2, 1, 0 に反転する
|
||||
/// </summary>
|
||||
static void ReverseAxisAndFlipTriangle(this Model model, Reverser reverser, bool ignoreVrm)
|
||||
{
|
||||
// 複数の gltf.accessor が別の要素間で共有されている場合に、2回処理されることを防ぐ
|
||||
// edgecase: InverseBindMatrices で遭遇
|
||||
var unique = new HashSet<ArraySegment<byte>>();
|
||||
|
||||
foreach (var g in model.MeshGroups)
|
||||
{
|
||||
foreach (var m in g.Meshes)
|
||||
{
|
||||
foreach (var (k, v) in m.VertexBuffer)
|
||||
{
|
||||
if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
|
||||
{
|
||||
if (unique.Add(v.Bytes))
|
||||
{
|
||||
reverser.ReverseBuffer(v);
|
||||
}
|
||||
}
|
||||
else if (k == VertexBuffer.TangentKey)
|
||||
{
|
||||
// I don't know
|
||||
}
|
||||
}
|
||||
|
||||
if (unique.Add(m.IndexBuffer.Bytes))
|
||||
{
|
||||
switch (m.IndexBuffer.ComponentType)
|
||||
{
|
||||
case AccessorValueType.UNSIGNED_BYTE:
|
||||
FlipTriangle(SpanLike.Wrap<Byte>(m.IndexBuffer.Bytes));
|
||||
break;
|
||||
case AccessorValueType.UNSIGNED_SHORT:
|
||||
FlipTriangle(SpanLike.Wrap<UInt16>(m.IndexBuffer.Bytes));
|
||||
break;
|
||||
case AccessorValueType.UNSIGNED_INT:
|
||||
FlipTriangle(SpanLike.Wrap<UInt32>(m.IndexBuffer.Bytes));
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mt in m.MorphTargets)
|
||||
{
|
||||
foreach (var (k, v) in mt.VertexBuffer)
|
||||
{
|
||||
if (k == VertexBuffer.PositionKey || k == VertexBuffer.NormalKey)
|
||||
{
|
||||
if (unique.Add(v.Bytes))
|
||||
{
|
||||
reverser.ReverseBuffer(v);
|
||||
}
|
||||
}
|
||||
if (k == VertexBuffer.TangentKey)
|
||||
{
|
||||
// I don't know
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 親から順に処理する
|
||||
// Rootは原点決め打ちのノード(GLTFに含まれない)
|
||||
foreach (var n in model.Root.Traverse().Skip(1))
|
||||
{
|
||||
n.SetMatrix(reverser.ReverseMatrix(n.Matrix), false);
|
||||
}
|
||||
// 親から順に処理したので不要
|
||||
// model.Root.CalcWorldMatrix();
|
||||
|
||||
foreach (var s in model.Skins)
|
||||
{
|
||||
if (s.InverseMatrices != null)
|
||||
{
|
||||
if (unique.Add(s.InverseMatrices.Bytes))
|
||||
{
|
||||
reverser.ReverseBuffer(s.InverseMatrices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var a in model.Animations)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
}
|
||||
|
||||
static void FlipTriangle(SpanLike<byte> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
// 0, 1, 2 to 2, 1, 0
|
||||
var tmp = indices[i + 2];
|
||||
indices[i + 2] = indices[i];
|
||||
indices[i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void FlipTriangle(SpanLike<ushort> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
// 0, 1, 2 to 2, 1, 0
|
||||
var tmp = indices[i + 2];
|
||||
indices[i + 2] = indices[i];
|
||||
indices[i] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void FlipTriangle(SpanLike<uint> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
// 0, 1, 2 to 2, 1, 0
|
||||
var tmp = indices[i + 2];
|
||||
indices[i + 2] = indices[i];
|
||||
indices[i] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 382bf6be84d3135438af6a559a599fa3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
/// <summary>
|
||||
/// Modelを安全に変更できるようにラップする
|
||||
/// </summary>
|
||||
public class ModelModifier
|
||||
{
|
||||
/// <summary>
|
||||
/// 直接データを変更する場合は整合性に注意
|
||||
/// </summary>
|
||||
public Model Model;
|
||||
|
||||
public ModelModifier(Model model)
|
||||
{
|
||||
Model = model;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Meshを置き換える。
|
||||
///
|
||||
/// src=null, dst!=null で追加。
|
||||
/// src!=null, dst=null で削除。
|
||||
/// </summary>
|
||||
/// <param name = "src">置き換え元</param>
|
||||
/// <param name = "dst">置き換え先</param>
|
||||
public void MeshReplace(MeshGroup src, MeshGroup dst)
|
||||
{
|
||||
// replace: Meshes
|
||||
if (src != null)
|
||||
{
|
||||
Model.MeshGroups.RemoveAll(x => x == src);
|
||||
}
|
||||
if (dst != null && !Model.MeshGroups.Contains(dst))
|
||||
{
|
||||
Model.MeshGroups.Add(dst);
|
||||
}
|
||||
|
||||
// replace: Node.Mesh
|
||||
foreach (var node in Model.Nodes)
|
||||
{
|
||||
if (src != null && src == node.MeshGroup)
|
||||
{
|
||||
node.MeshGroup = dst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Node
|
||||
public void NodeAdd(Node node, Node parent = null)
|
||||
{
|
||||
if (parent is null)
|
||||
{
|
||||
parent = Model.Root;
|
||||
}
|
||||
parent.Add(node);
|
||||
if (Model.Nodes.Contains(node))
|
||||
{
|
||||
throw new ArgumentException($"Nodes contain {node}");
|
||||
}
|
||||
Model.Nodes.Add(node);
|
||||
}
|
||||
|
||||
public void NodeRemove(Node remove)
|
||||
{
|
||||
foreach (var node in Model.Nodes)
|
||||
{
|
||||
if (node.Parent == remove)
|
||||
{
|
||||
remove.Remove(node);
|
||||
}
|
||||
if (remove.Parent == node)
|
||||
{
|
||||
node.Remove(remove);
|
||||
}
|
||||
}
|
||||
if (Model.Root.Children.Contains(remove))
|
||||
{
|
||||
Model.Root.Remove(remove);
|
||||
}
|
||||
|
||||
Model.Nodes.Remove(remove);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nodeを置き換える。参照を置換する。
|
||||
/// </summary>
|
||||
public void NodeReplace(Node src, Node dst)
|
||||
{
|
||||
if (src == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
if (dst == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
// add dst same parent
|
||||
src.Parent.Add(dst, ChildMatrixMode.KeepWorld);
|
||||
|
||||
// remove all child
|
||||
foreach (var child in src.Children.ToArray())
|
||||
{
|
||||
dst.Add(child, ChildMatrixMode.KeepWorld);
|
||||
}
|
||||
|
||||
// remove from parent
|
||||
src.Parent.Remove(src);
|
||||
Model.Nodes.Remove(src);
|
||||
|
||||
// remove from skinning
|
||||
foreach (var skin in Model.Skins)
|
||||
{
|
||||
skin.Replace(src, dst);
|
||||
}
|
||||
|
||||
// fix animation reference
|
||||
foreach (var animation in Model.Animations)
|
||||
{
|
||||
if (animation.NodeMap.TryGetValue(src, out NodeAnimation nodeAnimation))
|
||||
{
|
||||
animation.NodeMap.Remove(src);
|
||||
animation.NodeMap.Add(dst, nodeAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
if (Model.Nodes.Contains(dst))
|
||||
{
|
||||
throw new Exception("already exists");
|
||||
}
|
||||
Model.Nodes.Add(dst);
|
||||
|
||||
// TODO: SpringBone
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8a5b1c7c663b54b49913c6ade2a342c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class ModelModifierExtensions
|
||||
{
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 54fd566dc84b51643886ce8858a671db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using UniGLTF;
|
||||
|
||||
namespace VrmLib
|
||||
{
|
||||
public static class VertexBufferExtensions
|
||||
{
|
||||
public static void Add<T>(this VertexBuffer v, string key, T[] list) where T : struct
|
||||
{
|
||||
v.Add(key, BufferAccessor.Create(list));
|
||||
}
|
||||
|
||||
public static void FixBoneWeight(this VertexBuffer v)
|
||||
{
|
||||
var joints = v.Joints.GetSpan<SkinJoints>();
|
||||
var weights = v.Weights.GetSpan<Vector4>();
|
||||
if (joints.Length != weights.Length)
|
||||
{
|
||||
throw new System.Exception();
|
||||
}
|
||||
|
||||
for (int i = 0; i < joints.Length; ++i)
|
||||
{
|
||||
var j = joints[i];
|
||||
var w = weights[i];
|
||||
|
||||
int n = 0;
|
||||
if (w.X > 0) ++n;
|
||||
if (w.Y > 0) ++n;
|
||||
if (w.Z > 0) ++n;
|
||||
if (w.W > 0) ++n;
|
||||
if (n == 1)
|
||||
{
|
||||
if (w.X == 0)
|
||||
{
|
||||
if (w.Y > 0)
|
||||
{
|
||||
w.X = w.Y;
|
||||
w.Y = 0;
|
||||
j.Joint0 = j.Joint1;
|
||||
j.Joint1 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (n == 2)
|
||||
{
|
||||
if (w.X == 0 || w.Y == 0)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (w.X >= w.Y)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (n == 3)
|
||||
{
|
||||
if (w.W != 0)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (w.X >= w.Y && w.Y >= w.Z)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (n == 4)
|
||||
{
|
||||
if (w.X >= w.Y && w.Y >= w.Z && w.Z >= w.W)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
joints[i] = j;
|
||||
weights[i] = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1c95aaad1a35e0c45a6f37f4a5604458
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -11,23 +11,22 @@ namespace VrmLibTests
|
|||
public void ModifierTest()
|
||||
{
|
||||
var model = new Model(Coordinates.Vrm1);
|
||||
var modifier = new ModelModifier(model);
|
||||
|
||||
var node0 = new Node("node0");
|
||||
modifier.NodeAdd(node0);
|
||||
model.NodeAdd(node0);
|
||||
Assert.AreEqual(1, model.Nodes.Count);
|
||||
|
||||
var node1 = new Node("node1");
|
||||
modifier.NodeAdd(node1, node0);
|
||||
model.NodeAdd(node1, node0);
|
||||
Assert.AreEqual(2, model.Nodes.Count);
|
||||
|
||||
var node2 = new Node("node2");
|
||||
modifier.NodeReplace(node0, node2);
|
||||
model.NodeReplace(node0, node2);
|
||||
Assert.AreEqual(2, model.Nodes.Count);
|
||||
Assert.AreEqual(node2, model.Nodes[1]);
|
||||
Assert.AreEqual(1, node2.Children.Count);
|
||||
|
||||
modifier.NodeRemove(node1);
|
||||
model.NodeRemove(node1);
|
||||
Assert.AreEqual(1, model.Nodes.Count);
|
||||
Assert.AreEqual(0, node2.Children.Count);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user