#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