using System; using System.IO; using UnityEngine; using Unity.DemoTeam.Attributes; #if UNITY_EDITOR using UnityEditor; using System.Text.RegularExpressions; #endif namespace Unity.DemoTeam.DigitalHuman { [CreateAssetMenu(menuName = "Digital Human/Skin Deformation Clip")] public class SkinDeformationClip : ScriptableObject { public unsafe struct Frame { public const int FLT_STRIDE = 9; public const int FLT_OFFSET_POSITION = 0; public const int FLT_OFFSET_TANGENT = 3; public const int FLT_OFFSET_NORMAL = 6; public float* deltaPosTanNrm; //TODO don't write the data interleaved if we're not going to use it as such! //public float* deltaPositions; //public float* deltaTangents; //public float* deltaNormals; public float* fittedWeights; public Texture2D albedo; } [Serializable] public struct Subframe { public int frameIndexLo; public int frameIndexHi; public float fractionLo; public float fractionHi; } [HideInInspector] public int frameCount = 0; [HideInInspector] public float frameRate = 30.0f; [HideInInspector] public int frameVertexCount = 0; [HideInInspector] public int frameFittedWeightsCount = 0; [HideInInspector] public SkinDeformation[] frames = new SkinDeformation[0]; [HideInInspector] public bool framesContainAlbedo; [HideInInspector] public bool framesContainDeltas; [HideInInspector] public bool framesContainFittedWeights; [HideInInspector] public NativeFrameStream frameData; [HideInInspector] public string frameDataStreamingAssetsPath = null; [HideInInspector] private bool frameDataPending = true; [HideInInspector] public int subframeCount = 0; [HideInInspector] public Subframe[] subframes = new Subframe[0]; [HideInInspector] public int version = -1; //--- accessors --- public double Duration { get { return subframeCount / frameRate; } } public unsafe Frame GetFrame(int frameIndex) { if (frameDataPending) { Debug.Log("hotloading frame data"); LoadFrameData(); } var floatPtr = (float*)frameData.ReadFrame(frameIndex); { Frame frame; //frame.deltaPositions = floatPtr + 0 * frameVertexCount; //frame.deltaTangents = floatPtr + 3 * frameVertexCount; //frame.deltaNormals = floatPtr + 6 * frameVertexCount; frame.deltaPosTanNrm = floatPtr + 0 * frameVertexCount; frame.fittedWeights = floatPtr + 9 * frameVertexCount; frame.albedo = frames[frameIndex].albedo; return frame; } } public int GetFrameSizeBytes() { return (9 * frameVertexCount + frameFittedWeightsCount) * sizeof(float); } public void PrepareFrame(int frameIndex) { frameData.SeekFrame(frameIndex); } //--- import settings begin --- public enum TransferMode { PassThrough, PassThroughWithFirstFrameDelta, } [Serializable] public class ImportSettings { [Header("Asset paths")] public string keyframesCSV; [EditableIf("externalLoader", false)] public string meshFolder; [EditableIf("externalLoader", false)] public string meshPrefix; [EditableIf("externalLoader", false)] public string albedoFolder; [EditableIf("externalLoader", false)] public string albedoPrefix; public bool externalLoader = false; [EditableIf("externalLoader", true)] public string externalObjPath; [EditableIf("externalLoader", true)] public string externalObjPattern = "*.obj"; [Header("Mesh transform")] public Vector3 applyRotation = Vector3.zero; public float applyScale = 1.0f; [Header("Mesh processing")] [Range(0.0f, 1.0f)] public float denoiseFactor = 0.0f; public TextAsset[] denoiseRegion; [Range(0.0f, 1.0f)] public float transplantFactor = 0.0f; public TextAsset[] transplantRegion; public bool solveRegionPreview = false; public bool solveWelded = false; [Header("Frame transfer")] //PACKAGETODO cleanup public Mesh transferTarget; public TextAsset transferRegion; public TransferMode transferMode; [Header("Frame fitting")] public bool fitToBlendShapes = false; [TextArea(1, 20)] public string fittedIndices = ""; public SkinDeformationFitting.Method fittingMethod = SkinDeformationFitting.Method.LinearLeastSquares; public SkinDeformationFitting.Param fittingParam = SkinDeformationFitting.Param.DeltaPosition; public ImportSettings Clone() { var c = this.MemberwiseClone() as ImportSettings; c.keyframesCSV = c.keyframesCSV.Clone() as string; c.meshFolder = c.meshFolder.Clone() as string; c.meshPrefix = c.meshPrefix.Clone() as string; c.albedoFolder = c.albedoFolder.Clone() as string; c.albedoPrefix = c.albedoPrefix.Clone() as string; c.denoiseRegion = c.denoiseRegion.Clone() as TextAsset[]; c.transplantRegion = c.transplantRegion.Clone() as TextAsset[]; c.fittedIndices = c.fittedIndices.Clone() as string; return c; } } [ReadOnly] public ImportSettings lastBuild = new ImportSettings(); public ImportSettings importSettings = new ImportSettings(); //--- import settings end --- //--- frame data serialization begin --- void OnEnable() { if (frameDataPending) { LoadFrameData(); } } void OnDisable() { UnloadFrameData(); } void OnDestroy() { UnloadFrameData(); } void LoadFrameData() { #if UNITY_EDITOR string filename = AssetDatabase.GetAssetPath(this) + "_frames.bin"; #else string filename = Application.streamingAssetsPath + frameDataStreamingAssetsPath; Debug.Log("LoadFrameData " + filename + ")"); #endif int frameOffset = 3 * sizeof(Int32); int frameSize = GetFrameSizeBytes(); frameData.Dispose(); frameData = new NativeFrameStream(filename, frameOffset, frameCount, frameSize, 2, 16); frameDataPending = false; if (!File.Exists(filename)) { Debug.LogError("failed to load frame data (filename = " + filename + ")"); return; } /* OLD for (int i = 0; i != frameCount; i++) { frames[i].Allocate(frameVertexCount, frameFittedWeightsCount); } using (FileStream stream = File.OpenRead(filename)) { using (BinaryReader reader = new BinaryReader(stream)) { int __frameCount = reader.ReadInt32(); int __frameVertexCount = reader.ReadInt32(); int __frameFittedWeightsCount = reader.ReadInt32(); Debug.Assert(__frameCount == frameCount); Debug.Assert(__frameVertexCount == frameVertexCount); Debug.Assert(__frameFittedWeightsCount == frameFittedWeightsCount); var srcCursor = 0; var srcBuffer = new float[3 * 3 * frameVertexCount + 1 * frameFittedWeightsCount]; var dstBuffer = new byte[4 * srcBuffer.Length]; for (int i = 0; i != frameCount; i++) { reader.Read(dstBuffer, 0, dstBuffer.Length); Buffer.BlockCopy(dstBuffer, 0, srcBuffer, 0, dstBuffer.Length); for (int j = 0; j != frameVertexCount; j++) { frames[i].deltaPositions[j].x = srcBuffer[srcCursor++]; frames[i].deltaPositions[j].y = srcBuffer[srcCursor++]; frames[i].deltaPositions[j].z = srcBuffer[srcCursor++]; frames[i].deltaTangents[j].x = srcBuffer[srcCursor++]; frames[i].deltaTangents[j].y = srcBuffer[srcCursor++]; frames[i].deltaTangents[j].z = srcBuffer[srcCursor++]; frames[i].deltaNormals[j].x = srcBuffer[srcCursor++]; frames[i].deltaNormals[j].y = srcBuffer[srcCursor++]; frames[i].deltaNormals[j].z = srcBuffer[srcCursor++]; } for (int j = 0; j != frameFittedWeightsCount; j++) { frames[i].fittedWeights[j] = srcBuffer[srcCursor++]; } srcCursor = 0; } frameDataPending = false; } } */ } public void UnloadFrameData() { frameData.Dispose(); frameDataPending = true; } #if UNITY_EDITOR public void SaveFrameData() { string filenameAsset = AssetDatabase.GetAssetPath(this); string filenameFrameData = filenameAsset + "_frames.bin"; UnloadFrameData(); if (File.Exists(filenameFrameData)) File.Delete(filenameFrameData); using (FileStream stream = File.Create(filenameFrameData)) { using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write(frameCount); writer.Write(frameVertexCount); writer.Write(frameFittedWeightsCount); var srcCursor = 0; var srcBuffer = new float[3 * 3 * frameVertexCount + 1 * frameFittedWeightsCount]; var dstBuffer = new byte[4 * srcBuffer.Length]; for (int i = 0; i != frameCount; i++) { srcCursor = 0; Debug.Assert(frames[i].deltaPositions.Length == frameVertexCount, "invalid vertex count"); for (int j = 0; j != frameVertexCount; j++) { srcBuffer[srcCursor++] = frames[i].deltaPositions[j].x; srcBuffer[srcCursor++] = frames[i].deltaPositions[j].y; srcBuffer[srcCursor++] = frames[i].deltaPositions[j].z; srcBuffer[srcCursor++] = frames[i].deltaTangents[j].x; srcBuffer[srcCursor++] = frames[i].deltaTangents[j].y; srcBuffer[srcCursor++] = frames[i].deltaTangents[j].z; srcBuffer[srcCursor++] = frames[i].deltaNormals[j].x; srcBuffer[srcCursor++] = frames[i].deltaNormals[j].y; srcBuffer[srcCursor++] = frames[i].deltaNormals[j].z; } Debug.Assert(frames[i].fittedWeights.Length == frameFittedWeightsCount, "invalid fitted weights count"); for (int j = 0; j != frameFittedWeightsCount; j++) { srcBuffer[srcCursor++] = frames[i].fittedWeights[j]; } Buffer.BlockCopy(srcBuffer, 0, dstBuffer, 0, dstBuffer.Length); writer.Write(dstBuffer, 0, dstBuffer.Length); writer.Flush(); } frameDataPending = true; } } } #endif //--- frame data serialization end --- #if UNITY_EDITOR [ContextMenu("Save To StreamingAssets")] public void SaveToStreamingAssets() { string filenameAsset = AssetDatabase.GetAssetPath(this); string filenameFrameData = filenameAsset + "_frames.bin"; frameDataStreamingAssetsPath = "/SkinDeformationClip/" + AssetDatabase.AssetPathToGUID(filenameAsset) + "__" + this.name; var copySrc = filenameFrameData; var copyDst = Application.streamingAssetsPath + frameDataStreamingAssetsPath; //Debug.Log("filenameAsset: " + filenameAsset); //Debug.Log("copySrc: " + copySrc); //Debug.Log("copyDst: " + copyDst); var copyDstDir = copyDst.Substring(0, copyDst.LastIndexOf('/')); try { if (File.Exists(copyDst)) File.Delete(copyDst); Directory.CreateDirectory(copyDstDir); File.Copy(copySrc, copyDst); } catch (Exception ex) { Debug.LogError(ex.ToString()); } } #endif } #if UNITY_EDITOR public class SkinDeformationClipBuildProcessor : UnityEditor.Build.IPreprocessBuildWithReport { public int callbackOrder { get { return 0; } } public void OnPreprocessBuild(UnityEditor.Build.Reporting.BuildReport report) { var clips = Resources.FindObjectsOfTypeAll(); foreach (var clip in clips) { clip.SaveToStreamingAssets(); EditorUtility.SetDirty(clip); } AssetDatabase.SaveAssets(); } } #endif }