Code/Network/RoadFanBuilder.cs

Internal static mesh builder for the road tool. Fans a junction or lot surface out from a centre point to an outline, texturing each owner's stretch with its own UV mapping, cutting spokes into pieces where a mapping curves or where a blend needs rows, and adding rim bands on long spokes. Also handles open fans and blending the fan surface into an owner's road material.

using System;
using System.Collections.Generic;
using Sandbox;

namespace RedSnail.RoadTool2;

/// <summary>
/// How a surface fanned out from a centre (a junction, a parking lot) is textured over one owner's stretch of its
/// outline.
/// </summary>
internal interface IFanMapping
{
	/// <summary>
	/// The texture coordinates at a point.
	/// </summary>
	Vector2 Uv(Vector3 _Point);

	/// <summary>
	/// The texture coordinates at a point of the outline held to its kerb: where whatever runs along the edge of the
	/// road in the texture sits.
	/// </summary>
	Vector2 KerbUv(Vector3 _Point);

	/// <summary>
	/// What the fan's centre takes on this owner's triangles.
	/// </summary>
	Vector2 CentreUv(Vector3 _Centre);

	/// <summary>
	/// Where a point sits in the mapping's own terms, in world units: what a straight run along a spoke is measured
	/// against to tell whether the spoke needs cutting into pieces.
	/// </summary>
	Vector2 Measure(Vector3 _Point);

	/// <summary>
	/// The lane the texture is laid across where a point is: U at its left edge and at its right edge, and how wide it
	/// is there in world units.
	/// </summary>
	void Lane(Vector3 _Point, out float _LeftU, out float _RightU, out float _Width);
}

/// <summary>
/// The surface of a road an owner's stretch of a fan gives way to across it (a road blending a junction's surface into
/// its own, see <see cref="RoadMouthBlend"/>): the fan's own surface where the blend starts, the road's where it ends,
/// and the blend material between.
/// </summary>
internal interface IFanBlend
{
	/// <summary>
	/// The material the blend is drawn with, and the road's own, where the blend has given way to it.
	/// </summary>
	Material Material { get; }
	Material Own { get; }

	/// <summary>
	/// How far into the blend its rows are laid, from its start to its end.
	/// </summary>
	IReadOnlyList<float> Steps { get; }

	/// <summary>
	/// How far into the blend a point is, in world units: nought where it starts, less short of it.
	/// </summary>
	float Into(Vector3 _Point);

	/// <summary>
	/// How much of the fan's own surface there is a distance into the blend.
	/// </summary>
	float WeightAt(float _Into);

	/// <summary>
	/// The road's own texture coordinates at a point, as its own material takes them.
	/// </summary>
	Vector2 OwnUv(Vector3 _Point);

	/// <summary>
	/// The blend material's coordinates where the road has <paramref name="_Own"/>, the fan
	/// <paramref name="_Uv"/>, and there is <paramref name="_Weight"/> of the fan's surface.
	/// </summary>
	Vector2 BlendUv(Vector2 _Own, Vector2 _Uv, float _Weight);
}

/// <summary>
/// Fans a surface out from its centre to its outline, each edge of the outline textured by the mapping of the owner
/// it belongs to. The owners meet on the spokes between their stretches, each side of a spoke taking its own owner's
/// coordinates, the way the arms of a junction meet on lines from the node to the middle of its corners. Spokes
/// along which a mapping bends (a road curving in, an edge's texture turning back on itself) are cut into pieces so
/// the texture follows it, and a spoke that is long for the road it reaches has a rim along the outline (see
/// <see cref="SpokeUvs"/>). The pieces lie on the spokes, so the fan's triangles keep their planes and the surface is
/// the same however finely it is cut. An owner whose road blends the fan's surface into its own across its stretch
/// (see <see cref="IFanBlend"/>) has the spokes either side of it cut at each step of the blend too, so it is laid in
/// rows across the fan.
/// </summary>
internal static class RoadFanBuilder
{
	/// <summary>
	/// How many points along a spoke how far into a blend it reaches is read at, to find where it crosses the blend's
	/// steps: it runs all but straight between them.
	/// </summary>
	private const int BlendSamples = 16;

