#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; /// Isolated static or sampled-pose views; preserves all open scene contents. 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(prefabPath); if(prefab==null)throw new Exception("Missing visual prefab: "+prefabPath); var unit=(GameObject)PrefabUtility.InstantiatePrefab(prefab,scene); var animator=unit.GetComponentInChildren();if(animator!=null)animator.enabled=false; if(animator==null) { var filters=unit.GetComponentsInChildren(); 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().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(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();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();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().SelectMany(m=>m.sharedMesh.vertices.Select(v=>m.transform.TransformPoint(v))).ToList(); foreach(var skin in unit.GetComponentsInChildren()) { 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;iMathf.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