public bool Contains(Vector2d v)
        {
            Vector2d lv = v - Center;

            return((Math.Abs(lv.Dot(AxisX)) <= Extent.x) &&
                   (Math.Abs(lv.Dot(AxisY)) <= Extent.y));
        }
        /// <summary>
        /// Returns distance to box, or 0 if point is inside box.
        /// Ported from WildMagic5 Wm5DistPoint2Box2.cpp
        /// </summary>
        public double DistanceSquared(Vector2d v)
        {
            // Work in the box's coordinate system.
            v -= this.Center;

            // Compute squared distance and closest point on box.
            double sqrDistance = 0;
            double delta, c, extent;

            for (int i = 0; i < 2; ++i)
            {
                if (i == 0)
                {
                    c = v.Dot(AxisX); extent = Extent.x;
                }
                else
                {
                    c = v.Dot(AxisY); extent = Extent.y;
                }
                if (c < -extent)
                {
                    delta        = c + extent;
                    sqrDistance += delta * delta;
                }
                else if (c > extent)
                {
                    delta        = c - extent;
                    sqrDistance += delta * delta;
                }
            }

            return(sqrDistance);
        }
Exemple #3
0
        // This is a variant of Fit() where the d1 value is specified.
        // Note: has not been tested extensively, particularly the special case
        // where one of the arcs beomes a semi-circle...
        void Fit(double d1)
        {
            Vector2d p1 = Point1;
            Vector2d p2 = Point2;

            Vector2d t1 = Tangent1;
            Vector2d t2 = Tangent2;

            // fit biarc
            Vector2d v       = p2 - p1;
            double   vMagSqr = v.LengthSquared;

            // set d1 equal to d2
            Vector2d t       = t1 + t2;
            double   tMagSqr = t.LengthSquared;

            double vDotT1 = v.Dot(t1);

            double vDotT2      = v.Dot(t2);
            double t1DotT2     = t1.Dot(t2);
            double denominator = (vDotT2 - d1 * (t1DotT2 - 1.0));

            if (MathUtil.EpsilonEqual(denominator, 0.0, MathUtil.ZeroTolerancef))
            {
                // the second arc is a semicircle

                FitD1 = d1;
                FitD2 = double.PositiveInfinity;

                Vector2d joint = p1 + d1 * t1;
                joint += (vDotT2 - d1 * t1DotT2) * t2;

                // construct arcs
                // [TODO] this might not be right for semi-circle...
                SetArcFromEdge(0, p1, t1, joint, true);
                SetArcFromEdge(1, p2, t2, joint, false);
            }
            else
            {
                double d2     = (0.5 * vMagSqr - d1 * vDotT1) / denominator;
                double invLen = 1.0 / (d1 + d2);

                Vector2d joint = (d1 * d2) * (t1 - t2);
                joint += d1 * p2;
                joint += d2 * p1;
                joint *= invLen;

                FitD1 = d1;
                FitD2 = d2;

                // draw arcs
                SetArcFromEdge(0, p1, t1, joint, true);
                SetArcFromEdge(1, p2, t2, joint, false);
            }
        }
Exemple #4
0
        void SetArcFromEdge(int i, Vector2d p1, Vector2d t1, Vector2d p2, bool fromP1)
        {
            Vector2d chord      = p2 - p1;
            Vector2d n1         = new Vector2d(-t1.y, t1.x);
            double   chordDotN1 = chord.Dot(n1);

            if (MathUtil.EpsilonEqual(chordDotN1, 0, Epsilon))
            {
                // straight line case
                set_arc(i, new Arc(p1, p2));
            }
            else
            {
                double   radius = chord.LengthSquared / (2.0 * chordDotN1);
                Vector2d center = p1 + radius * n1;

                Vector2d p1Offset = p1 - center;
                Vector2d p2Offset = p2 - center;

                var p1Ang1 = Math.Atan2(p1Offset.y, p1Offset.x);
                var p2Ang1 = Math.Atan2(p2Offset.y, p2Offset.x);
                if (p1Offset.x * t1.y - p1Offset.y * t1.x > 0)
                {
                    set_arc(i, new Arc(center, Math.Abs(radius), p1Ang1, p2Ang1, !fromP1));
                }
                else
                {
                    set_arc(i, new Arc(center, Math.Abs(radius), p1Ang1, p2Ang1, fromP1));
                }
            }
        }
        public Vector2d ClosestPoint(Vector2d v)
        {
            // Work in the box's coordinate system.
            Vector2d diff = v - this.Center;

            // Compute squared distance and closest point on box.
            double   sqrDistance = 0;
            double   delta;
            Vector2d closest = new Vector2d();

            for (int i = 0; i < 2; ++i)
            {
                closest[i] = diff.Dot((i == 0) ? AxisX : AxisY);
                double extent = (i == 0) ? Extent.x : Extent.y;
                if (closest[i] < -extent)
                {
                    delta        = closest[i] + extent;
                    sqrDistance += delta * delta;
                    closest[i]   = -extent;
                }
                else if (closest[i] > extent)
                {
                    delta        = closest[i] - extent;
                    sqrDistance += delta * delta;
                    closest[i]   = extent;
                }
            }

            return(closest.x * AxisX + closest.y * AxisY);
        }
        public static int WhichSide(Triangle2d V, Vector2d P, Vector2d D)
        {
            // Vertices are projected to the form P+t*D.  Return value is +1 if all
            // t > 0, -1 if all t < 0, 0 otherwise, in which case the line splits the
            // triangle.

            int positive = 0, negative = 0, zero = 0;

            for (int i = 0; i < 3; ++i)
            {
                double t = D.Dot(V[i] - P);
                if (t > (double)0)
                {
                    ++positive;
                }
                else if (t < (double)0)
                {
                    ++negative;
                }
                else
                {
                    ++zero;
                }

                if (positive > 0 && negative > 0)
                {
                    return(0);
                }
            }
            return(zero == 0 ? (positive > 0 ? 1 : -1) : 0);
        }
