[WL-816za] 밭 자리 풀 경계 라운드 (#816)

밭(흙) 제외 사각형을 반듯한 사각형 대신 「모서리 반지름 + 경계 노이즈」로 판정해
풀이 흙 가장자리로 불규칙하게 파고들게 한다. 길·건물·소품 제외 규칙은 무변경.

- WLIslandLookSettings: farmEdgeRoundEnabled 1 / farmEdgeRadius 1.0 / farmEdgeNoise 0.4 / farmEdgeNoiseCell 0.5
- WLIslandGrass: 밭 rect 를 _farmRounds 로 분리 + InRoundedFarm(둥근 모서리 + 값 노이즈) 판정
- 라운드 모드에서 밭 안에 든 Soil 1x1 사각형은 빼서 깎아낸 모서리를 도로 메우지 않게 함
- farmEdgeRoundEnabled 0 이면 기존 동작 그대로

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
깃 관리자 2026-09-15 15:54:46 +09:00
parent 2799b311b6
commit dd5757bc4b
3 changed files with 90 additions and 5 deletions

View File

@ -109,6 +109,10 @@ MonoBehaviour:
edgeInset: 0.15
roadMargin: 0.15
farmMargin: 0.35
farmEdgeRoundEnabled: 1
farmEdgeRadius: 1
farmEdgeNoise: 0.4
farmEdgeNoiseCell: 0.5
propMargin: 0.05
propMaxSize: 7.5
maxInstances: 40000

View File

@ -54,6 +54,8 @@ namespace WL.Look.Farm
Bounds _bounds = new Bounds(Vector3.zero, Vector3.one * 16f);
readonly List<Rect> _blockers = new List<Rect>(128);
// 816za — 밭 자리만 따로. 반듯한 사각형이 아니라 「모서리 둥글게 + 경계 노이즈」로 판정한다.
readonly List<Rect> _farmRounds = new List<Rect>(8);
// ── 816o 캐시 — 한 번 뽑은 후보를 들고 있다가, 구름이 흐르면 「고르기」만 다시 한다 ──
struct Cand { public float x, z, y; public int def; public float sc; public float yaw; }
@ -570,6 +572,8 @@ namespace WL.Look.Farm
void BuildBlockers(List<TileRef> tiles)
{
_blockers.Clear();
_farmRounds.Clear();
bool round = cfg.farmEdgeRoundEnabled != 0 && cfg.farmEdgeRadius > 0f;
var scene = tiles.Count > 0 ? tiles[0].tf.gameObject.scene : default(UnityEngine.SceneManagement.Scene);
// ① 농사 — Farm 이 Awake 에서 만드는 BoxCollider(size = (length,2,width))가 밭의 정확한 넓이다.
@ -579,17 +583,21 @@ namespace WL.Look.Farm
var f = farms[i];
if (f == null || f.gameObject.scene != scene) continue;
var bc = f.GetComponent<BoxCollider>();
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);
Rect r = bc != null
? BoxRect(f.transform, bc.center, bc.size, cfg.farmMargin)
: MakeRect(f.transform.position.x, f.transform.position.z, 6f, 5f, cfg.farmMargin);
if (round) _farmRounds.Add(r); else _blockers.Add(r);
}
// ② 농사 타일 하나하나(1×1) — Farm 이 런타임에 만든다. 이중 안전.
// 라운드 모드에서는 ① 안에 든 타일을 빼야 한다. 안 그러면 반듯한 1×1 사각형들이 깎아낸 모서리를 도로 메운다.
var soils = Object.FindObjectsByType<FISoil>(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;
if (round && InAnyFarmRect(p.x, p.z)) continue;
AddRect(p.x, p.z, 1f, 1f, cfg.farmMargin);
}
@ -626,10 +634,47 @@ namespace WL.Look.Farm
var r = _blockers[i];
if (x >= r.xMin && x <= r.xMax && z >= r.yMin && z <= r.yMax) return true;
}
for (int i = 0; i < _farmRounds.Count; i++)
if (InRoundedFarm(_farmRounds[i], x, z)) return true;
return false;
}
void AddBox(Transform tf, Vector3 center, Vector3 size, float margin)
// 밭 사각형 안인가 — 타일을 어느 밭에 딸린 것으로 볼지 가르는 데만 쓴다(모서리·노이즈 무시).
bool InAnyFarmRect(float x, float z)
{
for (int i = 0; i < _farmRounds.Count; i++)
{
var r = _farmRounds[i];
if (x >= r.xMin && x <= r.xMax && z >= r.yMin && z <= r.yMax) return true;
}
return false;
}
// 816za — 밭 자리 판정. 네 모서리를 반지름만큼 깎고, 경계 전체를 노이즈로 흔든다.
// 노이즈가 () 면 흙이 물러나 풀이 파고들고, (+) 면 흙이 조금 넓어진다. 평균 0 이라 총량은 그대로다.
bool InRoundedFarm(Rect r, float x, float z)
{
float hx = r.width * 0.5f, hz = r.height * 0.5f;
float cx = r.xMin + hx, cz = r.yMin + hz;
if (cfg.farmEdgeNoise > 0f && cfg.farmEdgeNoiseCell > 0.0001f)
{
float n = (CellNoise(x / cfg.farmEdgeNoiseCell, z / cfg.farmEdgeNoiseCell) * 2f - 1f) * cfg.farmEdgeNoise;
hx += n; hz += n;
}
if (hx <= 0f || hz <= 0f) return false;
float dx = Mathf.Abs(x - cx), dz = Mathf.Abs(z - cz);
if (dx > hx || dz > hz) return false;
float rad = Mathf.Min(cfg.farmEdgeRadius, Mathf.Min(hx, hz));
if (rad <= 0f) return true;
float ox = dx - (hx - rad), oz = dz - (hz - rad);
if (ox <= 0f || oz <= 0f) return true; // 모서리 원호 밖 = 십자 몸통 = 안쪽
return ox * ox + oz * oz <= rad * rad;
}
static Rect BoxRect(Transform tf, Vector3 center, Vector3 size, float margin)
{
// 콜라이더가 disabled 여도 안전하게 — bounds 대신 직접 계산한다.
var c = tf.TransformPoint(center);
@ -637,12 +682,17 @@ namespace WL.Look.Farm
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);
return MakeRect(c.x, c.z, hx * 2f, hz * 2f, margin);
}
static Rect MakeRect(float cx, float cz, float w, float h, float margin)
{
return new Rect(cx - w * 0.5f - margin, cz - h * 0.5f - margin, w + margin * 2f, h + margin * 2f);
}
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));
_blockers.Add(MakeRect(cx, cz, w, h, margin));
}
static Rect Inflate(Rect r, float m)
@ -700,5 +750,23 @@ namespace WL.Look.Farm
uint v = Hash(h + (uint)(k * 0x9E3779B9U));
return (v & 0xFFFFFF) / 16777215f;
}
// 816za — 셀 격자 값 노이즈 [0,1]. 셀 네 귀퉁이 난수를 부드럽게 섞어 경계를 들쭉날쭉하게 만든다.
static float CellNoise(float fx, float fz)
{
int x0 = Mathf.FloorToInt(fx), z0 = Mathf.FloorToInt(fz);
float tx = fx - x0, tz = fz - z0;
tx = tx * tx * (3f - 2f * tx);
tz = tz * tz * (3f - 2f * tz);
float a = CellRand(x0, z0), b = CellRand(x0 + 1, z0);
float c = CellRand(x0, z0 + 1), d = CellRand(x0 + 1, z0 + 1);
return Mathf.Lerp(Mathf.Lerp(a, b, tx), Mathf.Lerp(c, d, tx), tz);
}
static float CellRand(int x, int z)
{
uint h = Hash(unchecked((uint)x * 73856093U) ^ unchecked((uint)z * 19349663U) ^ 0x9E3779B9U);
return (h & 0xFFFFFF) / 16777215f;
}
}
}

View File

@ -263,6 +263,19 @@ namespace WL.Look.Farm
[Tooltip("농사 타일(Farm 트리거 · Soil 1×1)에서 이만큼(m) 떨어뜨린다.")]
public float farmMargin = 0.35f;
[Tooltip("1 이면 밭 자리 제외 사각형의 네 모서리를 깎고 경계를 노이즈로 흔들어 " +
"풀이 흙 가장자리로 파고들게 한다. 0 이면 예전처럼 반듯한 사각형.")]
public int farmEdgeRoundEnabled = 1;
[Tooltip("밭 자리 모서리를 깎는 반지름(m). 클수록 둥글게 읽힌다.")]
public float farmEdgeRadius = 1.0f;
[Tooltip("밭 자리 경계를 흔드는 진폭(m). 이만큼 풀이 파고들거나 물러난다.")]
public float farmEdgeNoise = 0.4f;
[Tooltip("경계 노이즈의 셀 크기(m). 작을수록 잘게 들쭉날쭉해진다.")]
public float farmEdgeNoiseCell = 0.5f;
[Tooltip("건물·소품 콜라이더에서 이만큼(m) 떨어뜨린다.")]
public float propMargin = 0.05f;