	/// <summary>
	/// How close to all or none of a surface a blend's points can be and the triangle still be drawn in that surface
	/// alone: nearer than the vertex blend can tell apart.
	/// </summary>
	private const float WeightEpsilon = 1.0f / 512.0f;

	/// <summary>
	/// How far (in world units) a straight run of a mapping along a spoke may drift from the mapping itself before
	/// the spoke is cut into pieces.
	/// </summary>
	private const float SpokeTolerance = 2.0f;

	/// <summary>
	/// The most pieces a spoke is cut into.
	/// </summary>
	private const int MaxSpokePieces = 8;

	/// <summary>
	/// A spoke longer than this many times the width of the road it reaches has a rim.
	/// </summary>
	private const float RimReach = 2.0f;

	/// <summary>
	/// How deep a rim is, as a share of the width of the road it runs along: room for the texture's edge band.
	/// </summary>
	private const float RimDepth = 0.15f;

	/// <summary>
	/// How far across its road a kerb runs (the sine of the angle between them) where a rim starts turning the
	/// texture back into the lane, and where it has turned it back in full.
	/// </summary>
	private const float FoldFrom = 0.15f;
	private const float FoldTo = 0.5f;

	/// <summary>
	/// How far past its lane's edge (in U) a kerb has to lie for a rim to turn the texture back in full.
	/// </summary>
	private const float FoldBeyond = 0.05f;



	/// <summary>
	/// Adds the fan to a mesh. <paramref name="_Owners"/> names, for each outline point, the owner of the edge leaving
	/// it; <paramref name="_Kerbs"/> whether that owner's outline points are held to their kerb. Vertices come from
	/// <paramref name="_Vertex"/> when given (to share them with the rest of a mesh, or set their heights), straight
	/// from the mesh otherwise. An open fan (<paramref name="_Closed"/> false) stops at its last point instead of
	/// closing back to its first: a fan from a point on the edge of something else, along which its two end spokes lie.
	/// <paramref name="_Blends"/> gives, by owner, the road that owner's stretch gives way to, if any.
	/// </summary>
	public static void Build(PolygonMesh _Mesh, Material _Material, Vector3 _Centre, IReadOnlyList<Vector3> _Points, IReadOnlyList<int> _Owners,
		IReadOnlyList<IFanMapping> _Mappings, IReadOnlyList<bool> _Kerbs, Func<Vector3, HalfEdgeMesh.VertexHandle> _Vertex = null, bool _Closed = true,
		IReadOnlyList<IFanBlend> _Blends = null)
	{
		int count = _Points.Count;

		if (count < (_Closed ? 3 : 2))
			return;

		_Vertex ??= position => _Mesh.AddVertices(position)[0];

		// The spokes: where along each its points are (nought at the centre, one at the outline), how much of it is
		// rim, and their vertices.
		var centreHandle = _Vertex(_Centre);
		var fractions = new float[count][];
		var rims = new float[count];
		var spokes = new HalfEdgeMesh.VertexHandle[count][];

		for (int k = 0; k < count; k++)
		{
			// An open fan's end spokes are shared with whatever it is fanned from the edge of, which knows nothing of
			// any points along them: they stay whole, so the two meet point for point.
			if (!_Closed && (k == 0 || k == count - 1))
			{
				fractions[k] = new[] { 0.0f, 1.0f };
			}
			else
			{
				int entering = _Owners[(k + count - 1) % count];
				int leaving = _Owners[k];

				fractions[k] = SpokeFractions(_Mappings[entering], _Mappings[leaving], _Centre, _Points[k], out rims[k]);
				fractions[k] = BlendCuts(fractions[k], rims[k], BlendOf(_Blends, entering), BlendOf(_Blends, leaving), _Centre, _Points[k]);
			}

			var spoke = new HalfEdgeMesh.VertexHandle[fractions[k].Length];

			spoke[0] = centreHandle;

			for (int j = 1; j < spoke.Length; j++)
				spoke[j] = _Vertex(j == spoke.Length - 1 ? _Points[k] : Vector3.Lerp(_Centre, _Points[k], fractions[k][j]));

			spokes[k] = spoke;
		}

		for (int k = 0; k < (_Closed ? count : count - 1); k++)
		{
			int next = (k + 1) % count;
			var mapping = _Mappings[_Owners[k]];
			bool kerbs = _Kerbs[_Owners[k]];
			var uvs = SpokeUvs(mapping, kerbs, _Centre, _Points, k, _Closed, fractions[k], rims[k]);
			var nextUvs = SpokeUvs(mapping, kerbs, _Centre, _Points, next, _Closed, fractions[next], rims[next]);

			if (BlendOf(_Blends, _Owners[k]) is { } blend)
				ZipBlended(_Mesh, _Material, blend, _Centre, _Points[k], spokes[k], fractions[k], uvs, _Points[next], spokes[next], fractions[next], nextUvs);
			else
				Zip(_Mesh, _Material, spokes[k], fractions[k], uvs, spokes[next], fractions[next], nextUvs);
		}
	}



