Building/RiverRow.cs

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.

File Access
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]);
		}
	}
}