112 lines
6.6 KiB
C#
112 lines
6.6 KiB
C#
#if UNITY_EDITOR
|
|
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEditor.SceneManagement;
|
|
using UnityEngine;
|
|
using UnityEngine.SceneManagement;
|
|
using Object=UnityEngine.Object;
|
|
|
|
/// <summary>Isolated static or sampled-pose views; preserves all open scene contents.</summary>
|
|
public static class AuditCharacterVisual
|
|
{
|
|
public static string CaptureCampGuideIdle()
|
|
{
|
|
return Capture(
|
|
"Assets/Bundles/Characters/Human/CampGuide/CampGuide_Rigged.prefab",
|
|
"Tools/output/campguide_review/idle",
|
|
1.55f,
|
|
"Assets/Bundles/Characters/Human/CampGuide/Clips/Idle.anim",
|
|
0.45f, -1f); // Existing CampGuide asset uses legacy -Z.
|
|
}
|
|
|
|
public static string CaptureCampGuideWalk()
|
|
{
|
|
return Capture(
|
|
"Assets/Bundles/Characters/Human/CampGuide/CampGuide_Rigged.prefab",
|
|
"Tools/output/campguide_review/walk",
|
|
1.55f,
|
|
"Assets/Bundles/Characters/Human/CampGuide/Clips/Walk.anim",
|
|
0.3f, -1f);
|
|
}
|
|
|
|
public static string Capture(string prefabPath,string outputDirectory,float height=.55f,string clipPath="",float phase=0,float forwardZ=1f)
|
|
{
|
|
var directions=ViewDirections(forwardZ);
|
|
string dir=Path.GetFullPath(Path.Combine(Application.dataPath,"../..",outputDirectory));Directory.CreateDirectory(dir);
|
|
var scene=EditorSceneManager.NewPreviewScene();
|
|
try
|
|
{
|
|
var prefab=AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
|
|
if(prefab==null)throw new Exception("Missing visual prefab: "+prefabPath);
|
|
var unit=(GameObject)PrefabUtility.InstantiatePrefab(prefab,scene);
|
|
var animator=unit.GetComponentInChildren<Animator>();if(animator!=null)animator.enabled=false;
|
|
if(animator==null)
|
|
{
|
|
var filters=unit.GetComponentsInChildren<MeshFilter>();
|
|
var points=filters.SelectMany(m=>m.sharedMesh.vertices.Select(v=>m.transform.TransformPoint(v))).ToArray();
|
|
if(points.Length==0)throw new Exception("No static geometry");
|
|
float minY=points.Min(v=>v.y),actualHeight=points.Max(v=>v.y)-minY;
|
|
int triangles=filters.Sum(m=>m.sharedMesh.triangles.Length/3);
|
|
if(Mathf.Abs(minY)>.002f || Mathf.Abs(actualHeight-height)>.002f)throw new Exception("Static height/bottom invalid");
|
|
if(unit.GetComponentsInChildren<Renderer>().Any(r=>r.sharedMaterial==null || r.sharedMaterial.shader.name!="Test/XPbr"))throw new Exception("Static XPbr material missing");
|
|
File.WriteAllText(Path.Combine(dir,"static_verification.txt"),$"height={actualHeight:F6} bottom={minY:F6} triangles={triangles} XPbr=True; frontAxis={(forwardZ>0?"+Z":"-Z")}; facing requires visual review of front/side/back views");
|
|
}
|
|
if(!string.IsNullOrEmpty(clipPath))
|
|
{
|
|
var clip=AssetDatabase.LoadAssetAtPath<AnimationClip>(clipPath);
|
|
if(clip==null || animator==null)throw new Exception("Missing clip/animator");
|
|
clip.SampleAnimation(animator.gameObject,clip.length*phase);
|
|
}
|
|
var light=new GameObject("AuditLight").AddComponent<Light>();SceneManager.MoveGameObjectToScene(light.gameObject,scene);
|
|
light.type=LightType.Directional;light.intensity=1.4f;light.transform.rotation=Quaternion.Euler(40,-30,0);
|
|
var camera=new GameObject("AuditCamera").AddComponent<Camera>();SceneManager.MoveGameObjectToScene(camera.gameObject,scene);camera.scene=scene;
|
|
camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.56f,.64f,.7f);
|
|
camera.orthographic=true;camera.orthographicSize=height*.68f;camera.nearClipPlane=.01f;camera.farClipPlane=10;
|
|
var visualPoints=unit.GetComponentsInChildren<MeshFilter>().SelectMany(m=>m.sharedMesh.vertices.Select(v=>m.transform.TransformPoint(v))).ToList();
|
|
foreach(var skin in unit.GetComponentsInChildren<SkinnedMeshRenderer>())
|
|
{
|
|
var mesh=skin.sharedMesh;var weights=mesh.boneWeights;
|
|
var matrices=skin.bones.Select((b,i)=>b.localToWorldMatrix*mesh.bindposes[i]).ToArray();
|
|
visualPoints.AddRange(mesh.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;
|
|
}));
|
|
}
|
|
var bounds=new Bounds(visualPoints[0],Vector3.zero);foreach(var p in visualPoints)bounds.Encapsulate(p);
|
|
var rt=new RenderTexture(640,640,24);var tex=new Texture2D(640,640,TextureFormat.RGB24,false);var previous=RenderTexture.active;
|
|
try
|
|
{
|
|
string[] names={"front","side","back","perspective"};
|
|
for(int i=0;i<directions.Length;i++)
|
|
{
|
|
var center=bounds.center;camera.transform.position=center+directions[i]*Mathf.Max(height,bounds.size.magnitude)*3;
|
|
camera.transform.LookAt(center);camera.targetTexture=rt;
|
|
// Broad quadrupeds must fit horizontally as well as vertically.
|
|
camera.orthographicSize=1.15f*Mathf.Max(visualPoints.Max(p=>Mathf.Abs(Vector3.Dot(p-center,camera.transform.right))),visualPoints.Max(p=>Mathf.Abs(Vector3.Dot(p-center,camera.transform.up))));
|
|
camera.Render();RenderTexture.active=rt;
|
|
tex.ReadPixels(new Rect(0,0,640,640),0,0);tex.Apply();File.WriteAllBytes(Path.Combine(dir,names[i]+".png"),tex.EncodeToPNG());
|
|
}
|
|
}
|
|
finally {RenderTexture.active=previous;camera.targetTexture=null;rt.Release();Object.DestroyImmediate(rt);Object.DestroyImmediate(tex);}
|
|
}
|
|
finally {EditorSceneManager.ClosePreviewScene(scene);}
|
|
return dir;
|
|
}
|
|
|
|
static Vector3[] ViewDirections(float forwardZ = 1f)
|
|
{
|
|
if (forwardZ != 1f && forwardZ != -1f) throw new ArgumentException("forwardZ must be +1 or legacy -1");
|
|
return new[]{new Vector3(0,.22f,forwardZ),new Vector3(1,.22f,0),new Vector3(0,.22f,-forwardZ),new Vector3(.7f,.3f,forwardZ)};
|
|
}
|
|
|
|
public static string VerifyForwardConvention()
|
|
{
|
|
if(ViewDirections()[0].z!=1 || ViewDirections()[2].z!=-1 || ViewDirections(-1)[0].z!=-1)
|
|
throw new Exception("Front/back or legacy view directions are reversed");
|
|
return "PASS +Z default views and explicit legacy -Z views";
|
|
}
|
|
}
|
|
#endif
|