434 lines
15 KiB
C#
434 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace UniGLTF.MeshUtility
|
|
{
|
|
public class MeshIntegrator
|
|
{
|
|
public enum BlendShapeOperation
|
|
{
|
|
// No BlendShape(drop if mesh has blendshape)
|
|
None,
|
|
// Use blendShape(keep blendshape)
|
|
Use,
|
|
// Integrate to two mesh that is with blendShape and is without blendshape
|
|
Split,
|
|
}
|
|
|
|
struct SubMesh
|
|
{
|
|
public List<int> Indices;
|
|
public Material Material;
|
|
}
|
|
|
|
public class BlendShape
|
|
{
|
|
public string Name;
|
|
public List<Vector3> Positions = new List<Vector3>();
|
|
public List<Vector3> Normals = new List<Vector3>();
|
|
public List<Vector3> Tangents = new List<Vector3>();
|
|
|
|
public BlendShape(string name)
|
|
{
|
|
Name = name;
|
|
}
|
|
|
|
public void Fill(int count)
|
|
{
|
|
var size = count - Positions.Count;
|
|
if (size < 0)
|
|
{
|
|
throw new Exception();
|
|
}
|
|
Positions.AddRange(Enumerable.Repeat(Vector3.zero, size));
|
|
Normals.AddRange(Enumerable.Repeat(Vector3.zero, size));
|
|
Tangents.AddRange(Enumerable.Repeat(Vector3.zero, size));
|
|
}
|
|
}
|
|
|
|
MeshIntegrationResult Result { get; } = new MeshIntegrationResult();
|
|
List<Vector3> Positions { get; } = new List<Vector3>();
|
|
List<Vector3> Normals { get; } = new List<Vector3>();
|
|
List<Vector2> UV { get; } = new List<Vector2>();
|
|
List<Vector4> Tangents { get; } = new List<Vector4>();
|
|
List<BoneWeight> BoneWeights { get; } = new List<BoneWeight>();
|
|
List<SubMesh> SubMeshes { get; } = new List<SubMesh>();
|
|
List<Matrix4x4> _BindPoses { get; } = new List<Matrix4x4>();
|
|
List<Transform> _Bones { get; } = new List<Transform>();
|
|
List<BlendShape> BlendShapes { get; } = new List<BlendShape>();
|
|
void AddBlendShapesToMesh(Mesh mesh)
|
|
{
|
|
foreach (var x in BlendShapes)
|
|
{
|
|
//Debug.LogFormat("AddBlendShapeFrame: {0}", kv.Key);
|
|
mesh.AddBlendShapeFrame(x.Name, 100.0f,
|
|
x.Positions.ToArray(),
|
|
x.Normals.ToArray(),
|
|
x.Tangents.ToArray());
|
|
}
|
|
}
|
|
|
|
int AddBoneIfUnique(Transform bone, Matrix4x4? pose = default)
|
|
{
|
|
var index = _Bones.IndexOf(bone);
|
|
if (index == -1)
|
|
{
|
|
index = _Bones.Count;
|
|
_Bones.Add(bone);
|
|
_BindPoses.Add(pose.HasValue ? pose.Value : bone.worldToLocalMatrix);
|
|
}
|
|
return index;
|
|
}
|
|
|
|
void AddBoneIfUnique(Transform[] bones, Matrix4x4[] bindPoses, int boneIndex, float weight, Action<int, float> setter)
|
|
{
|
|
if (boneIndex < 0 || boneIndex >= bones.Length || weight <= 0)
|
|
{
|
|
setter(0, 0);
|
|
return;
|
|
}
|
|
var t = bones[boneIndex];
|
|
setter(AddBoneIfUnique(t, bindPoses[boneIndex]), weight);
|
|
}
|
|
|
|
void Push(MeshRenderer renderer)
|
|
{
|
|
var meshFilter = renderer.GetComponent<MeshFilter>();
|
|
if (meshFilter == null)
|
|
{
|
|
Debug.LogWarningFormat("{0} has no mesh filter", renderer.name);
|
|
return;
|
|
}
|
|
var mesh = meshFilter.sharedMesh;
|
|
if (mesh == null)
|
|
{
|
|
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
|
|
return;
|
|
}
|
|
Result.SourceMeshRenderers.Add(renderer);
|
|
Result.Sources.Add(mesh);
|
|
|
|
var indexOffset = Positions.Count;
|
|
|
|
Positions.AddRange(mesh.vertices
|
|
.Select(x => renderer.transform.TransformPoint(x))
|
|
);
|
|
Normals.AddRange(mesh.normals
|
|
.Select(x => renderer.transform.TransformVector(x))
|
|
);
|
|
UV.AddRange(mesh.uv);
|
|
Tangents.AddRange(mesh.tangents
|
|
.Select(t =>
|
|
{
|
|
var v = renderer.transform.TransformVector(t.x, t.y, t.z);
|
|
return new Vector4(v.x, v.y, v.z, t.w);
|
|
})
|
|
);
|
|
|
|
var bone = renderer.transform;
|
|
if (bone == null)
|
|
{
|
|
Debug.LogWarningFormat("{0} is root gameobject.", bone.name);
|
|
return;
|
|
}
|
|
var boneIndex = AddBoneIfUnique(bone);
|
|
|
|
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertices.Length)
|
|
.Select(x => new BoneWeight()
|
|
{
|
|
boneIndex0 = boneIndex,
|
|
weight0 = 1,
|
|
})
|
|
);
|
|
|
|
|
|
for (int i = 0; i < mesh.subMeshCount && i < renderer.sharedMaterials.Length; ++i)
|
|
{
|
|
var indices = mesh.GetIndices(i).Select(x => x + indexOffset);
|
|
var mat = renderer.sharedMaterials[i];
|
|
var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat));
|
|
if (sameMaterialSubMeshIndex >= 0)
|
|
{
|
|
SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices);
|
|
}
|
|
else
|
|
{
|
|
SubMeshes.Add(new SubMesh
|
|
{
|
|
Indices = indices.ToList(),
|
|
Material = mat,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Push(SkinnedMeshRenderer renderer)
|
|
{
|
|
if (renderer == null)
|
|
{
|
|
// Debug.LogWarningFormat("{0} was destroyed", renderer);
|
|
return;
|
|
}
|
|
|
|
var mesh = renderer.sharedMesh;
|
|
if (mesh == null)
|
|
{
|
|
Debug.LogWarningFormat("{0} has no mesh", renderer.name);
|
|
return;
|
|
}
|
|
Result.SourceSkinnedMeshRenderers.Add(renderer);
|
|
Result.Sources.Add(mesh);
|
|
|
|
var vertexOffset = Positions.Count;
|
|
// var boneIndexOffset = Bones.Count;
|
|
|
|
Positions.AddRange(mesh.vertices);
|
|
Normals.AddRange(mesh.normals);
|
|
UV.AddRange(mesh.uv);
|
|
Tangents.AddRange(mesh.tangents);
|
|
|
|
if (mesh.vertexCount == mesh.boneWeights.Length)
|
|
{
|
|
// AddBone AddBoneIndexOffset(x, boneIndexOffset)
|
|
BoneWeights.AddRange(mesh.boneWeights.Select(x =>
|
|
{
|
|
var bw = new BoneWeight();
|
|
AddBoneIfUnique(renderer.bones, mesh.bindposes, x.boneIndex0, x.weight0, (i, w) => { bw.boneIndex0 = i; bw.weight0 = w; });
|
|
AddBoneIfUnique(renderer.bones, mesh.bindposes, x.boneIndex1, x.weight1, (i, w) => { bw.boneIndex1 = i; bw.weight1 = w; });
|
|
AddBoneIfUnique(renderer.bones, mesh.bindposes, x.boneIndex2, x.weight2, (i, w) => { bw.boneIndex2 = i; bw.weight2 = w; });
|
|
AddBoneIfUnique(renderer.bones, mesh.bindposes, x.boneIndex3, x.weight3, (i, w) => { bw.boneIndex3 = i; bw.weight3 = w; });
|
|
return bw;
|
|
}).ToArray());
|
|
}
|
|
else
|
|
{
|
|
// Bone Count 0 の SkinnedMeshRenderer
|
|
var rigidBoneWeight = new BoneWeight
|
|
{
|
|
boneIndex0 = AddBoneIfUnique(renderer.transform),
|
|
weight0 = 1f,
|
|
};
|
|
BoneWeights.AddRange(Enumerable.Range(0, mesh.vertexCount).Select(x => rigidBoneWeight).ToArray());
|
|
}
|
|
|
|
for (int i = 0; i < mesh.subMeshCount && i < renderer.sharedMaterials.Length; ++i)
|
|
{
|
|
var indices = mesh.GetIndices(i).Select(x => x + vertexOffset);
|
|
var mat = renderer.sharedMaterials[i];
|
|
var sameMaterialSubMeshIndex = SubMeshes.FindIndex(x => ReferenceEquals(x.Material, mat));
|
|
if (sameMaterialSubMeshIndex >= 0)
|
|
{
|
|
SubMeshes[sameMaterialSubMeshIndex].Indices.AddRange(indices);
|
|
}
|
|
else
|
|
{
|
|
SubMeshes.Add(new SubMesh
|
|
{
|
|
Indices = indices.ToList(),
|
|
Material = mat,
|
|
});
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < mesh.blendShapeCount; ++i)
|
|
{
|
|
// arrays size must match mesh vertex count
|
|
var positions = new Vector3[mesh.vertexCount];
|
|
var normals = new Vector3[mesh.vertexCount];
|
|
var tangents = new Vector3[mesh.vertexCount];
|
|
mesh.GetBlendShapeFrameVertices(i, 0, positions, normals, tangents);
|
|
|
|
var blendShape = GetOrCreateBlendShape(mesh.GetBlendShapeName(i), vertexOffset);
|
|
blendShape.Positions.AddRange(positions);
|
|
blendShape.Normals.AddRange(normals);
|
|
blendShape.Tangents.AddRange(tangents);
|
|
}
|
|
foreach (var blendShape in BlendShapes)
|
|
{
|
|
blendShape.Fill(Positions.Count);
|
|
}
|
|
}
|
|
|
|
BlendShape GetOrCreateBlendShape(string name, int vertexOffset)
|
|
{
|
|
BlendShape found = null;
|
|
foreach (var blendshape in BlendShapes)
|
|
{
|
|
if (blendshape.Name == name)
|
|
{
|
|
found = blendshape;
|
|
break;
|
|
}
|
|
}
|
|
if (found == null)
|
|
{
|
|
found = new BlendShape(name);
|
|
BlendShapes.Add(found);
|
|
}
|
|
|
|
found.Fill(vertexOffset);
|
|
|
|
return found;
|
|
}
|
|
|
|
public static bool TryIntegrate(MeshIntegrationGroup group, BlendShapeOperation op,
|
|
out MeshIntegrationResult result)
|
|
{
|
|
var integrator = new MeshUtility.MeshIntegrator();
|
|
foreach (var x in group.Renderers)
|
|
{
|
|
if (x is SkinnedMeshRenderer smr)
|
|
{
|
|
integrator.Push(smr);
|
|
}
|
|
else if (x is MeshRenderer mr)
|
|
{
|
|
integrator.Push(mr);
|
|
}
|
|
}
|
|
result = integrator.Integrate(group.Name, op);
|
|
if (result.Integrated != null || result.IntegratedNoBlendShape != null)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
delegate bool TriangleFilter(int i0, int i1, int i2);
|
|
|
|
static int[] GetFilteredIndices(List<int> indices, TriangleFilter filter)
|
|
{
|
|
if (filter == null)
|
|
{
|
|
return indices.ToArray();
|
|
}
|
|
|
|
var filtered = new List<int>();
|
|
for (int i = 0; i < indices.Count; i += 3)
|
|
{
|
|
var i0 = indices[i];
|
|
var i1 = indices[i + 1];
|
|
var i2 = indices[i + 2];
|
|
if (filter(i0, i1, i2))
|
|
{
|
|
filtered.Add(i0);
|
|
filtered.Add(i1);
|
|
filtered.Add(i2);
|
|
}
|
|
}
|
|
return filtered.ToArray();
|
|
}
|
|
|
|
Mesh CreateMesh(string name, List<DrawCount> dst, TriangleFilter filter)
|
|
{
|
|
var mesh = new Mesh();
|
|
mesh.name = name;
|
|
if (Positions.Count > ushort.MaxValue)
|
|
{
|
|
Debug.LogFormat("exceed 65535 vertices: {0}", Positions.Count);
|
|
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
|
}
|
|
mesh.vertices = Positions.ToArray();
|
|
mesh.normals = Normals.ToArray();
|
|
mesh.uv = UV.ToArray();
|
|
if (Tangents != null && Tangents.Count == Positions.Count)
|
|
{
|
|
mesh.tangents = Tangents.ToArray();
|
|
}
|
|
if (BoneWeights != null && BoneWeights.Count == Positions.Count)
|
|
{
|
|
mesh.boneWeights = BoneWeights.ToArray();
|
|
}
|
|
|
|
int subMeshCount = 0;
|
|
foreach (var submesh in SubMeshes)
|
|
{
|
|
var indices = GetFilteredIndices(submesh.Indices, filter);
|
|
if (indices.Length > 0)
|
|
{
|
|
mesh.subMeshCount = (subMeshCount + 1);
|
|
mesh.SetIndices(indices, MeshTopology.Triangles, subMeshCount++);
|
|
dst.Add(new DrawCount
|
|
{
|
|
Count = indices.Length,
|
|
Material = submesh.Material,
|
|
});
|
|
}
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
MeshIntegrationResult Integrate(string name, BlendShapeOperation op)
|
|
{
|
|
if (_Bones.Count != _BindPoses.Count)
|
|
{
|
|
throw new ArgumentException();
|
|
}
|
|
|
|
var splitter = new TriangleSeparator(Positions.Count);
|
|
if (op == BlendShapeOperation.Split)
|
|
{
|
|
foreach (var blendShape in BlendShapes)
|
|
{
|
|
splitter.CheckPositions(blendShape.Positions);
|
|
}
|
|
}
|
|
|
|
if (splitter.ShouldSplit)
|
|
{
|
|
//
|
|
// has BlendShape
|
|
//
|
|
Result.Integrated = new MeshInfo();
|
|
var mesh = CreateMesh(name, Result.Integrated.SubMeshes,
|
|
splitter.TriangleHasBlendShape);
|
|
Result.Integrated.Mesh = mesh;
|
|
AddBlendShapesToMesh(mesh);
|
|
// skinning
|
|
mesh.bindposes = _BindPoses.ToArray();
|
|
|
|
//
|
|
// no BlendShape
|
|
//
|
|
Result.IntegratedNoBlendShape = new MeshInfo();
|
|
var meshWithoutBlendShape = CreateMesh(name + ".no_blendshape", Result.IntegratedNoBlendShape.SubMeshes,
|
|
splitter.TriangleHasNotBlendShape);
|
|
Result.IntegratedNoBlendShape.Mesh = meshWithoutBlendShape;
|
|
// skinning
|
|
meshWithoutBlendShape.bindposes = _BindPoses.ToArray();
|
|
}
|
|
else
|
|
{
|
|
var useBlendShape = op == BlendShapeOperation.Use && BlendShapes.Count > 0;
|
|
if (useBlendShape)
|
|
{
|
|
Result.Integrated = new MeshInfo();
|
|
var mesh = CreateMesh(name, Result.Integrated.SubMeshes, null);
|
|
Result.Integrated.Mesh = mesh;
|
|
AddBlendShapesToMesh(mesh);
|
|
// skinning
|
|
mesh.bindposes = _BindPoses.ToArray();
|
|
}
|
|
else
|
|
{
|
|
Result.IntegratedNoBlendShape = new MeshInfo();
|
|
var mesh = CreateMesh(name, Result.IntegratedNoBlendShape.SubMeshes, null);
|
|
Result.IntegratedNoBlendShape.Mesh = mesh;
|
|
// skinning
|
|
mesh.bindposes = _BindPoses.ToArray();
|
|
}
|
|
}
|
|
Result.Bones = _Bones.ToArray();
|
|
|
|
return Result;
|
|
}
|
|
}
|
|
}
|