remove unused

This commit is contained in:
ousttrue 2022-02-04 14:41:35 +09:00
parent b48c8707d8
commit 4bb2cfaf59
25 changed files with 517 additions and 1880 deletions

View File

@ -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());

View File

@ -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}");
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: ad2e7d02ba5894344a2f58d9d2157f98
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}
}

View File

@ -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;
}
}
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 1d56612ad117ef64fb1e374d054f449b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 66fe20a63e11a3b4fb734a57c5a71839
guid: 8d578a2f92d3c57439dc109ec5986c13
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: f3f90205124143446bab06f56827a6ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -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));
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: abaf884e0faa5d040ad4eae16120cb46
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 382bf6be84d3135438af6a559a599fa3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 8a5b1c7c663b54b49913c6ade2a342c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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";
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 54fd566dc84b51643886ce8858a671db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -1,11 +0,0 @@
fileFormatVersion: 2
guid: 1c95aaad1a35e0c45a6f37f4a5604458
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}