This record struct defines parameters for blending surface materials along a road segment. It calculates weights and provides steps and knots for applying the material transition smoothly.
using System;
using System.Collections.Generic;
using Sandbox;
namespace RedSnail.RoadTool2;
/// <summary>
/// Where another surface runs on into a segment's own and gives way to it: from each end that meets one (at
/// <see cref="StartAt"/> or <see cref="EndAt"/>, distances along the segment's curve where the other surface starts
/// giving way: where the segment's own stops, or at a junction back across the road's mouth, see
/// <see cref="RoadMouthBlend"/>), all of the other surface there, none of it <see cref="Length"/> in.
/// <see cref="StartSource"/> and <see cref="EndSource"/> say how that surface is laid there, so it carries straight on
/// into the blend. The default is a segment that blends into nothing.
/// </summary>
public readonly record struct RoadSurfaceBlend(bool AtStart, float StartAt, bool AtEnd, float EndAt, float Length, RoadBlendSource StartSource = default, RoadBlendSource EndSource = default)
{
/// <summary>
/// The steps a blend is laid in, so its rows give the material a smooth ramp to mix by.
/// </summary>
private const int StepCount = 4;
public bool IsNone => (!AtStart && !AtEnd) || Length <= 0.01f;
/// <summary>
/// How much of the other surface there is at a distance along the segment: 1 where the segment's surface stops, 0
/// from a blend's length in, easing between.
/// </summary>
public float WeightAt(float _Distance)
{
return MathF.Max(StartWeightAt(_Distance), EndWeightAt(_Distance));
}
/// <summary>
/// How much of the surface at the segment's start there is at a distance along it.
/// </summary>
public float StartWeightAt(float _Distance)
{
return IsNone || !AtStart ? 0.0f : Fall(_Distance - StartAt);
}
/// <summary>
/// How much of the surface at the segment's end there is at a distance along it.
/// </summary>
public float EndWeightAt(float _Distance)
{
return IsNone || !AtEnd ? 0.0f : Fall(EndAt - _Distance);
}
/// <summary>
/// How much of the other surface there is a distance into a blend from where it starts.
/// </summary>
public float WeightInto(float _Into)
{
return IsNone ? 0.0f : Fall(_Into);
}
/// <summary>
/// How far into a blend from where it starts its rows are laid, from its start to its end.
/// </summary>
public IEnumerable<float> Steps()
{
if (IsNone)
yield break;
for (int step = 0; step <= StepCount; step++)
yield return Length * step / StepCount;
}
/// <summary>
/// The distances a surface needs rows at for its blends to be laid smoothly.
/// </summary>
public IEnumerable<float> Knots()
{
foreach (float into in Steps())
{
if (AtStart)
yield return StartAt + into;
if (AtEnd)
yield return EndAt - into;
}
}
public void AddToHash(ref StableHash _Hash)
{
_Hash.Add(AtStart ? 1 : 0);
_Hash.Add(AtStart ? StartAt : 0.0f);
_Hash.Add(AtEnd ? 1 : 0);
_Hash.Add(AtEnd ? EndAt : 0.0f);
_Hash.Add(Length);
if (AtStart)
StartSource.AddToHash(ref _Hash);
if (AtEnd)
EndSource.AddToHash(ref _Hash);
}
private float Fall(float _Into)
{
float t = Math.Clamp(_Into / Length, 0.0f, 1.0f);
return 1.0f - t * t * (3.0f - 2.0f * t);
}
}