From 9ed8d37577b7982222f8d38d15226b2277fc1912 Mon Sep 17 00:00:00 2001 From: Rose Date: Mon, 26 Jul 2021 01:57:56 -0700 Subject: [PATCH 1/4] Added outputs for road curve length, and grade percentage --- .../Data/PrecisionCalculator.cs | 36 +++++++++++++++++++ Src/PrecisionEngineering/Manager.cs | 1 + 2 files changed, 37 insertions(+) diff --git a/Src/PrecisionEngineering/Data/PrecisionCalculator.cs b/Src/PrecisionEngineering/Data/PrecisionCalculator.cs index c400631..e274f29 100644 --- a/Src/PrecisionEngineering/Data/PrecisionCalculator.cs +++ b/Src/PrecisionEngineering/Data/PrecisionCalculator.cs @@ -3,6 +3,7 @@ using PrecisionEngineering.Detour; using PrecisionEngineering.Utilities; using UnityEngine; +using ColossalFramework.Math; namespace PrecisionEngineering.Data { @@ -47,6 +48,41 @@ public void Update(NetToolProxy netTool) CalculateControlPointElevation(netTool, _measurements); CalculateCompassAngle(netTool, _measurements); + + CalculateArcLength(netTool, _measurements); + } + + /// + /// Calculates the arc length of a curved road, if there are three control points. + /// + private void CalculateArcLength(NetToolProxy netTool, List measurements) + { + if (netTool.ControlPointsCount != 2) + { + return; + } + + var p1 = netTool.ControlPoints[0]; + var p2 = netTool.ControlPoints[1]; + var p3 = netTool.ControlPoints[2]; + + //Beziers take 4 points, not 3, so we make a crude approximation of the two midle controll points here + var c1 = (p2.m_position - p1.m_position) * 0.552f + p1.m_position; + var c2 = (p2.m_position - p3.m_position) * 0.552f + p3.m_position; + + var bezier = new Bezier3(p1.m_position, c1, c2, 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, MeasurementFlags.None)); } /// diff --git a/Src/PrecisionEngineering/Manager.cs b/Src/PrecisionEngineering/Manager.cs index e186a27..3a0f2fc 100644 --- a/Src/PrecisionEngineering/Manager.cs +++ b/Src/PrecisionEngineering/Manager.cs @@ -232,6 +232,7 @@ private void HandleMeasurement(RenderManager.CameraInfo cameraInfo, Measurement if (Mathf.Abs(heightdiff) > 0) { dist += string.Format("\n(Elev: {0})", StringUtil.GetHeightMeasurementString(heightdiff)); + dist += string.Format("\n(Grade: {0}", (heightdiff / dm.Length).ToString("P02")); } } } From 5aea71668bbe864b177016a7495b4aa30cf74a4f Mon Sep 17 00:00:00 2001 From: Rose Date: Mon, 26 Jul 2021 23:35:38 -0700 Subject: [PATCH 2/4] Added a radius of curvature readout. Still need to make it show up when there's none of the other readouts showing, if it's available. --- .../Data/Calculations/Guides.cs | 2 +- .../Data/DistanceMeasurement.cs | 8 +- Src/PrecisionEngineering/Data/Measurement.cs | 7 +- .../Data/PrecisionCalculator.cs | 50 ++++-- Src/PrecisionEngineering/Manager.cs | 9 + .../Utilities/BezierUtil.cs | 155 +++++++++++++++++- 6 files changed, 212 insertions(+), 19 deletions(-) diff --git a/Src/PrecisionEngineering/Data/Calculations/Guides.cs b/Src/PrecisionEngineering/Data/Calculations/Guides.cs index cf4c171..8fd99c6 100644 --- a/Src/PrecisionEngineering/Data/Calculations/Guides.cs +++ b/Src/PrecisionEngineering/Data/Calculations/Guides.cs @@ -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, MeasurementFlags.HideOverlay | MeasurementFlags.Guide)); } } diff --git a/Src/PrecisionEngineering/Data/DistanceMeasurement.cs b/Src/PrecisionEngineering/Data/DistanceMeasurement.cs index 7add2b5..6ad631e 100644 --- a/Src/PrecisionEngineering/Data/DistanceMeasurement.cs +++ b/Src/PrecisionEngineering/Data/DistanceMeasurement.cs @@ -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; } /// @@ -34,6 +35,11 @@ public DistanceMeasurement(float length, Vector3 position, bool isStraight, Vect /// public Vector3 EndPosition { get; } + /// + /// Radius of curvature at the point + /// + public float CurvatureRadius { get; } + /// /// Difference in height between the StartPosition and EndPosition. /// diff --git a/Src/PrecisionEngineering/Data/Measurement.cs b/Src/PrecisionEngineering/Data/Measurement.cs index d9d79fa..0a92765 100644 --- a/Src/PrecisionEngineering/Data/Measurement.cs +++ b/Src/PrecisionEngineering/Data/Measurement.cs @@ -35,7 +35,12 @@ internal enum MeasurementFlags /// Indicate that this measurement is used for snapping and should be visable when /// snapping is enabled. /// - Snap = 1 << 6 + Snap = 1 << 6, + + /// + /// Indicate that this measurement should include a grade readout. + /// + Grade = 1 << 7, } internal abstract class Measurement diff --git a/Src/PrecisionEngineering/Data/PrecisionCalculator.cs b/Src/PrecisionEngineering/Data/PrecisionCalculator.cs index e274f29..4e81b6f 100644 --- a/Src/PrecisionEngineering/Data/PrecisionCalculator.cs +++ b/Src/PrecisionEngineering/Data/PrecisionCalculator.cs @@ -46,7 +46,7 @@ public void Update(NetToolProxy netTool) CalculateControlPointDistances(netTool, _measurements); CalculateControlPointAngle(netTool, _measurements); - CalculateControlPointElevation(netTool, _measurements); + CalculateControlPointElevationAndCurveRadius(netTool, _measurements); CalculateCompassAngle(netTool, _measurements); CalculateArcLength(netTool, _measurements); @@ -66,12 +66,7 @@ private void CalculateArcLength(NetToolProxy netTool, List measurem var p2 = netTool.ControlPoints[1]; var p3 = netTool.ControlPoints[2]; - //Beziers take 4 points, not 3, so we make a crude approximation of the two midle controll points here - var c1 = (p2.m_position - p1.m_position) * 0.552f + p1.m_position; - var c2 = (p2.m_position - p3.m_position) * 0.552f + p3.m_position; - - var bezier = new Bezier3(p1.m_position, c1, c2, p3.m_position); - + 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. @@ -82,7 +77,7 @@ private void CalculateArcLength(NetToolProxy netTool, List measurem 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, MeasurementFlags.None)); + measurements.Add(new DistanceMeasurement(length, center, false, p1.m_position, p3.m_position, BezierUtil.FindCurvatureRadius(bezier, 0.5f), MeasurementFlags.Grade)); } /// @@ -103,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)); } } @@ -220,7 +220,7 @@ private static void CalculateNearbySegments(NetToolProxy netTool, ICollection Settings.MinimumDistanceMeasure) { measurements.Add(new DistanceMeasurement(Vector3.Distance(p1, p), Vector3Extensions.Average(p1, p), true, - p1, p, + p1, p, float.NaN, MeasurementFlags.Secondary)); } } @@ -228,8 +228,20 @@ private static void CalculateNearbySegments(NetToolProxy netTool, ICollection /// Calculates the elevation of each control point. The last control point will be marked as primary, others as secondary. /// - private void CalculateControlPointElevation(NetToolProxy netTool, IList measurements) + private void CalculateControlPointElevationAndCurveRadius(NetToolProxy netTool, IList 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 @@ -242,8 +254,22 @@ private void CalculateControlPointElevation(NetToolProxy netTool, IList 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); + } } } From f71082de762a88a7afec5b5657196cfcf5de3eec Mon Sep 17 00:00:00 2001 From: Rose Date: Tue, 27 Jul 2021 20:08:26 -0700 Subject: [PATCH 3/4] - Show the radius on all points that should have it. - Only show the grade on the actual road line, since it's useless in other places. --- Src/PrecisionEngineering/Manager.cs | 46 ++++++++++++++++------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/Src/PrecisionEngineering/Manager.cs b/Src/PrecisionEngineering/Manager.cs index 114002f..6b33da5 100644 --- a/Src/PrecisionEngineering/Manager.cs +++ b/Src/PrecisionEngineering/Manager.cs @@ -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; } @@ -215,36 +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)); - if (_secondaryDetailEnabled && !float.IsNaN(dm.CurvatureRadius)) + if ((dm.Flags & MeasurementFlags.Height) != 0) { - dist += string.Format("\n(Radius: {0}", StringUtil.GetDistanceMeasurementString(dm.CurvatureRadius, _secondaryDetailEnabled)); - } + dist = string.Format("H: {0}", StringUtil.GetHeightMeasurementString(dm.Length)); - } - else - { - dist = StringUtil.GetDistanceMeasurementString(dm.Length, _secondaryDetailEnabled); - - 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)); - dist += string.Format("\n(Grade: {0}", (heightdiff / dm.Length).ToString("P02")); - } - if(!float.IsNaN(dm.CurvatureRadius)) - { - dist += string.Format("\n(Radius: {0}", StringUtil.GetDistanceMeasurementString(dm.CurvatureRadius, _secondaryDetailEnabled)); + 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)); From d5fd756128cc167ab6a45eec96d27bac5f30b761 Mon Sep 17 00:00:00 2001 From: Rose Date: Mon, 8 Nov 2021 11:48:21 -0800 Subject: [PATCH 4/4] Use nullable float instead of float.NaN --- Src/PrecisionEngineering/Data/Calculations/Guides.cs | 2 +- Src/PrecisionEngineering/Data/DistanceMeasurement.cs | 4 ++-- Src/PrecisionEngineering/Data/PrecisionCalculator.cs | 4 ++-- Src/PrecisionEngineering/Manager.cs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Src/PrecisionEngineering/Data/Calculations/Guides.cs b/Src/PrecisionEngineering/Data/Calculations/Guides.cs index 8fd99c6..367e606 100644 --- a/Src/PrecisionEngineering/Data/Calculations/Guides.cs +++ b/Src/PrecisionEngineering/Data/Calculations/Guides.cs @@ -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, float.NaN, + measurements.Add(new DistanceMeasurement(dist, pos, true, guideLine.Origin, guideLine.Intersect, null, MeasurementFlags.HideOverlay | MeasurementFlags.Guide)); } } diff --git a/Src/PrecisionEngineering/Data/DistanceMeasurement.cs b/Src/PrecisionEngineering/Data/DistanceMeasurement.cs index 6ad631e..8e39c7a 100644 --- a/Src/PrecisionEngineering/Data/DistanceMeasurement.cs +++ b/Src/PrecisionEngineering/Data/DistanceMeasurement.cs @@ -5,7 +5,7 @@ namespace PrecisionEngineering.Data internal class DistanceMeasurement : Measurement { public DistanceMeasurement(float length, Vector3 position, bool isStraight, Vector3 startPosition, - Vector3 endPosition, float curvatureRadius, MeasurementFlags flags) + Vector3 endPosition, float? curvatureRadius, MeasurementFlags flags) : base(position, flags) { Length = length; @@ -38,7 +38,7 @@ public DistanceMeasurement(float length, Vector3 position, bool isStraight, Vect /// /// Radius of curvature at the point /// - public float CurvatureRadius { get; } + public float? CurvatureRadius { get; } /// /// Difference in height between the StartPosition and EndPosition. diff --git a/Src/PrecisionEngineering/Data/PrecisionCalculator.cs b/Src/PrecisionEngineering/Data/PrecisionCalculator.cs index 4e81b6f..9a25ef0 100644 --- a/Src/PrecisionEngineering/Data/PrecisionCalculator.cs +++ b/Src/PrecisionEngineering/Data/PrecisionCalculator.cs @@ -103,7 +103,7 @@ private static void CalculateControlPointDistances(NetToolProxy netTool, ICollec if (netTool.ControlPointsCount == 1) //it's straight. flags |= MeasurementFlags.Grade; - measurements.Add(new DistanceMeasurement(dist, pos, true, p1, p2, float.NaN, flags)); + measurements.Add(new DistanceMeasurement(dist, pos, true, p1, p2, null, flags)); } } @@ -220,7 +220,7 @@ private static void CalculateNearbySegments(NetToolProxy netTool, ICollection Settings.MinimumDistanceMeasure) { measurements.Add(new DistanceMeasurement(Vector3.Distance(p1, p), Vector3Extensions.Average(p1, p), true, - p1, p, float.NaN, + p1, p, null, MeasurementFlags.Secondary)); } } diff --git a/Src/PrecisionEngineering/Manager.cs b/Src/PrecisionEngineering/Manager.cs index 6b33da5..e0f3cf8 100644 --- a/Src/PrecisionEngineering/Manager.cs +++ b/Src/PrecisionEngineering/Manager.cs @@ -206,7 +206,7 @@ private void HandleMeasurement(RenderManager.CameraInfo cameraInfo, Measurement { var dm = m as DistanceMeasurement; - if (Mathf.Abs(dm.Length) < Settings.MinimumDistanceMeasure && (float.IsNaN(dm.CurvatureRadius) || !_secondaryDetailEnabled)) + if (Mathf.Abs(dm.Length) < Settings.MinimumDistanceMeasure && (!dm.CurvatureRadius.HasValue || !_secondaryDetailEnabled)) { return; } @@ -241,12 +241,12 @@ private void HandleMeasurement(RenderManager.CameraInfo cameraInfo, Measurement } } } - if (_secondaryDetailEnabled && !float.IsNaN(dm.CurvatureRadius)) + if (_secondaryDetailEnabled && dm.CurvatureRadius.HasValue) { //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)); + dist += string.Format("(Radius: {0})", StringUtil.GetDistanceMeasurementString(dm.CurvatureRadius.Value, _secondaryDetailEnabled)); }