Building/LakeBuilder.cs

Builds tiled water mesh data for a lake surface from a snapshot of networked input. It subdivides and triangulates the lake outline, samples initial per-vertex state (positions, velocities, waves, foam) including contributions from inlet/outlet jets, partitions triangles into square tiles, remaps vertex indices per tile and produces WaterMeshData instances.

File AccessNetworking
using System;
using System.Collections.Generic;
using Sandbox;

namespace RedSnail.WaterTool2;

/// <summary>
/// Everything a lake's surface is built from, copied out of the network so the build can run on a worker thread while
/// the editor carries on.
/// </summary>
internal sealed class LakeBuildInput
{
	public int LakeId;
	public long Signature;
	public List<OutlineVertex> Outline;
	public LakeJet[] Jets;
	public float Height;
	public float Waves;
	public float CellSize;
	public float TileSize;
	public Transform Transform;
}



/// <summary>
/// Builds a lake's surface: its outline (with the rows of every river that meets it) triangulated with points all over
/// its inside, the water's state on every point (still water, stirred by the jets of the rivers running in and out),
/// cut into square tiles so the parts of a big lake out of view are not drawn.
/// </summary>
internal static class LakeBuilder
{
	public static List<WaterMeshData> Build(LakeBuildInput _Input)
	{
		var tiles = new List<WaterMeshData>();
		var outline = SurfaceMesher.Subdivide(_Input.Outline, _Input.CellSize);

		if (outline.Count < 3)
			return tiles;

		var flat = new List<Vector2>(outline.Count);

		foreach (var vertex in outline)
			flat.Add(new Vector2(vertex.Position.x, vertex.Position.y));

		if (!SurfaceMesher.Triangulate(flat, _Input.CellSize, out var points, out var triangles))
			return null;

		int count = points.Count;
		var positions = new Vector3[count];
		var velocities = new Vector2[count];
		var waves = new float[count];
		var foams = new float[count];

		for (int i = 0; i < count; i++)
		{
			if (i < outline.Count && outline[i].IsFixed)
			{
				var vertex = outline[i];

				positions[i] = vertex.Position;
				velocities[i] = vertex.Velocity;
				waves[i] = vertex.Waves;
				foams[i] = vertex.Foam;
				continue;
			}

			positions[i] = i < outline.Count ? outline[i].Position : new Vector3(points[i].x, points[i].y, _Input.Height);
			waves[i] = _Input.Waves;

			Vector2 velocity = Vector2.Zero;
			float foam = 0.0f;

			foreach (var jet in _Input.Jets)
			{
				velocity += jet.Evaluate(points[i], out float jetFoam);
				foam += jetFoam;
			}

			velocities[i] = velocity;
			foams[i] = Math.Clamp(foam, 0.0f, 1.0f);
		}

		// Into tiles, by where each triangle's middle is. A vertex on a tile's edge is copied into every tile that uses
		// it, with the same data, so the tiles meet exactly.
		float tileSize = MathF.Max(_Input.CellSize * 4.0f, _Input.TileSize);
		var byTile = new Dictionary<(int X, int Y), List<int>>();

		for (int t = 0; t + 2 < triangles.Count; t += 3)
		{
			Vector2 middle = (points[triangles[t]] + points[triangles[t + 1]] + points[triangles[t + 2]]) / 3.0f;
			var key = ((int)MathF.Floor(middle.x / tileSize), (int)MathF.Floor(middle.y / tileSize));

			if (!byTile.TryGetValue(key, out var list))
			{
				list = new List<int>();
				byTile[key] = list;
			}

			list.Add(t);
		}

		var remap = new Dictionary<int, int>();

		foreach (var (_, list) in byTile)
		{
			remap.Clear();

			var indices = new List<int>(list.Count * 3);

			foreach (int t in list)
			{
				for (int k = 0; k < 3; k++)
				{
					int source = triangles[t + k];

					if (!remap.TryGetValue(source, out int local))
					{
						local = remap.Count;
						remap[source] = local;
					}

					indices.Add(local);
				}
			}

			var mesh = new WaterMeshData(remap.Count);

			foreach (var (source, local) in remap)
			{
				mesh.Positions[local] = positions[source];
				mesh.Velocities[local] = velocities[source];
				mesh.Waves[local] = waves[source];
				mesh.Foams[local] = foams[source];
			}

			mesh.Finish(_Input.Transform, indices, true);
			tiles.Add(mesh);
		}

		return tiles;
	}
}