/// <summary> /// Bounce a platform around inside a given perimeter /// </summary> /// <param name="perimeter">A GeoArray of points in the perimeter, which must satisfy certain conditions (no segments may cross and the polygon /// must be closed)</param> /// <param name="startPoint">A Geo containing the starting point. If this is null, a point inside the perimeter is chosen randomly</param> /// <param name="initialCourse">The initial course, in radians from true north. If startPoint is null, or if initialCourse is NaN, this is /// chosen randomly</param> /// <param name="minimumCourseLength">The minimum length of the returned course, in meters</param> /// <returns>A GeoArray containing the start location and each point where the course bounces off the perimeter</returns> public static GeoArray PerimeterBounce(this GeoArray perimeter, Geo startPoint, double initialCourse, double minimumCourseLength) { if (!perimeter.IsClosed) throw new InvalidPerimeterException("Perimeter is not closed"); if (perimeter.HasCrossingSegments) throw new InvalidPerimeterException("Perimeter is not a simple polygon (segments cross each other)"); var points = new List<Geo>(); while ((startPoint == null) || (!startPoint.IsInside(perimeter))) { var distance = Random.NextDouble() * perimeter.BoundingCircle.Radius; var azimuth = Random.NextDouble() * MoreMath.TwoPi; startPoint = perimeter.Center.Offset(distance, azimuth); } points.Add(startPoint); if (double.IsNaN(initialCourse)) initialCourse = Random.NextDouble()*MoreMath.TwoPi; var courseSegment = new GeoSegment(startPoint, startPoint.Offset(Geo.KilometersToRadians(0.001), initialCourse)).Scale(1000 * Geo.RadiansToKilometers(MoreMath.PiOverTwo)); var bounceCount = 1; while (minimumCourseLength > 0) { var minIntersectionRange = double.MaxValue; GeoSegment firstIntersectingSegment = null; Geo firstIntersection = null; foreach (var segment in perimeter.Segments) { var intersection = courseSegment.Intersection(segment); if (intersection == null) continue; var curIntersectionRange = startPoint.DistanceRadians(intersection); if (curIntersectionRange < Geo.KilometersToRadians(0.001) || curIntersectionRange >= minIntersectionRange) continue; minIntersectionRange = curIntersectionRange; firstIntersectingSegment = segment; firstIntersection = intersection; } if (firstIntersection == null) throw new PerimeterBounceException(string.Format("Course segment failed to intersect the perimeter on bounce {0}", bounceCount)); var actualCourseSegment = new GeoSegment(points.Last(), firstIntersection); minimumCourseLength -= Geo.RadiansToKilometers(actualCourseSegment.LengthRadians) * 1000; points.Add(firstIntersection); var reflectedSegment = courseSegment.Reflect(firstIntersectingSegment); var reflectionAzimuth = reflectedSegment[0].Azimuth(reflectedSegment[1]); courseSegment = new GeoSegment(reflectedSegment[1], 1000 * Geo.RadiansToKilometers(MoreMath.PiOverTwo), Geo.RadiansToDegrees(reflectionAzimuth)); bounceCount++; } return new GeoArray(points); }
public static bool Contains(this GeoArray region, Geo p) { return p.IsInside(region); }