Ship the WeChat hot-update catalogs and the camp, TideWhale T2, UI atlas, and animation-lab work.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using AIGame.Review;
|
||||
|
||||
public static class VerifyLmGirlGait
|
||||
{
|
||||
const string Root="Assets/Review/CharacterAnimationLab/LmGirl/Generated";
|
||||
static string Output=>Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/lmgirl_tpose_studio/rig_mixamo41");
|
||||
static Vector3 V(JToken t)=>new Vector3((float)t[0],(float)t[1],(float)t[2]);
|
||||
static void Check(bool valid,string message){if(!valid)throw new InvalidOperationException(message);}
|
||||
public static string Verify()
|
||||
{
|
||||
var expected=JObject.Parse(File.ReadAllText(Path.Combine(Output,"gait_correction.json")));
|
||||
var data=JObject.Parse(File.ReadAllText(Path.Combine(Output,"unity_review_data.json")));
|
||||
var website=JObject.Parse(File.ReadAllText(Path.Combine(Output,"website/unity_review_data.json")));
|
||||
var root=PrefabUtility.LoadPrefabContents(Root+"/LmGirlAnimationLab.prefab");
|
||||
var baked=new Mesh();var previousQuality=QualitySettings.skinWeights;
|
||||
try
|
||||
{
|
||||
QualitySettings.skinWeights=SkinWeights.Unlimited;
|
||||
var v=root.GetComponent<CharacterAnimationLab>();v.playing=false;v.inPlace=false;v.motion=0;v.focus=-1;
|
||||
Check(v.actors.All(a=>a.walkingClip&&Mathf.Abs(a.walkingClip.length-v.Duration)<.00001f),"Missing mapped walk or wrong timing.");
|
||||
Check(v.actors.Skip(1).All(a=>a.walkingClip==v.actors[1].walkingClip),"All TokenRig variants must share one corrected motion.");
|
||||
foreach(var a in v.actors)a.rig.transform.localScale=Vector3.one;
|
||||
var bones=v.actors.Select(a=>a.renderer.bones.ToDictionary(b=>b.name.Split(':').Last())).ToArray();
|
||||
float maxPositionError=0,maxLengthDrift=0;var minGaps=Enumerable.Repeat(float.MaxValue,4).ToArray();
|
||||
var lengths=new float[4,4];
|
||||
for(int ai=0;ai<4;ai++)
|
||||
{
|
||||
var bind=ai==0?website:data;
|
||||
var rest=bind["bones"].ToDictionary(b=>(string)b["name"],b=>Matrix(b["bindpose"]).inverse.GetColumn(3));
|
||||
int segment=0;
|
||||
foreach(string side in new[]{"Left","Right"})foreach(var pair in new[]{new[]{"UpLeg","Leg"},new[]{"Leg","Foot"}})
|
||||
lengths[ai,segment++]=Vector3.Distance(rest["mixamorig:"+side+pair[0]],rest["mixamorig:"+side+pair[1]]);
|
||||
}
|
||||
for(int frame=0;frame<32;frame++)
|
||||
{
|
||||
v.time=frame/30f;v.Sample();
|
||||
for(int ai=0;ai<4;ai++)
|
||||
{
|
||||
var a=v.actors[ai];var track=expected["reports"][ai==0?"mixamo":"tokenrig"]["tracks"][frame];
|
||||
foreach(var p in (JObject)track["positions"])
|
||||
{
|
||||
var actual=a.rig.transform.InverseTransformPoint(bones[ai][p.Key].position);
|
||||
maxPositionError=Mathf.Max(maxPositionError,Vector3.Distance(actual,V(p.Value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Check(maxPositionError<.00005f,"Native mapped pose disagrees with independent IK reference: "+maxPositionError);
|
||||
// 120Hz scan covers interpolation between original 30Hz keyframes.
|
||||
for(int frame=0;frame<=124;frame++)
|
||||
{
|
||||
v.time=frame/120f;v.Sample();
|
||||
for(int ai=0;ai<4;ai++)
|
||||
{
|
||||
var b=bones[ai];var lateral=(b["LeftUpLeg"].position-b["RightUpLeg"].position).normalized;
|
||||
float gap=Vector3.Dot(b["LeftFoot"].position-b["RightFoot"].position,lateral);minGaps[ai]=Mathf.Min(minGaps[ai],gap);
|
||||
int segment=0;
|
||||
foreach(string side in new[]{"Left","Right"})foreach(var pair in new[]{new[]{"UpLeg","Leg"},new[]{"Leg","Foot"}})
|
||||
maxLengthDrift=Mathf.Max(maxLengthDrift,Mathf.Abs(Vector3.Distance(b[side+pair[0]].position,b[side+pair[1]].position)-lengths[ai,segment++]));
|
||||
}
|
||||
}
|
||||
Check(minGaps.All(g=>g>.08f),"Feet cross or become too close: "+string.Join(",",minGaps));
|
||||
Check(maxLengthDrift<.0001f,"Leg segment lengths changed: "+maxLengthDrift);
|
||||
var deformation=new List<object>();
|
||||
foreach(var variant in data["variants"].Concat(website["variants"]))
|
||||
{
|
||||
string name=(string)variant["name"];int ai=name=="mixamo"?0:1;var actor=v.actors[ai];
|
||||
actor.renderer.sharedMesh=AssetDatabase.LoadAssetAtPath<Mesh>(Root+"/"+name+".asset");
|
||||
float[] maxima=new float[3];int[] bits={1,2,4};
|
||||
for(int frame=0;frame<32;frame++)
|
||||
{
|
||||
v.time=frame/30f;v.Sample();actor.renderer.BakeMesh(baked);var vertices=baked.vertices;
|
||||
foreach(var edge in variant["auditEdges"])
|
||||
{
|
||||
float ratio=Vector3.Distance(vertices[(int)edge[0]],vertices[(int)edge[1]])/(float)edge[2];
|
||||
for(int i=0;i<3;i++)if(((int)edge[3]&bits[i])!=0)maxima[i]=Mathf.Max(maxima[i],ratio);
|
||||
}
|
||||
}
|
||||
string[] regions={"all","hands","neck"};float error=0;
|
||||
for(int i=0;i<3;i++)error=Mathf.Max(error,Mathf.Abs(maxima[i]-(float)expected["deformation"][name]["maxStretch"][regions[i]]));
|
||||
Check(error<.003f,"Corrected skin mismatch "+name+": "+error);
|
||||
deformation.Add(new{variant=name,maxStretch=maxima[0],hands=maxima[1],neck=maxima[2],maxDifference=error});
|
||||
}
|
||||
var result=new{passed=true,originalFrames=32,interpolationHz=120,interpolationSamples=125*4,
|
||||
minimumFootSignedGaps=minGaps,maxPositionError,maxLengthDrift,deformationSamples=192,deformation};
|
||||
File.WriteAllText(Path.Combine(Output,"unity_gait_verification.json"),JsonConvert.SerializeObject(result,Formatting.Indented));
|
||||
return JsonConvert.SerializeObject(result);
|
||||
}
|
||||
finally{QualitySettings.skinWeights=previousQuality;UnityEngine.Object.DestroyImmediate(baked);PrefabUtility.UnloadPrefabContents(root);}
|
||||
}
|
||||
static Matrix4x4 Matrix(JToken rows)
|
||||
{
|
||||
var m=new Matrix4x4();for(int r=0;r<4;r++)for(int c=0;c<4;c++)m[r,c]=(float)rows[r][c];return m;
|
||||
}
|
||||
public static string Pose(bool corrected,int frame,int focus)
|
||||
{
|
||||
var v=UnityEngine.Object.FindObjectOfType<CharacterAnimationLab>();
|
||||
for(int i=0;i<v.actors.Length;i++)v.actors[i].walkingClip=corrected?
|
||||
AssetDatabase.LoadAssetAtPath<AnimationClip>(Root+"/Walking_LmGirl"+(i==0?"Mixamo33":"41")+".anim"):null;
|
||||
v.motion=0;v.time=frame/30f;v.playing=false;v.inPlace=true;v.focus=focus;v.detail=0;v.yaw=0;v.textured=false;v.Sample();
|
||||
return corrected?"Corrected foot-path mapping":"Previous angle-only mapping (temporary Play Mode preview)";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user