using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; using System.Text; using System.Text.RegularExpressions; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace Unity.DemoTeam.DigitalHuman { [CreateAssetMenu(menuName = "Digital Human/Snappers Head Importer", fileName = "MyCharacter_SnappersHeadImporter", order = 50)] public class SnappersHeadImporter : ScriptableObject { const int SB_SIZE = 65536; [Header("Source stacks")] public List scriptsResolveControllers; public List scriptsResolveBlendShapes; public List scriptsResolveShaderParam; [Header("Source literals")] public string namedControllers = "Controllers"; public string namedBlendShapes = "BlendShapes"; public string namedShaderParam = "ShaderParam"; [Header("Output settings")] public string csClassPrefix = "MyCharacter"; public string csNamespace = "MyNamespace"; public bool csCompacted = true; const string compactedControllers = "a"; const string compactedBlendShapes = "b"; const string compactedShaderParam = "c"; #if UNITY_EDITOR [ContextMenu("Generate")] public void Generate() { var includedControllers = new Dictionary(); var includedBlendShapes = new HashSet(); var includedShaderParam = new HashSet(); var indicesControllers = new Dictionary(); var indicesBlendShapes = new Dictionary(); var indicesShaderParam = new Dictionary(); // discover data members { DiscoverDataMembers(scriptsResolveControllers, includedControllers, includedBlendShapes, includedShaderParam, skipCaps: true); DiscoverDataMembers(scriptsResolveBlendShapes, includedControllers, includedBlendShapes, includedShaderParam, skipCaps: false); DiscoverDataMembers(scriptsResolveShaderParam, includedControllers, includedBlendShapes, includedShaderParam, skipCaps: false); } // generate data structures { GenerateDataStructure("SnappersControllers", indicesControllers, includedControllers.Keys); GenerateDataStructure("SnappersBlendShapes", indicesBlendShapes, includedBlendShapes); // ................... SnappersShaderParam.. already defined GenerateDataStructureIndices(indicesShaderParam); } // generate implementation { GenerateImplementation(scriptsResolveControllers, scriptsResolveBlendShapes, scriptsResolveShaderParam, includedControllers, indicesControllers, indicesBlendShapes, indicesShaderParam); } AssetDatabase.Refresh(); Debug.LogFormat("trying to create: {0}.{1}_SnappersHead", csNamespace, csClassPrefix); var scriptInstance = CreateInstance(string.Format("{0}.{1}_SnappersHead", csNamespace, csClassPrefix)); if (scriptInstance != null) { var outputDir = GetWritePath(); var outputPath = string.Format("{0}/{1}_SnappersHead.asset", outputDir, csClassPrefix); AssetDatabase.CreateAsset(scriptInstance, outputPath); AssetDatabase.SaveAssets(); } } public void DiscoverDataMembers(List scripts, Dictionary includedControllers, HashSet includedBlendShapes, HashSet includedShaderParams, bool skipCaps) { foreach (var script in scripts) { if (script == null) continue; DiscoverDataMembers(script, includedControllers, includedBlendShapes, includedShaderParams, skipCaps); } } public void DiscoverDataMembers(TextAsset script, Dictionary includedControllers, HashSet includedBlendShapes, HashSet includedShaderParams, bool skipCaps) { var regexMember = new Regex("([a-zA-Z_]+[\\w]*)\\.([a-zA-Z_]+[\\w]*)"); var inputPath = AssetDatabase.GetAssetPath(script); var inputStream = new StreamReader(inputPath); while (inputStream.EndOfStream == false) { var line = inputStream.ReadLine(); if (line.Length == 0) continue; // gather struct.member var matches = regexMember.Matches(line); { // note: this needs to be kept in sync with GenerateEvaluationFunctionSegment foreach (Match match in matches) { var nameStruct = match.Groups[1].Value; var nameMember = match.Groups[2].Value; if (nameStruct == namedBlendShapes) { includedBlendShapes.Add(nameMember); } else if (nameStruct == namedShaderParam) { includedShaderParams.Add(nameMember); } else { SnappersControllerCaps caps; includedControllers.TryGetValue(nameStruct, out caps); includedControllers[nameStruct] = caps | (skipCaps ? SnappersControllerCaps.none : TranslateControllerField(nameMember)); } } } } inputStream.Close(); } public void GenerateDataStructure(string name, Dictionary indices, IEnumerable fields) { var sb = new StringBuilder(SB_SIZE); List sortedFields; sortedFields = new List(fields); sortedFields.Sort(); sb.AppendLine("using Unity.DemoTeam.DigitalHuman;"); sb.AppendLine(); sb.AppendFormat("namespace {0}\n", csNamespace); sb.AppendLine("{"); sb.AppendFormat(" public struct {0}_{1} where T : struct\n", csClassPrefix, name); sb.AppendLine(" {"); for (int i = 0; i != sortedFields.Count; i++) { string field = sortedFields[i]; sb.Append(" public T "); sb.Append(field); sb.AppendLine(";"); indices.Add(field, i); } sb.AppendLine(" }"); sb.AppendLine("}"); WriteScriptAsset(string.Format("{0}_{1}", csClassPrefix, name), sb.ToString()); } public void GenerateDataStructureIndices(Dictionary indices) where T : struct { var names = typeof(T).GetFields(); for (int i = 0; i != names.Length; i++) { indices.Add(names[i].Name, i); } } public void GenerateImplementation(List resolveControllers, List resolveBlendShapes, List resolveShaderParam, Dictionary includedControllers, Dictionary indicesControllers, Dictionary indicesBlendShapes, Dictionary indicesShaderParam) { var sb = new StringBuilder(SB_SIZE); sb.AppendLine("using UnityEngine;"); sb.AppendLine("using Unity.DemoTeam.DigitalHuman;"); sb.AppendLine("using Unity.Collections.LowLevel.Unsafe;// for UnsafeUtilityEx.AsRef"); sb.AppendLine(); sb.AppendFormat("namespace {0}\n", csNamespace); sb.AppendLine("{"); sb.AppendFormat(" using SnappersControllers = {0}_SnappersControllers;\n", csClassPrefix); sb.AppendFormat(" using SnappersBlendShapes = {0}_SnappersBlendShapes;\n", csClassPrefix); sb.AppendLine(); sb.AppendFormat(" public class {0}_{1}\n", csClassPrefix, "SnappersHead : SnappersHeadDefinition"); sb.AppendLine(" {"); sb.AppendLine(" public override InstanceData CreateInstanceData(Mesh sourceMesh, Transform sourceRig, Warnings warnings)"); sb.AppendLine(" {"); sb.AppendLine(" return CreateInstanceData(sourceMesh, sourceRig, warnings);"); sb.AppendLine(" }"); sb.AppendLine(); sb.AppendLine(" bool CheckSizes()"); sb.AppendLine(" {"); unsafe { sb.AppendFormat(" const int INDEXED_SIZE_CONTROLLERS = {0};\n", indicesControllers.Count * sizeof(SnappersController)); sb.AppendFormat(" const int INDEXED_SIZE_BLENDSHAPES = {0};\n", indicesBlendShapes.Count * sizeof(float)); sb.AppendFormat(" const int INDEXED_SIZE_SHADERPARAM = {0};\n", indicesShaderParam.Count * sizeof(float)); } sb.AppendLine(); sb.AppendLine(" return"); sb.AppendLine(" INDEXED_SIZE_CONTROLLERS <= UnsafeUtility.SizeOf() &&"); sb.AppendLine(" INDEXED_SIZE_BLENDSHAPES <= UnsafeUtility.SizeOf() &&"); sb.AppendLine(" INDEXED_SIZE_SHADERPARAM <= UnsafeUtility.SizeOf();"); sb.AppendLine(" }"); sb.AppendLine(); GenerateEvaluationFunctionCallsite(sb, " ", "ResolveControllers"); sb.AppendLine(); GenerateEvaluationFunctionCallsite(sb, " ", "ResolveBlendShapes"); sb.AppendLine(); GenerateEvaluationFunctionCallsite(sb, " ", "ResolveShaderParam"); sb.AppendLine(); GenerateInitializeControllerCapsCallsite(sb, " ", "InitializeControllerCaps"); sb.AppendLine(" }"); sb.AppendLine("}"); WriteScriptAsset(string.Format("{0}_{1}", csClassPrefix, "SnappersHead"), sb.ToString()); sb.Clear(); sb.AppendLine("#pragma warning disable 0219"); sb.AppendLine(); sb.AppendFormat("namespace {0}\n", csNamespace); sb.AppendLine("{"); if (csCompacted == false) { sb.AppendFormat(" using SnappersControllers = {0}_SnappersControllers;\n", csClassPrefix); sb.AppendFormat(" using SnappersBlendShapes = {0}_SnappersBlendShapes;\n", csClassPrefix); sb.AppendLine(); } sb.AppendFormat(" public static class {0}_{1}\n", csClassPrefix, "SnappersHeadImpl"); sb.AppendLine(" {"); GenerateEvaluationFunction(sb, " ", "ResolveControllers", resolveControllers, includedControllers, indicesControllers, indicesBlendShapes, indicesShaderParam); sb.AppendLine(); GenerateEvaluationFunction(sb, " ", "ResolveBlendShapes", resolveBlendShapes, includedControllers, indicesControllers, indicesBlendShapes, indicesShaderParam); sb.AppendLine(); GenerateEvaluationFunction(sb, " ", "ResolveShaderParam", resolveShaderParam, includedControllers, indicesControllers, indicesBlendShapes, indicesShaderParam); sb.AppendLine(); GenerateInitializeControllerCaps(sb, " ", "InitializeControllerCaps", includedControllers, indicesControllers); sb.AppendLine(); sb.AppendLine(@" static float clamp(float value, float min, float max) { if (value < min) return min; else if (value > max) return max; else return value; } static float min(float a, float b) { if (a < b) return a; else return b; } static float max(float a, float b) { if (a > b) return a; else return b; } static float hermite(float p0, float p1, float r0, float r1, float t) { float t2 = t * t; float t3 = t2 * t; float _3t2 = 3.0f * t2; float _2t3 = 2.0f * t3; return (p0 * (_2t3 - _3t2 + 1.0f) + p1 * (-_2t3 + _3t2) + r0 * (t3 - 2.0f * t2 + t) + r1 * (t3 - t2)); } static float linstep(float a, float b, float value) { if (a != b) return clamp((value - a) / (b - a), 0.0f, 1.0f); else return 0.0f; } "); sb.AppendLine(" }"); sb.AppendLine("}"); WriteScriptAsset(string.Format("{0}_{1}", csClassPrefix, "SnappersHeadImpl"), sb.ToString()); } public void GenerateEvaluationFunctionCallsite(StringBuilder sb, string tabs, string name) { sb.AppendFormat(" public override unsafe void {0}(void* ptrSnappersControllers, void* ptrSnappersBlendShapes, void* ptrSnappersShaderParam)\n", name); sb.AppendLine(" {"); sb.AppendLine(" if (!CheckSizes())"); sb.AppendLine(" return;"); sb.AppendLine(); sb.AppendFormat(" {0}_SnappersHeadImpl.{1}(\n", csClassPrefix, name); if (csCompacted) { sb.AppendLine(" (float*)ptrSnappersControllers,"); sb.AppendLine(" (float*)ptrSnappersBlendShapes,"); sb.AppendLine(" (float*)ptrSnappersShaderParam"); } else { sb.AppendLine(" ref UnsafeUtilityEx.AsRef(ptrSnappersControllers),"); sb.AppendLine(" ref UnsafeUtilityEx.AsRef(ptrSnappersBlendShapes),"); sb.AppendLine(" ref UnsafeUtilityEx.AsRef(ptrSnappersShaderParam)"); } sb.AppendLine(" );"); sb.AppendLine(" }"); } public void GenerateEvaluationFunction(StringBuilder sb, string tabs, string name, List scripts, Dictionary includedControllers, Dictionary indicesControllers, Dictionary indicesBlendShapes, Dictionary indicesShaderParam) { if (csCompacted) { sb.AppendFormat(" public static unsafe void {0}(float* {1}, float* {2}, float* {3})\n", name, compactedControllers, compactedBlendShapes, compactedShaderParam); } else { sb.AppendFormat(" public static void {0}(ref SnappersControllers {1}, ref SnappersBlendShapes {2}, ref SnappersShaderParam {3})\n", name, namedControllers, namedBlendShapes, namedShaderParam); } sb.AppendLine(" {"); foreach (var script in scripts) { if (script == null) continue; if (csCompacted == false) { sb.AppendFormat(" // this segment generated from '{0}'\n", script.name); } sb.AppendLine(" {"); GenerateEvaluationFunctionSegment(sb, " ", script, includedControllers, indicesControllers, indicesBlendShapes, indicesShaderParam); sb.AppendLine(" }"); } sb.AppendLine(" }"); } public void GenerateEvaluationFunctionSegment(StringBuilder sb, string tabs, TextAsset script, Dictionary includedControllers, Dictionary indicesControllers, Dictionary indicesBlendShapes, Dictionary indicesShaderParam) { var regexDouble = new Regex("(\\d+\\.\\d+)([^f])"); var regexSymbol = new Regex("\\$([_a-zA-Z][_a-zA-Z0-9]*)"); var regexMember = new Regex("([a-zA-Z_]+[\\w]*)\\.([a-zA-Z_]+[\\w]*)"); var symbolCount = 0; var symbolTable = new Dictionary(); var inputPath = AssetDatabase.GetAssetPath(script); var inputStream = new StreamReader(inputPath); while (inputStream.EndOfStream == false) { var line = inputStream.ReadLine(); if (line.Length == 0) continue; // remove comments if (csCompacted) { var commentIndex = line.IndexOf("//"); if (commentIndex != -1) line = line.Substring(0, commentIndex); } // remove blanks line = line.Trim(); if (line.Length == 0) continue; // insert tabs sb.Append(tabs); // replace $ with _ if (csCompacted) { Match match; while ((match = regexSymbol.Match(line)).Success) { var symbol = match.Groups[0].Value; var symbolCompact = null as string; if (!symbolTable.TryGetValue(symbol, out symbolCompact)) { symbolCompact = "_s" + (++symbolCount); symbolTable.Add(symbol, symbolCompact); } line = line.Replace(symbol, symbolCompact); } } else { line = line.Replace('$', '_'); } // replace 0.0 with 0.0f line = regexDouble.Replace(line, "$1f$2"); // gather and replace struct.member var matches = regexMember.Matches(line); { int lineCursor = 0; // note: this needs to be kept in sync with DiscoverDataMembers foreach (Match match in matches) { // add everything until match sb.Append(line, lineCursor, match.Index - lineCursor); var nameStruct = match.Groups[1].Value; var nameMember = match.Groups[2].Value; if (nameStruct == namedBlendShapes) { // e.g. Head_blendShape.nameMember if (csCompacted) { sb.Append(compactedBlendShapes); sb.Append('['); sb.Append(indicesBlendShapes[nameMember]); sb.Append(']'); } } else if (nameStruct == namedShaderParam) { // e.g. SkinShader.nameMember if (csCompacted) { sb.Append(compactedShaderParam); sb.Append('['); sb.Append(indicesShaderParam[nameMember]); sb.Append(']'); } } else { // e.g. nameStruct.nameMember if (csCompacted) { unsafe { sb.Append(compactedControllers); sb.Append('['); sb.Append(indicesControllers[nameStruct] * (sizeof(SnappersController) / sizeof(float)) + IndexControllerField(nameMember)); sb.Append(']'); } } else { sb.Append(namedControllers); sb.Append('.'); } } if (csCompacted) lineCursor = match.Index + match.Length; else lineCursor = match.Index; } sb.Append(line, lineCursor, line.Length - lineCursor); } // add newline sb.Append('\n'); } inputStream.Close(); } public void GenerateInitializeControllerCapsCallsite(StringBuilder sb, string tabs, string name) { sb.AppendFormat(" public override unsafe void {0}(void* ptrSnappersControllers)\n", name); sb.AppendLine(" {"); sb.AppendFormat(" {0}_SnappersHeadImpl.{1}(\n", csClassPrefix, name); if (csCompacted) { sb.AppendLine(" (uint*)ptrSnappersControllers"); } else { sb.AppendLine(" ref UnsafeUtilityEx.AsRef(ptrSnappersControllers)"); } sb.AppendLine(" );"); sb.AppendLine(" }"); } public void GenerateInitializeControllerCaps(StringBuilder sb, string tabs, string name, Dictionary includedControllers, Dictionary indicesControllers) { Func concatCap = (_sb, _capCount, _caps, _cap) => { if ((_caps & _cap) != 0) { if (_capCount++ > 0) { sb.Append(" | "); } _sb.Append("SnappersControllerCaps."); _sb.Append(_cap); } return _capCount; }; if (csCompacted) { sb.AppendFormat(" public static unsafe void {0}(uint* {1})\n", name, compactedControllers); } else { sb.AppendFormat(" public static void {0}(ref SnappersControllers {1})\n", name, namedControllers); } sb.AppendLine(" {"); List sortedFields; sortedFields = new List(includedControllers.Keys); sortedFields.Sort(); foreach (var field in sortedFields) { if (csCompacted) { unsafe { sb.AppendFormat(" {0}[{1}] = {2};\n", compactedControllers, indicesControllers[field] * (sizeof(SnappersController) / sizeof(float)) + 9, (int)includedControllers[field]); } } else { sb.AppendFormat(" {0}.{1}.caps = ", namedControllers, field); var caps = includedControllers[field]; var capCount = 0; capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.translateX); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.translateY); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.translateZ); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.rotateX); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.rotateY); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.rotateZ); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.scaleX); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.scaleY); capCount = concatCap(sb, capCount, caps, SnappersControllerCaps.scaleZ); if (capCount == 0) sb.AppendLine("SnappersControllerCaps.none;"); else sb.AppendLine(";"); } } sb.AppendLine(" }"); } public string GetWritePath() { return Path.GetDirectoryName(AssetDatabase.GetAssetPath(this)).Replace('\\', '/'); } public string WriteScriptAsset(string identifier, string text) { var outputDir = GetWritePath(); var outputPath = string.Format("{0}/{1}.cs", outputDir, identifier); var outputStream = new StreamWriter(outputPath); outputStream.NewLine = "\n"; outputStream.Write(text.Replace("\r\n", "\n")); outputStream.Flush(); outputStream.Close(); Debug.LogFormat("Wrote {0}", outputPath); return outputPath; } static SnappersControllerCaps TranslateControllerField(string field) { switch (field) { case "translateX": return SnappersControllerCaps.translateX; case "translateY": return SnappersControllerCaps.translateY; case "translateZ": return SnappersControllerCaps.translateZ; case "rotateX": return SnappersControllerCaps.rotateX; case "rotateY": return SnappersControllerCaps.rotateY; case "rotateZ": return SnappersControllerCaps.rotateZ; case "scaleX": return SnappersControllerCaps.scaleX; case "scaleY": return SnappersControllerCaps.scaleY; case "scaleZ": return SnappersControllerCaps.scaleZ; default: return SnappersControllerCaps.none; } } static int IndexControllerField(string field) { switch (field) { case "translateX": return 0; case "translateY": return 1; case "translateZ": return 2; case "rotateX": return 3; case "rotateY": return 4; case "rotateZ": return 5; case "scaleX": return 6; case "scaleY": return 7; case "scaleZ": return 8; default: return -1; } } #endif } }