Defines water-related types and a WaterPiece class that holds CPU mesh data and uploads/draws it on the GPU. It defines WaterPieceKey, a GPU vertex struct (WaterVertex), and WaterPiece which converts mesh arrays into GPU buffers, tracks bounds and depth, and provides Upload, Release and Draw methods.
using System;
using System.Runtime.InteropServices;
using Sandbox;
using Sandbox.Rendering;
namespace RedSnail.WaterTool2;
public enum WaterPieceKind
{
/// <summary>
/// A stretch of a river.
/// </summary>
River,
/// <summary>
/// The water where rivers meet at a node.
/// </summary>
Junction,
/// <summary>
/// A tile of a lake.
/// </summary>
Lake,
/// <summary>
/// Where a river opens into the sea: its block of the coast zone.
/// </summary>
Estuary,
/// <summary>
/// A chunk of the coast zone: the sea's water round the land.
/// </summary>
Coast
}
/// <summary>
/// What a piece of water is: which river, node or lake it belongs to, and which of that owner's stretches or tiles.
/// </summary>
internal readonly record struct WaterPieceKey(WaterPieceKind Kind, int Owner, int Index);
/// <summary>
/// The vertex the water shader reads: the rest position, in the colour the water's state there (its flow in x and y,
/// in units per second, its wave strength and its foam), and in the first texture coordinate how far the water has
/// handed over to the sea (0 its own, 1 exactly the sea's surface). The sea's rings use the position as a grid index.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct WaterVertex
{
[VertexLayout.Position] public Vector3 Position;
[VertexLayout.Normal] public Vector3 Normal;
[VertexLayout.Tangent] public Vector4 Tangent;
[VertexLayout.TexCoord] public Vector2 TexCoord;
[VertexLayout.Color] public Color Color;
}
/// <summary>
/// One generated piece of water: its mesh on the CPU (which gameplay queries) and, wherever something renders, the
/// same mesh on the GPU. Pieces are not saved; the network builds them from the graph, and rebuilds one only when the
/// inputs it was built from (its signature) change.
/// </summary>
internal sealed class WaterPiece
{
public WaterPieceKey Key;
public long Signature;
public WaterMeshData Mesh;
/// <summary>
/// How deep the water is under this piece, for gameplay.
/// </summary>
public float Depth;
/// <summary>
/// The piece's world bounds, down to its bed: what it is culled with.
/// </summary>
public BBox Bounds;
private GpuBuffer<WaterVertex> m_VertexBuffer;
private GpuBuffer<uint> m_IndexBuffer;
private int m_IndexCount;
public bool IsUploaded => m_VertexBuffer.IsValid() && m_IndexBuffer.IsValid() && m_IndexCount > 0;
public WaterPiece(WaterPieceKey _Key, long _Signature, WaterMeshData _Mesh, float _Depth)
{
Key = _Key;
Signature = _Signature;
Mesh = _Mesh;
Depth = _Depth;
var bounds = _Mesh.Bounds;
Bounds = new BBox(bounds.Mins - new Vector3(0.0f, 0.0f, _Depth), bounds.Maxs);
}
/// <summary>
/// Puts the mesh on the GPU. Main thread only.
/// </summary>
public void Upload()
{
Release();
int count = Mesh.VertexCount;
if (count == 0 || Mesh.Indices is null || Mesh.Indices.Length == 0)
return;
var vertices = new WaterVertex[count];
for (int i = 0; i < count; i++)
{
Vector3 position = Mesh.Positions[i];
Vector2 velocity = Mesh.Velocities[i];
vertices[i] = new WaterVertex
{
Position = position,
Normal = Vector3.Up,
Tangent = new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
TexCoord = new Vector2(Mesh.SeaBlend[i], 0.0f),
Color = new Color(velocity.x, velocity.y, Mesh.Waves[i], Mesh.Foams[i])
};
}
var indices = new uint[Mesh.Indices.Length];
for (int i = 0; i < indices.Length; i++)
indices[i] = (uint)Mesh.Indices[i];
m_VertexBuffer = new GpuBuffer<WaterVertex>(count, GpuBuffer.UsageFlags.Vertex);
m_VertexBuffer.SetData(vertices);
m_IndexBuffer = new GpuBuffer<uint>(indices.Length, GpuBuffer.UsageFlags.Index);
m_IndexBuffer.SetData(indices);
m_IndexCount = indices.Length;
}
public void Release()
{
m_VertexBuffer?.Dispose();
m_VertexBuffer = null;
m_IndexBuffer?.Dispose();
m_IndexBuffer = null;
m_IndexCount = 0;
}
public void Draw(CommandList _CommandList, Material _Material, RenderAttributes _Attributes)
{
if (!IsUploaded)
return;
_CommandList.DrawIndexed(m_VertexBuffer, m_IndexBuffer, _Material, 0, m_IndexCount, _Attributes);
}
}