// ───────────────────────────────────────────────────────────────────────────── // WLDungeonGrassInstancer.cs — 던전 데모 인스턴서의 「물 밑 풀 빼기」판 (#851 · 2026-10-12) // // PD 「던전에 들어갈 경우 일부 잔디 이미지가 어색하게 보이는 현상(원형 지역에 패턴처럼 듬성듬성 나있는 지형)을 자연스럽게」. // 실측(WL_Dungeon01): 지형 스플랫이 전부 풀 층이라 데모 인스턴서가 **둥근 연못 바닥에도** 풀잎을 심었고, // 풀잎이 투명한 물 너머로 격자처럼 비쳤다(연못 바닥 −0.85 m · 수면 −0.10 m). // // ■ 하는 일 — 원본 `TerrainInstancesBehaviour` 의 생성 결과를 그대로 받아, 수면 사각형 안이면서 지형 높이가 // 수면보다 `margin` 이상 낮은 점(= 물에 잠긴 뿌리)만 뺀다. 원본 스크립트 0줄 · 씬 파일 0줄. // ■ 교체 방법 — `Replace` 가 정보를 정적 칸에 둔 뒤 AddComponent 한다 → Awake 에서 원본 값을 복사하고 OnEnable 에서 한 번에 심는다 // (AddComponent 직후 빈 값으로 OnEnable 이 돌면 데모 스크립트가 LogError → 오류 팝업 · 813z 규칙). // 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다. // ───────────────────────────────────────────────────────────────────────────── using System.Collections.Generic; using UnityEngine; using DemoInstancer = global::Environment.Instancing.TerrainInstancesBehaviour; using DemoSettings = global::Environment.Instancing.InstancingSettings; using DemoData = global::Environment.Instancing.InstanceData; namespace WL.Look.Farm { public sealed class WLDungeonGrassInstancer : DemoInstancer { /// 수면 하나 — 렌더러 바운즈의 xz 사각형 + 수면 높이. public struct Pool { public float minX, maxX, minZ, maxZ, surfaceY; } // ── 진단(프로브·로그가 읽는다) public static int LastKept, LastDropped; readonly List _pools = new List(); float _margin; struct Setup { public DemoInstancer src; public List pools; public float margin; } static Setup? s_next; /// 원본을 끄고(버퍼 해제는 호출 쪽) 같은 오브젝트에 이 판을 붙여 다시 심는다. 돌려주는 값 = 새 인스턴서(실패 시 null). public static WLDungeonGrassInstancer Replace(DemoInstancer src, List pools, float margin) { if (src == null || src.GetComponent() == null) return null; var have = src.GetComponent(); if (have != null) return have; s_next = new Setup { src = src, pools = pools, margin = margin }; try { return src.gameObject.AddComponent(); } // Awake(값 복사) → OnEnable(심기) finally { s_next = null; } } void Awake() { if (!s_next.HasValue) return; var s = s_next.Value; FirstLayer = s.src.FirstLayer; SecondLayer = s.src.SecondLayer; ThirdLayer = s.src.ThirdLayer; FourthLayer = s.src.FourthLayer; PositionVariance = s.src.PositionVariance; ScaleVariance = s.src.ScaleVariance; _pools.Clear(); if (s.pools != null) _pools.AddRange(s.pools); _margin = Mathf.Max(0f, s.margin); } public override Dictionary> GetInstanceData() { var data = base.GetInstanceData(); if (data == null || _pools.Count == 0) return data; var t = GetComponent(); if (t == null || t.terrainData == null) return data; // 원본은 위치에서 지형 바운즈 중심을 빼고, 그릴 때 「바운즈 중심 + 터레인 위치」를 더한다 → 월드 = 이 원점 + TRS 위치 Vector3 origin = t.transform.position + t.terrainData.bounds.center; int kept = 0, dropped = 0; var result = new Dictionary>(data.Count); foreach (var kv in data) { var list = kv.Value; var keep = new List(list.Count); for (int i = 0; i < list.Count; i++) { Vector4 c = list[i].TRS.GetColumn(3); if (Underwater(t, new Vector3(origin.x + c.x, 0f, origin.z + c.z))) { dropped++; continue; } keep.Add(list[i]); kept++; } result.Add(kv.Key, keep); } LastKept = kept; LastDropped = dropped; return result; } bool Underwater(Terrain t, Vector3 w) { for (int i = 0; i < _pools.Count; i++) { var p = _pools[i]; if (w.x < p.minX || w.x > p.maxX || w.z < p.minZ || w.z > p.maxZ) continue; float h = t.SampleHeight(w) + t.transform.position.y; if (h < p.surfaceY - _margin) return true; } return false; } } }