public static IPolygon Offset(this IPolygon p, double offset)
        {
            if (p.IsEmpty)
            {
                return(Polygon.Empty);
            }

            var    boundingBox     = p.BoundingBox().Offset(Math.Max(offset, 0));
            double scale           = Math.Max(boundingBox.Width, boundingBox.Height);
            var    fixedPointRange = new Box2(boundingBox.MinCorner, new Vector2(scale, scale));

            var fixedP = ConvertToFixedPoint(p, fixedPointRange);

            try
            {
                var offsetter = new ClipperOffset();
                offsetter.AddPaths(fixedP, JoinType.jtMiter, EndType.etClosedPolygon);

                var fixedAnswer = new ClipperPolygon();
                offsetter.Execute(ref fixedAnswer, offset * _fixedPointRange / scale);

                return(ConvertToFloatingPoint(fixedAnswer, fixedPointRange));
            }
            catch (Exception e)
            {
                Console.WriteLine("EXCEPTION: {0}", e);
                return(p);
            }
        }
        private static IPolygon BinaryOperation(IPolygon p, IPolygon q, ClipType operationType)
        {
            if (p.IsEmpty && q.IsEmpty)
            {
                return(Polygon.Empty);
            }

            var boundingBox = Box2.Hull(p.BoundingBox(), q.BoundingBox());

            var fixedP = ConvertToFixedPoint(p, boundingBox);
            var fixedQ = ConvertToFixedPoint(q, boundingBox);

            try
            {
                var clipper = new Clipper();
                clipper.AddPaths(fixedP, PolyType.ptSubject, closed: true);
                clipper.AddPaths(fixedQ, PolyType.ptClip, closed: true);

                var fixedAnswer = new ClipperPolygon();

                clipper.Execute(operationType, fixedAnswer);

                return(ConvertToFloatingPoint(fixedAnswer, boundingBox));
            }
            catch (Exception e)
            {
                Console.WriteLine("EXCEPTION: {0}", e);
                return(Polygon.Empty);
            }
        }
        // intersection of polygon with half plane lying on negative (left) side of line: this is the natural convention since we use external normals
        public static IPolygon CropBy(this IPolygon p, Line2 line)
        {
            var box = p.BoundingBox();

            var point  = line.Project(box.Center);
            var radius = box.Diameter / 2;

            var collinearDisplacement  = radius * line.Direction;
            var transverseDisplacement = 2 * collinearDisplacement.Rotate90();

            var vertex1 = point + collinearDisplacement;
            var vertex2 = point - collinearDisplacement;

            var vertices = new List <Vector2>
            {
                vertex1,
                vertex1 + transverseDisplacement,
                vertex2 + transverseDisplacement,
                vertex2
            };

            var square = new Polygon(vertices);

            return(Intersection(p, square));
        }
        public static IPolygon Opening(this IPolygon p, double offset)
        {
            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException("offset", offset, "Expected positive offset");
            }

            return(Clopening(p, p.BoundingBox(), -offset));
        }
        public static IPolygon Closing(this IPolygon p, double offset)
        {
            if (offset < 0)
            {
                throw new ArgumentOutOfRangeException("offset", offset, "Expected positive offset");
            }

            var boundingBox = p.BoundingBox().Offset(offset);

            return(Clopening(p, boundingBox, offset));
        }
        public static IPolygon RelativeClosing(this IPolygon p, double relativeOffset)
        {
            if (relativeOffset < 0)
            {
                throw new ArgumentOutOfRangeException("relativeOffset", relativeOffset, "Expected positive relativeOffset");
            }

            var boundingBox = p.BoundingBox();

            if (boundingBox.IsEmpty)
            {
                return(Polygon.Empty);
            }

            double scale = Math.Max(boundingBox.Width, boundingBox.Height);

            return(p.Closing(relativeOffset * scale));
        }
        public static IPolygon MinkowskiDifference(IPolygon p, IPolygon q)
        {
            if (p.IsEmpty || q.IsEmpty)
            {
                return(Polygon.Empty);
            }

            var boundingBox = Box2.Hull(p.BoundingBox(), q.BoundingBox());

            boundingBox = Box2.Hull(boundingBox, new Box2(-boundingBox.MaxCorner, boundingBox.Dimensions));

            var fixedAnswer = new ClipperPolygon();
            var fixedP      = ConvertToFixedPoint(p, boundingBox);

            foreach (var qContour in q.Contours)
            {
                var fixedQContour = MinusConvertToFixedPoint(qContour, boundingBox);

                try
                {
                    var clipper = new Clipper();
                    clipper.AddPaths(fixedAnswer, PolyType.ptSubject, closed: true);
                    clipper.AddPaths(Clipper.MinkowskiSum(fixedQContour, fixedP, pathIsClosed: true), PolyType.ptClip, closed: true);

                    var tempAnswer = new ClipperPolygon();

                    clipper.Execute(ClipType.ctUnion, tempAnswer);

                    fixedAnswer = tempAnswer;
                }
                catch (Exception e)
                {
                    Console.WriteLine("EXCEPTION: {0}", e);
                }
            }

            return(ConvertToFloatingPoint(fixedAnswer, boundingBox));
        }