Esempio n. 1
0
            /// <summary>
            /// Adds an intersection <see cref="EventPoint"/> to the <see cref="Schedule"/> if the
            /// two specified <see cref="SweepLine"/> nodes indicate a line crossing.</summary>
            /// <param name="a">
            /// The first <see cref="SweepLine"/> node to examine.</param>
            /// <param name="b">
            /// The second <see cref="SweepLine"/> node to examine.</param>
            /// <param name="e">
            /// The current <see cref="EventPoint"/> which receives a detected crossing that occurs
            /// exactly at the <see cref="Cursor"/>.</param>
            /// <remarks>
            /// If the <see cref="Schedule"/> already contains an <see cref="EventPoint"/> for the
            /// computed intersection, <b>AddCrossing</b> adds the indicated lines to the existing
            /// <see cref="EventPoint"/> if they are not already present.</remarks>

            private void AddCrossing(BraidedTreeNode <Int32, Int32> a,
                                     BraidedTreeNode <Int32, Int32> b, EventPoint e)
            {
                int aIndex = a.Key, bIndex = b.Key;
                LineIntersection c = Lines[aIndex].Intersect(Lines[bIndex]);

                // ignore crossings that involve only start or end points,
                // as those line events have been scheduled during initialization
                if ((c.First == LineLocation.Between && LineIntersection.Contains(c.Second)) ||
                    (LineIntersection.Contains(c.First) && c.Second == LineLocation.Between))
                {
                    // quit if crossing occurs before cursor
                    PointD p      = c.Shared.Value;
                    int    result = PointDComparerY.CompareExact(Cursor, p);
                    if (result > 0)
                    {
                        return;
                    }

                    // update schedule if crossing occurs after cursor
                    if (result < 0)
                    {
                        BraidedTreeNode <PointD, EventPoint> node;
                        Schedule.TryAddNode(p, new EventPoint(p), out node);
                        e = node._value;
                    }

                    // add crossing to current or scheduled event point
                    e.TryAddLines(aIndex, c.First, bIndex, c.Second);
                }
            }
Esempio n. 2
0
        /// <summary>
        /// Finds all intersections between the specified line segments, using a brute force
        /// algorithm and given the specified epsilon for coordinate comparisons.</summary>
        /// <param name="lines">
        /// An <see cref="Array"/> containing the <see cref="LineD"/> instances to intersect.
        /// </param>
        /// <param name="epsilon">
        /// The maximum absolute difference at which two coordinates should be considered equal.
        /// </param>
        /// <returns>
        /// A lexicographically sorted <see cref="Array"/> containing a <see cref="MultiLinePoint"/>
        /// for every point of intersection between the <paramref name="lines"/>.</returns>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="lines"/> is a null reference.</exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// <paramref name="epsilon"/> is equal to or less than zero.</exception>
        /// <remarks>
        /// <b>FindSimple</b> is identical with the basic <see cref="FindSimple(LineD[])"/> overload
        /// but uses the specified <paramref name="epsilon"/> to determine intersections between the
        /// specified <paramref name="lines"/> and to combine nearby intersections.</remarks>

        public static MultiLinePoint[] FindSimple(LineD[] lines, double epsilon)
        {
            if (lines == null)
            {
                ThrowHelper.ThrowArgumentNullException("lines");
            }
            if (epsilon <= 0.0)
            {
                ThrowHelper.ThrowArgumentOutOfRangeException(
                    "epsilon", epsilon, Strings.ArgumentNotPositive);
            }

            var crossings = new BraidedTree <PointD, EventPoint>(
                (a, b) => PointDComparerY.CompareEpsilon(a, b, epsilon));

            for (int i = 0; i < lines.Length - 1; i++)
            {
                for (int j = i + 1; j < lines.Length; j++)
                {
                    var crossing = lines[i].Intersect(lines[j], epsilon);

                    if (crossing.Exists)
                    {
                        PointD p = crossing.Shared.Value;
                        BraidedTreeNode <PointD, EventPoint> node;
                        crossings.TryAddNode(p, new EventPoint(p), out node);
                        node._value.TryAddLines(i, crossing.First, j, crossing.Second);
                    }
                }
            }

            return(EventPoint.Convert(crossings.Values));
        }
