120 lines
8.3 KiB
C#
120 lines
8.3 KiB
C#
#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<CharacterAnimationLab>();
|
|
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<CharacterAnimationLab>();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<AnimationClip>(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<object>();
|
|
foreach(var variant in data["variants"])
|
|
{
|
|
string name=(string)variant["name"];
|
|
var mesh=AssetDatabase.LoadAssetAtPath<Mesh>(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<Button>(true).ToDictionary(b=>b.name);
|
|
void Click(string name)=>buttons["Btn_"+name].onClick.Invoke();
|
|
Check(buttons.Count==22,"22 serialized buttons required.");
|
|
v.motion=0;v.time=0;v.playing=true;v.inPlace=true;v.Sample();Click("Play");Check(!v.playing,"Pause");
|
|
Click("Next");Check(Mathf.Abs(v.time-1f/30)<.00001f,"Next frame");Click("Previous");Check(v.time==0,"Previous frame");
|
|
Click("Breathe");Check(v.motion==1,"Breathing");Click("Happy");Check(v.motion==2,"Happy");Click("Walk");
|
|
foreach(var speed in new[]{"Speed025","Speed05","Speed1","Speed2"})Click(speed);
|
|
Check(v.speed==2,"Speed");Click("Speed1");
|
|
var seek=v.ui.GetComponentInChildren<AnimationLabSeekBar>();var corners=new Vector3[4];((RectTransform)seek.transform).GetWorldCorners(corners);
|
|
seek.OnPointerDown(new PointerEventData(EventSystem.current){position=RectTransformUtility.WorldToScreenPoint(null,Vector3.Lerp(corners[0],corners[2],.5f))});
|
|
Check(Mathf.Abs(v.time-v.Duration*.5f)<.001f&&!v.playing,"Pointer seek");
|
|
v.Seek(1);var stationary=v.actors[0].hips.localPosition;Click("Travel");
|
|
Check(Vector3.Distance(v.actors[0].hips.localPosition-stationary,v.travel[0]*v.actors[0].translationScale)<.00001f,"Proportional travel");Click("Travel");
|
|
Click("Focus4");Click("Detail");Click("Detail");Check(v.focus==3&&v.detail==2&&v.actors[3].rightHand,"Fourth actor hand close-up");
|
|
Check(v.actors.Count(a=>a.renderer.enabled)==1,"Focused view must isolate the selected actor.");
|
|
Click("Material");Check(v.actors.All(a=>a.renderer.sharedMaterial==v.clayMaterial),"Clay");Click("Material");
|
|
Click("Side");Click("Back");Click("Front");Click("All");
|
|
Check(v.actors.All(a=>a.renderer.enabled),"All-view must restore four actors.");
|
|
var first=v.actors[1].renderer.bones;
|
|
foreach(var actor in v.actors.Skip(2))for(int i=0;i<41;i++)
|
|
{
|
|
Check(Vector3.Distance(first[i].localPosition,actor.renderer.bones[i].localPosition)<.00001f,"Pose translations differ.");
|
|
Check(Mathf.Abs(Quaternion.Dot(first[i].localRotation,actor.renderer.bones[i].localRotation))>.99999f,"Pose rotations differ.");
|
|
}
|
|
var result=new{passed=true,buttons=22,checks=new[]{"pause","frame step","three clips","four speeds","pointer seek","scaled in-place travel","fourth actor focus/detail","materials","views","TokenRig 41-bone pose synchronization"}};
|
|
File.WriteAllText(Path.Combine(Output,"unity_controls.json"),JsonConvert.SerializeObject(result,Formatting.Indented));
|
|
v.time=0;v.playing=true;v.Sample();return JsonConvert.SerializeObject(result);
|
|
}
|
|
public static string Pose(int frame,int focus,int detail,int yaw,bool textured)
|
|
{
|
|
var v=Viewer();v.motion=0;v.time=frame/30f;v.playing=false;v.inPlace=true;v.focus=focus;v.detail=detail;v.yaw=yaw;v.textured=textured;v.Sample();
|
|
return JsonConvert.SerializeObject(new{v.time,v.focus,v.detail,v.yaw,v.textured});
|
|
}
|
|
}
|
|
#endif
|