Apply boundary sink free-outflow on ghost cells

This commit is contained in:
2026-02-11 00:00:33 +01:00
parent 9aa9daee79
commit 736f310118
4 changed files with 170 additions and 8 deletions

View File

@@ -13,6 +13,7 @@ Texture2D<float2> _VelWest;
Texture2D<float> _GhostOverrideMask;
Texture2D<float> _GhostOverrideWater;
Texture2D<float2> _GhostOverrideVel;
Texture2D<float> _GhostFreeOutflowMask;
RWTexture2D<float> _WaterOut;
RWTexture2D<float2> _VelOut;
@@ -23,6 +24,7 @@ int _HasSouth;
int _HasEast;
int _HasWest;
int _UseGhostOverride;
int _UseGhostFreeOutflow;
[numthreads(8, 8, 1)]
void GhostExchange(uint3 id : SV_DispatchThreadID)
@@ -65,7 +67,15 @@ void GhostExchange(uint3 id : SV_DispatchThreadID)
usedNeighbor = true;
}
if (!usedNeighbor)
bool isBoundary = (g.x == 0 || g.x == size - 1 || g.y == 0 || g.y == size - 1);
bool isFreeOutflow = false;
if (_UseGhostFreeOutflow == 1 && isBoundary)
{
float outflowMask = _GhostFreeOutflowMask[g];
isFreeOutflow = outflowMask > 0.5;
}
if (!usedNeighbor && !isFreeOutflow)
{
if (g.x == 0 || g.x == size - 1)
{
@@ -77,7 +87,7 @@ void GhostExchange(uint3 id : SV_DispatchThreadID)
}
}
if (_UseGhostOverride == 1 && (g.x == 0 || g.x == size - 1 || g.y == 0 || g.y == size - 1))
if (_UseGhostOverride == 1 && isBoundary)
{
float mask = _GhostOverrideMask[g];
if (mask > 0.5)

View File

@@ -86,6 +86,9 @@ public sealed class SweTileSimulator : MonoBehaviour
private float[] ghostOverrideWaterData;
private Vector2[] ghostOverrideVelocityData;
private bool useGhostBoundaryOverrides;
private Texture2D ghostFreeOutflowMask;
private float[] ghostFreeOutflowMaskData;
private bool useGhostFreeOutflowMask;
private RenderTexture clampMask;
private RenderTexture dummyRw;
@@ -304,6 +307,59 @@ public sealed class SweTileSimulator : MonoBehaviour
ghostOverrideVelocity.SetPixelData(ghostOverrideVelocityData, 0);
ghostOverrideVelocity.Apply(false, false);
useGhostBoundaryOverrides = false;
EnsureGhostFreeOutflowMask();
Array.Clear(ghostFreeOutflowMaskData, 0, ghostFreeOutflowMaskData.Length);
ghostFreeOutflowMask.SetPixelData(ghostFreeOutflowMaskData, 0);
ghostFreeOutflowMask.Apply(false, false);
useGhostFreeOutflowMask = false;
}
public bool SetGhostFreeOutflowCells(int[] ghostIndices)
{
if (!isInitialized)
{
return false;
}
if (ghostIndices == null || ghostIndices.Length == 0)
{
return false;
}
EnsureGhostFreeOutflowMask();
Array.Clear(ghostFreeOutflowMaskData, 0, ghostFreeOutflowMaskData.Length);
bool any = false;
int maxIndex = ghostFreeOutflowMaskData.Length - 1;
for (int i = 0; i < ghostIndices.Length; i++)
{
int idx = ghostIndices[i];
if (idx < 0 || idx > maxIndex)
{
continue;
}
ghostFreeOutflowMaskData[idx] = 1.0f;
any = true;
}
ghostFreeOutflowMask.SetPixelData(ghostFreeOutflowMaskData, 0);
ghostFreeOutflowMask.Apply(false, false);
useGhostFreeOutflowMask = any;
return any;
}
public void ClearGhostFreeOutflowCells()
{
if (!isInitialized)
{
return;
}
EnsureGhostFreeOutflowMask();
Array.Clear(ghostFreeOutflowMaskData, 0, ghostFreeOutflowMaskData.Length);
ghostFreeOutflowMask.SetPixelData(ghostFreeOutflowMaskData, 0);
ghostFreeOutflowMask.Apply(false, false);
useGhostFreeOutflowMask = false;
}
public bool TryGetBoundaryIdMasks(out int[] sourceIds, out int[] sinkIds)
@@ -602,6 +658,10 @@ public sealed class SweTileSimulator : MonoBehaviour
{
Destroy(ghostOverrideVelocity);
}
if (ghostFreeOutflowMask != null)
{
Destroy(ghostFreeOutflowMask);
}
resolvedTerrain = null;
resolvedPorosity = null;
@@ -617,6 +677,9 @@ public sealed class SweTileSimulator : MonoBehaviour
ghostOverrideWaterData = null;
ghostOverrideVelocityData = null;
useGhostBoundaryOverrides = false;
ghostFreeOutflowMask = null;
ghostFreeOutflowMaskData = null;
useGhostFreeOutflowMask = false;
}
private void ResolveStaticTextures()
@@ -867,6 +930,10 @@ public sealed class SweTileSimulator : MonoBehaviour
{
EnsureGhostOverrideTextures();
}
if (ghostFreeOutflowMask == null)
{
EnsureGhostFreeOutflowMask();
}
ghostExchangeShader.SetInt("_GridRes", gridRes);
ghostExchangeShader.SetInt("_HasNorth", 0);
@@ -874,6 +941,7 @@ public sealed class SweTileSimulator : MonoBehaviour
ghostExchangeShader.SetInt("_HasEast", 0);
ghostExchangeShader.SetInt("_HasWest", 0);
ghostExchangeShader.SetInt("_UseGhostOverride", useGhostBoundaryOverrides ? 1 : 0);
ghostExchangeShader.SetInt("_UseGhostFreeOutflow", useGhostFreeOutflowMask ? 1 : 0);
ghostExchangeShader.SetTexture(ghostKernel, "_WaterIn", CurrentWater);
ghostExchangeShader.SetTexture(ghostKernel, "_VelIn", CurrentVelocity);
@@ -890,6 +958,7 @@ public sealed class SweTileSimulator : MonoBehaviour
ghostExchangeShader.SetTexture(ghostKernel, "_GhostOverrideMask", ghostOverrideMask);
ghostExchangeShader.SetTexture(ghostKernel, "_GhostOverrideWater", ghostOverrideWater);
ghostExchangeShader.SetTexture(ghostKernel, "_GhostOverrideVel", ghostOverrideVelocity);
ghostExchangeShader.SetTexture(ghostKernel, "_GhostFreeOutflowMask", ghostFreeOutflowMask);
DispatchKernel(ghostExchangeShader, ghostKernel, gridRes + 2, gridRes + 2);
}
@@ -1344,6 +1413,26 @@ public sealed class SweTileSimulator : MonoBehaviour
}
}
private void EnsureGhostFreeOutflowMask()
{
int ghostRes = gridRes + 2;
int length = ghostRes * ghostRes;
if (ghostFreeOutflowMask == null || ghostFreeOutflowMask.width != ghostRes || ghostFreeOutflowMask.height != ghostRes)
{
if (ghostFreeOutflowMask != null)
{
Destroy(ghostFreeOutflowMask);
}
ghostFreeOutflowMask = CreateTextureFromFloats(new float[length], ghostRes, ghostRes, "SWE_GhostFreeOutflowMask");
ghostFreeOutflowMaskData = new float[length];
}
else if (ghostFreeOutflowMaskData == null || ghostFreeOutflowMaskData.Length != length)
{
ghostFreeOutflowMaskData = new float[length];
}
}
private static int[] ReadIdTexture(Texture2D texture)
{
if (texture == null || !texture.isReadable)

View File

@@ -65,6 +65,7 @@ namespace FloodSWE.Networking
private HashSet<int> activeSourceIds = new HashSet<int>();
private HashSet<int> activeSinkIds = new HashSet<int>();
private readonly Dictionary<int, int[]> activeBoundaryInflowGhostCells = new Dictionary<int, int[]>();
private readonly Dictionary<int, int[]> activeBoundarySinkGhostCells = new Dictionary<int, int[]>();
private readonly Dictionary<int, int[]> activeSourceAreaCells = new Dictionary<int, int[]>();
private readonly Dictionary<int, int[]> activeSinkCells = new Dictionary<int, int[]>();
private bool activeTileHasBoundaryCellGroups;
@@ -692,6 +693,7 @@ namespace FloodSWE.Networking
activeSourceIds = new HashSet<int>();
activeSinkIds = new HashSet<int>();
activeBoundaryInflowGhostCells.Clear();
activeBoundarySinkGhostCells.Clear();
activeSourceAreaCells.Clear();
activeSinkCells.Clear();
activeTileHasBoundaryCellGroups = false;
@@ -714,18 +716,20 @@ namespace FloodSWE.Networking
CollectIdsFromRefs(tile.boundary_inflow_ids, activeSourceIds);
CollectCellGroups(tile.boundary_cells, activeBoundaryInflowGhostCells, activeSourceIds);
CollectCellGroups(tile.boundary_sink_cells, activeBoundarySinkGhostCells, activeSinkIds);
CollectCellGroups(tile.source_area_cells, activeSourceAreaCells, activeSourceIds);
CollectCellGroups(tile.sink_cells, activeSinkCells, activeSinkIds);
activeTileHasBoundaryCellGroups =
activeBoundaryInflowGhostCells.Count > 0 ||
activeBoundarySinkGhostCells.Count > 0 ||
activeSourceAreaCells.Count > 0 ||
activeSinkCells.Count > 0;
Debug.Log(
$"SweServerRuntime: active tile inflowIds={activeSourceIds.Count}, sinkIds={activeSinkIds.Count}, " +
$"boundaryGhostGroups={activeBoundaryInflowGhostCells.Count}, sourceAreaGroups={activeSourceAreaCells.Count}, " +
$"sinkGroups={activeSinkCells.Count}");
$"boundaryInGhostGroups={activeBoundaryInflowGhostCells.Count}, boundaryOutGhostGroups={activeBoundarySinkGhostCells.Count}, " +
$"sourceAreaGroups={activeSourceAreaCells.Count}, sinkGroups={activeSinkCells.Count}");
}
private void RecomputeExternalDepthRate()
@@ -749,6 +753,8 @@ namespace FloodSWE.Networking
var ghostIndices = new List<int>(128);
var ghostLevels = new List<float>(128);
var ghostVelocities = new List<Vector2>(128);
var ghostOutflowCells = new HashSet<int>();
var boundarySinkIdsWithGhostOutflow = new HashSet<int>();
foreach (var pair in activeBoundaryInflowGhostCells)
{
@@ -786,6 +792,39 @@ namespace FloodSWE.Networking
simulator.ClearGhostBoundaryOverrides();
}
foreach (var pair in activeBoundarySinkGhostCells)
{
int boundaryId = pair.Key;
if (boundaryId <= 0)
{
continue;
}
boundarySinkIdsWithGhostOutflow.Add(boundaryId);
int[] cells = pair.Value;
if (cells == null || cells.Length == 0)
{
continue;
}
for (int i = 0; i < cells.Length; i++)
{
int idx = cells[i];
if (idx >= 0)
{
ghostOutflowCells.Add(idx);
}
}
}
bool hasGhostOutflow =
ghostOutflowCells.Count > 0 &&
simulator.SetGhostFreeOutflowCells(new List<int>(ghostOutflowCells).ToArray());
if (!hasGhostOutflow)
{
simulator.ClearGhostFreeOutflowCells();
}
int totalCells = simulator.gridRes * simulator.gridRes;
float[] perCell = new float[totalCells];
bool hasDepthForcing = false;
@@ -834,6 +873,11 @@ namespace FloodSWE.Networking
foreach (var pair in activeSinkCells)
{
int boundaryId = pair.Key;
if (boundarySinkIdsWithGhostOutflow.Contains(boundaryId))
{
continue;
}
float rate = 0.0f;
if (TryResolveBoundaryProfile("sink", boundaryId, out SweBoundaryProfile profile) && profile != null && profile.enabled)
{
@@ -897,22 +941,38 @@ namespace FloodSWE.Networking
forcedDepthCells = 0;
}
if (!hasGhostForcing && !hasDepthForcing)
if (!hasGhostForcing && !hasDepthForcing && !hasGhostOutflow)
{
lastForcedCellCount = 0;
lastForcingStatus = "disabled:no_active_boundary_profiles";
return;
}
lastForcedCellCount = ghostIndices.Count + forcedDepthCells;
if (hasGhostForcing && hasDepthForcing)
lastForcedCellCount = ghostIndices.Count + forcedDepthCells + (hasGhostOutflow ? ghostOutflowCells.Count : 0);
if (hasGhostForcing && hasDepthForcing && hasGhostOutflow)
{
lastForcingStatus = "ghost+localized+free_outflow";
}
else if (hasGhostForcing && hasDepthForcing)
{
lastForcingStatus = "ghost+localized";
}
else if (hasGhostForcing && hasGhostOutflow)
{
lastForcingStatus = "ghost+free_outflow";
}
else if (hasDepthForcing && hasGhostOutflow)
{
lastForcingStatus = "localized+free_outflow";
}
else if (hasGhostForcing)
{
lastForcingStatus = "ghost_only";
}
else if (hasGhostOutflow)
{
lastForcingStatus = "free_outflow_only";
}
else
{
lastForcingStatus = "localized";
@@ -921,13 +981,14 @@ namespace FloodSWE.Networking
if (verboseDiagnostics)
{
Debug.Log(
$"SweServerRuntime: forcing applied status={lastForcingStatus} ghost={ghostIndices.Count} depth={forcedDepthCells}");
$"SweServerRuntime: forcing applied status={lastForcingStatus} ghost={ghostIndices.Count} depth={forcedDepthCells} freeOutflow={ghostOutflowCells.Count}");
}
}
private void RecomputeLegacyBoundaryDepthRate()
{
simulator.ClearGhostBoundaryOverrides();
simulator.ClearGhostFreeOutflowCells();
var sourceRates = new Dictionary<int, float>();
foreach (int sourceId in activeSourceIds)

View File

@@ -253,6 +253,7 @@ namespace FloodSWE.Networking
public SweBoundaryTileIdRef[] boundary_inflow_ids;
public SweBoundaryTileIdRef[] source_area_ids;
public SweBoundaryTileCellGroup[] boundary_cells;
public SweBoundaryTileCellGroup[] boundary_sink_cells;
public SweBoundaryTileCellGroup[] source_area_cells;
public SweBoundaryTileCellGroup[] sink_cells;
public string source_id_path;
@@ -265,6 +266,7 @@ namespace FloodSWE.Networking
get
{
return (boundary_cells != null && boundary_cells.Length > 0) ||
(boundary_sink_cells != null && boundary_sink_cells.Length > 0) ||
(source_area_cells != null && source_area_cells.Length > 0) ||
(sink_cells != null && sink_cells.Length > 0);
}