Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added outputs for road curve length, and grade percentage #10

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Src/PrecisionEngineering/Data/Calculations/Guides.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public static void CalculateGuideLineDistance(NetToolProxy netTool, ICollection<
var dist = Vector3.Distance(guideLine.Origin.Flatten(), guideLine.Intersect.Flatten());
var pos = Vector3Extensions.Average(guideLine.Origin, guideLine.Intersect);

measurements.Add(new DistanceMeasurement(dist, pos, true, guideLine.Origin, guideLine.Intersect,
measurements.Add(new DistanceMeasurement(dist, pos, true, guideLine.Origin, guideLine.Intersect, float.NaN,
RosaryMala marked this conversation as resolved.
Show resolved Hide resolved
MeasurementFlags.HideOverlay | MeasurementFlags.Guide));
}
}
Expand Down
8 changes: 7 additions & 1 deletion Src/PrecisionEngineering/Data/DistanceMeasurement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ namespace PrecisionEngineering.Data
internal class DistanceMeasurement : Measurement
{
public DistanceMeasurement(float length, Vector3 position, bool isStraight, Vector3 startPosition,
Vector3 endPosition, MeasurementFlags flags)
Vector3 endPosition, float curvatureRadius, MeasurementFlags flags)
: base(position, flags)
{
Length = length;
IsStraight = isStraight;
StartPosition = startPosition;
EndPosition = endPosition;
CurvatureRadius = curvatureRadius;
}

/// <summary>
Expand All @@ -34,6 +35,11 @@ public DistanceMeasurement(float length, Vector3 position, bool isStraight, Vect
/// </summary>
public Vector3 EndPosition { get; }

/// <summary>
/// Radius of curvature at the point
/// </summary>
public float CurvatureRadius { get; }
RosaryMala marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Difference in height between the StartPosition and EndPosition.
/// </summary>
Expand Down
7 changes: 6 additions & 1 deletion Src/PrecisionEngineering/Data/Measurement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ internal enum MeasurementFlags
/// Indicate that this measurement is used for snapping and should be visable when
/// snapping is enabled.
/// </summary>
Snap = 1 << 6
Snap = 1 << 6,

/// <summary>
/// Indicate that this measurement should include a grade readout.
/// </summary>
Grade = 1 << 7,
}

internal abstract class Measurement
Expand Down
72 changes: 67 additions & 5 deletions Src/PrecisionEngineering/Data/PrecisionCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using PrecisionEngineering.Detour;
using PrecisionEngineering.Utilities;
using UnityEngine;
using ColossalFramework.Math;

namespace PrecisionEngineering.Data
{
Expand Down Expand Up @@ -45,8 +46,38 @@ public void Update(NetToolProxy netTool)
CalculateControlPointDistances(netTool, _measurements);
CalculateControlPointAngle(netTool, _measurements);

CalculateControlPointElevation(netTool, _measurements);
CalculateControlPointElevationAndCurveRadius(netTool, _measurements);
CalculateCompassAngle(netTool, _measurements);

CalculateArcLength(netTool, _measurements);
}

/// <summary>
/// Calculates the arc length of a curved road, if there are three control points.
/// </summary>
private void CalculateArcLength(NetToolProxy netTool, List<Measurement> measurements)
{
if (netTool.ControlPointsCount != 2)
{
return;
}

var p1 = netTool.ControlPoints[0];
var p2 = netTool.ControlPoints[1];
var p3 = netTool.ControlPoints[2];

var bezier = BezierUtil.CreateNetworkCurve(p1.m_position, p2.m_position, p3.m_position);
var center = bezier.Position(0.5f);

//Approximate the length by measuring between a bunch of points.
float length = 0;
const int count = 16;
for(int i = 0; i < count; i++)
{
length += (bezier.Position(i / (float)count).Flatten() - bezier.Position((i + 1) / (float)count).Flatten()).magnitude;
}

measurements.Add(new DistanceMeasurement(length, center, false, p1.m_position, p3.m_position, BezierUtil.FindCurvatureRadius(bezier, 0.5f), MeasurementFlags.Grade));
}

/// <summary>
Expand All @@ -67,7 +98,12 @@ private static void CalculateControlPointDistances(NetToolProxy netTool, ICollec
var dist = Vector3.Distance(p1.Flatten(), p2.Flatten());
var pos = Vector3Extensions.Average(p1, p2);

measurements.Add(new DistanceMeasurement(dist, pos, true, p1, p2, MeasurementFlags.HideOverlay));
var flags = MeasurementFlags.HideOverlay;

if (netTool.ControlPointsCount == 1) //it's straight.
flags |= MeasurementFlags.Grade;

measurements.Add(new DistanceMeasurement(dist, pos, true, p1, p2, float.NaN, flags));
}
}

Expand Down Expand Up @@ -184,16 +220,28 @@ private static void CalculateNearbySegments(NetToolProxy netTool, ICollection<Me
if (found && Mathf.Sqrt(minDist) > Settings.MinimumDistanceMeasure)
{
measurements.Add(new DistanceMeasurement(Vector3.Distance(p1, p), Vector3Extensions.Average(p1, p), true,
p1, p,
p1, p, float.NaN,
MeasurementFlags.Secondary));
}
}

