#if UNITY_EDITOR using System; using System.IO; using System.Linq; using System.Collections.Generic; using Newtonsoft.Json; using UnityEditor; using UnityEditor.SceneManagement; using UnityEditor.Animations; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.Animations; using UnityEngine.Playables; public static class DiagnoseSharedHumanoidWalk { const string Boy="Assets/Bundles/Characters/Human/test/Boy_mixamo/CampGuideStudio.fbx"; const string Girl="Assets/Bundles/Characters/Human/test/girl/LmGirlTpose.fbx"; const string Walk="Assets/Bundles/Characters/Human/Common/Walking.fbx"; static string Output=>Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/lmgirl_tpose_studio/shared_walk_audit"); static float[] V(Vector3 v)=>new[]{v.x,v.y,v.z}; static string P(UnityEngine.Object o)=>o?AssetDatabase.GetAssetPath(o):null; static readonly HumanBodyBones[] BoneIds={HumanBodyBones.Hips,HumanBodyBones.LeftUpperLeg,HumanBodyBones.RightUpperLeg,HumanBodyBones.LeftLowerLeg,HumanBodyBones.RightLowerLeg,HumanBodyBones.LeftFoot,HumanBodyBones.RightFoot,HumanBodyBones.LeftToes,HumanBodyBones.RightToes}; static object[] Pose(Animator a)=>BoneIds.Select(id=>{var t=a.GetBoneTransform(id);return (object)new{bone=id.ToString(),name=t?t.name:null,position=t?V(a.transform.InverseTransformPoint(t.position)):null,rotation=t?V(t.localEulerAngles):null};}).ToArray(); public static string Inspect() { var scene=EditorSceneManager.NewPreviewScene(); try { var assets=new List(); foreach(var path in new[]{Boy,Girl,Walk}) { var importer=(ModelImporter)AssetImporter.GetAtPath(path);var hd=importer.humanDescription; var avatar=AssetDatabase.LoadAllAssetsAtPath(path).OfType().FirstOrDefault(); var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene); var animator=go.GetComponent();if(animator)animator.runtimeAnimatorController=null; var ahd=avatar?avatar.humanDescription:new HumanDescription(); assets.Add(new{path,importer.animationType,importer.avatarSetup,sourceAvatar=P(importer.sourceAvatar),avatar=avatar?avatar.name:null,valid=avatar&&avatar.isValid, importerHuman=hd.human.Select(b=>new{b.humanName,b.boneName}).ToArray(),importerSkeleton=hd.skeleton.Select(b=>new{b.name,p=V(b.position),r=V(b.rotation.eulerAngles),s=V(b.scale)}).ToArray(), avatarHuman=ahd.human==null?null:ahd.human.Select(b=>new{b.humanName,b.boneName}).ToArray(),avatarSkeleton=ahd.skeleton==null?null:ahd.skeleton.Select(b=>new{b.name,p=V(b.position),r=V(b.rotation.eulerAngles),s=V(b.scale)}).ToArray(), pose=animator&&animator.isHuman?Pose(animator):null}); UnityEngine.Object.DestroyImmediate(go); } Directory.CreateDirectory(Output);var json=JsonConvert.SerializeObject(assets,Formatting.Indented);File.WriteAllText(Path.Combine(Output,"avatars.json"),json);return "Saved "+Output+"/avatars.json"; } finally{EditorSceneManager.ClosePreviewScene(scene);} } public static string Compare() { var scene=EditorSceneManager.NewPreviewScene();var results=new List(); try { foreach(var path in new[]{Boy,Girl})foreach(bool footIK in new[]{false,true}) { var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene); var a=go.GetComponent();a.runtimeAnimatorController=null;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate; var clip=AssetDatabase.LoadAllAssetsAtPath(Walk).OfType().First(c=>!c.name.StartsWith("__preview__")); var graph=PlayableGraph.Create("SharedWalkAudit");graph.SetTimeUpdateMode(DirectorUpdateMode.Manual); try { var playable=AnimationClipPlayable.Create(graph,clip);playable.SetApplyFootIK(footIK);playable.SetApplyPlayableIK(false); var output=AnimationPlayableOutput.Create(graph,"Animation",a);output.SetSourcePlayable(playable);graph.Play(); var frames=new List();float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0; for(int i=0;i<125;i++) { playable.SetTime(i/120.0);graph.Evaluate(0); var left=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.LeftFoot).position);var right=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.RightFoot).position); float gap=right.x-left.x;minGap=Mathf.Min(minGap,gap);maxGap=Mathf.Max(maxGap,gap);if(gap<0)crossed++; frames.Add(new{t=i/120.0,gap,pose=Pose(a)}); } results.Add(new{path,footIK,minGap,maxGap,crossed,frames}); } finally{graph.Destroy();UnityEngine.Object.DestroyImmediate(go);} } Directory.CreateDirectory(Output);var json=JsonConvert.SerializeObject(results,Formatting.Indented);File.WriteAllText(Path.Combine(Output,"baseline.json"),json); return "Saved "+Output+"/baseline.json"; } finally{EditorSceneManager.ClosePreviewScene(scene);} } public static string VerifyController() { const string controllerPath="Assets/Bundles/Characters/Human/Common/Man.controller"; var controller=AssetDatabase.LoadAssetAtPath(controllerPath); var scene=EditorSceneManager.NewPreviewScene();var results=new List(); var units=new List();var controllers=new List(); var material=new Material(Shader.Find("Universal Render Pipeline/Lit"));material.color=new Color(.65f,.7f,.76f); try { int column=0; foreach(var path in new[]{Boy,Girl})foreach(bool footIK in new[]{false,true}) { // A transient controller contains only the actual walk state and clip. var c=new AnimatorController();controllers.Add(c);c.AddLayer("Base Layer"); var state=c.layers[0].stateMachine.AddState("walk");state.motion=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state.motion;state.iKOnFeet=footIK;c.layers[0].stateMachine.defaultState=state; var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene);units.Add(go); go.transform.position=new Vector3((1.5f-column++)*.85f,0,0); var a=go.GetComponent();a.runtimeAnimatorController=c;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate; foreach(var r in go.GetComponentsInChildren())r.sharedMaterial=material; a.Rebind();a.Update(0); float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0,minFrame=0; for(int i=0;i<125;i++) { a.Play("Base Layer.walk",0,(i/120f)/state.motion.averageDuration);a.Update(0); var left=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.LeftFoot).position);var right=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.RightFoot).position); float gap=right.x-left.x;if(gap();SceneManager.MoveGameObjectToScene(light.gameObject,scene);light.type=LightType.Directional;light.intensity=1.5f;light.transform.rotation=Quaternion.Euler(35,135,0); var camera=new GameObject("AuditCamera").AddComponent();SceneManager.MoveGameObjectToScene(camera.gameObject,scene);camera.scene=scene;camera.orthographic=true;camera.orthographicSize=.8f; camera.transform.position=new Vector3(0,.65f,5);camera.transform.LookAt(new Vector3(0,.65f,0));camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.19f,.22f,.26f);camera.nearClipPlane=.01f;camera.farClipPlane=20; var rt=new RenderTexture(1600,720,24);var tex=new Texture2D(1600,720,TextureFormat.RGB24,false);var previous=RenderTexture.active; Directory.CreateDirectory(Output); try{camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,1600,720),0,0);tex.Apply();File.WriteAllBytes(Path.Combine(Output,"foot_ik_comparison.png"),tex.EncodeToPNG());} finally{RenderTexture.active=previous;camera.targetTexture=null;rt.Release();UnityEngine.Object.DestroyImmediate(rt);UnityEngine.Object.DestroyImmediate(tex);} var json=JsonConvert.SerializeObject(new{order=new[]{"Boy FootIK off","Boy FootIK on","Girl FootIK off","Girl FootIK on"},controllerPath,controllerDirty=EditorUtility.IsDirty(controller),stateDirty=EditorUtility.IsDirty(controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state),results},Formatting.Indented); File.WriteAllText(Path.Combine(Output,"controller_verification.json"),json);return json; } finally { foreach(var go in units)UnityEngine.Object.DestroyImmediate(go); foreach(var c in controllers){foreach(var l in c.layers){foreach(var s in l.stateMachine.states)UnityEngine.Object.DestroyImmediate(s.state);UnityEngine.Object.DestroyImmediate(l.stateMachine);}UnityEngine.Object.DestroyImmediate(c);} EditorSceneManager.ClosePreviewScene(scene);UnityEngine.Object.DestroyImmediate(material); } } public static string FixFootIK() { const string controllerPath="Assets/Bundles/Characters/Human/Common/Man.controller"; var controller=AssetDatabase.LoadAssetAtPath(controllerPath); var state=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state; if(EditorUtility.IsDirty(controller)||EditorUtility.IsDirty(state))throw new Exception("Controller has unsaved edits; refusing to save unrelated edits."); var scenesBefore=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray(); bool previous=state.iKOnFeet; state.iKOnFeet=true;EditorUtility.SetDirty(state);AssetDatabase.SaveAssetIfDirty(state); var importer=(ModelImporter)AssetImporter.GetAtPath(Walk); if(!state.iKOnFeet || P(state.motion)!=Walk || P(importer.sourceAvatar)!=Boy)throw new Exception("Unexpected controller/source setup"); var json=JsonConvert.SerializeObject(new{controllerPath,state=state.name,previousFootIK=previous,footIK=state.iKOnFeet,walk=P(state.motion),sourceAvatar=P(importer.sourceAvatar), controllerDirty=EditorUtility.IsDirty(controller),stateDirty=EditorUtility.IsDirty(state), actors=UnityEngine.Object.FindObjectsOfType(true).Where(a=>a.runtimeAnimatorController==controller).Select(a=>new{a.name,avatar=P(a.avatar),a.applyRootMotion}).ToArray(), scenesBefore,scenesAfter=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray()},Formatting.Indented); File.WriteAllText(Path.Combine(Output,"fix.json"),json);return json; } public static string VerifyApplied() { var controller=AssetDatabase.LoadAssetAtPath("Assets/Bundles/Characters/Human/Common/Man.controller"); var state=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state; if(!state.iKOnFeet)throw new Exception("Foot IK disabled"); var scene=EditorSceneManager.NewPreviewScene();var results=new List(); try { foreach(var path in new[]{Boy,Girl}) { var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene); try { var a=go.GetComponent();a.runtimeAnimatorController=controller;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate;a.Rebind();a.Update(0); float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0; for(int i=0;i<125;i++) { a.Play("Base Layer.walk",0,(i/120f)/state.motion.averageDuration);a.Update(0); var left=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.LeftFoot).position);var right=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.RightFoot).position); float gap=right.x-left.x;minGap=Mathf.Min(minGap,gap);maxGap=Mathf.Max(maxGap,gap);if(gap<0)crossed++; } if(crossed>0)throw new Exception("Crossing after fix: "+path); results.Add(new{path,avatar=P(a.avatar),samples=125,minGap,maxGap,crossed}); } finally{UnityEngine.Object.DestroyImmediate(go);} } var json=JsonConvert.SerializeObject(new{controller=P(controller),state=state.name,footIK=state.iKOnFeet,clip=P(state.motion),results},Formatting.Indented); File.WriteAllText(Path.Combine(Output,"validation.json"),json);return json; } finally{EditorSceneManager.ClosePreviewScene(scene);} } } #endif