#if UNITY_EDITOR using System; using System.IO; using System.Linq; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; /// Bake an upright, grounded, periodic in-place gait onto this exact bind. public static class FinalizeAnimalWalk { const string Root = "Assets/Bundles/Characters/Animal/RockBall"; sealed class Pose { public Vector3[] p, s; public Quaternion[] q; } static Pose Read(Transform[] bones) => new Pose { p=bones.Select(t=>t.localPosition).ToArray(), s=bones.Select(t=>t.localScale).ToArray(), q=bones.Select(t=>t.localRotation).ToArray() }; static void Write(Transform[] bones, Pose pose) { for(int i=0;ib.localToWorldMatrix*bind[i]).ToArray(); var weights=m.boneWeights; return m.vertices.Select((v,i)=> { var w=weights[i]; return matrices[w.boneIndex0].MultiplyPoint3x4(v)*w.weight0 +matrices[w.boneIndex1].MultiplyPoint3x4(v)*w.weight1 +matrices[w.boneIndex2].MultiplyPoint3x4(v)*w.weight2 +matrices[w.boneIndex3].MultiplyPoint3x4(v)*w.weight3; }).ToArray(); } static float Bottom(GameObject model) => model.GetComponentsInChildren().SelectMany(Vertices).Min(v=>v.y); static float Difference(Pose a,Pose b) { float sum=0; for(int i=0;i RunFor(Root,"RockBall",.55f,"bone_0","bone_8","bone_10",strideMeters,liftMeters,cycleSeconds,"Tools/output/rockball_walk_audit"); public static string RunFor(string assetRoot,string actor,float height,string rootBone,string rightFoot,string leftFoot, float strideMeters,float liftMeters,float cycleSeconds,string reportDirectory,bool calibrateBind=false,float sourceWeight=1f) { var asset=AssetDatabase.LoadAssetAtPath(assetRoot+"/Rigged/"+actor+"_Rigged.fbx"); var model=Object.Instantiate(asset); try { var animator=model.GetComponent(); if(animator!=null) animator.enabled=false; var bones=model.GetComponentsInChildren().Where(t=>t!=model.transform && (t.name=="Armature" || t.name.StartsWith("bone_"))).ToArray(); var root=bones.First(t=>t.name==rootBone); var feet=new[] { bones.First(t=>t.name==rightFoot),bones.First(t=>t.name==leftFoot) }; var bindPose=Read(bones); var bindRotation=root.rotation; var bindPosition=root.position; float bindBottom=Bottom(model); float bindHeight=model.GetComponentsInChildren().SelectMany(Vertices).Max(v=>v.y)-bindBottom; float metersToBind=bindHeight/height; var src=AssetDatabase.LoadAllAssetsAtPath(assetRoot+"/Anims/"+actor+"_Walk.fbx").OfType().First(c=>!c.name.StartsWith("__preview__")); int count=Mathf.RoundToInt(src.length*30)+1; var poses=new Pose[count]; Quaternion reference=Quaternion.identity; Pose referencePose=null; float referenceTilt=0; for(int f=0;f15) throw new InvalidOperationException($"No usable repeated gait phase (RMS={error:F2}). Generate a better walk."); int frames=last-first+1; var loop=new Pose[frames]; for(int f=0;f(assetRoot+"/Clips/Walk.anim"); Undo.RecordObject(clip,"Bake verified in-place Walk"); clip.ClearCurves(); clip.frameRate=30; for(int b=0;bnew Keyframe(cycleSeconds*f/(frames-1), c<3 ? loop[f].p[b][c] : c<7 ? loop[f].q[b][c-3] : loop[f].s[b][c-7])).ToArray(); for(int k=0;k