移除水
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53c547efc6e8b4d068d60adf2aae4695
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,143 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug draw crosses in an area around the GameObject on the water surface.
|
||||
/// </summary>
|
||||
[@ExecuteDuringEditMode]
|
||||
[AddComponentMenu(Constants.k_MenuPrefixDebug + "Collision Area Visualizer")]
|
||||
sealed class CollisionAreaVisualizer : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
[Tooltip(ICollisionProvider.k_LayerTooltip)]
|
||||
[SerializeField]
|
||||
internal CollisionLayer _Layer;
|
||||
|
||||
[SerializeField]
|
||||
float _ObjectWidth = 0f;
|
||||
|
||||
[SerializeField]
|
||||
float _StepSize = 5f;
|
||||
|
||||
[SerializeField]
|
||||
int _Steps = 10;
|
||||
|
||||
[SerializeField]
|
||||
bool _UseDisplacements;
|
||||
|
||||
[SerializeField]
|
||||
bool _UseNormals;
|
||||
|
||||
float[] _ResultHeights;
|
||||
Vector3[] _ResultDisplacements;
|
||||
Vector3[] _ResultNormals;
|
||||
Vector3[] _SamplePositions;
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
if (water.AnimatedWavesLod.Provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ResultHeights == null || _ResultHeights.Length != _Steps * _Steps)
|
||||
{
|
||||
_ResultHeights = new float[_Steps * _Steps];
|
||||
}
|
||||
if (_ResultDisplacements == null || _ResultDisplacements.Length != _Steps * _Steps)
|
||||
{
|
||||
_ResultDisplacements = new Vector3[_Steps * _Steps];
|
||||
}
|
||||
if (_ResultNormals == null || _ResultNormals.Length != _Steps * _Steps)
|
||||
{
|
||||
_ResultNormals = new Vector3[_Steps * _Steps];
|
||||
|
||||
for (var i = 0; i < _ResultNormals.Length; i++)
|
||||
{
|
||||
_ResultNormals[i] = Vector3.up;
|
||||
}
|
||||
}
|
||||
if (_SamplePositions == null || _SamplePositions.Length != _Steps * _Steps)
|
||||
{
|
||||
_SamplePositions = new Vector3[_Steps * _Steps];
|
||||
}
|
||||
|
||||
var collProvider = water.AnimatedWavesLod.Provider;
|
||||
|
||||
for (var i = 0; i < _Steps; i++)
|
||||
{
|
||||
for (var j = 0; j < _Steps; j++)
|
||||
{
|
||||
_SamplePositions[j * _Steps + i] = new(((i + 0.5f) - _Steps / 2f) * _StepSize, 0f, ((j + 0.5f) - _Steps / 2f) * _StepSize);
|
||||
_SamplePositions[j * _Steps + i].x += transform.position.x;
|
||||
_SamplePositions[j * _Steps + i].z += transform.position.z;
|
||||
}
|
||||
}
|
||||
|
||||
if (_UseDisplacements)
|
||||
{
|
||||
if (collProvider.RetrieveSucceeded(collProvider.Query(GetHashCode(), _ObjectWidth, _SamplePositions, _ResultDisplacements, _UseNormals ? _ResultNormals : null, null, _Layer)))
|
||||
{
|
||||
for (var i = 0; i < _Steps; i++)
|
||||
{
|
||||
for (var j = 0; j < _Steps; j++)
|
||||
{
|
||||
var result = _SamplePositions[j * _Steps + i];
|
||||
result.y = water.SeaLevel;
|
||||
result += _ResultDisplacements[j * _Steps + i];
|
||||
|
||||
var norm = _UseNormals ? _ResultNormals[j * _Steps + i] : Vector3.up;
|
||||
|
||||
DebugDrawCross(result, norm, Mathf.Min(_StepSize / 4f, 1f), Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (collProvider.RetrieveSucceeded(collProvider.Query(GetHashCode(), _ObjectWidth, _SamplePositions, _ResultHeights, _UseNormals ? _ResultNormals : null, null, _Layer)))
|
||||
{
|
||||
for (var i = 0; i < _Steps; i++)
|
||||
{
|
||||
for (var j = 0; j < _Steps; j++)
|
||||
{
|
||||
var result = _SamplePositions[j * _Steps + i];
|
||||
result.y = _ResultHeights[j * _Steps + i];
|
||||
|
||||
var norm = _UseNormals ? _ResultNormals[j * _Steps + i] : Vector3.up;
|
||||
|
||||
DebugDrawCross(result, norm, Mathf.Min(_StepSize / 4f, 1f), Color.green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DebugDrawCross(Vector3 pos, float r, Color col, float duration = 0f)
|
||||
{
|
||||
Debug.DrawLine(pos - Vector3.up * r, pos + Vector3.up * r, col, duration);
|
||||
Debug.DrawLine(pos - Vector3.right * r, pos + Vector3.right * r, col, duration);
|
||||
Debug.DrawLine(pos - Vector3.forward * r, pos + Vector3.forward * r, col, duration);
|
||||
}
|
||||
|
||||
public static void DebugDrawCross(Vector3 pos, Vector3 up, float r, Color col, float duration = 0f)
|
||||
{
|
||||
up.Normalize();
|
||||
var right = Vector3.Normalize(Vector3.Cross(up, Vector3.forward));
|
||||
var forward = Vector3.Cross(up, right);
|
||||
Debug.DrawLine(pos - up * r, pos + up * r, col, duration);
|
||||
Debug.DrawLine(pos - right * r, pos + right * r, col, duration);
|
||||
Debug.DrawLine(pos - forward * r, pos + forward * r, col, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02e47243a77e64c84b8a2d351386a074
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,82 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// NOTE: DWP2 depends on this file. Any API changes need to be communicated to the DWP2 authors in advance.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
// Linter does not support mixing inheritdoc plus defining own parameters.
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// A layer/event where queries are executed.
|
||||
/// </summary>
|
||||
public enum CollisionLayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Include all displacement.
|
||||
/// </summary>
|
||||
[Tooltip("Include all displacement.")]
|
||||
Everything,
|
||||
|
||||
/// <summary>
|
||||
/// Only include Animated Waves.
|
||||
/// </summary>
|
||||
[Tooltip("Only include Animated Waves.")]
|
||||
AfterAnimatedWaves,
|
||||
|
||||
/// <summary>
|
||||
/// Include Animated Waves and Dynamic Waves.
|
||||
/// </summary>
|
||||
[Tooltip("Include Animated Waves and Dynamic Waves.")]
|
||||
AfterDynamicWaves,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for an object that returns water surface displacement and height.
|
||||
/// </summary>
|
||||
public interface ICollisionProvider : IQueryProvider
|
||||
{
|
||||
internal const string k_LayerTooltip = "Which water collision layer to target.";
|
||||
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gives a flat, still water.
|
||||
/// </summary>
|
||||
internal sealed class NoneProvider : ICollisionProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything)
|
||||
{
|
||||
if (result0 != null) System.Array.Fill(result0, Vector3.zero);
|
||||
if (result1 != null) System.Array.Fill(result1, Vector3.up);
|
||||
if (result2 != null) System.Array.Fill(result2, Vector3.zero);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public int Query(int _0, float _1, Vector3[] _2, float[] result0, Vector3[] result1, Vector3[] result2, CollisionLayer _3 = CollisionLayer.Everything)
|
||||
{
|
||||
if (result0 != null) System.Array.Fill(result0, WaterRenderer.Instance.SeaLevel);
|
||||
if (result1 != null) System.Array.Fill(result1, Vector3.up);
|
||||
if (result2 != null) System.Array.Fill(result2, Vector3.zero);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query water physical data at a set of points. Pass in null to any out parameters that are not required.
|
||||
/// </summary>
|
||||
/// <param name="heights">Resulting heights (displacement Y + sea level) at the query positions. Pass null if this information is not required.</param>
|
||||
/// <param name="normals">Resulting normals at the query positions. Pass null if this information is not required.</param>
|
||||
/// <param name="velocities">Resulting velocities at the query positions. Pass null if this information is not required.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything);
|
||||
|
||||
/// <param name="displacements">Resulting displacement vectors at the query positions. Add sea level to Y to get world space height.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
/// <inheritdoc cref="Query(int, float, Vector3[], float[], Vector3[], Vector3[], CollisionLayer)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55075bf334a1445828a0ad179248fe9c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,148 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Samples water surface shape - displacement, height, normal, velocity.
|
||||
/// </summary>
|
||||
sealed class CollisionQuery : QueryBase, ICollisionProvider
|
||||
{
|
||||
public CollisionQuery(WaterRenderer water) : base(water) { }
|
||||
protected override int Kernel => 0;
|
||||
|
||||
public int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] resultDisplacements, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
if (!UpdateQueryPoints(ownerHash, minSpatialLength, queryPoints, resultNormals != null ? queryPoints : null))
|
||||
{
|
||||
result |= (int)QueryStatus.PostFailed;
|
||||
}
|
||||
|
||||
if (!RetrieveResults(ownerHash, resultDisplacements, null, resultNormals))
|
||||
{
|
||||
result |= (int)QueryStatus.RetrieveFailed;
|
||||
}
|
||||
|
||||
if (resultVelocities != null)
|
||||
{
|
||||
result |= CalculateVelocities(ownerHash, resultVelocities);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int Query(int ownerHash, float minimumSpatialLength, Vector3[] queryPoints, float[] resultHeights, Vector3[] resultNormals, Vector3[] resultVelocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
if (!UpdateQueryPoints(ownerHash, minimumSpatialLength, queryPoints, resultNormals != null ? queryPoints : null))
|
||||
{
|
||||
result |= (int)QueryStatus.PostFailed;
|
||||
}
|
||||
|
||||
if (!RetrieveResults(ownerHash, null, resultHeights, resultNormals))
|
||||
{
|
||||
result |= (int)QueryStatus.RetrieveFailed;
|
||||
}
|
||||
|
||||
if (resultVelocities != null)
|
||||
{
|
||||
result |= CalculateVelocities(ownerHash, resultVelocities);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class CollisionQueryWithPasses : ICollisionProvider, IQueryable
|
||||
{
|
||||
readonly CollisionQuery _AnimatedWaves;
|
||||
readonly CollisionQuery _DynamicWaves;
|
||||
readonly CollisionQuery _Displacement;
|
||||
readonly WaterRenderer _Water;
|
||||
|
||||
public int ResultGuidCount => _AnimatedWaves.ResultGuidCount + _DynamicWaves.ResultGuidCount + _Displacement.ResultGuidCount;
|
||||
public int RequestCount => _AnimatedWaves.RequestCount + _DynamicWaves.RequestCount + _Displacement.RequestCount;
|
||||
public int QueryCount => _AnimatedWaves.QueryCount + _DynamicWaves.QueryCount + _Displacement.QueryCount;
|
||||
|
||||
public CollisionQueryWithPasses(WaterRenderer water)
|
||||
{
|
||||
_Water = water;
|
||||
_AnimatedWaves = new(water);
|
||||
_DynamicWaves = new(water);
|
||||
_Displacement = new(water);
|
||||
}
|
||||
|
||||
// Gets the provider for the given layer, falling back to previous layer when needed.
|
||||
CollisionQuery GetProvider(CollisionLayer layer)
|
||||
{
|
||||
var layers = _Water.AnimatedWavesLod._CollisionLayers;
|
||||
|
||||
if (layers == CollisionLayers.Nothing)
|
||||
{
|
||||
return _Displacement;
|
||||
}
|
||||
|
||||
if (layer == CollisionLayer.Everything)
|
||||
{
|
||||
if (layers.HasFlag(CollisionLayers.Displacement))
|
||||
{
|
||||
return _Displacement;
|
||||
}
|
||||
}
|
||||
|
||||
if (layer >= CollisionLayer.AfterDynamicWaves)
|
||||
{
|
||||
if (layers.HasFlag(CollisionLayers.DynamicWaves) && _Water.DynamicWavesLod.Enabled)
|
||||
{
|
||||
return _DynamicWaves;
|
||||
}
|
||||
}
|
||||
|
||||
return _AnimatedWaves;
|
||||
}
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, float[] heights, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
return GetProvider(layer).Query(hash, minimumLength, points, heights, normals, velocities);
|
||||
}
|
||||
|
||||
public int Query(int hash, float minimumLength, Vector3[] points, Vector3[] displacements, Vector3[] normals, Vector3[] velocities, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
return GetProvider(layer).Query(hash, minimumLength, points, displacements, normals, velocities);
|
||||
}
|
||||
|
||||
public void UpdateQueries(WaterRenderer water, CollisionLayer layer)
|
||||
{
|
||||
switch (layer)
|
||||
{
|
||||
case CollisionLayer.Everything: _Displacement.UpdateQueries(water); break;
|
||||
case CollisionLayer.AfterAnimatedWaves: _AnimatedWaves.UpdateQueries(water); break;
|
||||
case CollisionLayer.AfterDynamicWaves: _DynamicWaves.UpdateQueries(water); break;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateQueries(WaterRenderer water)
|
||||
{
|
||||
_Displacement.UpdateQueries(water);
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
_AnimatedWaves.CleanUp();
|
||||
_DynamicWaves.CleanUp();
|
||||
_Displacement.CleanUp();
|
||||
}
|
||||
}
|
||||
|
||||
static partial class Extensions
|
||||
{
|
||||
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water, CollisionLayer layer) => (self as CollisionQueryWithPasses)?.UpdateQueries(water, layer);
|
||||
public static void UpdateQueries(this ICollisionProvider self, WaterRenderer water) => (self as IQueryable)?.UpdateQueries(water);
|
||||
public static void CleanUp(this ICollisionProvider self) => (self as IQueryable)?.CleanUp();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cddd187674954ce1a0e2e19017a744a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,98 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to trace a ray against the water surface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Works by sampling at a set of points along the ray and interpolating the
|
||||
/// intersection location.
|
||||
/// </remarks>
|
||||
public sealed class RayCastHelper : Internal.SampleHelper
|
||||
{
|
||||
readonly float _RayStepSize;
|
||||
readonly float _MinimumLength;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the ray and the step size must be given here. The smaller the step size, the greater the accuracy.
|
||||
/// </summary>
|
||||
/// <param name="rayLength">Length of the ray.</param>
|
||||
/// <param name="rayStepSize">Size of the step. With length the number of steps is computed.</param>
|
||||
public RayCastHelper(float rayLength, float rayStepSize = 2f) : base(ComputeQueryCount(rayLength, ref rayStepSize))
|
||||
{
|
||||
_RayStepSize = rayStepSize;
|
||||
// Waves go max double along min length. Thats too much - only allow half of a wave per step.
|
||||
_MinimumLength = _RayStepSize * 4f;
|
||||
}
|
||||
|
||||
static int ComputeQueryCount(float rayLength, ref float rayStepSize)
|
||||
{
|
||||
Debug.Assert(rayLength > 0f);
|
||||
Debug.Assert(rayStepSize > 0f);
|
||||
|
||||
var stepCount = Mathf.CeilToInt(rayLength / rayStepSize) + 1;
|
||||
|
||||
var maxStepCount = 128;
|
||||
if (stepCount > maxStepCount)
|
||||
{
|
||||
stepCount = maxStepCount;
|
||||
rayStepSize = rayLength / (stepCount - 1f);
|
||||
Debug.LogWarning($"Crest: RayTraceHelper: ray steps exceed maximum ({maxStepCount}), step size increased to {rayStepSize} to reduce step count.");
|
||||
}
|
||||
|
||||
return stepCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this once each frame to do the query.
|
||||
/// </summary>
|
||||
/// <param name="origin">World space position of ray origin</param>
|
||||
/// <param name="direction">World space ray direction</param>
|
||||
/// <param name="distance">The distance along the ray to the first intersection with the water surface.</param>
|
||||
/// <param name="layer">The layer this ray targets.</param>
|
||||
/// <returns>True if the results have come back from the GPU, and if the ray intersects the water surface.</returns>
|
||||
public bool RayCast(Vector3 origin, Vector3 direction, out float distance, CollisionLayer layer = CollisionLayer.Everything)
|
||||
{
|
||||
distance = -1f;
|
||||
|
||||
Validate(allowMultipleCallsPerFrame: false);
|
||||
|
||||
var water = WaterRenderer.Instance;
|
||||
var provider = water == null ? null : water.AnimatedWavesLod.Provider;
|
||||
if (provider == null) return false;
|
||||
|
||||
for (var i = 0; i < _QueryPosition.Length; i++)
|
||||
{
|
||||
_QueryPosition[i] = origin + i * _RayStepSize * direction;
|
||||
}
|
||||
|
||||
var status = provider.Query(GetHashCode(), _MinimumLength, _QueryPosition, _QueryResult, null, null, layer);
|
||||
|
||||
if (!provider.RetrieveSucceeded(status))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now that data is available, compare the height of the water to the height of each point of the ray. If
|
||||
// the ray crosses the surface, the distance to the intersection is interpolated from the heights.
|
||||
for (var i = 1; i < _QueryPosition.Length; i++)
|
||||
{
|
||||
var height0 = _QueryResult[i - 1].y + water.SeaLevel - _QueryPosition[i - 1].y;
|
||||
var height1 = _QueryResult[i].y + water.SeaLevel - _QueryPosition[i].y;
|
||||
|
||||
if (Mathf.Sign(height0) != Mathf.Sign(height1))
|
||||
{
|
||||
var prop = Mathf.Abs(height0) / (Mathf.Abs(height0) + Mathf.Abs(height1));
|
||||
distance = (i - 1 + prop) * _RayStepSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return distance >= 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f1f458d6962ed46be8f00bdff45b2725
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,44 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Debug draw a line trace from this gameobjects position, in this gameobjects forward direction.
|
||||
/// </summary>
|
||||
[@ExecuteDuringEditMode]
|
||||
[AddComponentMenu(Constants.k_MenuPrefixDebug + "Ray Cast Visualizer")]
|
||||
sealed class RayTraceVisualizer : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
readonly RayCastHelper _RayCast = new(50f, 2f);
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
if (water.AnimatedWavesLod.Provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Even if only a single ray trace is desired, this still must be called every frame until it returns true
|
||||
if (_RayCast.RayCast(transform.position, transform.forward, out var dist))
|
||||
{
|
||||
var endPos = transform.position + transform.forward * dist;
|
||||
Debug.DrawLine(transform.position, endPos, Color.green);
|
||||
CollisionAreaVisualizer.DebugDrawCross(endPos, 2f, Color.green, 0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.DrawLine(transform.position, transform.position + transform.forward * 50f, Color.red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 159faa53a32b34aff9d79e974c14d100
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9f84066852dc4c839cf39679aa347db
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,34 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
// Linter does not support mixing inheritdoc plus defining own parameters.
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an object that returns water depth and distance to water edge.
|
||||
/// </summary>
|
||||
public interface IDepthProvider : IQueryProvider
|
||||
{
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
internal sealed class NoneProvider : IDepthProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result)
|
||||
{
|
||||
if (result != null) System.Array.Clear(result, 0, result.Length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query water depth data at a set of points.
|
||||
/// </summary>
|
||||
/// <param name="results">Water depth and distance to shoreline (XY respectively). Both are signed.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c8c76a0ab417499bb25ecea61560108
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,30 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
sealed class DepthQuery : QueryBase, IDepthProvider
|
||||
{
|
||||
public DepthQuery(WaterRenderer water) : base(water) { }
|
||||
protected override int Kernel => 2;
|
||||
|
||||
public override int Query(int hash, float minimumSpatialLength, Vector3[] queries, Vector3[] results)
|
||||
{
|
||||
var id = base.Query(hash, minimumSpatialLength, queries, results);
|
||||
|
||||
// Infinity will become NaN. Convert back to infinity.
|
||||
// Negative infinity should not happen.
|
||||
for (var i = 0; i < results.Length; i++)
|
||||
{
|
||||
var value = results[i];
|
||||
if (float.IsNaN(value.x)) value.x = float.PositiveInfinity;
|
||||
if (float.IsNaN(value.y)) value.y = float.PositiveInfinity;
|
||||
results[i] = value;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92350636082384718babd120eff1cbf0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,8 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cacd27cd27186443ba5376920b9fd300
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,37 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
// Linter does not support mixing inheritdoc + defining own parameters.
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for an object that returns water surface displacement and height.
|
||||
/// </summary>
|
||||
public interface IFlowProvider : IQueryProvider
|
||||
{
|
||||
internal static NoneProvider None { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gives a stationary water (no horizontal flow).
|
||||
/// </summary>
|
||||
internal sealed class NoneProvider : IFlowProvider
|
||||
{
|
||||
public int Query(int _0, float _1, Vector3[] _2, Vector3[] result)
|
||||
{
|
||||
if (result != null) System.Array.Clear(result, 0, result.Length);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Query water flow data (horizontal motion) at a set of points.
|
||||
/// </summary>
|
||||
/// <param name="results">Water surface flow velocities at the query positions.</param>
|
||||
/// <inheritdoc cref="IQueryProvider.Query(int, float, Vector3[], int)" />
|
||||
int Query(int hash, float minimumLength, Vector3[] points, Vector3[] results);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c710a5a446bea43bdbd39db4be198f98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,14 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Samples horizontal motion of water volume
|
||||
/// </summary>
|
||||
sealed class FlowQuery : QueryBase, IFlowProvider
|
||||
{
|
||||
public FlowQuery(WaterRenderer water) : base(water) { }
|
||||
protected override int Kernel => 1;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a646106f5d35b4abea362416da973f83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,636 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
// Potential improvements
|
||||
// - Half return values
|
||||
// - Half minGridSize
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Base interface for providers.
|
||||
/// </summary>
|
||||
public interface IQueryProvider
|
||||
{
|
||||
// NOTE: Here for documentation reuse.
|
||||
/// <param name="hash">Unique ID for calling code. Typically acquired by calling GetHashCode.</param>
|
||||
/// <param name="minimumLength">The minimum spatial length of the object, such as the width of a boat. Useful for filtering out detail when not needed. Set to zero to get full available detail.</param>
|
||||
/// <param name="points">The world space points that will be queried.</param>
|
||||
/// <param name="layer">The layer this query targets.</param>
|
||||
/// <returns>The status of the query.</returns>
|
||||
internal static int Query(int hash, float minimumLength, Vector3[] points, int layer) => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Check if the query results could be retrieved successfully using the return code
|
||||
/// from Query method.
|
||||
/// </summary>
|
||||
/// <param name="status">The query status returned from Query.</param>
|
||||
/// <returns>Whether the retrieve was successful.</returns>
|
||||
bool RetrieveSucceeded(int status)
|
||||
{
|
||||
return (status & (int)QueryBase.QueryStatus.RetrieveFailed) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
interface IQueryable
|
||||
{
|
||||
int ResultGuidCount { get; }
|
||||
int RequestCount { get; }
|
||||
int QueryCount { get; }
|
||||
void UpdateQueries(WaterRenderer water);
|
||||
void CleanUp();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides heights and other physical data about the water surface. Works by uploading query positions to GPU and computing
|
||||
/// the data and then transferring back the results asynchronously. An exception to this is water surface velocities - these can
|
||||
/// not be computed on the GPU and are instead computed on the CPU by retaining last frames' query results and computing finite diffs.
|
||||
/// </summary>
|
||||
abstract class QueryBase : IQueryable
|
||||
{
|
||||
protected abstract int Kernel { get; }
|
||||
|
||||
// 4 was enough for a long time, but Linux setups seems to demand 7
|
||||
const int k_MaximumRequests = 7;
|
||||
const int k_MaximumGuids = 1024;
|
||||
|
||||
// We need only two additional queries to compute normals.
|
||||
const int k_NormalAdditionalQueryCount = 2;
|
||||
|
||||
readonly WaterRenderer _Water;
|
||||
|
||||
readonly PropertyWrapperCompute _Wrapper;
|
||||
|
||||
readonly System.Action<AsyncGPUReadbackRequest> _DataArrivedAction;
|
||||
|
||||
// Must match value in compute shader
|
||||
const int k_ComputeGroupSize = 64;
|
||||
|
||||
static class ShaderIDs
|
||||
{
|
||||
public static readonly int s_QueryPositions_MinimumGridSizes = Shader.PropertyToID("_Crest_QueryPositions_MinimumGridSizes");
|
||||
}
|
||||
|
||||
|
||||
const float k_FiniteDifferenceDx = 0.1f;
|
||||
|
||||
readonly ComputeBuffer _ComputeBufferQueries;
|
||||
readonly ComputeBuffer _ComputeBufferResults;
|
||||
|
||||
internal const int k_DefaultMaximumQueryCount = 4096;
|
||||
|
||||
readonly int _MaximumQueryCount = k_DefaultMaximumQueryCount;
|
||||
readonly Vector3[] _QueryPositionXZ_MinimumGridSize = new Vector3[k_DefaultMaximumQueryCount];
|
||||
|
||||
/// <summary>
|
||||
/// Holds information about all query points. Maps from unique hash code to position in point array.
|
||||
/// </summary>
|
||||
sealed class SegmentRegistrar
|
||||
{
|
||||
// Map from guids to (segment start index, segment end index, frame number when query was made)
|
||||
public Dictionary<int, Vector3Int> _Segments = new();
|
||||
public int _QueryCount = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Since query results return asynchronously and may not return at all (in theory), we keep a ringbuffer
|
||||
/// of the registrars of the last frames so that when data does come back it can be interpreted correctly.
|
||||
/// </summary>
|
||||
sealed class SegmentRegistrarRingBuffer
|
||||
{
|
||||
// Requests in flight plus 2 held values, plus one current
|
||||
static readonly int s_PoolSize = k_MaximumRequests + 2 + 1;
|
||||
|
||||
readonly SegmentRegistrar[] _Segments = new SegmentRegistrar[s_PoolSize];
|
||||
|
||||
public int _SegmentRelease = 0;
|
||||
public int _SegmentAcquire = 0;
|
||||
|
||||
public SegmentRegistrar Current => _Segments[_SegmentAcquire];
|
||||
|
||||
public SegmentRegistrarRingBuffer()
|
||||
{
|
||||
for (var i = 0; i < _Segments.Length; i++)
|
||||
{
|
||||
_Segments[i] = new();
|
||||
}
|
||||
}
|
||||
|
||||
public void AcquireNew()
|
||||
{
|
||||
var lastIndex = _SegmentAcquire;
|
||||
|
||||
{
|
||||
var newSegmentAcquire = (_SegmentAcquire + 1) % _Segments.Length;
|
||||
|
||||
if (newSegmentAcquire == _SegmentRelease)
|
||||
{
|
||||
// The last index has incremented and landed on the first index. This shouldn't happen normally, but
|
||||
// can happen if the Scene and Game view are not visible, in which case async readbacks dont get processed
|
||||
// and the pipeline blocks up.
|
||||
#if !UNITY_EDITOR
|
||||
Debug.LogError("Crest: Query ring buffer exhausted. Please report this to developers.");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
_SegmentAcquire = newSegmentAcquire;
|
||||
}
|
||||
|
||||
// Copy the registrations across from the previous frame. This makes queries persistent. This is needed because
|
||||
// queries are often made from FixedUpdate(), and at high framerates this may not be called, which would mean
|
||||
// the query would get lost and this leads to stuttering and other artifacts.
|
||||
{
|
||||
_Segments[_SegmentAcquire]._QueryCount = 0;
|
||||
_Segments[_SegmentAcquire]._Segments.Clear();
|
||||
|
||||
foreach (var segment in _Segments[lastIndex]._Segments)
|
||||
{
|
||||
var age = Time.frameCount - segment.Value.z;
|
||||
|
||||
// Don't keep queries around if they have not be active in the last 10 frames
|
||||
if (age < 10)
|
||||
{
|
||||
// Compute a new segment range - we may have removed some segments that were too old, so this ensures
|
||||
// we have a nice compact array of queries each frame rather than accumulating persistent air bubbles
|
||||
var newSegment = segment.Value;
|
||||
newSegment.x = _Segments[_SegmentAcquire]._QueryCount;
|
||||
newSegment.y = newSegment.x + (segment.Value.y - segment.Value.x);
|
||||
_Segments[_SegmentAcquire]._QueryCount = newSegment.y + 1;
|
||||
|
||||
_Segments[_SegmentAcquire]._Segments.Add(segment.Key, newSegment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReleaseLast()
|
||||
{
|
||||
_SegmentRelease = (_SegmentRelease + 1) % _Segments.Length;
|
||||
}
|
||||
|
||||
public void RemoveRegistrations(int key)
|
||||
{
|
||||
// Remove the guid for all of the next spare segment registrars. However, don't touch the ones that are being
|
||||
// used for active requests.
|
||||
var i = _SegmentAcquire;
|
||||
while (true)
|
||||
{
|
||||
if (_Segments[i]._Segments.ContainsKey(key))
|
||||
{
|
||||
_Segments[i]._Segments.Remove(key);
|
||||
}
|
||||
|
||||
i = (i + 1) % _Segments.Length;
|
||||
|
||||
if (i == _SegmentRelease)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAvailable()
|
||||
{
|
||||
// Extreme approach - flush all segments for next spare registrars (but don't touch ones being used for active requests)
|
||||
var i = _SegmentAcquire;
|
||||
while (true)
|
||||
{
|
||||
_Segments[i]._Segments.Clear();
|
||||
_Segments[i]._QueryCount = 0;
|
||||
|
||||
i = (i + 1) % _Segments.Length;
|
||||
|
||||
if (i == _SegmentRelease)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
for (var i = 0; i < _Segments.Length; i++)
|
||||
{
|
||||
_Segments[i]._QueryCount = 0;
|
||||
_Segments[i]._Segments.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly SegmentRegistrarRingBuffer _SegmentRegistrarRingBuffer = new();
|
||||
|
||||
NativeArray<Vector3> _QueryResults;
|
||||
float _QueryResultsTime = -1f;
|
||||
Dictionary<int, Vector3Int> _ResultSegments;
|
||||
|
||||
NativeArray<Vector3> _QueryResultsLast;
|
||||
float _QueryResultsTimeLast = -1f;
|
||||
Dictionary<int, Vector3Int> _ResultSegmentsLast;
|
||||
|
||||
struct ReadbackRequest
|
||||
{
|
||||
public AsyncGPUReadbackRequest _Request;
|
||||
public float _DataTimestamp;
|
||||
public Dictionary<int, Vector3Int> _Segments;
|
||||
}
|
||||
|
||||
readonly List<ReadbackRequest> _Requests = new();
|
||||
|
||||
public enum QueryStatus
|
||||
{
|
||||
OK = 0,
|
||||
RetrieveFailed = 1,
|
||||
PostFailed = 2,
|
||||
NotEnoughDataForVels = 4,
|
||||
VelocityDataInvalidated = 8,
|
||||
InvalidDtForVelocity = 16,
|
||||
}
|
||||
|
||||
public QueryBase(WaterRenderer water)
|
||||
{
|
||||
_Water = water;
|
||||
|
||||
_DataArrivedAction = new(DataArrived);
|
||||
|
||||
if (_MaximumQueryCount != water._AnimatedWavesLod.MaximumQueryCount)
|
||||
{
|
||||
_MaximumQueryCount = water._AnimatedWavesLod.MaximumQueryCount;
|
||||
_QueryPositionXZ_MinimumGridSize = new Vector3[_MaximumQueryCount];
|
||||
}
|
||||
|
||||
_ComputeBufferQueries = new(_MaximumQueryCount, 12, ComputeBufferType.Default);
|
||||
_ComputeBufferResults = new(_MaximumQueryCount, 12, ComputeBufferType.Default);
|
||||
|
||||
_QueryResults = new(_MaximumQueryCount, Allocator.Persistent, NativeArrayOptions.ClearMemory);
|
||||
_QueryResultsLast = new(_MaximumQueryCount, Allocator.Persistent, NativeArrayOptions.ClearMemory);
|
||||
|
||||
var shader = WaterResources.Instance.Compute._Query;
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogError($"Crest: Could not load Query compute shader");
|
||||
return;
|
||||
}
|
||||
_Wrapper = new(water.SimulationBuffer, shader, Kernel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a unique request ID and some world space XZ positions, and computes the displacement vector that lands at this position,
|
||||
/// to a good approximation. The world space height of the water at that position is then SeaLevel + displacement.y.
|
||||
/// </summary>
|
||||
protected bool UpdateQueryPoints(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] queryNormals)
|
||||
{
|
||||
if (queryPoints.Length + _SegmentRegistrarRingBuffer.Current._QueryCount > _MaximumQueryCount)
|
||||
{
|
||||
Debug.LogError($"Crest: Max query count ({_MaximumQueryCount}) exceeded, increase the max query count in the Animated Waves Settings to support a higher number of queries.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var segmentRetrieved = false;
|
||||
|
||||
// We'll send in 2 points to get normals
|
||||
var countPts = queryPoints != null ? queryPoints.Length : 0;
|
||||
var countNorms = queryNormals != null ? queryNormals.Length : 0;
|
||||
var countTotal = countPts + countNorms * k_NormalAdditionalQueryCount;
|
||||
|
||||
if (_SegmentRegistrarRingBuffer.Current._Segments.TryGetValue(ownerHash, out var segment))
|
||||
{
|
||||
var segmentSize = segment.y - segment.x + 1;
|
||||
if (segmentSize == countTotal)
|
||||
{
|
||||
// Update frame count
|
||||
segment.z = Time.frameCount;
|
||||
_SegmentRegistrarRingBuffer.Current._Segments[ownerHash] = segment;
|
||||
|
||||
segmentRetrieved = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_SegmentRegistrarRingBuffer.Current._Segments.Remove(ownerHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (countTotal == 0)
|
||||
{
|
||||
// No query data
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!segmentRetrieved)
|
||||
{
|
||||
if (_SegmentRegistrarRingBuffer.Current._Segments.Count >= k_MaximumGuids)
|
||||
{
|
||||
Debug.LogError("Crest: Too many guids registered with CollProviderCompute. Increase s_maxGuids.");
|
||||
return false;
|
||||
}
|
||||
|
||||
segment.x = _SegmentRegistrarRingBuffer.Current._QueryCount;
|
||||
segment.y = segment.x + countTotal - 1;
|
||||
segment.z = Time.frameCount;
|
||||
_SegmentRegistrarRingBuffer.Current._Segments.Add(ownerHash, segment);
|
||||
|
||||
_SegmentRegistrarRingBuffer.Current._QueryCount += countTotal;
|
||||
|
||||
//Debug.Log("Crest: Added points for " + guid);
|
||||
}
|
||||
|
||||
// The smallest wavelengths should repeat no more than twice across the smaller spatial length. Unless we're
|
||||
// in the last LOD - then this is the best we can do.
|
||||
var minWavelength = minSpatialLength / 2f;
|
||||
var samplesPerWave = 2f;
|
||||
var minGridSize = minWavelength / samplesPerWave;
|
||||
|
||||
if (countPts + segment.x > _QueryPositionXZ_MinimumGridSize.Length)
|
||||
{
|
||||
Debug.LogError("Crest: Too many wave height queries. Increase Max Query Count in the Animated Waves Settings.");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var pointi = 0; pointi < countPts; pointi++)
|
||||
{
|
||||
_QueryPositionXZ_MinimumGridSize[pointi + segment.x].x = queryPoints[pointi].x;
|
||||
_QueryPositionXZ_MinimumGridSize[pointi + segment.x].y = queryPoints[pointi].z;
|
||||
_QueryPositionXZ_MinimumGridSize[pointi + segment.x].z = minGridSize;
|
||||
}
|
||||
|
||||
// To compute each normal, post 2 query points (reuse point above)
|
||||
for (var normi = 0; normi < countNorms; normi++)
|
||||
{
|
||||
var arrIdx = segment.x + countPts + k_NormalAdditionalQueryCount * normi;
|
||||
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].x = queryNormals[normi].x + k_FiniteDifferenceDx;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].y = queryNormals[normi].z;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].z = minGridSize;
|
||||
|
||||
arrIdx += 1;
|
||||
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].x = queryNormals[normi].x;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].y = queryNormals[normi].z + k_FiniteDifferenceDx;
|
||||
_QueryPositionXZ_MinimumGridSize[arrIdx].z = minGridSize;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signal that we're no longer servicing queries. Note this leaves an air bubble in the query buffer.
|
||||
/// </summary>
|
||||
void RemoveQueryPoints(int guid)
|
||||
{
|
||||
_SegmentRegistrarRingBuffer.RemoveRegistrations(guid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove air bubbles from the query array. Currently this lazily just nukes all the registered
|
||||
/// query IDs so they'll be recreated next time (generating garbage).
|
||||
/// </summary>
|
||||
void CompactQueryStorage()
|
||||
{
|
||||
_SegmentRegistrarRingBuffer.ClearAvailable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy out displacements, heights, normals. Pass null if info is not required.
|
||||
/// </summary>
|
||||
protected bool RetrieveResults(int guid, Vector3[] displacements, float[] heights, Vector3[] normals)
|
||||
{
|
||||
if (_ResultSegments == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if there are results that came back for this guid
|
||||
if (!_ResultSegments.TryGetValue(guid, out var segment))
|
||||
{
|
||||
// Guid not found - no result
|
||||
return false;
|
||||
}
|
||||
|
||||
var countPoints = 0;
|
||||
if (displacements != null) countPoints = displacements.Length;
|
||||
if (heights != null) countPoints = heights.Length;
|
||||
if (displacements != null && heights != null) Debug.Assert(displacements.Length == heights.Length);
|
||||
var countNorms = normals != null ? normals.Length : 0;
|
||||
|
||||
if (countPoints > 0)
|
||||
{
|
||||
// Retrieve Results
|
||||
if (displacements != null) _QueryResults.Slice(segment.x, countPoints).CopyTo(displacements);
|
||||
|
||||
// Retrieve Result heights
|
||||
if (heights != null)
|
||||
{
|
||||
var seaLevel = _Water.SeaLevel;
|
||||
for (var i = 0; i < countPoints; i++)
|
||||
{
|
||||
heights[i] = seaLevel + _QueryResults[i + segment.x].y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (countNorms > 0)
|
||||
{
|
||||
var firstNorm = segment.x + countPoints;
|
||||
|
||||
var dx = -Vector3.right * k_FiniteDifferenceDx;
|
||||
var dz = -Vector3.forward * k_FiniteDifferenceDx;
|
||||
for (var i = 0; i < countNorms; i++)
|
||||
{
|
||||
var p = _QueryResults[i + segment.x];
|
||||
var px = dx + _QueryResults[firstNorm + k_NormalAdditionalQueryCount * i];
|
||||
var pz = dz + _QueryResults[firstNorm + k_NormalAdditionalQueryCount * i + 1];
|
||||
|
||||
normals[i] = Vector3.Cross(p - px, p - pz).normalized;
|
||||
normals[i].y *= -1f;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute time derivative of the displacements by calculating difference from last query. More complicated than it would seem - results
|
||||
/// may not be available in one or both of the results, or the query locations in the array may change.
|
||||
/// </summary>
|
||||
protected int CalculateVelocities(int ownerHash, Vector3[] results)
|
||||
{
|
||||
// Need at least 2 returned results to do finite difference
|
||||
if (_QueryResultsTime < 0f || _QueryResultsTimeLast < 0f)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!_ResultSegments.TryGetValue(ownerHash, out var segment))
|
||||
{
|
||||
return (int)QueryStatus.RetrieveFailed;
|
||||
}
|
||||
|
||||
if (!_ResultSegmentsLast.TryGetValue(ownerHash, out var segmentLast))
|
||||
{
|
||||
return (int)QueryStatus.NotEnoughDataForVels;
|
||||
}
|
||||
|
||||
if ((segment.y - segment.x) != (segmentLast.y - segmentLast.x))
|
||||
{
|
||||
// Number of queries changed - can't handle that
|
||||
return (int)QueryStatus.VelocityDataInvalidated;
|
||||
}
|
||||
|
||||
var dt = _QueryResultsTime - _QueryResultsTimeLast;
|
||||
if (dt < 0.0001f)
|
||||
{
|
||||
return (int)QueryStatus.InvalidDtForVelocity;
|
||||
}
|
||||
|
||||
var count = results.Length;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
results[i] = (_QueryResults[i + segment.x] - _QueryResultsLast[i + segmentLast.x]) / dt;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame update callback.
|
||||
/// </summary>
|
||||
/// <param name="water">The current <see cref="WaterRenderer"/>.</param>
|
||||
public void UpdateQueries(WaterRenderer water)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
// Seems to be a terrible memory leak coming from creating async GPU readbacks.
|
||||
// This was marked as resolved by Unity and confirmed fixed by forum posts.
|
||||
// May be worth keeping. See issue #630 for more details.
|
||||
if (!water._HeightQueries && !Application.isPlaying) return;
|
||||
#endif
|
||||
|
||||
if (_SegmentRegistrarRingBuffer.Current._QueryCount > 0)
|
||||
{
|
||||
ExecuteQueries();
|
||||
|
||||
// Remove oldest requests if we have hit the limit
|
||||
while (_Requests.Count >= k_MaximumRequests)
|
||||
{
|
||||
_Requests.RemoveAt(0);
|
||||
}
|
||||
|
||||
ReadbackRequest request;
|
||||
request._DataTimestamp = Time.time - Time.deltaTime;
|
||||
request._Request = AsyncGPUReadback.Request(_ComputeBufferResults, _DataArrivedAction);
|
||||
request._Segments = _SegmentRegistrarRingBuffer.Current._Segments;
|
||||
_Requests.Add(request);
|
||||
|
||||
_SegmentRegistrarRingBuffer.AcquireNew();
|
||||
}
|
||||
}
|
||||
|
||||
void ExecuteQueries()
|
||||
{
|
||||
_ComputeBufferQueries.SetData(_QueryPositionXZ_MinimumGridSize, 0, 0, _SegmentRegistrarRingBuffer.Current._QueryCount);
|
||||
_Wrapper.SetBuffer(ShaderIDs.s_QueryPositions_MinimumGridSizes, _ComputeBufferQueries);
|
||||
_Wrapper.SetBuffer(Crest.ShaderIDs.s_Target, _ComputeBufferResults);
|
||||
|
||||
var numGroups = (_SegmentRegistrarRingBuffer.Current._QueryCount + k_ComputeGroupSize - 1) / k_ComputeGroupSize;
|
||||
_Wrapper.Dispatch(numGroups, 1, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a compute buffer has been read back from the GPU to the CPU.
|
||||
/// </summary>
|
||||
void DataArrived(AsyncGPUReadbackRequest req)
|
||||
{
|
||||
// Can get callbacks after disable, so detect this.
|
||||
if (!_QueryResults.IsCreated)
|
||||
{
|
||||
_Requests.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove any error requests
|
||||
for (var i = _Requests.Count - 1; i >= 0; --i)
|
||||
{
|
||||
if (_Requests[i]._Request.hasError)
|
||||
{
|
||||
_Requests.RemoveAt(i);
|
||||
_SegmentRegistrarRingBuffer.ReleaseLast();
|
||||
}
|
||||
}
|
||||
|
||||
// Find the last request that was completed
|
||||
var lastDoneIndex = _Requests.Count - 1;
|
||||
while (lastDoneIndex >= 0 && !_Requests[lastDoneIndex]._Request.done)
|
||||
{
|
||||
--lastDoneIndex;
|
||||
}
|
||||
|
||||
// If there is a completed request, process it
|
||||
if (lastDoneIndex >= 0)
|
||||
{
|
||||
// Update "last" results
|
||||
(_QueryResults, _QueryResultsLast) = (_QueryResultsLast, _QueryResults);
|
||||
_QueryResultsTimeLast = _QueryResultsTime;
|
||||
_ResultSegmentsLast = _ResultSegments;
|
||||
|
||||
var data = _Requests[lastDoneIndex]._Request.GetData<Vector3>();
|
||||
data.CopyTo(_QueryResults);
|
||||
_QueryResultsTime = _Requests[lastDoneIndex]._DataTimestamp;
|
||||
_ResultSegments = _Requests[lastDoneIndex]._Segments;
|
||||
}
|
||||
|
||||
// Remove all the requests up to the last completed one
|
||||
for (var i = lastDoneIndex; i >= 0; --i)
|
||||
{
|
||||
_Requests.RemoveAt(i);
|
||||
_SegmentRegistrarRingBuffer.ReleaseLast();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On destroy, to clean up resources.
|
||||
/// </summary>
|
||||
public void CleanUp()
|
||||
{
|
||||
_ComputeBufferQueries.Dispose();
|
||||
_ComputeBufferResults.Dispose();
|
||||
|
||||
if (_QueryResults.IsCreated) _QueryResults.Dispose();
|
||||
if (_QueryResultsLast.IsCreated) _QueryResultsLast.Dispose();
|
||||
|
||||
_SegmentRegistrarRingBuffer.ClearAll();
|
||||
}
|
||||
|
||||
public virtual int Query(int ownerHash, float minSpatialLength, Vector3[] queryPoints, Vector3[] results)
|
||||
{
|
||||
var result = (int)QueryStatus.OK;
|
||||
|
||||
if (!UpdateQueryPoints(ownerHash, minSpatialLength, queryPoints, null))
|
||||
{
|
||||
result |= (int)QueryStatus.PostFailed;
|
||||
}
|
||||
|
||||
if (!RetrieveResults(ownerHash, results, null, null))
|
||||
{
|
||||
result |= (int)QueryStatus.RetrieveFailed;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int ResultGuidCount => _ResultSegments != null ? _ResultSegments.Count : 0;
|
||||
|
||||
public int RequestCount => _Requests != null ? _Requests.Count : 0;
|
||||
|
||||
public int QueryCount => _SegmentRegistrarRingBuffer != null ? _SegmentRegistrarRingBuffer.Current._QueryCount : 0;
|
||||
}
|
||||
|
||||
static partial class Extensions
|
||||
{
|
||||
public static void UpdateQueries(this IQueryProvider self, WaterRenderer water) => (self as IQueryable)?.UpdateQueries(water);
|
||||
public static void CleanUp(this IQueryProvider self) => (self as IQueryable)?.CleanUp();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b047926acd1d644acb5797a1d2b7b7b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,267 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using WaveHarmonic.Crest.Internal;
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// What transform to use for queries.
|
||||
/// </summary>
|
||||
public enum QuerySource
|
||||
{
|
||||
/// <summary>
|
||||
/// This game object's transform.
|
||||
/// </summary>
|
||||
Transform,
|
||||
|
||||
/// <summary>
|
||||
/// The viewer's transform.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The viewer is the main camera the system uses.
|
||||
/// </remarks>
|
||||
Viewer
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits events (UnityEvents) based on the sampled water data.
|
||||
/// </summary>
|
||||
[AddComponentMenu(Constants.k_MenuPrefixScripts + "Query Events")]
|
||||
[@HelpURL("Manual/Events.html#query-events")]
|
||||
public sealed partial class QueryEvents : ManagedBehaviour<WaterRenderer>
|
||||
{
|
||||
[SerializeField, HideInInspector]
|
||||
#pragma warning disable 414
|
||||
int _Version = 0;
|
||||
#pragma warning restore 414
|
||||
|
||||
|
||||
[Tooltip("What transform should the queries be based on.\n\n\"Viewer\" will reuse queries already performed by the Water Renderer")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
QuerySource _Source;
|
||||
|
||||
[Tooltip(ICollisionProvider.k_LayerTooltip)]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
internal CollisionLayer _Layer;
|
||||
|
||||
|
||||
[Header("Distance From Water Surface")]
|
||||
|
||||
[Tooltip("The minimum wavelength for queries.\n\nThe higher the value, the more smaller waves will be ignored when sampling the water surface.")]
|
||||
[@Predicated(nameof(_Source), inverted: true, nameof(QuerySource.Transform))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _MinimumWavelength = 1f;
|
||||
|
||||
[@Label("Signed")]
|
||||
[Tooltip("Whether to keep the sign of the value (ie positive/negative).\n\nA positive value means the query point is above the surface, while a negative means it below the surface.")]
|
||||
[@Predicated(nameof(_DistanceFromSurfaceUseCurve), inverted: true)]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DistanceFromSurfaceSigned;
|
||||
|
||||
[@Label("Maximum Distance")]
|
||||
[Tooltip("The maximum distance.\n\nAlways use a real distance in real units (ie not normalized).")]
|
||||
[@GenerateAPI]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_MaximumDistance")]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _DistanceFromSurfaceMaximum = 100f;
|
||||
|
||||
[@Label("Use Curve")]
|
||||
[Tooltip("Whether to apply a curve to the distance.\n\nNormalizes and inverts the distance to be between zero and one, then applies a curve.")]
|
||||
[@GenerateAPI]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_NormaliseDistance")]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DistanceFromSurfaceUseCurve = true;
|
||||
|
||||
[@Label("Curve")]
|
||||
[Tooltip("Apply a curve to the distance.\n\nValues towards \"one\" means closer to the water surface.")]
|
||||
[@Predicated(nameof(_DistanceFromSurfaceUseCurve))]
|
||||
[@GenerateAPI]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_DistanceCurve")]
|
||||
[@DecoratedField, SerializeField]
|
||||
AnimationCurve _DistanceFromSurfaceCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
|
||||
|
||||
|
||||
[Header("Distance From Water Edge")]
|
||||
|
||||
[@Label("Signed")]
|
||||
[Tooltip("Whether to keep the sign of the value (ie positive/negative).\n\nA positive value means the query point is over water, while a negative means it is over land.")]
|
||||
[@Predicated(nameof(_DistanceFromEdgeUseCurve), inverted: true)]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DistanceFromEdgeSigned;
|
||||
|
||||
[@Label("Maximum Distance")]
|
||||
[Tooltip("The maximum distance.\n\nAlways use a real distance in real units (ie not normalized).")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
float _DistanceFromEdgeMaximum = 100f;
|
||||
|
||||
[@Label("Use Curve")]
|
||||
[Tooltip("Apply a curve to the distance.\n\nNormalizes and inverts the distance to be between zero and one, then applies a curve.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
bool _DistanceFromEdgeUseCurve = true;
|
||||
|
||||
[@Label("Curve")]
|
||||
[Tooltip("Apply a curve to the distance.\n\nValues towards \"one\" means closer to the water's edge.")]
|
||||
[@Predicated(nameof(_DistanceFromEdgeUseCurve))]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
AnimationCurve _DistanceFromEdgeCurve = AnimationCurve.Linear(0f, 0f, 1f, 1f);
|
||||
|
||||
|
||||
[Header("Events")]
|
||||
|
||||
[Tooltip("Triggers when game object goes below water surface.\n\nTriggers once per state change.")]
|
||||
[@GenerateAPI]
|
||||
[@DecoratedField, SerializeField]
|
||||
UnityEvent _OnBelowWater = new();
|
||||
|
||||
[Tooltip("Triggers when game object goes above water surface.\n\nTriggers once per state change.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
UnityEvent _OnAboveWater = new();
|
||||
|
||||
[Tooltip("Sends the distance from the water surface.")]
|
||||
[@GenerateAPI]
|
||||
[UnityEngine.Serialization.FormerlySerializedAs("_DistanceFromWater")]
|
||||
[SerializeField]
|
||||
internal UnityEvent<float> _DistanceFromSurface = new();
|
||||
|
||||
[Tooltip("Sends the distance from the water's edge.")]
|
||||
[@GenerateAPI]
|
||||
[SerializeField]
|
||||
internal UnityEvent<float> _DistanceFromEdge = new();
|
||||
|
||||
bool HasOnBelowWater => OnBelowWater != null || !_OnBelowWater.IsEmpty();
|
||||
bool HasOnAboveWater => OnAboveWater != null || !_OnAboveWater.IsEmpty();
|
||||
bool HasDistanceFromSurface => DistanceFromSurface != null || !_DistanceFromSurface.IsEmpty();
|
||||
bool HasDistanceFromEdge => DistanceFromEdge != null || !_DistanceFromEdge.IsEmpty();
|
||||
|
||||
// Store state
|
||||
bool _IsAboveSurface = false;
|
||||
bool _IsFirstUpdate = true;
|
||||
readonly SampleCollisionHelper _SampleHeightHelper = new();
|
||||
readonly SampleDepthHelper _SampleDepthHelper = new();
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnUpdateMethod => OnUpdate;
|
||||
void OnUpdate(WaterRenderer water)
|
||||
{
|
||||
// Transform requires queries which need to be in Update.
|
||||
if (_Source != QuerySource.Transform) return;
|
||||
SendDistanceFromSurface(water);
|
||||
SendDistanceFromEdge(water);
|
||||
}
|
||||
|
||||
private protected override System.Action<WaterRenderer> OnLateUpdateMethod => OnLateUpdate;
|
||||
void OnLateUpdate(WaterRenderer water)
|
||||
{
|
||||
// Viewer is set between Update and LateUpdate.
|
||||
if (_Source != QuerySource.Viewer) return;
|
||||
SendDistanceFromSurface(water);
|
||||
SendDistanceFromEdge(water);
|
||||
}
|
||||
|
||||
void SendDistanceFromSurface(WaterRenderer water)
|
||||
{
|
||||
if (!HasDistanceFromSurface && !HasOnAboveWater && !HasOnBelowWater)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var distance = water.ViewerHeightAboveWater;
|
||||
|
||||
if (_Source == QuerySource.Transform)
|
||||
{
|
||||
if (!_SampleHeightHelper.SampleHeight(transform.position, out var height, minimumLength: 2f * _MinimumWavelength, _Layer)) return;
|
||||
distance = transform.position.y - height;
|
||||
}
|
||||
|
||||
var isAboveSurface = distance > 0;
|
||||
|
||||
// Has the below/above water surface state changed?
|
||||
if (_IsAboveSurface != isAboveSurface || _IsFirstUpdate)
|
||||
{
|
||||
_IsAboveSurface = isAboveSurface;
|
||||
_IsFirstUpdate = false;
|
||||
|
||||
if (_IsAboveSurface)
|
||||
{
|
||||
_OnAboveWater?.Invoke();
|
||||
OnAboveWater?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
_OnBelowWater?.Invoke();
|
||||
OnBelowWater?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
// Save some processing when not being used.
|
||||
if (HasDistanceFromSurface)
|
||||
{
|
||||
distance = Mathf.Clamp(distance, -_DistanceFromSurfaceMaximum, _DistanceFromSurfaceMaximum);
|
||||
|
||||
// Throw away sign when using a curve. Cannot think of a use case for negative
|
||||
// normalized numbers.
|
||||
if (!_DistanceFromSurfaceSigned || _DistanceFromSurfaceUseCurve)
|
||||
{
|
||||
distance = Mathf.Abs(distance);
|
||||
}
|
||||
|
||||
if (_DistanceFromSurfaceUseCurve)
|
||||
{
|
||||
// Normalize for the curve. Invert so towards one is closer to target.
|
||||
distance = _DistanceFromSurfaceCurve.Evaluate(1f - distance / _DistanceFromSurfaceMaximum);
|
||||
}
|
||||
|
||||
_DistanceFromSurface?.Invoke(distance);
|
||||
DistanceFromSurface?.Invoke(distance);
|
||||
}
|
||||
}
|
||||
|
||||
void SendDistanceFromEdge(WaterRenderer water)
|
||||
{
|
||||
// No events to process.
|
||||
if (!HasDistanceFromEdge)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var distance = water.ViewerDistanceToShoreline;
|
||||
|
||||
if (_Source == QuerySource.Transform)
|
||||
{
|
||||
if (!_SampleDepthHelper.SampleDistanceToWaterEdge(transform.position, out distance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
distance = Mathf.Clamp(distance, -_DistanceFromEdgeMaximum, _DistanceFromEdgeMaximum);
|
||||
|
||||
// Throw away sign when using a curve. Cannot think of a use case for negative
|
||||
// normalized numbers.
|
||||
if (!_DistanceFromEdgeSigned || _DistanceFromEdgeUseCurve)
|
||||
{
|
||||
distance = Mathf.Abs(distance);
|
||||
}
|
||||
|
||||
if (_DistanceFromEdgeUseCurve)
|
||||
{
|
||||
// Normalize for the curve. Invert so towards one is closer to target.
|
||||
distance = _DistanceFromEdgeCurve.Evaluate(1f - distance / _DistanceFromEdgeMaximum);
|
||||
}
|
||||
|
||||
_DistanceFromEdge?.Invoke(distance);
|
||||
DistanceFromEdge?.Invoke(distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 555f01a6f203a4b6db9ec558f7451e4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,320 +0,0 @@
|
||||
// Crest Water System
|
||||
// Copyright © 2024 Wave Harmonic. All rights reserved.
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
// Linter does not support mixing inheritdoc plus defining own parameters.
|
||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||
|
||||
namespace WaveHarmonic.Crest.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for sample helpers which sample a single point.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is not particularly efficient to sample a single point, but is a fairly
|
||||
/// common case.
|
||||
/// </remarks>
|
||||
public abstract class SampleHelper
|
||||
{
|
||||
private protected readonly Vector3[] _QueryPosition;
|
||||
private protected readonly Vector3[] _QueryResult;
|
||||
|
||||
int _LastFrame = -1;
|
||||
|
||||
private protected SampleHelper(int queryCount = 1)
|
||||
{
|
||||
_QueryPosition = new Vector3[queryCount];
|
||||
_QueryResult = new Vector3[queryCount];
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
private protected void Validate(bool allowMultipleCallsPerFrame)
|
||||
{
|
||||
if (Application.isPlaying && !Time.inFixedTimeStep && !allowMultipleCallsPerFrame && _LastFrame == Time.frameCount)
|
||||
{
|
||||
var type = GetType().Name;
|
||||
Debug.LogWarning($"Crest: {type} sample called multiple times in one frame which is not expected. Each {type} object services a single sample per frame. To perform multiple queries, create multiple {type} objects or use the query provider API directly.");
|
||||
}
|
||||
|
||||
_LastFrame = Time.frameCount;
|
||||
}
|
||||
|
||||
// The first method is there just to get inheritdoc to work as it does not like
|
||||
// inheriting params plus adding additional params.
|
||||
|
||||
/// <remarks>
|
||||
/// Only call once per frame.
|
||||
/// </remarks>
|
||||
/// <param name="position">World space position to sample.</param>
|
||||
/// <param name="minimumLength">
|
||||
/// The smallest length scale you are interested in. If you are sampling data for boat physics,
|
||||
/// pass in the boats width. Larger objects will ignore smaller details in the data.
|
||||
/// </param>
|
||||
/// <param name="layer">The collision layer to target.</param>
|
||||
/// <returns>Whether the query was successful.</returns>
|
||||
bool Sample(Vector3 position, float minimumLength, CollisionLayer layer) => false;
|
||||
}
|
||||
}
|
||||
|
||||
namespace WaveHarmonic.Crest
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to obtain the water surface collision at a single point per frame.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public sealed class SampleCollisionHelper : Internal.SampleHelper
|
||||
{
|
||||
readonly Vector3[] _QueryResultNormal = new Vector3[1];
|
||||
readonly Vector3[] _QueryResultVelocity = new Vector3[1];
|
||||
|
||||
enum QueryType
|
||||
{
|
||||
Displacement,
|
||||
Height,
|
||||
}
|
||||
|
||||
[System.Flags]
|
||||
enum QueryOptions
|
||||
{
|
||||
None,
|
||||
Velocity,
|
||||
Normal,
|
||||
All = Velocity | Normal,
|
||||
}
|
||||
|
||||
bool Sample(int id, Vector3 position, out Vector3 displacement, out float height, out Vector3 velocity, out Vector3 normal, QueryType type, QueryOptions options, CollisionLayer layer = CollisionLayer.Everything, float minimumLength = 0f, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
var water = WaterRenderer.Instance;
|
||||
var provider = water == null ? null : water.AnimatedWavesLod.Provider;
|
||||
|
||||
height = 0f;
|
||||
displacement = Vector3.zero;
|
||||
velocity = Vector3.zero;
|
||||
normal = Vector3.up;
|
||||
|
||||
if (provider == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var isHeight = type == QueryType.Height;
|
||||
var isDisplacement = type == QueryType.Displacement;
|
||||
var isVelocity = (options & QueryOptions.Velocity) == QueryOptions.Velocity;
|
||||
var isNormal = (options & QueryOptions.Normal) == QueryOptions.Normal;
|
||||
|
||||
Validate(allowMultipleCallsPerFrame);
|
||||
|
||||
_QueryPosition[0] = position;
|
||||
|
||||
var status = provider.Query
|
||||
(
|
||||
id,
|
||||
minimumLength,
|
||||
_QueryPosition,
|
||||
_QueryResult,
|
||||
isNormal ? _QueryResultNormal : null,
|
||||
isVelocity ? _QueryResultVelocity : null,
|
||||
layer
|
||||
);
|
||||
|
||||
if (!provider.RetrieveSucceeded(status))
|
||||
{
|
||||
height = water.SeaLevel;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isHeight) height = _QueryResult[0].y + water.SeaLevel;
|
||||
if (isDisplacement) displacement = _QueryResult[0];
|
||||
if (isVelocity) velocity = _QueryResultVelocity[0];
|
||||
if (isNormal) normal = _QueryResultNormal[0];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal bool SampleHeight(int id, Vector3 position, out float height, CollisionLayer layer = CollisionLayer.Everything, float minimumLength = 0f, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
return Sample(id, position, out _, out height, out _, out _, QueryType.Height, QueryOptions.None, layer, minimumLength, allowMultipleCallsPerFrame);
|
||||
}
|
||||
|
||||
internal bool SampleHeight(int id, Vector3 position, out float height, out Vector3 velocity, CollisionLayer layer = CollisionLayer.Everything, float minimumLength = 0f, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
return Sample(id, position, out _, out height, out velocity, out _, QueryType.Height, QueryOptions.Velocity, layer, minimumLength, allowMultipleCallsPerFrame);
|
||||
}
|
||||
|
||||
internal bool SampleHeight(int id, Vector3 position, out float height, out Vector3 velocity, out Vector3 normal, CollisionLayer layer = CollisionLayer.Everything, float minimumLength = 0f, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
return Sample(id, position, out _, out height, out velocity, out normal, QueryType.Height, QueryOptions.All, layer, minimumLength, allowMultipleCallsPerFrame);
|
||||
}
|
||||
|
||||
internal bool SampleDisplacement(int id, Vector3 position, out Vector3 displacement, out Vector3 velocity, out Vector3 normal, CollisionLayer layer = CollisionLayer.Everything, float minimumLength = 0f, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
return Sample(id, position, out displacement, out _, out velocity, out normal, QueryType.Displacement, QueryOptions.All, layer, minimumLength, allowMultipleCallsPerFrame);
|
||||
}
|
||||
|
||||
internal bool SampleDisplacement(int id, Vector3 position, out Vector3 displacement, out Vector3 velocity, CollisionLayer layer = CollisionLayer.Everything, float minimumLength = 0f, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
return Sample(id, position, out displacement, out _, out velocity, out _, QueryType.Displacement, QueryOptions.Velocity, layer, minimumLength, allowMultipleCallsPerFrame);
|
||||
}
|
||||
|
||||
internal bool SampleDisplacement(int id, Vector3 position, out Vector3 displacement, CollisionLayer layer = CollisionLayer.Everything, float minimumLength = 0f, bool allowMultipleCallsPerFrame = false)
|
||||
{
|
||||
return Sample(id, position, out displacement, out _, out _, out _, QueryType.Displacement, QueryOptions.None, layer, minimumLength, allowMultipleCallsPerFrame);
|
||||
}
|
||||
|
||||
// The first method is there just to get inheritdoc to work as it does not like
|
||||
// inheriting params plus adding additional params.
|
||||
|
||||
/// <summary>
|
||||
/// Sample displacement data.
|
||||
/// </summary>
|
||||
/// <param name="displacement">The water surface displacement to point.</param>
|
||||
/// <param name="height">The water surface height.</param>
|
||||
/// <param name="velocity">The velocity of the water surface excluding flow velocity.</param>
|
||||
/// <param name="normal">The water surface normal.</param>
|
||||
/// <inheritdoc cref="Internal.SampleHelper.Sample" />
|
||||
bool Sample(Vector3 position, float height, Vector3 displacement, Vector3 normal, Vector3 velocity, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => false;
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
[System.Obsolete("Please use SampleDisplacement instead. Be wary that the new API has switch the normal parameter with velocity.")]
|
||||
public bool Sample(Vector3 position, out Vector3 displacement, out Vector3 normal, out Vector3 velocity, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleDisplacement(GetHashCode(), position, out displacement, out velocity, out normal, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
[System.Obsolete("Please use SampleHeight instead. Be wary that the new API has switch the normal parameter with velocity.")]
|
||||
public bool Sample(Vector3 position, out float height, out Vector3 normal, out Vector3 velocity, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleHeight(GetHashCode(), position, out height, out velocity, out normal, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
[System.Obsolete("Please use SampleHeight instead. Be wary that the new API has switch the normal parameter with velocity.")]
|
||||
public bool Sample(Vector3 position, out float height, out Vector3 normal, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleHeight(GetHashCode(), position, out height, out _, out normal, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
[System.Obsolete("Please use SampleHeight instead. Be wary that the new API has switch the normal parameter with velocity.")]
|
||||
public bool Sample(Vector3 position, out float height, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleHeight(GetHashCode(), position, out height, layer, minimumLength);
|
||||
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
public bool SampleDisplacement(Vector3 position, out Vector3 displacement, out Vector3 velocity, out Vector3 normal, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleDisplacement(GetHashCode(), position, out displacement, out velocity, out normal, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
public bool SampleDisplacement(Vector3 position, out Vector3 displacement, out Vector3 velocity, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleDisplacement(GetHashCode(), position, out displacement, out velocity, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
public bool SampleDisplacement(Vector3 position, out Vector3 displacement, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleDisplacement(GetHashCode(), position, out displacement, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
public bool SampleHeight(Vector3 position, out float height, out Vector3 velocity, out Vector3 normal, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleHeight(GetHashCode(), position, out height, out velocity, out normal, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
public bool SampleHeight(Vector3 position, out float height, out Vector3 velocity, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleHeight(GetHashCode(), position, out height, out velocity, layer, minimumLength);
|
||||
|
||||
/// <inheritdoc cref="Sample(Vector3, float, Vector3, Vector3, Vector3, float, CollisionLayer)" />
|
||||
public bool SampleHeight(Vector3 position, out float height, float minimumLength = 0f, CollisionLayer layer = CollisionLayer.Everything) => SampleHeight(GetHashCode(), position, out height, layer, minimumLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to obtain the flow data (horizontal water motion) at a single point.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public sealed class SampleFlowHelper : Internal.SampleHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Sample flow data.
|
||||
/// </summary>
|
||||
/// <param name="flow">Filled with resulting flow.</param>
|
||||
/// <returns>Whether the query was successful.</returns>
|
||||
/// <inheritdoc cref="Internal.SampleHelper.Sample" />
|
||||
public bool Sample(Vector3 position, out Vector2 flow, float minimumLength = 0f)
|
||||
{
|
||||
var water = WaterRenderer.Instance;
|
||||
var flowProvider = WaterRenderer.Instance == null ? null : water.FlowLod.Provider;
|
||||
|
||||
if (flowProvider == null)
|
||||
{
|
||||
flow = Vector2.zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
Validate(false);
|
||||
|
||||
_QueryPosition[0] = position;
|
||||
|
||||
var status = flowProvider.Query(GetHashCode(), minimumLength, _QueryPosition, _QueryResult);
|
||||
|
||||
if (!flowProvider.RetrieveSucceeded(status))
|
||||
{
|
||||
flow = Vector2.zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't support float2 queries unfortunately, so unpack from float3
|
||||
flow.x = _QueryResult[0].x;
|
||||
flow.y = _QueryResult[0].z;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to obtain the depth data at a single point.
|
||||
/// </summary>
|
||||
public sealed class SampleDepthHelper : Internal.SampleHelper
|
||||
{
|
||||
bool Sample(Vector3 position, out Vector2 result)
|
||||
{
|
||||
var water = WaterRenderer.Instance;
|
||||
var depthProvider = WaterRenderer.Instance == null ? null : water.DepthLod.Provider;
|
||||
|
||||
if (depthProvider == null)
|
||||
{
|
||||
result = Vector2.zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
Validate(false);
|
||||
|
||||
_QueryPosition[0] = position;
|
||||
|
||||
var status = depthProvider.Query(GetHashCode(), minimumLength: 0, _QueryPosition, _QueryResult);
|
||||
if (!depthProvider.RetrieveSucceeded(status))
|
||||
{
|
||||
result = Vector2.zero;
|
||||
return false;
|
||||
}
|
||||
|
||||
result = _QueryResult[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sample both the water depth and water edge distance.
|
||||
/// </summary>
|
||||
/// <param name="depth">Filled by the water depth at the query position.</param>
|
||||
/// <param name="distance">Filled by the distance to water edge at the query position.</param>
|
||||
/// <inheritdoc cref="Internal.SampleHelper.Sample" />
|
||||
bool Sample(Vector3 position, out float depth, out float distance)
|
||||
{
|
||||
var success = Sample(position, out var result);
|
||||
depth = result.x;
|
||||
distance = result.y;
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>Sample water depth.</summary>
|
||||
/// <inheritdoc cref="Sample(Vector3, out float, out float)"/>
|
||||
bool SampleWaterDepth(Vector3 position, out float depth)
|
||||
{
|
||||
var success = Sample(position, out var result);
|
||||
depth = result.x;
|
||||
return success;
|
||||
}
|
||||
|
||||
/// <summary>Sample water edge distance.</summary>
|
||||
/// <inheritdoc cref="Sample(Vector3, out float, out float)"/>
|
||||
public bool SampleDistanceToWaterEdge(Vector3 position, out float distance)
|
||||
{
|
||||
var success = Sample(position, out var result);
|
||||
distance = result.y;
|
||||
return success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4b71f2a526b64377bc96c6e283e4535
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user