Files
AIGame/AIClient/AgentScripts/BuildCharacterAnimationLab.cs

194 lines
14 KiB
C#

#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<string,Motion> 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<Material>(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<Mesh>(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<byte>(item.weights.Select(x => (byte)x.Length).ToArray(), Allocator.Temp);
var flattened = new NativeArray<BoneWeight1>(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<AnimationClip>(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<Data>(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<Material>(texturedPath);
if (!textured)
{
textured = new Material(AssetDatabase.LoadAssetAtPath<Material>("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<CharacterAnimationLab>(); 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<SkinnedMeshRenderer>(); 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<Renderer>().sharedMaterial = Material("Pedestal" + i, colors[i]); UnityEngine.Object.DestroyImmediate(pedestal.GetComponent<Collider>());
}
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<Renderer>().sharedMaterial = Material("Floor", new Color(.83f,.88f,.92f)); UnityEngine.Object.DestroyImmediate(floor.GetComponent<Collider>());
var background = new GameObject("BackgroundCamera").AddComponent<Camera>(); 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>(); 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<AudioListener>(); 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>(); 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<GameObject>(uiPrefab),root.transform); viewer.ui=ui.transform;
ui.GetComponentInChildren<AnimationLabSeekBar>().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<CharacterAnimationLab>();
if (!viewer || viewer.gameObject.scene.path != ScenePath) throw new InvalidOperationException("Open the comparison scene first.");
for (int i=0;i<viewer.actors.Length;i++)
{
var actor=viewer.actors[i];
actor.rig.transform.parent.localPosition=new Vector3((i-1)*.99f,0,0);
actor.rightHand=actor.renderer.bones.First(b=>b.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<CharacterAnimationLab>().gameObject;
return ScenePath;
}
}
#endif