#if UNITY_EDITOR using System; using System.IO; using System.Linq; using Newtonsoft.Json; using UnityEditor; using UnityEngine; using UnityEngine.SceneManagement; using UnityEditor.SceneManagement; using UnityEditor.Animations; using UnityEngine.Animations; using UnityEngine.Playables; using System.Collections.Generic; public static class DiagnoseMixamoPlayback { static readonly string[] Paths={"Assets/Bundles/Characters/Human/test/Boy_mixamo/CampGuideStudio.fbx","Assets/Bundles/Characters/Human/Common/Walking.fbx","Assets/Bundles/Characters/Human/Common/CampGuideStudio@Walking.fbx"}; static float[] V(Vector3 v)=>new[]{v.x,v.y,v.z}; static string PathOf(UnityEngine.Object o)=>o?AssetDatabase.GetAssetPath(o):null; const string DiagnosticRoot="Assets/Review/CharacterAnimationLab/PlaybackDiagnostics"; static string OutputDirectory=>System.IO.Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/campguide_studio/playback_audit"); static void Folder(string path){if(AssetDatabase.IsValidFolder(path))return;var parent=System.IO.Path.GetDirectoryName(path).Replace('\\','/');Folder(parent);AssetDatabase.CreateFolder(parent,System.IO.Path.GetFileName(path));} public static string PrepareVariants() { Folder(DiagnosticRoot); var avatar=AssetDatabase.LoadAllAssetsAtPath(Paths[0]).OfType().Single(); foreach(var tag in new[]{"CopyBoyAvatar","Generic"}) { string path=DiagnosticRoot+"/Walking_"+tag+".fbx"; if(!AssetDatabase.LoadMainAssetAtPath(path) && !AssetDatabase.CopyAsset(Paths[1],path))throw new Exception("Copy failed "+path); var importer=(ModelImporter)AssetImporter.GetAtPath(path); importer.animationType=tag=="Generic"?ModelImporterAnimationType.Generic:ModelImporterAnimationType.Human; importer.avatarSetup=tag=="Generic"?ModelImporterAvatarSetup.NoAvatar:ModelImporterAvatarSetup.CopyFromOther; importer.sourceAvatar=tag=="Generic"?null:avatar; if(tag=="Generic")importer.animationCompression=ModelImporterAnimationCompression.Off; importer.SaveAndReimport(); } return "Prepared isolated generic and CopyBoyAvatar variants"; } public static string FixWalking() { var importer=(ModelImporter)AssetImporter.GetAtPath(Paths[1]); var before=new{path=Paths[1],animationType=importer.animationType.ToString(),avatarSetup=importer.avatarSetup.ToString(),sourceAvatar=PathOf(importer.sourceAvatar)}; Directory.CreateDirectory(OutputDirectory); var backup=System.IO.Path.Combine(OutputDirectory,"walking_importer_before.json"); if(!File.Exists(backup))File.WriteAllText(backup,JsonConvert.SerializeObject(before,Formatting.Indented)); importer.animationType=ModelImporterAnimationType.Human; importer.avatarSetup=ModelImporterAvatarSetup.CopyFromOther; importer.sourceAvatar=AssetDatabase.LoadAllAssetsAtPath(Paths[0]).OfType().Single(); importer.SaveAndReimport(); var clip=AssetDatabase.LoadAllAssetsAtPath(Paths[1]).OfType().First(c=>!c.name.StartsWith("__preview__")); if(!clip.humanMotion || !importer.sourceAvatar.isValid)throw new Exception("Invalid repaired Humanoid clip/avatar"); var report=new{path=Paths[1],avatarSetup=importer.avatarSetup.ToString(),sourceAvatar=PathOf(importer.sourceAvatar),clip=clip.name,clip.humanMotion, scenes=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray()}; string json=JsonConvert.SerializeObject(report,Formatting.Indented);File.WriteAllText(System.IO.Path.Combine(OutputDirectory,"fix.json"),json);return json; } public static string VerifyFix() { var importer=(ModelImporter)AssetImporter.GetAtPath(Paths[1]); var avatar=AssetDatabase.LoadAllAssetsAtPath(Paths[0]).OfType().Single(); if(importer.avatarSetup!=ModelImporterAvatarSetup.CopyFromOther || importer.sourceAvatar!=avatar)throw new Exception("Wrong source avatar"); 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(PathOf(state.motion)!=Paths[1] || !(state.motion as AnimationClip).humanMotion)throw new Exception("Controller walk clip lost"); var animators=UnityEngine.Object.FindObjectsOfType(true).Where(a=>a.runtimeAnimatorController==controller).ToArray(); var warnings=animators.Select(a=>new SerializedObject(a).FindProperty("m_WarningMessage")?.stringValue).Where(w=>!string.IsNullOrEmpty(w)).ToArray(); if(warnings.Length>0)throw new Exception(string.Join("\n",warnings)); var json=JsonConvert.SerializeObject(new{sourceAvatar=PathOf(avatar),controller=PathOf(controller),walk=PathOf(state.motion),footIK=state.iKOnFeet, actorCount=animators.Length,bindingWarnings=warnings.Length,EditorApplication.isPlaying, dirtyScenes=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray()},Formatting.Indented); File.WriteAllText(System.IO.Path.Combine(OutputDirectory,"validation.json"),json);return json; } class Sampled { public string tag;public GameObject unit;public Animator animator;public PlayableGraph graph;public AnimationClipPlayable playable; public Transform[] bones;public List> frames=new List>(); } public static string Compare() { var scene=EditorSceneManager.NewPreviewScene();var entries=new List(); var source=AssetDatabase.LoadAssetAtPath(Paths[0]); var material=new Material(Shader.Find("Universal Render Pipeline/Lit"));material.color=new Color(.6f,.65f,.72f); try { string[] tags={"Generic","CurrentHumanoid","CopyBoyAvatar"}; string[] paths={DiagnosticRoot+"/Walking_Generic.fbx",Paths[1],DiagnosticRoot+"/Walking_CopyBoyAvatar.fbx"}; for(int i=0;i();animator.runtimeAnimatorController=null;animator.applyRootMotion=false;animator.cullingMode=AnimatorCullingMode.AlwaysAnimate; if(tags[i]=="Generic")animator.avatar=null; var clip=AssetDatabase.LoadAllAssetsAtPath(paths[i]).OfType().First(c=>!c.name.StartsWith("__preview__")); var graph=PlayableGraph.Create("MixamoDiagnostic"+tags[i]);graph.SetTimeUpdateMode(DirectorUpdateMode.Manual); var playable=AnimationClipPlayable.Create(graph,clip);playable.SetApplyFootIK(false);playable.SetApplyPlayableIK(false); var output=AnimationPlayableOutput.Create(graph,"Animation",animator);output.SetSourcePlayable(playable);graph.Play(); foreach(var r in unit.GetComponentsInChildren())r.sharedMaterial=material; entries.Add(new Sampled{tag=tags[i],unit=unit,animator=animator,graph=graph,playable=playable,bones=unit.GetComponentsInChildren().Where(t=>t.name.StartsWith("mixamorig:")).ToArray()}); } for(int frame=0;frame<32;frame++)foreach(var e in entries) { e.playable.SetTime(frame/30.0);e.graph.Evaluate(0); e.frames.Add(e.bones.ToDictionary(b=>b.name,b=>V(e.unit.transform.InverseTransformPoint(b.position)))); } var reference=entries[0];var results=new List(); foreach(var e in entries.Skip(1)) { var errors=new Dictionary(); for(int frame=0;frame<32;frame++)foreach(string bone in e.frames[frame].Keys) { Vector3 P(float[] p)=>new Vector3(p[0],p[1],p[2]); var actual=P(e.frames[frame][bone])-P(e.frames[frame]["mixamorig:Hips"]); var expected=P(reference.frames[frame][bone])-P(reference.frames[frame]["mixamorig:Hips"]); float error=Vector3.Distance(actual,expected);if(!errors.ContainsKey(bone)||errors[bone]p.Value).ToArray()}); } foreach(var e in entries){e.playable.SetTime(10/30.0);e.graph.Evaluate(0);var hips=e.bones.First(b=>b.name=="mixamorig:Hips");var p=hips.position;p.x=e.unit.transform.position.x;p.z=0;hips.position=p;} var light=new GameObject("DiagnosticLight").AddComponent();SceneManager.MoveGameObjectToScene(light.gameObject,scene);light.type=LightType.Directional;light.intensity=1.4f;light.transform.rotation=Quaternion.Euler(40,140,0); var camera=new GameObject("DiagnosticCamera").AddComponent();SceneManager.MoveGameObjectToScene(camera.gameObject,scene);camera.scene=scene;camera.orthographic=true;camera.orthographicSize=.78f; camera.transform.position=new Vector3(0,.62f,5);camera.transform.LookAt(new Vector3(0,.62f,0));camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.2f,.23f,.27f);camera.nearClipPlane=.01f;camera.farClipPlane=20; var rt=new RenderTexture(1440,640,24);var tex=new Texture2D(1440,640,TextureFormat.RGB24,false);var previous=RenderTexture.active; Directory.CreateDirectory(OutputDirectory); try{camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,1440,640),0,0);tex.Apply();File.WriteAllBytes(System.IO.Path.Combine(OutputDirectory,"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=tags,comparison=results,samples=entries.Select(e=>new{e.tag,e.frames})},Formatting.Indented);File.WriteAllText(System.IO.Path.Combine(OutputDirectory,"comparison.json"),json); return JsonConvert.SerializeObject(results); } finally{foreach(var e in entries)if(e.graph.IsValid())e.graph.Destroy();EditorSceneManager.ClosePreviewScene(scene);UnityEngine.Object.DestroyImmediate(material);} } public static string Inspect() { var result=new{EditorApplication.isPlaying,scenes=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray(), assets=Paths.Select(path=>{ var importer=(ModelImporter)AssetImporter.GetAtPath(path);var hd=importer.humanDescription;var model=AssetDatabase.LoadAssetAtPath(path); return new{path,importer.animationType,importer.avatarSetup,sourceAvatar=PathOf(importer.sourceAvatar),importer.globalScale,importer.useFileScale,importer.bakeAxisConversion, human=new{hd.armStretch,hd.legStretch,hd.upperArmTwist,hd.lowerArmTwist,hd.upperLegTwist,hd.lowerLegTwist,hd.feetSpacing,hd.hasTranslationDoF, mapping=hd.human.Select(b=>new{b.humanName,b.boneName}).ToArray(),skeleton=hd.skeleton.Select(b=>new{b.name,position=V(b.position),rotation=V(b.rotation.eulerAngles),scale=V(b.scale)}).ToArray()}, bones=model.GetComponentsInChildren(true).Where(t=>t.name.StartsWith("mixamorig:")).Select(t=>new{t.name,parent=t.parent?t.parent.name:null,world=V(model.transform.InverseTransformPoint(t.position)),local=V(t.localPosition),rotation=V(t.localEulerAngles)}).ToArray(), clips=importer.clipAnimations.Select(c=>new{c.name,c.firstFrame,c.lastFrame,c.loopTime,c.loopPose,c.lockRootRotation,c.keepOriginalOrientation,c.rotationOffset,c.lockRootPositionXZ,c.keepOriginalPositionXZ,c.lockRootHeightY,c.heightFromFeet}).ToArray(), animatorClips=AssetDatabase.LoadAllAssetsAtPath(path).OfType().Select(c=>new{c.name,c.humanMotion,c.length,settings=AnimationUtility.GetAnimationClipSettings(c)}).ToArray()}; }).ToArray()}; string json=JsonConvert.SerializeObject(result,Formatting.Indented); string dir=System.IO.Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/campguide_studio/playback_audit");Directory.CreateDirectory(dir);File.WriteAllText(System.IO.Path.Combine(dir,"importers.json"),json);return "Saved "+dir+"/importers.json"; } } #endif