/// <summary> /// Finds the points of intersection between a line and a circle. The results are two lambdas along the line, one /// for each point, or NaN if there is no intersection.</summary> public static void LineWithCircle(ref EdgeD line, ref CircleD circle, out double lambda1, out double lambda2) { // The following expressions come up a lot in the solution, so simplify using them. double dx = line.End.X - line.Start.X; double dy = line.End.Y - line.Start.Y; double ax = -line.Start.X + circle.Center.X; double ay = -line.Start.Y + circle.Center.Y; // Solve simultaneously for l: // Eq of a line: x = sx + l * dx // y = sy + l * dy // Eq of a circle: (x - cx)^2 + (y - cy)^2 = r^2 // // Eventually we get a standard quadratic equation in l with the // following coefficients: double a = dx * dx + dy * dy; double b = -2 * (ax * dx + ay * dy); double c = ax * ax + ay * ay - circle.Radius * circle.Radius; // Now just solve the quadratic eqn... double D = b * b - 4 * a * c; if (D < 0) { lambda1 = lambda2 = double.NaN; } else { double sqrtD = Math.Sqrt(D); lambda1 = (-b + sqrtD) / (2 * a); lambda2 = (-b - sqrtD) / (2 * a); } }
private void AssertRayWithCircle(double frX, double frY, double toX, double toY, double cX, double cY, double cR, double expL1, double expL2) { EdgeD ray = new EdgeD(frX, frY, toX, toY); CircleD cir = new CircleD(cX, cY, cR); double l1, l2; Intersect.RayWithCircle(ref ray, ref cir, out l1, out l2); Assert.AreEqual(expL1, l1, 0.001); Assert.AreEqual(expL2, l2, 0.001); }
private void AssertLineWithCircle(double frX, double frY, double toX, double toY, double cX, double cY, double cR, double expL1, double expL2) { EdgeD lin = new EdgeD(frX, frY, toX, toY); CircleD cir = new CircleD(cX, cY, cR); double l1, l2; Intersect.LineWithCircle(ref lin, ref cir, out l1, out l2); Assert.AreEqual(MinTO(expL1, expL2), MinTO(l1, l2), 0.001); Assert.AreEqual(MaxTO(expL1, expL2), MaxTO(l1, l2), 0.001); }
/// <summary> /// Given this circle and another circle, tries to find a third and fourth circle with a given target radius such /// that the new circles are both tangent to the first two.</summary> /// <param name="other"> /// The other circle.</param> /// <param name="targetRadius"> /// Target radius for output circles.</param> /// <returns> /// The two output circles if they exist. If the input circles are further apart than twice the target radius, the /// desires circles do not exist and null is returned.</returns> public (CircleD, CircleD)? FindTangentCircles(CircleD other, double targetRadius) { double a = ((Center.X - other.Center.X) * (Center.X - other.Center.X)) / ((other.Center.Y - Center.Y) * (other.Center.Y - Center.Y)) + 1; double t = Center.Y - (Radius * Radius - other.Radius * other.Radius + other.Center.X * other.Center.X - Center.X * Center.X + other.Center.Y * other.Center.Y - Center.Y * Center.Y + 2 * targetRadius * (Radius - other.Radius)) / (other.Center.Y - Center.Y) / 2; double b = -2 * Center.X - 2 * t * (Center.X - other.Center.X) / (other.Center.Y - Center.Y); double c = Center.X * Center.X - (Radius + targetRadius) * (Radius + targetRadius) + t * t; double q = b * b - 4 * a * c; // At this point, Q < 0 means the circles are too far apart, so no solution if (q < 0) { return(null); } double s = Math.Sqrt(q); double xa = (-b + s) / (2 * a); double xb = (-b - s) / (2 * a); // These Sqrts should succeed, i.e. their parameter should never be < 0 double ya = Math.Sqrt(-other.Center.X * other.Center.X - xa * xa + targetRadius * targetRadius + 2 * other.Center.X * xa + other.Radius * other.Radius + 2 * other.Radius * targetRadius); double yb = Math.Sqrt(-Center.X * Center.X - xb * xb + targetRadius * targetRadius + 2 * Center.X * xb + Radius * Radius + 2 * Radius * targetRadius); if (Math.Sign(Center.X - other.Center.X) != Math.Sign(Center.Y - other.Center.Y)) { ya += other.Center.Y; yb = Center.Y - yb; } else { ya = other.Center.Y - ya; yb += Center.Y; } return(new CircleD(xa, ya, targetRadius), new CircleD(xb, yb, targetRadius)); }
/// <summary> /// Finds the points of intersection between a ray and a circle. The resulting lambdas along the ray are sorted in /// ascending order, so the "first" intersection is always in lambda1 (if any). Lambda may be NaN if there is no /// intersection (or no "second" intersection).</summary> public static void RayWithCircle(ref EdgeD ray, ref CircleD circle, out double lambda1, out double lambda2) { LineWithCircle(ref ray, ref circle, out lambda1, out lambda2); // Sort the two values in ascending order, with NaN last, // while resetting negative values to NaNs if (lambda1 < 0) { lambda1 = double.NaN; } if (lambda2 < 0) { lambda2 = double.NaN; } if (lambda1 > lambda2 || double.IsNaN(lambda1)) { double temp = lambda1; lambda1 = lambda2; lambda2 = temp; } }
/// <summary> /// Given this circle and another circle, tries to find a third and fourth circle with a given target radius such /// that the new circles are both tangent to the first two.</summary> /// <param name="other"> /// The other circle.</param> /// <param name="targetRadius"> /// Target radius for output circles.</param> /// <returns> /// The two output circles if they exist. If the input circles are further apart than twice the target radius, the /// desires circles do not exist and null is returned.</returns> public Tuple<CircleD, CircleD> FindTangentCircles(CircleD other, double targetRadius) { double a = ((Center.X - other.Center.X) * (Center.X - other.Center.X)) / ((other.Center.Y - Center.Y) * (other.Center.Y - Center.Y)) + 1; double t = Center.Y - (Radius * Radius - other.Radius * other.Radius + other.Center.X * other.Center.X - Center.X * Center.X + other.Center.Y * other.Center.Y - Center.Y * Center.Y + 2 * targetRadius * (Radius - other.Radius)) / (other.Center.Y - Center.Y) / 2; double b = -2 * Center.X - 2 * t * (Center.X - other.Center.X) / (other.Center.Y - Center.Y); double c = Center.X * Center.X - (Radius + targetRadius) * (Radius + targetRadius) + t * t; double q = b * b - 4 * a * c; // At this point, Q < 0 means the circles are too far apart, so no solution if (q < 0) return null; double s = Math.Sqrt(q); double xa = (-b + s) / (2 * a); double xb = (-b - s) / (2 * a); // These Sqrts should succeed, i.e. their parameter should never be < 0 double ya = Math.Sqrt(-other.Center.X * other.Center.X - xa * xa + targetRadius * targetRadius + 2 * other.Center.X * xa + other.Radius * other.Radius + 2 * other.Radius * targetRadius); double yb = Math.Sqrt(-Center.X * Center.X - xb * xb + targetRadius * targetRadius + 2 * Center.X * xb + Radius * Radius + 2 * Radius * targetRadius); if (Math.Sign(Center.X - other.Center.X) != Math.Sign(Center.Y - other.Center.Y)) { ya += other.Center.Y; yb = Center.Y - yb; } else { ya = other.Center.Y - ya; yb += Center.Y; } return Tuple.Create(new CircleD(xa, ya, targetRadius), new CircleD(xb, yb, targetRadius)); }
/// <summary> /// Finds the points of intersection between a line and a circle. The results are two lambdas along the line, one /// for each point, or NaN if there is no intersection.</summary> public static void LineWithCircle(EdgeD line, CircleD circle, out double lambda1, out double lambda2) { LineWithCircle(ref line, ref circle, out lambda1, out lambda2); }
/// <summary> /// Returns the smallest circle that encloses all the given points. If 1 point is given, a circle of radius 0 is /// returned.</summary> /// <param name="points"> /// The set of points to circumscribe.</param> /// <returns> /// The circumscribed circle.</returns> /// <remarks> /// <list type="bullet"> /// <item><description> /// Runs in expected O(n) time, randomized.</description></item></list></remarks> /// <exception cref="InvalidOperationException"> /// The input collection contained zero points.</exception> public static CircleD GetCircumscribedCircle(IList <PointD> points) { // Clone list to preserve the caller's data List <PointD> shuffled = new List <PointD>(points).Shuffle(); // Progressively add points to circle or recompute circle // Initially: No boundary points known CircleD?circ = null; for (int i = 0; i < shuffled.Count; i++) { PointD p = shuffled[i]; if (circ == null || !circ.Value.Contains(p)) { circ = MakeCircleOnePoint(shuffled.GetRange(0, i + 1), p); } } if (circ == null) { throw new InvalidOperationException("The input collection did not contain any points."); } return(circ.Value); // One boundary point known CircleD MakeCircleOnePoint(List <PointD> pts, PointD p) { CircleD c = new CircleD(p, 0); for (int i = 0; i < pts.Count; i++) { PointD q = pts[i]; if (!c.Contains(q)) { if (c.Radius == 0) { c = MakeDiameter(p, q); } else { c = MakeCircleTwoPoints(pts.GetRange(0, i + 1), p, q); } } } return(c); } // Two boundary pts known CircleD MakeCircleTwoPoints(List <PointD> pts, PointD p, PointD q) { CircleD crc = MakeDiameter(p, q); CircleD left = new CircleD(new PointD(0, 0), -1); CircleD right = new CircleD(new PointD(0, 0), -1); // For each point not in the two-point circle PointD pq = q - p; foreach (PointD r in pts) { if (crc.Contains(r)) { continue; } // Form a circumcircle and classify it on left or right side double cross = Cross(pq, r - p); CircleD c = MakeCircumcircle(p, q, r); if (c.Radius < 0) { continue; } else if (cross > 0 && (left.Radius < 0 || Cross(pq, c.Center - p) > Cross(pq, left.Center - p))) { left = c; } else if (cross < 0 && (right.Radius < 0 || Cross(pq, c.Center - p) < Cross(pq, right.Center - p))) { right = c; } } // Select which circle to return if (left.Radius < 0 && right.Radius < 0) { return(crc); } else if (left.Radius < 0) { return(right); } else if (right.Radius < 0) { return(left); } else { return(left.Radius <= right.Radius ? left : right); } } CircleD MakeDiameter(PointD a, PointD b) { PointD c = new PointD((a.X + b.X) / 2, (a.Y + b.Y) / 2); return(new CircleD(c, Math.Max(c.Distance(a), c.Distance(b)))); } CircleD MakeCircumcircle(PointD a, PointD b, PointD c) { // Mathematical algorithm from Wikipedia: Circumscribed circle double ox = (Math.Min(Math.Min(a.X, b.X), c.X) + Math.Max(Math.Min(a.X, b.X), c.X)) / 2; double oy = (Math.Min(Math.Min(a.Y, b.Y), c.Y) + Math.Max(Math.Min(a.Y, b.Y), c.Y)) / 2; double ax = a.X - ox, ay = a.Y - oy; double bx = b.X - ox, by = b.Y - oy; double cx = c.X - ox, cy = c.Y - oy; double d = (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) * 2; if (d == 0) { return(new CircleD(new PointD(0, 0), -1)); } double x = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d; double y = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d; PointD p = new PointD(ox + x, oy + y); double r = Math.Max(Math.Max(p.Distance(a), p.Distance(b)), p.Distance(c)); return(new CircleD(p, r)); } // Signed area / determinant thing double Cross(PointD p, PointD q) { return(p.X * q.Y - p.Y * q.X); } }
/// <summary> /// Finds the points of intersection between a ray and a circle. The resulting lambdas along the ray are sorted in /// ascending order, so the "first" intersection is always in lambda1 (if any). Lambda may be NaN if there is no /// intersection (or no "second" intersection).</summary> public static void RayWithCircle(ref EdgeD ray, ref CircleD circle, out double lambda1, out double lambda2) { LineWithCircle(ref ray, ref circle, out lambda1, out lambda2); // Sort the two values in ascending order, with NaN last, // while resetting negative values to NaNs if (lambda1 < 0) lambda1 = double.NaN; if (lambda2 < 0) lambda2 = double.NaN; if (lambda1 > lambda2 || double.IsNaN(lambda1)) { double temp = lambda1; lambda1 = lambda2; lambda2 = temp; } }