/// <summary>
/// Calculates the elevation of each control point. The last control point will be marked as primary, others as secondary.
/// </summary>
private void CalculateControlPointElevation(NetToolProxy netTool, IList<Measurement> measurements)
private void CalculateControlPointElevationAndCurveRadius(NetToolProxy netTool, IList<Measurement> measurements)
{
Bezier3 bezier = new Bezier3();
bool isCurved = false;
if(netTool.ControlPointsCount == 2)
{
isCurved = true;
bezier = BezierUtil.CreateNetworkCurve(
netTool.ControlPoints[0].m_position,
netTool.ControlPoints[1].m_position,
netTool.ControlPoints[2].m_position
);
}

for (var i = 0; i <= netTool.ControlPointsCount; i++)
{
// Only display the last control point elevation as a primary measurement
Expand All @@ -206,8 +254,22 @@ private void CalculateControlPointElevation(NetToolProxy netTool, IList<Measurem
var botPos = controlPoint.m_position;
var topPos = controlPoint.m_position - new Vector3(0, controlPoint.m_elevation, 0);

float radius = float.NaN;

if(isCurved)
{
if(i == 0)
{
radius = BezierUtil.FindCurvatureRadius(bezier, 0);
}
else if(i == 2)
{
radius = BezierUtil.FindCurvatureRadius(bezier, 1);
}
}

measurements.Add(new DistanceMeasurement(e, Vector3Extensions.Average(botPos, topPos), true, botPos,
topPos,
topPos, radius,
MeasurementFlags.HideOverlay | MeasurementFlags.Height | flag));
}
}
Expand Down
38 changes: 26 additions & 12 deletions Src/PrecisionEngineering/Manager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ private void HandleMeasurement(RenderManager.CameraInfo cameraInfo, Measurement
{
var dm = m as DistanceMeasurement;

if (Mathf.Abs(dm.Length) < Settings.MinimumDistanceMeasure)
if (Mathf.Abs(dm.Length) < Settings.MinimumDistanceMeasure && (float.IsNaN(dm.CurvatureRadius) || !_secondaryDetailEnabled))
{
return;
}
Expand All @@ -215,26 +215,40 @@ private void HandleMeasurement(RenderManager.CameraInfo cameraInfo, Measurement

var label = _ui.GetMeasurementLabel();

string dist;
string dist = "";

if ((dm.Flags & MeasurementFlags.Height) != 0)
if (Mathf.Abs(dm.Length) > Settings.MinimumDistanceMeasure)
{
dist = string.Format("H: {0}", StringUtil.GetHeightMeasurementString(dm.Length));
}
else
{
dist = StringUtil.GetDistanceMeasurementString(dm.Length, _secondaryDetailEnabled);
if ((dm.Flags & MeasurementFlags.Height) != 0)
{
dist = string.Format("H: {0}", StringUtil.GetHeightMeasurementString(dm.Length));

if (_secondaryDetailEnabled)
}
else
{
var heightdiff = (int) dm.RelativeHeight.RoundToNearest(1);
dist = StringUtil.GetDistanceMeasurementString(dm.Length, _secondaryDetailEnabled);

if (Mathf.Abs(heightdiff) > 0)
if (_secondaryDetailEnabled)
{
dist += string.Format("\n(Elev: {0})", StringUtil.GetHeightMeasurementString(heightdiff));
var heightdiff = (int)dm.RelativeHeight.RoundToNearest(1);

if (Mathf.Abs(heightdiff) > 0)
{
dist += string.Format("\n(Elev: {0})", StringUtil.GetHeightMeasurementString(heightdiff));
if ((dm.Flags & MeasurementFlags.Grade) != 0)
dist += string.Format("\n(Grade: {0})", (heightdiff / dm.Length).ToString("P02"));
}
}
}
}
if (_secondaryDetailEnabled && !float.IsNaN(dm.CurvatureRadius))
{
//We only need to add a newline if there's already text in there.
if (dist.Length > 0)
dist += "\n";
dist += string.Format("(Radius: {0})", StringUtil.GetDistanceMeasurementString(dm.CurvatureRadius, _secondaryDetailEnabled));
}


label.SetValue(dist);
label.SetWorldPosition(cameraInfo, DistanceRenderer.GetLabelWorldPosition(dm));
Expand Down
155 changes: 151 additions & 4 deletions Src/PrecisionEngineering/Utilities/BezierUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,158 @@ private static Bezier3 CreateSmallArc(float r, float a1, float a2)

return new Bezier3
{
a = new Vector3(r*Mathf.Cos(a1), 0, r*Mathf.Sin(a1)),
b = new Vector3(x2*cos_ar - y2*sin_ar, 0, x2*sin_ar + y2*cos_ar),
c = new Vector3(x3*cos_ar - y3*sin_ar, 0, x3*sin_ar + y3*cos_ar),
d = new Vector3(r*Mathf.Cos(a2), 0, r*Mathf.Sin(a2))
a = new Vector3(r * Mathf.Cos(a1), 0, r * Mathf.Sin(a1)),
b = new Vector3(x2 * cos_ar - y2 * sin_ar, 0, x2 * sin_ar + y2 * cos_ar),
c = new Vector3(x3 * cos_ar - y3 * sin_ar, 0, x3 * sin_ar + y3 * cos_ar),
d = new Vector3(r * Mathf.Cos(a2), 0, r * Mathf.Sin(a2))
};
}

public static Bezier3 CreateNetworkCurve(Vector3 p1, Vector3 p2, Vector3 p3)
{
//Beziers take 4 points, not 3, so we make a crude approximation of the two midle controll points here
var c1 = (p2 - p1) * 0.552f + p1;
var c2 = (p2 - p3) * 0.552f + p3;

return new Bezier3(p1, c1, c2, p3);
}

public static float FindCurvatureRadius(Bezier3 bezier, float t)
{
const float delta = 0.01f;
float t1, t2, t3;
if(t < delta*2)
{
t1 = t;
t2 = t + delta;
t3 = t + delta * 2;
}
else if (t > 1 - delta * 2)
{
t1 = t;
t2 = t - delta;
t3 = t - delta * 2;
}
else
{
t1 = t - delta;
t2 = t;
t3 = t + delta;
}
var p1 = bezier.Position(t1).Flatten();
var p2 = bezier.Position(t2).Flatten();
var p3 = bezier.Position(t3).Flatten();

FindCircle(p1, p2, p3, out var center, out var radius);

//If the curve radius is big enough, we may as well ignore it.
if (radius > 10000)
radius = float.NaN;

return radius;
}
// Find a circle through the three points.
private static void FindCircle(Vector3 a, Vector3 b, Vector3 c,
out Vector3 center, out float radius)
{
// Get the perpendicular bisector of (x1, y1) and (x2, y2).
float x1 = (b.x + a.x) / 2;
float y1 = (b.z + a.z) / 2;
float dy1 = b.x - a.x;
float dx1 = -(b.z - a.z);

// Get the perpendicular bisector of (x2, y2) and (x3, y3).
float x2 = (c.x + b.x) / 2;
float y2 = (c.z + b.z) / 2;
float dy2 = c.x - b.x;
float dx2 = -(c.z - b.z);

// See where the lines intersect.
bool lines_intersect, segments_intersect;
Vector3 intersection, close1, close2;
FindIntersection(
new Vector3(x1, 0, y1), new Vector3(x1 + dx1, 0, y1 + dy1),
new Vector3(x2, 0, y2), new Vector3(x2 + dx2, 0, y2 + dy2),
out lines_intersect, out segments_intersect,
out intersection, out close1, out close2);
if (!lines_intersect)
{
center = new Vector3(0, 0, 0);
radius = float.NaN;
}
else
{
center = intersection;
float dx = center.x - a.x;
float dy = center.z - a.z;
radius = (float)Mathf.Sqrt(dx * dx + dy * dy);
}
}
// Find the point of intersection between
// the lines p1 --> p2 and p3 --> p4.
private static void FindIntersection(
Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4,
out bool lines_intersect, out bool segments_intersect,
out Vector3 intersection,
out Vector3 close_p1, out Vector3 close_p2)
{
// Get the segments' parameters.
float dx12 = p2.x - p1.x;
float dy12 = p2.z - p1.z;
float dx34 = p4.x - p3.x;
float dy34 = p4.z - p3.z;

// Solve for t1 and t2
float denominator = (dy12 * dx34 - dx12 * dy34);

float t1 =
((p1.x - p3.x) * dy34 + (p3.z - p1.z) * dx34)
/ denominator;
if (float.IsInfinity(t1))
{
// The lines are parallel (or close enough to it).
lines_intersect = false;
segments_intersect = false;
intersection = new Vector3(float.NaN, float.NaN, float.NaN);
close_p1 = new Vector3(float.NaN, float.NaN, float.NaN);
close_p2 = new Vector3(float.NaN, float.NaN, float.NaN);
return;
}
lines_intersect = true;

float t2 =
((p3.x - p1.x) * dy12 + (p1.z - p3.z) * dx12)
/ -denominator;

// Find the point of intersection.
intersection = new Vector3(p1.x + dx12 * t1, 0, p1.z + dy12 * t1);

// The segments intersect if t1 and t2 are between 0 and 1.
segments_intersect =
((t1 >= 0) && (t1 <= 1) &&
(t2 >= 0) && (t2 <= 1));

// Find the closest points on the segments.
if (t1 < 0)
{
t1 = 0;
}
else if (t1 > 1)
{
t1 = 1;
}

if (t2 < 0)
{
t2 = 0;
}
else if (t2 > 1)
{
t2 = 1;
}

close_p1 = new Vector3(p1.x + dx12 * t1, 0, p1.z + dy12 * t1);
close_p2 = new Vector3(p3.x + dx34 * t2, 0, p3.z + dy34 * t2);
}
}
}