diff --git a/AgentScripts/WL816e_Build.cs b/AgentScripts/WL816e_Build.cs new file mode 100644 index 000000000..bf6ff6958 --- /dev/null +++ b/AgentScripts/WL816e_Build.cs @@ -0,0 +1,323 @@ +// WL-816e 빌드 — ① 섬 윗면 텍스처·머티리얼(데모 톤) ② 설정 SO ③ 윗면/길 마스크 굽기 +// 원본(FarmingIsland · 3DPixelArtEnvironment)은 읽기만 한다. +public static class WL816e_Build +{ + const string kDir = "Assets/WL/Look/Farm/"; + const string kTexPath = kDir + "Textures/WL_IslandTop_Demo.png"; + const string kMatPath = kDir + "Materials/Farm_IslandTop_Demo.mat"; + const string kSoPath = kDir + "Resources/WL/WLIslandLookSettings.asset"; + + // 🔴 「데모 지면과 **화면에서 같은 색**이 되는」 값 — 실측으로 역산했다. + // 데모 ToonTerrain 의 화면 초록(구름 그림자 없는 p95) = #B1E196 = 선형(0.4397, 0.7529, 0.3050) + // 우리 Shader Graphs/Toon 은 텍스처를 (1.031, 0.889, 0.717) 배로 화면에 낸다(실측) + // → 텍스처 초록 = 0.4397/1.031, 0.7529/0.889, 0.3050/0.717 = (0.4265, 0.8469, 0.4254) = #AFEDAE + static readonly UnityEngine.Color kGrassLin = new UnityEngine.Color(0.4265f, 0.8469f, 0.4254f, 1f); + // 모래·흙은 데모 Terrain 2층(_GreenHighlight / _GreenShadow)에 같은 감쇠 0.717 을 적용 + static readonly UnityEngine.Color kSandLin = new UnityEngine.Color(0.6020f, 0.5954f, 0.4629f, 1f); + static readonly UnityEngine.Color kDirtLin = new UnityEngine.Color(0.2165f, 0.1632f, 0.0949f, 1f); + // 그림자비 = Terrain 의 _RedShadow / _RedHighlight + static readonly UnityEngine.Color kShadowRatio = new UnityEngine.Color(0.3248f, 0.3416f, 0.3936f, 1f); + + public static void RunTex() + { + var sb = new System.Text.StringBuilder(); + Texture(sb); + Material(sb); + UnityEditor.AssetDatabase.SaveAssets(); + UnityEngine.Debug.Log(sb.ToString()); + } + + public static void Run() + { + var sb = new System.Text.StringBuilder(); + Texture(sb); + Material(sb); + Settings(sb); + UnityEditor.AssetDatabase.SaveAssets(); + UnityEngine.Debug.Log(sb.ToString()); + } + + // ── ① 8×8 텍스처 ────────────────────────────────────────────────── + static void Texture(System.Text.StringBuilder sb) + { + System.IO.Directory.CreateDirectory(kDir + "Textures"); + var t = new UnityEngine.Texture2D(8, 8, UnityEngine.TextureFormat.RGBA32, false, false); + // 원본 Island01.png 배치: (PNG 행 0 = 위) 위 왼쪽 = 모래 · 위 오른쪽 = 흙/절벽 · 아래 = 초록 2종(체크무늬) + // Unity 의 y 는 아래가 0 → v<0.5(아래 절반) = 초록. + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + { + UnityEngine.Color c; + if (y < 4) c = kGrassLin; // 초록 두 칸을 **한 색**으로 = 체크무늬 소멸 + else c = (x < 4) ? kSandLin : kDirtLin; + t.SetPixel(x, y, c.gamma); + } + t.Apply(); + System.IO.File.WriteAllBytes(kTexPath, UnityEngine.ImageConversion.EncodeToPNG(t)); + UnityEngine.Object.DestroyImmediate(t); + UnityEditor.AssetDatabase.ImportAsset(kTexPath, UnityEditor.ImportAssetOptions.ForceUpdate); + + var imp = UnityEditor.AssetImporter.GetAtPath(kTexPath) as UnityEditor.TextureImporter; + if (imp != null) + { + imp.textureType = UnityEditor.TextureImporterType.Default; + imp.sRGBTexture = true; + imp.filterMode = UnityEngine.FilterMode.Point; + imp.wrapMode = UnityEngine.TextureWrapMode.Clamp; + imp.mipmapEnabled = false; + imp.npotScale = UnityEditor.TextureImporterNPOTScale.None; + imp.textureCompression = UnityEditor.TextureImporterCompression.Uncompressed; + imp.maxTextureSize = 32; + imp.SaveAndReimport(); + } + sb.AppendLine("텍스처 " + kTexPath + " — 초록 " + Hex(kGrassLin) + " · 모래 " + Hex(kSandLin) + " · 흙 " + Hex(kDirtLin)); + } + + static string Hex(UnityEngine.Color lin) + { + var g = lin.gamma; + return "#" + UnityEngine.ColorUtility.ToHtmlStringRGB(g); + } + + // ── ② 머티리얼 ──────────────────────────────────────────────────── + static void Material(System.Text.StringBuilder sb) + { + var src = UnityEditor.AssetDatabase.LoadAssetAtPath(kDir + "Materials/Farm_Island01_ToonTex.mat"); + if (src == null) { sb.AppendLine("🔴 816a 의 Farm_Island01_ToonTex.mat 이 없다"); return; } + var tex = UnityEditor.AssetDatabase.LoadAssetAtPath(kTexPath); + + var m = UnityEngine.Object.Instantiate(src); + m.name = "Farm_IslandTop_Demo"; + if (m.HasProperty("_BaseMap")) m.SetTexture("_BaseMap", tex); + if (m.HasProperty("_ShadowBaseMap")) m.SetTexture("_ShadowBaseMap", tex); + if (m.HasProperty("_DiffuseColor")) m.SetColor("_DiffuseColor", UnityEngine.Color.white); + if (m.HasProperty("_ShadowDiffuseColor")) m.SetColor("_ShadowDiffuseColor", kShadowRatio); + + var old = UnityEditor.AssetDatabase.LoadAssetAtPath(kMatPath); + if (old != null) { old.CopyPropertiesFromMaterial(m); UnityEngine.Object.DestroyImmediate(m); UnityEditor.EditorUtility.SetDirty(old); } + else UnityEditor.AssetDatabase.CreateAsset(m, kMatPath); + sb.AppendLine("머티리얼 " + kMatPath + " (shader " + src.shader.name + " · _ShadowDiffuseColor " + kShadowRatio.ToString("F4") + ")"); + } + + // ── ③ 설정 SO + 마스크 ──────────────────────────────────────────── + static void Settings(System.Text.StringBuilder sb) + { + System.IO.Directory.CreateDirectory(kDir + "Resources/WL"); + UnityEditor.AssetDatabase.Refresh(); + + // 🔴 마스크를 **먼저** 굽는다 — Bake 가 씬을 열면 AssetDatabase 가 재로드돼 + // 앞서 잡아 둔 SO 참조가 끊긴다(MissingReferenceException). 구운 뒤에 SO 를 잡는다. + var bakedTiles = new System.Collections.Generic.List(); + var bakedRoads = new System.Collections.Generic.List(); + Bake(bakedTiles, bakedRoads, sb); + + var so = UnityEditor.AssetDatabase.LoadAssetAtPath(kSoPath); + if (so == null) + { + so = UnityEngine.ScriptableObject.CreateInstance(); + UnityEditor.AssetDatabase.CreateAsset(so, kSoPath); + UnityEditor.AssetDatabase.SaveAssets(); + so = UnityEditor.AssetDatabase.LoadAssetAtPath(kSoPath); + } + so.tileMasks = bakedTiles.ToArray(); + so.roadMasks = bakedRoads.ToArray(); + + // 머티리얼 교체 표 + var remap = new System.Collections.Generic.List(); + string[,] pairs = { + { "Assets/FarmingIsland/Materials/Palette.mat", kDir + "Materials/Farm_Palette_ToonTex.mat" }, + { "Assets/FarmingIsland/Materials/Islands/Island01.mat", kDir + "Materials/Farm_IslandTop_Demo.mat" }, + { "Assets/FarmingIsland/Materials/Islands/Road01.mat", kDir + "Materials/Farm_Road01_ToonTex.mat" }, + { "Assets/FarmingIsland/Materials/Skins/Skin_Purple.mat", kDir + "Materials/Farm_Skin_Purple_ToonTex.mat" }, + { "Assets/FarmingIsland/Materials/Skins/Skin_Yellow.mat", kDir + "Materials/Farm_Skin_Yellow_ToonTex.mat" }, + { "Assets/FarmingIsland/Materials/Environments/SimpleWater.mat", kDir + "Materials/Farm_SimpleWater_Demo.mat" }, + }; + for (int i = 0; i < pairs.GetLength(0); i++) + { + var a = UnityEditor.AssetDatabase.LoadAssetAtPath(pairs[i, 0]); + var b = UnityEditor.AssetDatabase.LoadAssetAtPath(pairs[i, 1]); + if (a == null || b == null) { sb.AppendLine("🔴 remap 실패 " + pairs[i, 0] + " → " + pairs[i, 1]); continue; } + remap.Add(new WL.Look.Farm.WLMatRemap { enabled_ = 1, from = a, to = b }); + } + so.materialRemap = remap.ToArray(); + so.islandTopMaterial = UnityEditor.AssetDatabase.LoadAssetAtPath(kMatPath); + so.skybox = UnityEditor.AssetDatabase.GetBuiltinExtraResource("Default-Skybox.mat"); + + // 흩뿌릴 것 — 데모 실측값 그대로 + var grassMesh = FindMesh("Assets/3DPixelArtEnvironment/Meshes/Instanced/Grass_Leaf.fbx", "Grass_Leaf"); + var flowerMesh = FindMesh("Assets/3DPixelArtEnvironment/Meshes/Instanced/Flower_Leaf.fbx", "Flower_Leaf"); + var stoneMesh = FindMeshByName("Stone_1"); + var mGrass = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/3DPixelArtEnvironment/Materials/Instanced_Grass.mat"); + var mFlower = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/3DPixelArtEnvironment/Materials/Instanced_Flower.mat"); + var mGravel = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/3DPixelArtEnvironment/Materials/Instanced_Gravel.mat"); + + var sc = new System.Collections.Generic.List(); + sc.Add(new WL.Look.Farm.WLScatterDef { label = "풀", mesh = grassMesh, material = mGrass, probability = 100f, scale = 0.35f, normalOffset = 0.04f, density = 2.5f }); + sc.Add(new WL.Look.Farm.WLScatterDef { label = "꽃", mesh = flowerMesh, material = mFlower, probability = 1f, scale = 0.35f, normalOffset = 0.04f, density = 2.5f }); + sc.Add(new WL.Look.Farm.WLScatterDef { label = "자갈", mesh = stoneMesh, material = mGravel, probability = 1f, scale = 0.7f, normalOffset = 0.1f, density = 0.05f }); + so.scatter = sc.ToArray(); + sb.AppendLine("scatter — 풀 " + (grassMesh == null ? "메시없음" : grassMesh.name) + + " · 꽃 " + (flowerMesh == null ? "메시없음" : flowerMesh.name) + + " · 자갈 " + (stoneMesh == null ? "메시없음" : stoneMesh.name)); + + UnityEditor.EditorUtility.SetDirty(so); + sb.AppendLine("SO " + kSoPath + " — remap " + so.materialRemap.Length + + " · tileMasks " + so.tileMasks.Length + " · roadMasks " + so.roadMasks.Length); + } + + static UnityEngine.Mesh FindMesh(string path, string name) + { + var all = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(path); + for (int i = 0; i < all.Length; i++) + { + var m = all[i] as UnityEngine.Mesh; + if (m != null) return m; + } + return null; + } + + static UnityEngine.Mesh FindMeshByName(string name) + { + var guids = UnityEditor.AssetDatabase.FindAssets(name + " t:Mesh"); + for (int i = 0; i < guids.Length; i++) + { + var p = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]); + var all = UnityEditor.AssetDatabase.LoadAllAssetsAtPath(p); + for (int k = 0; k < all.Length; k++) + { + var m = all[k] as UnityEngine.Mesh; + if (m != null && m.name == name) return m; + } + } + // 데모 씬의 인스턴싱 설정에서 직접 집는다 + return null; + } + + // ── 마스크 굽기 ─────────────────────────────────────────────────── + public static void Bake(System.Collections.Generic.List tiles, + System.Collections.Generic.List roads, + System.Text.StringBuilder sb) + { + var prev = UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene().path; + UnityEditor.SceneManagement.EditorSceneManager.OpenScene("Assets/FarmingIsland/Scenes/Level01.unity", + UnityEditor.SceneManagement.OpenSceneMode.Single); + var mgr = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); + var t = typeof(CryingSnow.FarmingIsland.IslandManager); + var islMeshes = t.GetField("islandMeshes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .GetValue(mgr) as System.Collections.Generic.List; + var roadMeshes = t.GetField("roadMeshes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .GetValue(mgr) as System.Collections.Generic.List; + + int res = 64; + + for (int i = 0; islMeshes != null && i < islMeshes.Count; i++) + { + var m = islMeshes[i]; + if (m == null) continue; + if (m.name == "Bridge") continue; // 다리에는 안 깐다 + var mask = Rasterize(m, res, 4f, true, out int on); + mask.meshName = m.name; + tiles.Add(mask); + if (i <= 1 || i == 46 || i == 47) + sb.AppendLine(" 마스크 isl[" + i + "] " + m.name + " 초록칸 " + on + "/" + (res * res) + + " (" + (on * 64f / (res * res)).ToString("F1") + "㎡)"); + } + for (int i = 0; roadMeshes != null && i < roadMeshes.Count; i++) + { + var m = roadMeshes[i]; + if (m == null) continue; + var mask = Rasterize(m, res, 4f, false, out int on); + mask.meshName = m.name; + roads.Add(mask); + } + if (!string.IsNullOrEmpty(prev)) UnityEditor.SceneManagement.EditorSceneManager.OpenScene(prev, UnityEditor.SceneManagement.OpenSceneMode.Single); + } + + /// + /// 메시의 **윗면**(법선 +Y · 최상단)을 XZ 격자에 굽는다. + /// greenOnly = true 면 **UV 가 Island01.png 의 초록 사분면(v < 0.5)** 인 삼각형만 센다 + /// = FI 가 「여기는 잔디」라고 표시한 자리. 모래 해변·흙 절벽에는 풀을 안 깐다. + /// + static WL.Look.Farm.WLTileMask Rasterize(UnityEngine.Mesh m, int res, float half, bool greenOnly, out int onCells) + { + var mask = new WL.Look.Farm.WLTileMask { res = res, half = half, bits = new byte[(res * res + 7) / 8] }; + var v = m.vertices; var tri = m.triangles; var uv = m.uv; + float maxY = -99999f; + for (int i = 0; i < v.Length; i++) if (v[i].y > maxY) maxY = v[i].y; + + float cell = half * 2f / res; + onCells = 0; + for (int i = 0; i + 2 < tri.Length; i += 3) + { + int i0 = tri[i], i1 = tri[i + 1], i2 = tri[i + 2]; + var a = v[i0]; var b = v[i1]; var c = v[i2]; + var n = UnityEngine.Vector3.Cross(b - a, c - a); + if (n.y <= 0f) continue; + if ((a.y + b.y + c.y) / 3f < maxY - 0.05f) continue; + if (greenOnly && uv != null && uv.Length > i2) + { + float vv = (uv[i0].y + uv[i1].y + uv[i2].y) / 3f; + if (vv >= 0.5f) continue; // 모래(위 왼쪽)·흙(위 오른쪽) = 잔디 아님 + } + + float minx = UnityEngine.Mathf.Min(a.x, UnityEngine.Mathf.Min(b.x, c.x)); + float maxx = UnityEngine.Mathf.Max(a.x, UnityEngine.Mathf.Max(b.x, c.x)); + float minz = UnityEngine.Mathf.Min(a.z, UnityEngine.Mathf.Min(b.z, c.z)); + float maxz = UnityEngine.Mathf.Max(a.z, UnityEngine.Mathf.Max(b.z, c.z)); + int cx0 = UnityEngine.Mathf.Max(0, UnityEngine.Mathf.FloorToInt((minx + half) / cell)); + int cx1 = UnityEngine.Mathf.Min(res - 1, UnityEngine.Mathf.CeilToInt((maxx + half) / cell)); + int cz0 = UnityEngine.Mathf.Max(0, UnityEngine.Mathf.FloorToInt((minz + half) / cell)); + int cz1 = UnityEngine.Mathf.Min(res - 1, UnityEngine.Mathf.CeilToInt((maxz + half) / cell)); + + for (int cx = cx0; cx <= cx1; cx++) + for (int cz = cz0; cz <= cz1; cz++) + { + float px = -half + (cx + 0.5f) * cell; + float pz = -half + (cz + 0.5f) * cell; + float d1 = (px - b.x) * (a.z - b.z) - (a.x - b.x) * (pz - b.z); + float d2 = (px - c.x) * (b.z - c.z) - (b.x - c.x) * (pz - c.z); + float d3 = (px - a.x) * (c.z - a.z) - (c.x - a.x) * (pz - a.z); + bool neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + bool pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + if (neg && pos) continue; + if (!mask.At(px, pz)) onCells++; + mask.Set(cx, cz, true); + } + } + return mask; + } + + // ── 마스크 눈으로 확인 ──────────────────────────────────────────── + public static void Show() + { + var so = UnityEditor.AssetDatabase.LoadAssetAtPath(kSoPath); + var sb = new System.Text.StringBuilder(); + string[] want = { "47", "46", "33", "01" }; + for (int w = 0; w < want.Length; w++) + { + var m = so.FindTileMask(want[w]); + if (m == null) { sb.AppendLine("마스크 없음 " + want[w]); continue; } + sb.AppendLine("--- TILE " + want[w] + " ---"); + for (int r = 31; r >= 0; r--) + { + var line = new System.Text.StringBuilder(" "); + for (int c = 0; c < 32; c++) + line.Append(m.At(-4f + (c + 0.5f) * 0.25f, -4f + (r + 0.5f) * 0.25f) ? '#' : '.'); + sb.AppendLine(line.ToString()); + } + } + var rm = so.FindRoadMask("15"); + if (rm != null) + { + sb.AppendLine("--- ROAD 15 ---"); + for (int r = 31; r >= 0; r--) + { + var line = new System.Text.StringBuilder(" "); + for (int c = 0; c < 32; c++) + line.Append(rm.At(-4f + (c + 0.5f) * 0.25f, -4f + (r + 0.5f) * 0.25f) ? '#' : '.'); + sb.AppendLine(line.ToString()); + } + } + UnityEngine.Debug.Log(sb.ToString()); + } +} diff --git a/AgentScripts/WL816e_Cap.cs b/AgentScripts/WL816e_Cap.cs new file mode 100644 index 000000000..de9a5b456 --- /dev/null +++ b/AgentScripts/WL816e_Cap.cs @@ -0,0 +1,139 @@ +// WL-816e 캡처 — 데모 씬(에디트 모드에서 풀 인스턴싱을 직접 그려서) vs 우리 섬 · 좌우 비교 합성 +// 🔴 데모 씬은 **Play 하지 않는다**(CLAUDE.md §9 상시 제약). 인스턴싱을 스크립트로 직접 발행해 렌더만 한다. +public static class WL816e_Cap +{ + const string Dir = "Screenshots_WL/WL816e/"; + const int W = 1080, H = 1920; + + // ── 데모 씬을 에디트 모드에서 「풀까지 보이게」 렌더 ──────────────── + public static void Demo() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + + var cam = DemoAngleCam(); + var sb = new System.Text.StringBuilder(); + int n = DrawInstancing(sb); + Shot(cam, Dir + "x_demo_demoangle.png", sb); + var mainCam = FindMainCam(); + int n2 = DrawInstancing(sb); + Shot(mainCam, Dir + "x_demo_main.png", sb); + sb.AppendLine("데모 인스턴싱 구성 " + n + "/" + n2); + UnityEngine.Debug.Log("[WL816e Cap Demo]\n" + sb.ToString()); + } + + /// 씬의 InstancesBehaviour 들이 그릴 것을 이번 프레임에 직접 발행한다(Play 없이). + static int DrawInstancing(System.Text.StringBuilder sb) + { + int n = 0; + var bs = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + long inst = 0; + for (int i = 0; i < bs.Length; i++) + { + var b = bs[i]; + System.Collections.Generic.Dictionary> data = null; + try { data = b.GetInstanceData(); } catch { continue; } + if (data == null) continue; + var bounds = b.CalculateInstancesBounds(); + var l2w = b.transform.localToWorldMatrix; + l2w.m03 = 0; l2w.m13 = 0; l2w.m23 = 0; + foreach (var kv in data) + { + if (kv.Value == null || kv.Value.Count == 0) continue; + var cfg = new Environment.Instancing.InstancingConfiguration(kv.Key, kv.Value, "_InstanceData"); + cfg.MaterialPropertyBlock.SetMatrix("_LocalToWorld", l2w); + UnityEngine.Graphics.DrawMeshInstancedIndirect(cfg.Mesh, 0, cfg.Material, bounds, cfg.CommandBuffer, 0, + cfg.MaterialPropertyBlock, UnityEngine.Rendering.ShadowCastingMode.Off, false, 0); + n++; inst += kv.Value.Count; + s_pending.Add(cfg); + } + } + sb.AppendLine(" 데모 인스턴스 총 " + inst + " (구성 " + n + ")"); + return n; + } + + static readonly System.Collections.Generic.List s_pending + = new System.Collections.Generic.List(); + + static void FreePending() + { + for (int i = 0; i < s_pending.Count; i++) s_pending[i].FreeMemory(); + s_pending.Clear(); + } + + static UnityEngine.Camera FindMainCam() + { + var cams = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + for (int i = 0; i < cams.Length; i++) if (cams[i].name == "Main Camera" || cams[i].CompareTag("MainCamera")) return cams[i]; + return cams.Length > 0 ? cams[0] : null; + } + + static UnityEngine.Camera DemoAngleCam() + { + var go = UnityEngine.GameObject.Find("__WL816eDemoAngle"); + if (go == null) { go = new UnityEngine.GameObject("__WL816eDemoAngle"); go.hideFlags = UnityEngine.HideFlags.DontSave; } + var c = go.GetComponent(); + if (c == null) c = go.AddComponent(); + c.orthographic = true; c.orthographicSize = 10f; + c.nearClipPlane = 0.3f; c.farClipPlane = 500f; + c.clearFlags = UnityEngine.CameraClearFlags.Skybox; + c.backgroundColor = new UnityEngine.Color(0.192f, 0.302f, 0.475f, 0f); + c.depth = -100f; + var rot = UnityEngine.Quaternion.Euler(30f, 60f, 0f); + go.transform.rotation = rot; + // 데모는 지형 중앙(약 130, 5, 90) 근처가 풀밭이다 + go.transform.position = new UnityEngine.Vector3(130f, 5.3f, 90f) - rot * UnityEngine.Vector3.forward * 60f; + if (go.GetComponent() == null) + go.AddComponent(); + return c; + } + + static void Shot(UnityEngine.Camera cam, string path, System.Text.StringBuilder sb) + { + if (cam == null) { sb.AppendLine(" 카메라 없음 " + path); return; } + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; + cam.targetTexture = rt; + cam.Render(); + UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0, 0, W, H), 0, 0); tex.Apply(); + System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); + UnityEngine.RenderTexture.active = prevA; cam.targetTexture = prevT; + UnityEngine.Object.DestroyImmediate(tex); + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + FreePending(); + sb.AppendLine(" 캡처 " + path); + } + + // ── 좌우 합성 ────────────────────────────────────────────────────── + public static void Compose() + { + Side(Dir + "a_play_main.png", Dir + "b_play_main.png", Dir + "c_side_by_side.png"); + Side(Dir + "x_demo_demoangle.png", Dir + "b_play_demoangle.png", Dir + "d_demo_vs_island.png"); + UnityEngine.Debug.Log("[WL816e Cap Compose] c_side_by_side.png(좌 지금/우 풀밭) · d_demo_vs_island.png(좌 데모/우 섬)"); + } + + public static void Side(string left, string right, string outPath) + { + if (!System.IO.File.Exists(left) || !System.IO.File.Exists(right)) { UnityEngine.Debug.Log("합성 건너뜀 " + outPath); return; } + var a = new UnityEngine.Texture2D(2, 2); UnityEngine.ImageConversion.LoadImage(a, System.IO.File.ReadAllBytes(left)); + var b = new UnityEngine.Texture2D(2, 2); UnityEngine.ImageConversion.LoadImage(b, System.IO.File.ReadAllBytes(right)); + int h = UnityEngine.Mathf.Max(a.height, b.height); + int w = a.width + b.width + 8; + var o = new UnityEngine.Texture2D(w, h, UnityEngine.TextureFormat.RGB24, false); + var fill = new UnityEngine.Color[w * h]; + for (int i = 0; i < fill.Length; i++) fill[i] = UnityEngine.Color.black; + o.SetPixels(fill); + o.SetPixels(0, h - a.height, a.width, a.height, a.GetPixels()); + o.SetPixels(a.width + 8, h - b.height, b.width, b.height, b.GetPixels()); + o.Apply(); + System.IO.File.WriteAllBytes(outPath, UnityEngine.ImageConversion.EncodeToPNG(o)); + UnityEngine.Object.DestroyImmediate(a); UnityEngine.Object.DestroyImmediate(b); UnityEngine.Object.DestroyImmediate(o); + } +} diff --git a/AgentScripts/WL816e_Color.cs b/AgentScripts/WL816e_Color.cs new file mode 100644 index 000000000..3329cd7c3 --- /dev/null +++ b/AgentScripts/WL816e_Color.cs @@ -0,0 +1,56 @@ +public static class WL816e_Color +{ + const string Dir = "Screenshots_WL/WL816e/"; + // 위에서 수직으로 내려다본 순수 지면 색을 잰다(선형 RGB 평균). + public static void Demo() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + var c = Cam(new UnityEngine.Vector3(100f, 30f, 100f), 6f); + var col = Sample(c, Dir + "x_demo_ground.png"); + UnityEngine.Debug.Log("[WL816e Color] DEMO 지면 sRGB=" + Hex(col) + " linear=" + col.linear.ToString("F4")); + } + public static void Ours() + { + var c = Cam(new UnityEngine.Vector3(0f, 30f, 0f), 3f); + var col = Sample(c, Dir + "x_ours_ground.png"); + UnityEngine.Debug.Log("[WL816e Color] OURS 지면 sRGB=" + Hex(col) + " linear=" + col.linear.ToString("F4")); + } + static string Hex(UnityEngine.Color c){ return "#" + UnityEngine.ColorUtility.ToHtmlStringRGB(c); } + static UnityEngine.Camera Cam(UnityEngine.Vector3 at, float size) + { + var go = UnityEngine.GameObject.Find("__WL816eColor"); + if (go == null) { go = new UnityEngine.GameObject("__WL816eColor"); go.hideFlags = UnityEngine.HideFlags.DontSave; } + var c = go.GetComponent(); if (c == null) c = go.AddComponent(); + c.orthographic = true; c.orthographicSize = size; c.nearClipPlane = 0.3f; c.farClipPlane = 500f; + c.clearFlags = UnityEngine.CameraClearFlags.Skybox; c.depth = -100f; + go.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); + go.transform.position = at; + if (go.GetComponent() == null) + go.AddComponent(); + return c; + } + static UnityEngine.Color Sample(UnityEngine.Camera cam, string path) + { + int W = 256, H = 256; + var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var pt = cam.targetTexture; var pa = UnityEngine.RenderTexture.active; + cam.targetTexture = rt; cam.Render(); + UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0,0,W,H), 0, 0); tex.Apply(); + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); + var px = tex.GetPixels(); + // 중앙값에 가까운 색 = 최빈 색을 찾는다(양자화 후) + var hist = new System.Collections.Generic.Dictionary(); + for (int i=0;ibestN){bestN=kv.Value;best=kv.Key;} + var mode = new UnityEngine.Color(((best>>12)&63)/63f, ((best>>6)&63)/63f, (best&63)/63f, 1f); + UnityEngine.RenderTexture.active = pa; cam.targetTexture = pt; + UnityEngine.Object.DestroyImmediate(tex); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + UnityEngine.Debug.Log(" 최빈색 비율 " + (bestN*100f/px.Length).ToString("F1") + "%"); + return mode; + } +} diff --git a/AgentScripts/WL816e_Color2.cs b/AgentScripts/WL816e_Color2.cs new file mode 100644 index 000000000..5fc48c4a1 --- /dev/null +++ b/AgentScripts/WL816e_Color2.cs @@ -0,0 +1,46 @@ +public static class WL816e_Color2 +{ + const string Dir = "Screenshots_WL/WL816e/"; + // 구름 그림자(_Cloud_Cover 0.5)가 절반을 덮으므로 **넓게 찍어 밝은 쪽 90퍼센타일**을 지면색으로 본다. + public static void Demo() + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/3DPixelArtEnvironment/Demo/Demo.unity", UnityEditor.SceneManagement.OpenSceneMode.Single); + Go(new UnityEngine.Vector3(120f, 60f, 100f), 45f, "DEMO", Dir + "x_demo_wide.png"); + } + public static void Ours() { Go(new UnityEngine.Vector3(8f, 60f, -4f), 18f, "OURS", Dir + "x_ours_wide.png"); } + + static void Go(UnityEngine.Vector3 at, float size, string tag, string path) + { + var go = UnityEngine.GameObject.Find("__WL816eColor2"); + if (go == null) { go = new UnityEngine.GameObject("__WL816eColor2"); go.hideFlags = UnityEngine.HideFlags.DontSave; } + var c = go.GetComponent(); if (c == null) c = go.AddComponent(); + c.orthographic = true; c.orthographicSize = size; c.nearClipPlane = 0.3f; c.farClipPlane = 500f; + c.clearFlags = UnityEngine.CameraClearFlags.SolidColor; c.backgroundColor = UnityEngine.Color.black; c.depth = -100f; + go.transform.rotation = UnityEngine.Quaternion.Euler(90f, 0f, 0f); go.transform.position = at; + if (go.GetComponent() == null) + go.AddComponent(); + + int W = 512, H = 512; + var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + c.targetTexture = rt; c.Render(); + var pa = UnityEngine.RenderTexture.active; UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0,0,W,H),0,0); tex.Apply(); + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); + var px = tex.GetPixels(); + var list = new System.Collections.Generic.List(px.Length); + for (int i=0;ip.r && p.g>p.b && p.g>0.15f) list.Add(p); } + list.Sort(delegate(UnityEngine.Color a, UnityEngine.Color b){ return (0.2126f*a.r+0.7152f*a.g+0.0722f*a.b).CompareTo(0.2126f*b.r+0.7152f*b.g+0.0722f*b.b); }); + UnityEngine.RenderTexture.active = pa; c.targetTexture = null; + UnityEngine.Object.DestroyImmediate(tex); rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + if (list.Count == 0) { UnityEngine.Debug.Log("[WL816e C2] " + tag + " 초록 픽셀 0"); return; } + var sb = new System.Text.StringBuilder("[WL816e C2] " + tag + " 초록픽셀 " + list.Count + "/" + px.Length + " "); + int[] pc = {50, 70, 85, 90, 95}; + for (int i=0;i("Assets/3DPixelArtEnvironment/Materials/"+p[i]+".mat"); + if(m==null){sb.AppendLine(p[i]+" none");continue;} + sb.Append(p[i]+" shader="+m.shader.name); + string[] k={"_DiffuseColor","_ShadowDiffuseColor","_Shades","_Brightness","_MinimumDarkness","_AmbientStrength","_ShallowColor","_DeepColor"}; + for(int j=0;j(UnityEngine.FindObjectsInactive.Include); + int unlocked = 0, total = 0; + var isls = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + for (int i = 0; i < isls.Length; i++) { total++; if (isls[i].IsUnlocked) unlocked++; } + sb.AppendLine("island : 총 " + total + " · 열린 " + unlocked + + " · coin=" + (mgr == null ? -1 : mgr.Coin) + + " · navmesh=" + (mgr == null ? false : mgr.HasNavMesh)); + + // 씬 정적 비용 + var rends = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + long tris = 0; int slots = 0; + var shaders = new System.Collections.Generic.Dictionary(); + for (int i = 0; i < rends.Length; i++) + { + var r = rends[i]; + if (r is UnityEngine.ParticleSystemRenderer) continue; + UnityEngine.Mesh mesh = null; + var mr = r as UnityEngine.MeshRenderer; + var sk = r as UnityEngine.SkinnedMeshRenderer; + if (mr != null) { var mf = r.GetComponent(); if (mf != null) mesh = mf.sharedMesh; } + else if (sk != null) mesh = sk.sharedMesh; + if (mesh != null) for (int s = 0; s < mesh.subMeshCount; s++) tris += mesh.GetIndexCount(s) / 3; + var ms = r.sharedMaterials; + for (int m = 0; m < ms.Length; m++) + { + if (ms[m] == null) continue; slots++; + string sh = ms[m].shader.name; + shaders[sh] = shaders.TryGetValue(sh, out int v) ? v + 1 : 1; + } + } + sb.Append("씬 정적 : 렌더러 " + rends.Length + " · 슬롯 " + slots + " · 삼각형 " + tris.ToString("N0") + " · 셰이더 "); + foreach (var kv in shaders) sb.Append(kv.Key + " x" + kv.Value + " · "); + sb.AppendLine(); + + // ── 성능 실측(같은 카메라로 N 프레임 렌더 시간) ──────────────── + var cam = FindCam("MainCamera"); + if (cam != null) + { + var perf = Measure(cam, 60); + sb.AppendLine("성능(" + tag + ") : " + perf); + } + + if (shoot) + { + System.IO.Directory.CreateDirectory(Dir); + Shot(cam, Dir + tag + "_play_main.png"); + Shot(TopDownCam(), Dir + tag + "_play_top.png"); + Shot(DemoAngleCam(), Dir + tag + "_play_demoangle.png"); + sb.AppendLine("캡처 " + tag + "_play_main / _play_top / _play_demoangle"); + } + UnityEngine.Debug.Log("[WL816e Play " + tag + "]\n" + sb.ToString()); + } + + /// 같은 카메라를 N 번 렌더해 프레임 시간을 잰다 + 마지막 프레임의 드로우콜/삼각형. + public static string Measure(UnityEngine.Camera cam, int frames) + { + var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; + cam.targetTexture = rt; + cam.Render(); // 워밈업 + var sw = System.Diagnostics.Stopwatch.StartNew(); + for (int i = 0; i < frames; i++) cam.Render(); + sw.Stop(); + int dc = UnityEditor.UnityStats.drawCalls; + int batches = UnityEditor.UnityStats.batches; + int setPass = UnityEditor.UnityStats.setPassCalls; + int tri = UnityEditor.UnityStats.triangles; + int vert = UnityEditor.UnityStats.vertices; + cam.targetTexture = prevT; + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + double ms = sw.Elapsed.TotalMilliseconds / frames; + return "렌더 " + ms.ToString("F2") + " ms/frame (= " + (1000.0 / System.Math.Max(0.001, ms)).ToString("F0") + " FPS 환산)" + + " · 드로우콜 " + dc + " · 배치 " + batches + " · SetPass " + setPass + + " · 삼각형 " + tri.ToString("N0") + " · 정점 " + vert.ToString("N0"); + } + + // ── 카메라 ───────────────────────────────────────────────────────── + public static UnityEngine.Camera TopDownCam() + { + var go = UnityEngine.GameObject.Find("__WL816eTop"); + if (go == null) { go = new UnityEngine.GameObject("__WL816eTop"); go.hideFlags = UnityEngine.HideFlags.DontSave; } + var c = go.GetComponent(); + if (c == null) c = go.AddComponent(); + c.orthographic = true; c.orthographicSize = 9f; + c.nearClipPlane = 0.3f; c.farClipPlane = 500f; + c.clearFlags = UnityEngine.CameraClearFlags.Skybox; + c.depth = -100f; + var rot = UnityEngine.Quaternion.Euler(50f, 0f, 0f); + go.transform.rotation = rot; + go.transform.position = new UnityEngine.Vector3(0f, 0f, 4f) - rot * UnityEngine.Vector3.forward * 40f; + if (go.GetComponent() == null) + go.AddComponent(); + return c; + } + + public static UnityEngine.Camera DemoAngleCam() + { + var go = UnityEngine.GameObject.Find("__WL816eDemoAngle"); + if (go == null) { go = new UnityEngine.GameObject("__WL816eDemoAngle"); go.hideFlags = UnityEngine.HideFlags.DontSave; } + var c = go.GetComponent(); + if (c == null) c = go.AddComponent(); + c.orthographic = true; c.orthographicSize = 10f; + c.nearClipPlane = 0.3f; c.farClipPlane = 500f; + c.clearFlags = UnityEngine.CameraClearFlags.Skybox; + c.backgroundColor = new UnityEngine.Color(0.192f, 0.302f, 0.475f, 0f); + c.depth = -100f; + var rot = UnityEngine.Quaternion.Euler(30f, 60f, 0f); + go.transform.rotation = rot; + go.transform.position = new UnityEngine.Vector3(0f, 0.6f, 4f) - rot * UnityEngine.Vector3.forward * 60f; + if (go.GetComponent() == null) + go.AddComponent(); + return c; + } + + public static UnityEngine.Camera FindCam(string name) + { + var cams = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Exclude, UnityEngine.FindObjectsSortMode.None); + for (int i = 0; i < cams.Length; i++) if (cams[i].name == name) return cams[i]; + for (int i = 0; i < cams.Length; i++) if (cams[i].name.StartsWith("__WL")) continue; else return cams[i]; + return cams.Length > 0 ? cams[0] : null; + } + + public static void Shot(UnityEngine.Camera cam, string path) + { + if (cam == null) return; + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)); + var rt = new UnityEngine.RenderTexture(W, H, 24, UnityEngine.RenderTextureFormat.ARGB32, UnityEngine.RenderTextureReadWrite.sRGB); + rt.antiAliasing = 1; rt.Create(); + var prevT = cam.targetTexture; var prevA = UnityEngine.RenderTexture.active; + cam.targetTexture = rt; + cam.Render(); + UnityEngine.RenderTexture.active = rt; + var tex = new UnityEngine.Texture2D(W, H, UnityEngine.TextureFormat.RGB24, false); + tex.ReadPixels(new UnityEngine.Rect(0, 0, W, H), 0, 0); + tex.Apply(); + System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); + UnityEngine.RenderTexture.active = prevA; + cam.targetTexture = prevT; + UnityEngine.Object.DestroyImmediate(tex); + rt.Release(); UnityEngine.Object.DestroyImmediate(rt); + } +} diff --git a/AgentScripts/WL816e_Probe.cs b/AgentScripts/WL816e_Probe.cs new file mode 100644 index 000000000..7a1bc7d41 --- /dev/null +++ b/AgentScripts/WL816e_Probe.cs @@ -0,0 +1,215 @@ +// WL-816e 실측 프로브 — ① 데모 풀밭이 정확히 무엇인가 ② 섬 타일의 윗면·제외 대상 +// 원본 씬은 열기만 하고 저장하지 않는다. +public static class WL816e_Probe +{ + public static void Run() + { + var sb = new System.Text.StringBuilder(); + Demo(sb); + Island(sb); + UnityEngine.Debug.Log(sb.ToString()); + } + + // ── ① 데모 풀밭 ──────────────────────────────────────────────────── + public static void Demo(System.Text.StringBuilder sb) + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/3DPixelArtEnvironment/Demo/Demo.unity", + UnityEditor.SceneManagement.OpenSceneMode.Single); + + sb.AppendLine("===== A. DEMO GRASS ====="); + var behaviours = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + sb.AppendLine("InstancesBehaviour 개수 = " + behaviours.Length); + + for (int i = 0; i < behaviours.Length; i++) + { + var b = behaviours[i]; + sb.AppendLine("--- [" + i + "] " + b.name + " (" + b.GetType().Name + ") active=" + b.gameObject.activeInHierarchy + + " enabled=" + b.enabled + " pos=" + b.transform.position.ToString("F3")); + + var ti = b as Environment.Instancing.TerrainInstancesBehaviour; + if (ti == null) continue; + + sb.AppendLine(" PositionVariance=" + ti.PositionVariance + " ScaleVariance=" + ti.ScaleVariance); + var terr = b.GetComponent(); + if (terr != null) + { + var td = terr.terrainData; + sb.AppendLine(" Terrain size=" + td.size.ToString("F3") + " alphamapTextureCount=" + td.alphamapTextureCount + + " alphamapRes=" + td.alphamapResolution + " heightmapRes=" + td.heightmapResolution + + " bounds=" + td.bounds.ToString() + + " drawTreesAndFoliage=" + terr.drawTreesAndFoliage + + " terrainMat=" + (terr.materialTemplate == null ? "null" : terr.materialTemplate.name + "/" + terr.materialTemplate.shader.name)); + // 지형 지면의 실제 높낮이 + float minH = 9999f, maxH = -9999f; + for (int x = 0; x <= 20; x++) + for (int z = 0; z <= 20; z++) + { + float h = td.GetInterpolatedHeight(x / 20f, z / 20f); + if (h < minH) minH = h; if (h > maxH) maxH = h; + } + sb.AppendLine(" 지형 높이 min=" + minH.ToString("F3") + " max=" + maxH.ToString("F3")); + } + + var inputs = new Environment.Instancing.TerrainInstancesBehaviour.TerrainInstancingInput[] + { ti.FirstLayer, ti.SecondLayer, ti.ThirdLayer, ti.FourthLayer }; + string[] names = { "FirstLayer", "SecondLayer", "ThirdLayer", "FourthLayer" }; + for (int L = 0; L < inputs.Length; L++) + { + var inp = inputs[L]; + if (inp == null) { sb.AppendLine(" " + names[L] + " = null"); continue; } + int nset = inp.Settings == null ? 0 : inp.Settings.Length; + sb.AppendLine(" " + names[L] + " Density=" + inp.Density + " (=> " + (inp.Density * inp.Density).ToString("F4") + + " 개/㎡ · step " + (1f / inp.Density).ToString("F3") + "m) settings=" + nset); + for (int s = 0; s < nset; s++) + { + var st = inp.Settings[s]; + if (st == null) { sb.AppendLine(" [" + s + "] null"); continue; } + long tris = 0; + if (st.Mesh != null) for (int m = 0; m < st.Mesh.subMeshCount; m++) tris += st.Mesh.GetIndexCount(m) / 3; + sb.AppendLine(" [" + s + "] mesh=" + (st.Mesh == null ? "null" : st.Mesh.name + " tris=" + tris + + " bounds=" + st.Mesh.bounds.size.ToString("F4")) + + " mat=" + (st.Material == null ? "null" : st.Material.name + "/" + st.Material.shader.name) + + " Prob=" + st.Probability + " Scale=" + st.Scale + " NormalOffset=" + st.NormalOffset); + } + } + } + + // 인스턴스 머티리얼 값 전수 + string[] matPaths = { + "Assets/3DPixelArtEnvironment/Materials/Instanced_Grass.mat", + "Assets/3DPixelArtEnvironment/Materials/Instanced_Flower.mat", + "Assets/3DPixelArtEnvironment/Materials/Instanced_Gravel.mat", + "Assets/3DPixelArtEnvironment/Materials/Instanced_Leaves.mat", + "Assets/3DPixelArtEnvironment/Materials/Terrain.mat", + }; + for (int i = 0; i < matPaths.Length; i++) + { + var m = UnityEditor.AssetDatabase.LoadAssetAtPath(matPaths[i]); + if (m == null) { sb.AppendLine("MAT 없음: " + matPaths[i]); continue; } + sb.AppendLine("MAT " + m.name + " shader=" + m.shader.name + " renderQueue=" + m.renderQueue + + " keywords=[" + string.Join(",", m.shaderKeywords) + "]"); + var sh = m.shader; + int n = UnityEditor.ShaderUtil.GetPropertyCount(sh); + for (int p = 0; p < n; p++) + { + string pn = UnityEditor.ShaderUtil.GetPropertyName(sh, p); + var pt = UnityEditor.ShaderUtil.GetPropertyType(sh, p); + string v; + if (pt == UnityEditor.ShaderUtil.ShaderPropertyType.Color) v = m.GetColor(pn).ToString("F4"); + else if (pt == UnityEditor.ShaderUtil.ShaderPropertyType.Vector) v = m.GetVector(pn).ToString("F4"); + else if (pt == UnityEditor.ShaderUtil.ShaderPropertyType.TexEnv) { var t = m.GetTexture(pn); v = t == null ? "none" : t.name; } + else v = m.GetFloat(pn).ToString("F4"); + sb.Append(" " + pn + "=" + v + "\n"); + } + } + } + + // ── ② 섬 ─────────────────────────────────────────────────────────── + public static void Island(System.Text.StringBuilder sb) + { + UnityEditor.SceneManagement.EditorSceneManager.OpenScene( + "Assets/FarmingIsland/Scenes/Level01.unity", + UnityEditor.SceneManagement.OpenSceneMode.Single); + + sb.AppendLine("===== B. LEVEL01 ISLAND ====="); + var mgr = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); + sb.AppendLine("IslandManager = " + (mgr == null ? "null" : mgr.name + " IslandSize=" + mgr.IslandSize)); + + var islands = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + sb.AppendLine("Island 개수 = " + islands.Length); + int unlocked = 0; + for (int i = 0; i < islands.Length; i++) + { + var isl = islands[i]; + if (isl.IsUnlocked) unlocked++; + var mf = isl.GetComponent(); + var mr = isl.GetComponent(); + string meshInfo = "none"; + if (mf != null && mf.sharedMesh != null) + meshInfo = mf.sharedMesh.name + " bounds=" + mf.sharedMesh.bounds.ToString() + " tris=" + (mf.sharedMesh.GetIndexCount(0) / 3); + string matInfo = mr == null ? "none" : (mr.sharedMaterial == null ? "null" : mr.sharedMaterial.name + "/" + mr.sharedMaterial.shader.name); + sb.AppendLine(" [" + i + "] " + isl.name + " pos=" + isl.transform.position.ToString("F2") + + " rotY=" + isl.transform.eulerAngles.y.ToString("F0") + + " scale=" + isl.transform.localScale.ToString("F2") + + " active=" + isl.gameObject.activeSelf + + " Unlocked=" + isl.IsUnlocked + " Bridge=" + isl.IsBridge + " Road=" + isl.HasRoad + + " mesh=" + meshInfo + " mat=" + matInfo + + " children=" + isl.transform.childCount); + if (i < 3) + { + for (int c = 0; c < isl.transform.childCount; c++) + { + var ch = isl.transform.GetChild(c); + var cmr = ch.GetComponent(); + var cmf = ch.GetComponent(); + sb.AppendLine(" child[" + c + "] " + ch.name + " active=" + ch.gameObject.activeSelf + + " localPos=" + ch.localPosition.ToString("F2") + + " comps=" + Comps(ch.gameObject) + + (cmf != null && cmf.sharedMesh != null ? " mesh=" + cmf.sharedMesh.name + " b=" + cmf.sharedMesh.bounds.ToString() : "") + + (cmr != null && cmr.sharedMaterial != null ? " mat=" + cmr.sharedMaterial.name : "")); + } + } + } + sb.AppendLine("Unlocked island 수 = " + unlocked); + + // 섬 콜라이더(윗면 판정용) + var isl0 = islands.Length > 0 ? islands[0] : null; + if (isl0 != null) + { + var cols = isl0.GetComponentsInChildren(true); + for (int c = 0; c < cols.Length; c++) + sb.AppendLine(" ISL0 collider " + cols[c].GetType().Name + " on " + cols[c].name + + " enabled=" + cols[c].enabled + " trigger=" + cols[c].isTrigger + + " bounds=" + cols[c].bounds.ToString() + " layer=" + UnityEngine.LayerMask.LayerToName(cols[c].gameObject.layer)); + } + + // 농사 타일 / 길 / 건물 + var farms = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + sb.AppendLine("Farm 개수 = " + farms.Length); + for (int i = 0; i < farms.Length; i++) + { + var f = farms[i]; + var soils = f.GetComponentsInChildren(true); + var rends = f.GetComponentsInChildren(true); + var b = new UnityEngine.Bounds(f.transform.position, UnityEngine.Vector3.zero); + for (int r = 0; r < rends.Length; r++) b.Encapsulate(rends[r].bounds); + sb.AppendLine(" Farm[" + i + "] " + f.name + " pos=" + f.transform.position.ToString("F2") + + " soils=" + soils.Length + " rends=" + rends.Length + " worldBounds=" + b.ToString()); + } + + var props = UnityEngine.Object.FindObjectsByType(UnityEngine.FindObjectsInactive.Include, UnityEngine.FindObjectsSortMode.None); + int nprop = 0; + var propTypes = new System.Collections.Generic.Dictionary(); + for (int i = 0; i < props.Length; i++) + { + if (props[i] is CryingSnow.FarmingIsland.IProp) + { + nprop++; + string t = props[i].GetType().Name; + propTypes[t] = propTypes.TryGetValue(t, out int v) ? v + 1 : 1; + } + } + sb.AppendLine("IProp 개수 = " + nprop); + foreach (var kv in propTypes) sb.AppendLine(" " + kv.Key + " x" + kv.Value); + + // 레이어 목록 + sb.AppendLine("--- 레이어 ---"); + for (int i = 0; i < 32; i++) + { + string ln = UnityEngine.LayerMask.LayerToName(i); + if (!string.IsNullOrEmpty(ln)) sb.Append(i + ":" + ln + " "); + } + sb.AppendLine(); + } + + static string Comps(UnityEngine.GameObject go) + { + var cs = go.GetComponents(); + var s = new System.Text.StringBuilder(); + for (int i = 0; i < cs.Length; i++) { if (cs[i] == null) continue; s.Append(cs[i].GetType().Name); s.Append('|'); } + return s.ToString(); + } +} diff --git a/AgentScripts/WL816e_Probe2.cs b/AgentScripts/WL816e_Probe2.cs new file mode 100644 index 000000000..1cab53418 --- /dev/null +++ b/AgentScripts/WL816e_Probe2.cs @@ -0,0 +1,149 @@ +// WL-816e 실측 프로브 2 — 섬 타일 윗면 기하 · 길 메시 · Island01 텍스처 · 816a 머티리얼 값 +public static class WL816e_Probe2 +{ + public static void Run() + { + var sb = new System.Text.StringBuilder(); + + // ── Island01.png 픽셀 ────────────────────────────────────────── + var tex = UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/FarmingIsland/Textures/Islands/Island01.png"); + sb.AppendLine("=== Island01.png " + (tex == null ? "null" : tex.width + "x" + tex.height + " fmt=" + tex.format) + " ==="); + if (tex != null) + { + var path = UnityEditor.AssetDatabase.GetAssetPath(tex); + var imp = UnityEditor.AssetImporter.GetAtPath(path) as UnityEditor.TextureImporter; + sb.AppendLine(" readable=" + (imp == null ? "?" : imp.isReadable.ToString()) + " filter=" + tex.filterMode + " wrap=" + tex.wrapMode); + try + { + for (int y = tex.height - 1; y >= 0; y--) + { + var line = new System.Text.StringBuilder(" y" + y + ": "); + for (int x = 0; x < tex.width; x++) + { + var c = tex.GetPixel(x, y); + line.Append("(" + c.r.ToString("F2") + "," + c.g.ToString("F2") + "," + c.b.ToString("F2") + ") "); + } + sb.AppendLine(line.ToString()); + } + } + catch (System.Exception e) { sb.AppendLine(" 픽셀 읽기 실패: " + e.Message); } + } + + // ── 816a 머티리얼 값 ─────────────────────────────────────────── + string[] mp = { + "Assets/WL/Look/Farm/Materials/Farm_Island01_ToonTex.mat", + "Assets/WL/Look/Farm/Materials/Farm_Palette_ToonTex.mat", + "Assets/WL/Look/Farm/Materials/Farm_Road01_ToonTex.mat", + "Assets/FarmingIsland/Materials/Islands/Island01.mat", + "Assets/FarmingIsland/Materials/Roads/Road01.mat", + }; + for (int i = 0; i < mp.Length; i++) + { + var m = UnityEditor.AssetDatabase.LoadAssetAtPath(mp[i]); + if (m == null) { sb.AppendLine("MAT 없음 " + mp[i]); continue; } + sb.AppendLine("MAT " + m.name + " shader=" + m.shader.name); + string[] keys = { "_DiffuseColor", "_ShadowDiffuseColor", "_BaseColor", "_Color", "_Shades", "_Brightness", "_MinimumDarkness", "_AmbientStrength", "_Outline", "_OutlineThickness" }; + for (int k = 0; k < keys.Length; k++) + { + if (!m.HasProperty(keys[k])) continue; + int id = UnityEngine.Shader.PropertyToID(keys[k]); + if (keys[k].Contains("Color") || keys[k] == "_Outline") sb.AppendLine(" " + keys[k] + "=" + m.GetColor(id).ToString("F4")); + else sb.AppendLine(" " + keys[k] + "=" + m.GetFloat(id).ToString("F4")); + } + if (m.HasProperty("_BaseMap")) { var t = m.GetTexture("_BaseMap"); sb.AppendLine(" _BaseMap=" + (t == null ? "none" : t.name)); } + } + + // ── Level01: 섬 메시 목록 · 윗면 기하 · 길 메시 ────────────────── + UnityEditor.SceneManagement.EditorSceneManager.OpenScene("Assets/FarmingIsland/Scenes/Level01.unity", + UnityEditor.SceneManagement.OpenSceneMode.Single); + var mgr = UnityEngine.Object.FindFirstObjectByType(UnityEngine.FindObjectsInactive.Include); + var t1 = typeof(CryingSnow.FarmingIsland.IslandManager); + var fIsl = t1.GetField("islandMeshes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var fRoad = t1.GetField("roadMeshes", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + var islMeshes = fIsl.GetValue(mgr) as System.Collections.Generic.List; + var roadMeshes = fRoad.GetValue(mgr) as System.Collections.Generic.List; + sb.AppendLine("=== islandMeshes " + (islMeshes == null ? 0 : islMeshes.Count) + " · roadMeshes " + (roadMeshes == null ? 0 : roadMeshes.Count) + " ==="); + + for (int i = 0; islMeshes != null && i < islMeshes.Count && i < 50; i++) + { + var m = islMeshes[i]; + if (m == null) { sb.AppendLine(" isl[" + i + "] null"); continue; } + TopFace(m, sb, " isl[" + i + "] " + m.name); + } + for (int i = 0; roadMeshes != null && i < roadMeshes.Count; i++) + { + var m = roadMeshes[i]; + if (m == null) { sb.AppendLine(" road[" + i + "] null"); continue; } + TopFace(m, sb, " road[" + i + "] " + m.name); + } + + // ── 마스크 그림(메시 47 · 0 · 46) ─────────────────────────────── + int[] show = { 47, 0, 46, 33 }; + for (int s = 0; s < show.Length; s++) + { + if (islMeshes == null || show[s] >= islMeshes.Count) continue; + Ascii(islMeshes[show[s]], sb, "ISLAND MASK [" + show[s] + "] " + islMeshes[show[s]].name, 0.001f); + } + if (roadMeshes != null && roadMeshes.Count > 15) Ascii(roadMeshes[15], sb, "ROAD MASK [15] " + roadMeshes[15].name, 0.001f); + if (roadMeshes != null && roadMeshes.Count > 5) Ascii(roadMeshes[5], sb, "ROAD MASK [5] " + roadMeshes[5].name, 0.001f); + + UnityEngine.Debug.Log(sb.ToString()); + } + + static void TopFace(UnityEngine.Mesh m, System.Text.StringBuilder sb, string label) + { + var v = m.vertices; var tri = m.triangles; + int upTri = 0; float area = 0f; float maxY = -9999f, topY = -9999f; + for (int i = 0; i < v.Length; i++) if (v[i].y > maxY) maxY = v[i].y; + for (int i = 0; i + 2 < tri.Length; i += 3) + { + var a = v[tri[i]]; var b = v[tri[i + 1]]; var c = v[tri[i + 2]]; + var n = UnityEngine.Vector3.Cross(b - a, c - a); + if (n.y <= 0f) continue; + float ay = (a.y + b.y + c.y) / 3f; + if (ay < maxY - 0.05f) continue; + upTri++; + area += n.magnitude * 0.5f; + if (ay > topY) topY = ay; + } + sb.AppendLine(label + " verts=" + v.Length + " tris=" + (tri.Length / 3) + " bounds=" + m.bounds.ToString() + + " maxY=" + maxY.ToString("F3") + " 윗면tri=" + upTri + " 윗면면적=" + area.ToString("F2") + "㎡"); + } + + static void Ascii(UnityEngine.Mesh m, System.Text.StringBuilder sb, string label, float eps) + { + sb.AppendLine("--- " + label + " (32x32 over local -4..4) ---"); + var v = m.vertices; var tri = m.triangles; + float maxY = -9999f; + for (int i = 0; i < v.Length; i++) if (v[i].y > maxY) maxY = v[i].y; + for (int r = 31; r >= 0; r--) + { + var line = new System.Text.StringBuilder(" "); + for (int c = 0; c < 32; c++) + { + float x = -4f + (c + 0.5f) * 0.25f; + float z = -4f + (r + 0.5f) * 0.25f; + line.Append(Inside(v, tri, x, z, maxY) ? '#' : '.'); + } + sb.AppendLine(line.ToString()); + } + } + + static bool Inside(UnityEngine.Vector3[] v, int[] tri, float px, float pz, float maxY) + { + for (int i = 0; i + 2 < tri.Length; i += 3) + { + var a = v[tri[i]]; var b = v[tri[i + 1]]; var c = v[tri[i + 2]]; + var n = UnityEngine.Vector3.Cross(b - a, c - a); + if (n.y <= 0f) continue; + if ((a.y + b.y + c.y) / 3f < maxY - 0.05f) continue; + float d1 = (px - b.x) * (a.z - b.z) - (a.x - b.x) * (pz - b.z); + float d2 = (px - c.x) * (b.z - c.z) - (b.x - c.x) * (pz - c.z); + float d3 = (px - a.x) * (c.z - a.z) - (c.x - a.x) * (pz - a.z); + bool neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + bool pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + if (!(neg && pos)) return true; + } + return false; + } +} diff --git a/AgentScripts/WL816e_Tune.cs b/AgentScripts/WL816e_Tune.cs new file mode 100644 index 000000000..4891db34f --- /dev/null +++ b/AgentScripts/WL816e_Tune.cs @@ -0,0 +1,19 @@ +public static class WL816e_Tune +{ + const string kSo = "Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset"; + public static void Run() + { + var so = UnityEditor.AssetDatabase.LoadAssetAtPath(kSo); + var sb = new System.Text.StringBuilder(); + for (int i = 0; i < so.scatter.Length; i++) + { + var s = so.scatter[i]; + if (s.label == "자갈") s.density = 0.05f; // 데모 SecondLayer Density 실측값 + sb.AppendLine("scatter[" + i + "] " + s.label + " density=" + s.density + " prob=" + s.probability + " scale=" + s.scale + + " mesh=" + (s.mesh == null ? "null" : s.mesh.name) + " mat=" + (s.material == null ? "null" : s.material.name)); + } + UnityEditor.EditorUtility.SetDirty(so); + UnityEditor.AssetDatabase.SaveAssets(); + UnityEngine.Debug.Log("[WL816e Tune]\n" + sb.ToString()); + } +} diff --git a/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Demo.mat b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Demo.mat new file mode 100644 index 000000000..0c1500917 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Demo.mat @@ -0,0 +1,103 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Farm_IslandTop_Demo + m_Shader: {fileID: -6465566751694194690, guid: 378227f8a69213e419dfb0d889d8dd2f, + type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _OUTLINESENABLED + - _SHADOWS_SOFT + m_InvalidKeywords: + - _CLOUDSENABLED + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: + RenderType: Opaque + disabledShaderPasses: + - MOTIONVECTORS + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BaseMap: + m_Texture: {fileID: 2800000, guid: c8df71f32cd52ec4ab69508f09a66921, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ShadowBaseMap: + m_Texture: {fileID: 2800000, guid: c8df71f32cd52ec4ab69508f09a66921, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_Lightmaps: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_LightmapsInd: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - unity_ShadowMasks: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _AlphaClip: 0 + - _AlphaToMask: 0 + - _Blend: 0 + - _Brightness: 0.25 + - _CastShadows: 1 + - _Cloud_Change: 0.005 + - _Cloud_Cover: 0.5 + - _Cloud_Density: 0.01 + - _Cloud_Strength: 1 + - _Cull: 2 + - _DepthEdgeStrength: 0.5 + - _DepthThreshold: 0.01 + - _DstBlend: 0 + - _DstBlendAlpha: 0 + - _MinimumDarkness: 0.2 + - _NormalEdgeStrength: 0.3 + - _NormalThreshold: 1 + - _OUTLINESENABLED: 1 + - _QueueControl: 0 + - _QueueOffset: 0 + - _Shades: 7 + - _SrcBlend: 1 + - _SrcBlendAlpha: 1 + - _Surface: 0 + - _XRMotionVectorsPass: 1 + - _ZTest: 4 + - _ZWrite: 1 + - _ZWriteControl: 0 + m_Colors: + - _Cloud_Movement: {r: 1, g: 1, b: 0, a: 0} + - _Cloud_Step: {r: 13, g: 17, b: 0, a: 0} + - _DiffuseColor: {r: 1, g: 1, b: 1, a: 1} + - _NormalBias: {r: 1, g: 1, b: 1, a: 0} + - _Outline: {r: 0, g: 0, b: 0, a: 0} + - _ShadowDiffuseColor: {r: 0.3248, g: 0.3416, b: 0.3936, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 +--- !u!114 &4831086338097714236 +MonoBehaviour: + m_ObjectHideFlags: 11 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d0353a89b1f911e48b9e16bdc9f2e058, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Editor::UnityEditor.Rendering.Universal.AssetVersion + version: 10 diff --git a/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Demo.mat.meta b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Demo.mat.meta new file mode 100644 index 000000000..959cef6f6 --- /dev/null +++ b/Assets/WL/Look/Farm/Materials/Farm_IslandTop_Demo.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 500f1cf3127afb748b316fe28b128edc +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset new file mode 100644 index 000000000..63bc62710 --- /dev/null +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -0,0 +1,344 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3e03e9bd93ee2ab42b5e0e002e11a343, type: 3} + m_Name: WLIslandLookSettings + m_EditorClassIdentifier: Assembly-CSharp::WL.Look.Farm.WLIslandLookSettings + enabled_: 1 + verboseLog: 1 + applyLook: 1 + applyLighting: 1 + applyReferenceLook: 1 + materialRemap: + - enabled_: 1 + from: {fileID: 2100000, guid: 1a0cfabe54dd9ae428d86e71aeec9122, type: 2} + to: {fileID: 2100000, guid: 68107aa66ca3855458e2ac55c239d57c, type: 2} + - enabled_: 1 + from: {fileID: 2100000, guid: 8b9c3fe585ec43742a0e29e3b3c82f6b, type: 2} + to: {fileID: 2100000, guid: 500f1cf3127afb748b316fe28b128edc, type: 2} + - enabled_: 1 + from: {fileID: 2100000, guid: ee0c382820516a74ba37852af34ecd11, type: 2} + to: {fileID: 2100000, guid: ece3895a184497040812f320d69cb967, type: 2} + - enabled_: 1 + from: {fileID: 2100000, guid: 582c561159122304fa6af2b7af90fde9, type: 2} + to: {fileID: 2100000, guid: 8d74133a7e5ceda4e882d35f7074296e, type: 2} + - enabled_: 1 + from: {fileID: 2100000, guid: 4adfce9253b18954ab9ef1e93111db39, type: 2} + to: {fileID: 2100000, guid: 2faacee2c1ffa36499387f201cae7361, type: 2} + - enabled_: 1 + from: {fileID: 2100000, guid: ce0f8fd131f36964dbc918b8c63f838b, type: 2} + to: {fileID: 2100000, guid: 1c9ce9b7865f6d643ad9601f6fa81ba3, type: 2} + islandTopMaterial: {fileID: 2100000, guid: 500f1cf3127afb748b316fe28b128edc, type: 2} + skipSoilRenderers: 1 + ambientSky: {r: 0.212, g: 0.227, b: 0.259, a: 1} + ambientEquator: {r: 0.114, g: 0.125, b: 0.133, a: 1} + ambientGround: {r: 0.047, g: 0.043, b: 0.035, a: 1} + ambientIntensity: 1 + skybox: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + dirLightColor: {r: 1, g: 1, b: 1, a: 1} + dirLightIntensity: 1 + dirLightSoftShadows: 1 + fogOff: 1 + disableFiGraphicsManager: 1 + rescanAtSeconds: + - 0.5 + - 2 + - 5 + grassEnabled: 1 + scatter: + - enabled_: 1 + label: "\uD480" + mesh: {fileID: -4413095505993930501, guid: 1f298817fdd2a184480e8af5b21278bc, type: 3} + material: {fileID: 2100000, guid: a194cb4ce6027c246af528b83b728a15, type: 2} + probability: 100 + scale: 0.35 + normalOffset: 0.04 + density: 2.5 + - enabled_: 1 + label: "\uAF43" + mesh: {fileID: -6327915695457451627, guid: dab2c84bf0b5ced4aaf0cffc4c491d55, type: 3} + material: {fileID: 2100000, guid: d50a40cb47ec86444b898d9121bbe0f7, type: 2} + probability: 1 + scale: 0.35 + normalOffset: 0.04 + density: 2.5 + - enabled_: 1 + label: "\uC790\uAC08" + mesh: {fileID: 2928966540353291585, guid: 7f1f11ac2c783374b876f8dc2bb7c7db, type: 3} + material: {fileID: 2100000, guid: 77d84437c100f274aad603219110357d, type: 2} + probability: 1 + scale: 0.7 + normalOffset: 0.1 + density: 0.05 + positionVariance: 0.5 + scaleVariance: 0.2 + randomYaw: 0 + edgeInset: 0.15 + roadMargin: 0.15 + farmMargin: 0.35 + propMargin: 0.05 + propMaxSize: 7.5 + maxInstances: 40000 + rebuildPollSeconds: 1 + rebuildDelaySeconds: 1.4 + tileMasks: + - meshName: 01 + res: 64 + half: 4 + bits: 00000000000000000000000000000000f0ffffffffffff0ff8ffffffffffff1ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3f + - meshName: 02 + res: 64 + half: 4 + bits: 00000000000000000000000000000000ffffffffffffff0fffffffffffffff1fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff1fffffffffffffff0f00000000000000000000000000000000 + - meshName: 03 + res: 64 + half: 4 + bits: 00000000000000000000000000000000ffffffffffffff0fffffffffffffff1fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3ffeffffffffffff3ffcffffffffffff3f + - meshName: 04 + res: 64 + half: 4 + bits: 00000000000000000000000000000000ffffffffffffff0fffffffffffffff1fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3f + - meshName: 05 + res: 64 + half: 4 + bits: 00000000000000000000000000000000f0fffffffffffffff8fffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffff8fffffffffffffff0ffffffffffffff00000000000000000000000000000000 + - meshName: 06 + res: 64 + half: 4 + bits: 00000000000000000000000000000000f0fffffffffffffff8fffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcffffffffffff7ffcffffffffffff3f + - meshName: 07 + res: 64 + half: 4 + bits: 00000000000000000000000000000000f0fffffffffffffff8fffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcffffffffffffff + - meshName: 08 + res: 64 + half: 4 + bits: 00000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000 + - meshName: 09 + res: 64 + half: 4 + bits: 00000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffff7ffcffffffffffff3f + - meshName: 10 + res: 64 + half: 4 + bits: 00000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffff3f + - meshName: 11 + res: 64 + half: 4 + bits: 00000000000000000000000000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffcffffffffffffff + - meshName: 12 + res: 64 + half: 4 + bits: 00000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + - meshName: 13 + res: 64 + half: 4 + bits: fcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ff8ffffffffffff1ff0ffffffffffff0f00000000000000000000000000000000 + - meshName: 14 + res: 64 + half: 4 + bits: fcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3f + - meshName: 15 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff1fffffffffffffff0f00000000000000000000000000000000 + - meshName: 16 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3ffeffffffffffff3ffcffffffffffff3f + - meshName: 17 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3f + - meshName: 18 + res: 64 + half: 4 + bits: fcffffffffffff3ffcffffffffffff7ffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffff8fffffffffffffff0ffffffffffffff00000000000000000000000000000000 + - meshName: 19 + res: 64 + half: 4 + bits: fcffffffffffff3ffcffffffffffff7ffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcffffffffffff7ffcffffffffffff3f + - meshName: 20 + res: 64 + half: 4 + bits: fcffffffffffff3ffcffffffffffff7ffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcffffffffffffff + - meshName: 21 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000 + - meshName: 22 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffff7ffcffffffffffff3f + - meshName: 23 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffff3f + - meshName: 24 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffcffffffffffffff + - meshName: 25 + res: 64 + half: 4 + bits: fcffffffffffff3ffeffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + - meshName: 26 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff1fffffffffffffff0f00000000000000000000000000000000 + - meshName: 27 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3ffeffffffffffff3ffcffffffffffff3f + - meshName: 28 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3fffffffffffffff3f + - meshName: 29 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000 + - meshName: 30 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffff7ffcffffffffffff3f + - meshName: 31 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffff3f + - meshName: 32 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffcffffffffffffff + - meshName: 33 + res: 64 + half: 4 + bits: ffffffffffffff3fffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + - meshName: 34 + res: 64 + half: 4 + bits: fcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffff8fffffffffffffff0ffffffffffffff00000000000000000000000000000000 + - meshName: 35 + res: 64 + half: 4 + bits: fcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcffffffffffff7ffcffffffffffff3f + - meshName: 36 + res: 64 + half: 4 + bits: fcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcfffffffffffffffcffffffffffffff + - meshName: 37 + res: 64 + half: 4 + bits: fcfffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000 + - meshName: 38 + res: 64 + half: 4 + bits: fcfffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffff7ffcffffffffffff3f + - meshName: 39 + res: 64 + half: 4 + bits: fcfffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffff3f + - meshName: 40 + res: 64 + half: 4 + bits: fcfffffffffffffffefffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffcffffffffffffff + - meshName: 41 + res: 64 + half: 4 + bits: fcfffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + - meshName: 42 + res: 64 + half: 4 + bits: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000 + - meshName: 43 + res: 64 + half: 4 + bits: fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffff7ffcffffffffffff3f + - meshName: 44 + res: 64 + half: 4 + bits: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffff3f + - meshName: 45 + res: 64 + half: 4 + bits: fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffffffffffffcffffffffffffff + - meshName: 46 + res: 64 + half: 4 + bits: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + - meshName: 47 + res: 64 + half: 4 + bits: 00000000000000000000000000000000f0ffffffffffff0ff8ffffffffffff1ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ffcffffffffffff3ff8ffffffffffff1ff0ffffffffffff0f00000000000000000000000000000000 + roadMasks: + - meshName: 01 + res: 64 + half: 4 + bits: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + - meshName: 02 + res: 64 + half: 4 + bits: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + - meshName: 03 + res: 64 + half: 4 + bits: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + - meshName: 04 + res: 64 + half: 4 + bits: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + - meshName: 05 + res: 64 + half: 4 + bits: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + - meshName: 06 + res: 64 + half: 4 + bits: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + - meshName: 07 + res: 64 + half: 4 + bits: 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + - meshName: 08 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + - meshName: 09 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + - meshName: 10 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + - meshName: 11 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + - meshName: 12 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + - meshName: 13 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffffffffff000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + - meshName: 14 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + - meshName: 15 + res: 64 + half: 4 + bits: 000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000 + islandSceneNames: + - Level01 + - WL_FarmLook diff --git a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset.meta b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset.meta new file mode 100644 index 000000000..ae2d43345 --- /dev/null +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ff7c7237221909a4eb2541de63e08e6f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Textures.meta b/Assets/WL/Look/Farm/Textures.meta new file mode 100644 index 000000000..dd8a9b49a --- /dev/null +++ b/Assets/WL/Look/Farm/Textures.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3c977e188be9fb54fba70ffcc9bdc7b7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png new file mode 100644 index 000000000..083457176 Binary files /dev/null and b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png differ diff --git a/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png.meta b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png.meta new file mode 100644 index 000000000..67886d2f9 --- /dev/null +++ b/Assets/WL/Look/Farm/Textures/WL_IslandTop_Demo.png.meta @@ -0,0 +1,181 @@ +fileFormatVersion: 2 +guid: c8df71f32cd52ec4ab69508f09a66921 +TextureImporter: + internalIDToNameTable: + - first: + 213: -7907069911445722437 + second: WL_IslandTop_Demo_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 1 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: iOS + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: WL_IslandTop_Demo_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 8 + height: 8 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: bba291a1fc1744290800000000000000 + internalID: -7907069911445722437 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/WL/Look/Farm/WLIslandGrass.cs b/Assets/WL/Look/Farm/WLIslandGrass.cs new file mode 100644 index 000000000..02c7f325f --- /dev/null +++ b/Assets/WL/Look/Farm/WLIslandGrass.cs @@ -0,0 +1,448 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLIslandGrass.cs — 섬 타일 **윗면**에 데모와 똑같은 풀·꽃·자갈을 깐다 (WL-816e · #816) +// +// ■ 왜 이렇게 하나 (816a 의 「터레인이 없어 구조상 불가」를 뒤집은 근거) +// 데모의 풀은 Terrain 기능이 아니라 `Environment.Instancing.InstancesBehaviour` +// (추상 클래스)가 GPU 인스턴싱으로 그리는 것이다. Terrain 은 **점을 어디에 찍을지** +// 알려 주는 역할만 한다(`TerrainInstancesBehaviour.GetInstanceData`). +// → 같은 추상 클래스를 상속해 **점을 섬 타일 윗면에서 뽑으면** 지형 없이 같은 그림이 나온다. +// 데모 에셋(`Assets/3DPixelArtEnvironment/**`)은 **한 글자도 고치지 않는다**(상속만). +// +// ■ 비용 — 그리기는 `Graphics.DrawMeshInstancedIndirect` 1회/설정. +// 풀·꽃·자갈 3종 = **드로우콜 3개**(인스턴스 수와 무관). 그림자 캐스팅 off. +// +// ■ 안전 — 콜라이더 0 · NavMeshSurface 가 굽는 대상 0 · 레이캐스트 대상 0. +// 걷기·밭 갈기·수확·건설에 물리적으로 관여할 수 없다. +// +// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다. +// ───────────────────────────────────────────────────────────────────────────── + +using System.Collections.Generic; +using UnityEngine; +using Environment.Instancing; +using CryingSnow.FarmingIsland; +using FIIsland = CryingSnow.FarmingIsland.Island; +using FIFarm = CryingSnow.FarmingIsland.Farm; +using FISoil = CryingSnow.FarmingIsland.Soil; + +namespace WL.Look.Farm +{ + [DisallowMultipleComponent] + public sealed class WLIslandGrass : InstancesBehaviour + { + public const string ObjectName = "~WL_IslandGrass"; + + // ── 진단(프로브·보고가 읽는다) ───────────────────────────────── + public static int Instances, Tiles, Rebuilds, Excluded, DrawnConfigs; + public static long Triangles; + public static float UsedDensity; + public static string LastLog = ""; + + public WLIslandLookSettings cfg; + + Bounds _bounds = new Bounds(Vector3.zero, Vector3.one * 16f); + readonly List _blockers = new List(128); + + public override Bounds CalculateInstancesBounds() { return _bounds; } + + /// 다시 깐다(섬이 확장됐을 때). 멱등 — 몇 번 불러도 안전. + public void Rebuild() + { + if (!isActiveAndEnabled) { enabled = true; return; } // OnEnable 이 알아서 만든다 + enabled = false; // OnDisable → 버퍼 해제 + enabled = true; // OnEnable → 다시 생성 + Rebuilds++; + } + + // ───────────────────────────────────────────────────────────────── + // 점 뽑기 — 데모 `TerrainInstancesBehaviour.GetInstanceData` 와 같은 절차 + // ───────────────────────────────────────────────────────────────── + public override Dictionary> GetInstanceData() + { + Instances = 0; Tiles = 0; Excluded = 0; Triangles = 0; DrawnConfigs = 0; UsedDensity = 0f; + + if (cfg == null) cfg = WLIslandLookSettings.Instance; + if (cfg == null || cfg.enabled_ == 0 || cfg.grassEnabled == 0) { LastLog = "꺼짐"; return null; } + if (cfg.scatter == null || cfg.scatter.Length == 0) { LastLog = "scatter 표가 비었다"; return null; } + + // 🔴 컴퓨트 버퍼(StructuredBuffer)를 못 쓰는 기기에서는 깔지 않는다(데모 셰이더와 같은 요구). + if (!SystemInfo.supportsComputeShaders || !SystemInfo.supportsInstancing) + { + LastLog = "기기가 인스턴싱/컴퓨트버퍼 미지원 → 풀 생략(그래도 게임은 그대로 돈다)"; + cfg.Log(LastLog); + return null; + } + + // 우리 오브젝트는 항상 원점·무회전·크기 1 이어야 한다(_LocalToWorld = 단위행렬). + transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + transform.localScale = Vector3.one; + transform.hasChanged = true; // 기반 클래스 Update 가 _LocalToWorld 를 밀어 넣게 한다 + + var tiles = CollectTiles(); + Tiles = tiles.Count; + if (tiles.Count == 0) { LastLog = "깔 타일 0"; return null; } + + BuildBlockers(tiles); + + // 밀도 자동 조절(모바일 예산) + float budgetScale = 1f; + if (cfg.maxInstances > 0) + { + float area = 0f; + for (int i = 0; i < tiles.Count; i++) area += 64f; // 타일 8×8 + float topLayer = 0f; + for (int i = 0; i < cfg.scatter.Length; i++) + { + var s = cfg.scatter[i]; + if (s == null || s.enabled_ == 0) continue; + if (s.density > topLayer) topLayer = s.density; + } + float est = area * topLayer * topLayer * 0.93f; // 윗면은 8×8 의 약 93 % + if (est > cfg.maxInstances) budgetScale = Mathf.Sqrt(cfg.maxInstances / est); + } + + // 같은 density 끼리 한 층으로 묶는다(데모의 FirstLayer/SecondLayer 와 같은 구조) + var layers = new List(4); + for (int i = 0; i < cfg.scatter.Length; i++) + { + var s = cfg.scatter[i]; + if (s == null || s.enabled_ == 0 || s.mesh == null || s.material == null || s.scale <= 0f) continue; + float d = s.density * budgetScale; + if (d <= 0f) continue; + bool found = false; + for (int k = 0; k < layers.Count; k++) if (Mathf.Abs(layers[k] - d) < 1e-5f) { found = true; break; } + if (!found) layers.Add(d); + } + + var result = new Dictionary>(); + var bmin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue); + var bmax = new Vector3(float.MinValue, float.MinValue, float.MinValue); + for (int i = 0; i < tiles.Count; i++) + { + var t = tiles[i]; + bmin = Vector3.Min(bmin, new Vector3(t.center.x - 4f, t.center.y - 1f, t.center.z - 4f)); + bmax = Vector3.Max(bmax, new Vector3(t.center.x + 4f, t.center.y + 3f, t.center.z + 4f)); + } + _bounds = new Bounds((bmin + bmax) * 0.5f, bmax - bmin); + Vector3 bc = _bounds.center; + + for (int L = 0; L < layers.Count; L++) + { + float density = layers[L]; + + // 이 층에 속한 정의들 + 누적 확률 + var defs = new List(4); + float wsum = 0f; + for (int i = 0; i < cfg.scatter.Length; i++) + { + var s = cfg.scatter[i]; + if (s == null || s.enabled_ == 0 || s.mesh == null || s.material == null || s.scale <= 0f) continue; + if (Mathf.Abs(s.density * budgetScale - density) > 1e-5f) continue; + defs.Add(s); wsum += Mathf.Max(0f, s.probability); + } + if (defs.Count == 0 || wsum <= 0f) continue; + + var buckets = new List[defs.Count]; + var iset = new InstancingSettings[defs.Count]; + for (int i = 0; i < defs.Count; i++) + { + buckets[i] = new List(1024); + iset[i] = new InstancingSettings + { + Mesh = defs[i].mesh, + Material = defs[i].material, + Probability = defs[i].probability, + Scale = defs[i].scale, + NormalOffset = defs[i].normalOffset, + }; + } + + float step = 1f / density; + float pv = cfg.positionVariance; + float sv = cfg.scaleVariance; + + for (int i = 0; i < tiles.Count; i++) + { + var t = tiles[i]; + int ix0 = Mathf.CeilToInt((t.center.x - 4f) / step - 0.5f); + int ix1 = Mathf.FloorToInt((t.center.x + 4f) / step - 0.5f); + int iz0 = Mathf.CeilToInt((t.center.z - 4f) / step - 0.5f); + int iz1 = Mathf.FloorToInt((t.center.z + 4f) / step - 0.5f); + + for (int ix = ix0; ix <= ix1; ix++) + { + for (int iz = iz0; iz <= iz1; iz++) + { + // 같은 칸이면 몇 번 다시 깔아도 같은 결과(확장 때 풀이 튀지 않는다) + uint h = Hash((uint)(ix * 73856093) ^ (uint)(iz * 19349663) ^ (uint)(L * 83492791)); + float jx = (Frac(h, 0) - 0.5f) * 2f * pv; + float jz = (Frac(h, 1) - 0.5f) * 2f * pv; + float wx = (ix + 0.5f + jx) * step; + float wz = (iz + 0.5f + jz) * step; + + if (!OnTile(t, wx, wz)) { Excluded++; continue; } + if (Blocked(wx, wz)) { Excluded++; continue; } + + // 어느 정의가 걸리나(가중 추첨 · 결정적) + float r = Frac(h, 2) * wsum; + int pick = defs.Count - 1; + float acc = 0f; + for (int d = 0; d < defs.Count; d++) + { + acc += Mathf.Max(0f, defs[d].probability); + if (r <= acc) { pick = d; break; } + } + + float sc = 1f + (Frac(h, 3) - 0.5f) * 2f * sv; + var rot = cfg.randomYaw != 0 ? Quaternion.Euler(0f, Frac(h, 4) * 360f, 0f) : Quaternion.identity; + var pos = new Vector3(wx, t.center.y, wz) - bc; + + var trs = Matrix4x4.TRS(pos, rot, Vector3.one * sc); + var def = defs[pick]; + trs *= Matrix4x4.TRS(def.normalOffset * Vector3.up, Quaternion.identity, + new Vector3(def.scale, def.scale, def.scale)); + + buckets[pick].Add(new InstanceData { TRS = trs, Normal = Vector3.up }); + } + } + } + + for (int i = 0; i < defs.Count; i++) + { + if (buckets[i].Count == 0) continue; + result.Add(iset[i], buckets[i]); + Instances += buckets[i].Count; + DrawnConfigs++; + long tri = 0; + for (int s = 0; s < defs[i].mesh.subMeshCount; s++) tri += defs[i].mesh.GetIndexCount(s) / 3; + Triangles += tri * buckets[i].Count; + if (density > UsedDensity) UsedDensity = density; + } + } + + LastLog = "타일 " + Tiles + " · 인스턴스 " + Instances + " · 드로우콜 " + DrawnConfigs + + " · 삼각형 " + Triangles + " · 제외점 " + Excluded + + " · 밀도 " + UsedDensity.ToString("F2") + "(=" + (UsedDensity * UsedDensity).ToString("F2") + "개/㎡)" + + (budgetScale < 1f ? " · 예산으로 밀도 ×" + budgetScale.ToString("F2") : ""); + cfg.Log(LastLog); + return result.Count == 0 ? null : result; + } + + // ───────────────────────────────────────────────────────────────── + // 섬 타일 모으기 + // ───────────────────────────────────────────────────────────────── + public struct TileRef + { + public Transform tf; // 섬 트랜스폼 + public Vector3 center; // 윗면 중심(월드 · y = 윗면) + public WLTileMask mask; // 윗면 마스크(섬 로컬) + public Transform roadTf; + public WLTileMask roadMask; + } + + readonly List _tiles = new List(96); + + List CollectTiles() + { + _tiles.Clear(); + var islands = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + for (int i = 0; i < islands.Length; i++) + { + var isl = islands[i]; + if (isl == null || !isl.IsUnlocked || isl.IsBridge) continue; + if (!isl.gameObject.activeInHierarchy) continue; + if (isl.transform.localScale.x < 0.99f) continue; // 확장 애니메이션 중 + + var mf = isl.GetComponent(); + if (mf == null || mf.sharedMesh == null) continue; + var mask = cfg.FindTileMask(mf.sharedMesh.name); + if (mask == null) continue; // 모르는 메시 = 안 깐다(안전) + + Transform roadTf = null; WLTileMask roadMask = null; + var rt = isl.transform.Find("Road"); + if (rt != null && rt.gameObject.activeInHierarchy) + { + var rmf = rt.GetComponent(); + if (rmf != null && rmf.sharedMesh != null) + { + roadMask = cfg.FindRoadMask(rmf.sharedMesh.name); + if (roadMask != null) roadTf = rt; + } + } + + _tiles.Add(new TileRef + { + tf = isl.transform, + center = isl.transform.position, // 윗면 y = 섬 원점 y (실측: 메시 maxY = 0) + mask = mask, + roadTf = roadTf, + roadMask = roadMask, + }); + } + return _tiles; + } + + bool OnTile(TileRef t, float wx, float wz) + { + var w = new Vector3(wx, t.center.y, wz); + var l = t.tf.InverseTransformPoint(w); + + // 가장자리 여백 — 점과 ±inset 네 방향이 전부 윗면이어야 한다 + float e = cfg.edgeInset; + if (!t.mask.At(l.x, l.z)) return false; + if (e > 0f) + { + if (!t.mask.At(l.x + e, l.z) || !t.mask.At(l.x - e, l.z) || + !t.mask.At(l.x, l.z + e) || !t.mask.At(l.x, l.z - e)) return false; + } + + // 길 제외 + if (t.roadMask != null && t.roadTf != null) + { + var lr = t.roadTf.InverseTransformPoint(w); + float m = cfg.roadMargin; + if (t.roadMask.At(lr.x, lr.z)) return false; + if (m > 0f && (t.roadMask.At(lr.x + m, lr.z) || t.roadMask.At(lr.x - m, lr.z) || + t.roadMask.At(lr.x, lr.z + m) || t.roadMask.At(lr.x, lr.z - m))) return false; + } + return true; + } + + // ───────────────────────────────────────────────────────────────── + // 제외 사각형 — 농사 타일 · 건물 · 소품 + // ───────────────────────────────────────────────────────────────── + void BuildBlockers(List tiles) + { + _blockers.Clear(); + var scene = tiles.Count > 0 ? tiles[0].tf.gameObject.scene : default(UnityEngine.SceneManagement.Scene); + + // ① 농사 — Farm 이 Awake 에서 만드는 BoxCollider(size = (length,2,width))가 밭의 정확한 넓이다. + var farms = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < farms.Length; i++) + { + var f = farms[i]; + if (f == null || f.gameObject.scene != scene) continue; + var bc = f.GetComponent(); + if (bc != null) AddBox(f.transform, bc.center, bc.size, cfg.farmMargin); + else AddRect(f.transform.position.x, f.transform.position.z, 6f, 5f, cfg.farmMargin); + } + + // ② 농사 타일 하나하나(1×1) — Farm 이 런타임에 만든다. 이중 안전. + var soils = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < soils.Length; i++) + { + var s = soils[i]; + if (s == null || s.gameObject.scene != scene) continue; + var p = s.transform.position; + AddRect(p.x, p.z, 1f, 1f, cfg.farmMargin); + } + + // ③ 건물·소품 — 섬 본체(Colliders/…)와 움직이는 것(플레이어·동물)은 뺀다. + var cols = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < cols.Length; i++) + { + var c = cols[i]; + if (c == null || c.gameObject.scene != scene) continue; + var tf = c.transform; + + // 섬 본체 박스 + 네 방향 벽(Colliders 아래) + if (tf.name == "Colliders" || (tf.parent != null && tf.parent.name == "Colliders")) continue; + // 물 + if (c.gameObject.layer == LayerMask.NameToLayer("Water")) continue; + // 움직이는 것 + if (c.GetComponentInParent() != null) continue; + if (c.attachedRigidbody != null && !c.attachedRigidbody.isKinematic) continue; + // 밭 트리거는 ①에서 이미 넣었다 + if (c.GetComponent() != null) continue; + if (c.GetComponent() != null) continue; + + var b = ColliderXZ(c); + if (b.width <= 0f || b.height <= 0f) continue; + if (b.width >= cfg.propMaxSize && b.height >= cfg.propMaxSize) continue; // 섬 크기 = 본체 + _blockers.Add(Inflate(b, cfg.propMargin)); + } + } + + bool Blocked(float x, float z) + { + for (int i = 0; i < _blockers.Count; i++) + { + var r = _blockers[i]; + if (x >= r.xMin && x <= r.xMax && z >= r.yMin && z <= r.yMax) return true; + } + return false; + } + + void AddBox(Transform tf, Vector3 center, Vector3 size, float margin) + { + // 콜라이더가 disabled 여도 안전하게 — bounds 대신 직접 계산한다. + var c = tf.TransformPoint(center); + var ex = tf.TransformVector(new Vector3(size.x * 0.5f, 0f, 0f)); + var ez = tf.TransformVector(new Vector3(0f, 0f, size.z * 0.5f)); + float hx = Mathf.Abs(ex.x) + Mathf.Abs(ez.x); + float hz = Mathf.Abs(ex.z) + Mathf.Abs(ez.z); + AddRect(c.x, c.z, hx * 2f, hz * 2f, margin); + } + + void AddRect(float cx, float cz, float w, float h, float margin) + { + _blockers.Add(new Rect(cx - w * 0.5f - margin, cz - h * 0.5f - margin, w + margin * 2f, h + margin * 2f)); + } + + static Rect Inflate(Rect r, float m) + { + return new Rect(r.xMin - m, r.yMin - m, r.width + m * 2f, r.height + m * 2f); + } + + static Rect ColliderXZ(Collider c) + { + var bx = c as BoxCollider; + if (bx != null) + { + var tf = c.transform; + var ctr = tf.TransformPoint(bx.center); + var ex = tf.TransformVector(new Vector3(bx.size.x * 0.5f, 0f, 0f)); + var ez = tf.TransformVector(new Vector3(0f, 0f, bx.size.z * 0.5f)); + float hx = Mathf.Abs(ex.x) + Mathf.Abs(ez.x); + float hz = Mathf.Abs(ex.z) + Mathf.Abs(ez.z); + return new Rect(ctr.x - hx, ctr.z - hz, hx * 2f, hz * 2f); + } + var sp = c as SphereCollider; + if (sp != null) + { + var tf = c.transform; + var ctr = tf.TransformPoint(sp.center); + float s = Mathf.Max(Mathf.Abs(tf.lossyScale.x), Mathf.Abs(tf.lossyScale.z)); + float r = sp.radius * s; + return new Rect(ctr.x - r, ctr.z - r, r * 2f, r * 2f); + } + var cp = c as CapsuleCollider; + if (cp != null) + { + var tf = c.transform; + var ctr = tf.TransformPoint(cp.center); + float s = Mathf.Max(Mathf.Abs(tf.lossyScale.x), Mathf.Abs(tf.lossyScale.z)); + float r = cp.radius * s; + return new Rect(ctr.x - r, ctr.z - r, r * 2f, r * 2f); + } + var b = c.bounds; // MeshCollider 등 + if (b.size.x <= 0f || b.size.z <= 0f) return new Rect(0f, 0f, 0f, 0f); + return new Rect(b.min.x, b.min.z, b.size.x, b.size.z); + } + + // ── 결정적 난수 ───────────────────────────────────────────────── + static uint Hash(uint x) + { + x ^= x >> 16; x *= 0x7feb352dU; + x ^= x >> 15; x *= 0x846ca68bU; + x ^= x >> 16; + return x; + } + + static float Frac(uint h, int k) + { + uint v = Hash(h + (uint)(k * 0x9E3779B9U)); + return (v & 0xFFFFFF) / 16777215f; + } + } +} diff --git a/Assets/WL/Look/Farm/WLIslandGrass.cs.meta b/Assets/WL/Look/Farm/WLIslandGrass.cs.meta new file mode 100644 index 000000000..d5c25eb9a --- /dev/null +++ b/Assets/WL/Look/Farm/WLIslandGrass.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b9a73101e801047499a875f06d78a87e \ No newline at end of file diff --git a/Assets/WL/Look/Farm/WLIslandLook.cs b/Assets/WL/Look/Farm/WLIslandLook.cs new file mode 100644 index 000000000..9aa8b991f --- /dev/null +++ b/Assets/WL/Look/Farm/WLIslandLook.cs @@ -0,0 +1,368 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLIslandLook.cs — **실제 게임이 로드하는 섬 씬**(FarmingIsland/Scenes/Level01)에 +// 데모 룩과 풀밭을 런타임으로 얹는다 (WL-816e · #816) +// +// ■ 왜 필요한가(발주서 §0) +// 816a 는 복사본 씬 `Assets/WL/Look/Farm/Scenes/WL_FarmLook.unity` 에만 룩을 저장했다. +// 게임은 `Level01` 을 로드하므로 PD 캡처의 섬은 여전히 **원본 색**(쨍한 파란 하늘·원색 초록)이었다. +// → 원본 씬을 고치지 않고(`Assets/FarmingIsland/**` 0줄) **로드 직후** 얹는다. +// +// ■ 걸리는 지점 +// `[RuntimeInitializeOnLoadMethod]` + `SceneManager.sceneLoaded` — 기존 파일 **0줄 수정**. +// (816d 의 `WLIslandBridge` 를 고치지 않는다 = 다른 worktree 와 충돌 0) +// +// ■ C8 되돌리기 — `WLIslandLookSettings.enabled_ = 0` → 아무것도 얹지 않는다. +// 씬·머티리얼·프리팹 **파일을 쓰지 않으므로** 복원 작업 자체가 없다. +// +// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 팝업). +// ───────────────────────────────────────────────────────────────────────────── + +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.SceneManagement; +using CryingSnow.FarmingIsland; +using FIIsland = CryingSnow.FarmingIsland.Island; +using FISoil = CryingSnow.FarmingIsland.Soil; + +namespace WL.Look.Farm +{ + /// 섬 씬에 룩·풀밭을 얹는 정적 진입점. + public static class WLIslandLook + { + public const string RunnerName = "~WLIslandLookRunner"; + public const string ReferenceLookName = "WL_ReferenceLook"; + + // ── 진단 ──────────────────────────────────────────────────────── + public static int SwappedRenderers, SwappedSlots, Rescans, LightingApplied, GrassRebuilds; + public static bool LookApplied, ReferenceLookApplied, GrassSpawned; + public static string LastLog = ""; + + static bool s_installed; + static WLIslandLookRunner s_runner; + static readonly HashSet s_done = new HashSet(); + static readonly HashSet s_hooked = new HashSet(); + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] + public static void Install() + { + if (s_installed) return; + var cfg = WLIslandLookSettings.Instance; + if (cfg == null || cfg.enabled_ == 0) return; + s_installed = true; + + SceneManager.sceneLoaded += OnSceneLoaded; + SceneManager.sceneUnloaded += OnSceneUnloaded; + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var sc = SceneManager.GetSceneAt(i); + if (sc.isLoaded) OnSceneLoaded(sc, LoadSceneMode.Additive); + } + } + + /// 에디터 프로브가 Play 중에 다시 걸 때 쓴다. + public static void ForceApply(Scene scene) + { + var cfg = WLIslandLookSettings.Instance; + if (cfg == null) return; + s_done.Clear(); + EnsureRunner(); + s_runner.StartCoroutine(Co_Apply(cfg, scene)); + } + + static void OnSceneLoaded(Scene scene, LoadSceneMode mode) + { + var cfg = WLIslandLookSettings.Instance; + if (cfg == null || cfg.enabled_ == 0) return; + if (!IsIslandScene(cfg, scene)) return; + EnsureRunner(); + s_runner.StartCoroutine(Co_Apply(cfg, scene)); + } + + static void OnSceneUnloaded(Scene scene) + { + s_done.Clear(); + s_hooked.Clear(); + LookApplied = false; GrassSpawned = false; ReferenceLookApplied = false; + } + + static bool IsIslandScene(WLIslandLookSettings cfg, Scene scene) + { + if (!scene.IsValid() || !scene.isLoaded) return false; + if (cfg.islandSceneNames != null) + for (int i = 0; i < cfg.islandSceneNames.Length; i++) + if (scene.name == cfg.islandSceneNames[i]) return true; + + // 이름을 몰라도 IslandManager 가 있으면 섬이다. + var roots = scene.GetRootGameObjects(); + for (int i = 0; i < roots.Length; i++) + if (roots[i].GetComponentInChildren(true) != null) return true; + return false; + } + + static void EnsureRunner() + { + if (s_runner != null) return; + var go = new GameObject(RunnerName); + go.hideFlags = HideFlags.DontSave; + Object.DontDestroyOnLoad(go); + s_runner = go.AddComponent(); + } + + // ───────────────────────────────────────────────────────────────── + // 본체 + // ───────────────────────────────────────────────────────────────── + static IEnumerator Co_Apply(WLIslandLookSettings cfg, Scene scene) + { + yield return null; // FI 매니저들의 Awake 를 기다린다 + + if (cfg.disableFiGraphicsManager != 0) DisableGraphicsManager(scene); + + if (cfg.applyLook != 0) + { + SwapMaterials(cfg, scene); + LookApplied = true; + } + if (cfg.applyLighting != 0) ApplyLighting(cfg, scene); + if (cfg.applyLook != 0 && cfg.applyReferenceLook != 0) ApplyReferenceLook(cfg, scene); + + if (cfg.grassEnabled != 0) SpawnGrass(cfg, scene); + + HookIslands(cfg, scene); + + cfg.Log("섬 룩 적용 — 렌더러 " + SwappedRenderers + " · 슬롯 " + SwappedSlots + + " · 조명 " + (cfg.applyLighting != 0 ? "데모" : "원본") + + " · ReferenceLook " + (ReferenceLookApplied ? "on" : "off") + + " · 풀 " + WLIslandGrass.LastLog); + + // 늦게 생기는 오브젝트(상인·작물·아이템)까지 다시 훑는다 + if (cfg.rescanAtSeconds != null) + { + float prev = 0f; + for (int i = 0; i < cfg.rescanAtSeconds.Length; i++) + { + float w = cfg.rescanAtSeconds[i] - prev; + prev = cfg.rescanAtSeconds[i]; + if (w > 0f) yield return new WaitForSeconds(w); + if (!scene.isLoaded) yield break; + if (cfg.applyLook != 0) { SwapMaterials(cfg, scene); Rescans++; } + HookIslands(cfg, scene); + } + } + + // 섬 확장 감시 — 새로 열린 타일에도 자동으로 풀이 깔린다 + if (cfg.rebuildPollSeconds > 0f) + { + int last = CountUnlocked(scene); + while (scene.isLoaded) + { + yield return new WaitForSeconds(cfg.rebuildPollSeconds); + if (!scene.isLoaded) yield break; + HookIslands(cfg, scene); + int now = CountUnlocked(scene); + if (now != last) + { + last = now; + if (cfg.applyLook != 0) SwapMaterials(cfg, scene); + RequestGrassRebuild(cfg); + } + } + } + } + + static int CountUnlocked(Scene scene) + { + var islands = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + int n = 0; + for (int i = 0; i < islands.Length; i++) + if (islands[i] != null && islands[i].IsUnlocked && islands[i].gameObject.activeInHierarchy) n++; + return n; + } + + /// FI `FIIsland.OnActivated`(public event)에 붙는다 — FI 코드 0줄. + static void HookIslands(WLIslandLookSettings cfg, Scene scene) + { + var islands = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < islands.Length; i++) + { + var isl = islands[i]; + if (isl == null || isl.gameObject.scene != scene) continue; + if (s_hooked.Contains(isl)) continue; + s_hooked.Add(isl); + isl.OnActivated += () => OnIslandActivated(cfg, isl); + } + } + + static void OnIslandActivated(WLIslandLookSettings cfg, FIIsland isl) + { + if (cfg == null || cfg.enabled_ == 0) return; + EnsureRunner(); + s_runner.StartCoroutine(Co_AfterActivate(cfg, isl)); + } + + static IEnumerator Co_AfterActivate(WLIslandLookSettings cfg, FIIsland isl) + { + // 확장 애니메이션(DOTween DOScale 1초) + 소품 배치가 끝나기를 기다린다 + yield return new WaitForSeconds(cfg.rebuildDelaySeconds); + if (isl == null) yield break; + if (cfg.applyLook != 0) SwapMaterials(cfg, isl.gameObject.scene); + RequestGrassRebuild(cfg); + } + + static void RequestGrassRebuild(WLIslandLookSettings cfg) + { + if (cfg.grassEnabled == 0) return; + var g = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (g == null) return; + g.Rebuild(); + GrassRebuilds++; + } + + // ───────────────────────────────────────────────────────────────── + // ① 머티리얼 — 원본을 **읽기만** 하고 렌더러의 sharedMaterials 만 바꾼다 + // ───────────────────────────────────────────────────────────────── + public static void SwapMaterials(WLIslandLookSettings cfg, Scene scene) + { + if (cfg.materialRemap == null || cfg.materialRemap.Length == 0) return; + + var rends = Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + for (int i = 0; i < rends.Length; i++) + { + var r = rends[i]; + if (r == null || r.gameObject.scene != scene) continue; + if (s_done.Contains(r)) continue; + if (r is ParticleSystemRenderer) continue; + + // 🔴 농사 타일은 코드가 `material.color` 로 직접 색을 칠한다(Soil.Initialize). + // Toon 셰이더에는 그 프로퍼티가 없어 갈아끼우면 밭 색이 죽는다 → 원본 유지. + if (cfg.skipSoilRenderers != 0 && r.GetComponent() != null) { s_done.Add(r); continue; } + + bool isIslandBody = r.GetComponent() != null; + + var mats = r.sharedMaterials; + bool changed = false; + for (int m = 0; m < mats.Length; m++) + { + var src = mats[m]; + if (src == null) continue; + Material dst = null; + + if (isIslandBody && cfg.islandTopMaterial != null) dst = cfg.islandTopMaterial; + else dst = Lookup(cfg, src); + + if (dst == null || dst == src) continue; + mats[m] = dst; changed = true; SwappedSlots++; + } + if (changed) { r.sharedMaterials = mats; SwappedRenderers++; } + s_done.Add(r); + } + } + + static Material Lookup(WLIslandLookSettings cfg, Material src) + { + for (int i = 0; i < cfg.materialRemap.Length; i++) + { + var e = cfg.materialRemap[i]; + if (e == null || e.enabled_ == 0 || e.from == null || e.to == null) continue; + if (e.from == src) return e.to; + // 런타임 인스턴스("Palette (Instance)")도 잡는다 + if (src.name.StartsWith(e.from.name) && src.shader == e.from.shader) return e.to; + } + return null; + } + + // ───────────────────────────────────────────────────────────────── + // ② 조명 · 앰비언트 · 스카이박스 (816a 실측 = 데모/아레나 값) + // ───────────────────────────────────────────────────────────────── + public static void ApplyLighting(WLIslandLookSettings cfg, Scene scene) + { + RenderSettings.ambientMode = AmbientMode.Skybox; + RenderSettings.ambientSkyColor = cfg.ambientSky; + RenderSettings.ambientEquatorColor = cfg.ambientEquator; + RenderSettings.ambientGroundColor = cfg.ambientGround; + RenderSettings.ambientLight = cfg.ambientSky; + RenderSettings.ambientIntensity = cfg.ambientIntensity; + if (cfg.fogOff != 0) RenderSettings.fog = false; + if (cfg.skybox != null) RenderSettings.skybox = cfg.skybox; + RenderSettings.defaultReflectionMode = DefaultReflectionMode.Skybox; + + var lights = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); + for (int i = 0; i < lights.Length; i++) + { + var l = lights[i]; + if (l == null || l.gameObject.scene != scene) continue; + if (l.type != LightType.Directional) continue; + l.color = cfg.dirLightColor; + l.intensity = cfg.dirLightIntensity; + if (cfg.dirLightSoftShadows != 0 && l.shadows != LightShadows.None) l.shadows = LightShadows.Soft; + RenderSettings.sun = l; + LightingApplied++; + } + } + + // ───────────────────────────────────────────────────────────────── + // ③ 아레나와 같은 외곽선·대비·무드 (씬 비의존 — 816a 실측) + // ───────────────────────────────────────────────────────────────── + public static void ApplyReferenceLook(WLIslandLookSettings cfg, Scene scene) + { + var existing = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (existing != null) + { + if (!existing.gameObject.activeSelf) existing.gameObject.SetActive(true); + ReferenceLookApplied = true; + return; + } + var go = new GameObject(ReferenceLookName); + SceneManager.MoveGameObjectToScene(go, scene); + go.AddComponent(); // OnEnable 이 적용한다 + ReferenceLookApplied = true; + } + + // ───────────────────────────────────────────────────────────────── + // ④ 풀밭 + // ───────────────────────────────────────────────────────────────── + public static void SpawnGrass(WLIslandLookSettings cfg, Scene scene) + { + var g = Object.FindFirstObjectByType(FindObjectsInactive.Include); + if (g == null) + { + var go = new GameObject(WLIslandGrass.ObjectName); + SceneManager.MoveGameObjectToScene(go, scene); + go.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); + g = go.AddComponent(); // OnEnable 이 만든다 + g.cfg = cfg; + } + else + { + g.cfg = cfg; + g.Rebuild(); + } + GrassSpawned = true; + } + + // ───────────────────────────────────────────────────────────────── + // ⑤ FI GraphicsManager — Play 중 FI 의 URP 에셋 **파일**을 고친다(816a 실측) + // 우리 활성 URP 가 아니라 화면 영향 0. `Assets/FarmingIsland/**` 0줄을 지키려면 꺼야 한다. + // ───────────────────────────────────────────────────────────────── + static void DisableGraphicsManager(Scene scene) + { + var roots = scene.GetRootGameObjects(); + for (int i = 0; i < roots.Length; i++) + { + var comps = roots[i].GetComponentsInChildren(true); + for (int c = 0; c < comps.Length; c++) + { + if (comps[c] == null) continue; + if (comps[c].GetType().Name != "GraphicsManager") continue; + comps[c].enabled = false; + comps[c].gameObject.SetActive(false); + } + } + } + } + + /// 코루틴 숙주. 씬을 넘어 살아남는다. + public sealed class WLIslandLookRunner : MonoBehaviour { } +} diff --git a/Assets/WL/Look/Farm/WLIslandLook.cs.meta b/Assets/WL/Look/Farm/WLIslandLook.cs.meta new file mode 100644 index 000000000..bbb192ca4 --- /dev/null +++ b/Assets/WL/Look/Farm/WLIslandLook.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d80b035318d07654cbf109047ed1d19b \ No newline at end of file diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs b/Assets/WL/Look/Farm/WLIslandLookSettings.cs new file mode 100644 index 000000000..de79d8488 --- /dev/null +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs @@ -0,0 +1,230 @@ +// ───────────────────────────────────────────────────────────────────────────── +// WLIslandLookSettings.cs — 실제 게임 섬(FarmingIsland Level01)에 **런타임으로** 얹는 +// ① 데모 룩(머티리얼·조명) ② 데모 풀밭의 단일 출처 (WL-816e · #816) +// +// PD 지시(2026-09-13) 「섬 배경이 단순 타일로만 되어있어. 기본 타일의 디자인을 +// 데모씬에서 사용한 풀밭처럼 꾸미고, 전체적인 비주얼을 맞춰줘」 +// +// ■ 왜 런타임인가 — 816a 는 **복사본 씬**(WL_FarmLook)에만 룩을 저장했다. +// 게임이 실제로 로드하는 것은 `Assets/FarmingIsland/Scenes/Level01.unity` 라 +// 그 씬은 아직 원본 색이다. 원본 씬을 고치지 않고(FI 0줄) 로드 직후 얹는다. +// +// ■ C8 되돌리기 — `enabled_ = 0` 이면 아무것도 얹지 않는다 = 지금 상태 100 %. +// 씬 파일·FI 에셋을 한 글자도 쓰지 않으므로 되돌리기에 복원 작업이 필요 없다. +// +// ■ C45 — 밀도·크기·제외 여백 같은 값은 전부 이 에셋에 있다(코드 상수 0). +// +// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 팝업). +// 🔴 어셈블리 주의: Assets/WL/Look/ 에 .asmdef 를 만들지 말 것(Assembly-CSharp 단일). +// ───────────────────────────────────────────────────────────────────────────── + +using System; +using UnityEngine; + +namespace WL.Look.Farm +{ + /// 원본 머티리얼 → 데모 톤 복사본. 한 줄 = 머티리얼 하나. + [Serializable] + public sealed class WLMatRemap + { + [Tooltip("0 이면 이 줄은 없는 것으로 친다.")] + public int enabled_ = 1; + + [Tooltip("FarmingIsland 원본 머티리얼(한 글자도 안 고친다 · 참조만).")] + public Material from; + + [Tooltip("816e/816a 가 만든 데모 톤 복사본.")] + public Material to; + } + + /// + /// 메시 하나의 **윗면(걸어다니는 면)** 을 XZ 격자 비트맵으로 구운 것. + /// FI 메시는 `isReadable = 0` 이라 런타임에 정점을 못 읽는다 → 에디터에서 구워 둔다. + /// (FI 에셋의 임포트 설정을 고치지 않기 위한 선택 — `Assets/FarmingIsland/**` 0줄) + /// + [Serializable] + public sealed class WLTileMask + { + public string meshName = ""; + [Tooltip("격자 해상도(res × res).")] public int res = 64; + [Tooltip("마스크가 덮는 반폭(m). 섬 타일 8 m → 4.")] public float half = 4f; + [Tooltip("res*res 비트를 담은 패킹 배열.")] public byte[] bits; + + public bool At(float lx, float lz) + { + if (bits == null || res <= 0) return false; + int cx = (int)((lx + half) / (half * 2f) * res); + int cz = (int)((lz + half) / (half * 2f) * res); + if (cx < 0 || cz < 0 || cx >= res || cz >= res) return false; + int i = cz * res + cx; + int b = i >> 3; + if (b >= bits.Length) return false; + return (bits[b] & (1 << (i & 7))) != 0; + } + + public void Set(int cx, int cz, bool v) + { + if (bits == null) bits = new byte[(res * res + 7) / 8]; + int i = cz * res + cx; + int b = i >> 3; + if (b >= bits.Length) return; + if (v) bits[b] |= (byte)(1 << (i & 7)); + else bits[b] &= (byte)~(1 << (i & 7)); + } + } + + /// 흩뿌릴 것 하나(풀 · 꽃 · 자갈). 데모 `InstancingSettings` 와 같은 값 구성. + [Serializable] + public sealed class WLScatterDef + { + public int enabled_ = 1; + [Tooltip("이름표(로그용).")] public string label = "풀"; + public Mesh mesh; + public Material material; + [Tooltip("같은 층 안에서의 가중치. 데모 = 풀 100 : 꽃 1.")] public float probability = 100f; + [Tooltip("메시 배율. 데모 = 0.35.")] public float scale = 0.35f; + [Tooltip("법선 방향으로 띄우는 거리(m). 데모 = 0.04.")] public float normalOffset = 0.04f; + [Tooltip("이 층의 밀도(개/m 한 변). 실제 밀도 = 값². 데모 1층 = 2.5(6.25개/㎡).")] + public float density = 2.5f; + } + + [CreateAssetMenu(fileName = "WLIslandLookSettings", menuName = "WL/Island Look Settings", order = 39)] + public sealed class WLIslandLookSettings : ScriptableObject + { + public const string ResourcesPath = "WL/WLIslandLookSettings"; + + static WLIslandLookSettings s_inst; + static bool s_tried; + + public static WLIslandLookSettings Instance + { + get + { + if (s_inst == null && !s_tried) { s_tried = true; s_inst = Resources.Load(ResourcesPath); } + return s_inst; + } + } + + public static void Invalidate() { s_inst = null; s_tried = false; } + + // ───────────────────────────────── 마스터 + [Header("되돌리기 (C8)")] + [Tooltip("0 이면 섬에 아무것도 얹지 않는다 = 지금(원본) 상태 100 %.")] + public int enabled_ = 1; + + [Tooltip("1 이면 적용 결과를 Console 에 남긴다.")] + public int verboseLog = 1; + + // ───────────────────────────────── §0 룩 + [Header("§0 — 실게임 섬(Level01)에 데모 룩 적용")] + [Tooltip("1 이면 섬 씬 로드 직후 머티리얼을 데모 톤 복사본으로 바꾼다(씬 파일 무변경 · 런타임만).")] + public int applyLook = 1; + + [Tooltip("1 이면 조명·앰비언트·스카이박스도 데모/아레나 값으로 맞춘다.")] + public int applyLighting = 1; + + [Tooltip("1 이면 아레나와 같은 `WL_ReferenceLook`(외곽선·대비·무드)을 섬에도 얹는다.")] + public int applyReferenceLook = 1; + + [Tooltip("머티리얼 교체 표. 한 줄 = 원본 → 복사본.")] + public WLMatRemap[] materialRemap = new WLMatRemap[0]; + + [Tooltip("섬 타일 윗면(걸어다니는 면)에 쓸 머티리얼. 비우면 위 표의 Island01 줄을 쓴다. " + + "체크무늬 두 초록을 데모 잔디색 한 가지로 합친 것.")] + public Material islandTopMaterial; + + [Tooltip("1 이면 농사 타일(Soil)은 원본 머티리얼 그대로 둔다(색을 코드가 직접 칠하는 곳이라 건드리면 안 된다).")] + public int skipSoilRenderers = 1; + + [Header("§0 — 조명 값 (816a 실측 = 데모/아레나와 동일)")] + public Color ambientSky = new Color(0.212f, 0.227f, 0.259f, 1f); + public Color ambientEquator = new Color(0.114f, 0.125f, 0.133f, 1f); + public Color ambientGround = new Color(0.047f, 0.043f, 0.035f, 1f); + public float ambientIntensity = 1f; + [Tooltip("빌트인 Default-Skybox. 비우면 스카이박스는 안 건드린다.")] + public Material skybox; + public Color dirLightColor = Color.white; + public float dirLightIntensity = 1f; + [Tooltip("1 = Soft(데모) · 0 = 원본(Hard) 유지.")] + public int dirLightSoftShadows = 1; + [Tooltip("1 이면 안개를 끈다(데모 = off).")] + public int fogOff = 1; + + [Tooltip("1 이면 FI `GraphicsManager` 를 끈다. 이 컴포넌트는 Play 중 FI 의 URP 에셋 파일에 " + + "renderScale 을 **직접 써 넣어** `Assets/FarmingIsland/**` 를 더럽힌다(816a 실측). " + + "우리 프로젝트의 활성 URP 에셋이 아니라 화면에는 영향 0.")] + public int disableFiGraphicsManager = 1; + + [Tooltip("런타임에 뒤늦게 생기는 오브젝트(상인·작물·아이템)까지 잡기 위해 다시 훑는 시각(초).")] + public float[] rescanAtSeconds = new float[] { 0.5f, 2f, 5f }; + + // ───────────────────────────────── §1 풀밭 + [Header("§1 — 섬 타일 위 풀밭 (데모와 같은 인스턴싱)")] + [Tooltip("0 이면 풀을 깔지 않는다.")] + public int grassEnabled = 1; + + [Tooltip("흩뿌릴 것들. 같은 density 를 가진 줄끼리 한 층으로 묶여 확률 분배된다(데모와 같은 방식).")] + public WLScatterDef[] scatter = new WLScatterDef[0]; + + [Tooltip("격자 흔들기(격자 칸 단위). 데모 = 0.5.")] + [Range(0f, 0.5f)] public float positionVariance = 0.5f; + + [Tooltip("크기 흔들기. 데모 = 0.2 → ×0.8~1.2.")] + [Range(0f, 0.9f)] public float scaleVariance = 0.2f; + + [Tooltip("1 이면 풀 하나하나를 Y 축으로 랜덤 회전한다(데모는 회전 0 = 빌보드라 불필요). " + + "빌보드 셰이더에는 영향이 없다.")] + public int randomYaw = 0; + + [Header("§1 — 풀을 깔지 않는 곳 (제외 규칙)")] + [Tooltip("섬 타일 가장자리에서 이만큼(m) 안쪽까지만 깐다. 물 위로 삐져나오지 않게.")] + public float edgeInset = 0.15f; + + [Tooltip("길(Road 메시)에서 이만큼(m) 떨어뜨린다.")] + public float roadMargin = 0.15f; + + [Tooltip("농사 타일(Farm 트리거 · Soil 1×1)에서 이만큼(m) 떨어뜨린다.")] + public float farmMargin = 0.35f; + + [Tooltip("건물·소품 콜라이더에서 이만큼(m) 떨어뜨린다.")] + public float propMargin = 0.05f; + + [Tooltip("이 크기(m) 이상인 콜라이더는 섬 본체로 보고 제외 대상에서 뺀다.")] + public float propMaxSize = 7.5f; + + [Header("§1 — 모바일 예산")] + [Tooltip("인스턴스 총개수 상한. 넘으면 밀도를 자동으로 낮춘다(0 = 무제한).")] + public int maxInstances = 40000; + + [Tooltip("섬이 확장되면 다시 깐다 — 이 주기(초)로 잠긴 섬이 열렸는지 본다. 0 이면 감시 안 함.")] + public float rebuildPollSeconds = 1f; + + [Tooltip("확장 애니메이션(DOTween 1초)이 끝나기를 기다리는 초.")] + public float rebuildDelaySeconds = 1.4f; + + // ───────────────────────────────── 구운 마스크 + [Header("§1 — 구운 윗면 마스크 (에디터에서 생성 · 손대지 말 것)")] + public WLTileMask[] tileMasks = new WLTileMask[0]; + public WLTileMask[] roadMasks = new WLTileMask[0]; + + // ───────────────────────────────── 섬 씬 판별 + [Header("어느 씬에 적용하는가")] + [Tooltip("이 이름의 씬에 적용한다(비워도 IslandManager 가 있으면 섬으로 본다).")] + public string[] islandSceneNames = new string[] { "Level01", "WL_FarmLook" }; + + public WLTileMask FindTileMask(string meshName) { return Find(tileMasks, meshName); } + public WLTileMask FindRoadMask(string meshName) { return Find(roadMasks, meshName); } + + static WLTileMask Find(WLTileMask[] a, string n) + { + if (a == null || string.IsNullOrEmpty(n)) return null; + // `MeshFilter.mesh` 로 읽으면 이름 뒤에 " Instance" 가 붙는다 — 떼고 비교한다. + int k = n.IndexOf(" Instance"); + if (k > 0) n = n.Substring(0, k); + for (int i = 0; i < a.Length; i++) if (a[i] != null && a[i].meshName == n) return a[i]; + return null; + } + + public void Log(string msg) { if (verboseLog != 0) Debug.Log("[WL-816e] " + msg); } + } +} diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs.meta b/Assets/WL/Look/Farm/WLIslandLookSettings.cs.meta new file mode 100644 index 000000000..680934c76 --- /dev/null +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3e03e9bd93ee2ab42b5e0e002e11a343 \ No newline at end of file