#if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using System.Linq; using AIGame.EditorTools; using AIGame.Review; using Newtonsoft.Json; using Unity.Collections; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; public static class BuildCharacterAnimationLab { public const string Root = "Assets/Review/CharacterAnimationLab"; public const string ScenePath = Root + "/CharacterAnimationLab.unity"; const string Generated = Root + "/Generated"; static string Repo => Directory.GetParent(Application.dataPath).Parent.FullName; static string DataPath => Path.Combine(Repo, "Tools/output/campguide_studio/rig_mixamo41/unity_review/review_data.json"); public class Bone { public string name; public int parent; public float[] rest; public float[][] bindpose; } public class Variant { public string name; public float[][] vertices, normals, uv, auditEdges; public int[] triangles; public float[][][] weights; public int originalVertices, trianglesCount; } public class Motion { public string name; public int fps; public float[][][] samples; } public class Gait { public Dictionary motions; } public class Data { public Bone[] bones; public Variant[] variants; public Motion[] clips; public float translationScale,height; public float[][] translationOffsets,translationDeltaRotations,bindRotationCorrections; public int[] rotationParents; } static Vector3 V(float[] x, int offset = 0) => new Vector3(x[offset], x[offset + 1], x[offset + 2]); static Quaternion Q(float[] x) => new Quaternion(x[3], x[4], x[5], x[6]); static void SetTRS(Transform t, float[] x) { t.localPosition = V(x); t.localRotation = Q(x); t.localScale = V(x, 7); } static Matrix4x4 Matrix(float[][] rows) { var m = new Matrix4x4(); for (int r = 0; r < 4; r++) for (int c = 0; c < 4; c++) m[r, c] = rows[r][c]; return m; } static void Folder(string path) { if (AssetDatabase.IsValidFolder(path)) return; var parent = path.Substring(0, path.LastIndexOf('/')); Folder(parent); AssetDatabase.CreateFolder(parent, path.Substring(path.LastIndexOf('/') + 1)); } static Material Material(string name, Color color, string destination = Generated) { var path = destination + "/" + name + ".mat"; var material = AssetDatabase.LoadAssetAtPath(path); if (!material) { material = new Material(Shader.Find("Universal Render Pipeline/Lit")); AssetDatabase.CreateAsset(material, path); } material.SetColor("_BaseColor", color); material.SetFloat("_Smoothness", .22f); material.SetFloat("_Metallic", 0); EditorUtility.SetDirty(material); return material; } static Mesh MakeMesh(Variant item, Bone[] bones, string destination = Generated) { string path = destination + "/" + item.name + ".asset"; var mesh = AssetDatabase.LoadAssetAtPath(path); if (!mesh) { mesh = new Mesh { name = item.name }; AssetDatabase.CreateAsset(mesh, path); } mesh.Clear(); mesh.indexFormat = IndexFormat.UInt32; mesh.vertices = item.vertices.Select(x => V(x)).ToArray(); mesh.normals = item.normals.Select(x => V(x)).ToArray(); mesh.uv = item.uv.Select(x => new Vector2(x[0], x[1])).ToArray(); mesh.triangles = item.triangles; mesh.bindposes = bones.Select(b => Matrix(b.bindpose)).ToArray(); var counts = new NativeArray(item.weights.Select(x => (byte)x.Length).ToArray(), Allocator.Temp); var flattened = new NativeArray(item.weights.SelectMany(x => x).Select(x => new BoneWeight1 { boneIndex = (int)x[0], weight = x[1] }).ToArray(), Allocator.Temp); try { mesh.SetBoneWeights(counts, flattened); } finally { counts.Dispose(); flattened.Dispose(); } mesh.RecalculateBounds(); mesh.RecalculateTangents(); EditorUtility.SetDirty(mesh); return mesh; } static AnimationClip MakeClip(Motion motion, Bone[] bones, string destination = Generated) { string path = destination + "/" + motion.name + ".anim"; var clip = AssetDatabase.LoadAssetAtPath(path); if (!clip) { clip = new AnimationClip(); AssetDatabase.CreateAsset(clip, path); } clip.ClearCurves(); clip.name = motion.name; clip.frameRate = motion.fps; clip.legacy = false; string[] paths = new string[bones.Length]; string[] properties = { "m_LocalPosition.x", "m_LocalPosition.y", "m_LocalPosition.z", "m_LocalRotation.x", "m_LocalRotation.y", "m_LocalRotation.z", "m_LocalRotation.w", "m_LocalScale.x", "m_LocalScale.y", "m_LocalScale.z" }; for (int b = 0; b < bones.Length; b++) { paths[b] = bones[b].parent < 0 ? bones[b].name : paths[bones[b].parent] + "/" + bones[b].name; for (int channel = 0; channel < properties.Length; channel++) { var keys = new Keyframe[motion.samples.Length]; for (int f = 0; f < keys.Length; f++) keys[f] = new Keyframe(f / (float)motion.fps, motion.samples[f][b][channel]); var curve = new AnimationCurve(keys); for (int f = 0; f < keys.Length; f++) { AnimationUtility.SetKeyLeftTangentMode(curve, f, AnimationUtility.TangentMode.Linear); AnimationUtility.SetKeyRightTangentMode(curve, f, AnimationUtility.TangentMode.Linear); } AnimationUtility.SetEditorCurve(clip, EditorCurveBinding.FloatCurve(paths[b], typeof(Transform), properties[channel]), curve); } } clip.EnsureQuaternionContinuity(); var settings = AnimationUtility.GetAnimationClipSettings(clip); settings.loopTime = true; AnimationUtility.SetAnimationClipSettings(clip, settings); EditorUtility.SetDirty(clip); return clip; } public static string Build() { if (EditorApplication.isPlaying) throw new InvalidOperationException("Build in Edit Mode."); var data = JsonConvert.DeserializeObject(File.ReadAllText(DataPath)); if (data.bones.Length != 41 || data.variants.Length != 3) throw new InvalidDataException("Expected 3 variants and 41 bones."); Folder(Generated); var meshes = data.variants.Select(v => MakeMesh(v, data.bones)).ToArray(); var clips = data.clips.Select(c => MakeClip(c, data.bones)).ToArray(); var clay = Material("Clay", new Color(.68f, .73f, .79f)); string texturedPath = Generated + "/CharacterTextured.mat"; var textured = AssetDatabase.LoadAssetAtPath(texturedPath); if (!textured) { textured = new Material(AssetDatabase.LoadAssetAtPath("Assets/Bundles/Characters/Human/CampGuideStudio/CampGuideStudio.mat")); textured.name = "CharacterTextured"; AssetDatabase.CreateAsset(textured, texturedPath); } string uiPrefab = JsonUIPrefabBuilder.BuildFromJsonFile(Path.Combine(Repo, "Doc/UIPrefabCreater/UI_CharacterAnimationLab.json")); if (string.IsNullOrEmpty(uiPrefab)) throw new InvalidOperationException("Comparison UI JSON failed."); var previous = SceneManager.GetActiveScene(); var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive); try { SceneManager.SetActiveScene(scene); var root = new GameObject("CharacterAnimationLab"); var viewer = root.AddComponent(); viewer.clips = clips; viewer.texturedMaterial = textured; viewer.clayMaterial = clay; viewer.travel = data.clips.Select(c => V(c.samples[c.samples.Length - 1][0]) - V(c.samples[0][0])).ToArray(); viewer.actors = new CharacterAnimationLab.Actor[3]; string[] labels = { "Mixamo 网站", "TokenRig 原始", "手部约束后" }; Color[] colors = { new Color(.49f,.78f,.85f), new Color(.94f,.72f,.43f), new Color(.62f,.82f,.51f) }; for (int i = 0; i < 3; i++) { var slot = new GameObject("Stage_" + data.variants[i].name); slot.transform.SetParent(root.transform, false); slot.transform.localPosition = new Vector3((i - 1) * .99f, 0, 0); var rig = new GameObject("Rig"); rig.transform.SetParent(slot.transform, false); rig.transform.localScale = Vector3.one * (1.55f / 1.1348386f); var bones = new Transform[data.bones.Length]; for (int b = 0; b < bones.Length; b++) { var go = new GameObject(data.bones[b].name); bones[b] = go.transform; go.transform.SetParent(data.bones[b].parent < 0 ? rig.transform : bones[data.bones[b].parent], false); SetTRS(go.transform, data.bones[b].rest); } var meshGo = new GameObject("CharacterMesh"); meshGo.transform.SetParent(rig.transform, false); var skin = meshGo.AddComponent(); skin.sharedMesh = meshes[i]; skin.bones = bones; skin.rootBone = bones[0]; skin.sharedMaterial = textured; skin.quality = SkinQuality.Auto; skin.updateWhenOffscreen = true; skin.localBounds = new Bounds(new Vector3(0,.75f,0), new Vector3(3,3,3)); viewer.actors[i] = new CharacterAnimationLab.Actor { label = labels[i], rig = rig, hips = bones[0], rightHand = bones.First(b=>b.name=="mixamorig:RightHand"), renderer = skin }; var pedestal = GameObject.CreatePrimitive(PrimitiveType.Cylinder); pedestal.name = "Pedestal"; pedestal.transform.SetParent(slot.transform, false); pedestal.transform.localPosition = new Vector3(0,-.02f,0); pedestal.transform.localScale = new Vector3(.98f,.01f,.98f); pedestal.GetComponent().sharedMaterial = Material("Pedestal" + i, colors[i]); UnityEngine.Object.DestroyImmediate(pedestal.GetComponent()); } var floor = GameObject.CreatePrimitive(PrimitiveType.Cube); floor.name = "ReviewFloor"; floor.transform.SetParent(root.transform, false); floor.transform.localPosition = new Vector3(0,-.09f,.15f); floor.transform.localScale = new Vector3(5.4f,.1f,3.2f); floor.GetComponent().sharedMaterial = Material("Floor", new Color(.83f,.88f,.92f)); UnityEngine.Object.DestroyImmediate(floor.GetComponent()); var background = new GameObject("BackgroundCamera").AddComponent(); background.transform.SetParent(root.transform,false); background.depth=-10; background.cullingMask=0; background.clearFlags=CameraClearFlags.SolidColor; background.backgroundColor=new Color(.75f,.83f,.89f); var camera = new GameObject("ReviewCamera").AddComponent(); camera.transform.SetParent(root.transform,false); camera.tag="MainCamera"; camera.orthographic=true; camera.rect=new Rect(0,.34f,1,.535f); camera.clearFlags=CameraClearFlags.SolidColor; camera.backgroundColor=background.backgroundColor; camera.allowHDR=false; camera.nearClipPlane=.05f; camera.farClipPlane=30; camera.gameObject.AddComponent(); viewer.reviewCamera=camera; foreach (var setup in new[] { ("KeyLight",new Vector3(45,-35,0),1.1f), ("FillLight",new Vector3(35,145,0),.45f) }) { var light=new GameObject(setup.Item1).AddComponent(); light.transform.SetParent(root.transform,false); light.type=LightType.Directional; light.transform.rotation=Quaternion.Euler(setup.Item2); light.intensity=setup.Item3; light.shadows=setup.Item1=="KeyLight"?LightShadows.Soft:LightShadows.None; } RenderSettings.ambientMode=AmbientMode.Flat; RenderSettings.ambientLight=new Color(.65f,.69f,.73f); RenderSettings.fog=false; var ui=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(uiPrefab),root.transform); viewer.ui=ui.transform; ui.GetComponentInChildren().viewer=viewer; var events = new GameObject("EventSystem", typeof(EventSystem),typeof(StandaloneInputModule)); events.transform.SetParent(root.transform,false); viewer.motion=0; viewer.time=10f/30; viewer.inPlace=true; viewer.Sample(); EditorSceneManager.MarkSceneDirty(scene); EditorSceneManager.SaveScene(scene,ScenePath); PrefabUtility.SaveAsPrefabAsset(root,Generated+"/CharacterAnimationLab.prefab"); AssetDatabase.SaveAssets(); return JsonConvert.SerializeObject(new { scene=ScenePath, variants=3,bones=41,triangles=3000,clips=clips.Select(c=>new {c.name,c.length}), ui=uiPrefab }); } finally { if (previous.IsValid() && previous.isLoaded) SceneManager.SetActiveScene(previous); EditorSceneManager.CloseScene(scene,true); } } public static string RefineLayout() { if (EditorApplication.isPlaying) throw new InvalidOperationException("Edit Mode required."); var viewer = UnityEngine.Object.FindObjectOfType(); if (!viewer || viewer.gameObject.scene.path != ScenePath) throw new InvalidOperationException("Open the comparison scene first."); for (int i=0;ib.name=="mixamorig:RightHand"); } viewer.Sample(); EditorUtility.SetDirty(viewer); EditorSceneManager.MarkSceneDirty(viewer.gameObject.scene); EditorSceneManager.SaveScene(viewer.gameObject.scene); PrefabUtility.SaveAsPrefabAsset(viewer.gameObject,Generated+"/CharacterAnimationLab.prefab"); AssetDatabase.SaveAssets(); return "Saved adaptive comparison framing, stage spacing and hand focus references."; } public static string Open() { if (EditorSceneManager.GetSceneManagerSetup().Any(s=>SceneManager.GetSceneByPath(s.path).isDirty)) throw new InvalidOperationException("Existing scene has unsaved changes; open comparison additively instead."); EditorSceneManager.OpenScene(ScenePath,OpenSceneMode.Single); Selection.activeGameObject=UnityEngine.Object.FindObjectOfType().gameObject; return ScenePath; } } #endif