Exemple #7
0
        public double GetSquared()
        {
            if (DistanceSquared >= 0)
            {
                return(DistanceSquared);
            }

            // Projection of P-C onto plane is Q-C = P-C - Dot(N,P-C)*N.
            Vector2d PmC       = point - circle.Center;
            double   lengthPmC = PmC.Length;

            if (lengthPmC > MathUtil.Epsilon)
            {
                CircleClosest = circle.Center + circle.Radius * PmC / lengthPmC;
                AllCirclePointsEquidistant = false;
            }
            else
            {
                // All circle points are equidistant from P.  Return one of them.
                CircleClosest = circle.Center + circle.Radius;
                AllCirclePointsEquidistant = true;
            }

            Vector2d diff        = point - CircleClosest;
            double   sqrDistance = diff.Dot(diff);

            // Account for numerical round-off error.
            if (sqrDistance < 0)
            {
                sqrDistance = 0;
            }
            DistanceSquared = sqrDistance;
            return(sqrDistance);
        }
        public double GetSquared()
        {
            if (DistanceSquared >= 0)
            {
                return(DistanceSquared);
            }

            Vector2d diff = line1.Origin - line2.Origin;
            double   a01 = -line1.Direction.Dot(line2.Direction);
            double   b0 = diff.Dot(line1.Direction);
            double   c = diff.LengthSquared;
            double   det = Math.Abs(1.0 - a01 * a01);
            double   b1, s0, s1, sqrDist;

            if (det >= MathUtil.ZeroTolerance)
            {
                // Lines are not parallel.
                b1 = -diff.Dot(line2.Direction);
                double invDet = ((double)1) / det;
                s0      = (a01 * b1 - b0) * invDet;
                s1      = (a01 * b0 - b1) * invDet;
                sqrDist = (double)0;
            }
            else
            {
                // Lines are parallel, select any closest pair of points.
                s0      = -b0;
                s1      = (double)0;
                sqrDist = b0 * s0 + c;

                // Account for numerical round-off errors.
                if (sqrDist < (double)0)
                {
                    sqrDist = (double)0;
                }
            }

            Line1Parameter = s0;
            Line1Closest   = line1.Origin + s0 * line1.Direction;
            Line2Parameter = s1;
            Line2Closest   = line2.Origin + s1 * line2.Direction;

            DistanceSquared = sqrDist;
            return(sqrDist);
        }
Exemple #9
0
        // Evaluate the quadratic function Q(X) = (X-K)^T * M * (X-K) - 1.
        public double Evaluate(Vector2d point)
        {
            Vector2d diff   = point - Center;
            double   ratio0 = Axis0.Dot(diff) / Extent[0];
            double   ratio1 = Axis1.Dot(diff) / Extent[1];
            double   value  = ratio0 * ratio0 + ratio1 * ratio1 - (double)1;

            return(value);
        }
Exemple #10
0
        /// <summary>
        /// Compute barycentric coordinates/weights of vPoint inside triangle (V0,V1,V2).
        /// If point is inside triangle, coords will pe positive and sum to 1.
        /// ie if result is a, then vPoint = a.x*V0 + a.y*V1 + a.z*V2.
        /// </summary>
        public static Vector3d BarycentricCoords(Vector2d vPoint, Vector2d V0, Vector2d V1, Vector2d V2)
        {
            Vector2d kV02    = V0 - V2;
            Vector2d kV12    = V1 - V2;
            Vector2d kPV2    = vPoint - V2;
            double   fM00    = kV02.Dot(kV02);
            double   fM01    = kV02.Dot(kV12);
            double   fM11    = kV12.Dot(kV12);
            double   fR0     = kV02.Dot(kPV2);
            double   fR1     = kV12.Dot(kPV2);
            double   fDet    = fM00 * fM11 - fM01 * fM01;
            double   fInvDet = 1.0 / fDet;
            double   fBary1  = (fM11 * fR0 - fM01 * fR1) * fInvDet;
            double   fBary2  = (fM00 * fR1 - fM01 * fR0) * fInvDet;
            double   fBary3  = 1.0 - fBary1 - fBary2;

            return(new Vector3d(fBary1, fBary2, fBary3));
        }
        protected void UpdateBox(Vector2d LPoint, Vector2d RPoint,
                                 Vector2d BPoint, Vector2d TPoint,
                                 ref Vector2d U, ref Vector2d V, ref double minAreaDiv4)
        {
            Vector2d RLDiff   = RPoint - LPoint;
            Vector2d TBDiff   = TPoint - BPoint;
            double   extent0  = ((double)0.5) * (U.Dot(RLDiff));
            double   extent1  = ((double)0.5) * (V.Dot(TBDiff));
            double   areaDiv4 = extent0 * extent1;

            if (areaDiv4 < minAreaDiv4)
            {
                minAreaDiv4       = areaDiv4;
                mMinBox.AxisX     = U;
                mMinBox.AxisY     = V;
                mMinBox.Extent[0] = extent0;
                mMinBox.Extent[1] = extent1;
                Vector2d LBDiff = LPoint - BPoint;
                mMinBox.Center = LPoint + U * extent0 + V * (extent1 - V.Dot(LBDiff));
            }
        }
        public bool Find()
        {
            if (Result != IntersectionResult.NotComputed)
            {
                return(Result == IntersectionResult.Intersects);
            }

            // The potential intersection is initialized to triangle1.  The set of
            // vertices is refined based on clipping against each edge of triangle0.
            Quantity = 3;
            Points   = new Vector2d[6];
            for (int i = 0; i < 3; ++i)
            {
                Points[i] = triangle1[i];
            }

            for (int i1 = 2, i0 = 0; i0 < 3; i1 = i0++)
            {
                // Clip against edge <V0[i1],V0[i0]>.
                var N = new Vector2d(
                    triangle0[i1].y - triangle0[i0].y,
                    triangle0[i0].x - triangle0[i1].x);
                double c = N.Dot(triangle0[i1]);
                ClipConvexPolygonAgainstLine(N, c, ref Quantity, ref Points);
                if (Quantity == 0)
                {
                    // Triangle completely clipped, no intersection occurs.
                    Type = IntersectionType.Empty;
                }
                else if (Quantity == 1)
                {
                    Type = IntersectionType.Point;
                }
                else if (Quantity == 2)
                {
                    Type = IntersectionType.Segment;
                }
                else
                {
                    Type = IntersectionType.Polygon;
                }
            }

            Result = (Type != IntersectionType.Empty) ?
                     IntersectionResult.Intersects : IntersectionResult.NoIntersection;
            return(Result == IntersectionResult.Intersects);
        }
        public double GetSquared()
        {
            if (DistanceSquared >= 0)
            {
                return(DistanceSquared);
            }

            // Work in the box's coordinate system.
            Vector2d diff = point - box.Center;

            // Compute squared distance and closest point on box.
            double   sqrDistance = (double)0;
            double   delta;
            Vector2d closest = Vector2d.Zero;
            int      i;

            for (i = 0; i < 2; ++i)
            {
                closest[i] = diff.Dot(box.Axis(i));
                if (closest[i] < -box.Extent[i])
                {
                    delta        = closest[i] + box.Extent[i];
                    sqrDistance += delta * delta;
                    closest[i]   = -box.Extent[i];
                }
                else if (closest[i] > box.Extent[i])
                {
                    delta        = closest[i] - box.Extent[i];
                    sqrDistance += delta * delta;
                    closest[i]   = box.Extent[i];
                }
            }

            BoxClosest = box.Center;
            for (i = 0; i < 2; ++i)
            {
                BoxClosest += closest[i] * box.Axis(i);
            }

            DistanceSquared = sqrDistance;
            return(sqrDistance);
        }