	/// <summary>
	/// The road an owner's stretch gives way to, if any.
	/// </summary>
	private static IFanBlend BlendOf(IReadOnlyList<IFanBlend> _Blends, int _Owner)
	{
		return _Blends is not null && _Owner >= 0 && _Owner < _Blends.Count ? _Blends[_Owner] : null;
	}



	/// <summary>
	/// A spoke's points (see <see cref="SpokeFractions"/>) with more where it crosses each step of a blend either side of
	/// it, inside its rim, so the blend is laid in rows across the fan as it is along the road. A crossing within a unit of
	/// a point the spoke has already is left out.
	/// </summary>
	private static float[] BlendCuts(float[] _Fractions, float _Rim, IFanBlend _Entering, IFanBlend _Leaving, Vector3 _Centre, Vector3 _End)
	{
		if (_Entering is null && _Leaving is null)
			return _Fractions;

		float reach = 1.0f - _Rim;
		int innerLast = _Rim > 0.0f ? _Fractions.Length - 2 : _Fractions.Length - 1;
		float gap = 1.0f / MathF.Max(1.0f, Flat(_End - _Centre).Length);
		var points = new List<float>();
		var cuts = new List<float>();

		for (int j = 0; j <= innerLast; j++)
			points.Add(_Fractions[j]);

		Crossings(_Entering, _Centre, _End, reach, cuts);

		if (_Leaving != _Entering)
			Crossings(_Leaving, _Centre, _End, reach, cuts);

		foreach (float cut in cuts)
		{
			if (cut > 0.0f && cut < reach && points.TrueForAll(point => MathF.Abs(point - cut) >= gap))
				points.Add(cut);
		}

		points.Sort();

		if (_Rim > 0.0f)
			points.Add(_Fractions[^1]);

		return points.ToArray();
	}



	/// <summary>
	/// Adds where a spoke crosses each step of a blend, as shares of its length out to <paramref name="_Reach"/>.
	/// </summary>
	private static void Crossings(IFanBlend _Blend, Vector3 _Centre, Vector3 _End, float _Reach, List<float> _Cuts)
	{
		if (_Blend is null)
			return;

		var into = new float[BlendSamples + 1];

		for (int i = 0; i <= BlendSamples; i++)
			into[i] = _Blend.Into(Vector3.Lerp(_Centre, _End, _Reach * i / BlendSamples));

		foreach (float step in _Blend.Steps)
		{
			for (int i = 0; i < BlendSamples; i++)
			{
				float from = into[i] - step;
				float to = into[i + 1] - step;

				if (from * to > 0.0f || from == to)
					continue;

				_Cuts.Add(_Reach * (i + from / (from - to)) / BlendSamples);
			}
		}
	}



	/// <summary>
	/// Where along a spoke its points are, as shares of its length: evenly out to its rim (to the outline itself, for
	/// a spoke without one) in as many pieces as either owner it borders needs, then the outline. A spoke longer than
	/// <see cref="RimReach"/> times the wider of the two owners' roads has a rim <see cref="RimDepth"/> of that width
	/// deep; <paramref name="_Rim"/> is the share of the spoke it takes, nought for none.
	/// </summary>
	private static float[] SpokeFractions(IFanMapping _Entering, IFanMapping _Leaving, Vector3 _Centre, Vector3 _End, out float _Rim)
	{
		_Entering.Lane(_End, out _, out _, out float enteringWidth);
		_Leaving.Lane(_End, out _, out _, out float leavingWidth);

		float width = MathF.Max(enteringWidth, leavingWidth);
		float length = Flat(_End - _Centre).Length;

		_Rim = length > RimReach * width && length > 0.001f ? RimDepth * width / length : 0.0f;

		Vector3 inner = Vector3.Lerp(_Centre, _End, 1.0f - _Rim);
		int pieces = Math.Max(SpokePieces(_Entering, _Centre, inner), SpokePieces(_Leaving, _Centre, inner));
		var fractions = new float[pieces + (_Rim > 0.0f ? 2 : 1)];

		for (int j = 0; j <= pieces; j++)
			fractions[j] = (1.0f - _Rim) * j / pieces;

		fractions[^1] = 1.0f;

		return fractions;
	}



