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); }
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); }
/// <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)); }
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); }