#if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; using System.Linq; using AIGame.Review; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public static class VerifyLmGirlAnimationLab { const string Root="Assets/Review/CharacterAnimationLab"; static string Output=>Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/lmgirl_tpose_studio/rig_mixamo41"); static void Check(bool ok,string message){if(!ok)throw new InvalidOperationException(message);} static CharacterAnimationLab Viewer()=>UnityEngine.Object.FindObjectOfType(); public static string Verify() { var data=JObject.Parse(File.ReadAllText(Path.Combine(Output,"unity_review_data.json"))); var expected=JObject.Parse(File.ReadAllText(Path.Combine(Output,"walking_comparison.json"))); var root=PrefabUtility.LoadPrefabContents(Root+"/LmGirl/Generated/LmGirlAnimationLab.prefab"); var baked=new Mesh();var quality=QualitySettings.skinWeights; try { QualitySettings.skinWeights=SkinWeights.Unlimited; var v=root.GetComponent();v.playing=false;v.inPlace=false;v.motion=0; // Historical bind/weight baseline, deliberately excluding the later // foot-path correction. Active corrected playback has a gait audit. foreach(var actor in v.actors)actor.walkingClip=null; foreach(var actor in v.actors)actor.rig.transform.localScale=Vector3.one; Check(v.clips[0]==AssetDatabase.LoadAssetAtPath(Root+"/Generated/Walking.anim"),"Walking clip must be the same asset."); Check(v.actors.Length==4&&v.actors[0].renderer.bones.Length==33,"Website 33-bone actor required."); Check(v.actors.Skip(1).All(a=>a.renderer.bones.Length==41&&a.translationOffsets.Length==41),"TokenRig 41 bones/offsets required."); var results=new List(); foreach(var variant in data["variants"]) { string name=(string)variant["name"]; var mesh=AssetDatabase.LoadAssetAtPath(Root+"/LmGirl/Generated/"+name+".asset"); Check(mesh.triangles.Length/3==4999,"Topology changed."); v.actors[1].renderer.sharedMesh=mesh; float[] maxima=new float[3];int[] masks={1,2,4}; for(int frame=0;frame<32;frame++) { v.time=frame/30f;v.Sample();v.actors[1].renderer.BakeMesh(baked);var points=baked.vertices; foreach(var edge in variant["auditEdges"]) { float ratio=Vector3.Distance(points[(int)edge[0]],points[(int)edge[1]])/(float)edge[2]; for(int r=0;r<3;r++)if(((int)edge[3]&masks[r])!=0)maxima[r]=Mathf.Max(maxima[r],ratio); } } string[] regions={"all","hands","neck"};float error=0; for(int r=0;r<3;r++)error=Mathf.Max(error,Mathf.Abs(maxima[r]-(float)expected[name]["maxStretch"][regions[r]])); Check(error<.003f,name+" native comparison mismatch: "+error); results.Add(new{variant=name,frames=32,maxStretch=maxima[0],hands=maxima[1],neck=maxima[2],maxDifference=error}); } var website=JObject.Parse(File.ReadAllText(Path.Combine(Output,"website/unity_review_data.json"))); var websiteExpected=JObject.Parse(File.ReadAllText(Path.Combine(Output,"website/walking_comparison.json"))); Check(v.actors[0].renderer.sharedMesh.GetBonesPerVertex().ToArray().Max()==5,"Website influences were truncated."); float[] siteMax=new float[3];int[] siteMasks={1,2,4}; for(int frame=0;frame<32;frame++) { v.time=frame/30f;v.Sample();v.actors[0].renderer.BakeMesh(baked);var points=baked.vertices; foreach(var edge in website["variants"][0]["auditEdges"]) { float ratio=Vector3.Distance(points[(int)edge[0]],points[(int)edge[1]])/(float)edge[2]; for(int r=0;r<3;r++)if(((int)edge[3]&siteMasks[r])!=0)siteMax[r]=Mathf.Max(siteMax[r],ratio); } } string[] siteRegions={"all","hands","neck"};float siteError=0; for(int r=0;r<3;r++)siteError=Mathf.Max(siteError,Mathf.Abs(siteMax[r]-(float)websiteExpected["maxStretch"][siteRegions[r]])); Check(siteError<.003f,"Website rest-axis mapping mismatch: "+siteError); results.Add(new{variant="mixamo",frames=32,maxStretch=siteMax[0],hands=siteMax[1],neck=siteMax[2],maxDifference=siteError}); var report=new{passed=true,samples=192,bones=41,websiteBones=33,websiteMaxInfluences=5,triangles=4999,sameWalkingClip=true,results}; File.WriteAllText(Path.Combine(Output,"unity_verification.json"),JsonConvert.SerializeObject(report,Formatting.Indented)); return JsonConvert.SerializeObject(report); } finally{QualitySettings.skinWeights=quality;UnityEngine.Object.DestroyImmediate(baked);PrefabUtility.UnloadPrefabContents(root);} } public static string Controls() { var v=Viewer();Check(Application.isPlaying,"Play Mode required."); var buttons=v.ui.GetComponentsInChildren