RoadComponent/RoadComponent.Road.cs
using Sandbox;

namespace RedSnail.RoadTool;

public partial class RoadComponent
{
	[Property(Title = "🔒 Locked"), Feature("Road")] private bool IsRoadLocked { get; set; } = false;
	[Property(Title = "Material"), Feature("Road", Icon = "fork_left", Tint = EditorTint.Green)] private Material RoadMaterial { get; set { field = value; IsDirty = true; } }
	[Property(Title = "Width"), Feature("Road"), Range(10.0f, 1000.0f)] public float RoadWidth { get; set { field = value; IsDirty = true; } } = 500.0f;
	[Property(Title = "Precision"), Feature("Road"), Range(10.0f, 100.0f)] private float RoadPrecision { get; set { field = value.Clamp(1.0f, 10000.0f); IsDirty = true; } } = 40.0f;
	[Property(Title = "Texture Repeat"), Feature("Road")] private float RoadTextureInchesPerRepeat { get; set { field = value.Clamp(1.0f, 100000.0f); IsDirty = true; } } = 500.0f;



	private void BuildRoadMesh()
	{
		GetSplineFrameData(out var frames, out var segmentsToKeep);

		if (segmentsToKeep.Count < 2)
			return;

		var polygonMesh = new PolygonMesh();
		var material = RoadMaterial ?? Material.Load("materials/dev/reflectivity_30.vmat");
		float halfWidth = RoadWidth * 0.5f;
		var frameVertices = new HalfEdgeMesh.VertexHandle[segmentsToKeep.Count][];

		for (int i = 0; i < segmentsToKeep.Count; i++)
		{
			Transform frame = frames[segmentsToKeep[i]];
			Vector3 p = frame.Position;
			Vector3 right = frame.Rotation.Right;
			Vector3 l = p - right * halfWidth;
			Vector3 r = p + right * halfWidth;

			frameVertices[i] = polygonMesh.AddVertices(l, r);
		}

		for (int i = 0; i < segmentsToKeep.Count - 1; i++)
		{
			int idx0 = segmentsToKeep[i];
			int idx1 = segmentsToKeep[i + 1];

			Transform f0 = frames[idx0];
			Transform f1 = frames[idx1];

			Vector3 p0 = f0.Position;
			Vector3 p1 = f1.Position;

			Vector3 right0 = f0.Rotation.Right;
			Vector3 right1 = f1.Rotation.Right;

			Vector3 l0 = p0 - right0 * halfWidth;
			Vector3 r0 = p0 + right0 * halfWidth;
			Vector3 l1 = p1 - right1 * halfWidth;
			Vector3 r1 = p1 + right1 * halfWidth;

			Vector2 uv00 = new Vector2(l0.x, l0.y) / RoadTextureInchesPerRepeat;
			Vector2 uv10 = new Vector2(r0.x, r0.y) / RoadTextureInchesPerRepeat;
			Vector2 uv11 = new Vector2(r1.x, r1.y) / RoadTextureInchesPerRepeat;
			Vector2 uv01 = new Vector2(l1.x, l1.y) / RoadTextureInchesPerRepeat;

			MeshUtility.AddTexturedQuad(polygonMesh, material, frameVertices[i][0], frameVertices[i][1], frameVertices[i + 1][1], frameVertices[i + 1][0], uv00, uv10, uv11, uv01);
		}

		CreateRoadMeshComponent(polygonMesh);
	}



	private void CreateRoadMeshComponent(PolygonMesh _PolygonMesh)
	{
		var child = new GameObject(GameObject, true, "Road");
		child.Tags.Add(RoadMeshTag);
		child.Tags.Add(RoadSurfaceTag);

		var meshComponent = child.AddComponent<MeshComponent>();
		meshComponent.Mesh = _PolygonMesh;
		meshComponent.SmoothingAngle = 40.0f;
	}



	private void EnsureRoadMeshExist()
	{
		if (SandboxUtility.IsInPlayMode)
			return;

		if (IsRoadLocked)
			return;

		if (HasGeneratedMeshChildren(RoadSurfaceTag))
			return;

		BuildRoadMesh();
	}
}