Exemple #14
0
        /// <summary>
        /// Test if segments intersect. Returns true for parallel-line overlaps.
        /// Returns same result as IntrSegment2Segment2.
        /// </summary>
        public bool Intersects(ref Segment2d seg2, double dotThresh = double.Epsilon, double intervalThresh = 0)
        {
            // see IntrLine2Line2 and IntrSegment2Segment2 for details on this code

            Vector2d diff        = seg2.Center - Center;
            double   D0DotPerpD1 = Direction.DotPerp(seg2.Direction);

            if (Math.Abs(D0DotPerpD1) > dotThresh)
            {               // Lines intersect in a single point.
                double invD0DotPerpD1 = ((double)1) / D0DotPerpD1;
                double diffDotPerpD0  = diff.DotPerp(Direction);
                double diffDotPerpD1  = diff.DotPerp(seg2.Direction);
                double s  = diffDotPerpD1 * invD0DotPerpD1;
                double s2 = diffDotPerpD0 * invD0DotPerpD1;
                return(Math.Abs(s) <= (Extent + intervalThresh) &&
                       Math.Abs(s2) <= (seg2.Extent + intervalThresh));
            }

            // Lines are parallel.
            diff.Normalize();
            double diffNDotPerpD1 = diff.DotPerp(seg2.Direction);

            if (Math.Abs(diffNDotPerpD1) <= dotThresh)
            {
                // Compute the location of segment1 endpoints relative to segment0.
                diff = seg2.Center - Center;
                double t1      = Direction.Dot(diff);
                double tmin    = t1 - seg2.Extent;
                double tmax    = t1 + seg2.Extent;
                var    extents = new Interval1d(-Extent, Extent);
                if (extents.Overlaps(new Interval1d(tmin, tmax)))
                {
                    return(true);
                }

                return(false);
            }

            // lines are parallel but not collinear
            return(false);
        }
        public void Contain(Vector2d v)
        {
            Vector2d lv = v - Center;

            for (int k = 0; k < 2; ++k)
            {
                double t = lv.Dot(Axis(k));
                if (Math.Abs(t) > Extent[k])
                {
                    double min = -Extent[k], max = Extent[k];
                    if (t < min)
                    {
                        min = t;
                    }
                    else if (t > max)
                    {
                        max = t;
                    }
                    Extent[k] = (max - min) * 0.5;
                    Center    = Center + ((max + min) * 0.5) * Axis(k);
                }
            }
        }
