396 lines
22 KiB
C#
396 lines
22 KiB
C#
// ─────────────────────────────────────────────────────────────────────────────
|
||
// WLShoreSkirt.cs — 섬 둘레 완만한 모래 경사 테두리(물가 스커트) (WL-816zg · #816)
|
||
//
|
||
// PD 결정(2026-09-15 「물은 진행」) — 데모 연못처럼 섬 가장자리가 완만한 모래 경사로 물에 잠기게.
|
||
// · 둥근 윤곽 : 열린 타일 발자국의 **유클리드 거리장**으로 띠를 만든다 → 볼록 모서리가 저절로 둥글다.
|
||
// · 흰 물가 띠 : 물 셰이더(Farm_Water_Demo3)가 **깊이**로 만든다. 경사가 물 아래까지 이어져 얕은 물이 생긴다.
|
||
// · 타일 옆면 가림 : 경사가 타일 가장자리(윗면 − skirtTopDrop)에서 시작해 수직 옆면을 덮는다.
|
||
//
|
||
// ■ 만드는 법
|
||
// 1) 열린 타일(FI `Island` · IsUnlocked && activeInHierarchy · 한 변 = IslandManager.IslandSize)의 중심을 모은다.
|
||
// 2) 발자국의 **바깥 테두리 선분**(이웃 타일이 없는 변)만 골라 둔다. 격자 정점마다 그 선분들까지의 최단 거리가
|
||
// 부호 있는 거리 d(안쪽 −, 바깥 +)다 — 타일별 사각형 SDF 의 min 은 타일 이음매에서 「안쪽인데 0 근처」로 틀려
|
||
// 윗면 위에 조각이 생기므로 쓰지 않는다(WLSoftDirt.SignedDist 의 「부호 있는 거리 → 띠」 방식은 그대로).
|
||
// 3) −skirtInnerKeep ≤ d ≤ skirtWidth 인 격자(셀 skirtCell)만 만들고, y = smoothstep(d / skirtWidth) 로 윗면 → skirtBottomY.
|
||
// 격자 원점을 타일 가장자리에 맞춰 정점이 가장자리 선 위에 정확히 놓인다(틈 0 · 윗면 z-fight 0).
|
||
// 4) 색 = 정점색. d 0~skirtGrassBand 는 풀색(islandTopMaterial `_DiffuseColor`) → 모래색(softDirtColor) · 물 아래 × skirtUnderwaterMul.
|
||
// 셰이더 `WL/Shore Skirt` = 언릿 정점색 · URP · DepthOnly/DepthNormals 패스 포함(물 셰이더가 깊이 텍스처에서 경사를 봐야 한다).
|
||
// 메시 1장 · 드로우콜 +1 · 삼각형 수 = LastTriangles(로그에도 남긴다).
|
||
//
|
||
// ■ 되돌리기 — `shoreSkirtEnabled = 0` 이면 만들지 않는다(지금 상태 100 %).
|
||
//
|
||
// 🔴 FI 코드 0줄 · 씬 파일 0줄 · 에셋 0줄. 메시·머티리얼은 런타임 생성이다.
|
||
// 🔴 Debug.LogError / Debug.LogException 을 쓰지 않는다(ErrorLogHookManager 팝업).
|
||
// ─────────────────────────────────────────────────────────────────────────────
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.Rendering;
|
||
using FIIsland = CryingSnow.FarmingIsland.Island;
|
||
using FIIslandManager = CryingSnow.FarmingIsland.IslandManager;
|
||
|
||
namespace WL.Look.Farm
|
||
{
|
||
[DisallowMultipleComponent]
|
||
public sealed class WLShoreSkirt : MonoBehaviour
|
||
{
|
||
public const string ObjectName = "~WL_ShoreSkirt";
|
||
public const string ShaderName = "WL/Shore Skirt";
|
||
|
||
// ── 진단(프로브·보고가 읽는다)
|
||
public static int Rebuilds;
|
||
public static int LastTiles, LastVertices, LastTriangles;
|
||
public static string LastLog = "꺼짐";
|
||
|
||
public WLIslandLookSettings cfg;
|
||
|
||
MeshFilter _mf;
|
||
MeshRenderer _mr;
|
||
Mesh _mesh;
|
||
Material _inst;
|
||
bool _vertexColor; // 전용 셰이더를 찾았는가(아니면 URP Unlit 모래 단색 대체)
|
||
|
||
static readonly int IdBaseColor = Shader.PropertyToID("_BaseColor");
|
||
static readonly int IdColor = Shader.PropertyToID("_Color");
|
||
static readonly int IdDiffuseColor = Shader.PropertyToID("_DiffuseColor");
|
||
|
||
struct Seg { public float ax, az, bx, bz; }
|
||
readonly List<Vector3> _tiles = new List<Vector3>(); // x·z = 타일 중심 · y = 렌더러 윗면 실측
|
||
readonly List<Seg> _edges = new List<Seg>(); // 발자국 바깥 테두리 선분
|
||
readonly List<Vector3> _verts = new List<Vector3>();
|
||
readonly List<Color32> _cols = new List<Color32>();
|
||
readonly List<int> _tris = new List<int>();
|
||
|
||
// 마지막으로 만든 조건 — 같으면 다시 만들지 않는다(1초 폴링·재훑기에서 공짜로 돌게).
|
||
bool _dirty = true;
|
||
long _lastTileSig;
|
||
float _lastValSig = float.NaN;
|
||
|
||
void OnEnable() { Ensure(); _dirty = true; Rebuild(); }
|
||
void OnDisable() { if (_mr != null) _mr.enabled = false; }
|
||
|
||
void OnDestroy()
|
||
{
|
||
if (_inst != null) Destroy(_inst);
|
||
if (_mesh != null) Destroy(_mesh);
|
||
}
|
||
|
||
void Ensure()
|
||
{
|
||
if (_mf == null) _mf = GetComponent<MeshFilter>();
|
||
if (_mf == null) _mf = gameObject.AddComponent<MeshFilter>();
|
||
if (_mr == null) _mr = GetComponent<MeshRenderer>();
|
||
if (_mr == null) _mr = gameObject.AddComponent<MeshRenderer>();
|
||
_mr.shadowCastingMode = ShadowCastingMode.Off;
|
||
_mr.receiveShadows = false;
|
||
_mr.lightProbeUsage = LightProbeUsage.Off;
|
||
_mr.reflectionProbeUsage = ReflectionProbeUsage.Off;
|
||
_mr.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
|
||
if (_mesh == null)
|
||
{
|
||
_mesh = new Mesh { name = "WL_ShoreSkirtMesh" };
|
||
_mesh.MarkDynamic();
|
||
_mesh.hideFlags = HideFlags.DontSave;
|
||
}
|
||
_mf.sharedMesh = _mesh;
|
||
}
|
||
|
||
/// <summary>다시 만든다(섬 확장·재훑기). 멱등 — 발자국·값이 그대로면 아무것도 하지 않는다.</summary>
|
||
public void Rebuild()
|
||
{
|
||
Ensure();
|
||
if (cfg == null) cfg = WLIslandLookSettings.Instance;
|
||
if (cfg == null || cfg.enabled_ == 0 || cfg.shoreSkirtEnabled == 0)
|
||
{ _mr.enabled = false; LastLog = "꺼짐"; return; }
|
||
|
||
float size = TileSize();
|
||
if (size <= 0.01f) { _mr.enabled = false; LastLog = "타일 크기 미확인(IslandManager 없음) — 대기"; return; }
|
||
float half = size * 0.5f;
|
||
|
||
long tileSig = CollectTiles(out float topMeasured);
|
||
LastTiles = _tiles.Count;
|
||
if (_tiles.Count == 0) { _mr.enabled = false; LastLog = "열린 타일 0 — 대기"; return; }
|
||
|
||
bool fixedTop = cfg.skirtTopMode != 0 || float.IsNaN(topMeasured);
|
||
float topY = fixedTop ? cfg.skirtTopY : topMeasured;
|
||
float waterY = FindWaterY(out bool waterFound);
|
||
|
||
Color grass = ToVertexSpace(GrassColor());
|
||
Color sand = ToVertexSpace(cfg.softDirtColor);
|
||
|
||
// 값이 바뀌면 다시 만든다(인스펙터에서 만져도 다음 폴링에 반영된다).
|
||
float valSig = cfg.skirtWidth * 7.1f + cfg.skirtBottomY * 13.3f + cfg.skirtGrassBand * 3.7f + cfg.skirtSandBlend * 29.3f
|
||
+ cfg.skirtCell * 5.9f + cfg.skirtInnerKeep * 11.9f + cfg.skirtTopDrop * 17.7f + cfg.skirtUnderwaterMul * 31.7f
|
||
+ cfg.skirtMaxGridNodes * 0.001f + topY * 37.3f + waterY * 41.1f
|
||
+ grass.r * 101f + grass.g * 103f + grass.b * 107f + sand.r * 109f + sand.g * 113f + sand.b * 127f;
|
||
|
||
bool same = !_dirty && _inst != null && tileSig == _lastTileSig && Mathf.Abs(_lastValSig - valSig) < 0.0001f;
|
||
if (same) { _mr.enabled = true; return; }
|
||
|
||
if (!EnsureMaterial()) { _mr.enabled = false; LastLog = "🔴 셰이더를 못 찾음(WL/Shore Skirt · URP Unlit 둘 다)"; return; }
|
||
|
||
BuildEdges(size, half);
|
||
float cell = BuildMesh(half, topY, waterY, grass, sand);
|
||
|
||
_mr.sharedMaterial = _inst;
|
||
_mr.enabled = true;
|
||
_lastTileSig = tileSig; _lastValSig = valSig; _dirty = false;
|
||
Rebuilds++;
|
||
|
||
LastLog = "물가 스커트 — 타일 " + _tiles.Count + " · 테두리 변 " + _edges.Count
|
||
+ " · 정점 " + LastVertices + " · 삼각형 " + LastTriangles
|
||
+ " · 폭 " + cfg.skirtWidth.ToString("F1") + " m · 윗면 y " + topY.ToString("F2")
|
||
+ (fixedTop ? "(고정 · 실측 " + (float.IsNaN(topMeasured) ? "없음" : topMeasured.ToString("F2"))
|
||
: "(실측 · 고정값 " + cfg.skirtTopY.ToString("F2")) + ")"
|
||
+ " → 바닥 " + cfg.skirtBottomY.ToString("F2") + " · 물 " + waterY.ToString("F2") + (waterFound ? "" : "(미발견·대체값)")
|
||
+ " · 격자 " + cell.ToString("F2") + " m · 셰이더 " + (_vertexColor ? ShaderName : "대체 URP Unlit(정점색 없음 · 모래 단색)")
|
||
+ " · 드로우콜 +1";
|
||
cfg.Log(LastLog);
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────
|
||
// 입력 수집
|
||
// ─────────────────────────────────────────────────────────────────
|
||
static float TileSize()
|
||
{
|
||
var im = FIIslandManager.Instance;
|
||
return im != null ? im.IslandSize : 0f;
|
||
}
|
||
|
||
/// <summary>열린 타일 중심을 모은다. 반환 = 발자국 서명(순서 무관) — 확장으로 바뀌면 값이 바뀐다.</summary>
|
||
long CollectTiles(out float topMeasured)
|
||
{
|
||
_tiles.Clear();
|
||
topMeasured = float.NaN;
|
||
var scene = gameObject.scene;
|
||
var islands = Object.FindObjectsByType<FIIsland>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||
long sig = 17;
|
||
for (int i = 0; i < islands.Length; i++)
|
||
{
|
||
var isl = islands[i];
|
||
if (isl == null || !isl.IsUnlocked || !isl.gameObject.activeInHierarchy) continue;
|
||
if (scene.IsValid() && isl.gameObject.scene != scene) continue;
|
||
var p = isl.transform.position;
|
||
float top = p.y;
|
||
var r = isl.GetComponent<Renderer>(); // 섬 본체 렌더러(FIIsland 와 같은 오브젝트 · SwapMaterials 와 같은 판별)
|
||
if (r != null) top = r.bounds.max.y;
|
||
if (float.IsNaN(topMeasured) || top > topMeasured) topMeasured = top;
|
||
_tiles.Add(new Vector3(p.x, top, p.z));
|
||
sig += ((long)Mathf.RoundToInt(p.x * 8f) * 73856093L) ^ ((long)Mathf.RoundToInt(p.z * 8f) * 19349663L);
|
||
}
|
||
return sig ^ ((long)_tiles.Count << 40);
|
||
}
|
||
|
||
/// <summary>물 높이 — SpawnSeabed 와 같은 판별(씬의 `Water` 렌더러). 없으면 거품 띠의 대체값.</summary>
|
||
float FindWaterY(out bool found)
|
||
{
|
||
var scene = gameObject.scene;
|
||
var mrs = Object.FindObjectsByType<MeshRenderer>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
||
for (int i = 0; i < mrs.Length; i++)
|
||
{
|
||
var r = mrs[i];
|
||
if (r == null || r.gameObject.name != "Water") continue;
|
||
if (scene.IsValid() && r.gameObject.scene != scene) continue;
|
||
found = true;
|
||
return r.transform.position.y;
|
||
}
|
||
found = false;
|
||
return cfg.shoreFoamFallbackY;
|
||
}
|
||
|
||
/// <summary>풀색 — 타일 윗면 머티리얼(Farm_IslandTop_Demo3)의 `_DiffuseColor`. 없으면 `_BaseColor`·`_Color` → 설정 대체값.</summary>
|
||
Color GrassColor()
|
||
{
|
||
var m = cfg.islandTopMaterial;
|
||
if (m != null)
|
||
{
|
||
if (m.HasProperty(IdDiffuseColor)) return m.GetColor(IdDiffuseColor);
|
||
if (m.HasProperty(IdBaseColor)) return m.GetColor(IdBaseColor);
|
||
if (m.HasProperty(IdColor)) return m.GetColor(IdColor);
|
||
}
|
||
return cfg.skirtGrassFallback;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 설정·머티리얼의 색은 sRGB 로 적힌 값이다(URP Unlit `_BaseColor` 와 같은 해석 — 바닥판·흙 데칼과 같은 숫자면 같은 색).
|
||
/// 정점색은 변환 없이 셰이더로 들어가므로 리니어 파이프라인에서는 리니어로 옮겨 넣는다.
|
||
/// </summary>
|
||
static Color ToVertexSpace(Color c)
|
||
{
|
||
return QualitySettings.activeColorSpace == ColorSpace.Linear ? c.linear : c;
|
||
}
|
||
|
||
/// <summary>전용 셰이더(정점색) → 없으면 URP Unlit 모래 단색 대체. 에셋은 만들지 않는다.</summary>
|
||
bool EnsureMaterial()
|
||
{
|
||
if (_inst != null) return true;
|
||
|
||
Shader sh = cfg.shoreSkirtMaterial != null ? cfg.shoreSkirtMaterial.shader : null;
|
||
if (sh == null) sh = Shader.Find(ShaderName);
|
||
if (sh == null) sh = Shader.Find("Universal Render Pipeline/Unlit");
|
||
if (sh == null) sh = Shader.Find("Unlit/Color");
|
||
if (sh == null) return false;
|
||
_vertexColor = sh.name == ShaderName;
|
||
|
||
_inst = cfg.shoreSkirtMaterial != null && cfg.shoreSkirtMaterial.shader == sh
|
||
? new Material(cfg.shoreSkirtMaterial) : new Material(sh);
|
||
_inst.name = "WL_ShoreSkirt(runtime)";
|
||
_inst.hideFlags = HideFlags.DontSave;
|
||
if (!_vertexColor)
|
||
{
|
||
// 대체 셰이더의 색 프로퍼티는 sRGB 해석 → 설정값을 그대로 넣는다(흙 데칼과 같은 색)
|
||
if (_inst.HasProperty(IdBaseColor)) _inst.SetColor(IdBaseColor, cfg.softDirtColor);
|
||
if (_inst.HasProperty(IdColor)) _inst.SetColor(IdColor, cfg.softDirtColor);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────
|
||
// 거리장 — 발자국 바깥 테두리 선분까지의 유클리드 거리(정확) · 부호 = 타일 안(−)/밖(+)
|
||
// ─────────────────────────────────────────────────────────────────
|
||
void BuildEdges(float size, float half)
|
||
{
|
||
_edges.Clear();
|
||
float tol = size * 0.25f; // 이웃 판정 허용 오차(타일 격자는 정수 배)
|
||
for (int i = 0; i < _tiles.Count; i++)
|
||
{
|
||
float cx = _tiles[i].x, cz = _tiles[i].z;
|
||
if (!HasTile(cx + size, cz, tol)) _edges.Add(new Seg { ax = cx + half, az = cz - half, bx = cx + half, bz = cz + half });
|
||
if (!HasTile(cx - size, cz, tol)) _edges.Add(new Seg { ax = cx - half, az = cz - half, bx = cx - half, bz = cz + half });
|
||
if (!HasTile(cx, cz + size, tol)) _edges.Add(new Seg { ax = cx - half, az = cz + half, bx = cx + half, bz = cz + half });
|
||
if (!HasTile(cx, cz - size, tol)) _edges.Add(new Seg { ax = cx - half, az = cz - half, bx = cx + half, bz = cz - half });
|
||
}
|
||
}
|
||
|
||
bool HasTile(float x, float z, float tol)
|
||
{
|
||
for (int i = 0; i < _tiles.Count; i++)
|
||
if (Mathf.Abs(_tiles[i].x - x) <= tol && Mathf.Abs(_tiles[i].z - z) <= tol) return true;
|
||
return false;
|
||
}
|
||
|
||
bool Inside(float x, float z, float half)
|
||
{
|
||
for (int i = 0; i < _tiles.Count; i++)
|
||
if (Mathf.Abs(x - _tiles[i].x) <= half && Mathf.Abs(z - _tiles[i].z) <= half) return true;
|
||
return false;
|
||
}
|
||
|
||
/// <summary>발자국 테두리까지의 부호 있는 거리(m). **음수 = 타일 안 · 양수 = 바깥(물 쪽)** · 테두리 선 위 = 0.</summary>
|
||
float SignedDist(float x, float z, float half)
|
||
{
|
||
float best = float.MaxValue;
|
||
for (int i = 0; i < _edges.Count; i++)
|
||
{
|
||
var s = _edges[i];
|
||
float ex = s.bx - s.ax, ez = s.bz - s.az;
|
||
float t = Mathf.Clamp01(((x - s.ax) * ex + (z - s.az) * ez) / (ex * ex + ez * ez));
|
||
float dx = x - (s.ax + ex * t), dz = z - (s.az + ez * t);
|
||
float d2 = dx * dx + dz * dz;
|
||
if (d2 < best) best = d2;
|
||
}
|
||
float d = Mathf.Sqrt(best);
|
||
return Inside(x, z, half) ? -d : d;
|
||
}
|
||
|
||
// ─────────────────────────────────────────────────────────────────
|
||
// 메시 — 격자(셀 skirtCell) · 정점마다 d → 높이(smoothstep)·정점색
|
||
// ─────────────────────────────────────────────────────────────────
|
||
float BuildMesh(float half, float topY, float waterY, Color grass, Color sand)
|
||
{
|
||
float cell = Mathf.Max(0.1f, cfg.skirtCell);
|
||
float width = Mathf.Max(cell, cfg.skirtWidth);
|
||
float keep = Mathf.Max(0f, cfg.skirtInnerKeep);
|
||
float top = topY - Mathf.Max(0f, cfg.skirtTopDrop);
|
||
float bottom = cfg.skirtBottomY;
|
||
float band = Mathf.Max(0f, cfg.skirtGrassBand);
|
||
float blend = Mathf.Max(0.01f, cfg.skirtSandBlend);
|
||
float uw = Mathf.Clamp01(cfg.skirtUnderwaterMul);
|
||
|
||
float minX = float.MaxValue, minZ = float.MaxValue, maxX = float.MinValue, maxZ = float.MinValue;
|
||
for (int i = 0; i < _tiles.Count; i++)
|
||
{
|
||
var t = _tiles[i];
|
||
if (t.x - half < minX) minX = t.x - half;
|
||
if (t.x + half > maxX) maxX = t.x + half;
|
||
if (t.z - half < minZ) minZ = t.z - half;
|
||
if (t.z + half > maxZ) maxZ = t.z + half;
|
||
}
|
||
|
||
// 격자 정점 수 상한 — 넘으면 칸을 키운다(모바일 안전 · 섬이 아무리 커져도 비용이 묶인다)
|
||
int maxNodes = Mathf.Max(1000, cfg.skirtMaxGridNodes);
|
||
int mc, nx, nz;
|
||
while (true)
|
||
{
|
||
mc = Mathf.CeilToInt((width + cell * 2f) / cell);
|
||
nx = Mathf.CeilToInt((maxX - minX) / cell - 0.0001f) + 2 * mc + 1;
|
||
nz = Mathf.CeilToInt((maxZ - minZ) / cell - 0.0001f) + 2 * mc + 1;
|
||
if ((long)nx * nz <= maxNodes) break;
|
||
cell *= 2f;
|
||
}
|
||
// 원점 = 발자국 최소 가장자리에서 정수 칸만큼 바깥 → 칸이 타일 한 변을 나누면 정점이 모든 타일 가장자리 선 위에 놓인다
|
||
float x0 = minX - mc * cell, z0 = minZ - mc * cell;
|
||
|
||
var dist = new float[nx * nz];
|
||
var index = new int[nx * nz];
|
||
for (int j = 0; j < nz; j++)
|
||
{
|
||
float z = z0 + j * cell;
|
||
for (int i = 0; i < nx; i++) dist[j * nx + i] = SignedDist(x0 + i * cell, z, half);
|
||
}
|
||
|
||
_verts.Clear(); _cols.Clear(); _tris.Clear();
|
||
float outerKeep = width + cell * 2f; // 사각형 네 귀가 전부 있어야 하므로 바깥으로 두 칸 여유
|
||
for (int j = 0; j < nz; j++)
|
||
{
|
||
for (int i = 0; i < nx; i++)
|
||
{
|
||
int n = j * nx + i;
|
||
float d = dist[n];
|
||
if (d < -keep || d > outerKeep) { index[n] = -1; continue; }
|
||
|
||
float t = Mathf.Clamp01(d / width);
|
||
t = t * t * (3f - 2f * t); // smoothstep — 윗면에서도 바닥에서도 접선이 수평(완만)
|
||
float y = top + (bottom - top) * t;
|
||
index[n] = _verts.Count;
|
||
_verts.Add(new Vector3(x0 + i * cell, y, z0 + j * cell));
|
||
|
||
float k = Mathf.Clamp01((d - band) / blend); // 풀 띠(0~band) → blend 폭에서 모래로
|
||
k = k * k * (3f - 2f * k);
|
||
var c = Color.LerpUnclamped(grass, sand, k);
|
||
if (y < waterY) { c.r *= uw; c.g *= uw; c.b *= uw; }
|
||
c.a = 1f;
|
||
_cols.Add(c);
|
||
}
|
||
}
|
||
|
||
for (int j = 0; j < nz - 1; j++)
|
||
{
|
||
for (int i = 0; i < nx - 1; i++)
|
||
{
|
||
int n = j * nx + i;
|
||
int ia = index[n], ib = index[n + 1], ic = index[n + nx], id = index[n + nx + 1];
|
||
if (ia < 0 || ib < 0 || ic < 0 || id < 0) continue;
|
||
// 네 귀가 전부 경사 바깥(d ≥ width)이면 바닥과 같은 높이의 평판 — 만들지 않는다
|
||
float dm = Mathf.Min(Mathf.Min(dist[n], dist[n + 1]), Mathf.Min(dist[n + nx], dist[n + nx + 1]));
|
||
if (dm >= width) continue;
|
||
// 바닥판(SpawnSeabed)과 같은 감김 — (x0,z0)→(x0,z1)→(x1,z1) 이 위를 향한다
|
||
_tris.Add(ia); _tris.Add(ic); _tris.Add(id);
|
||
_tris.Add(ia); _tris.Add(id); _tris.Add(ib);
|
||
}
|
||
}
|
||
|
||
_mesh.Clear();
|
||
_mesh.indexFormat = _verts.Count > 65000 ? IndexFormat.UInt32 : IndexFormat.UInt16;
|
||
_mesh.SetVertices(_verts);
|
||
_mesh.SetColors(_cols);
|
||
_mesh.SetTriangles(_tris, 0);
|
||
_mesh.RecalculateNormals(); // DepthNormals 패스용
|
||
_mesh.RecalculateBounds();
|
||
|
||
LastVertices = _verts.Count;
|
||
LastTriangles = _tris.Count / 3;
|
||
return cell;
|
||
}
|
||
}
|
||
}
|