bool DrawGUIGrowShrink <T>(string label, ref T[] buffer, int count)
        {
            var capacity        = buffer.Length;
            var capacityChanged = false;

            EditorGUILayout.BeginHorizontal();
            {
                GUILayout.Button(string.Empty, GUILayout.ExpandWidth(true));
                EditorGUI.ProgressBar(GUILayoutUtility.GetLastRect(), count / (float)capacity, label + ": " + count + " / " + capacity);

                if (GUILayout.Button("Grow", GUILayout.ExpandWidth(false)))
                {
                    ArrayUtils.ResizeChecked(ref buffer, buffer.Length * 2);
                    capacityChanged = true;
                }

                EditorGUI.BeginDisabledGroup(count > capacity / 2);
                if (GUILayout.Button("Shrink", GUILayout.ExpandWidth(false)))
                {
                    ArrayUtils.ResizeChecked(ref buffer, buffer.Length / 2);
                    capacityChanged = true;
                }
                EditorGUI.EndDisabledGroup();
            }
            EditorGUILayout.EndHorizontal();

            return(capacityChanged);
        }
 public void ComputeLengths(ref float[] lengths, Vector3[] positions)
 {
     ArrayUtils.ResizeChecked(ref lengths, edges.Length);
     for (int i = 0; i != edges.Length; i++)
     {
         Vector3 p1 = positions[edges[i].p1];
         Vector3 p2 = positions[edges[i].p2];
         lengths[i] = Vector3.Magnitude(p2 - p1);
     }
 }
        public void LoadFrom(int[] triangles)
        {
            unsafe
            {
                var numTriangles = triangles.Length / 3;
                var numEdges     = 0;

                var buffer = (Edge *)UnsafeUtility.Malloc(sizeof(Edge) * numTriangles * 3, 1, Unity.Collections.Allocator.Temp);

                __hashedPairsClear();

                for (int triangleIndex = 0; triangleIndex != numTriangles; triangleIndex++)
                {
                    var i = triangles[triangleIndex * 3 + 0];
                    var j = triangles[triangleIndex * 3 + 1];
                    var k = triangles[triangleIndex * 3 + 2];

                    if (__hashedPairsAdd(i, j))
                    {
                        buffer[numEdges++] = new Edge {
                            p1 = i, p2 = j
                        }
                    }
                    ;

                    if (__hashedPairsAdd(j, k))
                    {
                        buffer[numEdges++] = new Edge {
                            p1 = j, p2 = k
                        }
                    }
                    ;

                    if (__hashedPairsAdd(k, i))
                    {
                        buffer[numEdges++] = new Edge {
                            p1 = k, p2 = i
                        }
                    }
                    ;
                }

                ArrayUtils.ResizeChecked(ref edges, numEdges);

                fixed(Edge *edgesPtr = edges)
                {
                    UnsafeUtility.MemCpy(edgesPtr, buffer, sizeof(Edge) * numEdges);
                    UnsafeUtility.Free(buffer, Unity.Collections.Allocator.Temp);
                }

                //Debug.Log("numTriangles = " + numTriangles + ", numEdges = " + numEdges);
            }
        }
        public void Allocate(int vertexCount, int fittedWeightsCount)
        {
            ArrayUtils.ResizeChecked(ref deltaPositions, vertexCount);
            ArrayUtils.ResizeChecked(ref deltaNormals, vertexCount);
            ArrayUtils.ResizeChecked(ref fittedWeights, fittedWeightsCount);

            for (int i = 0; i != vertexCount; i++)
            {
                deltaPositions[i] = Vector3.zero;
                deltaNormals[i]   = Vector3.zero;
            }

            for (int i = 0; i != fittedWeightsCount; i++)
            {
                fittedWeights[i] = 0.0f;
            }
        }
 public void ComputeCurvatures(ref float[] curvatures, Vector3[] positions, Vector3[] normals)
 {
     ArrayUtils.ResizeChecked(ref curvatures, edges.Length);
     for (int i = 0; i != edges.Length; i++)
     {
         // https://computergraphics.stackexchange.com/a/1719
         var p1            = positions[edges[i].p1];
         var p2            = positions[edges[i].p2];
         var p1p2          = p2 - p1;
         var squaredLength = Vector3.Dot(p1p2, p1p2);
         if (squaredLength != 0.0f)
         {
             var n1 = normals[edges[i].p1];
             var n2 = normals[edges[i].p2];
             curvatures[i] = Vector3.Dot(n2 - n1, p1p2) / squaredLength;
         }
         else
         {
             curvatures[i] = 0.0f;
         }
     }
 }
        public void SetDeltas(MeshBuffers buffersFrame0, MeshBuffers buffersFrameX)
        {
            int vertexCount = buffersFrame0.vertexCount;

            if (vertexCount < buffersFrameX.vertexCount)
            {
                Debug.LogWarning("target has more vertices (" + buffersFrameX.vertexCount + " vs. " + vertexCount + "), ignoring excess");
            }
            else if (vertexCount > buffersFrameX.vertexCount)
            {
                Debug.LogError("target has too few vertices (" + buffersFrameX.vertexCount + " vs. " + vertexCount + "), aborting");
                return;
            }

            ArrayUtils.ResizeChecked(ref deltaPositions, vertexCount);
            ArrayUtils.ResizeChecked(ref deltaNormals, vertexCount);

            for (int i = 0; i != vertexCount; i++)
            {
                deltaPositions[i] = buffersFrameX.vertexPositions[i] - buffersFrame0.vertexPositions[i];
                deltaNormals[i]   = buffersFrameX.vertexNormals[i] - buffersFrame0.vertexNormals[i];
            }
        }
        void ResolveSubjects()
        {
            if (attachData == null)
            {
                return;
            }

            if (attachData.driverVertexCount > meshBuffers.vertexCount)
            {
                return;                // prevent out of bounds if mesh shrunk since data was built
            }
            Profiler.BeginSample("resolve-subj-all");

            subjects.RemoveAll(p => p == null);

            //Profiler.BeginSample("sort");
            //subjects.Sort((a, b) => { return b.attachmentCount.CompareTo(a.attachmentCount); });
            //Profiler.EndSample();

            int stagingPinsSourceDataCount  = 3;
            int stagingPinsSourceDataOffset = subjects.Count * 2;

            ArrayUtils.ResizeChecked(ref stagingJobs, subjects.Count);
            ArrayUtils.ResizeChecked(ref stagingData, subjects.Count * 2);
            ArrayUtils.ResizeChecked(ref stagingPins, subjects.Count * 2 + stagingPinsSourceDataCount);

            GCHandle attachDataPosePin = GCHandle.Alloc(attachData.pose, GCHandleType.Pinned);
            GCHandle attachDataItemPin = GCHandle.Alloc(attachData.item, GCHandleType.Pinned);

            stagingPins[stagingPinsSourceDataOffset + 0] = GCHandle.Alloc(meshBuffers.vertexPositions, GCHandleType.Pinned);
            stagingPins[stagingPinsSourceDataOffset + 1] = GCHandle.Alloc(meshBuffers.vertexTangents, GCHandleType.Pinned);
            stagingPins[stagingPinsSourceDataOffset + 2] = GCHandle.Alloc(meshBuffers.vertexNormals, GCHandleType.Pinned);

            var targetToWorld = Matrix4x4.TRS(this.transform.position, this.transform.rotation, Vector3.one);
            // NOTE: targetToWorld specifically excludes scale, since source data (BakeMesh) is already scaled

            var targetMeshWorldBounds       = meshBakedOrAsset.bounds;
            var targetMeshWorldBoundsCenter = targetMeshWorldBounds.center;
            var targetMeshWorldBoundsExtent = targetMeshWorldBounds.extents;

            for (int i = 0, n = subjects.Count; i != n; i++)
            {
                var subject = subjects[i];
                if (subject.ChecksumCompare(attachData) == false)
                {
                    continue;
                }

                int attachmentIndex = subject.attachmentIndex;
                int attachmentCount = subject.attachmentCount;
                if (attachmentIndex == -1)
                {
                    continue;
                }

                if (attachmentIndex + attachmentCount > attachData.itemCount)
                {
                    continue;                    // prevent out of bounds if subject holds damaged index/count
                }
                var indexPos = i * 2 + 0;
                var indexNrm = i * 2 + 1;

                ArrayUtils.ResizeChecked(ref stagingData[indexPos], attachmentCount);
                ArrayUtils.ResizeChecked(ref stagingData[indexNrm], attachmentCount);
                stagingPins[indexPos] = GCHandle.Alloc(stagingData[indexPos], GCHandleType.Pinned);
                stagingPins[indexNrm] = GCHandle.Alloc(stagingData[indexNrm], GCHandleType.Pinned);

                unsafe
                {
                    var resolvedPositions = (Vector3 *)stagingPins[indexPos].AddrOfPinnedObject().ToPointer();
                    var resolvedNormals   = (Vector3 *)stagingPins[indexNrm].AddrOfPinnedObject().ToPointer();
                    switch (subject.attachmentType)
                    {
                    case SkinAttachment.AttachmentType.Transform:
                    {
                        stagingJobs[i] = ScheduleResolve(attachmentIndex, attachmentCount, ref targetToWorld, resolvedPositions, resolvedNormals);
                    }
                    break;

                    case SkinAttachment.AttachmentType.Mesh:
                    case SkinAttachment.AttachmentType.MeshRoots:
                    {
                        Matrix4x4 targetToSubject;
                        {
                            // this used to always read:
                            //   var targetToSubject = subject.transform.worldToLocalMatrix * targetToWorld;
                            //
                            // to support attachments that have skinning renderers, we sometimes have to transform
                            // the vertices into a space that takes into account the subsequently applied skinning:
                            //    var targetToSubject = (subject.skinningBone.localToWorldMatrix * subject.meshInstanceBoneBindPose).inverse * targetToWorld;
                            //
                            // we can reshuffle a bit to get rid of the per-resolve inverse:
                            //    var targetToSubject = (subject.skinningBoneBindPoseInverse * subject.meshInstanceBone.worldToLocalMatrix) * targetToWorld;

                            if (subject.skinningBone != null)
                            {
                                targetToSubject = (subject.skinningBoneBindPoseInverse * subject.skinningBone.worldToLocalMatrix) * targetToWorld;
                            }
                            else
                            {
                                targetToSubject = subject.transform.worldToLocalMatrix * targetToWorld;
                            }
                        }

                        stagingJobs[i] = ScheduleResolve(attachmentIndex, attachmentCount, ref targetToSubject, resolvedPositions, resolvedNormals);
                    }
                    break;
                    }
                }
            }

            JobHandle.ScheduleBatchedJobs();

            while (true)
            {
                var jobsRunning = false;

                for (int i = 0, n = subjects.Count; i != n; i++)
                {
                    var subject = subjects[i];
                    if (subject.ChecksumCompare(attachData) == false)
                    {
                        continue;
                    }

                    var stillRunning = (stagingJobs[i].IsCompleted == false);
                    if (stillRunning)
                    {
                        jobsRunning = true;
                        continue;
                    }

                    var indexPos = i * 2 + 0;
                    var indexNrm = i * 2 + 1;

                    var alreadyApplied = (stagingPins[indexPos].IsAllocated == false);
                    if (alreadyApplied)
                    {
                        continue;
                    }

                    stagingPins[indexPos].Free();
                    stagingPins[indexNrm].Free();

                    Profiler.BeginSample("gather-subj");
                    switch (subject.attachmentType)
                    {
                    case SkinAttachment.AttachmentType.Transform:
                    {
                        subject.transform.position = stagingData[indexPos][0];
                    }
                    break;

                    case SkinAttachment.AttachmentType.Mesh:
                    case SkinAttachment.AttachmentType.MeshRoots:
                    {
                        if (subject.meshInstance.vertexCount != stagingData[indexPos].Length)
                        {
                            Debug.LogError("mismatching vertex- and attachment count", subject);
                            break;
                        }

                        subject.meshInstance.SilentlySetVertices(stagingData[indexPos]);
                        subject.meshInstance.SilentlySetNormals(stagingData[indexNrm]);

                        Profiler.BeginSample("conservative-bounds");
                        {
                            //Debug.Log("targetMeshWorldBoundsCenter = " + targetMeshWorldBoundsCenter.ToString("G4") + " (from meshBakedOrAsset = " + meshBakedOrAsset.ToString() + ")");
                            //Debug.Log("targetMeshWorldBoundsExtents = " + targetMeshWorldBoundsExtents.ToString("G4"));
                            var worldToSubject      = subject.transform.worldToLocalMatrix;
                            var subjectBoundsCenter = worldToSubject.MultiplyPoint(targetMeshWorldBoundsCenter);
                            var subjectBoundsRadius = worldToSubject.MultiplyVector(targetMeshWorldBoundsExtent).magnitude + subject.meshAssetRadius;
                            var subjectBounds       = subject.meshInstance.bounds;
                            {
                                subjectBounds.center  = subjectBoundsCenter;
                                subjectBounds.extents = subjectBoundsRadius * Vector3.one;
                            }
                            subject.meshInstance.bounds = subjectBounds;
                        }
                        Profiler.EndSample();
                    }
                    break;
                    }
                    Profiler.EndSample();
                }

                if (jobsRunning == false)
                {
                    break;
                }
            }

            for (int i = 0; i != stagingPinsSourceDataCount; i++)
            {
                stagingPins[stagingPinsSourceDataOffset + i].Free();
            }

            attachDataPosePin.Free();
            attachDataItemPin.Free();

            Profiler.EndSample();
        }
 public MeshEdges()
 {
     ArrayUtils.ResizeChecked(ref edges, 0);
 }
