Code/RoadParkingLotComponent/RoadParkingLotComponent.Lines.cs
using System;
using Sandbox;

namespace RedSnail.RoadTool;

[Flags]
public enum LineCap
{
	[Hide]
	None = 0,
	Start = 1 << 0,
	End = 1 << 1
}

public partial class RoadParkingLotComponent
{
	/// <summary>
	/// The material used to render the line segments between each parking spots
	/// </summary>
	[Property(Title = "Material"), Feature("Lines", Icon = "show_chart", Tint = EditorTint.Yellow)] private Material LinesMaterial { get; set { field = value; m_IsDirty = true; } }

	/// <summary>
	/// Do we want line caps at the start and end of the parking lot ?
	/// </summary>
	[Property(Title = "Caps"), Feature("Lines")] private LineCap LinesCap { get; set { field = value; m_IsDirty = true; } } = LineCap.Start | LineCap.End;

	/// <summary>
	/// The width of a line segment.
	/// </summary>
	[Property(Title = "Width"), Feature("Lines"), Range(1.0f, 50.0f)] private float LinesWidth { get; set { field = value; m_IsDirty = true; } } = 5.0f;

	/// <summary>
	/// The height offset above the ground (This is used to avoid Z fighting with the ground)
	/// </summary>
	[Property(Title = "Offset"), Feature("Lines"), Range(0.01f, 1.0f)] private float LinesOffset { get; set { field = value; m_IsDirty = true; } } = 0.1f;

	/// <summary>
	/// How many units before the line texture repeat itself (This is used to avoid stretching)
	/// </summary>
	[Property(Title = "Texture Repeat"), Feature("Lines")] private float LinesTextureRepeat { get; set { field = value.Clamp(1.0f, 100000.0f); m_IsDirty = true; } } = 10.0f;



	private void BuildParkingLines()
	{
		var material = LinesMaterial ?? Material.Load("materials/dev/reflectivity_90.vmat");
		var mesh = new PolygonMesh();

		for (int i = 0; i <= SpotCount; i++)
		{
			if (i == 0 && !LinesCap.HasFlag(LineCap.Start))
				continue;

			if (i == SpotCount && !LinesCap.HasFlag(LineCap.End))
				continue;

			float xPos = i * CalculateSpacing();
			DrawLine(mesh, material, xPos);
		}

		var child = new GameObject(GameObject, true, "ParkingLines");
		child.Tags.Add(LinesTag);

		var meshComponent = child.AddComponent<MeshComponent>();
		meshComponent.Mesh = mesh;
		meshComponent.Collision = MeshComponent.CollisionType.None;
		meshComponent.RenderType = ModelRenderer.ShadowRenderType.Off;
		meshComponent.SmoothingAngle = 40.0f;
		meshComponent.Static = true;
	}



	private void DrawLine(PolygonMesh _Mesh, Material _Material, float _PositionX)
	{
		float hw = LinesWidth * 0.5f;
		float angleRad = SpotAngle.DegreeToRadian();
		float sinAngle = float.Sin(angleRad);
		float cosAngle = float.Cos(angleRad);

		Vector3 lineDir = new Vector3(-sinAngle, cosAngle, 0);
		Vector3 perpDir = new Vector3(cosAngle, sinAngle, 0);
		Vector3 up = Vector3.Up;

		Vector3 basePos = new Vector3(_PositionX, 0, 0);

		Vector3 p0 = basePos - perpDir * hw;
		Vector3 p1 = basePos + perpDir * hw;
		Vector3 p2 = p1 + lineDir * SpotLength;
		Vector3 p3 = p0 + lineDir * SpotLength;

		Vector3 t0 = p0 + up * LinesOffset;
		Vector3 t1 = p1 + up * LinesOffset;
		Vector3 t2 = p2 + up * LinesOffset;
		Vector3 t3 = p3 + up * LinesOffset;

		float v0 = SpotLength / LinesTextureRepeat;

		var verts = _Mesh.AddVertices(t1, t2, t3, t0);
		MeshUtility.AddTexturedQuad(_Mesh, _Material, verts[0], verts[1], verts[2], verts[3],
			new Vector2(1, 0), new Vector2(1, v0), new Vector2(0, v0), new Vector2(0, 0));
	}
}