Esempio n. 3
0
            /// <summary>
            /// Normalizes all <see cref="Locations"/> to match the corresponding <see
            /// cref="Lines"/>.</summary>
            /// <param name="lines">
            /// An <see cref="Array"/> containing all input <see cref="LineD"/> segments.</param>
            /// <remarks><para>
            /// <b>Normalize</b> inverts any <see cref="LineLocation.Start"/> or <see
            /// cref="LineLocation.End"/> values in the <see cref="Locations"/> collection whose
            /// corresponding <see cref="Lines"/> element indicates a <see cref="LineD"/> whose <see
            /// cref="LineD.Start"/> and <see cref="LineD.End"/> points have the opposite orientation.
            /// </para><para>
            /// Therefore, the <see cref="Locations"/> collection no longer reflects the sweep line
            /// direction, but rather the point at which the corresponding <see cref="LineD"/>
            /// touches the <see cref="Shared"/> coordinates. Call this method to prepare for output
            /// generation, after the <see cref="EventPoint"/> has been removed from the schedule of
            /// the sweep line algorithm.</para></remarks>

            internal void Normalize(LineD[] lines)
            {
                for (int i = 0; i < Locations.Count; i++)
                {
                    LineLocation       location = Locations[i];
                    const LineLocation startEnd = LineLocation.Start | LineLocation.End;

                    // check for start & end point events
                    if ((location & startEnd) != 0)
                    {
                        LineD line = lines[Lines[i]];

                        // report orientation of inverted line
                        if (PointDComparerY.CompareExact(line.Start, line.End) > 0)
                        {
                            location    ^= startEnd;
                            Locations[i] = location;
                        }
                    }
                }
            }
Esempio n. 4
0
            /// <summary>
            /// Builds the <see cref="Schedule"/> and precomputes all <see cref="Slopes"/>.
            /// </summary>
            /// <param name="lines">
            /// An <see cref="Array"/> containing the <see cref="LineD"/> segments to intersect.
            /// </param>
            /// <exception cref="ArgumentException">
            /// <paramref name="lines"/> contains a <see cref="LineD"/> whose <see
            /// cref="LineD.Start"/> and <see cref="LineD.End"/> coordinates are equal.</exception>
            /// <exception cref="ArgumentNullException">
            /// <paramref name="lines"/> is a null reference.</exception>

            private void BuildSchedule(LineD[] lines)
            {
                if (lines == null)
                {
                    ThrowHelper.ThrowArgumentNullException("lines");
                }

                Lines     = lines;
                Positions = new double[lines.Length];
                Slopes    = new double[lines.Length];

                for (int i = 0; i < lines.Length; i++)
                {
                    LineD line = lines[i];
                    Slopes[i] = line.InverseSlope;

                    int direction = PointDComparerY.CompareExact(line.Start, line.End);
                    if (direction == 0)
                    {
                        ThrowHelper.ThrowArgumentExceptionWithFormat(
                            "lines", Strings.ArgumentContainsEmpty, "LineD");
                    }

                    // start & end point events use lexicographic ordering
                    if (direction > 0)
                    {
                        line = line.Reverse();
                    }
                    BraidedTreeNode <PointD, EventPoint> node;

                    // add start point event for current line
                    Schedule.TryAddNode(line.Start, new EventPoint(line.Start), out node);
                    node._value.AddLine(i, LineLocation.Start);

                    // add end point event for current line
                    Schedule.TryAddNode(line.End, new EventPoint(line.End), out node);
                    node._value.AddLine(i, LineLocation.End);
                }
            }
