#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 { [Serializable] public sealed class QuadrupedRecipe { public string assetRoot,id,rootBone,frontRightFoot,hindLeftFoot,frontLeftFoot,hindRightFoot,outputDir; public float height,walkStride,walkLift,walkSeconds,walkSourceWeight,walkSwingFraction; } public static string RunQuadruped(string recipePath) { var r=JsonUtility.FromJson(File.ReadAllText(Path.GetFullPath(Path.Combine(Application.dataPath,"../..",recipePath)))); if(!(r.walkSwingFraction>0 && r.walkSwingFraction<=.25f))throw new ArgumentException("Four-beat swing must be at most a quarter cycle"); return RunCore(r.assetRoot,r.id,r.height,r.rootBone,new[]{r.frontRightFoot,r.hindLeftFoot,r.frontLeftFoot,r.hindRightFoot},r.walkStride,r.walkLift,r.walkSeconds,r.outputDir,true,r.walkSourceWeight,true,r.walkSwingFraction); } 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) =>RunCore(assetRoot,actor,height,rootBone,new[]{rightFoot,leftFoot},strideMeters,liftMeters,cycleSeconds,reportDirectory,calibrateBind,sourceWeight,false,.2f); static string RunCore(string assetRoot,string actor,float height,string rootBone,string[] footNames, float strideMeters,float liftMeters,float cycleSeconds,string reportDirectory,bool calibrateBind,float sourceWeight,bool fourBeat,float swingFraction) { 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=footNames.Select(name=>bones.First(t=>t.name==name)).ToArray(); if(feet.Distinct().Count()!=feet.Length)throw new ArgumentException("Foot mappings must be distinct"); var footPositions=feet.Select(f=>f.position).ToArray();var footRotations=feet.Select(f=>f.rotation).ToArray(); 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