/// <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);
 }
Ejemplo n.º 2
0
        static bool IsPointInPolygon(Geo x, Geo center, IList<Geo> poly)
        {
            // do this only once and pass it in for the math.
            // Geo c = Limits.center(poly);

            // bail out if the point is more than 90 degrees off the
            // centroid
            var d = x.DistanceRadians(center);
            if (d >= (Math.PI / 2))
            {
                return false;
            }
            // ray is normal to the great circle from c to x. reusing c to hold ray
            // info
            var ray = center.CrossNormalize(x);
            /*
       * side is a point on the great circle between c and x. It is used to
       * choose a direction.
       */
            var side = x.CrossNormalize(ray);
            var isIn = false;
            // Why do we need to allocate new Geos?
            // Geo p1 = new Geo(poly[0]);
            // Geo p2 = new Geo(poly[0]);
            var p1 = new Geo(poly[0]);
            Geo p2;
            var polySize = poly.Count;
            for (var i = 1; i < polySize; i++)
            {
                p2 = new Geo(poly[i]);
                /*
          * p1 and p2 are on different sides of the ray, and the great acircle
          * between p1 and p2 is on the side that counts;
          */
                if ((p1.Dot(ray) < 0.0) != (p2.Dot(ray) < 0.0) && new GeoSegment(p1, p2).GreatCircleIntersection(ray).Dot(side) > 0.0)
                {
                    isIn = !isIn;
                }

                p1 = new Geo(p2);
            }

            // Check for unclosed polygons, if the polygon isn't closed,
            // do the calculation for the last point to the starting
            // point.
            if (!poly[0].Equals(p1))
            {
                p2 = new Geo(poly[0]);
                if ((p1.Dot(ray) < 0.0) != (p2.Dot(ray) < 0.0) && new GeoSegment(p1, p2).GreatCircleIntersection(ray).Dot(side) > 0.0)
                {
                    isIn = !isIn;
                }
            }

            return isIn;
        }