Esempio n. 5
0
        /// <summary>
        /// Finds the convex hull for the specified set of <see cref="PointD"/> coordinates.
        /// </summary>
        /// <param name="points">
        /// An <see cref="Array"/> containing the <see cref="PointD"/> coordinates whose convex hull
        /// to find.</param>
        /// <returns>
        /// An <see cref="Array"/> containing the subset of the specified <paramref name="points"/>
        /// that represent the vertices of their convex hull.</returns>
        /// <exception cref="ArgumentNullOrEmptyException">
        /// <paramref name="points"/> is a null reference or an empty array.</exception>
        /// <remarks><para>
        /// If the specified <paramref name="points"/> array contains only one or two elements,
        /// <b>ConvexHull</b> returns a new array containing the same elements. Points that are
        /// coincident or collinear with other hull vertices are always removed from the returned
        /// array, however. A <paramref name="points"/> array containing the same <see
        /// cref="PointD"/> twice will return an array containing that <see cref="PointD"/> once.
        /// </para><para>
        /// <b>ConvexHull</b> performs a Graham scan with an asymptotic runtime of O(n log n). This
        /// C# implementation was adapted from the <c>Graham</c> algorithm by Joseph O’Rourke,
        /// <em>Computational Geometry in C</em> (2nd ed.), Cambridge University Press 1998, p.72ff.
        /// </para></remarks>

        public static PointD[] ConvexHull(params PointD[] points)
        {
            if (points == null || points.Length == 0)
            {
                ThrowHelper.ThrowArgumentNullOrEmptyException("points");
            }

            // handle trivial edge cases
            switch (points.Length)
            {
            case 1: return(new PointD[] { points[0] });

            case 2:
                if (points[0] == points[1])
                {
                    goto case 1;
                }
                else
                {
                    return new PointD[] { points[0], points[1] }
                };
            }

            /*
             * Set index n to lowest vertex. Unlike O’Rourke, we immediately mark duplicates
             * of the current lowest vertex for deletion. This eliminates some corner cases
             * of multiple duplicates that are missed by ConvexHullVertexComparer.Compare.
             */

            var    p   = new ConvexHullVertex[points.Length];
            PointD pnv = points[0];

            p[0] = new ConvexHullVertex(pnv, 0);

            int i, n = 0;

            for (i = 1; i < p.Length; i++)
            {
                PointD piv = points[i];
                p[i] = new ConvexHullVertex(piv, i);

                int result = PointDComparerY.CompareExact(piv, pnv);
                if (result < 0)
                {
                    n = i; pnv = piv;
                }
                else if (result == 0)
                {
                    p[i].Delete = true;
                }
            }

            // move lowest vertex to index 0
            if (n > 0)
            {
                var swap = p[0]; p[0] = p[n]; p[n] = swap;
            }

            // sort and mark collinear/coincident vertices for deletion
            var comparer = new ConvexHullVertexComparer(p[0]);

            Array.Sort(p, 1, p.Length - 1, comparer);

            // delete marked vertices (n is remaining count)
            for (i = 0, n = 0; i < p.Length; i++)
            {
                if (!p[i].Delete)
                {
                    p[n++] = p[i];
                }
            }

            // quit if only one unique vertex remains
            if (n == 1)
            {
                return new[] { p[0].Vertex }
            }
            ;

            // begin stack of convex hull vertices
            var top       = p[1]; top.Next = p[0];
            int hullCount = 2;

            // first two vertices are permanent, now examine others
            for (i = 2; i < n;)
            {
                ConvexHullVertex pi = p[i];

                if (top.Next.Vertex.CrossProductLength(top.Vertex, pi.Vertex) > 0)
                {
                    // push p[i] on stack
                    pi.Next = top;
                    top     = pi; ++i;
                    ++hullCount;
                }
                else
                {
                    // pop top from stack
                    top = top.Next;
                    --hullCount;
                }
            }

            // convert vertex stack to point array
            PointD[] hull = new PointD[hullCount];
            for (i = 0; i < hull.Length; i++)
            {
                hull[i] = top.Vertex;
                top     = top.Next;
            }
            Debug.Assert(top == null);

            return(hull);
        }