	/// <summary>
	/// How many pieces the spoke from the centre to an outline point needs for a straight run of a mapping along each
	/// to stay within <see cref="SpokeTolerance"/> of the mapping itself.
	/// </summary>
	private static int SpokePieces(IFanMapping _Mapping, Vector3 _Centre, Vector3 _End)
	{
		Vector2 from = _Mapping.Measure(_Centre);
		Vector2 to = _Mapping.Measure(_End);
		float drift = 0.0f;

		for (int i = 1; i < 4; i++)
		{
			float t = i * 0.25f;

			drift = MathF.Max(drift, (_Mapping.Measure(Vector3.Lerp(_Centre, _End, t)) - Vector2.Lerp(from, to, t)).Length);
		}

		// A straight run's drift from a curve falls with the square of its length.
		return Math.Clamp((int)MathF.Ceiling(MathF.Sqrt(drift / SpokeTolerance)), 1, MaxSpokePieces);
	}



	/// <summary>
	/// The mapping at each point of a spoke: the owner's own, with whatever the centre and the outline point are held
	/// to worked in evenly from nothing at the other end, so the texture never folds back on itself on the way.
	/// Worked in over the whole of a long spoke, the outline point's hold would draw its kerb's band out along the
	/// spoke, so a long spoke has a rim: its point there takes the value that leaves the spoke inside it just as it
	/// would be without one, unless the kerb runs across its road and lies past its lane's edge (the nose between two
	/// roads that split apart, seen from a node far back). There it is set as far into the lane as the texture is past
	/// it, turned back in, so the band runs round the nose within the rim while the texture inside still changes from
	/// one spoke to the next rather than being smeared along them.
	/// </summary>
	private static Vector2[] SpokeUvs(IFanMapping _Mapping, bool _Kerbs, Vector3 _Centre, IReadOnlyList<Vector3> _Points, int _Index, bool _Closed,
		float[] _Fractions, float _Rim)
	{
		Vector3 end = _Points[_Index];
		var uvs = new Vector2[_Fractions.Length];
		Vector2 centre = _Mapping.Uv(_Centre);
		Vector2 centreHeld = _Mapping.CentreUv(_Centre);
		Vector2 outer = _Mapping.Uv(end);
		Vector2 outerHeld = _Kerbs ? _Mapping.KerbUv(end) : outer;

		// The stretch inside the rim, out to the rim's point (the outline itself, without a rim).
		float reach = 1.0f - _Rim;
		int innerLast = _Rim > 0.0f ? _Fractions.Length - 2 : _Fractions.Length - 1;
		Vector2 inner = outer;
		Vector2 innerHeld = outerHeld;

		if (_Rim > 0.0f)
		{
			inner = _Mapping.Uv(Vector3.Lerp(_Centre, end, reach));
			innerHeld = inner + (centreHeld - centre) * _Rim + (outerHeld - outer) * reach;

			if (_Kerbs)
			{
				int count = _Points.Count;
				Vector3 before = _Points[_Closed ? (_Index + count - 1) % count : Math.Max(_Index - 1, 0)];
				Vector3 after = _Points[_Closed ? (_Index + 1) % count : Math.Min(_Index + 1, count - 1)];
				float field = inner.x + (centreHeld.x - centre.x) * _Rim;

				innerHeld.x = Folded(_Mapping, _Centre, before, end, after, _Rim, innerHeld.x, field, outerHeld.x, outer.x);
			}
		}

		for (int j = 0; j <= innerLast; j++)
		{
			float t = _Fractions[j] / reach;
			Vector2 uv = j == 0 ? centre : j == innerLast ? inner : _Mapping.Uv(Vector3.Lerp(_Centre, end, _Fractions[j]));

			uvs[j] = uv + (centreHeld - centre) * (1.0f - t) + (innerHeld - inner) * t;
		}

		if (_Rim > 0.0f)
			uvs[^1] = outerHeld;

		return uvs;
	}



