An editor tool that paints road markings along a drawn path as generated geometry. Supports a solid line, dashes with configurable length and gap, and a double line, and builds the ribbon quads into a MeshKit mesh with a set line width and height offset.
namespace AdvancedMappingTools;
public enum MarkingStyle
{
Solid,
Dashed,
[Title( "Double" )] Double
}
/// <summary>
/// Painted road markings along a path: a solid line, dashes or a double line.
/// </summary>
[ToolCategory( "Roads & Paths" )]
[Title( "Road Markings" ), Icon( "more_horiz" )]
public sealed class RoadMarkingsEditor( PrimitiveTool tool ) : PathEditor( tool )
{
protected override int MinPoints => 2;
[Property, Group( "Markings" ), WideMode]
public MarkingStyle Style { get; set; } = MarkingStyle.Solid;
[Property, Group( "Markings" ), Range( 1, 32, slider: false ), WideMode]
public float LineWidth { get; set; } = 4.0f;
[Property, Group( "Markings" ), Title( "Dash Length" ), Range( 4, 256, slider: false ), WideMode]
public float DashLength { get; set; } = 32.0f;
[Property, Group( "Markings" ), Title( "Dash Gap" ), Range( 4, 256, slider: false ), WideMode]
public float DashGap { get; set; } = 32.0f;
[Property, Group( "Markings" ), Title( "Height" ), Range( 0, 4, slider: false ), WideMode]
public float Lift { get; set; } = 0.6f;
protected override void BuildMesh( MeshKit kit )
{
var path = SamplePath();
if ( path.Count < 2 ) return;
var width = MathF.Max( LineWidth, 1.0f );
var lift = MathF.Max( Lift, 0.0f );
var dash = Style == MarkingStyle.Solid ? 0.0f : MathF.Max( DashLength, 4.0f );
var gap = Style == MarkingStyle.Solid ? 0.0f : MathF.Max( DashGap, 4.0f );
var lines = Style == MarkingStyle.Double ? 2 : 1;
var offset = width * 1.4f;
var walked = 0.0f;
kit.Material = TrimMaterial;
for ( var i = 0; i + 1 < path.Count; i++ )
{
var a = path[i];
var b = path[i + 1];
var length = (b - a).Length;
if ( length < 0.01f ) continue;
var direction = (b - a) / length;
var normal = new Vector3( -direction.y, direction.x, 0.0f );
for ( var line = 0; line < lines; line++ )
{
var shift = lines == 1 ? 0.0f : (line == 0 ? -offset : offset) * 0.5f;
if ( dash <= 0.0f )
{
Ribbon( kit, a, b, normal, width, lift, shift );
continue;
}
var pattern = dash + gap;
var segmentStart = walked;
var segmentEnd = walked + length;
for ( var k = (int)Math.Floor( segmentStart / pattern ); k * pattern < segmentEnd; k++ )
{
var fromDistance = MathF.Max( k * pattern, segmentStart );
var toDistance = MathF.Min( k * pattern + dash, segmentEnd );
if ( toDistance <= fromDistance ) continue;
var from = a + direction * (fromDistance - segmentStart);
var to = a + direction * (toDistance - segmentStart);
Ribbon( kit, from, to, normal, width, lift, shift );
}
}
walked += length;
}
}
static void Ribbon( MeshKit kit, Vector3 a, Vector3 b, Vector3 normal, float width, float lift, float shift )
{
var n = normal * (width * 0.5f) + normal * shift;
kit.Face( a + n + Vector3.Up * lift, a - n + Vector3.Up * lift, b - n + Vector3.Up * lift, b + n + Vector3.Up * lift );
kit.Face( b + n + Vector3.Up * lift, b - n + Vector3.Up * lift, a - n + Vector3.Up * lift, a + n + Vector3.Up * lift );
}
}