Esempio n. 9
0
        static void ImportClip(SkinDeformationClip clip)
        {
            var sourceObjsPreloaded = null as NativeMeshSOA[];

            try
            {
                var progressTitle = "Importing '" + clip.name + "'";
                var progressIndex = 0;
                var progressCount = 4.0f;

                EditorUtility.DisplayProgressBar(progressTitle, "Loading assets", progressIndex++ / progressCount);

                var sourceObjPaths     = null as string[];
                var sourceMeshAssets   = null as Mesh[];
                var sourceAlbedoAssets = null as Texture2D[];

                var referenceObjPath      = clip.settings.referenceObjPath;
                var referenceMeshAsset    = clip.settings.referenceMeshAsset;
                var referenceIsFirstFrame = clip.settings.referenceIsFirstFrame;

                int frameCount            = 0;
                int frameVertexCount      = 0;
                int frameFaceIndicesCount = 0;

                var useExternalLoader = (clip.settings.sourceFrom == SkinDeformationClip.SourceType.ExternalObj);
                if (useExternalLoader)
                {
                    sourceObjPaths = GetFilesAtPath(clip.settings.externalObjPath, clip.settings.externalObjPattern);
                    Debug.Assert(sourceObjPaths.Length > 0, "source obj count == 0 (check import settings)");

                    if (referenceIsFirstFrame)
                    {
                        referenceObjPath = sourceObjPaths[0];
                        sourceObjPaths   = sourceObjPaths.Skip(1).ToArray();
                    }

                    using (var nativeMesh = NativeMeshObjLoader.Parse(referenceObjPath))
                    {
                        frameCount            = sourceObjPaths.Length;
                        frameVertexCount      = nativeMesh.vertexCount;
                        frameFaceIndicesCount = nativeMesh.faceIndicesCount;
                    }

                    if (clip.settings.externalObjPreloadThreaded)
                    {
                        sharedJobData.paths         = sourceObjPaths;
                        sharedJobData.vertexAttribs = NativeMeshObjLoader.VertexAttribs.Position;                        // ignored for now
                        sharedJobData.vertexOrder   = NativeMeshObjLoader.VertexOrder.ByDefinition;
                        sharedJobData.result        = new NativeMeshSOA[sourceObjPaths.Length];
                        sharedJobData.resultTouched = new bool[sourceObjPaths.Length];

                        for (int i = 0; i != sharedJobData.result.Length; i++)
                        {
                            sharedJobData.result[i].Allocate(frameVertexCount, frameFaceIndicesCount, Allocator.Persistent);
                        }

                        unsafe
                        {
                            fixed(bool *resultTouched = sharedJobData.resultTouched)
                            {
                                var jobDef = new ObjLoaderJob();
                                var job    = jobDef.Schedule(sourceObjPaths.Length, 1);

                                JobHandle.ScheduleBatchedJobs();

                                while (true)
                                {
                                    int numTotal   = sourceObjPaths.Length;
                                    int numTouched = 0;

                                    for (int i = 0; i != numTotal; i++)
                                    {
                                        if (resultTouched[i])
                                        {
                                            numTouched++;
                                        }
                                    }

                                    EditorUtility.DisplayProgressBar(progressTitle, "Loading assets (" + numTouched + " / " + numTotal + ")", (progressIndex - 1 + ((float)numTouched / numTotal)) / progressCount);

                                    if (job.IsCompleted)
                                    {
                                        break;
                                    }
                                }

                                job.Complete();
                            }
                        }

                        sourceObjsPreloaded = sharedJobData.result;
                    }
                }
                else
                {
                    sourceMeshAssets = GetAssetsAtPath <Mesh>(clip.settings.meshAssetFolder, clip.settings.meshAssetPrefix);
                    Debug.Assert(sourceMeshAssets.Length > 0, "source mesh count == 0 (check import settings)");

                    sourceAlbedoAssets = GetAssetsAtPath <Texture2D>(clip.settings.albedoAssetFolder, clip.settings.albedoAssetPrefix);
                    if (sourceAlbedoAssets.Length != sourceMeshAssets.Length)
                    {
                        sourceAlbedoAssets = null;
                        Debug.LogWarning("source albedo count != mesh count (SKIPPING albedo assets)");
                    }

                    if (referenceIsFirstFrame)
                    {
                        referenceMeshAsset = sourceMeshAssets[0];
                        sourceMeshAssets   = sourceMeshAssets.Skip(1).ToArray();

                        if (sourceAlbedoAssets != null)
                        {
                            sourceAlbedoAssets = sourceAlbedoAssets.Skip(1).ToArray();
                        }
                    }

                    Debug.Assert(referenceMeshAsset != null);

                    frameCount       = sourceMeshAssets.Length;
                    frameVertexCount = referenceMeshAsset.vertexCount;
                }

                Debug.Log("frameCount: " + frameCount);
                Debug.Log("frameVertexCount: " + frameVertexCount);

                int frameFittedWeightsCount = 0;                // modified later
                var frames = new SkinDeformation[frameCount];

                int subframeCount = frameCount - 1;
                var subframes     = new SkinDeformationClip.Subframe[subframeCount];

                MeshBuffers meshBuffers          = new MeshBuffers(frameVertexCount);
                MeshBuffers meshBuffersReference = new MeshBuffers(frameVertexCount);

                MeshAdjacency weldedAdjacency = new MeshAdjacency(meshBuffersReference, clip.settings.solveWelded);

                EditorUtility.DisplayProgressBar(progressTitle, "Importing frames", progressIndex++ / progressCount);
                {
                    var sourceRotation = Quaternion.Euler(clip.settings.applyRotation);
                    var sourceScale    = clip.settings.applyScaling;

                    if (useExternalLoader)
                    {
                        using (var referenceObj = NativeMeshObjLoader.Parse(referenceObjPath))
                        {
                            meshBuffersReference.LoadFrom(referenceObj);
                            meshBuffersReference.ApplyRotation(sourceRotation);
                            meshBuffersReference.ApplyScale(sourceScale);
                        }
                    }
                    else
                    {
                        meshBuffersReference.LoadFrom(referenceMeshAsset);
                        meshBuffersReference.ApplyRotation(sourceRotation);
                        meshBuffersReference.ApplyScale(sourceScale);
                    }

                    var denoiseIndices = ResolveIndexArrayFromVertexSelectionArray(clip.settings.denoiseRegions, weldedAdjacency);
                    var denoiseFactor  = clip.settings.denoiseStrength;
                    if (denoiseFactor < float.Epsilon)
                    {
                        denoiseIndices = new int[0];
                    }

                    var transplantIndices = ResolveIndexArrayFromVertexSelectionArray(clip.settings.transplantRegions, weldedAdjacency);
                    var transplantFactor  = clip.settings.transplantStrength;
                    if (transplantFactor < float.Epsilon)
                    {
                        transplantIndices = new int[0];
                    }

#if SOLVE_FULL_LAPLACIAN
                    var laplacianConstraintCount   = frameVertexCount;
                    var laplacianConstraintIndices = null as int[];

                    unsafe
                    {
                        using (var laplacianFreeVertexMap = new UnsafeArrayBool(frameVertexCount))
                        {
                            laplacianFreeVertexMap.Clear(false);

                            for (int k = 0; k != denoiseIndices.Length; k++)
                            {
                                if (laplacianFreeVertexMap.val[denoiseIndices[k]] == false)
                                {
                                    laplacianFreeVertexMap.val[denoiseIndices[k]] = true;
                                    laplacianConstraintCount--;
                                }
                            }

                            for (int k = 0; k != transplantIndices.Length; k++)
                            {
                                if (laplacianFreeVertexMap.val[transplantIndices[k]] == false)
                                {
                                    laplacianFreeVertexMap.val[transplantIndices[k]] = true;
                                    laplacianConstraintCount--;
                                }
                            }

                            laplacianConstraintIndices = new int[laplacianConstraintCount];

                            for (int i = 0, k = 0; i != frameVertexCount; i++)
                            {
                                if (laplacianFreeVertexMap.val[i] == false)
                                {
                                    laplacianConstraintIndices[k++] = i;
                                }
                            }
                        }
                    }
#else
                    var laplacianROIIndices      = denoiseIndices.Union(transplantIndices).ToArray();
                    var laplacianConstraintCount = frameVertexCount - laplacianROIIndices.Length;
#endif

#if SOLVE_FULL_LAPLACIAN
                    var laplacianTransform = null as MeshLaplacianTransform;
#else
                    var laplacianTransform = null as MeshLaplacianTransformROI;
#endif
                    var meshLaplacian          = new MeshLaplacian();
                    var meshLaplacianDenoised  = new MeshLaplacian();
                    var meshLaplacianReference = new MeshLaplacian();

                    var laplacianResolve = (laplacianConstraintCount < frameVertexCount);
                    if (laplacianResolve)
                    {
#if SOLVE_FULL_LAPLACIAN
                        laplacianTransform = new MeshLaplacianTransform(weldedAdjacency, laplacianConstraintIndices);
#else
                        laplacianTransform = new MeshLaplacianTransformROI(weldedAdjacency, laplacianROIIndices, 0);
                        {
                            for (int i = 0; i != denoiseIndices.Length; i++)
                            {
                                denoiseIndices[i] = laplacianTransform.internalFromExternal[denoiseIndices[i]];
                            }
                            for (int i = 0; i != transplantIndices.Length; i++)
                            {
                                transplantIndices[i] = laplacianTransform.internalFromExternal[transplantIndices[i]];
                            }
                        }
#endif
                        laplacianTransform.ComputeMeshLaplacian(meshLaplacianDenoised, meshBuffersReference);
                        laplacianTransform.ComputeMeshLaplacian(meshLaplacianReference, meshBuffersReference);
                    }

                    for (int i = 0; i != frameCount; i++)
                    {
                        EditorUtility.DisplayProgressBar(progressTitle, "Importing frames (" + (i + 1) + " / " + frameCount + ")", (progressIndex - 1 + ((float)i / frameCount)) / progressCount);

                        if (useExternalLoader)
                        {
                            if (clip.settings.externalObjPreloadThreaded)
                            {
                                meshBuffers.LoadFrom(sourceObjsPreloaded[i]);
                            }
                            else
                            {
                                using (var sourceObj_i = NativeMeshObjLoader.Parse(sourceObjPaths[i]))
                                {
                                    meshBuffers.LoadFrom(sourceObj_i);
                                }
                            }

                            meshBuffers.ApplyRotation(sourceRotation);
                            meshBuffers.ApplyScale(sourceScale);
                        }
                        else
                        {
                            meshBuffers.LoadFrom(sourceMeshAssets[i]);
                            meshBuffers.ApplyRotation(sourceRotation);
                            meshBuffers.ApplyScale(sourceScale);
                        }

                        if (meshBuffers.vertexCount != frameVertexCount)
                        {
                            Debug.LogWarning("frame " + i + " has " + meshBuffers.vertexCount + " vertices (expected " + frameVertexCount + ")");
                        }

                        if (laplacianResolve)
                        {
                            laplacianTransform.ComputeMeshLaplacian(meshLaplacian, meshBuffers);

                            double historyFactor = denoiseFactor;
                            foreach (int j in denoiseIndices)
                            {
                                double dx = denoiseFactor * meshLaplacianDenoised.vertexDifferentialX[j] + (1.0 - denoiseFactor) * meshLaplacian.vertexDifferentialX[j];
                                double dy = denoiseFactor * meshLaplacianDenoised.vertexDifferentialY[j] + (1.0 - denoiseFactor) * meshLaplacian.vertexDifferentialY[j];
                                double dz = denoiseFactor * meshLaplacianDenoised.vertexDifferentialZ[j] + (1.0 - denoiseFactor) * meshLaplacian.vertexDifferentialZ[j];
                                meshLaplacian.vertexDifferentialX[j]         = dx;
                                meshLaplacian.vertexDifferentialY[j]         = dy;
                                meshLaplacian.vertexDifferentialZ[j]         = dz;
                                meshLaplacianDenoised.vertexDifferentialX[j] = dx;
                                meshLaplacianDenoised.vertexDifferentialY[j] = dy;
                                meshLaplacianDenoised.vertexDifferentialZ[j] = dz;
                            }

                            foreach (int j in transplantIndices)
                            {
                                meshLaplacian.vertexDifferentialX[j] = transplantFactor * meshLaplacianReference.vertexDifferentialX[j] + (1.0 - transplantFactor) * meshLaplacian.vertexDifferentialX[j];
                                meshLaplacian.vertexDifferentialY[j] = transplantFactor * meshLaplacianReference.vertexDifferentialY[j] + (1.0 - transplantFactor) * meshLaplacian.vertexDifferentialY[j];
                                meshLaplacian.vertexDifferentialZ[j] = transplantFactor * meshLaplacianReference.vertexDifferentialZ[j] + (1.0 - transplantFactor) * meshLaplacian.vertexDifferentialZ[j];
                            }

                            laplacianTransform.ResolveMeshBuffers(meshBuffers, meshLaplacian);

                            meshBuffers.RecalculateNormals(weldedAdjacency);
                            meshBuffers.ApplyWeldedChanges(weldedAdjacency);
                        }

                        frames[i].SetAlbedo(sourceAlbedoAssets != null ? sourceAlbedoAssets[i] : null);
                        frames[i].SetDeltas(meshBuffersReference, meshBuffers);
                    }

                    for (int i = 0; i != subframeCount; i++)
                    {
                        subframes[i].frameIndexLo = i;
                        subframes[i].frameIndexHi = i + 1;
                        subframes[i].fractionLo   = 0.0f;
                        subframes[i].fractionHi   = 1.0f;
                    }

                    if (clip.settings.keyframes)
                    {
                        ImportFrameIntervalsFromCSV(clip.settings.keyframesCSV, frameCount - 1, ref subframeCount, ref subframes);
                    }
                }

                EditorUtility.DisplayProgressBar(progressTitle, "Transferring frames", progressIndex++ / progressCount);
                if (clip.settings.transferTarget != null)
                {
                    var targetBuffers       = new MeshBuffers(clip.settings.transferTarget);
                    var targetVertexCount   = targetBuffers.vertexCount;
                    var targetVertexResolve = new int[targetVertexCount];

                    Debug.LogFormat("transferMode: {0}", clip.settings.transferMode);
                    Debug.LogFormat("targetVertexCount: {0}", targetVertexCount);

                    switch (clip.settings.transferMode)
                    {
                    case SkinDeformationClip.ImportSettings.TransferMode.ByVertexIndex:
                    {
                        Debug.Assert(frameVertexCount == targetVertexCount, "target vertex count does not match reference vertex count");
                        for (int i = 0; i != targetVertexCount; i++)
                        {
                            targetVertexResolve[i] = i;
                        }
                    }
                    break;

                    case SkinDeformationClip.ImportSettings.TransferMode.ByVertexPosition:
                    {
                        var referenceBSP             = new KdTree3(meshBuffersReference.vertexPositions, meshBuffersReference.vertexCount);
                        var referenceDelta2Max       = 0.0f;
                        var referenceDelta2Count     = 0;
                        var referenceDelta2Threshold = 1e-10f;

                        for (int i = 0; i != targetVertexCount; i++)
                        {
                            targetVertexResolve[i] = referenceBSP.FindNearest(ref targetBuffers.vertexPositions[i]);
                        }

                        for (int i = 0; i != targetVertexCount; i++)
                        {
                            Vector3 posTarget    = targetBuffers.vertexPositions[i];
                            Vector3 posReference = meshBuffersReference.vertexPositions[targetVertexResolve[i]];

                            var delta2 = Vector3.SqrMagnitude(posTarget - posReference);
                            if (delta2 > referenceDelta2Threshold)
                            {
                                referenceDelta2Max = Mathf.Max(delta2, referenceDelta2Max);
                                referenceDelta2Count++;
                            }
                        }

                        if (referenceDelta2Count > 0)
                        {
                            Debug.LogWarning("some (" + referenceDelta2Count + ") target vertices were far from reference (max delta: " + referenceDelta2Max + ")");
                        }
                    }
                    break;
                    }

                    var targetDeltaPositions = new Vector3[targetVertexCount];
                    var targetDeltaNormals   = new Vector3[targetVertexCount];

                    for (int frameIndex = 0; frameIndex != frameCount; frameIndex++)
                    {
                        EditorUtility.DisplayProgressBar(progressTitle, "Transferring frames (" + (frameIndex + 1) + " / " + frameCount + ")", (progressIndex - 1 + ((float)frameIndex / frameCount)) / progressCount);

                        for (int i = 0; i != targetVertexCount; i++)
                        {
                            int j = targetVertexResolve[i];                            // reference index

                            targetDeltaPositions[i] = frames[frameIndex].deltaPositions[j];
                            targetDeltaNormals[i]   = frames[frameIndex].deltaNormals[j];
                        }

                        ArrayUtils.ResizeChecked(ref frames[frameIndex].deltaPositions, targetVertexCount);
                        ArrayUtils.ResizeChecked(ref frames[frameIndex].deltaNormals, targetVertexCount);

                        ArrayUtils.CopyChecked(targetDeltaPositions, ref frames[frameIndex].deltaPositions, targetVertexCount);
                        ArrayUtils.CopyChecked(targetDeltaNormals, ref frames[frameIndex].deltaNormals, targetVertexCount);
                    }

                    frameVertexCount = targetVertexCount;

                    /*
                     * switch (clip.importSettings.transferMode)
                     * {
                     *      case SkinDeformationClip.TransferMode.PassThrough:
                     *              {
                     *                      // clean copy
                     *              }
                     *              break;
                     *
                     *      case SkinDeformationClip.TransferMode.PassThroughWithFirstFrameDelta:
                     *              {
                     *                      buffersFrameX.LoadFrom(clip.importSettings.transferTarget);
                     *
                     *                      Vector3 anchorOrigin = buffersFrame0.CalcMeshCenter();
                     *                      Vector3 anchorTarget = buffersFrameX.CalcMeshCenter();
                     *                      Vector3 offsetTarget = anchorOrigin - anchorTarget;
                     *                      for (int j = 0; j != frameVertexCount; j++)
                     *                      {
                     *                              buffersFrameX.vertexPositions[j] += offsetTarget;
                     *                      }
                     *
                     *                      SkinDeformation firstFrameDelta;
                     *                      firstFrameDelta = new SkinDeformation();
                     *                      firstFrameDelta.SetDeltas(buffersFrameX, buffersFrame0);
                     *
                     *                      for (int i = 0; i != frameCount; i++)
                     *                      {
                     *                              EditorUtility.DisplayProgressBar(progressTitle, "Retargeting frames", (progressIndex - 1 + ((float)i / frameCount)) / progressCount);
                     *
                     *                              for (int j = 0; j != frameVertexCount; j++)
                     *                              {
                     *                                      frames[i].deltaPositions[j] += firstFrameDelta.deltaPositions[j];
                     *                                      frames[i].deltaNormals[j] += firstFrameDelta.deltaNormals[j];
                     *                              }
                     *                      }
                     *              }
                     *              break;
                     * }
                     */
                }

                EditorUtility.DisplayProgressBar(progressTitle, "Fitting frames to blend shapes", progressIndex++ / progressCount);
                if (clip.settings.transferTarget != null)
                {
                    if (clip.settings.fitToBlendShapes)
                    {
                        frameFittedWeightsCount = clip.settings.transferTarget.blendShapeCount;
                    }
                    else
                    {
                        frameFittedWeightsCount = 0;
                    }

                    for (int i = 0; i != frameCount; i++)
                    {
                        frames[i].fittedWeights = new float[frameFittedWeightsCount];
                    }

                    if (frameFittedWeightsCount > 0)
                    {
                        var blendShapeIndicesCommaSep = clip.settings.fittedIndices;
                        var blendShapeIndices         = Array.ConvertAll <string, int>(blendShapeIndicesCommaSep.Split(','), int.Parse);

                        SkinDeformationFitting.FitFramesToBlendShapes(frames, clip.settings.transferTarget, blendShapeIndices, clip.settings.fittingMethod, clip.settings.fittingParam);
                    }
                }
                else
                {
                    for (int i = 0; i != frameCount; i++)
                    {
                        frames[i].fittedWeights = new float[0];
                    }
                }

                EditorUtility.DisplayProgressBar(progressTitle, "Saving binary", progressIndex++ / progressCount);
                {
                    clip.settingsLastImported    = clip.settings.Clone();
                    clip.frameCount              = frameCount;
                    clip.frameVertexCount        = frameVertexCount;
                    clip.frameFittedWeightsCount = frameFittedWeightsCount;
                    clip.frames = frames;
                    clip.framesContainAlbedo        = (frames[0].albedo != null);
                    clip.framesContainDeltas        = (frames[0].deltaPositions.Length > 0);
                    clip.framesContainFittedWeights = (frames[0].fittedWeights.Length > 0);
                    clip.subframeCount = subframeCount;
                    clip.subframes     = subframes;
                    clip.version++;

                    EditorUtility.SetDirty(clip);
                    AssetDatabase.SaveAssets();

                    clip.SaveFrameData();
                }
            }
            catch (Exception ex)
            {
                Debug.LogError(ex);
            }
            finally
            {
                if (sourceObjsPreloaded != null)
                {
                    for (int i = 0; i != sourceObjsPreloaded.Length; i++)
                    {
                        sourceObjsPreloaded[i].Dispose();
                    }

                    sourceObjsPreloaded = null;
                }

                EditorUtility.ClearProgressBar();
            }
        }
        void RenderBlendInputs()
        {
            int fittedWeightsBufferSize = 0;

            {
                fittedWeightsAvailable = false;
                for (int i = 0; i != blendInputs.Length; i++)
                {
                    if (blendInputs[i].active == false || blendInputs[i].clip.framesContainFittedWeights == false)
                    {
                        continue;
                    }

                    fittedWeightsBufferSize = Mathf.Max(fittedWeightsBufferSize, blendInputs[i].clip.frameFittedWeightsCount);
                    fittedWeightsAvailable  = true;
                }

                if (fittedWeightsAvailable)
                {
                    ArrayUtils.ResizeChecked(ref fittedWeights, fittedWeightsBufferSize);
                }

                for (int i = 0; i != fittedWeights.Length; i++)
                {
                    fittedWeights[i] = 0.0f;
                }
            }

            ArrayUtils.ResizeChecked(ref blendedPositions, meshAssetBuffers.vertexCount);
            ArrayUtils.ResizeChecked(ref blendedNormals, meshAssetBuffers.vertexCount);

            Array.Copy(meshAssetBuffers.vertexPositions, blendedPositions, meshAssetBuffers.vertexCount);
            Array.Copy(meshAssetBuffers.vertexNormals, blendedNormals, meshAssetBuffers.vertexCount);
            {
                if (smrProps == null)
                {
                    smrProps = new MaterialPropertyBlock();
                }

                smr.GetPropertyBlock(smrProps);
                {
                    for (int i = 0; i != blendInputs.Length; i++)
                    {
                        RenderBlendInputAdditive(i);
                    }
                }
                smr.SetPropertyBlock(smrProps);
            }

            meshInstance.SilentlySetVertices(blendedPositions);
            meshInstance.SilentlySetNormals(blendedNormals);

            if (forceRecalculateTangents)
            {
                meshInstance.SilentlyRecalculateTangents();
            }

            if (renderFittedWeights)
            {
                if (fittedWeightsAvailable == false)
                {
                    Debug.LogWarning("SkinDeformationRenderer is trying to render fitted weights, but none are available", this);
                }

                for (int i = 0; i != fittedWeights.Length; i++)
                {
                    smr.SetBlendShapeWeight(i, 100.0f * (fittedWeights[i] * renderFittedWeightsScale));
                }
            }
            else
            {
                if (renderFittedWeightsPrev)
                {
                    for (int i = 0; i != fittedWeights.Length; i++)
                    {
                        smr.SetBlendShapeWeight(i, 0.0f);
                    }
                }

                if (muteFacialRig)
                {
                    var blendShapeCount = meshInstance.blendShapeCount;
                    if (blendShapeCount != muteFacialRigExcludeMark.Length || muteFacialRigExclude != muteFacialRigExcludePrev)
                    {
                        ArrayUtils.ResizeChecked(ref muteFacialRigExcludeMark, blendShapeCount);
                        Array.Clear(muteFacialRigExcludeMark, 0, blendShapeCount);

                        var excludeNames = muteFacialRigExclude.Split(',');
                        foreach (var excludeName in excludeNames)
                        {
                            var excludeIndex = meshInstance.GetBlendShapeIndex(meshAsset.name + "_" + excludeName.Trim());
                            if (excludeIndex != -1)
                            {
                                muteFacialRigExcludeMark[excludeIndex] = true;
                            }
                        }

                        muteFacialRigExcludePrev = muteFacialRigExclude;
                    }

                    for (int i = 0; i != blendShapeCount; i++)
                    {
                        if (muteFacialRigExcludeMark[i] == false)
                        {
                            smr.SetBlendShapeWeight(i, 0.0f);
                        }
                    }
                }
                else
                {
                    ArrayUtils.ResizeChecked(ref muteFacialRigExcludeMark, 0);
                }
            }

            blendInputsRendered = true;
        }