Files
Fishing2/Packages/com.nwh.dynamicwaterphysics/Samples~/KWSIntegration/KWSWaterDataProvider.cs
2026-02-27 17:44:21 +08:00

183 lines
7.0 KiB
C#

// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
using KWS;
using NWH.DWP2.WaterObjects;
using UnityEngine;
using System.Collections.Generic;
namespace NWH.DWP2.WaterData
{
/// <summary>
/// KWS only supports one active request at a time. This provider batches all WaterObject
/// queries into a single request per frame, then distributes results back.
/// </summary>
[DefaultExecutionOrder(-50)]
public class KWSWaterDataProvider : WaterDataProvider
{
// Single shared request for batch queries
private WaterSurfaceRequestArray _request = new WaterSurfaceRequestArray();
// Separate request for single-point queries (Engine.Submerged, etc.)
private WaterSurfaceRequestArray _singleRequest = new WaterSurfaceRequestArray();
private Vector3[] _singlePointArray = new Vector3[1];
private float _lastSingleHeight;
// Double-buffered batching: collect current frame, submit previous frame
private List<Vector3> _currentPoints = new List<Vector3>();
private List<Vector3> _pendingPoints = new List<Vector3>();
private List<(int id, int start, int count)> _currentRanges = new List<(int, int, int)>();
private List<(int id, int start, int count)> _pendingRanges = new List<(int, int, int)>();
// Cached results per WaterObject
private Dictionary<int, (float[] heights, Vector3[] flows, bool valid)> _cachedResults
= new Dictionary<int, (float[], Vector3[], bool)>();
public override bool SupportsWaterHeightQueries() => true;
public override bool SupportsWaterNormalQueries() => false;
public override bool SupportsWaterFlowQueries() => true;
// Override to prevent single-point queries from corrupting batch cache
public override float GetWaterHeightSingle(WaterObject waterObject, Vector3 point)
{
_singlePointArray[0] = point;
_singleRequest.SetNewPositions(_singlePointArray);
WaterSystem.TryGetWaterSurfaceData(_singleRequest);
if (_singleRequest.IsDataReady)
{
_lastSingleHeight = _singleRequest.Result[0].Position.y;
}
return _lastSingleHeight;
}
private void FixedUpdate()
{
// Check if pending request has results
if (_pendingPoints.Count > 0 && _request.IsDataReady)
{
DistributeResults();
}
// Submit current batch (becomes pending)
if (_currentPoints.Count > 0)
{
// Swap buffers
(_currentPoints, _pendingPoints) = (_pendingPoints, _currentPoints);
(_currentRanges, _pendingRanges) = (_pendingRanges, _currentRanges);
_request.SetNewPositions(_pendingPoints.ToArray());
WaterSystem.TryGetWaterSurfaceData(_request);
}
// Clear current for new collection
_currentPoints.Clear();
_currentRanges.Clear();
}
public override void GetWaterHeights(WaterObject waterObject, ref Vector3[] points, ref float[] waterHeights)
{
int id = waterObject.instanceID;
// Add this WaterObject's points to current batch
int startIndex = _currentPoints.Count;
for (int i = 0; i < points.Length; i++)
{
_currentPoints.Add(points[i]);
}
_currentRanges.Add((id, startIndex, points.Length));
// Ensure cache exists and is large enough (only grow, never shrink)
if (!_cachedResults.TryGetValue(id, out var cached) ||
cached.heights == null || cached.heights.Length < points.Length)
{
_cachedResults[id] = (new float[points.Length], new Vector3[points.Length], false);
cached = _cachedResults[id];
}
// Return cached values
if (cached.valid)
{
int count = Mathf.Min(cached.heights.Length, waterHeights.Length);
for (int i = 0; i < count; i++)
{
waterHeights[i] = cached.heights[i];
}
}
}
private void DistributeResults()
{
var results = _request.Result;
if (results == null) return;
// Use pending ranges (matches the pending request that just completed)
foreach (var (id, start, count) in _pendingRanges)
{
if (!_cachedResults.TryGetValue(id, out var cached)) continue;
if (start >= results.Length) continue;
var heights = cached.heights;
var flows = cached.flows;
int resultCount = Mathf.Min(count, results.Length - start);
for (int i = 0; i < resultCount; i++)
{
heights[i] = results[start + i].Position.y;
flows[i] = results[start + i].Velocity;
}
_cachedResults[id] = (heights, flows, true);
}
}
public override void GetWaterFlows(WaterObject waterObject, ref Vector3[] points, ref Vector3[] waterFlows)
{
int id = waterObject.instanceID;
if (!_cachedResults.TryGetValue(id, out var cached) || !cached.valid || cached.flows == null)
return;
int count = Mathf.Min(cached.flows.Length, waterFlows.Length);
for (int i = 0; i < count; i++)
{
waterFlows[i] = cached.flows[i];
}
}
}
}
#if UNITY_EDITOR
namespace NWH.DWP2.WaterData
{
using UnityEditor;
[CustomEditor(typeof(KWSWaterDataProvider))]
[CanEditMultipleObjects]
public class KWSWaterDataProviderEditor : WaterDataProviderEditor
{
protected override void DrawStatus(WaterDataProvider provider)
{
drawer.BeginSubsection("Status");
bool hasWaterSystem = WaterSystem.Instance != null;
if (hasWaterSystem)
{
drawer.Info("KWS Water System found in scene.");
}
else
{
drawer.Info("KWS Water System not found in scene.", MessageType.Warning);
}
drawer.EndSubsection();
}
}
}
#endif