Editor/WaterGizmos.cs

Editor utility for drawing water-related gizmos in the editor. It renders bezier curves, river outlines, flow direction arrows, node markers, polylines and lake shores using provided WaterCurve, RiverWidth, WaterNetwork and Transform data.

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

namespace RedSnail.WaterTool2.Editor;

/// <summary>
/// Gizmo drawing shared by the water tools. Everything is given in network-local space and drawn through the
/// network's transform, in whatever colour and thickness the caller set up.
/// </summary>
internal static class WaterGizmos
{
	public static void DrawCurve(in WaterCurve _Curve, in Transform _Transform, int _Steps = 24)
	{
		Vector3 previous = _Transform.PointToWorld(_Curve.P0);

		for (int i = 1; i <= _Steps; i++)
		{
			Vector3 point = _Transform.PointToWorld(_Curve.Evaluate((float)i / _Steps));

			Gizmo.Draw.Line(previous, point);
			previous = point;
		}
	}



	/// <summary>
	/// The two banks of a river along its curve (the curve's parameter standing in for its distance), capped at both
	/// ends.
	/// </summary>
	public static void DrawRiverOutline(in WaterCurve _Curve, in RiverWidth _Width, in Transform _Transform, int _Steps = 24)
	{
		Vector3 previousLeft = default;
		Vector3 previousRight = default;

		for (int i = 0; i <= _Steps; i++)
		{
			float t = (float)i / _Steps;
			float half = _Width.HalfAt(t * _Width.Length);
			Vector3 point = _Curve.Evaluate(t);
			Vector3 right = WaterCurve.RightOf(_Curve.Tangent(t));

			Vector3 left = _Transform.PointToWorld(point - right * half);
			Vector3 rightBank = _Transform.PointToWorld(point + right * half);

			if (i == 0 || i == _Steps)
				Gizmo.Draw.Line(left, rightBank);

			if (i > 0)
			{
				Gizmo.Draw.Line(previousLeft, left);
				Gizmo.Draw.Line(previousRight, rightBank);
			}

			previousLeft = left;
			previousRight = rightBank;
		}
	}



	/// <summary>
	/// Arrows down a river's centreline, every <paramref name="_Spacing"/> or so, pointing the way its water flows.
	/// </summary>
	public static void DrawFlowArrows(in WaterCurve _Curve, in Transform _Transform, float _Spacing, float _Size)
	{
		float length = _Curve.ApproximateLength(16);
		int arrows = Math.Clamp((int)(length / MathF.Max(1.0f, _Spacing)), 1, 64);

		for (int i = 0; i < arrows; i++)
		{
			float t = (i + 0.5f) / arrows;
			Vector3 point = _Curve.Evaluate(t);
			Vector3 tangent = _Curve.Tangent(t);

			Vector3 from = _Transform.PointToWorld(point - tangent * _Size * 0.5f);
			Vector3 to = _Transform.PointToWorld(point + tangent * _Size * 0.5f);

			Gizmo.Draw.Arrow(from, to, _Size * 0.35f, _Size * 0.2f);
		}
	}



	/// <summary>
	/// A node marker: a flat ring, filled when it is the one being worked on.
	/// </summary>
	public static void DrawNode(Vector3 _WorldPosition, bool _Filled)
	{
		float radius = WaterPicker.NodeRadius(_WorldPosition);

		if (_Filled)
			Gizmo.Draw.SolidSphere(_WorldPosition, radius * 0.6f);

		Gizmo.Draw.LineCircle(_WorldPosition, Vector3.Up, radius);
	}



	public static void DrawPolyline(IReadOnlyList<Vector3> _Points, in Transform _Transform, bool _Closed)
	{
		if (_Points is null || _Points.Count < 2)
			return;

		int count = _Closed ? _Points.Count : _Points.Count - 1;

		for (int i = 0; i < count; i++)
			Gizmo.Draw.Line(_Transform.PointToWorld(_Points[i]), _Transform.PointToWorld(_Points[(i + 1) % _Points.Count]));
	}



	/// <summary>
	/// A lake's shore, or its control polygon when the shore can't be built (too few points).
	/// </summary>
	public static void DrawLake(WaterNetwork _Network, WaterLake _Lake, in Transform _Transform)
	{
		var shore = _Network.GetShore(_Lake);

		if (shore is not null)
		{
			DrawPolyline(shore.Points, _Transform, true);
			return;
		}

		var points = new List<Vector3>();

		for (int i = 0; i < _Lake.Points.Count; i++)
			points.Add(_Lake.PointAt(i));

		DrawPolyline(points, _Transform, true);
	}
}