	/// <summary>
	/// U at a rim's point: <paramref name="_Plain"/> (the spoke's own value there), turned as far towards the
	/// texture's own U there (<paramref name="_Field"/>, with the centre's hold worked in), reflected back into the
	/// lane about the rim's own depth into it, as the kerb (U <paramref name="_Outline"/> on the mapping, held to
	/// <paramref name="_Edge"/>) runs across its road and lies past its lane's edge. Reflected rather than clamped, the
	/// texture keeps changing from one rim point to the next; about the rim's depth rather than the lane's edge, the
	/// edge band never shows where the texture crosses the edge.
	/// </summary>
	private static float Folded(IFanMapping _Mapping, Vector3 _Centre, Vector3 _Before, Vector3 _End, Vector3 _After, float _Rim, float _Plain, float _Field,
		float _Edge, float _Outline)
	{
		Vector2 chord = Flat(_After - _Before);
		Vector2 spoke = Flat(_End - _Centre);
		float chordLength = chord.Length;
		float spokeLength = spoke.Length;

		if (chordLength < 0.001f || spokeLength < 0.001f)
			return _Plain;

		float along = MathF.Min(1.0f, MathF.Abs(_Mapping.Measure(_After).y - _Mapping.Measure(_Before).y) / chordLength);
		float across = MathF.Sqrt(1.0f - along * along);

		_Mapping.Lane(_End, out float leftU, out float rightU, out float width);

		float middle = (leftU + rightU) * 0.5f;
		float inward = middle >= _Edge ? 1.0f : -1.0f;
		float beyond = MathF.Max(0.0f, (_Edge - _Outline) * inward);
		float weight = SmoothStep(FoldFrom, FoldTo, across) * SmoothStep(0.0f, FoldBeyond, beyond);

		if (weight <= 0.0f)
			return _Plain;

		// How deep into the lane the rim's point is, square to the kerb, and how deep the texture there is.
		float facing = MathF.Abs(Cross(spoke, chord)) / (spokeLength * chordLength);
		float lateral = width > 0.001f ? MathF.Abs(rightU - leftU) / width : 0.0f;
		float rimDepth = _Rim * spokeLength * facing * lateral;
		float fieldDepth = (_Field - _Edge) * inward;
		float depth = MathF.Min(MathF.Abs(middle - _Edge), rimDepth + MathF.Abs(fieldDepth - rimDepth));

		return _Plain + (_Edge + inward * depth - _Plain) * weight;
	}



	/// <summary>
	/// Fills one triangle of the fan, between two spokes that start at the centre, with triangles from one to the other
	/// in the order their points reach out (<paramref name="_AtA"/> and <paramref name="_AtB"/>, as shares of each
	/// spoke), wound the way the fan triangle is.
	/// </summary>
	private static void Zip(PolygonMesh _Mesh, Material _Material, HalfEdgeMesh.VertexHandle[] _A, float[] _AtA, Vector2[] _UvA, HalfEdgeMesh.VertexHandle[] _B,
		float[] _AtB, Vector2[] _UvB)
	{
		var handles = Join(_A, _B);
		var uvs = Join(_UvA, _UvB);

		foreach (var (p, q, r) in Strip(_AtA, _AtB))
			MeshUtility.AddTexturedTriangle(_Mesh, _Material, handles[p], handles[q], handles[r], uvs[p], uvs[q], uvs[r]);
	}



