Code/RoadComponent/RoadComponent.Lines.cs
using System;
using Sandbox;
namespace RedSnail.RoadTool;
public partial class RoadComponent
{
[Property, FeatureEnabled("Lines", Icon = "show_chart", Tint = EditorTint.Yellow), Change] private bool HasLines { get; set; } = false;
[Property(Title = "Lines"), Feature("Lines")] public RoadLineDefinition[] LineDefinitions { get; set { field = value; IsDirty = true; } }
[Property(Title = "Offset"), Feature("Lines"), Range(0.01f, 1.0f)] private float LinesOffset { get; set { field = value; IsDirty = true; } } = 0.1f;
[Property(Title = "Width"), Feature("Lines"), Range(1.0f, 50.0f)] private float LinesWidth { get; set { field = value; IsDirty = true; } } = 5.0f;
[Property(Title = "Extra Spacing"), Feature("Lines"), Range(0.0f, 1000.0f)] private float LinesExtraSpacing { get; set { field = value; IsDirty = true; } } = 0.0f;
[Property(Title = "Texture Repeat"), Feature("Lines")] private float LinesTextureRepeat { get; set { field = value.Clamp(1.0f, 100000.0f); IsDirty = true; } } = 10.0f;
private void OnHasLinesChanged(bool _OldValue, bool _NewValue)
{
IsDirty = true;
}
private void CreateLines()
{
EnsureLinesMeshExist();
}
private void UpdateLines()
{
}
private void RemoveLines()
{
RemoveGeneratedMeshChildren(LineSurfaceTag);
}
private void EnsureLinesMeshExist()
{
if (SandboxUtility.IsInPlayMode)
return;
if (HasGeneratedMeshChildren(LineSurfaceTag))
return;
BuildLinesMesh();
}
private void RebuildLinesMesh()
{
if (SandboxUtility.IsInPlayMode)
return;
RemoveGeneratedMeshChildren(LineSurfaceTag);
BuildLinesMesh();
}
private void BuildLinesMesh()
{
if (!HasLines || LineDefinitions == null || LineDefinitions.Length == 0)
return;
GetSplineFrameData(out var frames, out var segmentsToKeep);
int finalSegmentCount = segmentsToKeep.Count - 1;
if (finalSegmentCount <= 0)
return;
float roadWidth = RoadWidth + LinesExtraSpacing;
float lineSpacing = roadWidth / (LineDefinitions.Length + 1);
var polygonMeshes = new PolygonMesh[LineDefinitions.Length];
for (int i = 0; i < LineDefinitions.Length; i++)
polygonMeshes[i] = new PolygonMesh();
float[] lineDistances = new float[LineDefinitions.Length];
for (int i = 0; i < finalSegmentCount; 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;
for (int line = 0; line < LineDefinitions.Length; line++)
{
float offsetFromCenter = ((line + 1) * lineSpacing) - (roadWidth * 0.5f);
Vector3 center0 =
p0 +
f0.Rotation.Right * offsetFromCenter +
f0.Rotation.Up * LinesOffset;
Vector3 center1 =
p1 +
f1.Rotation.Right * offsetFromCenter +
f1.Rotation.Up * LinesOffset;
float segmentLength = Vector3.DistanceBetween(center0, center1);
Vector3 dir = (center1 - center0).Normal;
float remaining = segmentLength;
Vector3 curCenter = center0;
float dashSpacing = LineDefinitions[line]?.DashSpacing ?? 0.0f;
float dashFillRatio = LineDefinitions[line]?.DashFillRatio ?? 1.0f;
float dashLength = dashSpacing * dashFillRatio;
float halfWidth = LinesWidth * 0.5f;
var polygonMesh = polygonMeshes[line];
var material = LineDefinitions[line]?.Material ?? Material.Load("materials/default.vmat");
while (remaining > 0.001f)
{
float linePos = lineDistances[line];
float cyclePos = dashSpacing > 0 ? linePos % dashSpacing : 0;
if (cyclePos < 0.0001f)
cyclePos = 0.0f;
if (dashSpacing > 0 && dashSpacing - cyclePos < 0.0001f)
cyclePos = dashSpacing;
bool inDash = dashSpacing <= 0 || cyclePos <= dashLength - 0.0001f;
float step;
if (dashSpacing <= 0)
step = remaining;
else if (inDash)
step = dashLength - cyclePos;
else
step = dashSpacing - cyclePos;
step = Math.Max(step, 0.01f);
step = Math.Min(step, remaining);
Vector3 nextCenter = curCenter + dir * step;
if (inDash)
{
Vector3 l0 = curCenter - right0 * halfWidth;
Vector3 r0 = curCenter + right0 * halfWidth;
Vector3 l1 = nextCenter - right0 * halfWidth;
Vector3 r1 = nextCenter + right0 * halfWidth;
float v0 = linePos / LinesTextureRepeat;
float v1 = (linePos + step) / LinesTextureRepeat;
var verts = polygonMesh.AddVertices(l0, r0, r1, l1);
MeshUtility.AddTexturedQuad(polygonMesh, material, verts[0], verts[1], verts[2], verts[3],
new Vector2(0, v0), new Vector2(1, v0),
new Vector2(1, v1), new Vector2(0, v1));
}
lineDistances[line] += step;
curCenter = nextCenter;
remaining -= step;
}
}
}
for (int line = 0; line < LineDefinitions.Length; line++)
{
var child = new GameObject(GameObject, true, $"Line_{line}");
child.Tags.Add(LineSurfaceTag);
var meshComponent = child.AddComponent<MeshComponent>();
meshComponent.Mesh = polygonMeshes[line];
meshComponent.Collision = MeshComponent.CollisionType.None;
meshComponent.RenderType = ModelRenderer.ShadowRenderType.Off;
meshComponent.SmoothingAngle = 40.0f;
meshComponent.Static = true;
}
}
}