Represents the computed state of water along a single river, holding sampled speeds, trim/blend/wave parameters and providing lookup and hashing utilities. It computes interpolated speed at distance, blended wave strength at distance, a smooth ramp function, quantization of speeds, and adds relevant fields to a StableHash for change detection.
using System;
namespace RedSnail.WaterTool2;
/// <summary>
/// The state of the water along one river, worked out by the network from the whole graph: how fast it runs at every
/// distance (it arrives with whatever flows into the river, speeds up on slopes and settles back to the river's own
/// speed on the flat), and how its wave strength blends into whatever it meets at each end.
/// </summary>
internal sealed class RiverFlow
{
/// <summary>
/// Speeds are kept to a quarter of a unit per second. A small change upstream then dies out a little way down
/// the network instead of nudging, and rebuilding, every river below it.
/// </summary>
public const float SpeedQuantum = 0.25f;
public float Length;
public float Step;
public float[] Speeds;
/// <summary>
/// The range of the river that is built as river: past the confluence or the lake at each end.
/// </summary>
public float TrimStart;
public float TrimEnd;
/// <summary>
/// The river's own wave strength, the strength it has to arrive at on its first and last rows, and how far from
/// those rows the blend reaches.
/// </summary>
public float Waves;
public float WavesStart;
public float WavesEnd;
public float BlendStart;
public float BlendEnd;
public float SpeedAt(float _Distance)
{
if (Speeds is null || Speeds.Length == 0)
return 0.0f;
if (Speeds.Length == 1 || Step <= 0.0f)
return Speeds[0];
float position = Math.Clamp(_Distance, 0.0f, Length) / Step;
int index = Math.Min((int)position, Speeds.Length - 2);
float fraction = Math.Clamp(position - index, 0.0f, 1.0f);
return Speeds[index] + (Speeds[index + 1] - Speeds[index]) * fraction;
}
public float WavesAt(float _Distance)
{
float waves = Waves;
float fromStart = _Distance - TrimStart;
float fromEnd = Length - TrimEnd - _Distance;
waves += (WavesStart - Waves) * Ramp(fromStart, BlendStart);
waves += (WavesEnd - Waves) * Ramp(fromEnd, BlendEnd);
return MathF.Max(0.0f, waves);
}
/// <summary>
/// 1 on the row the blend starts from, easing to 0 at the blend's reach.
/// </summary>
private static float Ramp(float _Distance, float _Reach)
{
if (_Reach <= 0.001f)
return _Distance <= 0.001f ? 1.0f : 0.0f;
float t = Math.Clamp(_Distance / _Reach, 0.0f, 1.0f);
return 1.0f - t * t * (3.0f - 2.0f * t);
}
public static float Quantize(float _Speed)
{
return MathF.Round(_Speed / SpeedQuantum) * SpeedQuantum;
}
public void AddToHash(ref StableHash _Hash, float _From, float _To)
{
_Hash.Add(Length);
_Hash.Add(Step);
_Hash.Add(TrimStart);
_Hash.Add(TrimEnd);
_Hash.Add(Waves);
_Hash.Add(WavesStart);
_Hash.Add(WavesEnd);
_Hash.Add(BlendStart);
_Hash.Add(BlendEnd);
if (Speeds is null || Step <= 0.0f)
return;
// Only the samples that reach into the stretch: a speed change further down does not touch it.
int first = Math.Max(0, (int)MathF.Floor(_From / Step) - 1);
int last = Math.Min(Speeds.Length - 1, (int)MathF.Ceiling(_To / Step) + 1);
for (int i = first; i <= last; i++)
_Hash.Add(Speeds[i]);
}
}