umasteeringgroup / UMA

Unity Multipurpose Avatar
MIT License
739 stars 168 forks source link

Mesh data on SlotDataAsset is whole source mesh. #370

Closed jimc1664 closed 6 hours ago

jimc1664 commented 2 years ago

When creating multiple slots via slot builder window from a single source I noticed mesh data for each slot was identical. Not a big issue on its own but when these are recombined for a renderer later the whole data is appended rather than what is needed from specified submesh index.

To summarise; if you have a source mesh that splits to ten slots which are then all recombined the resulting mesh will have ten copies of all vertex data. Indexes are fine, but might now need to be 32bit.

My local fix is a tweak to UMAMeshData::RetrieveDataFromUnityMesh so it can copy and remap single submesh, not exactly thoroughly tested:

        static T[] RemapArray<T>(ICollection<int> map, T[] src)
        {
            if (src == null || map == null || src.Length == 0)
                return src;

            var ret = new T[map.Count];
            var dstI = 0;
            foreach (var srcI in map)
                ret[dstI++] = src[srcI];

            return ret;
        }

        /// <summary>
        /// Initialize UMA mesh data from Unity mesh.
        /// </summary>
        /// <param name="sharedMesh">Source mesh.</param>
        public void RetrieveDataFromUnityMesh(Mesh sharedMesh, int subMeshInd = -1)
        {

            if (subMeshInd >= sharedMesh.subMeshCount)
            {
                Debug.LogError("Requested submesh index is invalid");
                subMeshInd = -1;
            }

            SortedSet<int> vertRemap = null;

            if (subMeshInd < 0 || sharedMesh.subMeshCount == 1)
            {
                vertexCount = sharedMesh.vertexCount;
                subMeshCount = sharedMesh.subMeshCount;
                submeshes = new SubMeshTriangles[subMeshCount];
                for (int i = 0; i < subMeshCount; i++)
                {
                    submeshes[i].triangles = sharedMesh.GetTriangles(i);
                }
            }
            else
            {
                subMeshCount = 1;
                submeshes = new SubMeshTriangles[subMeshCount];
                var tris = sharedMesh.GetTriangles(subMeshInd);

                vertRemap = new SortedSet<int>(tris);
                var indRemap = new int[sharedMesh.vertexCount];

                vertexCount = 0;
                foreach (var vi in vertRemap)
                    indRemap[vi] = vertexCount++;

                for (var ii = 0; ii < tris.Length; ii++)
                    tris[ii] = indRemap[tris[ii]];
                submeshes[0].triangles = tris;
            }

            bindPoses = sharedMesh.bindposes;
#if USE_NATIVE_ARRAYS
            unityBonesPerVertex =  sharedMesh.GetBonesPerVertex();
            unityBoneWeights = sharedMesh.GetAllBoneWeights();
            SerializedBoneWeights = vertRemap,unityBoneWeights.ToArray();
            SerializedBonesPerVertex = vertRemap,unityBonesPerVertex.ToArray();

            !you need to remap these!
#else
            var unityBonesPerVertex = sharedMesh.GetBonesPerVertex();
            var unityBoneWeights = sharedMesh.GetAllBoneWeights();
            ManagedBonesPerVertex = RemapArray(vertRemap, unityBonesPerVertex.ToArray());

            if (vertRemap == null || unityBoneWeights.Length == 0)
            {
                ManagedBoneWeights = unityBoneWeights.ToArray();
            }
            else
            {
                List<BoneWeight1> boneWeights = new List<BoneWeight1>();

                int srcI = 0, boneWI = 0;
                foreach (var nextI in vertRemap)
                {
                    for (; srcI < nextI; srcI++)
                        boneWI += unityBonesPerVertex[srcI];

                    var boneC = unityBonesPerVertex[nextI];
                    for (; boneC-- > 0;)
                        boneWeights.Add(unityBoneWeights[boneWI++]);

                    srcI++;
                }
                ManagedBoneWeights = boneWeights.ToArray();
            }

            //if (unityBonesPerVertex.IsCreated)
            //  unityBonesPerVertex.Dispose();
            //if (unityBoneWeights.IsCreated)
            //  unityBoneWeights.Dispose();
#endif

            vertices = RemapArray(vertRemap, sharedMesh.vertices);
            // vertexCount = vertices.Length;
            normals = RemapArray(vertRemap, sharedMesh.normals);
            tangents = RemapArray(vertRemap, sharedMesh.tangents);
            colors32 = RemapArray(vertRemap, sharedMesh.colors32);
            uv = RemapArray(vertRemap, sharedMesh.uv);
            uv2 = RemapArray(vertRemap, sharedMesh.uv2);
            uv3 = RemapArray(vertRemap, sharedMesh.uv3);
            uv4 = RemapArray(vertRemap, sharedMesh.uv4);

            //Create the blendshape data on the slot asset from the unity mesh
            #region Blendshape
            blendShapes = new UMABlendShape[sharedMesh.blendShapeCount];

            for (int shapeIndex = 0; shapeIndex < sharedMesh.blendShapeCount; shapeIndex++)
            {
                blendShapes[shapeIndex] = new UMABlendShape();
                blendShapes[shapeIndex].shapeName = sharedMesh.GetBlendShapeName(shapeIndex);

                int frameCount = sharedMesh.GetBlendShapeFrameCount(shapeIndex);
                blendShapes[shapeIndex].frames = new UMABlendFrame[frameCount];

                for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
                {
                    var deltaVertices = new Vector3[sharedMesh.vertexCount];
                    var deltaNormals = new Vector3[sharedMesh.vertexCount];
                    var deltaTangents = new Vector3[sharedMesh.vertexCount];

                    bool hasNormals = false;
                    bool hasTangents = false;

                    //Get the delta arrays first so we can determine if we don't need the delta normals or the delta tangents.
                    sharedMesh.GetBlendShapeFrameVertices(shapeIndex, frameIndex, deltaVertices, deltaNormals, deltaTangents);

                    deltaVertices = RemapArray(vertRemap, deltaVertices);
                    deltaNormals = RemapArray(vertRemap, deltaNormals);
                    deltaTangents = RemapArray(vertRemap, deltaTangents);

                    if (!UMABlendFrame.isAllZero(deltaNormals))
                        hasNormals = true;

                    if (!UMABlendFrame.isAllZero(deltaTangents))
                        hasTangents = true;

                    blendShapes[shapeIndex].frames[frameIndex] = new UMABlendFrame();
                    blendShapes[shapeIndex].frames[frameIndex].frameWeight = sharedMesh.GetBlendShapeFrameWeight(shapeIndex, frameIndex);

                    blendShapes[shapeIndex].frames[frameIndex].deltaVertices = deltaVertices;
                    if (hasNormals)
                        blendShapes[shapeIndex].frames[frameIndex].deltaNormals = deltaNormals;
                    if (hasTangents)
                        blendShapes[shapeIndex].frames[frameIndex].deltaTangents = deltaTangents;

                }
            }
            #endregion
        }
Jaimi commented 6 hours ago

Fixed bug introduced with code merging. (my fault)