Example #1
0
        /// <summary>
        /// Returns the rotation of the supplied counter clockwise enumerated
        /// convex polygon that results in the minimum area enclosing box.
        /// If multiple rotations are within epsilon in their area, the one
        /// that is closest to an axis-aligned rotation (0, 90, 180, 270) is
        /// returned. O(n).
        /// </summary>
        public static M22d ComputeMinAreaEnclosingBoxRotation(
            this Polygon2d polygon, double epsilon = 1e-6)
        {
            polygon = polygon.WithoutMultiplePoints(epsilon);
            var pc = polygon.PointCount;

            if (pc < 2)
            {
                return(M22d.Identity);
            }
            var ea = polygon.GetEdgeArray();

            ea.Apply(v => v.Normalized);

            int i0 = 0, i1 = 0;
            int i2 = 0, i3 = 0;
            var min = polygon[0]; var max = polygon[0];

            for (int pi = 1; pi < pc; pi++)
            {
                var p = polygon[pi];
                if (p.Y < min.Y)
                {
                    i0 = pi; min.Y = p.Y;
                }
                else if (p.Y > max.Y)
                {
                    i2 = pi; max.Y = p.Y;
                }
                if (p.X > max.X)
                {
                    i1 = pi; max.X = p.X;
                }
                else if (p.X < min.X)
                {
                    i3 = pi; min.X = p.X;
                }
            }

            V2d p0 = polygon[i0], e0 = ea[i0], p1 = polygon[i1], e1 = ea[i1];
            V2d p2 = polygon[i2], e2 = ea[i2], p3 = polygon[i3], e3 = ea[i3];

            int end0 = (i0 + 1) % pc, end1 = (i1 + 1) % pc;
            int end2 = (i2 + 1) % pc, end3 = (i3 + 1) % pc;
            var dir = V2d.XAxis;

            var best      = dir;
            var bestArea  = double.MaxValue;
            var bestValue = double.MaxValue;

            while (true)
            {
                var s0 = Fun.FastAtan2(e0.Dot90(dir), e0.Dot(dir));
                var s1 = Fun.FastAtan2(e1.Dot180(dir), e1.Dot90(dir));
                var s2 = Fun.FastAtan2(e2.Dot270(dir), e2.Dot180(dir));
                var s3 = Fun.FastAtan2(e3.Dot(dir), e3.Dot270(dir));

                int si, si01, si23; double s01, s23;
                if (s0 < s1)
                {
                    s01 = s0; si01 = 0;
                }
                else
                {
                    s01 = s1; si01 = 1;
                }
                if (s2 < s3)
                {
                    s23 = s2; si23 = 2;
                }
                else
                {
                    s23 = s3; si23 = 3;
                }
                if (s01 < s23)
                {
                    si = si01;
                }
                else
                {
                    si = si23;
                }

                if (si == 0)
                {
                    dir = ea[i0];
                }
                else if (si == 1)
                {
                    dir = ea[i1].Rot270;
                }
                else if (si == 2)
                {
                    dir = ea[i2].Rot180;
                }
                else
                {
                    dir = ea[i3].Rot90;
                }

                double sx = (p2 - p0).Dot90(dir), sy = (p1 - p3).Dot(dir);
                double area  = sx * sy;
                double value = Fun.Min(Fun.Abs(dir.X), Fun.Abs(dir.Y));

                if (area < bestArea - epsilon ||
                    (area < bestArea + epsilon && value < bestValue))
                {
                    bestArea = area; bestValue = value; best = dir;
                }

                if (si == 0)
                {
                    if (++i0 >= pc)
                    {
                        i0 -= pc;
                    }
                    if (i0 == end1)
                    {
                        break;
                    }
                    p0 = polygon[i0]; e0 = ea[i0];
                }
                else if (si == 1)
                {
                    if (++i1 >= pc)
                    {
                        i1 -= pc;
                    }
                    if (i1 == end2)
                    {
                        break;
                    }
                    p1 = polygon[i1]; e1 = ea[i1];
                }
                else if (si == 2)
                {
                    if (++i2 >= pc)
                    {
                        i2 -= pc;
                    }
                    if (i2 == end3)
                    {
                        break;
                    }
                    p2 = polygon[i2]; e2 = ea[i2];
                }
                else
                {
                    if (++i3 >= pc)
                    {
                        i3 -= pc;
                    }
                    if (i3 == end0)
                    {
                        break;
                    }
                    p3 = polygon[i3]; e3 = ea[i3];
                }
            }
            return(new M22d(best.X, best.Y, -best.Y, best.X));
        }