Files
Fishing2/Packages/com.waveharmonic.crest/Runtime/Scripts/Data/Query/QueryEvents.cs
2025-05-10 12:49:47 +08:00

268 lines
10 KiB
C#

// 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);
}
}
}