	/// <summary>
	/// Fills one triangle of the fan as <see cref="Zip"/> does, over a stretch whose road gives way to the fan's surface
	/// across it: out along the spokes in the order their points reach into the blend, so each of its steps is a row
	/// across the fan. A triangle that is all the fan's surface is drawn in it as the rest of the fan is, one that is all
	/// the road's in the road's own material, and anything between in the blend material, its points painted with how
	/// much of the fan's surface there is at each.
	/// </summary>
	private static void ZipBlended(PolygonMesh _Mesh, Material _Material, IFanBlend _Blend, Vector3 _Centre,
		Vector3 _EndA, HalfEdgeMesh.VertexHandle[] _A, float[] _AtA, Vector2[] _UvA,
		Vector3 _EndB, HalfEdgeMesh.VertexHandle[] _B, float[] _AtB, Vector2[] _UvB)
	{
		var handles = Join(_A, _B);
		var uvs = Join(_UvA, _UvB);
		var positions = Join(SpokePoints(_Centre, _EndA, _AtA), SpokePoints(_Centre, _EndB, _AtB));
		int count = positions.Length;
		var into = new float[count];
		var weights = new float[count];
		var own = new Vector2[count];
		var mixed = new Vector2[count];

		for (int j = 0; j < count; j++)
		{
			into[j] = _Blend.Into(positions[j]);
			weights[j] = _Blend.WeightAt(into[j]);
			own[j] = _Blend.OwnUv(positions[j]);
			mixed[j] = _Blend.BlendUv(own[j], uvs[j], weights[j]);
		}

		foreach (var (p, q, r) in Strip(into[.._A.Length], into[_A.Length..]))
		{
			float least = MathF.Min(weights[p], MathF.Min(weights[q], weights[r]));
			float most = MathF.Max(weights[p], MathF.Max(weights[q], weights[r]));

			if (least >= 1.0f - WeightEpsilon)
			{
				MeshUtility.AddTexturedTriangle(_Mesh, _Material, handles[p], handles[q], handles[r], uvs[p], uvs[q], uvs[r]);
				continue;
			}

			if (most <= WeightEpsilon)
			{
				MeshUtility.AddTexturedTriangle(_Mesh, _Blend.Own, handles[p], handles[q], handles[r], own[p], own[q], own[r]);
				continue;
			}

			var face = MeshUtility.AddTexturedTriangle(_Mesh, _Blend.Material, handles[p], handles[q], handles[r], mixed[p], mixed[q], mixed[r]);

			MeshUtility.SetFaceBlends(_Mesh, face, new[] { RoadSurfaceBuilder.BlendColour(weights[p]), RoadSurfaceBuilder.BlendColour(weights[q]), RoadSurfaceBuilder.BlendColour(weights[r]) });
		}
	}



	/// <summary>
	/// The triangles filling one triangle of the fan between two spokes that start at the centre, from one to the other
	/// in the order their points reach out by <paramref name="_A"/> and <paramref name="_B"/>, wound the way the fan
	/// triangle is: each as three points, A's numbered from nought and B's on from the end of A's.
	/// </summary>
	private static IEnumerable<(int, int, int)> Strip(float[] _A, float[] _B)
	{
		int lastA = _A.Length - 1;
		int lastB = _B.Length - 1;
		int offset = _A.Length;
		int a = 1;
		int b = 1;

		yield return (0, 1, offset + 1);

		while (a < lastA || b < lastB)
		{
			// Step out along whichever spoke is further behind.
			if (b == lastB || (a < lastA && _A[a + 1] <= _B[b + 1]))
			{
				yield return (a, a + 1, offset + b);
				a++;
			}
			else
			{
				yield return (a, offset + b + 1, offset + b);
				b++;
			}
		}
	}



	/// <summary>
	/// Where a spoke's points are: the centre, the outline point, and the shares of the way between.
	/// </summary>
	private static Vector3[] SpokePoints(Vector3 _Centre, Vector3 _End, float[] _Fractions)
	{
		var points = new Vector3[_Fractions.Length];

		for (int j = 0; j < points.Length; j++)
			points[j] = j == 0 ? _Centre : j == points.Length - 1 ? _End : Vector3.Lerp(_Centre, _End, _Fractions[j]);

		return points;
	}



	private static T[] Join<T>(T[] _A, T[] _B)
	{
		var joined = new T[_A.Length + _B.Length];

		_A.CopyTo(joined, 0);
		_B.CopyTo(joined, _A.Length);

		return joined;
	}



	private static float SmoothStep(float _From, float _To, float _Value)
	{
		float t = Math.Clamp((_Value - _From) / (_To - _From), 0.0f, 1.0f);

		return t * t * (3.0f - 2.0f * t);
	}



	private static Vector2 Flat(Vector3 _Vector)
	{
		return new Vector2(_Vector.x, _Vector.y);
	}



	private static float Cross(Vector2 _A, Vector2 _B)
	{
		return _A.x * _B.y - _A.y * _B.x;
	}
}