124 lines
9.7 KiB
C#
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
|