RoadComponent/RoadComponent.cs
using System.Linq;
using Sandbox;
namespace RedSnail.RoadTool;
/// <summary>
/// Represents a road component that can be manipulated within the editor and at runtime.
/// </summary>
[Icon("signpost")]
public partial class RoadComponent : Component, Component.ExecuteInEditor, Component.IHasBounds
{
[Property, Feature("General"), Hide]
public Spline Spline = new();
private bool m_DoesRoadMeshNeedRebuild;
private const string RoadMeshTag = "road_mesh";
private const string RoadSurfaceTag = "road_surface";
private const string SidewalkSurfaceTag = "road_sidewalk";
private const string LineSurfaceTag = "road_lines";
[Property, Feature("General", Icon = "public", Tint = EditorTint.White), Category("Optimization")] private bool AutoSimplify { get; set { field = value; IsDirty = true; } } = false;
[Property, Feature("General"), Category("Optimization"), Range(0.1f, 10.0f)] private float StraightThreshold { get; set { field = value; IsDirty = true; } } = 1.0f; // Degrees - how straight before merging
[Property, Feature("General"), Category("Optimization"), Range(2, 50)] private int MinSegmentsToMerge { get; set { field = value; IsDirty = true; } } = 3; // Minimum consecutive straight segments before merging
[Property, Feature("General"), Category("Miscellaneous")] public bool UseRotationMinimizingFrames { get; set { field = value; IsDirty = true; } }
private bool IsDirty
{
get;
set
{
field = value;
m_DoesRoadMeshNeedRebuild = value;
m_DoesLamppostsNeedRebuild = value;
}
}
public BBox LocalBounds => Spline.Bounds;
public RoadComponent()
{
Spline.InsertPoint(Spline.PointCount, new Spline.Point { Position = new Vector3(0, 0, 0) });
Spline.InsertPoint(Spline.PointCount, new Spline.Point { Position = new Vector3(1000, 0, 0) });
Spline.InsertPoint(Spline.PointCount, new Spline.Point { Position = new Vector3(1600, 1000, 0) });
}
protected override void OnEnabled()
{
Spline.SplineChanged += UpdateData;
EnsureRoadMeshExist();
EnsureSidewalkMeshExist();
EnsureBridgeMeshExist();
CreateLines();
CreateDecals();
CreateLampposts();
CreateCrosswalks();
}
protected override void OnDisabled()
{
Spline.SplineChanged -= UpdateData;
RemoveRoadMesh();
RemoveSidewalkMesh();
RemoveLines();
RemoveDecals();
RemoveLampposts();
RemoveCrosswalks();
RemoveBridge();
}
protected override void OnUpdate()
{
UpdateRoadMeshes();
UpdateLines();
UpdateDecals();
UpdateLampposts();
UpdateCrosswalks();
UpdateBridge();
}
private void UpdateRoadMeshes()
{
if (!m_DoesRoadMeshNeedRebuild)
return;
RebuildRoadMesh();
RebuildSidewalkMesh();
RebuildLinesMesh();
m_DoesRoadMeshNeedRebuild = false;
}
private void RebuildRoadMesh()
{
if (SandboxUtility.IsInPlayMode)
return;
if (IsRoadLocked)
return;
RemoveGeneratedMeshChildren(RoadSurfaceTag);
BuildRoadMesh();
}
private void RebuildSidewalkMesh()
{
if (SandboxUtility.IsInPlayMode)
return;
if (IsSidewalkLocked)
return;
RemoveGeneratedMeshChildren(SidewalkSurfaceTag);
BuildSidewalkMesh();
}
private void RemoveRoadMesh()
{
if (IsRoadLocked)
return;
RemoveGeneratedMeshChildren(RoadSurfaceTag);
}
private void RemoveSidewalkMesh()
{
if (IsSidewalkLocked)
return;
RemoveGeneratedMeshChildren(SidewalkSurfaceTag);
}
private void RemoveGeneratedMeshChildren(string _Tag)
{
var toRemove = GameObject.Children.Where(child => child.Tags.Has(_Tag)).ToList();
foreach (var child in toRemove)
child.Destroy();
}
private bool HasGeneratedMeshChildren(string _Tag)
{
return GameObject.Children.Any(child => child.Tags.Has(_Tag));
}
private void UpdateData()
{
if (Scene.IsEditor)
IsDirty = true;
}
}