Files
AIGame/AIClient/AgentScripts/ImportLmGirlTokenRigFbx.cs
T

124 lines
9.7 KiB
C#

#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Security.Cryptography;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.Animations;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class ImportLmGirlTokenRigFbx
{
const string FolderPath="Assets/Bundles/Characters/Human/test/girl_tokenrig";
const string ModelPath=FolderPath+"/LmGirlTokenRig.fbx";
const string MaterialPath=FolderPath+"/LmGirlTokenRig.mat";
static string Repo=>Directory.GetParent(Application.dataPath).Parent.FullName;
static string Output=>Path.Combine(Repo,"Tools/output/lmgirl_tpose_studio/rig_mixamo41/fbx");
static float[] V(Vector3 v)=>new[]{v.x,v.y,v.z};
static void Folder(string p){if(AssetDatabase.IsValidFolder(p))return;Folder(Path.GetDirectoryName(p).Replace('\\','/'));AssetDatabase.CreateFolder(Path.GetDirectoryName(p).Replace('\\','/'),Path.GetFileName(p));}
static string Hash(string p){using(var sha=SHA256.Create())return BitConverter.ToString(sha.ComputeHash(File.ReadAllBytes(p))).Replace("-","").ToLowerInvariant();}
static void CopyNew(string source,string target)
{
var full=Path.Combine(Directory.GetParent(Application.dataPath).FullName,target);
if(File.Exists(full)){if(Hash(full)!=Hash(source))throw new Exception("Existing destination differs: "+target);}
else File.Copy(source,full);
AssetDatabase.ImportAsset(target,ImportAssetOptions.ForceSynchronousImport);
}
public static string Import()
{
if(EditorApplication.isPlaying)throw new Exception("Edit Mode required");
Folder(FolderPath);
foreach(var suffix in new[]{"base","mix"})
{
string path=FolderPath+"/LmGirlTokenRig_"+suffix+".png";
CopyNew(Path.Combine(Repo,"Tools/output/lmgirl_tpose_studio/xpbr/LmGirlTpose_"+suffix+".png"),path);
var ti=(TextureImporter)AssetImporter.GetAtPath(path);ti.sRGBTexture=suffix=="base";ti.alphaIsTransparency=false;ti.maxTextureSize=1024;ti.SaveAndReimport();
}
var material=AssetDatabase.LoadAssetAtPath<Material>(MaterialPath);
if(!material)
{
var reference=AssetDatabase.LoadAssetAtPath<Material>("Assets/Review/CharacterAnimationLab/LmGirl/Generated/LmGirlTextured.mat");
if(!reference)throw new Exception("Missing approved XPbr reference material");
material=new Material(reference);material.name="LmGirlTokenRig";AssetDatabase.CreateAsset(material,MaterialPath);
}
material.SetTexture("_MainTex",AssetDatabase.LoadAssetAtPath<Texture2D>(FolderPath+"/LmGirlTokenRig_base.png"));
material.SetTexture("_MixTex",AssetDatabase.LoadAssetAtPath<Texture2D>(FolderPath+"/LmGirlTokenRig_mix.png"));
EditorUtility.SetDirty(material);AssetDatabase.SaveAssetIfDirty(material);
CopyNew(Path.Combine(Output,"LmGirlTokenRig.fbx"),ModelPath);
var importer=(ModelImporter)AssetImporter.GetAtPath(ModelPath);
importer.animationType=ModelImporterAnimationType.Human;importer.avatarSetup=ModelImporterAvatarSetup.CreateFromThisModel;
importer.importAnimation=false;importer.globalScale=1;importer.useFileScale=true;
importer.importCameras=false;importer.importLights=false;
importer.meshCompression=ModelImporterMeshCompression.Off;
importer.skinWeights=ModelImporterSkinWeights.Custom;importer.maxBonesPerVertex=4;importer.minBoneWeight=0;
importer.materialImportMode=ModelImporterMaterialImportMode.ImportStandard;
importer.AddRemap(new AssetImporter.SourceAssetIdentifier(typeof(Material),"LmGirlTokenRig_Body"),material);
importer.SaveAndReimport();
var model=AssetDatabase.LoadAssetAtPath<GameObject>(ModelPath);
var avatar=AssetDatabase.LoadAllAssetsAtPath(ModelPath).OfType<Avatar>().FirstOrDefault();
if(!avatar || !avatar.isValid || !avatar.isHuman)throw new Exception("Invalid Humanoid Avatar");
if(!model.GetComponentsInChildren<SkinnedMeshRenderer>().All(s=>s.sharedMaterial==material))throw new Exception("Material remap failed");
return "Imported "+ModelPath+" with valid Humanoid Avatar and project XPbr material";
}
public static string Verify()
{
var scene=EditorSceneManager.NewPreviewScene();var units=new List<GameObject>();
var controller=AssetDatabase.LoadAssetAtPath<AnimatorController>("Assets/Bundles/Characters/Human/Common/Man.controller");
var walk=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state;
var records=new List<object>();
try
{
int column=0;
foreach(var path in new[]{"Assets/Bundles/Characters/Human/test/girl/LmGirlTpose.fbx",ModelPath})
{
var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(path),scene);units.Add(go);
go.transform.position=new Vector3((.5f-column++)*.95f,0,0);
var a=go.GetComponent<Animator>();var skin=go.GetComponentInChildren<SkinnedMeshRenderer>();
if(!a||!a.isHuman||!a.avatar.isValid)throw new Exception("Invalid Animator: "+path);
var baked=new Mesh();skin.BakeMesh(baked);
var vertices=baked.vertices.Select(p=>go.transform.InverseTransformPoint(skin.transform.TransformPoint(p))).ToArray();
var lo=vertices.Aggregate(Vector3.positiveInfinity,Vector3.Min);var hi=vertices.Aggregate(Vector3.negativeInfinity,Vector3.Max);
var toes=new[]{HumanBodyBones.LeftToes,HumanBodyBones.RightToes}.Select(b=>go.transform.InverseTransformPoint(a.GetBoneTransform(b).position)).ToArray();
var ankles=new[]{HumanBodyBones.LeftFoot,HumanBodyBones.RightFoot}.Select(b=>go.transform.InverseTransformPoint(a.GetBoneTransform(b).position)).ToArray();
if(hi.y-lo.y<1 || hi.y-lo.y>1.4f || Mathf.Abs(lo.y)>.001f)throw new Exception("Wrong height/floor: "+path);
if(toes[0].z<=ankles[0].z||toes[1].z<=ankles[1].z)throw new Exception("Not facing +Z: "+path);
var counts=skin.sharedMesh.GetBonesPerVertex();int maxInfluences=0;foreach(var n in counts)maxInfluences=Math.Max(maxInfluences,n);counts.Dispose();
var rest=new{boundsMin=V(lo),boundsMax=V(hi),rootRotation=V(go.transform.localEulerAngles),rootScale=V(go.transform.localScale),toes=toes.Select(V).ToArray(),ankles=ankles.Select(V).ToArray()};
UnityEngine.Object.DestroyImmediate(baked);
a.runtimeAnimatorController=controller;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate;a.Rebind();a.Update(0);
float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0;
for(int i=0;i<125;i++)
{
a.Play("Base Layer.walk",0,(i/120f)/walk.motion.averageDuration);a.Update(0);
var left=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.LeftFoot).position);var right=go.transform.InverseTransformPoint(a.GetBoneTransform(HumanBodyBones.RightFoot).position);
float gap=right.x-left.x;minGap=Mathf.Min(minGap,gap);maxGap=Mathf.Max(maxGap,gap);if(gap<0)crossed++;
}
if(crossed>0)throw new Exception("Crossed ankles: "+path);
records.Add(new{path,avatar=a.avatar.name,a.avatar.isHuman,a.avatar.isValid,bones=skin.bones.Length,vertices=skin.sharedMesh.vertexCount,triangles=skin.sharedMesh.triangles.Length/3,
uv=skin.sharedMesh.uv.Length,maxInfluences,material=AssetDatabase.GetAssetPath(skin.sharedMaterial),shader=skin.sharedMaterial.shader.name,rest,
walk=new{clip=AssetDatabase.GetAssetPath(walk.motion),footIK=walk.iKOnFeet,samples=125,minGap,maxGap,crossed}});
a.Play("Base Layer.walk",0,.2666667f/walk.motion.averageDuration);a.Update(0);
var hips=a.GetBoneTransform(HumanBodyBones.Hips);var hp=hips.position;hp.x=go.transform.position.x;hp.z=0;hips.position=hp;
}
Render(scene);
var json=JsonConvert.SerializeObject(new{records,scenes=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray()},Formatting.Indented);
File.WriteAllText(Path.Combine(Output,"unity_validation.json"),json);return json;
}
finally{EditorSceneManager.ClosePreviewScene(scene);}
}
static void Render(Scene scene)
{
var light=new GameObject("ExportAuditLight").AddComponent<Light>();SceneManager.MoveGameObjectToScene(light.gameObject,scene);light.type=LightType.Directional;light.intensity=1.3f;light.transform.rotation=Quaternion.Euler(35,140,0);
var camera=new GameObject("ExportAuditCamera").AddComponent<Camera>();SceneManager.MoveGameObjectToScene(camera.gameObject,scene);camera.scene=scene;camera.orthographic=true;camera.orthographicSize=.73f;
camera.transform.position=new Vector3(0,.61f,5);camera.transform.LookAt(new Vector3(0,.61f,0));camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.22f,.25f,.29f);camera.nearClipPlane=.01f;camera.farClipPlane=20;
var rt=new RenderTexture(1080,800,24);var tex=new Texture2D(1080,800,TextureFormat.RGB24,false);var previous=RenderTexture.active;
try{camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,1080,800),0,0);tex.Apply();File.WriteAllBytes(Path.Combine(Output,"unity_walking.png"),tex.EncodeToPNG());}
finally{RenderTexture.active=previous;camera.targetTexture=null;rt.Release();UnityEngine.Object.DestroyImmediate(rt);UnityEngine.Object.DestroyImmediate(tex);}
}
}
#endif