using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object=UnityEngine.Object;
///
/// Studio low-poly candidate for any character (generalized from GreatHornStudioTrial): binds the Studio mesh to the
/// ORIGINAL avatar, controller and finished clips. No animation regeneration. Every entry takes
/// (actor, kind, originalPrefabFile, height); the candidate lives at Characters/{kind}/{actor}Studio.
///
public static class StudioTrial
{
sealed class Ctx
{
public string Actor, Kind, Prefab; public float Height;
public string Original=>"Assets/Bundles/Characters/"+Kind+"/"+Actor;
public string Trial=>"Assets/Bundles/Characters/"+Kind+"/"+Actor+"Studio";
public string Name=>Actor+"Studio";
public string Animated=>Trial+"/"+Name+"_"+(Kind=="Enemy"?"Enemy":"Companion")+".prefab";
public string Scene=>"Assets/Bundles/Scenes/Review/Studio_"+Actor+"_Review.unity";
public string Out=>Path.Combine(Repo,"Tools/output/studio_trial",Actor);
}
static Ctx C(string actor,string kind,string prefab,float height)=>new Ctx{Actor=actor,Kind=kind,Prefab=prefab,Height=height};
static string Repo=>Path.GetFullPath(Path.Combine(Application.dataPath,"../.."));
static void Check(bool value,string message){if(!value)throw new Exception(message);}
static void Write(Ctx c,string name,object data){Directory.CreateDirectory(c.Out);File.WriteAllText(Path.Combine(c.Out,name),JsonConvert.SerializeObject(data,Formatting.Indented));}
static Vector3[] Vertices(GameObject go)
{
return go.GetComponentsInChildren().SelectMany(s=>{
var m=s.sharedMesh;var weights=m.boneWeights;var binds=m.bindposes;var matrices=s.bones.Select((b,i)=>b.localToWorldMatrix*binds[i]).ToArray();
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();
}).ToArray();
}
static Bounds Bounds(Vector3[] points){var b=new Bounds(points[0],Vector3.zero);foreach(var p in points)b.Encapsulate(p);return b;}
static AnimationClip Clip(Ctx c,string name)=>AssetDatabase.LoadAssetAtPath(c.Original+"/Clips/"+name+".anim");
static Animator Reference(Ctx c)=>AssetDatabase.LoadAssetAtPath(c.Original+"/"+c.Prefab).GetComponentInChildren();
public static string Build(string actor,string kind,string prefab,float height)=>BuildCore(C(actor,kind,prefab,height),true);
public static string Prepare(string actor,string kind,string prefab,float height)=>BuildCore(C(actor,kind,prefab,height),false);
static string BuildCore(Ctx c,bool verify)
{
Check(!EditorApplication.isPlaying,"Author trial in Edit Mode");
string fbx=c.Trial+"/Rigged/"+c.Name+"_Rigged.fbx";
AssetDatabase.ImportAsset(fbx,ImportAssetOptions.ForceUpdate);
var imp=(ModelImporter)AssetImporter.GetAtPath(fbx);imp.animationType=ModelImporterAnimationType.Generic;imp.avatarSetup=ModelImporterAvatarSetup.CreateFromThisModel;imp.importAnimation=false;imp.globalScale=1;imp.useFileScale=false;imp.bakeAxisConversion=true;imp.materialImportMode=ModelImporterMaterialImportMode.None;imp.meshCompression=ModelImporterMeshCompression.Off;imp.SaveAndReimport();
var reference=Reference(c);
var scene=EditorSceneManager.NewPreviewScene();
try
{
var root=new GameObject(Path.GetFileNameWithoutExtension(c.Animated));SceneManager.MoveGameObjectToScene(root,scene);
var model=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(fbx),scene);model.transform.SetParent(root.transform,false);model.name="Model";
model.transform.localPosition=reference.transform.localPosition;model.transform.localRotation=reference.transform.localRotation;model.transform.localScale=reference.transform.localScale;
var anim=model.GetComponent();if(anim==null)anim=model.AddComponent();anim.avatar=reference.avatar;anim.runtimeAnimatorController=reference.runtimeAnimatorController;anim.applyRootMotion=false;anim.cullingMode=AnimatorCullingMode.AlwaysAnimate;
var material=AssetDatabase.LoadAssetAtPath(c.Trial+"/"+c.Name+".mat");foreach(var skin in model.GetComponentsInChildren()){skin.sharedMaterial=material;skin.updateWhenOffscreen=true;}
var expected=reference.GetComponentsInChildren().Where(t=>t.name.StartsWith("bone_")).ToDictionary(t=>AnimationUtility.CalculateTransformPath(t,reference.transform));
var actual=model.GetComponentsInChildren().Where(t=>t.name.StartsWith("bone_")).ToDictionary(t=>AnimationUtility.CalculateTransformPath(t,model.transform));
Check(expected.Count>0&&expected.Keys.OrderBy(x=>x).SequenceEqual(actual.Keys.OrderBy(x=>x)),"Original skeleton paths changed ("+expected.Count+" vs "+actual.Count+")");
foreach(var entry in expected){var t=actual[entry.Key];Check(Vector3.Distance(t.localPosition,entry.Value.localPosition)<.0001f&&Quaternion.Angle(t.localRotation,entry.Value.localRotation)<.01f&&Vector3.Distance(t.localScale,entry.Value.localScale)<.0001f,"Bind transform changed "+entry.Key);}
PrefabUtility.SaveAsPrefabAsset(root,c.Animated);
}
finally{EditorSceneManager.ClosePreviewScene(scene);}
AssetDatabase.SaveAssets();return verify?Verify(c):"Prepared original skeleton/controller candidate; sole constraints pending";
}
public static string Verify(string actor,string kind,string prefab,float height)=>Verify(C(actor,kind,prefab,height));
static string Verify(Ctx c)
{
var scene=EditorSceneManager.NewPreviewScene();
try
{
var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(c.Animated),scene);var anim=go.GetComponentInChildren();anim.enabled=false;
var reference=Reference(c);Check(anim.runtimeAnimatorController==reference.runtimeAnimatorController&&anim.avatar==reference.avatar,"Expected exact original controller/avatar");
int boneCount=reference.GetComponentsInChildren().Count(t=>t.name.StartsWith("bone_"));
var bind=Vertices(go);var bounds=Bounds(bind);int triangles=go.GetComponentsInChildren().Sum(s=>s.sharedMesh.triangles.Length/3);int target=(int?)Newtonsoft.Json.Linq.JObject.Parse(File.ReadAllText(Path.Combine(c.Out,"trial.json")))["targetTriangles"]??2000;Check(triangles<=target*1.3f,"Studio target tolerance exceeded");Check(Mathf.Abs(bounds.size.y-c.Height)<.003f&&Mathf.Abs(bounds.min.y)<.003f,"Bind ground/height changed "+bounds);
var staticGo=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(c.Trial+"/"+c.Name+".prefab"),scene);var staticPoints=staticGo.GetComponentsInChildren().SelectMany(m=>m.sharedMesh.vertices.Select(v=>m.transform.TransformPoint(v))).ToArray();float bindError=bind.Max(v=>staticPoints.Min(p=>Vector3.Distance(v,p)));Check(bindError<.002f,"Weight transfer changed static geometry "+bindError);
Check(go.GetComponentsInChildren().All(s=>s.sharedMesh.boneWeights.All(w=>Mathf.Abs(w.weight0+w.weight1+w.weight2+w.weight3-1)<.001f)),"Invalid weights");
var rows=new List