412 lines
23 KiB
C#
412 lines
23 KiB
C#
|
|
using System.Collections.Generic;
|
||
|
|
using System.Linq;
|
||
|
|
|
||
|
|
// PD 지시 #771 2단계 A — Demo_01 구조 실측 + 플레이 영역(200x200 m) 후보 선정
|
||
|
|
// 실행: unity command run_script --file AgentScripts/staging/WL_Nature2/NatureArea.cs --entry NatureArea.Run --timeout 300
|
||
|
|
// 씬은 열기만 하고 절대 저장하지 않는다 (ClearDirty 로 dirty 해제).
|
||
|
|
public static class NatureArea
|
||
|
|
{
|
||
|
|
const string SCENE = "Assets/LMHPOLY/Low Poly Nature Bundle/_Demo Scenes/Demo_01.unity";
|
||
|
|
const string OUT = @"E:\NerdNavis\nn_himminji\AgentScripts\staging\WL_Nature2\out";
|
||
|
|
|
||
|
|
const float HALF = 100f; // 플레이 영역 반폭 (200 x 200 m)
|
||
|
|
const float SLOPE_LIMIT = 30f; // 소품 배치 경사 상한 (발주)
|
||
|
|
const float WALK_LIMIT = 45f; // 보행 판정 경사 상한 (NavMesh agent slope)
|
||
|
|
|
||
|
|
static System.Text.StringBuilder sb;
|
||
|
|
static void L(string s) { sb.AppendLine(s); }
|
||
|
|
|
||
|
|
public static object Run()
|
||
|
|
{
|
||
|
|
System.IO.Directory.CreateDirectory(OUT);
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
if (UnityEngine.Application.isPlaying) return "PLAYING - 중단";
|
||
|
|
|
||
|
|
var setup = UnityEditor.SceneManagement.EditorSceneManager.GetSceneManagerSetup();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var sc = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(SCENE, UnityEditor.SceneManagement.OpenSceneMode.Single);
|
||
|
|
L("== SCENE " + SCENE + " ==");
|
||
|
|
|
||
|
|
// ── 1. 루트 구조 + 벤더 스크립트 실측 ──────────────────
|
||
|
|
var roots = sc.GetRootGameObjects();
|
||
|
|
L("루트 오브젝트 " + roots.Length + "개");
|
||
|
|
var vendorScripts = new Dictionary<string, int>();
|
||
|
|
int camCount = 0, lightCount = 0;
|
||
|
|
foreach (var r in roots)
|
||
|
|
{
|
||
|
|
var mrs = r.GetComponentsInChildren<UnityEngine.Renderer>(true).Length;
|
||
|
|
var cams = r.GetComponentsInChildren<UnityEngine.Camera>(true);
|
||
|
|
var lts = r.GetComponentsInChildren<UnityEngine.Light>(true);
|
||
|
|
camCount += cams.Length; lightCount += lts.Length;
|
||
|
|
var comps = new List<string>();
|
||
|
|
foreach (var c in r.GetComponents<UnityEngine.Component>())
|
||
|
|
if (c != null && !(c is UnityEngine.Transform)) comps.Add(c.GetType().Name);
|
||
|
|
L(string.Format(" [{0,-34}] children={1,-4} renderers={2,-4} cam={3} light={4} comps=[{5}]",
|
||
|
|
r.name, CountDeep(r.transform), mrs, cams.Length, lts.Length, string.Join(",", comps)));
|
||
|
|
foreach (var mb in r.GetComponentsInChildren<UnityEngine.MonoBehaviour>(true))
|
||
|
|
{
|
||
|
|
if (mb == null) continue;
|
||
|
|
var t = mb.GetType();
|
||
|
|
var asm = t.Assembly.GetName().Name;
|
||
|
|
if (asm.StartsWith("Unity") || asm.StartsWith("com.unity")) continue;
|
||
|
|
string key = t.FullName + " (" + Path(mb.transform) + ")";
|
||
|
|
if (!vendorScripts.ContainsKey(t.FullName)) vendorScripts[t.FullName] = 0;
|
||
|
|
vendorScripts[t.FullName]++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
L("카메라 " + camCount + "대 · 라이트 " + lightCount + "개");
|
||
|
|
L("== 씬에 붙은 게임 스크립트 (제거 후보) ==");
|
||
|
|
if (vendorScripts.Count == 0) L(" (없음)");
|
||
|
|
foreach (var kv in vendorScripts.OrderByDescending(k => k.Value)) L(" [" + kv.Value + "] " + kv.Key);
|
||
|
|
|
||
|
|
// 라이트 상세
|
||
|
|
L("== 라이트 상세 ==");
|
||
|
|
foreach (var r in roots)
|
||
|
|
foreach (var lt in r.GetComponentsInChildren<UnityEngine.Light>(true))
|
||
|
|
L(" " + Path(lt.transform) + " type=" + lt.type + " color=" + lt.color + " intensity=" + lt.intensity
|
||
|
|
+ " shadows=" + lt.shadows + " euler=" + lt.transform.rotation.eulerAngles.ToString("F2")
|
||
|
|
+ " active=" + lt.gameObject.activeInHierarchy);
|
||
|
|
|
||
|
|
// ── 2. 수면 실측 ──────────────────────────────────────
|
||
|
|
var water = new List<UnityEngine.Bounds>();
|
||
|
|
L("== 수역 렌더러 ==");
|
||
|
|
foreach (var r in roots)
|
||
|
|
foreach (var rd in r.GetComponentsInChildren<UnityEngine.Renderer>(true))
|
||
|
|
{
|
||
|
|
if (!IsWater(rd)) continue;
|
||
|
|
water.Add(rd.bounds);
|
||
|
|
L(" " + rd.name + " center=" + rd.bounds.center.ToString("F1") + " size=" + rd.bounds.size.ToString("F1")
|
||
|
|
+ " topY=" + rd.bounds.max.y.ToString("F2"));
|
||
|
|
}
|
||
|
|
L("수역 " + water.Count + "개");
|
||
|
|
|
||
|
|
// ── 3. 전체 바운즈 ────────────────────────────────────
|
||
|
|
bool has = false; var b = new UnityEngine.Bounds();
|
||
|
|
foreach (var r in roots)
|
||
|
|
foreach (var mr in r.GetComponentsInChildren<UnityEngine.MeshRenderer>(false))
|
||
|
|
{ if (!has) { b = mr.bounds; has = true; } else b.Encapsulate(mr.bounds); }
|
||
|
|
L("bounds center=" + b.center.ToString("F1") + " size=" + b.size.ToString("F1"));
|
||
|
|
CollectProps(roots);
|
||
|
|
|
||
|
|
// ── 4. 후보 중심 스캔 (섬 전역 40 m 격자) ─────────────
|
||
|
|
L("");
|
||
|
|
L("== 후보 중심 스캔 (40 m 격자 · 각 후보 200x200 m 를 20 m 간격 11x11 로 평가) ==");
|
||
|
|
var cands = new List<Cand>();
|
||
|
|
for (float cx = b.min.x + HALF; cx <= b.max.x - HALF; cx += 40f)
|
||
|
|
for (float cz = b.min.z + HALF; cz <= b.max.z - HALF; cz += 40f)
|
||
|
|
{
|
||
|
|
var c = Evaluate(cx, cz, b, water, 20f);
|
||
|
|
if (c != null) cands.Add(c);
|
||
|
|
}
|
||
|
|
var ranked = cands.OrderByDescending(c => c.Score()).Take(8).ToList();
|
||
|
|
L(string.Format("{0,-22} {1,6} {2,7} {3,7} {4,7} {5,8} {6,8} {7,7}",
|
||
|
|
"center", "walk%", "flat%", "waterV", "yRange", "centerY", "score", "nearWtr"));
|
||
|
|
foreach (var c in ranked) L(c.Line());
|
||
|
|
|
||
|
|
// PM 후보 (1단계 PickPlayerSpot 결과)
|
||
|
|
L("");
|
||
|
|
L("== PM 지정 후보 (209, -148) 정밀 평가 (5 m 간격 41x41) ==");
|
||
|
|
var pm = Evaluate(209f, -148f, b, water, 5f);
|
||
|
|
if (pm != null) { L(pm.Line()); L(pm.Detail()); }
|
||
|
|
|
||
|
|
// 상위 후보 정밀 평가
|
||
|
|
for (int i = 0; i < System.Math.Min(3, ranked.Count); i++)
|
||
|
|
{
|
||
|
|
var fine = Evaluate(ranked[i].cx, ranked[i].cz, b, water, 5f);
|
||
|
|
L("");
|
||
|
|
L("== 상위 후보 #" + (i + 1) + " 정밀 평가 (5 m 간격) ==");
|
||
|
|
if (fine != null) { L(fine.Line()); L(fine.Detail()); }
|
||
|
|
}
|
||
|
|
|
||
|
|
ClearDirty();
|
||
|
|
}
|
||
|
|
catch (System.Exception e) { L("EXCEPTION " + e); }
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
try { if (setup != null && setup.Length > 0) UnityEditor.SceneManagement.EditorSceneManager.RestoreSceneManagerSetup(setup); } catch { }
|
||
|
|
}
|
||
|
|
System.IO.File.WriteAllText(System.IO.Path.Combine(OUT, "A1_area.txt"), sb.ToString(), new System.Text.UTF8Encoding(true));
|
||
|
|
return "wrote A1_area.txt (" + sb.Length + " chars)";
|
||
|
|
}
|
||
|
|
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
// 시작 지점 + 플레이 영역을 함께 고른다.
|
||
|
|
// 시작 지점 조건: 경사<=15° · 수중 아님 · 머리 위 12 m 개방 · 반경 12 m 개활지가 평탄
|
||
|
|
// · 수면까지 6~35 m (강가) · 지형 가장자리에서 half+여유 안쪽
|
||
|
|
// 실행: --entry NatureArea.Spot --args '[100.0]'
|
||
|
|
// ═══════════════════════════════════════════════════════════
|
||
|
|
public static object Spot(float half)
|
||
|
|
{
|
||
|
|
System.IO.Directory.CreateDirectory(OUT);
|
||
|
|
sb = new System.Text.StringBuilder();
|
||
|
|
if (UnityEngine.Application.isPlaying) return "PLAYING - 중단";
|
||
|
|
var setup = UnityEditor.SceneManagement.EditorSceneManager.GetSceneManagerSetup();
|
||
|
|
string res = "?";
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var sc = UnityEditor.SceneManagement.EditorSceneManager.OpenScene(SCENE, UnityEditor.SceneManagement.OpenSceneMode.Single);
|
||
|
|
var roots = sc.GetRootGameObjects();
|
||
|
|
CollectProps(roots);
|
||
|
|
|
||
|
|
// 지형+수역만의 바운즈 (원경 산·구름 제외)
|
||
|
|
bool has = false; var tb = new UnityEngine.Bounds();
|
||
|
|
var water = new List<UnityEngine.Bounds>();
|
||
|
|
foreach (var r in roots)
|
||
|
|
{
|
||
|
|
bool isTerrain = r.name == "_Terrain" || r.name == "_Water";
|
||
|
|
foreach (var mr in r.GetComponentsInChildren<UnityEngine.MeshRenderer>(true))
|
||
|
|
{
|
||
|
|
if (IsWater(mr)) water.Add(mr.bounds);
|
||
|
|
if (!isTerrain) continue;
|
||
|
|
if (!has) { tb = mr.bounds; has = true; } else tb.Encapsulate(mr.bounds);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
L("지형(_Terrain+_Water) 바운즈 center=" + tb.center.ToString("F1") + " size=" + tb.size.ToString("F1"));
|
||
|
|
L("수역 " + water.Count + "개");
|
||
|
|
|
||
|
|
// 전체 바운즈 (레이 시작 높이용)
|
||
|
|
var full = new UnityEngine.Bounds(); bool hf = false;
|
||
|
|
foreach (var r in roots)
|
||
|
|
foreach (var mr in r.GetComponentsInChildren<UnityEngine.MeshRenderer>(true))
|
||
|
|
{ if (!hf) { full = mr.bounds; hf = true; } else full.Encapsulate(mr.bounds); }
|
||
|
|
|
||
|
|
float cosStart = UnityEngine.Mathf.Cos(15f * UnityEngine.Mathf.Deg2Rad);
|
||
|
|
var cands = new List<Spotc>();
|
||
|
|
int nTried = 0, rejGround = 0, rejSlope = 0, rejWater = 0, rejHead = 0, rejClear = 0, rejBand = 0, rejEdge = 0;
|
||
|
|
for (float x = tb.min.x + half * 0.5f; x <= tb.max.x - half * 0.5f; x += 5f)
|
||
|
|
for (float z = tb.min.z + half * 0.5f; z <= tb.max.z - half * 0.5f; z += 5f)
|
||
|
|
{
|
||
|
|
nTried++;
|
||
|
|
UnityEngine.RaycastHit h;
|
||
|
|
if (!UnityEngine.Physics.Raycast(new UnityEngine.Vector3(x, full.max.y + 100f, z), UnityEngine.Vector3.down, out h, full.size.y + 500f)) { rejGround++; continue; }
|
||
|
|
if (h.normal.y < cosStart) { rejSlope++; continue; }
|
||
|
|
if (InWater(h.point, water)) { rejWater++; continue; }
|
||
|
|
if (UnityEngine.Physics.Raycast(h.point + UnityEngine.Vector3.up * 0.5f, UnityEngine.Vector3.up, 12f)) { rejHead++; continue; }
|
||
|
|
|
||
|
|
// 반경 12 m 개활지 평탄도
|
||
|
|
float ymin = h.point.y, ymax = h.point.y; int ok = 0;
|
||
|
|
for (int k = 0; k < 8; k++)
|
||
|
|
{
|
||
|
|
float a = k * 45f * UnityEngine.Mathf.Deg2Rad;
|
||
|
|
var q = h.point + new UnityEngine.Vector3(UnityEngine.Mathf.Cos(a) * 12f, 0f, UnityEngine.Mathf.Sin(a) * 12f);
|
||
|
|
UnityEngine.RaycastHit h2;
|
||
|
|
if (!UnityEngine.Physics.Raycast(new UnityEngine.Vector3(q.x, full.max.y + 100f, q.z), UnityEngine.Vector3.down, out h2, full.size.y + 500f)) continue;
|
||
|
|
if (InWater(h2.point, water)) continue;
|
||
|
|
ok++;
|
||
|
|
if (h2.point.y < ymin) ymin = h2.point.y;
|
||
|
|
if (h2.point.y > ymax) ymax = h2.point.y;
|
||
|
|
}
|
||
|
|
if (ok < 8 || (ymax - ymin) > 2.5f) { rejClear++; continue; }
|
||
|
|
|
||
|
|
float dW = 9999f;
|
||
|
|
for (int i = 0; i < water.Count; i++) dW = UnityEngine.Mathf.Min(dW, UnityEngine.Mathf.Sqrt(SqDistXZ(h.point, water[i])));
|
||
|
|
if (dW < 6f || dW > 35f) { rejBand++; continue; }
|
||
|
|
|
||
|
|
var area = Evaluate(x, z, full, water, 20f);
|
||
|
|
if (area == null || area.Score() < 0f) { rejEdge++; continue; }
|
||
|
|
|
||
|
|
var s = new Spotc();
|
||
|
|
s.p = h.point; s.dWater = dW; s.drop = ymax - ymin; s.area = area;
|
||
|
|
cands.Add(s);
|
||
|
|
}
|
||
|
|
L("시작 지점 격자 " + nTried + "점(5 m) → 후보 " + cands.Count
|
||
|
|
+ " (제외: 지형없음 " + rejGround + " · 경사 " + rejSlope + " · 수중 " + rejWater
|
||
|
|
+ " · 머리막힘 " + rejHead + " · 개활지불량 " + rejClear + " · 수면거리밖 " + rejBand + " · 영역부적합 " + rejEdge + ")");
|
||
|
|
|
||
|
|
cands.Sort((a, b2) => b2.Total().CompareTo(a.Total()));
|
||
|
|
L("");
|
||
|
|
L(string.Format("{0,-26} {1,7} {2,6} {3,6} {4,6} {5,6} {6,5} {7,5} {8,7}",
|
||
|
|
"start(x,y,z)", "수면m", "단차", "walk%", "flat%", "water%", "나무", "바위", "총점"));
|
||
|
|
for (int i = 0; i < System.Math.Min(10, cands.Count); i++) L(cands[i].Line());
|
||
|
|
|
||
|
|
if (cands.Count > 0)
|
||
|
|
{
|
||
|
|
var b0 = cands[0];
|
||
|
|
L("");
|
||
|
|
L("== 채택 후보 상세 ==");
|
||
|
|
L(" 시작 지점 = " + b0.p.ToString("F3"));
|
||
|
|
L(" 플레이 영역 = 중심 (" + b0.p.x.ToString("F1") + ", " + b0.p.z.ToString("F1") + ") ± " + half + " m (" + (half * 2) + " x " + (half * 2) + " m)");
|
||
|
|
L(" 최근접 수면 " + b0.dWater.ToString("F1") + " m · 개활지 12 m 단차 " + b0.drop.ToString("F2") + " m");
|
||
|
|
L(" 영역 보행가능 " + (b0.area.walkPct * 100f).ToString("F1") + "% · 완경사(<=30°) " + (b0.area.flatPct * 100f).ToString("F1")
|
||
|
|
+ "% · 수면셀 " + b0.area.waterCells + "/" + b0.area.samples
|
||
|
|
+ " · 기존 나무 " + b0.area.treeNear + " 바위 " + b0.area.rockNear);
|
||
|
|
L(" 영역 고도 " + b0.area.yMin.ToString("F1") + " ~ " + b0.area.yMax.ToString("F1") + " m");
|
||
|
|
res = string.Format("start=({0:F2},{1:F2},{2:F2}) center=({3:F1},{4:F1}) dWater={5:F1} walk={6:F1}% water={7} trees={8} rocks={9}",
|
||
|
|
b0.p.x, b0.p.y, b0.p.z, b0.p.x, b0.p.z, b0.dWater, b0.area.walkPct * 100f, b0.area.waterCells, b0.area.treeNear, b0.area.rockNear);
|
||
|
|
}
|
||
|
|
else res = "후보 없음";
|
||
|
|
ClearDirty();
|
||
|
|
}
|
||
|
|
catch (System.Exception e) { L("EXCEPTION " + e); res = "EXCEPTION " + e.Message; }
|
||
|
|
finally { try { if (setup != null && setup.Length > 0) UnityEditor.SceneManagement.EditorSceneManager.RestoreSceneManagerSetup(setup); } catch { } }
|
||
|
|
System.IO.File.WriteAllText(System.IO.Path.Combine(OUT, "A2_spot.txt"), sb.ToString(), new System.Text.UTF8Encoding(true));
|
||
|
|
return res;
|
||
|
|
}
|
||
|
|
|
||
|
|
class Spotc
|
||
|
|
{
|
||
|
|
public UnityEngine.Vector3 p;
|
||
|
|
public float dWater, drop;
|
||
|
|
public Cand area;
|
||
|
|
public float Total()
|
||
|
|
{
|
||
|
|
float s = area.Score();
|
||
|
|
s += (1f - UnityEngine.Mathf.Abs(dWater - 18f) / 20f) * 25f; // 수면에서 18 m 근처가 최적
|
||
|
|
s += (2.5f - drop) * 8f; // 개활지가 평탄할수록
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
public string Line()
|
||
|
|
{
|
||
|
|
return string.Format("({0,7:F1},{1,6:F2},{2,7:F1}) {3,6:F1} {4,6:F2} {5,6:F1} {6,6:F1} {7,6:F1} {8,5} {9,5} {10,7:F1}",
|
||
|
|
p.x, p.y, p.z, dWater, drop, area.walkPct * 100f, area.flatPct * 100f,
|
||
|
|
area.samples == 0 ? 0f : area.waterCells * 100f / area.samples, area.treeNear, area.rockNear, Total());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 후보 평가 ────────────────────────────────────────────────
|
||
|
|
class Cand
|
||
|
|
{
|
||
|
|
public float cx, cz, walkPct, flatPct, yMin, yMax, centerY, nearWater;
|
||
|
|
public int waterCells, samples, treeNear, rockNear;
|
||
|
|
// 발주 요구 = "물·숲·개활지가 함께 보이는 곳". 넓은 맨땅 구석이 1위가 되지 않도록
|
||
|
|
// 보행 가능 비율은 하한(45%)만 요구하고, 물·기존 소품·완만함에 배점을 나눈다.
|
||
|
|
public float Score()
|
||
|
|
{
|
||
|
|
if (walkPct < 0.45f) return -1000f + walkPct * 100f; // 플레이 영역으로 부적합
|
||
|
|
float s = walkPct * 60f + flatPct * 40f;
|
||
|
|
float wr = samples == 0 ? 0f : waterCells / (float)samples;
|
||
|
|
if (wr >= 0.03f && wr <= 0.32f) s += 55f; // 물이 보이되 영역을 덮지 않음
|
||
|
|
else if (wr > 0.32f) s -= (wr - 0.32f) * 200f; // 절반이 물이면 감점
|
||
|
|
else s -= 20f; // 물이 전혀 없으면 감점
|
||
|
|
s += UnityEngine.Mathf.Min(treeNear, 12) * 2.5f; // 기존 나무 (숲 씨앗)
|
||
|
|
s += UnityEngine.Mathf.Min(rockNear, 15) * 1.2f; // 기존 바위
|
||
|
|
s -= UnityEngine.Mathf.Clamp((yMax - yMin) - 12f, 0f, 60f) * 1.5f; // 12 m 넘는 고저차 감점
|
||
|
|
return s;
|
||
|
|
}
|
||
|
|
public string Line()
|
||
|
|
{
|
||
|
|
return string.Format("({0,7:F0},{1,7:F0}) {2,5:F1} {3,6:F1} {4,7} {5,7:F1} {6,8:F2} {7,8:F1} {8,7:F1}",
|
||
|
|
cx, cz, walkPct * 100f, flatPct * 100f, waterCells, yMax - yMin, centerY, Score(), nearWater);
|
||
|
|
}
|
||
|
|
public string Detail()
|
||
|
|
{
|
||
|
|
return " 표본=" + samples + " 중심고도=" + centerY.ToString("F2")
|
||
|
|
+ " y=" + yMin.ToString("F1") + "~" + yMax.ToString("F1")
|
||
|
|
+ " 수면셀=" + waterCells + " 최근접수면=" + nearWater.ToString("F1") + "m"
|
||
|
|
+ " 영역내 나무=" + treeNear + " 바위=" + rockNear;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 씬 안 기존 프리팹 인스턴스의 위치·분류 (후보 평가에서 재사용)
|
||
|
|
static List<KeyValuePair<UnityEngine.Vector3, string>> s_props;
|
||
|
|
static void CollectProps(UnityEngine.GameObject[] roots)
|
||
|
|
{
|
||
|
|
s_props = new List<KeyValuePair<UnityEngine.Vector3, string>>();
|
||
|
|
foreach (var r in roots)
|
||
|
|
foreach (var t in r.GetComponentsInChildren<UnityEngine.Transform>(true))
|
||
|
|
{
|
||
|
|
if (!UnityEditor.PrefabUtility.IsAnyPrefabInstanceRoot(t.gameObject)) continue;
|
||
|
|
var src = UnityEditor.PrefabUtility.GetCorrespondingObjectFromOriginalSource(t.gameObject);
|
||
|
|
var p = src == null ? "" : UnityEditor.AssetDatabase.GetAssetPath(src);
|
||
|
|
string k = p.Contains("/Trees/") ? "tree"
|
||
|
|
: p.Contains("/Rocks/") ? "rock"
|
||
|
|
: p.Contains("/Vegetation/") ? "veg"
|
||
|
|
: p.Contains("/Modular Terrain/") ? "terrain" : "other";
|
||
|
|
s_props.Add(new KeyValuePair<UnityEngine.Vector3, string>(t.position, k));
|
||
|
|
}
|
||
|
|
L("프리팹 인스턴스 " + s_props.Count + "개 수집");
|
||
|
|
}
|
||
|
|
|
||
|
|
static Cand Evaluate(float cx, float cz, UnityEngine.Bounds b, List<UnityEngine.Bounds> water, float step)
|
||
|
|
{
|
||
|
|
var c = new Cand(); c.cx = cx; c.cz = cz;
|
||
|
|
if (s_props != null)
|
||
|
|
for (int i = 0; i < s_props.Count; i++)
|
||
|
|
{
|
||
|
|
var p = s_props[i].Key;
|
||
|
|
if (UnityEngine.Mathf.Abs(p.x - cx) > HALF || UnityEngine.Mathf.Abs(p.z - cz) > HALF) continue;
|
||
|
|
if (s_props[i].Value == "tree") c.treeNear++;
|
||
|
|
else if (s_props[i].Value == "rock") c.rockNear++;
|
||
|
|
}
|
||
|
|
int walk = 0, flat = 0, hit = 0, n = 0, wcell = 0;
|
||
|
|
float ymin = float.MaxValue, ymax = float.MinValue;
|
||
|
|
float cosWalk = UnityEngine.Mathf.Cos(WALK_LIMIT * UnityEngine.Mathf.Deg2Rad);
|
||
|
|
float cosFlat = UnityEngine.Mathf.Cos(SLOPE_LIMIT * UnityEngine.Mathf.Deg2Rad);
|
||
|
|
for (float x = cx - HALF; x <= cx + HALF + 0.01f; x += step)
|
||
|
|
for (float z = cz - HALF; z <= cz + HALF + 0.01f; z += step)
|
||
|
|
{
|
||
|
|
n++;
|
||
|
|
UnityEngine.RaycastHit h;
|
||
|
|
if (!UnityEngine.Physics.Raycast(new UnityEngine.Vector3(x, b.max.y + 100f, z), UnityEngine.Vector3.down, out h, b.size.y + 500f)) continue;
|
||
|
|
hit++;
|
||
|
|
if (h.point.y < ymin) ymin = h.point.y;
|
||
|
|
if (h.point.y > ymax) ymax = h.point.y;
|
||
|
|
bool inW = InWater(h.point, water);
|
||
|
|
if (inW) { wcell++; continue; }
|
||
|
|
if (h.normal.y >= cosWalk) walk++;
|
||
|
|
if (h.normal.y >= cosFlat) flat++;
|
||
|
|
}
|
||
|
|
if (hit == 0) return null;
|
||
|
|
c.samples = n; c.walkPct = walk / (float)n; c.flatPct = flat / (float)n;
|
||
|
|
c.yMin = ymin; c.yMax = ymax; c.waterCells = wcell;
|
||
|
|
UnityEngine.RaycastHit ch;
|
||
|
|
c.centerY = UnityEngine.Physics.Raycast(new UnityEngine.Vector3(cx, b.max.y + 100f, cz), UnityEngine.Vector3.down, out ch, b.size.y + 500f) ? ch.point.y : -9999f;
|
||
|
|
c.nearWater = 9999f;
|
||
|
|
var center = new UnityEngine.Vector3(cx, c.centerY, cz);
|
||
|
|
foreach (var w in water)
|
||
|
|
{
|
||
|
|
float d = UnityEngine.Mathf.Sqrt(SqDistXZ(center, w));
|
||
|
|
if (d < c.nearWater) c.nearWater = d;
|
||
|
|
}
|
||
|
|
return c;
|
||
|
|
}
|
||
|
|
|
||
|
|
static float SqDistXZ(UnityEngine.Vector3 p, UnityEngine.Bounds b)
|
||
|
|
{
|
||
|
|
float dx = UnityEngine.Mathf.Max(0f, UnityEngine.Mathf.Max(b.min.x - p.x, p.x - b.max.x));
|
||
|
|
float dz = UnityEngine.Mathf.Max(0f, UnityEngine.Mathf.Max(b.min.z - p.z, p.z - b.max.z));
|
||
|
|
return dx * dx + dz * dz;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool InWater(UnityEngine.Vector3 p, List<UnityEngine.Bounds> water)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < water.Count; i++)
|
||
|
|
{
|
||
|
|
var b = water[i];
|
||
|
|
if (p.x < b.min.x || p.x > b.max.x || p.z < b.min.z || p.z > b.max.z) continue;
|
||
|
|
if (p.y < b.max.y + 0.3f) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
static bool IsWater(UnityEngine.Renderer rd)
|
||
|
|
{
|
||
|
|
if (rd.name.ToLowerInvariant().Contains("water")) return true;
|
||
|
|
var ms = rd.sharedMaterials;
|
||
|
|
for (int i = 0; i < ms.Length; i++)
|
||
|
|
{
|
||
|
|
if (ms[i] == null) continue;
|
||
|
|
if (ms[i].name.ToLowerInvariant().Contains("water")) return true;
|
||
|
|
if (ms[i].shader != null && ms[i].shader.name.ToLowerInvariant().Contains("water")) return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int CountDeep(UnityEngine.Transform t)
|
||
|
|
{ int n = 0; for (int i = 0; i < t.childCount; i++) n += 1 + CountDeep(t.GetChild(i)); return n; }
|
||
|
|
|
||
|
|
static string Path(UnityEngine.Transform t)
|
||
|
|
{ var s = t.name; var p = t.parent; while (p != null) { s = p.name + "/" + s; p = p.parent; } return s; }
|
||
|
|
|
||
|
|
static void ClearDirty()
|
||
|
|
{
|
||
|
|
var t = typeof(UnityEditor.SceneManagement.EditorSceneManager);
|
||
|
|
var mi = t.GetMethod("ClearSceneDirtiness",
|
||
|
|
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||
|
|
if (mi == null) return;
|
||
|
|
for (int i = 0; i < UnityEngine.SceneManagement.SceneManager.sceneCount; i++)
|
||
|
|
mi.Invoke(null, new object[] { UnityEngine.SceneManagement.SceneManager.GetSceneAt(i) });
|
||
|
|
}
|
||
|
|
}
|