Example #1
0
        public static bool PolygonContains(this IEnumerable <IPrimitive> primitives, Point point)
        {
            // TODO: this kind of ray casing can fail if the ray and a primitive line are part of the
            // same infinite line or if the ray barely skims the other primitive
            var maxX          = primitives.Select(p => Math.Max(p.StartPoint().X, p.EndPoint().X)).Max();
            var dist          = Math.Abs(maxX - point.X);
            var ray           = new PrimitiveLine(point, new Point(point.X + (dist * 1.1), point.Y, point.Z));
            int intersections = 0;

            foreach (var primitive in primitives)
            {
                intersections += ray.IntersectionPoints(primitive).Count();
            }

            return(intersections % 2 == 1);
        }
Example #2
0
        public static IEnumerable <Point> IntersectionPoints(this PrimitiveLine line, IPrimitive other, bool withinBounds = true)
        {
            IEnumerable <Point> result;

            switch (other.Kind)
            {
            case PrimitiveKind.Line:
                var p = line.IntersectionPoint((PrimitiveLine)other, withinBounds);
                if (p.HasValue)
                {
                    result = new[] { p.Value }
                }
                ;
                else
                {
                    result = new Point[0];
                }
                break;

            case PrimitiveKind.Ellipse:
                result = line.IntersectionPoints((PrimitiveEllipse)other, withinBounds);
                break;

            case PrimitiveKind.Point:
                var point = (PrimitivePoint)other;
                if (line.IsPointOnPrimitive(point.Location, withinBounds))
                {
                    result = new Point[] { point.Location };
                }
                else
                {
                    result = new Point[0];
                }
                break;

            case PrimitiveKind.Text:
                result = Enumerable.Empty <Point>();
                break;

            default:
                Debug.Assert(false, "Unsupported primitive type");
                result = Enumerable.Empty <Point>();
                break;
            }

            return(result);
        }
Example #3
0
        /// <summary>
        /// Using algorithm as described at http://www.ceometric.com/support/examples/v-ellipse-projection-using-rytz-construction.html
        /// </summary>
        public static ProjectedCircle FromConjugateDiameters(Circle originalCircle, Layer originalLayer, Point center, Point majorAxisConjugate, Point minorAxisConjugate)
        {
            // re-map to shorter variables
            var m = center;
            var p = majorAxisConjugate;
            var q = minorAxisConjugate;

            // PM must be longer than QM.  Swap if necessary.
            var pm = p - m;
            var qm = q - m;

            if (pm.LengthSquared > qm.LengthSquared)
            {
                var temp = p;
                p  = q;
                q  = temp;
                pm = p - m;
                qm = q - m;
            }

            // if axes are already orthoganal, no transform is needed
            if (MathHelper.CloseTo(0.0, pm.Dot(qm)))
            {
                return(new ProjectedCircle(originalCircle, originalLayer, m, qm.Length, pm.Length, 0.0));
            }

            // find the plane containing the projected ellipse
            var plane = Plane.From3Points(m, p, q);

            // rotate P by 90 degrees around the normal
            var rotationMatrix = Matrix4.Identity;

            rotationMatrix.RotateAt(new Quaternion(plane.Normal, 90.0), m);
            var rotp = rotationMatrix.Transform(p);

            // the angle between (rotp-M) and (Q-M) should be less than 90 degrees.  mirror if not
            if (Vector.AngleBetween(rotp - m, qm) > 90.0)
            {
                rotp = ((rotp - m) * -1.0) + m;
            }

            // construct the rytz circle
            // the center is the midpoint of the edge from rotp to Q
            // the radius is the distance from M to the center
            // the normal is the normal of the plane
            var rc   = (rotp + q) / 2.0;
            var rytz = new PrimitiveEllipse(rc, (m - rc).Length, plane.Normal);

            // intersect the rytz circle with a line passing through the center and Q
            var intersectingLine   = new PrimitiveLine(rc, q);
            var intersectingPoints = intersectingLine.IntersectionPoints(rytz, false).ToList();

            if (intersectingPoints.Count != 2)
            {
                return(null);
            }
            var da = (q - intersectingPoints[0]).Length;
            var db = (q - intersectingPoints[1]).Length;

            if (da < db)
            {
                // da must be large than db
                var temp = da;
                da = db;
                db = temp;
            }

            // get main axes
            var a = intersectingPoints[0] - m;
            var b = intersectingPoints[1] - m;

            if (b.LengthSquared > a.LengthSquared)
            {
                var temp = a;
                a = b;
                b = temp;
            }

            // return the new ellipse
            return(new ProjectedCircle(originalCircle, originalLayer, m, da, db, a.ToAngle() * -1.0));
        }
