using System; using System.Collections.Generic; using System.Linq; using System.Numerics; namespace VrmLib { class MeshSplitter { public static ValueTuple< List>, List>> 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(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>(); var withoutTriangles = new List>(); 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>, List>> SplitTrianglesByBoneIndices(Mesh src, HashSet targetBoneIndices) { var triangles = src.Triangles.ToArray(); var isBodyMeshTriangle = new bool[triangles.Length]; var skinJointsSpan = SpanLike.Wrap(src.VertexBuffer.Joints.Bytes); var boneWeightSpan = SpanLike.Wrap(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>(); var headTriangles = new List>(); 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 IndexMap = new List(); public BufferAccessor MapBuffer(BufferAccessor srcBuffer) where T : struct { var src = SpanLike.Wrap(srcBuffer.Bytes); var dstBytes = new byte[srcBuffer.Stride * IndexMap.Count]; var dst = SpanLike.Wrap(new ArraySegment(dstBytes)); for (int i = 0; i < IndexMap.Count; ++i) { dst[i] = src[IndexMap[i]]; } return new BufferAccessor(new ArraySegment(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(buffer); break; case AccessorVectorType.VEC3: mapped = MapBuffer(buffer); break; case AccessorVectorType.VEC4: mapped = MapBuffer(buffer); break; default: throw new NotImplementedException(); } } else if (buffer.ComponentType == AccessorValueType.UNSIGNED_SHORT) { if (buffer.AccessorType == AccessorVectorType.VEC4) { mapped = MapBuffer(buffer); } else { throw new NotImplementedException(); } } else { throw new NotImplementedException(); } dst.Add(semantic, mapped); } return dst; } public readonly List Indices = new List(); public ArraySegment IndexBytes { get { var bytes = new byte[Indices.Count * 4]; var span = SpanLike.Wrap(new ArraySegment(bytes)); for (int i = 0; i < span.Length; ++i) { span[i] = Indices[i]; } return new ArraySegment(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> 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> 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; } } }