using System; using System.Diagnostics; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Routing.Visibility; namespace Microsoft.Msagl.Routing.Rectilinear { internal class PointComparer { // Due to the vagaries of rounding, we may encounter a result that is not quite 0 // when subtracting two numbers that are close. // ReSharper disable InconsistentNaming private static readonly double differenceEpsilon = ApproximateComparer.DistanceEpsilon / 2; // ReSharper restore InconsistentNaming static internal double DifferenceEpsilon { get { return differenceEpsilon; } } /// /// Determines whether the specified Points, which are assumed to have been Round()ed, /// are close enough to be considered equal. /// /// The first object of type Point to compare. /// The second object of type Point to compare. /// True if the inputs are close enough to be considered equal, else false public static bool Equal(Point a, Point b) { return Equal(a.X, b.X) && Equal(a.Y, b.Y); } /// /// Determines whether the specified double values, which are assumed to have been Round()ed, /// are close enough to be considered equal. /// /// The first double value to compare. /// The second double value to compare. /// True if the inputs are close enough to be considered equal, else false public static bool Equal(double x, double y) { return (0 == Compare(x, y)); } /// /// The usual Compare operation, with inputs that are assumed to have been Round()ed. /// /// /// /// 0 if the inputs are close enough to be considered equal, else -1 if lhs is /// less than rhs, else 1. public static int Compare(double lhs, double rhs) { // If the inputs are not rounded, then two numbers that are close together at the // middle of the rounding range may Compare as 0 but Round to different values // (e.g., with rounding to 6 digits, xxx.yyyyyy49 and xxx.yyyyyy51 will exhibit this). Assert_Rounded(lhs); Assert_Rounded(rhs); int cmp = 0; if (lhs + DifferenceEpsilon < rhs) { cmp = -1; } else if (rhs + DifferenceEpsilon < lhs) { cmp = 1; } // Just to be sure we're in sync with CompassVector Debug.Assert((cmp < 0) == (Directions.East == CompassVector.VectorDirection(new Point(lhs, 0), new Point(rhs, 0)))); Debug.Assert((0 == cmp) == (Directions. None == CompassVector.VectorDirection(new Point(lhs, 0), new Point(rhs, 0)))); return cmp; } /// /// The usual Compare operation, with inputs that are assumed to have been Round()ed. /// /// /// /// 0 if the inputs are close enough to be considered equal, else -1 if lhs is /// less than rhs, else 1. public static int Compare(Point lhs, Point rhs) { int cmp = Compare(lhs.X, rhs.X); if (0 == cmp) { cmp = Compare(lhs.Y, rhs.Y); } return cmp; } /// /// return true if less or equal holds for two values that are assumed to have been Round()ed /// /// /// /// public static bool LessOrEqual(double a, double b){ int comp = Compare(a, b); return comp < 0 || comp == 0; } /// /// return true if less holds for two values that are assumed to have been Round()ed /// /// /// /// public static bool Less(double a, double b) { return Compare(a, b) < 0; } [Conditional("DEBUG")] // ReSharper disable InconsistentNaming static void Assert_Rounded(double d) { // Be sure there is enough precision to round that far; anything larger than this is // unlikely to be a graph coordinate (it's probably a line intersection way out of range). if (Math.Log10(Math.Abs(d)) < (14 - ApproximateComparer.DistanceEpsilonPrecision)) { Debug.Assert(Math.Abs(ApproximateComparer.Round(d) - d) < DifferenceEpsilon, "unRounded value passed"); } } [Conditional("DEBUG")] static void Assert_Rounded(Point p) { Assert_Rounded(p.X); Assert_Rounded(p.Y); } // ReSharper restore InconsistentNaming #region Direction_Utilities // These call through to CompassVector methods, assuming operands that use PointComparer rounding. internal static Directions GetDirections(Point a, Point b) { Assert_Rounded(a); Assert_Rounded(b); return CompassVector.DirectionsFromPointToPoint(a, b); } internal static Directions GetPureDirection(Point a, Point b) { Assert_Rounded(a); Assert_Rounded(b); Directions dir = GetDirections(a, b); Debug.Assert(CompassVector.IsPureDirection(dir), "Impure direction found"); return dir; } internal static bool IsPureDirection(Point a, Point b) { Assert_Rounded(a); Assert_Rounded(b); return CompassVector.IsPureDirection(GetDirections(a, b)); } internal static bool IsPureDirection(Directions dir) { return CompassVector.IsPureDirection(dir); } internal static bool IsPureLower(Point a, Point b) { Assert_Rounded(a); Assert_Rounded(b); // Is a lower than b along the orthogonal line segment? That means moving // from a to b is in the increasing direction. Directions dir = GetDirections(a, b); return (Directions.East == dir) || (Directions.North == dir); } static internal Directions GetPureDirection(VisibilityVertex first, VisibilityVertex second) { Assert_Rounded(first.Point); Assert_Rounded(second.Point); return GetPureDirection(first.Point, second.Point); } #endregion // Direction_Utilities } }