// ─────────────────────────────────────────────────────────────────────────────
// 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;
}
}
}