diff --git a/.gitignore b/.gitignore index e509ab3..0560425 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ Tools/**/*.pyc .env Tools/*.env Tools/*.env.local +Tools/.cdn-deploy/ +Tools/cdn_publish.local.json # 混元3D 登录凭证与任务中间文件(勿提交) Tools/data/hunyuan/storage_state.json diff --git a/AIClient/AgentScripts/BuildCharacterAnimationLab.cs b/AIClient/AgentScripts/BuildCharacterAnimationLab.cs index 16b61cb..7ab6623 100644 --- a/AIClient/AgentScripts/BuildCharacterAnimationLab.cs +++ b/AIClient/AgentScripts/BuildCharacterAnimationLab.cs @@ -162,120 +162,6 @@ public static class BuildCharacterAnimationLab EditorSceneManager.CloseScene(scene,true); } } - public static string BuildLmGirl() - { - if (EditorApplication.isPlaying) throw new InvalidOperationException("Edit Mode required."); - const string targetRoot=Root+"/LmGirl"; - const string generated=targetRoot+"/Generated"; - if (SceneManager.GetSceneByPath(targetRoot+"/LmGirlAnimationLab.unity").isLoaded) - throw new InvalidOperationException("Open a different scene before rebuilding LmGirl; Unity cannot overwrite another loaded scene."); - Folder(generated); - var data=JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(Repo,"Tools/output/lmgirl_tpose_studio/rig_mixamo41/unity_review_data.json"))); - var meshes=data.variants.ToDictionary(v=>v.name,v=>MakeMesh(v,data.bones,generated)); - var website=JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(Repo,"Tools/output/lmgirl_tpose_studio/rig_mixamo41/website/unity_review_data.json"))); - meshes["mixamo"]=MakeMesh(website.variants[0],website.bones,generated); - var gait=JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(Repo,"Tools/output/lmgirl_tpose_studio/rig_mixamo41/gait_correction.json"))); - var walk41=MakeClip(gait.motions["tokenrig"],data.bones,generated); - var walk33=MakeClip(gait.motions["mixamo"],website.bones,generated); - foreach (string suffix in new[]{"base","mix"}) - { - string path=generated+"/LmGirlTpose_"+suffix+".png"; - File.Copy(Path.Combine(Repo,"Tools/output/lmgirl_tpose_studio/xpbr/LmGirlTpose_"+suffix+".png"),path,true); - AssetDatabase.ImportAsset(path,ImportAssetOptions.ForceSynchronousImport); - var importer=(TextureImporter)AssetImporter.GetAtPath(path); - importer.sRGBTexture=suffix=="base";importer.maxTextureSize=1024; - importer.alphaIsTransparency=false;importer.SaveAndReimport(); - } - string matPath=generated+"/LmGirlTextured.mat"; - var material=AssetDatabase.LoadAssetAtPath(matPath); - if (!material) - { - material=new Material(AssetDatabase.LoadAssetAtPath(Generated+"/CharacterTextured.mat")); - AssetDatabase.CreateAsset(material,matPath); - } - material.SetTexture("_MainTex",AssetDatabase.LoadAssetAtPath(generated+"/LmGirlTpose_base.png")); - material.SetTexture("_MixTex",AssetDatabase.LoadAssetAtPath(generated+"/LmGirlTpose_mix.png")); - EditorUtility.SetDirty(material); - var uiPath=JsonUIPrefabBuilder.BuildFromJsonFile(Path.Combine(Repo,"Doc/UIPrefabCreater/UI_LmGirlAnimationLab.json")); - if (string.IsNullOrEmpty(uiPath)) throw new InvalidOperationException("LmGirl UI conversion failed."); - var previous=SceneManager.GetActiveScene(); - var scene=EditorSceneManager.NewScene(NewSceneSetup.EmptyScene,NewSceneMode.Additive); - try - { - SceneManager.SetActiveScene(scene); - var root=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(Generated+"/CharacterAnimationLab.prefab")); - PrefabUtility.UnpackPrefabInstance(root,PrefabUnpackMode.Completely,InteractionMode.AutomatedAction); - root.name="LmGirlAnimationLab"; - var viewer=root.GetComponent(); - string[] variants={"mixamo","raw","post","local"}; - string[] labels={"Mixamo 网站 · 33 骨","TokenRig 原始 · 41 骨","体素后处理 · 41 骨","局部约束 · 41 骨"}; - var slots=viewer.actors.Select(a=>a.rig.transform.parent).ToList(); - foreach(var actor in viewer.actors)UnityEngine.Object.DestroyImmediate(actor.rig); - slots.Add(UnityEngine.Object.Instantiate(slots[2],root.transform)); - viewer.actors=new CharacterAnimationLab.Actor[4]; - Color[] colors={new Color(.49f,.78f,.85f),new Color(.94f,.72f,.43f),new Color(.68f,.62f,.85f),new Color(.62f,.82f,.51f)}; - Quaternion Rotation(float[] q)=>new Quaternion(q[0],q[1],q[2],q[3]); - for (int i=0;i().sharedMaterial=Material("LmGirlPedestal"+i,colors[i],generated); - var rig=new GameObject("Rig");rig.transform.SetParent(slot,false);rig.transform.localScale=Vector3.one*(1.55f/actorData.height); - var bones=new Transform[actorData.bones.Length]; - for (int b=0;b();skin.sharedMesh=meshes[variants[i]];skin.bones=bones;skin.rootBone=bones[0]; - skin.sharedMaterial=material;skin.quality=SkinQuality.Auto;skin.updateWhenOffscreen=true; - skin.localBounds=new Bounds(new Vector3(0,.75f,0),new Vector3(3,3,3)); - viewer.actors[i]=new CharacterAnimationLab.Actor{label=labels[i],rig=rig,hips=bones[0], - rightHand=bones.First(b=>b.name=="mixamorig:RightHand"),renderer=skin, - walkingClip=i==0?walk33:walk41, - translationScale=actorData.translationScale,translationOffsets=actorData.translationOffsets.Select(x=>V(x)).ToArray(), - translationDeltaRotations=actorData.translationDeltaRotations?.Select(Rotation).ToArray()??Array.Empty(), - bindRotationCorrections=actorData.bindRotationCorrections?.Select(Rotation).ToArray()??Array.Empty(), - rotationParents=actorData.rotationParents??Array.Empty()}; - } - UnityEngine.Object.DestroyImmediate(viewer.ui.gameObject); - var ui=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(uiPath),root.transform); - viewer.ui=ui.transform;ui.GetComponentInChildren().viewer=viewer; - viewer.texturedMaterial=material;viewer.motion=0;viewer.time=10f/30;viewer.focus=-1;viewer.detail=0;viewer.yaw=0;viewer.textured=true;viewer.playing=true; - viewer.Sample(); - RenderSettings.ambientMode=AmbientMode.Flat;RenderSettings.ambientLight=new Color(.65f,.69f,.73f);RenderSettings.fog=false; - if (!EditorSceneManager.SaveScene(scene,targetRoot+"/LmGirlAnimationLab.unity")) - throw new IOException("LmGirl scene was not saved."); - PrefabUtility.SaveAsPrefabAsset(root,generated+"/LmGirlAnimationLab.prefab"); - // Keep the rejected generic-cleaning experiments inspectable as - // persistent prefabs without promoting them to the default lineup. - for(int b=0;bAssetDatabase.GetAssetPath(c)),walkingClips=new[]{AssetDatabase.GetAssetPath(walk33),AssetDatabase.GetAssetPath(walk41)},translationScale=data.translationScale}); - } - finally - { - if (previous.IsValid()&&previous.isLoaded)SceneManager.SetActiveScene(previous); - EditorSceneManager.CloseScene(scene,true); - } - } - public static string OpenLmGirl() - { - if (EditorSceneManager.GetSceneManagerSetup().Any(s=>SceneManager.GetSceneByPath(s.path).isDirty))throw new InvalidOperationException("Existing scene has unsaved changes."); - EditorSceneManager.OpenScene(Root+"/LmGirl/LmGirlAnimationLab.unity",OpenSceneMode.Single); - Selection.activeGameObject=UnityEngine.Object.FindObjectOfType().gameObject; - return SceneManager.GetActiveScene().path; - } public static string RefineLayout() { if (EditorApplication.isPlaying) throw new InvalidOperationException("Edit Mode required."); diff --git a/AIClient/AgentScripts/BuildHomeCamp.cs b/AIClient/AgentScripts/BuildHomeCamp.cs index af59b36..6f089eb 100644 --- a/AIClient/AgentScripts/BuildHomeCamp.cs +++ b/AIClient/AgentScripts/BuildHomeCamp.cs @@ -340,8 +340,8 @@ public static class BuildHomeCamp } Add().mode.value = TonemappingMode.Neutral; var color = Add(); - // Noon frame + another ~20% brightness (EV += log2(1.2)); mild contrast keeps shade soft-PBR. - color.postExposure.value = .74f; color.contrast.value = 10; color.saturation.value = 5; color.hueShift.value = 0; + // Keep the sunny palette while retaining roof and paving highlights in the home screen. + color.postExposure.value = .30f; color.contrast.value = 10; color.saturation.value = 5; color.hueShift.value = 0; color.colorFilter.value = Color.white; var white = Add(); white.temperature.value = 6; white.tint.value = 0; var bloom = Add(); bloom.threshold.value = 1.15f; bloom.intensity.value = .12f; bloom.scatter.value = .55f; @@ -415,6 +415,7 @@ public static class BuildHomeCamp camera.allowHDR = true; camera.allowMSAA = true; var data = camera.GetUniversalAdditionalCameraData(); data.renderPostProcessing = true; data.volumeLayerMask = 1 << Layer; + data.renderShadows = true; return camera; } static Light Light(Scene scene, Transform parent) @@ -425,10 +426,10 @@ public static class BuildHomeCamp // Key: warm sun. Fill comes from a separate baked skylight so Subtractive maps do not crush backfaces. // Fully Baked sun: cast shadows live in the lightmap RGB. Mixed/Subtractive still needs // realtime shadow maps, which offscreen camera.Render does not produce — so review looked shadowless. - // Noon key: high elevation, +30% intensity vs prior look-dev; shade deepened in bake/shaders. - light.color = new Color(1, .98f, .94f); light.intensity = 2.15f; light.shadows = LightShadows.Soft; - light.lightmapBakeType = LightmapBakeType.Baked; light.shadowAngle = 4; - light.shadowStrength = .94f; light.shadowBias = .05f; light.shadowNormalBias = .3f; + // Noon key: warm sun with realtime soft shadows for buildings, explorer and companions. + light.color = new Color(1, .98f, .94f); light.intensity = 1.95f; light.shadows = LightShadows.Soft; + light.lightmapBakeType = LightmapBakeType.Realtime; light.shadowAngle = 4; + light.shadowStrength = .85f; light.shadowBias = .05f; light.shadowNormalBias = .3f; light.cullingMask = 1 << Layer; go.transform.rotation = Quaternion.Euler(68, 50, 0); BakeFillLight(scene, parent); return light; @@ -471,12 +472,13 @@ public static class BuildHomeCamp renderer.sharedMaterial = AssetDatabase.LoadAssetAtPath(Root + "/backdrop.mat"); renderer.shadowCastingMode = ShadowCastingMode.Off; renderer.receiveShadows = false; const float distance = 75; - var halfHeight = distance * Mathf.Tan(camera.fieldOfView * Mathf.Deg2Rad * .5f); - var width = halfHeight * 2 * 1080f / 1920; - var height = width / 1.5f; - go.transform.position = camera.transform.position + camera.transform.forward * distance + camera.transform.up * (halfHeight - height * .5f); + // Wide enough for 3:4 tablets and tall enough for 9:22 ultra-long displays with margin. + const float width = 72f; + const float height = 56f; + const float topHeight = 52f; + go.transform.position = camera.transform.position + camera.transform.forward * distance + camera.transform.up * (topHeight - height * .5f); go.transform.rotation = camera.transform.rotation; - go.transform.localScale = new Vector3(width * 1.04f, height * 1.04f, 1); + go.transform.localScale = new Vector3(width, height, 1); SetLayer(go); } @@ -529,6 +531,66 @@ public static class BuildHomeCamp foreach (var house in new[] { "MainHouse", "HouseLeft", "HouseRight" }) root.transform.Find(house).position += Vector3.down * .08f; Put("fountain", "Fountain", 0, .8f, 4.4f, true); + // Place protagonist Explorer in the central plaza (scaled 4x for isometric camp readability) + var explorerPrefab = AssetDatabase.LoadAssetAtPath("Assets/Bundles/Characters/Human/Explorer/Explorer.prefab"); + if (explorerPrefab != null) + { + var explorerGo = (GameObject)PrefabUtility.InstantiatePrefab(explorerPrefab, root.transform); + explorerGo.name = "Explorer"; + float expY = GroundY(ground, 0, -3.5f); + explorerGo.transform.position = new Vector3(0, expY, -3.5f); + explorerGo.transform.localScale = Vector3.one * 4.0f; + // Face towards camera / player by default + explorerGo.transform.rotation = Quaternion.Euler(0, 180f, 0); + SetLayer(explorerGo); + placements.Add("Explorer protagonist groundY=" + expY.ToString("F3")); + } + // Place wandering companions in the camp (scaled 4x for isometric readability) + var petDefs = new[] + { + (path: "Assets/Bundles/Characters/Animal/FireCat/FireCat_Companion.prefab", name: "Pet_FireCat", x: -3.8f, z: 1.2f, r: 2.8f, s: 3.40f), + (path: "Assets/Bundles/Characters/Animal/RockBallStudio/RockBallStudio_Companion.prefab", name: "Pet_RockBall", x: 2.6f, z: -1.8f, r: 2.8f, s: 3.00f), + (path: "Assets/Bundles/Characters/Animal/BudBud/BudBud_Companion.prefab", name: "Pet_BudBud", x: -1.6f, z: 4.2f, r: 2.6f, s: 3.20f) + }; + + var petWandererType = AppDomain.CurrentDomain.GetAssemblies() + .Select(a => a.GetType("HotUpdate.Gameplay.CampPetWanderer")) + .FirstOrDefault(t => t != null); + + foreach (var petDef in petDefs) + { + var petPrefab = AssetDatabase.LoadAssetAtPath(petDef.path); + if (petPrefab == null) continue; + var petGo = (GameObject)PrefabUtility.InstantiatePrefab(petPrefab, root.transform); + petGo.name = petDef.name; + float petY = GroundY(ground, petDef.x, petDef.z); + petGo.transform.position = new Vector3(petDef.x, petY, petDef.z); + petGo.transform.localScale = Vector3.one * petDef.s; + // Default facing towards camera for -Z models + petGo.transform.rotation = Quaternion.Euler(0, 0, 0); + SetLayer(petGo); + + var col = petGo.GetComponent(); + if (col == null) + { + col = petGo.AddComponent(); + col.center = new Vector3(0, 0.45f, 0); + col.radius = 0.4f; + col.height = 1.0f; + } + + if (petWandererType != null) + { + var wanderer = petGo.GetComponent(petWandererType); + if (wanderer == null) wanderer = petGo.AddComponent(petWandererType); + var centerField = petWandererType.GetField("wanderCenter"); + if (centerField != null) centerField.SetValue(wanderer, new Vector3(petDef.x, petY, petDef.z)); + var radiusField = petWandererType.GetField("wanderRadius"); + if (radiusField != null) radiusField.SetValue(wanderer, petDef.r); + } + + placements.Add($"{petDef.name} groundY={petY:F3}"); + } // Keep canopy trees behind the roofs so they frame the backdrop without dunking HouseLeft in shadow. // Nearer canopy slots are placed after the viewer vector, as DGX meshes when ready. // TreeRearLeft / TreeRearRight stay cards: they sit behind the roofs and another 2000 tris each is not worth it. diff --git a/AIClient/AgentScripts/BuildModelView.cs b/AIClient/AgentScripts/BuildModelView.cs index a872646..68b01e7 100644 --- a/AIClient/AgentScripts/BuildModelView.cs +++ b/AIClient/AgentScripts/BuildModelView.cs @@ -6,7 +6,7 @@ using UnityEngine; using UnityEngine.Rendering; /// -/// 历史 LmGirl / LmCat 观察场景,保留旧 -Z 资产及相机方向;项目新资源采用 +Z 正面。 +/// 历史 Explorer / LmCat 观察场景,保留旧 -Z 资产及相机方向;项目新资源采用 +Z 正面。 /// unity command run_script --file AgentScripts/BuildModelView.cs --entry BuildModelView.Run /// public static class BuildModelView @@ -14,7 +14,7 @@ public static class BuildModelView const string SceneDir = "Assets/Bundles/Scenes/Test/ModelView"; const string ScenePath = SceneDir + "/ModelView.unity"; const string GroundMatPath = SceneDir + "/Ground.mat"; - const string GirlPath = "Assets/Bundles/Characters/Human/LmGirl/LmGirl.prefab"; + const string GirlPath = "Assets/Bundles/Characters/Human/Explorer/Explorer.prefab"; const string CatPath = "Assets/Bundles/Characters/Animal/LmCat/LmCat.prefab"; public static string Run() @@ -54,7 +54,7 @@ public static class BuildModelView var houses = PlaceHouses(); var girlGo = (GameObject)PrefabUtility.InstantiatePrefab(girl); - girlGo.name = "LmGirl"; + girlGo.name = "Explorer"; girlGo.transform.SetPositionAndRotation(new Vector3(-1.6f, 0f, -36f), Quaternion.identity); var catGo = (GameObject)PrefabUtility.InstantiatePrefab(cat); diff --git a/AIClient/AgentScripts/CheckPetFacing.cs b/AIClient/AgentScripts/CheckPetFacing.cs new file mode 100644 index 0000000..6fe3cfb --- /dev/null +++ b/AIClient/AgentScripts/CheckPetFacing.cs @@ -0,0 +1,41 @@ +#if UNITY_EDITOR +using UnityEditor; +using UnityEngine; + +public static class CheckPetFacing +{ + public static string Run() + { + var paths = new[] + { + "Assets/Bundles/Characters/Animal/FireCat/FireCat_Companion.prefab", + "Assets/Bundles/Characters/Animal/RockBallStudio/RockBallStudio_Companion.prefab", + "Assets/Bundles/Characters/Animal/BudBud/BudBud_Companion.prefab" + }; + var sb = new System.Text.StringBuilder(); + foreach (var path in paths) + { + var go = AssetDatabase.LoadAssetAtPath(path); + if (go == null) continue; + var smr = go.GetComponentInChildren(); + var anim = go.GetComponentInChildren(); + sb.AppendLine($"=== {go.name} ==="); + if (smr != null) + { + var bounds = smr.localBounds; + sb.AppendLine($" SMR: {smr.name}, localBounds center={bounds.center}, size={bounds.size}"); + if (smr.rootBone != null) + { + sb.AppendLine($" RootBone: {smr.rootBone.name}, localRot={smr.rootBone.localEulerAngles}, forward={smr.rootBone.forward}"); + } + } + if (anim != null) + { + sb.AppendLine($" Animator on: {anim.gameObject.name}, rot={anim.transform.localEulerAngles}"); + } + } + return sb.ToString(); + } +} +#endif + diff --git a/AIClient/AgentScripts/DiagnoseSharedHumanoidWalk.cs b/AIClient/AgentScripts/DiagnoseSharedHumanoidWalk.cs deleted file mode 100644 index 6ef4851..0000000 --- a/AIClient/AgentScripts/DiagnoseSharedHumanoidWalk.cs +++ /dev/null @@ -1,178 +0,0 @@ -#if UNITY_EDITOR -using System; -using System.IO; -using System.Linq; -using System.Collections.Generic; -using Newtonsoft.Json; -using UnityEditor; -using UnityEditor.SceneManagement; -using UnityEditor.Animations; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.Animations; -using UnityEngine.Playables; - -public static class DiagnoseSharedHumanoidWalk -{ - const string Boy="Assets/Bundles/Characters/Human/test/Boy_mixamo/CampGuideStudio.fbx"; - const string Girl="Assets/Bundles/Characters/Human/test/girl/LmGirlTpose.fbx"; - const string Walk="Assets/Bundles/Characters/Human/Common/Walking.fbx"; - static string Output=>Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/lmgirl_tpose_studio/shared_walk_audit"); - static float[] V(Vector3 v)=>new[]{v.x,v.y,v.z}; - static string P(UnityEngine.Object o)=>o?AssetDatabase.GetAssetPath(o):null; - static readonly HumanBodyBones[] BoneIds={HumanBodyBones.Hips,HumanBodyBones.LeftUpperLeg,HumanBodyBones.RightUpperLeg,HumanBodyBones.LeftLowerLeg,HumanBodyBones.RightLowerLeg,HumanBodyBones.LeftFoot,HumanBodyBones.RightFoot,HumanBodyBones.LeftToes,HumanBodyBones.RightToes}; - static object[] Pose(Animator a)=>BoneIds.Select(id=>{var t=a.GetBoneTransform(id);return (object)new{bone=id.ToString(),name=t?t.name:null,position=t?V(a.transform.InverseTransformPoint(t.position)):null,rotation=t?V(t.localEulerAngles):null};}).ToArray(); - public static string Inspect() - { - var scene=EditorSceneManager.NewPreviewScene(); - try - { - var assets=new List(); - foreach(var path in new[]{Boy,Girl,Walk}) - { - var importer=(ModelImporter)AssetImporter.GetAtPath(path);var hd=importer.humanDescription; - var avatar=AssetDatabase.LoadAllAssetsAtPath(path).OfType().FirstOrDefault(); - var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene); - var animator=go.GetComponent();if(animator)animator.runtimeAnimatorController=null; - var ahd=avatar?avatar.humanDescription:new HumanDescription(); - assets.Add(new{path,importer.animationType,importer.avatarSetup,sourceAvatar=P(importer.sourceAvatar),avatar=avatar?avatar.name:null,valid=avatar&&avatar.isValid, - importerHuman=hd.human.Select(b=>new{b.humanName,b.boneName}).ToArray(),importerSkeleton=hd.skeleton.Select(b=>new{b.name,p=V(b.position),r=V(b.rotation.eulerAngles),s=V(b.scale)}).ToArray(), - avatarHuman=ahd.human==null?null:ahd.human.Select(b=>new{b.humanName,b.boneName}).ToArray(),avatarSkeleton=ahd.skeleton==null?null:ahd.skeleton.Select(b=>new{b.name,p=V(b.position),r=V(b.rotation.eulerAngles),s=V(b.scale)}).ToArray(), - pose=animator&&animator.isHuman?Pose(animator):null}); - UnityEngine.Object.DestroyImmediate(go); - } - Directory.CreateDirectory(Output);var json=JsonConvert.SerializeObject(assets,Formatting.Indented);File.WriteAllText(Path.Combine(Output,"avatars.json"),json);return "Saved "+Output+"/avatars.json"; - } - finally{EditorSceneManager.ClosePreviewScene(scene);} - } - public static string Compare() - { - var scene=EditorSceneManager.NewPreviewScene();var results=new List(); - try - { - foreach(var path in new[]{Boy,Girl})foreach(bool footIK in new[]{false,true}) - { - var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene); - var a=go.GetComponent();a.runtimeAnimatorController=null;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate; - var clip=AssetDatabase.LoadAllAssetsAtPath(Walk).OfType().First(c=>!c.name.StartsWith("__preview__")); - var graph=PlayableGraph.Create("SharedWalkAudit");graph.SetTimeUpdateMode(DirectorUpdateMode.Manual); - try - { - var playable=AnimationClipPlayable.Create(graph,clip);playable.SetApplyFootIK(footIK);playable.SetApplyPlayableIK(false); - var output=AnimationPlayableOutput.Create(graph,"Animation",a);output.SetSourcePlayable(playable);graph.Play(); - var frames=new List();float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0; - for(int i=0;i<125;i++) - { - playable.SetTime(i/120.0);graph.Evaluate(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++; - frames.Add(new{t=i/120.0,gap,pose=Pose(a)}); - } - results.Add(new{path,footIK,minGap,maxGap,crossed,frames}); - } - finally{graph.Destroy();UnityEngine.Object.DestroyImmediate(go);} - } - Directory.CreateDirectory(Output);var json=JsonConvert.SerializeObject(results,Formatting.Indented);File.WriteAllText(Path.Combine(Output,"baseline.json"),json); - return "Saved "+Output+"/baseline.json"; - } - finally{EditorSceneManager.ClosePreviewScene(scene);} - } - public static string VerifyController() - { - const string controllerPath="Assets/Bundles/Characters/Human/Common/Man.controller"; - var controller=AssetDatabase.LoadAssetAtPath(controllerPath); - var scene=EditorSceneManager.NewPreviewScene();var results=new List(); - var units=new List();var controllers=new List(); - var material=new Material(Shader.Find("Universal Render Pipeline/Lit"));material.color=new Color(.65f,.7f,.76f); - try - { - int column=0; - foreach(var path in new[]{Boy,Girl})foreach(bool footIK in new[]{false,true}) - { - // A transient controller contains only the actual walk state and clip. - var c=new AnimatorController();controllers.Add(c);c.AddLayer("Base Layer"); - var state=c.layers[0].stateMachine.AddState("walk");state.motion=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state.motion;state.iKOnFeet=footIK;c.layers[0].stateMachine.defaultState=state; - var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene);units.Add(go); - go.transform.position=new Vector3((1.5f-column++)*.85f,0,0); - var a=go.GetComponent();a.runtimeAnimatorController=c;a.applyRootMotion=false;a.cullingMode=AnimatorCullingMode.AlwaysAnimate; - foreach(var r in go.GetComponentsInChildren())r.sharedMaterial=material; - a.Rebind();a.Update(0); - float minGap=float.MaxValue,maxGap=float.MinValue;int crossed=0,minFrame=0; - for(int i=0;i<125;i++) - { - a.Play("Base Layer.walk",0,(i/120f)/state.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;if(gap();SceneManager.MoveGameObjectToScene(light.gameObject,scene);light.type=LightType.Directional;light.intensity=1.5f;light.transform.rotation=Quaternion.Euler(35,135,0); - var camera=new GameObject("AuditCamera").AddComponent();SceneManager.MoveGameObjectToScene(camera.gameObject,scene);camera.scene=scene;camera.orthographic=true;camera.orthographicSize=.8f; - camera.transform.position=new Vector3(0,.65f,5);camera.transform.LookAt(new Vector3(0,.65f,0));camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.19f,.22f,.26f);camera.nearClipPlane=.01f;camera.farClipPlane=20; - var rt=new RenderTexture(1600,720,24);var tex=new Texture2D(1600,720,TextureFormat.RGB24,false);var previous=RenderTexture.active; - Directory.CreateDirectory(Output); - try{camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,1600,720),0,0);tex.Apply();File.WriteAllBytes(Path.Combine(Output,"foot_ik_comparison.png"),tex.EncodeToPNG());} - finally{RenderTexture.active=previous;camera.targetTexture=null;rt.Release();UnityEngine.Object.DestroyImmediate(rt);UnityEngine.Object.DestroyImmediate(tex);} - var json=JsonConvert.SerializeObject(new{order=new[]{"Boy FootIK off","Boy FootIK on","Girl FootIK off","Girl FootIK on"},controllerPath,controllerDirty=EditorUtility.IsDirty(controller),stateDirty=EditorUtility.IsDirty(controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state),results},Formatting.Indented); - File.WriteAllText(Path.Combine(Output,"controller_verification.json"),json);return json; - } - finally - { - foreach(var go in units)UnityEngine.Object.DestroyImmediate(go); - foreach(var c in controllers){foreach(var l in c.layers){foreach(var s in l.stateMachine.states)UnityEngine.Object.DestroyImmediate(s.state);UnityEngine.Object.DestroyImmediate(l.stateMachine);}UnityEngine.Object.DestroyImmediate(c);} - EditorSceneManager.ClosePreviewScene(scene);UnityEngine.Object.DestroyImmediate(material); - } - } - public static string FixFootIK() - { - const string controllerPath="Assets/Bundles/Characters/Human/Common/Man.controller"; - var controller=AssetDatabase.LoadAssetAtPath(controllerPath); - var state=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state; - if(EditorUtility.IsDirty(controller)||EditorUtility.IsDirty(state))throw new Exception("Controller has unsaved edits; refusing to save unrelated edits."); - var scenesBefore=Enumerable.Range(0,SceneManager.sceneCount).Select(i=>{var s=SceneManager.GetSceneAt(i);return new{s.path,s.isDirty};}).ToArray(); - bool previous=state.iKOnFeet; - state.iKOnFeet=true;EditorUtility.SetDirty(state);AssetDatabase.SaveAssetIfDirty(state); - var importer=(ModelImporter)AssetImporter.GetAtPath(Walk); - if(!state.iKOnFeet || P(state.motion)!=Walk || P(importer.sourceAvatar)!=Boy)throw new Exception("Unexpected controller/source setup"); - var json=JsonConvert.SerializeObject(new{controllerPath,state=state.name,previousFootIK=previous,footIK=state.iKOnFeet,walk=P(state.motion),sourceAvatar=P(importer.sourceAvatar), - controllerDirty=EditorUtility.IsDirty(controller),stateDirty=EditorUtility.IsDirty(state), - actors=UnityEngine.Object.FindObjectsOfType(true).Where(a=>a.runtimeAnimatorController==controller).Select(a=>new{a.name,avatar=P(a.avatar),a.applyRootMotion}).ToArray(), - scenesBefore,scenesAfter=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,"fix.json"),json);return json; - } - public static string VerifyApplied() - { - var controller=AssetDatabase.LoadAssetAtPath("Assets/Bundles/Characters/Human/Common/Man.controller"); - var state=controller.layers[0].stateMachine.states.Single(s=>s.state.name=="walk").state; - if(!state.iKOnFeet)throw new Exception("Foot IK disabled"); - var scene=EditorSceneManager.NewPreviewScene();var results=new List(); - try - { - foreach(var path in new[]{Boy,Girl}) - { - var go=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(path),scene); - try - { - var a=go.GetComponent();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)/state.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("Crossing after fix: "+path); - results.Add(new{path,avatar=P(a.avatar),samples=125,minGap,maxGap,crossed}); - } - finally{UnityEngine.Object.DestroyImmediate(go);} - } - var json=JsonConvert.SerializeObject(new{controller=P(controller),state=state.name,footIK=state.iKOnFeet,clip=P(state.motion),results},Formatting.Indented); - File.WriteAllText(Path.Combine(Output,"validation.json"),json);return json; - } - finally{EditorSceneManager.ClosePreviewScene(scene);} - } -} -#endif diff --git a/AIClient/AgentScripts/ImportLmGirlTokenRigFbx.cs b/AIClient/AgentScripts/ImportLmGirlTokenRigFbx.cs deleted file mode 100644 index 5113384..0000000 --- a/AIClient/AgentScripts/ImportLmGirlTokenRigFbx.cs +++ /dev/null @@ -1,123 +0,0 @@ -#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(MaterialPath); - if(!material) - { - var reference=AssetDatabase.LoadAssetAtPath("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(FolderPath+"/LmGirlTokenRig_base.png")); - material.SetTexture("_MixTex",AssetDatabase.LoadAssetAtPath(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(ModelPath); - var avatar=AssetDatabase.LoadAllAssetsAtPath(ModelPath).OfType().FirstOrDefault(); - if(!avatar || !avatar.isValid || !avatar.isHuman)throw new Exception("Invalid Humanoid Avatar"); - if(!model.GetComponentsInChildren().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(); - var controller=AssetDatabase.LoadAssetAtPath("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(); - 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(path),scene);units.Add(go); - go.transform.position=new Vector3((.5f-column++)*.95f,0,0); - var a=go.GetComponent();var skin=go.GetComponentInChildren(); - 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();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();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 diff --git a/AIClient/AgentScripts/InspectCampLighting.cs b/AIClient/AgentScripts/InspectCampLighting.cs new file mode 100644 index 0000000..ffc695c --- /dev/null +++ b/AIClient/AgentScripts/InspectCampLighting.cs @@ -0,0 +1,41 @@ +using System; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; +using UnityEngine.SceneManagement; + +// Read-only diagnosis of the actual running camp, without rendering an extra camera frame. +public static class InspectCampLighting +{ + public static string Run() + { + var pipeline = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset; + var cameras = UnityEngine.Object.FindObjectsOfType(true); + var lights = UnityEngine.Object.FindObjectsOfType(true); + var renderers = UnityEngine.Object.FindObjectsOfType(true).Where(r => r.gameObject.layer == 29).ToArray(); + var report = new { + time = DateTime.UtcNow, activeScene = SceneManager.GetActiveScene().path, + scenes = Enumerable.Range(0,SceneManager.sceneCount).Select(i=>new { SceneManager.GetSceneAt(i).path, SceneManager.GetSceneAt(i).isLoaded }).ToArray(), + maps = LightmapSettings.lightmaps.Length, ambient = RenderSettings.ambientMode.ToString(), + sky = RenderSettings.ambientSkyColor.ToString(), equator = RenderSettings.ambientEquatorColor.ToString(), + sun = RenderSettings.sun != null ? RenderSettings.sun.name : null, + pipeline = pipeline != null ? pipeline.name : null, mainShadows = pipeline != null && pipeline.supportsMainLightShadows, + shadowDistance = pipeline != null ? pipeline.shadowDistance : 0, + cameras = cameras.Select(c => new { c.name, c.enabled, active = c.gameObject.activeInHierarchy, + c.cullingMask, target = c.targetTexture != null ? c.targetTexture.name : null, + shadows = c.GetComponent()?.renderShadows }).ToArray(), + lights = lights.Select(l=>new { l.name, l.enabled, active = l.gameObject.activeInHierarchy, l.intensity, l.cullingMask, + bake=l.lightmapBakeType.ToString(), shadows=l.shadows.ToString(), l.shadowStrength }).ToArray(), + rendererCount=renderers.Length, lightmapped=renderers.Count(r=>r.lightmapIndex>=0 && r.lightmapIndexnew {r.name, r.lightmapIndex, shadows=r.shadowCastingMode.ToString(), r.receiveShadows, + shader=r.sharedMaterial!=null?r.sharedMaterial.shader.name:null}).ToArray() + }; + string json = JsonConvert.SerializeObject(report,Formatting.Indented); + string folder=Path.GetFullPath(Path.Combine(Application.dataPath,"../../Tools/output/camp_lighting")); + Directory.CreateDirectory(folder); File.WriteAllText(Path.Combine(folder,"runtime_diagnosis.json"),json); + return json; + } +} diff --git a/AIClient/AgentScripts/InspectHomeCampAot.cs b/AIClient/AgentScripts/InspectHomeCampAot.cs new file mode 100644 index 0000000..61f9edc --- /dev/null +++ b/AIClient/AgentScripts/InspectHomeCampAot.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections; +using Newtonsoft.Json; +using UnityEngine; + +public static class InspectHomeCampAot +{ + public static string BuildAndCheck() + { + var built = AOT.Editor.YooAssetTools.BuildAndStageHotUpdate.BuildAndStage(); + if (!built.StartsWith("ok", StringComparison.Ordinal)) throw new Exception(built); + return built + " | " + CheckHotUpdate(); + } + + public static string CheckHotUpdate() + { + var cecil = AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "Mono.Cecil"); + var definition = cecil.GetType("Mono.Cecil.AssemblyDefinition"); + var stripped = Path.GetFullPath(Path.Combine(Application.dataPath, "../Library/Bee/artifacts/WebGL/ManagedStripped/YooAsset.dll")); + object original = definition.GetMethod("ReadAssembly", new[] { typeof(string) }).Invoke(null, new object[] { stripped }); + object update = definition.GetMethod("ReadAssembly", new[] { typeof(string) }).Invoke(null, new object[] { Path.Combine(Application.dataPath, "Bundles/Raw/HotUpdate.Runtime.bytes") }); + var calls = new System.Collections.Generic.List(); + using ((IDisposable)original) + using ((IDisposable)update) + { + var oldModule = Property(original, "MainModule"); + var module = Property(update, "MainModule"); + var references = (IEnumerable)module.GetType().GetMethod("GetMemberReferences", Type.EmptyTypes).Invoke(module, null); + foreach (object reference in references) + { + var declaring = Property(reference, "DeclaringType"); + string typeName = (string)Property(declaring, "FullName"); + if (typeName != "YooAsset.SceneHandle" && typeName != "YooAsset.HandleBase") continue; + string fullName = (string)Property(reference, "FullName"); + object type = oldModule.GetType().GetMethod("GetType", new[] { typeof(string) }).Invoke(oldModule, new object[] { typeName }); + var retained = ((IEnumerable)Property(type, "Methods")).Cast().Any(m => (string)Property(m, "FullName") == fullName); + if (!retained) throw new Exception("Hot update uses a method absent from the shipped shell: " + fullName); + calls.Add(fullName); + } + } + var report = JsonConvert.SerializeObject(new { passed=true, baseline=stripped, retainedCalls=calls }); + var output = Path.GetFullPath(Path.Combine(Application.dataPath, "../../Tools/output/homecamp_aot_compatibility")); + Directory.CreateDirectory(output); File.WriteAllText(Path.Combine(output, "aot_check.json"), report); + return report; + } + public static string Run() + { + var directory = Path.GetFullPath(Path.Combine(Application.dataPath, "../Library/Bee/artifacts/WebGL/ManagedStripped")); + var cecil = AppDomain.CurrentDomain.GetAssemblies().First(a => a.GetName().Name == "Mono.Cecil"); + var definition = cecil.GetType("Mono.Cecil.AssemblyDefinition"); + var output = new System.Collections.Generic.List(); + foreach (var file in new[] { "YooAsset.dll", "UnityEngine.CoreModule.dll" }) + { + object assembly = definition.GetMethod("ReadAssembly", new[] { typeof(string) }).Invoke(null, new object[] { Path.Combine(directory, file) }); + using ((IDisposable)assembly) + { + object module = Property(assembly, "MainModule"); + foreach (var name in new[] { "YooAsset.SceneHandle", "YooAsset.HandleBase", "UnityEngine.SceneManagement.SceneManager" }) + { + object type = module.GetType().GetMethod("GetType", new[] { typeof(string) }).Invoke(module, new object[] { name }); + if (type != null) output.Add(new { file, written = File.GetLastWriteTimeUtc(Path.Combine(directory, file)), type = name, + methods = ((IEnumerable)Property(type, "Methods")).Cast().Select(m => Property(m, "FullName")).ToArray() }); + } + } + } + return JsonConvert.SerializeObject(output); + } + static object Property(object value, string name) => value.GetType().GetProperty(name).GetValue(value); +} diff --git a/AIClient/AgentScripts/InspectLmGirlMaterials.cs b/AIClient/AgentScripts/InspectLmGirlMaterials.cs deleted file mode 100644 index 9204fda..0000000 --- a/AIClient/AgentScripts/InspectLmGirlMaterials.cs +++ /dev/null @@ -1,34 +0,0 @@ -#if UNITY_EDITOR -using System; -using System.IO; -using System.Linq; -using Newtonsoft.Json; -using UnityEditor; -using UnityEngine; - -public static class InspectLmGirlMaterials -{ - static string PathOf(UnityEngine.Object obj)=>obj?AssetDatabase.GetAssetPath(obj):null; - static object MaterialInfo(Material material)=>material?new{name=material.name,path=PathOf(material),shader=material.shader?material.shader.name:null, - textures=material.GetTexturePropertyNames().Select(p=>new{property=p,texture=PathOf(material.GetTexture(p))}).ToArray()}:null; - static object Renderers(GameObject obj)=>obj.GetComponentsInChildren(true).Select(r=>new{r.name, - mesh=r is SkinnedMeshRenderer skin&&skin.sharedMesh?new{name=skin.sharedMesh.name,vertices=skin.sharedMesh.vertexCount,uv=skin.sharedMesh.HasVertexAttribute(UnityEngine.Rendering.VertexAttribute.TexCoord0)}:null, - materials=r.sharedMaterials.Select(MaterialInfo).ToArray()}).ToArray(); - public static string Inspect() - { - const string path="Assets/Bundles/Characters/Human/test/girl/LmGirlTpose.fbx"; - var importer=(ModelImporter)AssetImporter.GetAtPath(path); - var model=AssetDatabase.LoadAssetAtPath(path); - var prefab=AssetDatabase.LoadAssetAtPath("Assets/Bundles/Characters/Human/test/girl/Girl.prefab"); - var report=new{importer=new{importer.materialImportMode,importer.materialLocation,importer.materialSearch, - remaps=importer.GetExternalObjectMap().Select(p=>new{p.Key.name,path=PathOf(p.Value)}).ToArray()}, - fbx=Renderers(model),prefab=prefab?Renderers(prefab):null, - embeddedMaterials=AssetDatabase.LoadAllAssetsAtPath(path).OfType().Select(MaterialInfo).ToArray(), - availableReviewMaterial=MaterialInfo(AssetDatabase.LoadAssetAtPath("Assets/Review/CharacterAnimationLab/LmGirl/Generated/LmGirlTextured.mat")), - scene=UnityEngine.Object.FindObjectsOfType(true).Where(a=>a.name.IndexOf("LmGirl",StringComparison.OrdinalIgnoreCase)>=0).Select(a=>new{a.name,renderers=Renderers(a.gameObject)}).ToArray()}; - var json=JsonConvert.SerializeObject(report,Formatting.Indented); - var directory=System.IO.Path.Combine(Directory.GetParent(Application.dataPath).Parent.FullName,"Tools/output/lmgirl_tpose_studio/material_audit"); - Directory.CreateDirectory(directory);File.WriteAllText(System.IO.Path.Combine(directory,"unity_materials.json"),json);return json; - } -} -#endif diff --git a/AIClient/AgentScripts/IntegrateHomeCamp.cs b/AIClient/AgentScripts/IntegrateHomeCamp.cs new file mode 100644 index 0000000..e747bd7 --- /dev/null +++ b/AIClient/AgentScripts/IntegrateHomeCamp.cs @@ -0,0 +1,132 @@ +using System; +using System.IO; +using AIGame.EditorTools; +using AOT.Editor.YooAssetTools; +using UnityEditor; +using UnityEngine; +using UnityEngine.UI; +using UnityEngine.EventSystems; +using System.Collections.Generic; + +public static class IntegrateHomeCamp +{ + public static string Status() => "frame=" + Time.frameCount + " paused=" + EditorApplication.isPaused + " background=" + Application.runInBackground; + public static string ExplorerStatus() + { + var presenter = UnityEngine.Object.FindObjectOfType(); + var stage = GameObject.Find("HomeExplorerStage"); + if (!stage) return "No explorer stage"; + var animator = stage.GetComponentInChildren(true); + return "ready=" + (presenter && presenter.Ready) + " animator=" + (animator != null) + " controller=" + (animator && animator.runtimeAnimatorController ? animator.runtimeAnimatorController.name : "null") + " avatar=" + (animator && animator.avatar ? animator.avatar.name : "null"); + } + public static string EnablePreviewFrames() { Application.runInBackground = true; return Status(); } + public static async System.Threading.Tasks.Task CheckArtManifest() + { + if (!EditorApplication.isPlaying) throw new InvalidOperationException("Play Mode required"); + if (!YooAsset.YooAssets.IsInitialized) YooAsset.YooAssets.Initialize(); + if (!YooAsset.YooAssets.TryGetPackage("Art", out var package)) package = YooAsset.YooAssets.CreatePackage("Art"); + var build = YooAsset.EditorSimulateBuildInvoker.Build("Art", (int)YooAsset.EBundleType.VirtualAssetBundle); + var init = package.InitializePackageAsync(new YooAsset.EditorSimulateModeOptions { + EditorFileSystemParameters = YooAsset.FileSystemParameters.CreateDefaultEditorFileSystemParameters(build.PackageRootDirectory) + }); + await init; + if (init.Status != YooAsset.EOperationStatus.Succeeded) throw new InvalidOperationException(init.Error); + var version = package.RequestPackageVersionAsync(); await version; + if (version.Status != YooAsset.EOperationStatus.Succeeded) throw new InvalidOperationException(version.Error); + var manifest = package.LoadPackageManifestAsync(new YooAsset.LoadPackageManifestOptions(version.PackageVersion, 60)); await manifest; + if (manifest.Status != YooAsset.EOperationStatus.Succeeded) throw new InvalidOperationException(manifest.Error); + if (!package.IsLocationValid("HomeCamp")) throw new InvalidOperationException("HomeCamp missing from Art manifest"); + return "PASS simulated Art manifest resolves HomeCamp"; + } + public static async System.Threading.Tasks.Task CheckArtSceneLoad() + { + var manager = HotUpdate.UI.UiManager.Instance; + var oldHud = manager.Get("UI_MainHUD"); + manager.Close("UI_MainHUD", true); + for (int i = 0; i < 100 && UnityEngine.SceneManagement.SceneManager.GetSceneByPath("Assets/Bundles/Scenes/Town/HomeCamp/HomeCamp.unity").isLoaded; i++) + await System.Threading.Tasks.Task.Delay(50); + GameObject hud = null; + manager.StartCoroutine(manager.OpenAsync("UI_MainHUD", loaded => hud = loaded)); + for (int i = 0; i < 200 && !hud; i++) await System.Threading.Tasks.Task.Delay(50); + if (!hud) throw new InvalidOperationException("HUD reload failed"); + var view = hud.GetComponent() ?? hud.AddComponent(); + view.Bind(); + for (int i = 0; i < 200 && (!hud || !hud.GetComponentInChildren(true) || !hud.GetComponentInChildren(true).Ready); i++) + await System.Threading.Tasks.Task.Delay(50); + var presenter = hud.GetComponentInChildren(true); + var field = typeof(HotUpdate.UI.HomeCampPresenter).GetField("_handle", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + if (presenter == null || !presenter.Ready || !(field.GetValue(presenter) is YooAsset.SceneHandle handle) || handle.Status != YooAsset.EOperationStatus.Succeeded) + throw new InvalidOperationException("YooAsset scene handle did not load HomeCamp"); + return "PASS YooAsset additive scene load and HUD render"; + } + public static string CheckPreview() + { + var hud = HotUpdate.UI.UiManager.Instance?.Get("UI_MainHUD"); + if (!hud) throw new InvalidOperationException("HUD not ready"); + var camp = hud.GetComponentInChildren(true); + if (!camp || !camp.Ready) throw new InvalidOperationException("Camp preview not ready"); + var image = camp.GetComponent(); + if (!image.enabled || image.raycastTarget || !(image.texture is RenderTexture rt)) + throw new InvalidOperationException("Camp render target or hit testing invalid"); + var camera = GameObject.Find("HomeCampCamera")?.GetComponent(); + if (!camera || camera.targetTexture != rt || !camera.enabled) + throw new InvalidOperationException("Camp camera is not rendering into HUD"); + var explorer = hud.transform.Find("ExplorerStage"); + if (!explorer || explorer.gameObject.activeSelf || hud.transform.Find("Btn_Explorer").gameObject.activeSelf) + throw new InvalidOperationException("Legacy explorer overlay should be hidden over the 3D camp"); + hud.SetActive(false); + if (camera.enabled && camera.gameObject.activeInHierarchy) throw new InvalidOperationException("Hidden HUD still renders camp"); + hud.SetActive(true); + if (!camera.enabled || !camera.gameObject.activeInHierarchy) throw new InvalidOperationException("Camp did not resume"); + return "PASS camp render " + rt.width + "x" + rt.height + "; FOV=" + camera.fieldOfView + "; legacy overlay hidden; hide/resume passed"; + } + public static string Capture() + { + var hud = HotUpdate.UI.UiManager.Instance.Get("UI_MainHUD"); + var camera = GameObject.Find("HomeMobileTestCamera").GetComponent(); + var canvas = hud.GetComponent(); + Canvas.ForceUpdateCanvases(); + foreach (var button in hud.transform.Find("Layout_Top").GetComponentsInChildren