Esempio n. 6
0
        /// <summary>
        /// Finds the intersection of the specified line segments, given the specified epsilon for
        /// coordinate comparisons.</summary>
        /// <param name="a">
        /// The <see cref="LineD.Start"/> point of the first line segment.</param>
        /// <param name="b">
        /// The <see cref="LineD.End"/> point of the first line segment.</param>
        /// <param name="c">
        /// The <see cref="LineD.Start"/> point of the second line segment.</param>
        /// <param name="d">
        /// The <see cref="LineD.End"/> point of the second line segment.</param>
        /// <param name="epsilon">
        /// The maximum absolute difference at which coordinates and intermediate results should be
        /// considered equal. This value is always raised to a minium of 1e-10.</param>
        /// <returns>
        /// A <see cref="LineIntersection"/> instance that describes if and how the line segments
        /// from <paramref name="a"/> to <paramref name="b"/> and from <paramref name="c"/> to
        /// <paramref name="d"/> intersect.</returns>
        /// <remarks><para>
        /// <b>Find</b> is identical with the basic <see cref="Find(PointD, PointD, PointD,
        /// PointD)"/> overload but uses the specified <paramref name="epsilon"/> to compare
        /// coordinates and intermediate results.
        /// </para><para>
        /// <b>Find</b> always raises the specified <paramref name="epsilon"/> to a minimum of 1e-10
        /// because the algorithm is otherwise too unstable, and would initiate multiple recursions
        /// with a greater epsilon anyway.</para></remarks>

        public static LineIntersection Find(PointD a, PointD b, PointD c, PointD d, double epsilon)
        {
            if (epsilon < 1e-10)
            {
                epsilon = 1e-10;
            }
            LineLocation first, second;

            double bax = b.X - a.X, bay = b.Y - a.Y;
            double dcx = d.X - c.X, dcy = d.Y - c.Y;

            // compute cross-products for all end points
            double d1 = (a.X - c.X) * dcy - (a.Y - c.Y) * dcx;
            double d2 = (b.X - c.X) * dcy - (b.Y - c.Y) * dcx;
            double d3 = (c.X - a.X) * bay - (c.Y - a.Y) * bax;
            double d4 = (d.X - a.X) * bay - (d.Y - a.Y) * bax;

            //Debug.Assert(d1 == c.CrossProductLength(a, d));
            //Debug.Assert(d2 == c.CrossProductLength(b, d));
            //Debug.Assert(d3 == a.CrossProductLength(c, b));
            //Debug.Assert(d4 == a.CrossProductLength(d, b));

            // check for collinear (but not parallel) lines
            if (Math.Abs(d1) <= epsilon && Math.Abs(d2) <= epsilon &&
                Math.Abs(d3) <= epsilon && Math.Abs(d4) <= epsilon)
            {
                // find lexicographically first point where segments overlap
                if (PointDComparerY.CompareExact(c, d) < 0)
                {
                    first = LocateCollinear(a, b, c, epsilon);
                    if (Contains(first))
                    {
                        return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear));
                    }

                    first = LocateCollinear(a, b, d, epsilon);
                    if (Contains(first))
                    {
                        return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear));
                    }
                }
                else
                {
                    first = LocateCollinear(a, b, d, epsilon);
                    if (Contains(first))
                    {
                        return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear));
                    }

                    first = LocateCollinear(a, b, c, epsilon);
                    if (Contains(first))
                    {
                        return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear));
                    }
                }

                // collinear line segments without overlapping points
                return(new LineIntersection(LineRelation.Collinear));
            }

            // check for divergent lines with end point intersection
            if (Math.Abs(d1) <= epsilon)
            {
                second = LocateCollinear(c, d, a, epsilon);
                return(new LineIntersection(a, LineLocation.Start, second, LineRelation.Divergent));
            }
            if (Math.Abs(d2) <= epsilon)
            {
                second = LocateCollinear(c, d, b, epsilon);
                return(new LineIntersection(b, LineLocation.End, second, LineRelation.Divergent));
            }
            if (Math.Abs(d3) <= epsilon)
            {
                first = LocateCollinear(a, b, c, epsilon);
                return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Divergent));
            }
            if (Math.Abs(d4) <= epsilon)
            {
                first = LocateCollinear(a, b, d, epsilon);
                return(new LineIntersection(d, first, LineLocation.End, LineRelation.Divergent));
            }

            // compute parameters of line equations
            double denom = dcx * bay - bax * dcy;

            if (Math.Abs(denom) <= epsilon)
            {
                return(new LineIntersection(LineRelation.Parallel));
            }

            double snum = a.X * dcy - a.Y * dcx - c.X * d.Y + c.Y * d.X;
            double s    = snum / denom;

            if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
            {
                if (s < 0)
                {
                    first = LineLocation.Before;
                }
                else if (s > 1)
                {
                    first = LineLocation.After;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }
            else
            {
                if (s > 0 && s < 1)
                {
                    first = LineLocation.Between;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }

            double tnum = c.Y * bax - c.X * bay + a.X * b.Y - a.Y * b.X;
            double t    = tnum / denom;

            if ((d3 < 0 && d4 < 0) || (d3 > 0 && d4 > 0))
            {
                if (t < 0)
                {
                    second = LineLocation.Before;
                }
                else if (t > 1)
                {
                    second = LineLocation.After;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }
            else
            {
                if (t > 0 && t < 1)
                {
                    second = LineLocation.Between;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }

            PointD shared = new PointD(a.X + s * bax, a.Y + s * bay);

            /*
             * Epsilon comparisons of cross products (or line equation parameters) might miss
             * epsilon-close end point intersections of very long line segments. We compensate by
             * directly comparing the computed intersection point against the four end points.
             */

            if (PointD.Equals(a, shared, epsilon))
            {
                first = LineLocation.Start;
            }
            else if (PointD.Equals(b, shared, epsilon))
            {
                first = LineLocation.End;
            }

            if (PointD.Equals(c, shared, epsilon))
            {
                second = LineLocation.Start;
            }
            else if (PointD.Equals(d, shared, epsilon))
            {
                second = LineLocation.End;
            }

            return(new LineIntersection(shared, first, second, LineRelation.Divergent));
        }
Esempio n. 7
0
        /// <overloads>
        /// Finds the intersection of the specified line segments.</overloads>
        /// <summary>
        /// Finds the intersection of the specified line segments, using exact coordinate
        /// comparisons.</summary>
        /// <param name="a">
        /// The <see cref="LineD.Start"/> point of the first line segment.</param>
        /// <param name="b">
        /// The <see cref="LineD.End"/> point of the first line segment.</param>
        /// <param name="c">
        /// The <see cref="LineD.Start"/> point of the second line segment.</param>
        /// <param name="d">
        /// The <see cref="LineD.End"/> point of the second line segment.</param>
        /// <returns>
        /// A <see cref="LineIntersection"/> instance that describes if and how the line segments
        /// from <paramref name="a"/> to <paramref name="b"/> and from <paramref name="c"/> to
        /// <paramref name="d"/> intersect.</returns>
        /// <remarks><para>
        /// <b>Find</b> was adapted from the <c>Segments-Intersect</c> algorithm by Thomas H. Cormen
        /// et al., <em>Introduction to Algorithms</em> (3rd ed.), The MIT Press 2009, p.1018, for
        /// intersection testing; and from the <c>SegSegInt</c> and <c>ParallelInt</c> algorithms by
        /// Joseph O’Rourke, <em>Computational Geometry in C</em> (2nd ed.), Cambridge University
        /// Press 1998, p.224f, for line relationships and shared coordinates.
        /// </para><para>
        /// Cormen’s intersection testing first examines the <see cref="PointD.CrossProductLength"/>
        /// for each triplet of specified points. If that is insufficient, O’Rourke’s algorithm then
        /// examines the parameters of both line equations. This is mathematically redundant since
        /// O’Rourke’s algorithm alone should produce all desired information, but the combination
        /// of both algorithms proved much more resilient against misjudging line relationships due
        /// to floating-point inaccuracies.
        /// </para><para>
        /// Although most comparisons in this overload are exact, cross-product testing is always
        /// performed with a minimum epsilon of 1e-10. Moreover, <b>Find</b> will return the result
        /// of the other <see cref="Find(PointD, PointD, PointD, PointD, Double)"/> overload with an
        /// epsilon of 2e-10 if cross-product testing contradicts line equation testing. Subsequent
        /// contradictions result in further recursive calls, each time with a doubled epsilon,
        /// until an intersection can be determined without contradictions.</para></remarks>

        public static LineIntersection Find(PointD a, PointD b, PointD c, PointD d)
        {
            const double epsilon = 1e-10;
            LineLocation first, second;

            double bax = b.X - a.X, bay = b.Y - a.Y;
            double dcx = d.X - c.X, dcy = d.Y - c.Y;

            // compute cross-products for all end points
            double d1 = (a.X - c.X) * dcy - (a.Y - c.Y) * dcx;
            double d2 = (b.X - c.X) * dcy - (b.Y - c.Y) * dcx;
            double d3 = (c.X - a.X) * bay - (c.Y - a.Y) * bax;
            double d4 = (d.X - a.X) * bay - (d.Y - a.Y) * bax;

            //Debug.Assert(d1 == c.CrossProductLength(a, d));
            //Debug.Assert(d2 == c.CrossProductLength(b, d));
            //Debug.Assert(d3 == a.CrossProductLength(c, b));
            //Debug.Assert(d4 == a.CrossProductLength(d, b));

            /*
             * Some cross-products are zero: corresponding end point triplets are collinear.
             *
             * The infinite lines intersect on the corresponding end points. The lines are collinear
             * exactly if all cross-products are zero; otherwise, the lines are divergent and we
             * need to check whether the finite line segments also intersect on the end points.
             *
             * We always perform epsilon comparisons on cross-products, even in the exact overload,
             * because almost-zero cases are very frequent, especially for collinear lines.
             */
            if (Math.Abs(d1) <= epsilon && Math.Abs(d2) <= epsilon &&
                Math.Abs(d3) <= epsilon && Math.Abs(d4) <= epsilon)
            {
                // find lexicographically first point where segments overlap
                if (PointDComparerY.CompareExact(c, d) < 0)
                {
                    first = LocateCollinear(a, b, c);
                    if (Contains(first))
                    {
                        return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear));
                    }

                    first = LocateCollinear(a, b, d);
                    if (Contains(first))
                    {
                        return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear));
                    }
                }
                else
                {
                    first = LocateCollinear(a, b, d);
                    if (Contains(first))
                    {
                        return(new LineIntersection(d, first, LineLocation.End, LineRelation.Collinear));
                    }

                    first = LocateCollinear(a, b, c);
                    if (Contains(first))
                    {
                        return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Collinear));
                    }
                }

                // collinear line segments without overlapping points
                return(new LineIntersection(LineRelation.Collinear));
            }

            // check for divergent lines with end point intersection
            if (Math.Abs(d1) <= epsilon)
            {
                second = LocateCollinear(c, d, a);
                return(new LineIntersection(a, LineLocation.Start, second, LineRelation.Divergent));
            }
            if (Math.Abs(d2) <= epsilon)
            {
                second = LocateCollinear(c, d, b);
                return(new LineIntersection(b, LineLocation.End, second, LineRelation.Divergent));
            }
            if (Math.Abs(d3) <= epsilon)
            {
                first = LocateCollinear(a, b, c);
                return(new LineIntersection(c, first, LineLocation.Start, LineRelation.Divergent));
            }
            if (Math.Abs(d4) <= epsilon)
            {
                first = LocateCollinear(a, b, d);
                return(new LineIntersection(d, first, LineLocation.End, LineRelation.Divergent));
            }

            /*
             * All cross-products are non-zero: divergent or parallel lines.
             *
             * The lines and segments might intersect, but not on any end point.
             * Compute parameters of both line equations to determine intersections.
             * Zero denominator indicates parallel lines (but not collinear, see above).
             */
            double denom = dcx * bay - bax * dcy;

            if (Math.Abs(denom) <= epsilon)
            {
                return(new LineIntersection(LineRelation.Parallel));
            }

            /*
             * Compute position of intersection point relative to line segments, and also perform
             * sanity checks for floating-point inaccuracies. If a check fails, we cannot give a
             * reliable result at the current precision and must recurse with a greater epsilon.
             *
             * Cross-products have pairwise opposite signs exactly if the corresponding line segment
             * straddles the infinite extension of the other line segment, implying a line equation
             * parameter between zero and one. Pairwise identical signs imply a parameter less than
             * zero or greater than one. Parameters cannot be exactly zero or one, as that indicates
             * end point intersections which were already ruled out by cross-product testing.
             */
            double snum = a.X * dcy - a.Y * dcx - c.X * d.Y + c.Y * d.X;
            double s    = snum / denom;

            if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
            {
                if (s < 0)
                {
                    first = LineLocation.Before;
                }
                else if (s > 1)
                {
                    first = LineLocation.After;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }
            else
            {
                if (s > 0 && s < 1)
                {
                    first = LineLocation.Between;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }

            double tnum = c.Y * bax - c.X * bay + a.X * b.Y - a.Y * b.X;
            double t    = tnum / denom;

            if ((d3 < 0 && d4 < 0) || (d3 > 0 && d4 > 0))
            {
                if (t < 0)
                {
                    second = LineLocation.Before;
                }
                else if (t > 1)
                {
                    second = LineLocation.After;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }
            else
            {
                if (t > 0 && t < 1)
                {
                    second = LineLocation.Between;
                }
                else
                {
                    return(Find(a, b, c, d, 2 * epsilon));
                }
            }

            PointD shared = new PointD(a.X + s * bax, a.Y + s * bay);

            return(new LineIntersection(shared, first, second, LineRelation.Divergent));
        }