Example #4
0
        public static IEnumerable <Point> IntersectionPoints(this PrimitiveEllipse first, PrimitiveEllipse second, bool withinBounds = true)
        {
            var empty = Enumerable.Empty <Point>();
            IEnumerable <Point> results;

            // planes either intersect in a line or are parallel
            var lineVector = first.Normal.Cross(second.Normal);

            if (lineVector.IsZeroVector)
            {
                // parallel or the same plane
                if ((first.Center == second.Center) ||
                    Math.Abs((second.Center - first.Center).Dot(first.Normal)) < MathHelper.Epsilon)
                {
                    // if they share a point or second.Center is on the first plane, they are the same plane
                    // project second back to a unit circle and find intersection points
                    var fromUnit = first.FromUnitCircle;
                    var toUnit   = fromUnit.Inverse();

                    // transform second ellipse to be on the unit circle's plane
                    var           secondCenter   = toUnit.Transform(second.Center);
                    var           secondMajorEnd = toUnit.Transform(second.Center + second.MajorAxis);
                    var           secondMinorEnd = toUnit.Transform(second.Center + (second.Normal.Cross(second.MajorAxis).Normalize() * second.MajorAxis.Length * second.MinorAxisRatio));
                    RoundedDouble a = (secondMajorEnd - secondCenter).Length;
                    RoundedDouble b = (secondMinorEnd - secondCenter).Length;

                    if (a == b)
                    {
                        // if second ellipse is a circle we can absolutely solve for the intersection points
                        // rotate to place the center of the second circle on the x-axis
                        var angle           = ((Vector)secondCenter).ToAngle();
                        var rotation        = Matrix4.RotateAboutZ(angle);
                        var returnTransform = fromUnit * Matrix4.RotateAboutZ(-angle);
                        var newSecondCenter = rotation.Transform(secondCenter);
                        var secondRadius    = a;

                        if (Math.Abs(newSecondCenter.X) > secondRadius + 1.0)
                        {
                            // no points of intersection
                            results = empty;
                        }
                        else
                        {
                            // 2 points of intersection
                            var x = (secondRadius * secondRadius - newSecondCenter.X * newSecondCenter.X - 1.0)
                                    / (-2.0 * newSecondCenter.X);
                            var y = Math.Sqrt(1.0 - x * x);
                            results = new[]
                            {
                                new Point(x, y, 0),
                                new Point(x, -y, 0)
                            };
                        }

                        results = results.Distinct().Select(p => returnTransform.Transform(p));
                    }
                    else
                    {
                        // rotate about the origin to make the major axis align with the x-axis
                        var angle       = (secondMajorEnd - secondCenter).ToAngle();
                        var rotation    = Matrix4.RotateAboutZ(angle);
                        var finalCenter = rotation.Transform(secondCenter);
                        fromUnit = fromUnit * Matrix4.RotateAboutZ(-angle);
                        toUnit   = fromUnit.Inverse();

                        if (a < b)
                        {
                            // rotate to ensure a > b
                            fromUnit    = fromUnit * Matrix4.RotateAboutZ(90);
                            toUnit      = fromUnit.Inverse();
                            finalCenter = Matrix4.RotateAboutZ(-90).Transform(finalCenter);

                            // and swap a and b
                            var temp = a;
                            a = b;
                            b = temp;
                        }

                        RoundedDouble h  = finalCenter.X;
                        RoundedDouble k  = finalCenter.Y;
                        var           h2 = h * h;
                        var           k2 = k * k;
                        var           a2 = a * a;
                        var           b2 = b * b;
                        var           a4 = a2 * a2;
                        var           b4 = b2 * b2;

                        // RoundedDouble is used to ensure all operations are rounded to a certain precision
                        if (Math.Abs(h) < MathHelper.Epsilon)
                        {
                            // ellipse x = 0; wide
                            // find x values
                            var A = a2 - a2 * b2 + a2 * k2 - ((2 * a4 * k2) / (a2 - b2));
                            var B = (2 * a2 * k * Math.Sqrt(b2 * (-a2 + a4 + b2 - a2 * b2 + a2 * k2))) / (a2 - b2);
                            var C = (RoundedDouble)Math.Sqrt(a2 - b2);

                            var x1 = -Math.Sqrt(A + B) / C;
                            var x2 = (RoundedDouble)(-x1);
                            var x3 = -Math.Sqrt(A - B) / C;
                            var x4 = (RoundedDouble)(-x3);

                            // find y values
                            var D = a2 * k;
                            var E = Math.Sqrt(-a2 * b2 + a4 * b2 + b4 - a2 * b4 + a2 * b2 * k2);
                            var F = a2 - b2;

                            var y1 = (D - E) / F;
                            var y2 = (D + E) / F;

                            results = new[] {
                                new Point(x1, y1, 0),
                                new Point(x2, y1, 0),
                                new Point(x3, y2, 0),
                                new Point(x4, y2, 0)
                            };
                        }
                        else if (Math.Abs(k) < MathHelper.Epsilon)
                        {
                            // ellipse y = 0; wide
                            // find x values
                            var A = -b2 * h;
                            var B = Math.Sqrt(a4 - a2 * b2 - a4 * b2 + a2 * b4 + a2 * b2 * h2);
                            var C = a2 - b2;

                            var x1 = (A - B) / C;
                            var x2 = (A + B) / C;

                            // find y values
                            var D = -b2 + a2 * b2 - b2 * h2 - ((2 * b4 * h2) / (a2 - b2));
                            var E = (2 * b2 * h * Math.Sqrt(a2 * (a2 - b2 - a2 * b2 + b4 + b2 * h2))) / (a2 - b2);
                            var F = (RoundedDouble)Math.Sqrt(a2 - b2);

                            var y1 = -Math.Sqrt(D - E) / F;
                            var y2 = (RoundedDouble)(-y1);
                            var y3 = -Math.Sqrt(D + E) / F;
                            var y4 = (RoundedDouble)(-y3);

                            results = new[] {
                                new Point(x1, y1, 0),
                                new Point(x1, y2, 0),
                                new Point(x2, y3, 0),
                                new Point(x2, y4, 0)
                            };
                        }
                        else
                        {
                            // brute-force approximate intersections
                            results = BruteForceEllipseWithUnitCircle(finalCenter, a, b);
                        }

                        results = results
                                  .Where(p => !(double.IsNaN(p.X) || double.IsNaN(p.Y) || double.IsNaN(p.Z)))
                                  .Where(p => !(double.IsInfinity(p.X) || double.IsInfinity(p.Y) || double.IsInfinity(p.Z)))
                                  .Distinct()
                                  .Select(p => fromUnit.Transform(p))
                                  .Select(p => new Point((RoundedDouble)p.X, (RoundedDouble)p.Y, (RoundedDouble)p.Z));
                    }
                }
                else
                {
                    // parallel with no intersections
                    results = empty;
                }
            }
            else
            {
                // intersection was a line
                // find a common point to complete the line then intersect that line with the circles
                double x = 0, y = 0, z = 0;
                var    n = first.Normal;
                var    m = second.Normal;
                var    q = first.Center;
                var    r = second.Center;
                if (Math.Abs(lineVector.X) >= MathHelper.Epsilon)
                {
                    x = 0.0;
                    y = (-m.Z * n.X * q.X - m.Z * n.Y * q.Y - m.Z * n.Z * q.Z + m.X * n.Z * r.X + m.Y * n.Z * r.Y + m.Z * n.Z * r.Z)
                        / (-m.Z * n.Y + m.Y * n.Z);
                    z = (-m.Y * n.X * q.X - m.Y * n.Y * q.Y - m.Y * n.Z * q.Z + m.X * n.Y * r.X + m.Y * n.Y * r.Y + m.Z * n.Y * r.Z)
                        / (-m.Z * n.Y + m.Y * n.Z);
                }
                else if (Math.Abs(lineVector.Y) >= MathHelper.Epsilon)
                {
                    x = (-m.Z * n.X * q.X - m.Z * n.Y * q.Y - m.Z * n.Z * q.Z + m.X * n.Z * r.X + m.Y * n.Z * r.Y + m.Z * n.Z * r.Z)
                        / (-m.Z * n.X + m.X * n.Z);
                    y = 0.0;
                    z = (-m.X * n.X * q.X - m.X * n.Y * q.Y - m.X * n.Z * q.Z + m.X * n.X * r.X + m.Y * n.X * r.Y + m.Z * n.X * r.Z)
                        / (-m.Z * n.X + m.X * n.Z);
                }
                else if (Math.Abs(lineVector.Z) >= MathHelper.Epsilon)
                {
                    x = (-m.Y * n.X * q.X - m.Y * n.Y * q.Y - m.Y * n.Z * q.Z + m.X * n.Y * r.X + m.Y * n.Y * r.Y + m.Z * n.Y * r.Z)
                        / (m.Y * n.X - m.X * n.Y);
                    y = (-m.X * n.X * q.X - m.X * n.Y * q.Y - m.X * n.Z * q.Z + m.X * n.X * r.X + m.Y * n.X * r.Y + m.Z * n.X * r.Z)
                        / (m.Y * n.X - m.X * n.Y);
                    z = 0.0;
                }
                else
                {
                    Debug.Assert(false, "zero-vector shouldn't get here");
                }

                var point              = new Point(x, y, z);
                var other              = point + lineVector;
                var intersectionLine   = new PrimitiveLine(point, other);
                var firstIntersections = intersectionLine.IntersectionPoints(first, false)
                                         .Select(p => new Point((RoundedDouble)p.X, (RoundedDouble)p.Y, (RoundedDouble)p.Z));
                var secondIntersections = intersectionLine.IntersectionPoints(second, false)
                                          .Select(p => new Point((RoundedDouble)p.X, (RoundedDouble)p.Y, (RoundedDouble)p.Z));
                results = firstIntersections
                          .Union(secondIntersections)
                          .Distinct();
            }

            // verify points are in angle bounds
            if (withinBounds)
            {
                var toFirstUnit  = first.FromUnitCircle.Inverse();
                var toSecondUnit = second.FromUnitCircle.Inverse();
                results = from res in results
                          // verify point is within first ellipse's angles
                          let trans1                       = toFirstUnit.Transform(res)
                                                  let ang1 = ((Vector)trans1).ToAngle()
                                                             where first.IsAngleContained(ang1)
                                                             // and same for second
                                                             let trans2                       = toSecondUnit.Transform(res)
                                                                                     let ang2 = ((Vector)trans2).ToAngle()
                                                                                                where second.IsAngleContained(ang2)
                                                                                                select res;
            }

            return(results);
        }