diff --git a/Assets/FloodSWE/Compute/SWE_GhostExchange.compute b/Assets/FloodSWE/Compute/SWE_GhostExchange.compute index e1bc234cb..cd06b8255 100644 --- a/Assets/FloodSWE/Compute/SWE_GhostExchange.compute +++ b/Assets/FloodSWE/Compute/SWE_GhostExchange.compute @@ -13,6 +13,7 @@ Texture2D _VelWest; Texture2D _GhostOverrideMask; Texture2D _GhostOverrideWater; Texture2D _GhostOverrideVel; +Texture2D _GhostFreeOutflowMask; RWTexture2D _WaterOut; RWTexture2D _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) diff --git a/Assets/FloodSWE/Scripts/SweTileSimulator.cs b/Assets/FloodSWE/Scripts/SweTileSimulator.cs index 72fc6447b..deeee78d2 100644 --- a/Assets/FloodSWE/Scripts/SweTileSimulator.cs +++ b/Assets/FloodSWE/Scripts/SweTileSimulator.cs @@ -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) diff --git a/Assets/Scripts/Networking/Server/SweServerRuntime.cs b/Assets/Scripts/Networking/Server/SweServerRuntime.cs index e66d256b0..04de497b4 100644 --- a/Assets/Scripts/Networking/Server/SweServerRuntime.cs +++ b/Assets/Scripts/Networking/Server/SweServerRuntime.cs @@ -65,6 +65,7 @@ namespace FloodSWE.Networking private HashSet activeSourceIds = new HashSet(); private HashSet activeSinkIds = new HashSet(); private readonly Dictionary activeBoundaryInflowGhostCells = new Dictionary(); + private readonly Dictionary activeBoundarySinkGhostCells = new Dictionary(); private readonly Dictionary activeSourceAreaCells = new Dictionary(); private readonly Dictionary activeSinkCells = new Dictionary(); private bool activeTileHasBoundaryCellGroups; @@ -692,6 +693,7 @@ namespace FloodSWE.Networking activeSourceIds = new HashSet(); activeSinkIds = new HashSet(); 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(128); var ghostLevels = new List(128); var ghostVelocities = new List(128); + var ghostOutflowCells = new HashSet(); + var boundarySinkIdsWithGhostOutflow = new HashSet(); 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(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(); foreach (int sourceId in activeSourceIds) diff --git a/Assets/Scripts/Networking/Shared/SweBoundaryManifest.cs b/Assets/Scripts/Networking/Shared/SweBoundaryManifest.cs index 14f77e083..935807875 100644 --- a/Assets/Scripts/Networking/Shared/SweBoundaryManifest.cs +++ b/Assets/Scripts/Networking/Shared/SweBoundaryManifest.cs @@ -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); }