Exemple #16
0
        public double GetSquared()
        {
            if (DistanceSquared >= 0)
            {
                return(DistanceSquared);
            }

            Vector2d diff = segment0.Center - segment1.Center;
            double   a01 = -segment0.Direction.Dot(segment1.Direction);
            double   b0 = diff.Dot(segment0.Direction);
            double   b1 = -diff.Dot(segment1.Direction);
            double   c = diff.LengthSquared;
            double   det = Math.Abs((double)1 - a01 * a01);
            double   s0, s1, sqrDist, extDet0, extDet1, tmpS0, tmpS1;

            if (det >= MathUtil.ZeroTolerance)
            {
                // Segments are not parallel.
                s0      = a01 * b1 - b0;
                s1      = a01 * b0 - b1;
                extDet0 = segment0.Extent * det;
                extDet1 = segment1.Extent * det;

                if (s0 >= -extDet0)
                {
                    if (s0 <= extDet0)
                    {
                        if (s1 >= -extDet1)
                        {
                            if (s1 <= extDet1)                              // region 0 (interior)
                            {
                                // Minimum at interior points of segments.
                                double invDet = ((double)1) / det;
                                s0     *= invDet;
                                s1     *= invDet;
                                sqrDist = (double)0;
                            }
                            else                              // region 3 (side)
                            {
                                s1    = segment1.Extent;
                                tmpS0 = -(a01 * s1 + b0);
                                if (tmpS0 < -segment0.Extent)
                                {
                                    s0      = -segment0.Extent;
                                    sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                              s1 * (s1 + ((double)2) * b1) + c;
                                }
                                else if (tmpS0 <= segment0.Extent)
                                {
                                    s0      = tmpS0;
                                    sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                                }
                                else
                                {
                                    s0      = segment0.Extent;
                                    sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                              s1 * (s1 + ((double)2) * b1) + c;
                                }
                            }
                        }
                        else                          // region 7 (side)
                        {
                            s1    = -segment1.Extent;
                            tmpS0 = -(a01 * s1 + b0);
                            if (tmpS0 < -segment0.Extent)
                            {
                                s0      = -segment0.Extent;
                                sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                          s1 * (s1 + ((double)2) * b1) + c;
                            }
                            else if (tmpS0 <= segment0.Extent)
                            {
                                s0      = tmpS0;
                                sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                            }
                            else
                            {
                                s0      = segment0.Extent;
                                sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                          s1 * (s1 + ((double)2) * b1) + c;
                            }
                        }
                    }
                    else
                    {
                        if (s1 >= -extDet1)
                        {
                            if (s1 <= extDet1)                              // region 1 (side)
                            {
                                s0    = segment0.Extent;
                                tmpS1 = -(a01 * s0 + b1);
                                if (tmpS1 < -segment1.Extent)
                                {
                                    s1      = -segment1.Extent;
                                    sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                              s0 * (s0 + ((double)2) * b0) + c;
                                }
                                else if (tmpS1 <= segment1.Extent)
                                {
                                    s1      = tmpS1;
                                    sqrDist = -s1 * s1 + s0 * (s0 + ((double)2) * b0) + c;
                                }
                                else
                                {
                                    s1      = segment1.Extent;
                                    sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                              s0 * (s0 + ((double)2) * b0) + c;
                                }
                            }
                            else                              // region 2 (corner)
                            {
                                s1    = segment1.Extent;
                                tmpS0 = -(a01 * s1 + b0);
                                if (tmpS0 < -segment0.Extent)
                                {
                                    s0      = -segment0.Extent;
                                    sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                              s1 * (s1 + ((double)2) * b1) + c;
                                }
                                else if (tmpS0 <= segment0.Extent)
                                {
                                    s0      = tmpS0;
                                    sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                                }
                                else
                                {
                                    s0    = segment0.Extent;
                                    tmpS1 = -(a01 * s0 + b1);
                                    if (tmpS1 < -segment1.Extent)
                                    {
                                        s1      = -segment1.Extent;
                                        sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                                  s0 * (s0 + ((double)2) * b0) + c;
                                    }
                                    else if (tmpS1 <= segment1.Extent)
                                    {
                                        s1      = tmpS1;
                                        sqrDist = -s1 * s1 + s0 * (s0 + ((double)2) * b0) + c;
                                    }
                                    else
                                    {
                                        s1      = segment1.Extent;
                                        sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                                  s0 * (s0 + ((double)2) * b0) + c;
                                    }
                                }
                            }
                        }
                        else                          // region 8 (corner)
                        {
                            s1    = -segment1.Extent;
                            tmpS0 = -(a01 * s1 + b0);
                            if (tmpS0 < -segment0.Extent)
                            {
                                s0      = -segment0.Extent;
                                sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                          s1 * (s1 + ((double)2) * b1) + c;
                            }
                            else if (tmpS0 <= segment0.Extent)
                            {
                                s0      = tmpS0;
                                sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                            }
                            else
                            {
                                s0    = segment0.Extent;
                                tmpS1 = -(a01 * s0 + b1);
                                if (tmpS1 > segment1.Extent)
                                {
                                    s1      = segment1.Extent;
                                    sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                              s0 * (s0 + ((double)2) * b0) + c;
                                }
                                else if (tmpS1 >= -segment1.Extent)
                                {
                                    s1      = tmpS1;
                                    sqrDist = -s1 * s1 + s0 * (s0 + ((double)2) * b0) + c;
                                }
                                else
                                {
                                    s1      = -segment1.Extent;
                                    sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                              s0 * (s0 + ((double)2) * b0) + c;
                                }
                            }
                        }
                    }
                }
                else
                {
                    if (s1 >= -extDet1)
                    {
                        if (s1 <= extDet1)                          // region 5 (side)
                        {
                            s0    = -segment0.Extent;
                            tmpS1 = -(a01 * s0 + b1);
                            if (tmpS1 < -segment1.Extent)
                            {
                                s1      = -segment1.Extent;
                                sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                          s0 * (s0 + ((double)2) * b0) + c;
                            }
                            else if (tmpS1 <= segment1.Extent)
                            {
                                s1      = tmpS1;
                                sqrDist = -s1 * s1 + s0 * (s0 + ((double)2) * b0) + c;
                            }
                            else
                            {
                                s1      = segment1.Extent;
                                sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                          s0 * (s0 + ((double)2) * b0) + c;
                            }
                        }
                        else                          // region 4 (corner)
                        {
                            s1    = segment1.Extent;
                            tmpS0 = -(a01 * s1 + b0);
                            if (tmpS0 > segment0.Extent)
                            {
                                s0      = segment0.Extent;
                                sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                          s1 * (s1 + ((double)2) * b1) + c;
                            }
                            else if (tmpS0 >= -segment0.Extent)
                            {
                                s0      = tmpS0;
                                sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                            }
                            else
                            {
                                s0    = -segment0.Extent;
                                tmpS1 = -(a01 * s0 + b1);
                                if (tmpS1 < -segment1.Extent)
                                {
                                    s1      = -segment1.Extent;
                                    sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                              s0 * (s0 + ((double)2) * b0) + c;
                                }
                                else if (tmpS1 <= segment1.Extent)
                                {
                                    s1      = tmpS1;
                                    sqrDist = -s1 * s1 + s0 * (s0 + ((double)2) * b0) + c;
                                }
                                else
                                {
                                    s1      = segment1.Extent;
                                    sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                              s0 * (s0 + ((double)2) * b0) + c;
                                }
                            }
                        }
                    }
                    else                       // region 6 (corner)
                    {
                        s1    = -segment1.Extent;
                        tmpS0 = -(a01 * s1 + b0);
                        if (tmpS0 > segment0.Extent)
                        {
                            s0      = segment0.Extent;
                            sqrDist = s0 * (s0 - ((double)2) * tmpS0) +
                                      s1 * (s1 + ((double)2) * b1) + c;
                        }
                        else if (tmpS0 >= -segment0.Extent)
                        {
                            s0      = tmpS0;
                            sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                        }
                        else
                        {
                            s0    = -segment0.Extent;
                            tmpS1 = -(a01 * s0 + b1);
                            if (tmpS1 < -segment1.Extent)
                            {
                                s1      = -segment1.Extent;
                                sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                          s0 * (s0 + ((double)2) * b0) + c;
                            }
                            else if (tmpS1 <= segment1.Extent)
                            {
                                s1      = tmpS1;
                                sqrDist = -s1 * s1 + s0 * (s0 + ((double)2) * b0) + c;
                            }
                            else
                            {
                                s1      = segment1.Extent;
                                sqrDist = s1 * (s1 - ((double)2) * tmpS1) +
                                          s0 * (s0 + ((double)2) * b0) + c;
                            }
                        }
                    }
                }
            }
            else
            {
                // The segments are parallel.  The average b0 term is designed to
                // ensure symmetry of the function.  That is, dist(seg0,seg1) and
                // dist(seg1,seg0) should produce the same number.
                double e0pe1  = segment0.Extent + segment1.Extent;
                double sign   = (a01 > (double)0 ? (double)-1 : (double)1);
                double b0Avr  = ((double)0.5) * (b0 - sign * b1);
                double lambda = -b0Avr;
                if (lambda < -e0pe1)
                {
                    lambda = -e0pe1;
                }
                else if (lambda > e0pe1)
                {
                    lambda = e0pe1;
                }

                s1      = -sign * lambda * segment1.Extent / e0pe1;
                s0      = lambda + sign * s1;
                sqrDist = lambda * (lambda + ((double)2) * b0Avr) + c;
            }

            // Account for numerical round-off errors.
            if (sqrDist < (double)0)
            {
                sqrDist = (double)0;
            }

            Segment1Parameter = s0;
            Segment1Closest   = segment0.Center + s0 * segment0.Direction;
            Segment2Parameter = s1;
            Segment2Closest   = segment1.Center + s1 * segment1.Direction;

            DistanceSquared = sqrDist;
            return(sqrDist);
        }
        public static void GetInterval(Vector2d origin, Vector2d direction, Triangle2d tri,
                                       Vector3d dist, Vector3i sign, ref Vector2d param)
        {
            // Project triangle onto line.
            Vector3d proj = Vector3d.Zero;
            int      i;

            for (i = 0; i < 3; ++i)
            {
                Vector2d diff = tri[i] - origin;
                proj[i] = direction.Dot(diff);
            }

            // Compute transverse intersections of triangle edges with line.
            double numer, denom;
            int    i0, i1, i2;
            int    quantity = 0;

            for (i0 = 2, i1 = 0; i1 < 3; i0 = i1++)
            {
                if (sign[i0] * sign[i1] < 0)
                {
                    if (quantity >= 2)
                    {
                        throw new Exception("IntrLine2Triangle2.GetInterval: too many intersections!");
                    }

                    numer             = dist[i0] * proj[i1] - dist[i1] * proj[i0];
                    denom             = dist[i0] - dist[i1];
                    param[quantity++] = numer / denom;
                }
            }

            // Check for grazing contact.
            if (quantity < 2)
            {
                for (i0 = 1, i1 = 2, i2 = 0; i2 < 3; i0 = i1, i1 = i2++)
                {
                    if (sign[i2] == 0)
                    {
                        if (quantity >= 2)
                        {
                            throw new Exception("IntrLine2Triangle2.GetInterval: too many intersections!");
                        }

                        param[quantity++] = proj[i2];
                    }
                }
            }

            // Sort.
            if (quantity < 1)
            {
                throw new Exception("IntrLine2Triangle2.GetInterval: need at least one intersection");
            }

            if (quantity == 2)
            {
                if (param[0] > param[1])
                {
                    double save = param[0];
                    param[0] = param[1];
                    param[1] = save;
                }
            }
            else
            {
                param[1] = param[0];
            }
        }
        // Vin is input polygon vertices, returns clipped polygon, vertex count of
        //   clipped polygon is returned in quantity
        // **NOTE** returned array may have more elements than quantity!!
        public static void ClipConvexPolygonAgainstLine(
            Vector2d N, double c, ref int quantity, ref Vector2d[] V)
        {
            // The input vertices are assumed to be in counterclockwise order.  The
            // ordering is an invariant of this function.

            // Test on which side of line the vertices are.
            int positive = 0, negative = 0, pIndex = -1;

            double[] test = new double[6];
            int      i;

            for (i = 0; i < quantity; ++i)
            {
                test[i] = N.Dot(V[i]) - c;
                if (test[i] > (double)0)
                {
                    positive++;
                    if (pIndex < 0)
                    {
                        pIndex = i;
                    }
                }
                else if (test[i] < (double)0)
                {
                    negative++;
                }
            }

            if (positive > 0)
            {
                if (negative > 0)
                {
                    // Line transversely intersects polygon.
                    var    CV = new Vector2d[6];
                    int    cQuantity = 0, cur, prv;
                    double t;

                    if (pIndex > 0)
                    {
                        // First clip vertex on line.
                        cur             = pIndex;
                        prv             = cur - 1;
                        t               = test[cur] / (test[cur] - test[prv]);
                        CV[cQuantity++] = V[cur] + t * (V[prv] - V[cur]);

                        // Vertices on positive side of line.
                        while (cur < quantity && test[cur] > (double)0)
                        {
                            CV[cQuantity++] = V[cur++];
                        }

                        // Last clip vertex on line.
                        if (cur < quantity)
                        {
                            prv = cur - 1;
                        }
                        else
                        {
                            cur = 0;
                            prv = quantity - 1;
                        }
                        t = test[cur] / (test[cur] - test[prv]);
                        CV[cQuantity++] = V[cur] + t * (V[prv] - V[cur]);
                    }
                    else                      // pIndex is 0
                    {
                        // Vertices on positive side of line.
                        cur = 0;
                        while (cur < quantity && test[cur] > (double)0)
                        {
                            CV[cQuantity++] = V[cur++];
                        }

                        // Last clip vertex on line.
                        prv             = cur - 1;
                        t               = test[cur] / (test[cur] - test[prv]);
                        CV[cQuantity++] = V[cur] + t * (V[prv] - V[cur]);

                        // Skip vertices on negative side.
                        while (cur < quantity && test[cur] <= (double)0)
                        {
                            ++cur;
                        }

                        // First clip vertex on line.
                        if (cur < quantity)
                        {
                            prv             = cur - 1;
                            t               = test[cur] / (test[cur] - test[prv]);
                            CV[cQuantity++] = V[cur] + t * (V[prv] - V[cur]);

                            // Vertices on positive side of line.
                            while (cur < quantity && test[cur] > (double)0)
                            {
                                CV[cQuantity++] = V[cur++];
                            }
                        }
                        else
                        {
                            // cur = 0
                            prv             = quantity - 1;
                            t               = test[0] / (test[0] - test[prv]);
                            CV[cQuantity++] = V[0] + t * (V[prv] - V[0]);
                        }
                    }

                    quantity = cQuantity;
                    Array.Copy(CV, V, cQuantity);
                }
                // else polygon fully on positive side of line, nothing to do.
            }
            else
            {
                // Polygon does not intersect positive side of line, clip all.
                quantity = 0;
            }
        }
        // ported from WildMagic5 Wm5ContBox2.cpp::MergeBoxes
        public static Box2d Merge(ref Box2d box0, ref Box2d box1)
        {
            // Construct a box that contains the input boxes.
            Box2d box = new Box2d();

            // The first guess at the box center.  This value will be updated later
            // after the input box vertices are projected onto axes determined by an
            // average of box axes.
            box.Center = 0.5 * (box0.Center + box1.Center);

            // The merged box axes are the averages of the input box axes.  The
            // axes of the second box are negated, if necessary, so they form acute
            // angles with the axes of the first box.
            if (box0.AxisX.Dot(box1.AxisX) >= 0)
            {
                box.AxisX = (0.5) * (box0.AxisX + box1.AxisX);
                box.AxisX.Normalize();
            }
            else
            {
                box.AxisX = (0.5) * (box0.AxisX - box1.AxisX);
                box.AxisX.Normalize();
            }
            box.AxisY = -box.AxisX.Perp;

            // Project the input box vertices onto the merged-box axes.  Each axis
            // D[i] containing the current center C has a minimum projected value
            // min[i] and a maximum projected value max[i].  The corresponding end
            // points on the axes are C+min[i]*D[i] and C+max[i]*D[i].  The point C
            // is not necessarily the midpoint for any of the intervals.  The actual
            // box center will be adjusted from C to a point C' that is the midpoint
            // of each interval,
            //   C' = C + sum_{i=0}^1 0.5*(min[i]+max[i])*D[i]
            // The box extents are
            //   e[i] = 0.5*(max[i]-min[i])

            int            i, j;
            double         dot;
            Vector2d       diff   = new Vector2d();
            Vector2d       pmin   = Vector2d.Zero;
            Vector2d       pmax   = Vector2d.Zero;
            Vector2dTuple4 vertex = new Vector2dTuple4();

            box0.ComputeVertices(ref vertex);
            for (i = 0; i < 4; ++i)
            {
                diff = vertex[i] - box.Center;
                for (j = 0; j < 2; ++j)
                {
                    dot = diff.Dot(box.Axis(j));
                    if (dot > pmax[j])
                    {
                        pmax[j] = dot;
                    }
                    else if (dot < pmin[j])
                    {
                        pmin[j] = dot;
                    }
                }
            }

            box1.ComputeVertices(ref vertex);
            for (i = 0; i < 4; ++i)
            {
                diff = vertex[i] - box.Center;
                for (j = 0; j < 2; ++j)
                {
                    dot = diff.Dot(box.Axis(j));
                    if (dot > pmax[j])
                    {
                        pmax[j] = dot;
                    }
                    else if (dot < pmin[j])
                    {
                        pmin[j] = dot;
                    }
                }
            }

            // [min,max] is the axis-aligned box in the coordinate system of the
            // merged box axes.  Update the current box center to be the center of
            // the new box.  Compute the extents based on the new center.
            box.Extent[0] = 0.5 * (pmax[0] - pmin[0]);
            box.Extent[1] = 0.5 * (pmax[1] - pmin[1]);
            box.Center   += box.AxisX * (0.5 * (pmax[0] + pmin[0]));
            box.Center   += box.AxisY * (0.5 * (pmax[1] + pmin[1]));

            return(box);
        }
        public double GetSquared()
        {
            if (DistanceSquared >= 0)
            {
                return(DistanceSquared);
            }

            Vector2d diff = line.Origin - segment.Center;
            double   a01 = -line.Direction.Dot(segment.Direction);
            double   b0 = diff.Dot(line.Direction);
            double   c = diff.LengthSquared;
            double   det = Math.Abs((double)1 - a01 * a01);
            double   b1, s0, s1, sqrDist, extDet;

            if (det >= MathUtil.ZeroTolerance)
            {
                // The line and segment are not parallel.
                b1     = -diff.Dot(segment.Direction);
                s1     = a01 * b0 - b1;
                extDet = segment.Extent * det;

                if (s1 >= -extDet)
                {
                    if (s1 <= extDet)
                    {
                        // Two interior points are closest, one on the line and one
                        // on the segment.
                        double invDet = ((double)1) / det;
                        s0      = (a01 * b1 - b0) * invDet;
                        s1     *= invDet;
                        sqrDist = (double)0;
                    }
                    else
                    {
                        // The endpoint e1 of the segment and an interior point of
                        // the line are closest.
                        s1      = segment.Extent;
                        s0      = -(a01 * s1 + b0);
                        sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                    }
                }
                else
                {
                    // The endpoint e0 of the segment and an interior point of the
                    // line are closest.
                    s1      = -segment.Extent;
                    s0      = -(a01 * s1 + b0);
                    sqrDist = -s0 * s0 + s1 * (s1 + ((double)2) * b1) + c;
                }
            }
            else
            {
                // The line and segment are parallel.  Choose the closest pair so that
                // one point is at segment origin.
                s1      = (double)0;
                s0      = -b0;
                sqrDist = b0 * s0 + c;
            }

            LineParameter    = s0;
            LineClosest      = line.Origin + s0 * line.Direction;
            SegmentParameter = s1;
            SegmentClosest   = segment.Center + s1 * segment.Direction;

            // Account for numerical round-off errors
            if (sqrDist < (double)0)
            {
                sqrDist = (double)0;
            }

            DistanceSquared = sqrDist;
            return(sqrDist);
        }
 // u^T*M*v
 public double QForm(Vector2d u, Vector2d v)
 {
     return(u.Dot(this * v));
 }
        public ContMinBox2(IList <Vector2d> points, double epsilon, QueryNumberType queryType, bool isConvexPolygon)
        {
            // Get the convex hull of the points.
            IList <Vector2d> hullPoints;
            int numPoints;

            if (isConvexPolygon)
            {
                hullPoints = points;
                numPoints  = hullPoints.Count;
            }
            else
            {
                ConvexHull2 hull             = new ConvexHull2(points, epsilon, queryType);
                int         hullDim          = hull.Dimension;
                int         hullNumSimplices = hull.NumSimplices;
                int[]       hullIndices      = hull.HullIndices;

                if (hullDim == 0)
                {
                    mMinBox.Center    = points[0];
                    mMinBox.AxisX     = Vector2d.AxisX;
                    mMinBox.AxisY     = Vector2d.AxisY;
                    mMinBox.Extent[0] = (double)0;
                    mMinBox.Extent[1] = (double)0;
                    return;
                }

                if (hullDim == 1)
                {
                    throw new NotImplementedException("ContMinBox2: Have not implemented 1d case");
                    //ConvexHull1 hull1 = hull.GetConvexHull1();
                    //hullIndices = hull1->GetIndices();

                    //mMinBox.Center = ((double)0.5) * (points[hullIndices[0]] +
                    //    points[hullIndices[1]]);
                    //Vector2d diff =
                    //    points[hullIndices[1]] - points[hullIndices[0]];
                    //mMinBox.Extent[0] = ((double)0.5) * diff.Normalize();
                    //mMinBox.Extent[1] = (double)0.0;
                    //mMinBox.Axis[0] = diff;
                    //mMinBox.Axis[1] = -mMinBox.Axis[0].Perp();
                    //return;
                }

                numPoints = hullNumSimplices;
                Vector2d[] pointsArray = new Vector2d[numPoints];
                for (int i = 0; i < numPoints; ++i)
                {
                    pointsArray[i] = points[hullIndices[i]];
                }
                hullPoints = pointsArray;
            }

            // The input points are V[0] through V[N-1] and are assumed to be the
            // vertices of a convex polygon that are counterclockwise ordered.  The
            // input points must not contain three consecutive collinear points.

            // Unit-length edge directions of convex polygon.  These could be
            // precomputed and passed to this routine if the application requires it.
            int numPointsM1 = numPoints - 1;

            Vector2d[] edges   = new Vector2d[numPoints];
            bool[]     visited = new bool[numPoints];
            for (int i = 0; i < numPointsM1; ++i)
            {
                edges[i] = hullPoints[i + 1] - hullPoints[i];
                edges[i].Normalize();
                visited[i] = false;
            }
            edges[numPointsM1] = hullPoints[0] - hullPoints[numPointsM1];
            edges[numPointsM1].Normalize();
            visited[numPointsM1] = false;

            // Find the smallest axis-aligned box containing the points.  Keep track
            // of the extremum indices, L (left), R (right), B (bottom), and T (top)
            // so that the following constraints are met:
            //   V[L].x <= V[i].x for all i and V[(L+1)%N].x > V[L].x
            //   V[R].x >= V[i].x for all i and V[(R+1)%N].x < V[R].x
            //   V[B].y <= V[i].y for all i and V[(B+1)%N].y > V[B].y
            //   V[T].y >= V[i].y for all i and V[(T+1)%N].y < V[T].y
            double xmin = hullPoints[0].x, xmax = xmin;
            double ymin = hullPoints[0].y, ymax = ymin;
            int    LIndex = 0, RIndex = 0, BIndex = 0, TIndex = 0;

            for (int i = 1; i < numPoints; ++i)
            {
                if (hullPoints[i].x <= xmin)
                {
                    xmin   = hullPoints[i].x;
                    LIndex = i;
                }
                if (hullPoints[i].x >= xmax)
                {
                    xmax   = hullPoints[i].x;
                    RIndex = i;
                }

                if (hullPoints[i].y <= ymin)
                {
                    ymin   = hullPoints[i].y;
                    BIndex = i;
                }
                if (hullPoints[i].y >= ymax)
                {
                    ymax   = hullPoints[i].y;
                    TIndex = i;
                }
            }

            // Apply wrap-around tests to ensure the constraints mentioned above are
            // satisfied.
            if (LIndex == numPointsM1)
            {
                if (hullPoints[0].x <= xmin)
                {
                    xmin   = hullPoints[0].x;
                    LIndex = 0;
                }
            }

            if (RIndex == numPointsM1)
            {
                if (hullPoints[0].x >= xmax)
                {
                    xmax   = hullPoints[0].x;
                    RIndex = 0;
                }
            }

            if (BIndex == numPointsM1)
            {
                if (hullPoints[0].y <= ymin)
                {
                    ymin   = hullPoints[0].y;
                    BIndex = 0;
                }
            }

            if (TIndex == numPointsM1)
            {
                if (hullPoints[0].y >= ymax)
                {
                    ymax   = hullPoints[0].y;
                    TIndex = 0;
                }
            }

            // The dimensions of the axis-aligned box.  The extents store width and
            // height for now.
            mMinBox.Center.x  = ((double)0.5) * (xmin + xmax);
            mMinBox.Center.y  = ((double)0.5) * (ymin + ymax);
            mMinBox.AxisX     = Vector2d.AxisX;
            mMinBox.AxisY     = Vector2d.AxisY;
            mMinBox.Extent[0] = ((double)0.5) * (xmax - xmin);
            mMinBox.Extent[1] = ((double)0.5) * (ymax - ymin);
            double minAreaDiv4 = mMinBox.Extent[0] * mMinBox.Extent[1];

            // The rotating calipers algorithm.
            Vector2d U = Vector2d.AxisX;
            Vector2d V = Vector2d.AxisY;

            bool done = false;

            while (!done)
            {
                // Determine the edge that forms the smallest angle with the current
                // box edges.
                RCFlags flag   = RCFlags.F_NONE;
                double  maxDot = (double)0;

                double dot = U.Dot(edges[BIndex]);
                if (dot > maxDot)
                {
                    maxDot = dot;
                    flag   = RCFlags.F_BOTTOM;
                }

                dot = V.Dot(edges[RIndex]);
                if (dot > maxDot)
                {
                    maxDot = dot;
                    flag   = RCFlags.F_RIGHT;
                }

                dot = -U.Dot(edges[TIndex]);
                if (dot > maxDot)
                {
                    maxDot = dot;
                    flag   = RCFlags.F_TOP;
                }

                dot = -V.Dot(edges[LIndex]);
                if (dot > maxDot)
                {
                    maxDot = dot;
                    flag   = RCFlags.F_LEFT;
                }

                switch (flag)
                {
                case RCFlags.F_BOTTOM:
                    if (visited[BIndex])
                    {
                        done = true;
                    }
                    else
                    {
                        // Compute box axes with E[B] as an edge.
                        U = edges[BIndex];
                        V = -U.Perp;
                        UpdateBox(hullPoints[LIndex], hullPoints[RIndex],
                                  hullPoints[BIndex], hullPoints[TIndex], ref U, ref V,
                                  ref minAreaDiv4);

                        // Mark edge visited and rotate the calipers.
                        visited[BIndex] = true;
                        if (++BIndex == numPoints)
                        {
                            BIndex = 0;
                        }
                    }
                    break;

                case RCFlags.F_RIGHT:
                    if (visited[RIndex])
                    {
                        done = true;
                    }
                    else
                    {
                        // Compute box axes with E[R] as an edge.
                        V = edges[RIndex];
                        U = V.Perp;
                        UpdateBox(hullPoints[LIndex], hullPoints[RIndex],
                                  hullPoints[BIndex], hullPoints[TIndex], ref U, ref V,
                                  ref minAreaDiv4);

                        // Mark edge visited and rotate the calipers.
                        visited[RIndex] = true;
                        if (++RIndex == numPoints)
                        {
                            RIndex = 0;
                        }
                    }
                    break;

                case RCFlags.F_TOP:
                    if (visited[TIndex])
                    {
                        done = true;
                    }
                    else
                    {
                        // Compute box axes with E[T] as an edge.
                        U = -edges[TIndex];
                        V = -U.Perp;
                        UpdateBox(hullPoints[LIndex], hullPoints[RIndex],
                                  hullPoints[BIndex], hullPoints[TIndex], ref U, ref V,
                                  ref minAreaDiv4);

                        // Mark edge visited and rotate the calipers.
                        visited[TIndex] = true;
                        if (++TIndex == numPoints)
                        {
                            TIndex = 0;
                        }
                    }
                    break;

                case RCFlags.F_LEFT:
                    if (visited[LIndex])
                    {
                        done = true;
                    }
                    else
                    {
                        // Compute box axes with E[L] as an edge.
                        V = -edges[LIndex];
                        U = V.Perp;
                        UpdateBox(hullPoints[LIndex], hullPoints[RIndex],
                                  hullPoints[BIndex], hullPoints[TIndex], ref U, ref V,
                                  ref minAreaDiv4);

                        // Mark edge visited and rotate the calipers.
                        visited[LIndex] = true;
                        if (++LIndex == numPoints)
                        {
                            LIndex = 0;
                        }
                    }
                    break;

                case RCFlags.F_NONE:
                    // The polygon is a rectangle.
                    done = true;
                    break;
                }
            }
        }
