#pragma warning disable 0219 //#define SOLVE_FULL_LAPLACIAN using System; using System.IO; using System.Linq; using UnityEngine; using UnityEditor; namespace Unity.DemoTeam.DigitalHuman { [CustomEditor(typeof(SkinDeformationClip)), CanEditMultipleObjects] public class SkinDeformationClipEditor : Editor { static GUIStyle miniLabelAlignLeft; static GUIStyle miniLabelAlignRight; static bool framesFoldout = true; static Vector2 framesScroll = Vector2.zero; void FittedIndices_SetFromTargetMesh(object userData) { var clip = userData as SkinDeformationClip; var iarr = SkinDeformationFittingImpl.GetBlendShapeIndices(clip.importSettings.transferTarget); clip.importSettings.fittedIndices = String.Join(",", iarr.Select(i => i.ToString()).ToArray()); } void FittedIndices_SetPrecomputed(object userData) { var clip = userData as SkinDeformationClip; var iarr = new int[] { // all indices from snappers head // 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317 // subtract linearly dependent indices // 23,106,107,108,109,176,177,178,179,296,297,298,299 // => 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317 }; clip.importSettings.fittedIndices = String.Join(",", iarr.Select(i => i.ToString()).ToArray()); } void FittedIndices_SetPrecomputedWrinkles(object userData) { var clip = userData as SkinDeformationClip; var iarr = new int[] { // all indices from snappers head that contribute to wrinkle maps // => 1,14,15,44,48,51,54,59,62,67,72,80,85,87,89,91,93,95,97,110,113,116,118,119,121,123,126,129,132,135,138,141,144,147,150,158,163,181,183,184,187,191,194,197,200,207,209,219,221,227,229,230,231,232,233,235,237,240,241,250,251,252,253,254,255,256,257,258,259,264,265,290,291,292,293,294,295 }; clip.importSettings.fittedIndices = String.Join(",", iarr.Select(i => i.ToString()).ToArray()); } void FittedIndices_ApplyFormattingAndSortAscending(object userData) { var clip = userData as SkinDeformationClip; var istr = clip.importSettings.fittedIndices; var iarr = Array.ConvertAll(istr.Split(','), int.Parse); Array.Sort(iarr); clip.importSettings.fittedIndices = String.Join(",", iarr.Select(i => i.ToString()).ToArray()); } void FittedIndices_ApplyFilterToEnsureLinearlyIndependent(object userData) { var clip = userData as SkinDeformationClip; var istr = clip.importSettings.fittedIndices; var iarr = Array.ConvertAll(istr.Split(','), int.Parse); Array.Sort(iarr); iarr = SkinDeformationFittingImpl.ComputeLinearlyIndependentBlendShapeIndices(clip.importSettings.transferTarget, iarr); if (iarr == null) { Debug.LogError("linearly independent filter did not complete"); return; } clip.importSettings.fittedIndices = String.Join(",", iarr.Select(i => i.ToString()).ToArray()); } void OnDisable() { SkinDeformationClipPreview.Disable(); } public override void OnInspectorGUI() { if (miniLabelAlignLeft == null) { miniLabelAlignLeft = new GUIStyle(EditorStyles.miniLabel); miniLabelAlignLeft.alignment = TextAnchor.UpperLeft; } if (miniLabelAlignRight == null) { miniLabelAlignRight = new GUIStyle(EditorStyles.miniLabel); miniLabelAlignRight.alignment = TextAnchor.UpperRight; } if (targets.Length == 1) { SkinDeformationClip clip = (SkinDeformationClip)target; if (clip.importSettings.solveRegionPreview) SkinDeformationClipPreview.Enable(clip); else SkinDeformationClipPreview.Disable(); EditorGUILayout.HelpBox(GetInfoString(clip), MessageType.Info, true); if (GUILayout.Button("Import")) { ImportClip(clip); } EditorGUILayout.Separator(); base.OnInspectorGUI(); EditorGUILayout.Separator(); if (GUILayout.Button("Configure Fitted Indices ...")) { GUI.FocusControl(null); var menu = new GenericMenu(); menu.AddItem(new GUIContent("Set from target mesh"), false, FittedIndices_SetFromTargetMesh, clip); menu.AddItem(new GUIContent("Set precomputed linearly independent"), false, FittedIndices_SetPrecomputed, clip); menu.AddItem(new GUIContent("Set precomputed linearly independent (wrinkles only)"), false, FittedIndices_SetPrecomputedWrinkles, clip); menu.AddItem(new GUIContent("Apply formatting and sort ascending"), false, FittedIndices_ApplyFormattingAndSortAscending, clip); menu.AddItem(new GUIContent("Apply filter to ensure linearly independent"), false, FittedIndices_ApplyFilterToEnsureLinearlyIndependent, clip); menu.ShowAsContext(); } EditorGUILayout.Separator(); framesFoldout = EditorGUILayout.Foldout(framesFoldout, "Frame intervals", EditorStyles.foldout); if (framesFoldout) { framesScroll = EditorGUILayout.BeginScrollView(framesScroll, false, true); for (int i = 0; i != clip.subframeCount; i++) { Rect rectGroup = EditorGUILayout.BeginHorizontal(); { EditorGUILayout.PrefixLabel("interval " + i, EditorStyles.miniLabel); { Rect rectLabel = GUILayoutUtility.GetLastRect(); Rect rectFrame = new Rect(rectLabel); rectFrame.xMin = rectLabel.xMax; rectFrame.xMax = rectGroup.xMax; EditorGUI.DrawRect(rectFrame, Color.black); rectFrame.xMin += 1.0f; rectFrame.xMax -= 1.0f; rectFrame.yMin += 1.0f; rectFrame.yMax -= 1.0f; EditorGUI.DrawRect(rectFrame, Color.Lerp(Color.black, Color.green, 0.05f)); float x0 = rectFrame.xMin + rectFrame.width * clip.subframes[i].fractionLo; float x1 = rectFrame.xMin + rectFrame.width * clip.subframes[i].fractionHi; float y0 = clip.subframes[i].fractionLo; float y1 = clip.subframes[i].fractionHi; rectFrame.xMin = x0; rectFrame.xMax = x1; //EditorGUI.DrawRect(rectFrame, Color.Lerp(Color.black, Color.green, 0.4f)); Handles.color = Color.Lerp(Color.black, Color.green, 0.4f); Handles.DrawAAConvexPolygon( new Vector3(rectFrame.xMin, rectFrame.yMax, 0.0f), new Vector3(rectFrame.xMax, rectFrame.yMax, 0.0f), new Vector3(rectFrame.xMax, rectFrame.yMin + rectFrame.height * y1, 0.0f), new Vector3(rectFrame.xMin, rectFrame.yMin + rectFrame.height * y0, 0.0f) ); Handles.color = Color.Lerp(Color.black, Color.magenta, 0.4f); Handles.DrawAAConvexPolygon( new Vector3(rectFrame.xMin, rectFrame.yMax - rectFrame.height * (1.0f - y0), 0.0f), new Vector3(rectFrame.xMax, rectFrame.yMax - rectFrame.height * (1.0f - y1), 0.0f), new Vector3(rectFrame.xMax, rectFrame.yMin, 0.0f), new Vector3(rectFrame.xMin, rectFrame.yMin, 0.0f) ); } EditorGUILayout.TextField("keyframe " + clip.subframes[i].frameIndexLo, miniLabelAlignLeft); EditorGUILayout.TextField("keyframe " + clip.subframes[i].frameIndexHi, miniLabelAlignRight); } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); } else { framesScroll = Vector2.zero; } } else { SkinDeformationClipPreview.Disable(); EditorGUILayout.HelpBox("Skin Deformation Clip (multiple)", MessageType.Info, true); if (GUILayout.Button("Import")) { foreach (var target in targets) { SkinDeformationClip clip = (SkinDeformationClip)target; ImportClip(clip); } } EditorGUILayout.Separator(); base.OnInspectorGUI(); } } static T[] GetAssetsAtPath(string path, string name) where T : UnityEngine.Object { if (Directory.Exists(path) == false) return new T[0]; var guids = AssetDatabase.FindAssets(name + " t:" + typeof(T).Name, new string[] { path }); var paths = guids.Select(g => AssetDatabase.GUIDToAssetPath(g)).OrderBy(s => s); var array = paths.Select(p => AssetDatabase.LoadAssetAtPath(p)).ToArray(); return array; } static string[] GetFilesAtPath(string path, string pattern) { if (Directory.Exists(path) == false) return new string[0]; var result = Directory.GetFiles(path, pattern); for (int i = 0; i != result.Length; i++) { result[i] = result[i].Replace('\\', '/'); } Array.Sort(result); return result; } static string GetInfoString(bool flag) { if (flag) return "YES"; else return "no"; } static string GetInfoString(SkinDeformationClip clip) { string s = string.Empty; s += "Skin Deformation Clip"; s += "\n -- # frames: " + (clip.subframeCount + 1); s += "\n -- # keyframes: " + clip.frameCount; s += "\n -- # vertices: " + clip.frameVertexCount; s += "\n -- contains deltas: " + GetInfoString(clip.framesContainDeltas); s += "\n -- contains albedo: " + GetInfoString(clip.framesContainAlbedo); s += "\n -- contains fitted weights: " + GetInfoString(clip.framesContainFittedWeights); s += "\n -- (rev " + clip.version + ")"; //s += "\n" + clip.frameDataFilename; return s; } public static int[] ResolveIndexArrayFromVertexSelection(TextAsset vertexSelection, MeshAdjacency weldedAdjacency = null) { if (vertexSelection == null) return new int[0]; var indicesCommaSep = vertexSelection.text.Trim('[', ']'); if (indicesCommaSep.Length > 0) { var parsed = Array.ConvertAll(indicesCommaSep.Split(','), int.Parse); if (weldedAdjacency != null) return parsed.Select(i => weldedAdjacency.vertexResolve[i]).Distinct().ToArray(); else return parsed; } else { return new int[0]; } } public static int[] ResolveIndexArrayFromVertexSelectionArray(TextAsset[] vertexSelections, MeshAdjacency weldedAdjacency = null) { var indices = new int[0]; foreach (var vertexSelection in vertexSelections) { indices = indices.Union(ResolveIndexArrayFromVertexSelection(vertexSelection, weldedAdjacency)).ToArray(); } return indices; } static void ImportClip(SkinDeformationClip clip) { try { var progressTitle = "Importing '" + clip.name + "'"; var progressIndex = 0; var progressCount = 4.0f; EditorUtility.DisplayProgressBar(progressTitle, "Loading assets", progressIndex++ / progressCount); var sourceObjs = null as string[]; var sourceMeshes = null as Mesh[]; var sourceAlbedos = null as Texture2D[]; int frameCount = 0; int frameVertexCount = 0; var useExternalLoader = clip.importSettings.externalLoader; if (useExternalLoader) { sourceObjs = GetFilesAtPath(clip.importSettings.externalObjPath, clip.importSettings.externalObjPattern); Debug.Assert(sourceObjs.Length > 0, ".obj count == 0 (check import settings)"); using (var nativeMesh = NativeMeshObjLoader.Parse(sourceObjs[0])) { frameCount = sourceObjs.Length; frameVertexCount = nativeMesh.vertexCount; } } else { sourceMeshes = GetAssetsAtPath(clip.importSettings.meshFolder, clip.importSettings.meshPrefix); Debug.Assert(sourceMeshes.Length > 0, "mesh count == 0 (check import settings)"); sourceAlbedos = GetAssetsAtPath(clip.importSettings.albedoFolder, clip.importSettings.albedoPrefix); if (sourceAlbedos.Length != sourceMeshes.Length) { sourceAlbedos = null; Debug.LogWarning("mesh count != albedo count: skipping albedos"); } frameCount = sourceMeshes.Length; frameVertexCount = sourceMeshes[0].vertexCount; } int frameFittedWeightsCount = 0;// modified later var frames = new SkinDeformation[frameCount]; int subframeCount = frameCount - 1; var subframes = new SkinDeformationClip.Subframe[subframeCount]; MeshBuffers buffersFrame0 = new MeshBuffers(frameVertexCount); MeshBuffers buffersFrameX = new MeshBuffers(frameVertexCount); MeshBuffers buffersTarget = buffersFrame0; if (clip.importSettings.transferTarget != null) { buffersTarget = new MeshBuffers(clip.importSettings.transferTarget); } MeshAdjacency weldedAdjacency = new MeshAdjacency(buffersTarget, clip.importSettings.solveWelded); EditorUtility.DisplayProgressBar(progressTitle, "Importing frames", progressIndex++ / progressCount); { var sourceRotation = Quaternion.Euler(clip.importSettings.applyRotation); var sourceScale = clip.importSettings.applyScale; if (useExternalLoader) { using (var nativeMesh = NativeMeshObjLoader.Parse(sourceObjs[0])) { buffersFrame0.LoadFrom(nativeMesh); buffersFrame0.ApplyRotation(sourceRotation); buffersFrame0.ApplyScale(sourceScale); } } else { buffersFrame0.LoadFrom(sourceMeshes[0]); buffersFrame0.ApplyRotation(sourceRotation); buffersFrame0.ApplyScale(sourceScale); } var denoiseFactor = clip.importSettings.denoiseFactor; var denoiseIndices = ResolveIndexArrayFromVertexSelectionArray(clip.importSettings.denoiseRegion, weldedAdjacency); if (denoiseFactor < float.Epsilon) denoiseIndices = new int[0]; var transplantFactor = clip.importSettings.transplantFactor; var transplantIndices = ResolveIndexArrayFromVertexSelectionArray(clip.importSettings.transplantRegion, weldedAdjacency); if (transplantFactor < float.Epsilon || clip.importSettings.transferTarget == null) transplantIndices = new int[0]; //TODO cleanup var transferIndices = ResolveIndexArrayFromVertexSelection(clip.importSettings.transferRegion, weldedAdjacency); #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 meshLaplacianTransform = null as MeshLaplacianTransform; #else var meshLaplacianTransform = null as MeshLaplacianTransformROI; #endif var meshLaplacian = new MeshLaplacian(); var meshLaplacianDenoised = new MeshLaplacian(); var transplantBuffers = new MeshBuffers(frameVertexCount); var transplantLaplacian = new MeshLaplacian(); var laplacianResolve = (laplacianConstraintCount < frameVertexCount); if (laplacianResolve) { #if SOLVE_FULL_LAPLACIAN meshLaplacianTransform = new MeshLaplacianTransform(weldedAdjacency, laplacianConstraintIndices); #else meshLaplacianTransform = new MeshLaplacianTransformROI(weldedAdjacency, laplacianROIIndices, 0); { for (int i = 0; i != denoiseIndices.Length; i++) denoiseIndices[i] = meshLaplacianTransform.internalFromExternal[denoiseIndices[i]]; for (int i = 0; i != transplantIndices.Length; i++) transplantIndices[i] = meshLaplacianTransform.internalFromExternal[transplantIndices[i]]; } #endif meshLaplacianTransform.ComputeMeshLaplacian(meshLaplacianDenoised, buffersFrame0); if (transplantIndices.Length > 0 && clip.importSettings.transferTarget != null) { transplantBuffers.LoadFrom(clip.importSettings.transferTarget); meshLaplacianTransform.ComputeMeshLaplacian(transplantLaplacian, transplantBuffers); } } //TODO cleanup var transferTemp = new MeshBuffers(frameVertexCount); var transferLaplacianTransform = null as MeshLaplacianTransformROI; var transferLaplacian = new MeshLaplacian(); var laplacianTransfer = (transferIndices.Length > 0) && (clip.importSettings.transferTarget != null); if (laplacianTransfer) { transferLaplacianTransform = new MeshLaplacianTransformROI(weldedAdjacency, transferIndices, 0); } for (int i = 0; i != frameCount; i++) { EditorUtility.DisplayProgressBar(progressTitle, "Importing frames", (progressIndex - 1 + ((float)i / frameCount)) / progressCount); if (useExternalLoader) { using (var nativeMesh = NativeMeshObjLoader.Parse(sourceObjs[i])) { buffersFrameX.LoadFrom(nativeMesh); buffersFrameX.ApplyRotation(sourceRotation); buffersFrameX.ApplyScale(sourceScale); } } else { buffersFrameX.LoadFrom(sourceMeshes[i]); buffersFrameX.ApplyRotation(sourceRotation); buffersFrameX.ApplyScale(sourceScale); } if (laplacianResolve) { meshLaplacianTransform.ComputeMeshLaplacian(meshLaplacian, buffersFrameX); 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 * transplantLaplacian.vertexDifferentialX[j] + (1.0 - transplantFactor) * meshLaplacian.vertexDifferentialX[j]; meshLaplacian.vertexDifferentialY[j] = transplantFactor * transplantLaplacian.vertexDifferentialY[j] + (1.0 - transplantFactor) * meshLaplacian.vertexDifferentialY[j]; meshLaplacian.vertexDifferentialZ[j] = transplantFactor * transplantLaplacian.vertexDifferentialZ[j] + (1.0 - transplantFactor) * meshLaplacian.vertexDifferentialZ[j]; } meshLaplacianTransform.ResolveMeshBuffers(buffersFrameX, meshLaplacian); buffersFrameX.RecalculateNormals(weldedAdjacency); buffersFrameX.ApplyWeldedChanges(weldedAdjacency); } //TODO cleanup if (laplacianTransfer) { buffersTarget.CopyTo(transferTemp); transferLaplacianTransform.ComputeMeshLaplacian(transferLaplacian, buffersFrameX); transferLaplacianTransform.ResolveMeshBuffers(transferTemp, transferLaplacian); transferTemp.CopyTo(buffersFrameX); buffersFrameX.RecalculateNormals(weldedAdjacency); buffersFrameX.ApplyWeldedChanges(weldedAdjacency); } frames[i].SetAlbedo((sourceAlbedos != null) ? sourceAlbedos[i] : null); //frames[i].SetDeltas(buffersFrame0, buffersFrameX); //TODO cleanup if (laplacianTransfer) frames[i].SetDeltas(buffersTarget, buffersFrameX); else frames[i].SetDeltas(buffersFrame0, buffersFrameX); var targetVertexCount = laplacianTransfer ? buffersTarget.vertexCount : buffersFrame0.vertexCount; if (targetVertexCount != buffersFrameX.vertexCount) Debug.LogWarning("frame " + i + " has different vertexCount (" + buffersFrameX.vertexCount + " vs. " + targetVertexCount + " in frame 0)"); } 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; } ImportFrameIntervalsFromCSV(clip.importSettings.keyframesCSV, frameCount - 1, ref subframeCount, ref subframes); } EditorUtility.DisplayProgressBar(progressTitle, "Retargeting frames", progressIndex++ / progressCount); { 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].deltaTangents[j] += firstFrameDelta.deltaTangents[j]; frames[i].deltaNormals[j] += firstFrameDelta.deltaNormals[j]; } } } break; } } EditorUtility.DisplayProgressBar(progressTitle, "Fitting frames to blend shapes", progressIndex++ / progressCount); { if (clip.importSettings.fitToBlendShapes) frameFittedWeightsCount = clip.importSettings.transferTarget.blendShapeCount; else frameFittedWeightsCount = 0; for (int i = 0; i != frameCount; i++) frames[i].fittedWeights = new float[frameFittedWeightsCount]; if (frameFittedWeightsCount > 0) { var blendShapeIndicesCommaSep = clip.importSettings.fittedIndices; var blendShapeIndices = Array.ConvertAll(blendShapeIndicesCommaSep.Split(','), int.Parse); SkinDeformationFittingImpl.FitFramesToBlendShapes(frames, clip.importSettings.transferTarget, blendShapeIndices, clip.importSettings.fittingMethod, clip.importSettings.fittingParam); } } EditorUtility.DisplayProgressBar(progressTitle, "Saving binary", progressIndex++ / progressCount); { clip.lastBuild = clip.importSettings.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); clip.SaveFrameData(AssetDatabase.GetAssetPath(clip.GetInstanceID()) + "_frames.bin"); } } catch (Exception ex) { Debug.LogError(ex); } finally { EditorUtility.ClearProgressBar(); } } static void ImportFrameIntervalsFromCSV(string filename, int maxFrameIndex, ref int subframeCount, ref SkinDeformationClip.Subframe[] subframes) { if (!File.Exists(filename)) return; // skip 1st column of every row const int SKIP_COL = 1; const int NO_VALUE = -1; // rows[0] == comma sep. original frame # // rows[1] == comma sep. wrap frame # // rows[2] == comma sep. frame progress 0-100 const int ROW_ORIG = 0; const int ROW_KEYS = 1; const int ROW_TIME = 2; const int NUM_ROWS = 3; var vals = new int[NUM_ROWS][]; var valCount = NO_VALUE; using (StreamReader reader = new StreamReader(filename)) { for (int row = 0; row != NUM_ROWS; row++) { if (reader.EndOfStream) return;// bad input, missing rows var line = reader.ReadLine(); if (line == null) return;// bad input, missing rows var svals = line.Split(','); var svalCount = svals.Length; if (svalCount != valCount + SKIP_COL && valCount != NO_VALUE) return;// bad input, rows not equal length valCount = svalCount - SKIP_COL; vals[row] = new int[valCount]; for (int j = 0; j != valCount; j++) { if (!int.TryParse(svals[j + SKIP_COL], out vals[row][j])) vals[row][j] = NO_VALUE; } } } int keyIndex0 = NO_VALUE; int keyIndexN = NO_VALUE; for (int i = 0; i != valCount; i++) { int frameIndex = vals[ROW_KEYS][i]; if (frameIndex != NO_VALUE) { if (frameIndex > maxFrameIndex) break;// ignore rest of sequence keyIndexN = i; if (keyIndex0 == NO_VALUE) keyIndex0 = i; } } if (keyIndex0 == keyIndexN) return;// bad input, need at least two keyframes subframeCount = keyIndexN - keyIndex0; subframes = new SkinDeformationClip.Subframe[subframeCount]; int keyIndexLo = keyIndex0; int keyIndexHi = keyIndex0 + 1; while (keyIndexLo < keyIndexN) { while (vals[ROW_KEYS][keyIndexHi] == NO_VALUE) keyIndexHi++; int keyLo = vals[ROW_KEYS][keyIndexLo]; int keyHi = vals[ROW_KEYS][keyIndexHi]; float n = keyIndexHi - keyIndexLo; for (int i = keyIndexLo; i != keyIndexHi; i++) { subframes[i].frameIndexLo = keyLo; subframes[i].frameIndexHi = keyHi; subframes[i].fractionLo = (i - keyIndexLo + 0) / (float)n; subframes[i].fractionHi = (i - keyIndexLo + 1) / (float)n; } keyIndexLo = keyIndexHi; keyIndexHi = keyIndexHi + 1; } } } }