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