diff --git a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset index 2be69ddbc..ad0ddda1e 100644 --- a/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset +++ b/Assets/WL/Look/Farm/Resources/WL/WLIslandLookSettings.asset @@ -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 diff --git a/Assets/WL/Look/Farm/WLIslandGrass.cs b/Assets/WL/Look/Farm/WLIslandGrass.cs index c45e51f0b..96ef55851 100644 --- a/Assets/WL/Look/Farm/WLIslandGrass.cs +++ b/Assets/WL/Look/Farm/WLIslandGrass.cs @@ -54,6 +54,8 @@ namespace WL.Look.Farm Bounds _bounds = new Bounds(Vector3.zero, Vector3.one * 16f); readonly List _blockers = new List(128); + // 816za — 밭 자리만 따로. 반듯한 사각형이 아니라 「모서리 둥글게 + 경계 노이즈」로 판정한다. + readonly List _farmRounds = new List(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 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(); - 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(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; + } } } diff --git a/Assets/WL/Look/Farm/WLIslandLookSettings.cs b/Assets/WL/Look/Farm/WLIslandLookSettings.cs index 012854290..89def2543 100644 --- a/Assets/WL/Look/Farm/WLIslandLookSettings.cs +++ b/Assets/WL/Look/Farm/WLIslandLookSettings.cs @@ -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;