Land Explorer in HomeCamp with boot loading, journal UI, and CDN publish tooling.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-11-04 16:18:01 +08:00
co-authored by Cursor
parent 9eec364f30
commit 80678ec499
405 changed files with 48053 additions and 420302 deletions
@@ -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<Data>(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<Data>(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<Gait>(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<Material>(matPath);
if (!material)
{
material=new Material(AssetDatabase.LoadAssetAtPath<Material>(Generated+"/CharacterTextured.mat"));
AssetDatabase.CreateAsset(material,matPath);
}
material.SetTexture("_MainTex",AssetDatabase.LoadAssetAtPath<Texture2D>(generated+"/LmGirlTpose_base.png"));
material.SetTexture("_MixTex",AssetDatabase.LoadAssetAtPath<Texture2D>(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<GameObject>(Generated+"/CharacterAnimationLab.prefab"));
PrefabUtility.UnpackPrefabInstance(root,PrefabUnpackMode.Completely,InteractionMode.AutomatedAction);
root.name="LmGirlAnimationLab";
var viewer=root.GetComponent<CharacterAnimationLab>();
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<variants.Length;i++)
{
var actorData=i==0?website:data;
var slot=slots[i];slot.localPosition=new Vector3((i-1.5f)*.83f,0,0);
slot.name="Stage_LmGirl_"+variants[i];
slot.GetComponentInChildren<Renderer>().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<bones.Length;b++)
{
var go=new GameObject(actorData.bones[b].name);bones[b]=go.transform;
go.transform.SetParent(actorData.bones[b].parent<0?rig.transform:bones[actorData.bones[b].parent],false);
SetTRS(go.transform,actorData.bones[b].rest);
}
var meshGo=new GameObject("CharacterMesh");meshGo.transform.SetParent(rig.transform,false);
var skin=meshGo.AddComponent<SkinnedMeshRenderer>();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<Quaternion>(),
bindRotationCorrections=actorData.bindRotationCorrections?.Select(Rotation).ToArray()??Array.Empty<Quaternion>(),
rotationParents=actorData.rotationParents??Array.Empty<int>()};
}
UnityEngine.Object.DestroyImmediate(viewer.ui.gameObject);
var ui=(GameObject)PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath<GameObject>(uiPath),root.transform);
viewer.ui=ui.transform;ui.GetComponentInChildren<AnimationLabSeekBar>().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;b<data.bones.Length;b++)SetTRS(viewer.actors[1].renderer.bones[b],data.bones[b].rest);
for(int b=0;b<website.bones.Length;b++)SetTRS(viewer.actors[0].renderer.bones[b],website.bones[b].rest);
PrefabUtility.SaveAsPrefabAsset(viewer.actors[0].rig,generated+"/LmGirl_mixamo.prefab");
foreach (string variant in new[]{"raw","post","clean","clean_eps","local"})
{
var actor=viewer.actors[1];actor.renderer.sharedMesh=meshes[variant];
PrefabUtility.SaveAsPrefabAsset(actor.rig,generated+"/LmGirl_"+variant+".prefab");
}
AssetDatabase.SaveAssets();
return JsonConvert.SerializeObject(new{scene=targetRoot+"/LmGirlAnimationLab.unity",visibleVariants=4,websiteBones=website.bones.Length,tokenRigBones=data.bones.Length,triangles=4999,sourceClips=viewer.clips.Select(c=>AssetDatabase.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<CharacterAnimationLab>().gameObject;
return SceneManager.GetActiveScene().path;
}
public static string RefineLayout()
{
if (EditorApplication.isPlaying) throw new InvalidOperationException("Edit Mode required.");