/// <summary> /// Checks whether a location falls in the sector lying between the BC and EC /// of a circular arc. The only condition is that the location has an appropriate /// bearing with respect to the bearings of the BC & EC (i.e. the location does not /// necessarily lie ON the arc). /// </summary> /// <param name="pos">The position to check</param> /// <param name="lintol">Linear tolerance, in meters on the ground (locations that are /// beyond the BC or EC by up to this much will also be regarded as "in-sector").</param> /// <returns>True if position falls in the sector defined by this arc</returns> public static bool IsInSector(ICircularArcGeometry g, IPosition pos, double lintol) { // If the arc is a complete circle, it's ALWAYS in sector. if (IsCircle(g)) { return(true); } // Express the curve's start & end points in local system, // ordered clockwise. IPointGeometry start = g.First; IPointGeometry end = g.Second; // Get the centre of the circular arc. IPosition centre = g.Circle.Center; // If we have a linear tolerance, figure out the angular equivalent. if (lintol > MathConstants.TINY) { double angtol = lintol / g.Circle.Radius; return(BasicGeom.IsInSector(pos, centre, start, end, angtol)); } else { return(BasicGeom.IsInSector(pos, centre, start, end, 0.0)); } }
/// <summary> /// Gets the point on this line that is closest to a specified position. /// </summary> /// <param name="p">The position to search from.</param> /// <param name="tol">Maximum distance from line to the search position</param> /// <returns>The closest position (null if the line is further away than the specified /// max distance)</returns> internal override IPosition GetClosest(IPointGeometry p, ILength tol) { // Get the perpendicular point (or closest end) IPointGeometry s = Start; IPointGeometry e = End; double xp, yp; BasicGeom.GetPerpendicular(p.X, p.Y, s.X, s.Y, e.X, e.Y, out xp, out yp); // Ignore if point is too far away double t = tol.Meters; double dx = p.X - xp; if (dx > t) { return(null); } double dy = p.Y - yp; if (dy > t) { return(null); } double dsq = (dx * dx + dy * dy); if (dsq > t * t) { return(null); } return(new Position(xp, yp)); }
/// <summary> /// Returns the offset distance with respect to a reference direction, in meters /// on the ground. Offsets to the left are returned as a negated value, while /// offsets to the right are positive values. /// </summary> /// <param name="dir">The direction that the offset was observed with respect to.</param> /// <returns>The signed offset distance, in meters on the ground</returns> internal override double GetMetric(Direction dir) { // Return offset of zero if there is no offset point. if (m_Point == null) { return(0.0); } // Get the origin of the direction & it's bearing. This gives // us the info we need to express the equation of the line // in parametric form. PointFeature from = dir.From; double x = from.X; double y = from.Y; double bearing = dir.Bearing.Radians; // Get the position of the offset point. double xoff = m_Point.X; double yoff = m_Point.Y; // Get the signed perpendicular distance from the offset // point to the reference direction. return(BasicGeom.SignedDistance(x, y, bearing, xoff, yoff)); }
/// <summary> /// Returns the position of the intersection that is closest to a specific position. /// </summary> /// <param name="posn">The position to search for.</param> /// <param name="xsect">The closest intersection.</param> /// <param name="tolsq">The smallest allowable distance (squared) between the /// search position and the closest intersection (default=0.0).</param> /// <returns>TRUE if the position was found.</returns> internal bool GetClosest(IPosition posn, out IPosition xsect, double tolsq) { xsect = null; // Go through each intersection, looking for the closest // intersection that is not TOO close. double mindsq = Double.MaxValue; double dsq; foreach (IntersectionData d in m_Data) { dsq = BasicGeom.DistanceSquared(posn, d.P1); if (dsq < mindsq && dsq > tolsq) { mindsq = dsq; xsect = d.P1; } // If the intersection is a graze, check the 2nd // intersection too. if (d.IsGraze) { dsq = BasicGeom.DistanceSquared(posn, d.P2); if (dsq < mindsq && dsq > tolsq) { mindsq = dsq; xsect = d.P2; } } } return(xsect != null); }
/// <summary> /// Generates an approximation of a circular arc. /// </summary> /// <param name="tol">The maximum chord-to-circumference distance.</param> /// <returns></returns> public static IPointGeometry[] GetApproximation(ICircularArcGeometry g, ILength tol) { // Get info about the circle the curve lies on. IPosition center = g.Circle.Center; double radius = g.Circle.Radius; // Determine the change in bearing which will satisfy the specified tolerance // (if no tolerance has been specified, arbitrarily use a tolerance of 1mm on the ground). double tolm = (tol.Meters > Double.Epsilon ? tol.Meters : 0.001); double dbear = Math.Acos((radius - tolm) / radius); IPointGeometry start = g.BC; IPointGeometry end = g.EC; bool iscw = g.IsClockwise; // Get the total angle subtended by the curve. Turn reft = new Turn(center, start); double totang = reft.GetAngleInRadians(end); // clockwise if (!iscw) { totang = MathConstants.PIMUL2 - totang; } // Figure out how many positions we'll generate int nv = (int)(totang / dbear); // truncate Debug.Assert(nv >= 0); // Handle special case of very short arc. if (nv == 0) { return new IPointGeometry[] { start, end } } ; // Sign the delta-bearing the right way. if (!iscw) { dbear = -dbear; } // Get the initial bearing to the first position along the curve. double curbear = reft.BearingInRadians + dbear; // Append positions along the length of the curve. List <IPointGeometry> result = new List <IPointGeometry>(nv); result.Add(start); for (int i = 0; i < nv; i++, curbear += dbear) { IPosition p = BasicGeom.Polar(center, curbear, radius); result.Add(PositionGeometry.Create(p)); } result.Add(end); return(result.ToArray()); }
public static ILength GetDistance(ILineSegmentGeometry g, IPosition point) { double xp, yp; BasicGeom.GetPerpendicular(point.X, point.Y, g.Start.X, g.Start.Y, g.End.X, g.End.Y, out xp, out yp); double d = BasicGeom.Distance(new Position(xp, yp), point); return(new Length(d)); }
/// <summary> /// Checks if a position is coincident with a line segment /// </summary> /// <param name="p">The position to test</param> /// <param name="start">The start of the segment.</param> /// <param name="end">The end of the segment.</param> /// <param name="tolsq">The tolerance (squared) to use. Default is XYTOLSQ.</param> /// <returns>True if the test position lies somewhere along the segment.</returns> public static bool IsCoincidentWith(IPointGeometry p, IPointGeometry start, IPointGeometry end, double tolsq) { // Check whether there is exact coincidence at either end. if (p.IsCoincident(start) || p.IsCoincident(end)) { return(true); } // Get the distance squared of a perpendicular dropped from // the test position to the segment (or the closest end if the // perpendicular does not fall ON the segment). return(BasicGeom.DistanceSquared(p.X, p.Y, start.X, start.Y, end.X, end.Y) < tolsq); }
private PointGeometry[] CheckMultiSegmentEnds(PointGeometry[] pts) { if (pts.Length <= 2) { return(pts); } //double tol = (Constants.XYRES * Constants.XYRES); double tol = (0.001 * 0.001); PointGeometry[] res = pts; bool doCheck = true; while (doCheck && res.Length > 2) { doCheck = false; // If the start position coincides with the second segment, strip out // the second position. if (BasicGeom.DistanceSquared(res[0].X, res[0].Y, res[1].X, res[1].Y, res[2].X, res[2].Y) < tol) { PointGeometry[] tmp = new PointGeometry[res.Length - 1]; tmp[0] = res[0]; Array.Copy(res, 2, tmp, 1, res.Length - 2); res = tmp; doCheck = true; } } // If the end position coincides with the second last segment, strip out // the second last position. doCheck = true; while (doCheck && res.Length > 2) { doCheck = false; int last = res.Length - 1; if (BasicGeom.DistanceSquared(res[last].X, res[last].Y, res[last - 1].X, res[last - 1].Y, res[last - 2].X, res[last - 2].Y) < tol) { PointGeometry[] tmp = new PointGeometry[res.Length - 1]; Array.Copy(res, 0, tmp, 0, res.Length - 2); tmp[tmp.Length - 1] = res[last]; res = tmp; doCheck = true; } } return(res); }
static double MinDistanceSquared(ICircularArcGeometry g, IPosition p) { // If the position lies in the arc sector, the minimum distance // is given by the distance to the circle. Otherwise the minimum // distance is the distance to the closest end of the arc. if (IsInSector(g, p, 0.0)) { double dist = BasicGeom.Distance(p, g.Circle.Center); double radius = g.Circle.Radius; return((dist - radius) * (dist - radius)); } else { double d1 = BasicGeom.DistanceSquared(p, g.BC); double d2 = BasicGeom.DistanceSquared(p, g.EC); return(Math.Min(d1, d2)); } }
/// <summary> /// Gets geometric info for this geometry. For use during the formation /// of <c>Polygon</c> objects. /// </summary> /// <param name="window">The window of the geometry</param> /// <param name="area">The area (in square meters) between the geometry and the Y-axis.</param> /// <param name="length">The length of the geometry (in meters on the (projected) ground).</param> internal override void GetGeometry(out IWindow win, out double area, out double length) { IPosition start = Start; IPosition end = End; // Define the window. win = new Window(start, end); // Define line length length = BasicGeom.Distance(start, end); // Define area to the left of the line. // Uses the mid-X of the segment to get the area left (signed). // If the line is directed up the way, it contributes a negative // area. If directed down, it contributes a positive area. So, // if flat, it contributes nothing. double dy = start.Y - end.Y; area = dy * 0.5 * (start.X + end.X); }
public static bool GetPosition(IPosition start, IPosition end, double d, out IPosition result) { const double TOL = 0.000002; // Check for distance that is real close to the start (less // than 1 micron or so, since that's the smallest number we // can store). if (d < TOL) { result = start; return(d >= 0.0); } // Get length of the line double len = BasicGeom.Distance(start, end); // If the required distance is within the limiting tolerance // of the end of the line (or beyond it), return the end. if (d > len || (len - d) < TOL) { result = end; return(d <= (len + TOL)); } // How far up the line do we need to go? double ratio = d / len; // Figure out the position double x1 = start.X; double y1 = start.Y; double x2 = end.X; double y2 = end.Y; double dx = x2 - x1; double dy = y2 - y1; result = new Position(x1 + ratio * dx, y1 + ratio * dy); return(true); }
/// <summary> /// Gets the position that is a specific distance from the start of a circular arc. /// </summary> /// <param name="g">The geometry for the circular arc</param> /// <param name="distance">The distance from the start of the arc.</param> /// <param name="result">The position found</param> /// <returns>True if the distance is somewhere ON the arc. False if the distance /// was less than zero, or more than the arc length (in that case, the position /// found corresponds to the corresponding terminal point).</returns> public static bool GetPosition(ICircularArcGeometry g, ILength distance, out IPosition result) { // Allow 1 micron tolerance const double TOL = 0.000001; // Check for invalid distances. double d = distance.Meters; if (d < 0.0) { result = g.BC; return(false); } double clen = g.Length.Meters; // Arc length if (d > (clen + TOL)) { result = g.EC; return(false); } // Check for limiting values (if you don't do this, minute // roundoff at the BC & EC can lead to spurious locations). // (although it's possible to use TINY here, use 1 micron // instead, since we can't represent position any better // than that). if (d < TOL) { result = g.BC; return(true); } if (Math.Abs(d - clen) < TOL) { result = g.EC; return(true); } // Get the bearing of the BC ICircleGeometry circle = g.Circle; IPosition c = circle.Center; double radius = circle.Radius; double bearing = BasicGeom.BearingInRadians(c, g.BC); // Add the angle that subtends the required distance (or // subtract if the curve goes anti-clockwise). if (g.IsClockwise) { bearing += (d / radius); } else { bearing -= (d / radius); } // Figure out the required point from the new bearing. result = BasicGeom.Polar(c, bearing, radius); return(true); }
public ILength Distance(IPosition point) { return(new Length(BasicGeom.Distance(this, point))); }
/// <summary> /// Obtains annotation for this line. /// </summary> /// <param name="dist">The observed distance (if any).</param> /// <param name="drawObserved">Draw observed distance? Specify <c>false</c> for /// actual distance.</param> /// <returns>The annotation (null if it cannot be obtained)</returns> Annotation GetAnnotation(Distance dist, bool drawObserved) { // @devnote This function may not be that hot for curves that // are complete circles. At the moment though, I can't see why // we'd be drawing a distance alongside a circle. // Get the length of the arc double len = this.Length.Meters; // Get the string to output. string distr = GetDistance(len, dist, drawObserved); if (distr == null) { return(null); } // Get the mid-point of this arc. IPosition mid; this.GetPosition(new Length(len * 0.5), out mid); // Get the bearing from the center to the midpoint. IPosition center = m_Circle.Center; double bearing = BasicGeom.BearingInRadians(center, mid); // Get the height of the text, in meters on the ground. double grheight = EditingController.Current.LineAnnotationStyle.Height; // We will offset by 20% of the height (give a bit of space // between the text and the line). //double offset = grheight * 0.2; // ...looks fine with no offset (there is a space, but it's for descenders // that aren't there since we're dealing just with numbers). double offset = 0.0; // Get the rotation of the text. double rotation = bearing - MathConstants.PI; // If the midpoint is above the circle centre, we get upside-down // text so rotate it the other way. If we don't switch the // rotation, we need to make an extra shift, because the text // is aligned along its baseline. // This depends on whether the annotation is on the default // side or not. // Not sure about the following... (c.f. revised handling in SegmentGeometry) /* * if (mid.Y > center.Y) * { * rotation += MathConstants.PI; * if (isFlipped) * offset = -0.9 * grheight; * } * else * { * if (isFlipped) * offset = -offset; * else * offset = 0.9 * grheight; // 1.0 * grheight is too much * } */ // ...try this instead if (mid.Y > center.Y) { rotation += MathConstants.PI; } else { offset = 1.3 * grheight; // push the text to the outer edge of the arc } if (dist != null && dist.IsAnnotationFlipped) { rotation += MathConstants.PI; // and may need to adjust offset... } // Project to the offset point. IPosition p = Geom.Polar(center, bearing, m_Circle.Radius + offset); Annotation result = new Annotation(distr, p, grheight, rotation); result.FontStyle = FontStyle.Italic; return(result); }
/// <summary> /// Draws a text string (annotation) /// </summary> /// <param name="display">The display to draw to</param> /// <param name="text">The item of text</param> public void Render(ISpatialDisplay display, IString text) { // Draw the outline if it's too small Font f = text.CreateFont(display); IPosition[] outline = text.Outline; if (outline == null) { // This is a bit of a hack that covers the Backsight.Editor.Annotation class... if (f == null) { return; } // Note that the order you apply the transforms is significant... IPointGeometry pg = text.Position; PointF p = CreatePoint(display, pg); display.Graphics.TranslateTransform(p.X, p.Y); double rotation = text.Rotation.Degrees; display.Graphics.RotateTransform((float)rotation); StringFormat sf = text.Format; if (sf == null) { sf = StringFormat.GenericTypographic; } string s = text.Text; display.Graphics.DrawString(s, f, Brush, 0, 0, sf); display.Graphics.ResetTransform(); // TEST -- draw rotated 180 to see if that's all we need to do flip... looks good /* * display.Graphics.TranslateTransform(p.X, p.Y); * display.Graphics.RotateTransform((float)rotation+180); * display.Graphics.DrawString(s, f, Brush, 0, 0, sf); * display.Graphics.ResetTransform(); */ } else { if (f == null) { Render(display, outline); } else { string s = text.Text; // Note that the order you apply the transforms is significant... PointF p = CreatePoint(display, outline[0]); display.Graphics.TranslateTransform(p.X, p.Y); double rotation = text.Rotation.Degrees; display.Graphics.RotateTransform((float)rotation); Size size = TextRenderer.MeasureText(s, f); double groundWidth = BasicGeom.Distance(outline[0], outline[1]); float xScale = display.LengthToDisplay(groundWidth) / (float)size.Width; float yScale = f.Size / (float)size.Height; // ScaleTransform doesn't like values of 0 (single character names lead to outline with no width, // should really see if proper character width can be determined in that case). if (xScale < Single.Epsilon) { xScale = yScale; } display.Graphics.ScaleTransform(xScale, yScale); // I tried StringFormat.GenericDefault, but that seems to leave too much // leading space. display.Graphics.DrawString(s, f, Brush, 0, 0, StringFormat.GenericTypographic); display.Graphics.ResetTransform(); } } }
/// <summary> /// The shortest distance between this object and the specified position. /// </summary> /// <param name="point">The position of interest</param> /// <returns> /// The shortest distance between the specified position and this object /// </returns> public ILength Distance(IPosition point) { double dsq = BasicGeom.MinDistanceSquared(this.PositionArray, point); return(new Length(Math.Sqrt(dsq))); }
public static ILength GetLength(ILineSegmentGeometry g) { double d = BasicGeom.Distance(g.Start, g.End); return(new Length(d)); }
/// <summary> /// Checks whether a position is coincident with a line segment, using a tolerance that's /// consistent with the resolution of data. /// </summary> /// <param name="testX">The position to test</param> /// <param name="testY"></param> /// <param name="xs">Start of line segment.</param> /// <param name="ys"></param> /// <param name="xe">End of line segment.</param> /// <param name="ye"></param> /// <param name="tsq">The tolerance (squared) to use for checking whether the test point is /// coincident with the segment.</param> /// <returns>True if the distance from the test position to the line segment is less /// than the specified tolerance.</returns> private static bool IsCoincident(double testX, double testY, double xs, double ys, double xe, double ye, double tsq) { return(BasicGeom.DistanceSquared(testX, testY, xs, ys, xe, ye) < tsq); }
public static ILength GetDistance(IMultiSegmentGeometry g, IPosition p) { double dsq = BasicGeom.MinDistanceSquared(g.Data, p); return(new Length(Math.Sqrt(dsq))); }
public static double GetStartBearingInRadians(ICircularArcGeometry g) { return(BasicGeom.BearingInRadians(g.Circle.Center, g.First)); }
/// <summary> /// Obtains the geometry for spans along this leg. /// </summary> /// <param name="bc">The position for the start of the leg. /// <param name="bcBearing">The bearing on entry into the leg.</param> /// <param name="sfac">Scale factor to apply to distances.</param> /// <param name="spans">Information for the spans coinciding with this leg.</param> /// <returns>The sections along this leg</returns> internal override ILineGeometry[] GetSpanSections(IPosition bc, double bcBearing, double sfac, SpanInfo[] spans) { // Can't do anything if the leg radius isn't defined if (m_Metrics.ObservedRadius == null) { throw new InvalidOperationException("Cannot create sections for circular leg with undefined radius"); } var result = new ILineGeometry[spans.Length]; // Use supplied stuff to derive info on the center and EC. IPosition center; IPosition ec; double bearingToBC; double ecBearing; GetPositions(bc, bcBearing, sfac, out center, out bearingToBC, out ec, out ecBearing); // Define the underlying circle ICircleGeometry circle = new CircleGeometry(PointGeometry.Create(center), BasicGeom.Distance(center, bc)); // Handle case where the leg is a cul-de-sac with no observed spans if (spans.Length == 1 && spans[0].ObservedDistance == null) { result[0] = new CircularArcGeometry(circle, bc, ec, m_Metrics.IsClockwise); return(result); } /// Initialize scaling factor for distances in cul-de-sacs (ratio of the length calculated from /// the CA & Radius, versus the observed distances that were actually specified). For curves that /// are not cul-de-sacs, this will be 1.0 double culFactor = 1.0; if (m_Metrics.IsCulDeSac) { double obsv = PrimaryFace.GetTotal(); if (obsv > MathConstants.TINY) { culFactor = Length.Meters / obsv; } } IPosition sPos = bc; IPosition ePos = null; bool isClockwise = m_Metrics.IsClockwise; double radius = RadiusInMeters; double edist = 0.0; for (int i = 0; i < result.Length; i++, sPos = ePos) { // Add on the unscaled distance edist += spans[i].ObservedDistance.Meters; // Get the angle subtended at the center of the circle. We use // unscaled values here, since the scale factors would cancel out. // However, we DO apply any cul-de-sac scaling factor, to account // for the fact that the distance may be inconsistent with the // curve length derived from the CA and radius. For example, it // is possible that the calculated curve length=200, although the // total of the observed spans is somehow only 100. In that case, // if the supplied distance is 50, we actually want to use a // value of 50 * (200/100) = 100. double angle = (edist * culFactor) / radius; // Get the bearing of the point with respect to the center of the circle. double bearing; if (isClockwise) { bearing = bearingToBC + angle; } else { bearing = bearingToBC - angle; } // Calculate the position using the scaled radius. ePos = Geom.Polar(center, bearing, radius * sfac); result[i] = new CircularArcGeometry(circle, sPos, ePos, isClockwise); } return(result); }
private ArcFeature ImportArc(Ntx.Line line, Operation creator, ILength tol) { Debug.Assert(line.IsCurve); IEntity what = GetEntityType(line, SpatialType.Line); // Get positions defining the arc PointGeometry[] pts = GetPositions(line); // Ignore zero-length lines if (HasZeroLength(pts)) { return(null); } // Add a point at the center of the circle Ntx.Position pos = line.Center; PointGeometry pc = new PointGeometry(pos.Easting, pos.Northing); PointFeature center = EnsurePointExists(pc, tol, creator); // Calculate exact positions for the arc endpoints double radius = line.Radius; ICircleGeometry cg = new CircleGeometry(pc, radius); IPosition bc = CircleGeometry.GetClosestPosition(cg, pts[0]); IPosition ec = CircleGeometry.GetClosestPosition(cg, pts[pts.Length - 1]); // Round off to nearest micron PointGeometry bcg = PointGeometry.Create(bc); PointGeometry ecg = PointGeometry.Create(ec); // Ensure point features exist at both ends of the line. PointFeature ps = GetArcEndPoint(bcg, tol, creator); PointFeature pe = GetArcEndPoint(ecg, tol, creator); // Try to find a circle that's already been added by this import. Circle c = EnsureCircleExists(center, radius, tol, creator); // Determine which way the arc is directed bool iscw = LineStringGeometry.IsClockwise(pts, center); InternalIdValue id = CadastralMapModel.Current.WorkingSession.AllocateNextId(); ArcFeature arc = new ArcFeature(creator, id, what, c, ps, pe, iscw); // The toological status of the incoming arc may override the status that the // constructor derived from the entity type arc.SetTopology(line.IsTopologicalArc); #if DEBUG // Confirm the NTX data was valid (ensure it's consistent with what we've imported)... double readRad = c.Radius; double calcRad = BasicGeom.Distance(c.Center, ps); Debug.Assert(Math.Abs(readRad - calcRad) < tol.Meters); foreach (IPointGeometry pg in pts) { ILength check = arc.Geometry.Distance(pg); Debug.Assert(check.Meters < tol.Meters); } #endif return(arc); }
/// <summary> /// Creates a new <c>CircularArcGeometry</c> where the radius of the circle is defined as /// distance from the BC to the supplied circle center. /// </summary> /// <param name="center">The center of the circle</param> /// <param name="bc">The start of the arc (the circle passes through this point)</param> /// <param name="ec">The end of the arc (assumed to coincide with the circle)</param> /// <param name="isClockwise">Is the arc directed clockwise?</param> public CircularArcGeometry(IPointGeometry center, IPosition bc, IPosition ec, bool isClockwise) : this(new CircleGeometry(center, BasicGeom.Distance(center, bc)), bc, ec, isClockwise) { }
public static ILength Distance(ICircleGeometry g, IPosition p) { double d = BasicGeom.Distance(g.Center, p); return(new Length(Math.Abs(d - g.Radius))); }
/// <summary> /// Intersects a line segment with a circle. /// </summary> /// <param name="centre">Position of the circle's centre.</param> /// <param name="radius">Radius of the circle.</param> /// <param name="start">Start of segment.</param> /// <param name="end">End of segment.</param> /// <param name="x1">First intersection (if any).</param> /// <param name="x2">Second intersection (if any).</param> /// <param name="istangent">Is segment a tangent to the circle? This can be true only if /// ONE intersection is returned.</param> /// <param name="online">TRUE if the intersection must lie ON the segment.</param> /// <returns>The number of intersections found (0, 1, or 2).</returns> internal static uint IntersectCircle(IPosition centre , double radius , IPosition start , IPosition end , out IPosition ox1 , out IPosition ox2 , out bool istangent , bool online) { // Initialize the return info. Position x1 = new Position(0, 0); Position x2 = new Position(0, 0); ox1 = x1; ox2 = x2; istangent = false; // Get the bearing of this segment. double bearing = Geom.BearingInRadians(start, end); // The equation of each line is given in parametric form as: // // x = xo + f * r // y = yo + g * r // // where xo,yo is the from point // and f = sin(bearing) // and g = cos(bearing) // and r is the distance along the line. double g = Math.Cos(bearing); double f = Math.Sin(bearing); double fsq = f * f; double gsq = g * g; double startx = start.X; double starty = start.Y; double dx = centre.X - startx; double dy = centre.Y - starty; double fygx = f * dy - g * dx; double root = radius * radius - fygx * fygx; // Check for no intersection. if (root < -Constants.TINY) { return(0); } // We've either got 1 or 2 intersections ... // If the intersection has to be ON the line, we'll need // the length of the line segment. double seglen = 0.0; if (online) { seglen = BasicGeom.Distance(start, end); } // Check for tangential intersection. if (root < Constants.TINY) { double xdist = f * dx + g * dy; if (online && (xdist < 0.0 || xdist > seglen)) { return(0); } x1.X = startx + f * xdist; x1.Y = starty + g * xdist; istangent = true; return(1); } // That leaves us with 2 intersections, although one or both of // them may not actually fall on the segment. double fxgy = f * dx + g * dy; root = Math.Sqrt(root); double dist1 = (fxgy - root); double dist2 = (fxgy + root); if (online) { uint nok = 0; if (dist1 > 0.0 && dist1 < seglen) { x1.X = startx + f * dist1; x1.Y = starty + g * dist1; nok = 1; } if (dist2 > 0.0 && dist2 < seglen) { if (nok == 0) { x1.X = startx + f * dist2; x1.Y = starty + g * dist2; return(1); } x2.X = startx + f * dist2; x2.Y = starty + g * dist2; return(2); } return(nok); } // Doesn't need to be ON the segment. x1.X = startx + f * dist1; x1.Y = starty + g * dist1; x2.X = startx + f * dist2; x2.Y = starty + g * dist2; return(2); }