Project_WL/Assets/WL/Look/Farm/WLShoreFoam.cs

461 lines
25 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ─────────────────────────────────────────────────────────────────────────────
// WLShoreFoam.cs — 섬 **둘레**를 따라 흰 거품 띠를 두른다 (WL-816j2 · #816)
//
// 왜 필요한가 (816j 실측)
// 셰이더의 깊이 기반 Foam 은 **카메라가 보는 쪽 가장자리에만** 거품이 생긴다
// (섬이 수직 벽 덩어리라 시선이 물을 뚫고 잠긴 벽에 닿는 쪽만 「얕은 물」로 읽힌다).
// PD 가 「둘레에 띠를 둘러줘」로 결정 → 섬 윗면 footprint 의 **바깥 테두리**를 따라
// 메시 띠를 하나 만들어 물 위에 깐다. 사방 전부에 생기고, 카메라 방향과 무관하다.
//
// 비용
// 메시 1장 · 머티리얼 1개 · **드로우콜 +1**. 섬 타일 4칸 기준 삼각형 1천 대.
// 비싸면 `shoreFoamCell`(격자 간격)을 키우거나 `shoreFoamWidth`(폭)를 줄인다.
//
// 섬 확장
// `WLIslandLook.RequestIslandRebuild` 가 풀과 함께 이 띠도 다시 만든다
// (816e 확장 훅 = `FIIsland.OnActivated` + 잠금 해제 수 폴링) → 새 가장자리 자동.
//
// 🔴 FI 코드 0줄 · 씬 파일 0줄 · 원본 셰이더 0줄. 런타임에 메시만 만든다.
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic;
using UnityEngine;
using FIIsland = CryingSnow.FarmingIsland.Island;
namespace WL.Look.Farm
{
[DisallowMultipleComponent]
public sealed class WLShoreFoam : MonoBehaviour
{
public const string ObjectName = "~WL_ShoreFoam";
public static int Rebuilds, Verts, Tris, Tiles;
public static string LastLog = "";
public WLIslandLookSettings cfg;
// 검증용 런타임 오버라이드 — 0/음수면 설정값을 쓴다. 🔴 에셋을 고치지 않고 3단 비교를 하기 위한 것.
[System.NonSerialized] public float widthOverride = 0f;
[System.NonSerialized] public float innerOverride = -1f;
[System.NonSerialized] public float cellOverride = 0f;
MeshFilter _mf;
MeshRenderer _mr;
Mesh _mesh;
float _scroll;
void OnEnable() { Ensure(); WLDemoTerrain.Baked += OnTerrainBaked; Rebuild(); }
void OnDisable() { WLDemoTerrain.Baked -= OnTerrainBaked; if (_mr != null) _mr.enabled = false; }
/// <summary>데모 터레인이 실제로 다시 구워졌을 때(확장·밭 변화) 물가선을 다시 딴다.</summary>
void OnTerrainBaked() { if (isActiveAndEnabled) Rebuild(); }
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 = UnityEngine.Rendering.ShadowCastingMode.Off;
_mr.receiveShadows = false;
_mr.lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off;
_mr.reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off;
_mr.motionVectorGenerationMode = MotionVectorGenerationMode.ForceNoMotion;
if (_mesh == null)
{
_mesh = new Mesh { name = "WL_ShoreFoamMesh" };
_mesh.MarkDynamic();
_mesh.hideFlags = HideFlags.DontSave;
}
_mf.sharedMesh = _mesh;
}
/// <summary>거품이 해안을 따라 천천히 흐른다. 🔴 에셋이 아니라 **런타임 복사본**의 오프셋만 만진다.</summary>
void Update()
{
if (cfg == null || _mr == null || _inst == null) return;
float sp = cfg.shoreFoamScroll;
if (sp == 0f) return;
_scroll += Time.deltaTime * sp;
if (_scroll > 1f) _scroll -= 1f;
// 🔴 2026-09-16 PD 「각진 부분 사이의 사선」 이분 실측: v(띠 가로) 방향 스크롤이 물가선 알파 프로파일을 띠 밖으로 밀어 모래 위에 계단·사선
// 모양의 흰 덩어리를 만들었다(스크롤 0 이면 즉시 매끈). 물가선 모드에서는 **해안 방향(u)만** 흐른다. 네모 띠(816j2)는 예전 그대로.
bool waterline = cfg.demoTerrainEnabled != 0;
_inst.SetTextureOffset(IdBase, new Vector2(_scroll, waterline ? 0f : _scroll * 0.37f));
}
Material _inst;
static readonly int IdBase = Shader.PropertyToID("_BaseMap");
// ─────────────────────────────────────────────────────────────────
public void Rebuild()
{
Ensure();
if (cfg == null) cfg = WLIslandLookSettings.Instance;
if (cfg == null || cfg.enabled_ == 0 || cfg.shoreFoamEnabled == 0)
{ _mr.enabled = false; LastLog = "꺼짐"; return; }
if (cfg.shoreFoamMaterial == null) { _mr.enabled = false; LastLog = "🔴 머티리얼 없음"; return; }
if (_inst == null || _inst.shader != cfg.shoreFoamMaterial.shader)
{
if (_inst != null) Destroy(_inst);
_inst = new Material(cfg.shoreFoamMaterial) { name = "WL_ShoreFoam(runtime)", hideFlags = HideFlags.DontSave };
}
_mr.sharedMaterial = _inst;
// 🔴 2026-09-16 PD 「물이 섬 경계에 닿을 때 자연스러운 물보라」 — 데모 터레인 모드면 타일 발자국(네모) 대신
// **터레인 물가선**(지형 높이 = 물 높이)을 따라 띠를 두른다. 경사가 완만한 곳은 넓고 가파른 곳은 좁아 자연스럽다.
if (cfg.demoTerrainEnabled != 0)
{
var dt = Object.FindFirstObjectByType<WLDemoTerrain>(FindObjectsInactive.Include);
var terrain = dt != null ? dt.TerrainComponent : null;
if (terrain == null) { _mesh.Clear(); _mr.enabled = false; LastLog = "터레인 대기"; return; }
BuildFromTerrain(terrain);
return;
}
var tiles = CollectTiles();
Tiles = tiles.Count;
if (tiles.Count == 0) { _mesh.Clear(); _mr.enabled = false; LastLog = "타일 0"; return; }
float cell = Mathf.Max(0.15f, cellOverride > 0f ? cellOverride : cfg.shoreFoamCell);
float w = Mathf.Max(0.1f, widthOverride > 0f ? widthOverride : cfg.shoreFoamWidth);
float inner = Mathf.Max(0f, innerOverride >= 0f ? innerOverride : cfg.shoreFoamInner);
float pad = w + inner + cell * 2f;
// 섬 전체를 덮는 격자
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]; float h = t.half;
minX = Mathf.Min(minX, t.center.x - h); maxX = Mathf.Max(maxX, t.center.x + h);
minZ = Mathf.Min(minZ, t.center.z - h); maxZ = Mathf.Max(maxZ, t.center.z + h);
}
minX -= pad; minZ -= pad; maxX += pad; maxZ += pad;
int nx = Mathf.Clamp(Mathf.CeilToInt((maxX - minX) / cell) + 1, 2, 1024);
int nz = Mathf.Clamp(Mathf.CeilToInt((maxZ - minZ) / cell) + 1, 2, 1024);
// ① 안/밖
var inside = new bool[nx * nz];
for (int j = 0; j < nz; j++)
{
float z = minZ + j * cell;
for (int i = 0; i < nx; i++)
{
float x = minX + i * cell;
inside[j * nx + i] = OnAnyTile(tiles, x, z);
}
}
// ② 부호 있는 거리(챔퍼 2패스) — 밖은 +, 안은
var d = Chamfer(inside, nx, nz, cell, false); // 밖 → 가장 가까운 안까지
var e = Chamfer(inside, nx, nz, cell, true); // 안 → 가장 가까운 밖까지
var sd = new float[nx * nz];
for (int k = 0; k < sd.Length; k++) sd[k] = inside[k] ? -e[k] : d[k];
// ③ 띠 구간([-inner, w])에 걸치는 칸만 삼각형으로
float y = WaterY(tiles) + cfg.shoreFoamY;
float tileLen = Mathf.Max(0.5f, cfg.shoreFoamTileLength);
var vmap = new int[nx * nz];
for (int k = 0; k < vmap.Length; k++) vmap[k] = -1;
var verts = new List<Vector3>(4096);
var uvs = new List<Vector2>(4096);
var tris = new List<int>(8192);
for (int j = 0; j < nz - 1; j++)
for (int i = 0; i < nx - 1; i++)
{
int a = j * nx + i, b = a + 1, c = a + nx, dd = c + 1;
float lo = Mathf.Min(Mathf.Min(sd[a], sd[b]), Mathf.Min(sd[c], sd[dd]));
float hi = Mathf.Max(Mathf.Max(sd[a], sd[b]), Mathf.Max(sd[c], sd[dd]));
if (lo > w || hi < -inner) continue;
int va = V(a), vb = V(b), vc = V(c), vd = V(dd);
tris.Add(va); tris.Add(vc); tris.Add(vb);
tris.Add(vb); tris.Add(vc); tris.Add(vd);
}
int V(int k)
{
if (vmap[k] >= 0) return vmap[k];
int i = k % nx, j = k / nx;
float x = minX + i * cell, z = minZ + j * cell;
verts.Add(new Vector3(x, y, z));
float v = Mathf.Clamp01((sd[k] + inner) / (w + inner));
uvs.Add(new Vector2((x * 0.7071f + z * 0.7071f) / tileLen, v));
vmap[k] = verts.Count - 1;
return vmap[k];
}
_mesh.Clear();
if (verts.Count == 0 || tris.Count == 0) { _mr.enabled = false; LastLog = "띠 0"; return; }
_mesh.indexFormat = verts.Count > 65000
? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
_mesh.SetVertices(verts);
_mesh.SetUVs(0, uvs);
_mesh.SetTriangles(tris, 0);
_mesh.RecalculateBounds();
_mr.enabled = true;
Rebuilds++; Verts = verts.Count; Tris = tris.Count / 3;
LastLog = "타일 " + Tiles + " · 띠 삼각형 " + Tris + " · 정점 " + Verts
+ " · 폭 " + w.ToString("F2") + " m · 격자 " + cell.ToString("F2") + " m · 드로우콜 +1";
cfg.Log("둘레 거품 띠 — " + LastLog);
}
// ─────────────────────────────────────────────────────────────────
// 데모 터레인 물가선 띠 — 격자마다 f = 물 높이 지형 높이(+ = 물속 깊이 · = 모래 위 높이).
// 띠 = f ∈ [shoreFoamUp, +shoreFoamDown] 에 걸치는 칸. 정점 높이 = max(지형, 물) + shoreFoamY → 모래 위에서는 모래를 덮고 물 위에서는 물을 덮는다.
// UV v = 0(모래 끝) → 1(바다 끝) — 기존 띠 텍스처와 같은 방향. u = 해안 방향 반복(shoreFoamTileLength).
// ─────────────────────────────────────────────────────────────────
public static int TerrainNodes, WaterlinePoints;
void BuildFromTerrain(Terrain terrain)
{
float waterY = WaterYOfScene(terrain.gameObject.scene);
float up = Mathf.Max(0.01f, cfg.shoreFoamUp), down = Mathf.Max(0.01f, cfg.shoreFoamDown);
float cell = Mathf.Max(0.15f, cellOverride > 0f ? cellOverride : cfg.shoreFoamCell);
float lift = cfg.shoreFoamY;
float tileLen = Mathf.Max(0.5f, cfg.shoreFoamTileLength);
var tp = terrain.transform.position;
var sz = terrain.terrainData.size;
// 띠 텍스처 = 런타임 생성(물가선에서 진하고 모래 쪽은 짧게 · 바다 쪽은 길게 들쭉날쭉 사라진다) — 에셋 텍스처는 네모 띠(816j2)용이라 가장자리가 딱 끊긴다
var tex = FoamTexture(up / (up + down));
_inst.SetTexture(IdBase, tex);
if (_inst.HasProperty("_MainTex")) _inst.SetTexture("_MainTex", tex);
int nx = Mathf.Clamp(Mathf.CeilToInt(sz.x / cell) + 1, 2, 1024);
int nz = Mathf.Clamp(Mathf.CeilToInt(sz.z / cell) + 1, 2, 1024);
float stepX = sz.x / (nx - 1), stepZ = sz.z / (nz - 1);
var hgt = new float[nx * nz];
var under = new bool[nx * nz];
var probe = new Vector3();
for (int j = 0; j < nz; j++)
{
probe.z = tp.z + j * stepZ;
for (int i = 0; i < nx; i++)
{
probe.x = tp.x + i * stepX;
float h = terrain.SampleHeight(probe) + tp.y;
int k = j * nx + i;
hgt[k] = h; under[k] = h < waterY;
}
}
TerrainNodes = nx * nz;
// 🔴 2026-09-16 PD 「섬 가장자리가 각졌다」 실측: 높이 차(f)로 띠를 정하면 물 높이 근처가 평평한 곳에서 띠가 통째로 넓어져
// 흰 계단 덩어리가 됐다. → 물가선까지의 **수평 거리**(격자 챔퍼 · + 물 쪽 / 모래 쪽)로 띠를 정한다 = 어디서나 같은 폭.
var dToWater = Chamfer(under, nx, nz, cell, false); // 모래 노드 → 가장 가까운 물속 노드까지(m · 거친 선별용)
var dToSand = Chamfer(under, nx, nz, cell, true); // 물속 노드 → 가장 가까운 모래 노드까지(m · 거친 선별용)
// 🔴 2026-09-16 PD 「각진 부분 사이의 사선」 실측: 챔퍼 거리의 등고선은 축 방향 + 45° 사선의 **팔각형**이라 띠 가장자리가 그 모양 그대로
// 계단·사선으로 보였다(물 셰이더 교체·높이맵 257 로도 불변 → 거품 띠가 원인). 물가선 교점(격자 변에서 물 높이를 가로지르는 점)을 모아
// 띠 근처 노드만 **진짜 유클리드 거리**로 다시 잰다 → 등고선이 매끈한 곡선.
var wlx = new List<float>(1024); var wlz = new List<float>(1024);
for (int j = 0; j < nz; j++)
for (int i = 0; i < nx; i++)
{
int k = j * nx + i;
float x = tp.x + i * stepX, z = tp.z + j * stepZ;
if (i + 1 < nx && under[k] != under[k + 1])
{ float t = Mathf.Clamp01((waterY - hgt[k]) / (hgt[k + 1] - hgt[k])); wlx.Add(x + t * stepX); wlz.Add(z); }
if (j + 1 < nz && under[k] != under[k + nx])
{ float t = Mathf.Clamp01((waterY - hgt[k]) / (hgt[k + nx] - hgt[k])); wlx.Add(x); wlz.Add(z + t * stepZ); }
}
WaterlinePoints = wlx.Count;
float reach = Mathf.Max(up, down) + cell * 2f;
var f = new float[nx * nz];
for (int j = 0; j < nz; j++)
for (int i = 0; i < nx; i++)
{
int k = j * nx + i;
float dc = under[k] ? dToSand[k] : dToWater[k];
if (dc > reach || wlx.Count == 0) { f[k] = under[k] ? reach + 1f : -(reach + 1f); continue; }
float x = tp.x + i * stepX, z = tp.z + j * stepZ, best = float.MaxValue;
for (int q = 0; q < wlx.Count; q++)
{
float dx = x - wlx[q], dz = z - wlz[q]; float d2 = dx * dx + dz * dz;
if (d2 < best) best = d2;
}
float d = Mathf.Sqrt(best);
f[k] = under[k] ? d : -d;
}
var vmap = new int[nx * nz];
for (int k = 0; k < vmap.Length; k++) vmap[k] = -1;
var verts = new List<Vector3>(2048);
var uvs = new List<Vector2>(2048);
var tris = new List<int>(4096);
for (int j = 0; j < nz - 1; j++)
for (int i = 0; i < nx - 1; i++)
{
int a = j * nx + i, b = a + 1, c = a + nx, dd = c + 1;
float lo = Mathf.Min(Mathf.Min(f[a], f[b]), Mathf.Min(f[c], f[dd]));
float hi = Mathf.Max(Mathf.Max(f[a], f[b]), Mathf.Max(f[c], f[dd]));
if (lo > down || hi < -up) continue;
int va = V(a), vb = V(b), vc = V(c), vd = V(dd);
tris.Add(va); tris.Add(vc); tris.Add(vb);
tris.Add(vb); tris.Add(vc); tris.Add(vd);
}
int V(int k)
{
if (vmap[k] >= 0) return vmap[k];
int i = k % nx, j = k / nx;
float x = tp.x + i * stepX, z = tp.z + j * stepZ;
verts.Add(new Vector3(x, Mathf.Max(hgt[k], waterY) + lift, z));
float v = Mathf.Clamp01((f[k] + up) / (down + up));
uvs.Add(new Vector2((x * 0.7071f + z * 0.7071f) / tileLen, v));
vmap[k] = verts.Count - 1;
return vmap[k];
}
_mesh.Clear();
if (verts.Count == 0 || tris.Count == 0) { _mr.enabled = false; LastLog = "물가선 띠 0(물 높이 " + waterY.ToString("F2") + ")"; return; }
_mesh.indexFormat = verts.Count > 65000
? UnityEngine.Rendering.IndexFormat.UInt32 : UnityEngine.Rendering.IndexFormat.UInt16;
_mesh.SetVertices(verts);
_mesh.SetUVs(0, uvs);
_mesh.SetTriangles(tris, 0);
_mesh.RecalculateBounds();
_mr.enabled = true;
Rebuilds++; Verts = verts.Count; Tris = tris.Count / 3; Tiles = 0;
LastLog = "터레인 물가선 띠 — 삼각형 " + Tris + " · 정점 " + Verts + " · 물 y " + waterY.ToString("F2") + " · 물가선 점 " + WaterlinePoints
+ " · 모래 쪽 " + up.ToString("F2") + " m · 바다 쪽 " + down.ToString("F2") + " m(유클리드 수평 거리) · 격자 " + cell.ToString("F2") + " m · 드로우콜 +1";
cfg.Log("둘레 거품 띠 — " + LastLog);
}
static float SmoothStep01(float t) { t = Mathf.Clamp01(t); return t * t * (3f - 2f * t); }
// 물가선 띠 텍스처(런타임 · 에셋 0줄): u = 해안 방향(주기 1 · 반복) · v = 모래 끝(0) → 물가선(vWl) → 바다 끝(1).
// 알파 = 물가선 1 · 모래 쪽으로 짧게(제곱) · 바다 쪽으로 길게, 바다 쪽 끝은 해안 방향 파형으로 들쭉날쭉.
static Texture2D s_foamTex;
static float s_foamTexKey = float.NaN;
static Texture2D FoamTexture(float vWl)
{
vWl = Mathf.Clamp(vWl, 0.05f, 0.95f);
if (s_foamTex != null && Mathf.Abs(s_foamTexKey - vWl) < 0.001f) return s_foamTex;
const int W = 256, H = 64;
if (s_foamTex == null)
s_foamTex = new Texture2D(W, H, TextureFormat.RGBA32, false)
{ name = "WL_ShoreFoam(runtime)", hideFlags = HideFlags.DontSave, wrapMode = TextureWrapMode.Repeat, filterMode = FilterMode.Bilinear };
var px = new Color32[W * H];
// 🔴 2026-09-16 실측(남서 물가): 바다 쪽 끝까지 알파가 남아 메시 경계(격자 칸)가 네모 계단으로 보였다 → 바다 쪽은 제곱으로 빨리 꺼지고
// (띠 폭의 60 % 지점에서 ≈ 5 %) 물가선 최대 알파도 0.75 로 낮춘다(데모의 옅은 물보라). 메시 끝(v=1)은 0.
for (int y = 0; y < H; y++)
{
float v = (y + 0.5f) / H;
float a = v < vWl ? Mathf.Pow(Mathf.Clamp01(v / vWl), 2f) : Mathf.Pow(1f - SmoothStep01((v - vWl) / (1f - vWl)), 2.2f);
for (int x = 0; x < W; x++)
{
float u = (float)x / W;
float n = 0.5f + 0.5f * Mathf.Sin(u * 6.2832f * 3f + Mathf.Sin(u * 6.2832f * 7f + v * 3f) * 1.3f);
float edge = v > vWl ? Mathf.Clamp01(1f - (v - vWl) / (1f - vWl) * (0.8f + 0.9f * n)) : 1f;
float alpha = Mathf.Clamp01(a * edge * (0.7f + 0.3f * n)) * 0.75f;
px[y * W + x] = new Color32(255, 255, 255, (byte)(alpha * 255f));
}
}
s_foamTex.SetPixels32(px);
s_foamTex.Apply(false, false);
s_foamTexKey = vWl;
return s_foamTex;
}
/// <summary>물 평면의 높이(씬 기준) — `Water` 루트/자식을 찾고 없으면 설정값.</summary>
float WaterYOfScene(UnityEngine.SceneManagement.Scene scene)
{
if (!scene.IsValid()) return cfg.shoreFoamFallbackY;
var roots = scene.GetRootGameObjects();
for (int i = 0; i < roots.Length; i++)
{
if (roots[i].name == "Water") return roots[i].transform.position.y;
var t = roots[i].transform.Find("Water");
if (t != null) return t.position.y;
}
return cfg.shoreFoamFallbackY;
}
struct TileRef { public Transform tf; public Vector3 center; public WLTileMask mask; public float half; }
readonly List<TileRef> _tiles = new List<TileRef>(96);
List<TileRef> CollectTiles()
{
_tiles.Clear();
var islands = Object.FindObjectsByType<FIIsland>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
for (int i = 0; i < islands.Length; i++)
{
var isl = islands[i];
if (isl == null || !isl.IsUnlocked) continue;
if (!isl.gameObject.activeInHierarchy) continue;
if (isl.transform.localScale.x < 0.99f) continue; // 확장 애니메이션 중
var mf = isl.GetComponent<MeshFilter>();
if (mf == null || mf.sharedMesh == null) continue;
var mask = cfg.FindTileMask(mf.sharedMesh.name);
if (mask == null) continue;
_tiles.Add(new TileRef { tf = isl.transform, center = isl.transform.position, mask = mask, half = mask.half });
}
return _tiles;
}
static bool OnAnyTile(List<TileRef> tiles, float wx, float wz)
{
for (int i = 0; i < tiles.Count; i++)
{
var t = tiles[i];
var l = t.tf.InverseTransformPoint(new Vector3(wx, t.center.y, wz));
if (t.mask.At(l.x, l.z)) return true;
}
return false;
}
/// <summary>물 평면의 높이 — 섬 씬의 `Water` 를 찾고 없으면 설정값.</summary>
float WaterY(List<TileRef> tiles)
{
var scene = tiles.Count > 0 ? tiles[0].tf.gameObject.scene : gameObject.scene;
var roots = scene.GetRootGameObjects();
for (int i = 0; i < roots.Length; i++)
{
var t = roots[i].transform.Find("Water");
if (t != null) return t.position.y;
if (roots[i].name == "Water") return roots[i].transform.position.y;
}
return cfg.shoreFoamFallbackY;
}
/// <summary>
/// 챔퍼 거리 변환 — seed 가 true 인 칸에서의 거리(m).
/// 816zd 가 풀 배치의 둥근 윤곽(침식→팽창)에도 그대로 쓴다 — 거리 정의를 한 곳에만 둔다.
/// </summary>
public static float[] Chamfer(bool[] inside, int nx, int nz, float cell, bool seedOutside)
{
const float BIG = 1e9f;
var d = new float[nx * nz];
for (int k = 0; k < d.Length; k++)
{
bool seed = seedOutside ? !inside[k] : inside[k];
d[k] = seed ? 0f : BIG;
}
float a = cell, b = cell * 1.41421356f;
for (int j = 0; j < nz; j++)
for (int i = 0; i < nx; i++)
{
int k = j * nx + i; float m = d[k];
if (i > 0) m = Mathf.Min(m, d[k - 1] + a);
if (j > 0) m = Mathf.Min(m, d[k - nx] + a);
if (i > 0 && j > 0) m = Mathf.Min(m, d[k - nx - 1] + b);
if (i < nx - 1 && j > 0) m = Mathf.Min(m, d[k - nx + 1] + b);
d[k] = m;
}
for (int j = nz - 1; j >= 0; j--)
for (int i = nx - 1; i >= 0; i--)
{
int k = j * nx + i; float m = d[k];
if (i < nx - 1) m = Mathf.Min(m, d[k + 1] + a);
if (j < nz - 1) m = Mathf.Min(m, d[k + nx] + a);
if (i < nx - 1 && j < nz - 1) m = Mathf.Min(m, d[k + nx + 1] + b);
if (i > 0 && j < nz - 1) m = Mathf.Min(m, d[k + nx - 1] + b);
d[k] = m;
}
return d;
}
}
}