Represents one cross section (row) of a river mesh, storing positions across the channel, per-column horizontal velocities, wave and foam scalar values, and utility methods for cloning, reversing, computing counts and averages.
using System;
using Sandbox;
namespace RedSnail.WaterTool2;
/// <summary>
/// One cross section of a river as its mesh is built: the points across it from its left bank to its right bank
/// (looking downstream) and the state of the water at each of them. A row a river shares with whatever it runs into
/// (a confluence, a lake, the next river through a bend) is worked out once and handed to both pieces, so they meet
/// on exactly the same vertices carrying exactly the same data, and the surface runs on without a seam.
/// </summary>
internal sealed class RiverRow
{
/// <summary>
/// Columns are this far apart at most; a row always has its two banks.
/// </summary>
public const int MaxColumns = 512;
/// <summary>
/// How far along its river the row sits.
/// </summary>
public float Distance;
/// <summary>
/// Network-local positions, left bank first.
/// </summary>
public Vector3[] Points;
/// <summary>
/// Network-local horizontal water velocity at each point, in units per second.
/// </summary>
public Vector2[] Velocities;
public float[] Waves;
public float[] Foams;
public int Count => Points.Length;
public RiverRow(int _Count)
{
Points = new Vector3[_Count];
Velocities = new Vector2[_Count];
Waves = new float[_Count];
Foams = new float[_Count];
}
/// <summary>
/// How many points a row of this width gets for a mesh of this cell size: enough for no gap between them to be
/// wider than a cell.
/// </summary>
public static int ColumnCount(float _Width, float _CellSize)
{
return Math.Clamp((int)MathF.Ceiling(_Width / MathF.Max(1.0f, _CellSize)) + 1, 2, MaxColumns);
}
/// <summary>
/// Where a column sits across the river, from 0 on the left bank to 1 on the right.
/// </summary>
public static float Across(int _Column, int _Count)
{
return _Count > 1 ? (float)_Column / (_Count - 1) : 0.5f;
}
public RiverRow Clone()
{
var row = new RiverRow(Count) { Distance = Distance };
Array.Copy(Points, row.Points, Count);
Array.Copy(Velocities, row.Velocities, Count);
Array.Copy(Waves, row.Waves, Count);
Array.Copy(Foams, row.Foams, Count);
return row;
}
/// <summary>
/// The same row seen from the other side: right bank first. The data is the same, point for point.
/// </summary>
public RiverRow Reversed()
{
var row = new RiverRow(Count) { Distance = Distance };
for (int i = 0; i < Count; i++)
{
int from = Count - 1 - i;
row.Points[i] = Points[from];
row.Velocities[i] = Velocities[from];
row.Waves[i] = Waves[from];
row.Foams[i] = Foams[from];
}
return row;
}
public Vector3 Centre => (Points[0] + Points[^1]) * 0.5f;
public float HalfWidth => new Vector2(Points[^1].x - Points[0].x, Points[^1].y - Points[0].y).Length * 0.5f;
public Vector2 MeanVelocity
{
get
{
Vector2 sum = Vector2.Zero;
foreach (var velocity in Velocities)
sum += velocity;
return sum / MathF.Max(1, Count);
}
}
public float MeanFoam
{
get
{
float sum = 0.0f;
foreach (float foam in Foams)
sum += foam;
return sum / MathF.Max(1, Count);
}
}
public void AddToHash(ref StableHash _Hash)
{
_Hash.Add(Count);
for (int i = 0; i < Count; i++)
{
_Hash.Add(Points[i]);
_Hash.Add(Velocities[i]);
_Hash.Add(Waves[i]);
_Hash.Add(Foams[i]);
}
}
}