#region using System; using System.Collections.Generic; using System.Linq; using Unity.Collections; using UnityEngine; using UnityEngine.Rendering; #endregion namespace nadena.dev.modular_avatar.core.editor { internal static class RemoveBlendShapeFromMesh { private const float THRESHOLD = 0.001f; public static Mesh RemoveBlendshapes(Mesh original, IEnumerable targets) { var mesh = new Mesh(); mesh.indexFormat = original.indexFormat; mesh.bounds = original.bounds; bool[] toDeleteVertices = new bool[original.vertexCount]; bool[] toRetainVertices = new bool[original.vertexCount]; ProbeBlendshapes(original, toDeleteVertices, targets); ProbeRetainedVertices(original, toDeleteVertices, toRetainVertices); RemapVerts(toRetainVertices, out var origToNewVertIndex, out var newToOrigVertIndex); TransferVertexData(mesh, original, toRetainVertices); TransferBoneData(mesh, original, toRetainVertices); mesh.bindposes = original.bindposes; TransferShapes(mesh, original, newToOrigVertIndex); UpdateTriangles(mesh, original, toRetainVertices, origToNewVertIndex); return mesh; } private static VertexAttribute[] uvAttrs = new[] { VertexAttribute.TexCoord0, VertexAttribute.TexCoord1, VertexAttribute.TexCoord2, VertexAttribute.TexCoord3, VertexAttribute.TexCoord4, VertexAttribute.TexCoord5, VertexAttribute.TexCoord6, VertexAttribute.TexCoord7, }; private static void TransferVertexData(Mesh mesh, Mesh original, bool[] toRetain) { List tmpVec2 = new(); List tmpVec3 = new(); List tmpVec4 = new(); TransferData(tmpVec3, mesh.SetVertices, original.GetVertices); TransferData(tmpVec3, mesh.SetNormals, original.GetNormals); TransferData(tmpVec4, mesh.SetTangents, original.GetTangents); for (int uv = 0; uv < 8; uv++) { if (!original.HasVertexAttribute(uvAttrs[uv])) continue; switch (original.GetVertexAttributeDimension(uvAttrs[uv])) { case 2: TransferData(tmpVec2, l => mesh.SetUVs(uv, l), l => original.GetUVs(uv, l)); break; case 3: TransferData(tmpVec3, l => mesh.SetUVs(uv, l), l => original.GetUVs(uv, l)); break; case 4: TransferData(tmpVec4, l => mesh.SetUVs(uv, l), l => original.GetUVs(uv, l)); break; default: throw new ArgumentOutOfRangeException(); } } void TransferData(List tmp, Action> setter, Action> getter) { getter(tmp); int index = 0; tmp.RemoveAll(_t => !toRetain[index++]); setter(tmp); } } private static void TransferShapes(Mesh mesh, Mesh original, int[] newToOrigVertIndex) { Vector3[] o_pos = new Vector3[original.vertexCount]; Vector3[] n_pos = new Vector3[mesh.vertexCount]; Vector3[] o_nrm = new Vector3[original.vertexCount]; Vector3[] n_nrm = new Vector3[mesh.vertexCount]; Vector3[] o_tan = new Vector3[original.vertexCount]; Vector3[] n_tan = new Vector3[mesh.vertexCount]; int blendshapeCount = original.blendShapeCount; for (int s = 0; s < blendshapeCount; s++) { int frameCount = original.GetBlendShapeFrameCount(s); var shapeName = original.GetBlendShapeName(s); for (int f = 0; f < frameCount; f++) { original.GetBlendShapeFrameVertices(s, f, o_pos, o_nrm, o_tan); Remap(); var frameWeight = original.GetBlendShapeFrameWeight(s, f); mesh.AddBlendShapeFrame(shapeName, frameWeight, n_pos, n_nrm, n_tan); } } void Remap() { for (int i = 0; i < n_pos.Length; i++) { try { n_pos[i] = o_pos[newToOrigVertIndex[i]]; n_nrm[i] = o_nrm[newToOrigVertIndex[i]]; n_tan[i] = o_tan[newToOrigVertIndex[i]]; } catch (IndexOutOfRangeException) { throw; } } } } private static void TransferBoneData(Mesh mesh, Mesh original, bool[] toRetain) { var origBoneWeights = original.GetAllBoneWeights(); var origBonesPerVertex = original.GetBonesPerVertex(); List boneWeights = new(origBoneWeights.Length); List bonesPerVertex = new(origBonesPerVertex.Length); if (origBonesPerVertex.Length == 0) return; // no bones in this mesh int ptr = 0; for (int i = 0; i < toRetain.Length; i++) { byte n_weight = origBonesPerVertex[i]; if (toRetain[i]) { for (int j = 0; j < n_weight; j++) { boneWeights.Add(origBoneWeights[ptr + j]); } bonesPerVertex.Add(n_weight); } ptr += n_weight; } var native_boneWeights = new NativeArray(boneWeights.ToArray(), Allocator.Temp); var native_bonesPerVertex = new NativeArray(bonesPerVertex.ToArray(), Allocator.Temp); mesh.SetBoneWeights(native_bonesPerVertex, native_boneWeights); native_boneWeights.Dispose(); native_bonesPerVertex.Dispose(); } private static void UpdateTriangles(Mesh mesh, Mesh original, bool[] toRetainVertices, int[] origToNewVertIndex) { int submeshCount = original.subMeshCount; List orig_tris = new List(); List new_tris = new List(); List orig_tris_16 = new List(); List new_tris_16 = new List(); mesh.subMeshCount = submeshCount; for (int sm = 0; sm < submeshCount; sm++) { if (original.indexFormat == IndexFormat.UInt32) { original.GetTriangles(orig_tris, sm, true); ProcessSubmesh(orig_tris, new_tris, i => i, i => i); int min = Math.Max(0, new_tris.Min()); for (int i = 0; i < new_tris.Count; i++) { new_tris[i] -= min; } mesh.SetTriangles(new_tris, sm, true, min); } else { original.GetTriangles(orig_tris_16, sm, true); ProcessSubmesh(orig_tris_16, new_tris_16, i => i, i => (ushort)i); ushort min = new_tris_16.Min(); for (int i = 0; i < new_tris_16.Count; i++) { new_tris_16[i] -= min; } mesh.SetTriangles(new_tris_16, sm, true, min); } } void ProcessSubmesh(List orig_tri, List new_tri, Func toInt, Func fromInt) { int limit = orig_tri.Count - 2; new_tri.Clear(); for (int i = 0; i < limit; i += 3) { if (!toRetainVertices[toInt(orig_tri[i])] || !toRetainVertices[toInt(orig_tri[i + 1])] || !toRetainVertices[toInt(orig_tri[i + 2])] ) { continue; } new_tri.Add(fromInt(origToNewVertIndex[toInt(orig_tri[i])])); new_tri.Add(fromInt(origToNewVertIndex[toInt(orig_tri[i + 1])])); new_tri.Add(fromInt(origToNewVertIndex[toInt(orig_tri[i + 2])])); } if (new_tri.Count == 0) { new_tri.Add(default); new_tri.Add(default); new_tri.Add(default); } } } private static void RemapVerts(bool[] toRetainVertices, out int[] origToNewVertIndex, out int[] newToOrigVertIndex) { List n2o = new List(toRetainVertices.Length); List o2n = new List(toRetainVertices.Length); for (int j = 0; j < toRetainVertices.Length; j++) { if (toRetainVertices[j]) { o2n.Add(n2o.Count); n2o.Add(j); } else { o2n.Add(-1); } } newToOrigVertIndex = n2o.ToArray(); origToNewVertIndex = o2n.ToArray(); } private static void ProbeBlendshapes(Mesh mesh, bool[] toDeleteVertices, IEnumerable shapes) { var bsPos = new Vector3[mesh.vertexCount]; foreach (var index in shapes) { int frames = mesh.GetBlendShapeFrameCount(index); for (int f = 0; f < frames; f++) { mesh.GetBlendShapeFrameVertices(index, f, bsPos, null, null); for (int i = 0; i < bsPos.Length; i++) { if (bsPos[i].sqrMagnitude > 0.0001f) { toDeleteVertices[i] = true; } } } } } private static void ProbeRetainedVertices(Mesh mesh, bool[] toDeleteVertices, bool[] toRetainVertices) { List tris = new List(); for (int subMesh = 0; subMesh < mesh.subMeshCount; subMesh++) { tris.Clear(); var baseVertex = (int)mesh.GetBaseVertex(subMesh); mesh.GetTriangles(tris, subMesh, false); for (int i = 0; i < tris.Count; i += 3) { if (toDeleteVertices[tris[i] + baseVertex] || toDeleteVertices[tris[i + 1] + baseVertex] || toDeleteVertices[tris[i + 2] + baseVertex]) { continue; } else { toRetainVertices[tris[i] + baseVertex] = true; toRetainVertices[tris[i + 1] + baseVertex] = true; toRetainVertices[tris[i + 2] + baseVertex] = true; } } } } } }