Exemple #23
0
        // [TODO]
        //    - we could get a better fit to the original curve if we use the ability to have separate
        //      d1 and d2 values. There is a point where d1 > 0 and d2 > 0 where we will get a best-fit.
        //
        //      if d1==0, the first arc degenerates. The second arc degenerates when d2 == 0.
        //      If either d1 or d2 go negative, then we shouldn't use that result.
        //      But it's not clear if we can directly compute the positive-d-range...
        //
        //      It does seem like if we solve the d1=d2 case, then we use [0,2*d1] as the d1 range,
        //      then maybe we are safe. And we can always discard solutions where it is negative...


        // solve biarc fit where the free parameter is automatically set so that
        // d1=d2, which is basically the 'middle' case
        void Fit()
        {
            // get inputs
            Vector2d p1 = Point1;
            Vector2d p2 = Point2;

            Vector2d t1 = Tangent1;
            Vector2d t2 = Tangent2;

            // fit biarc
            Vector2d v       = p2 - p1;
            double   vMagSqr = v.LengthSquared;

            // set d1 equal to d2
            Vector2d t       = t1 + t2;
            double   tMagSqr = t.LengthSquared;

            // original code used 0.0001 here...
            bool equalTangents = MathUtil.EpsilonEqual(tMagSqr, 4.0, Epsilon);
            //var equalTangents = IsEqualEps(tMagSqr, 4.0);

            double vDotT1 = v.Dot(t1);
            bool   perpT1 = MathUtil.EpsilonEqual(vDotT1, 0.0, Epsilon);

            if (equalTangents && perpT1)
            {
                // we have two semicircles
                //Vector2d joint = p1 + 0.5 * v;

                // d1 = d2 = infinity here...
                FitD1 = FitD2 = double.PositiveInfinity;

                // draw arcs
                double   angle   = Math.Atan2(v.y, v.x);
                Vector2d center1 = p1 + 0.25 * v;
                Vector2d center2 = p1 + 0.75 * v;
                double   radius  = Math.Sqrt(vMagSqr) * 0.25;
                double   cross   = v.x * t1.y - v.y * t1.x;

                arc1 = new Arc(center1, radius, angle, angle + Math.PI, (cross < 0));
                arc1 = new Arc(center2, radius, angle, angle + Math.PI, (cross > 0));
            }
            else
            {
                double vDotT = v.Dot(t);

                // [RMS] this was unused in original code...
                //bool perpT1 = MathUtil.EpsilonEqual(vDotT1, 0, epsilon);

                double d1 = 0;
                if (equalTangents)
                {
                    d1 = vMagSqr / (4 * vDotT1);
                }
                else
                {
                    double denominator  = 2.0 - 2.0 * t1.Dot(t2);
                    double discriminant = vDotT * vDotT + denominator * vMagSqr;
                    d1 = (Math.Sqrt(discriminant) - vDotT) / denominator;
                }
                FitD1 = FitD2 = d1;

                Vector2d joint = p1 + p2 + d1 * (t1 - t2);
                joint *= 0.5;

                // construct arcs
                SetArcFromEdge(0, p1, t1, joint, true);
                SetArcFromEdge(1, p2, t2, joint, false);
            }
        }