Esempio n. 1
0
        /// Inserts a single rectangle into the bin. The packer might rotate the rectangle, in which case the returned
        /// struct will have the width and height values swapped.
        /// @param merge If true, performs free Rectangle Merge procedure after packing the new rectangle. This procedure
        ///		tries to defragment the list of disjoint free rectangles to improve packing performance, but also takes up
        ///		some extra time.
        /// @param rectChoice The free rectangle choice heuristic rule to use.
        /// @param splitMethod The free rectangle split heuristic rule to use.
        public BinRect Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod)
        {
            // Find where to put the new rectangle.
            int     freeNodeIndex = 0;
            BinRect newRect       = FindPositionForNewNode(width, height, rectChoice, ref freeNodeIndex);

            // Abort if we didn't have enough space in the bin.
            if (newRect.height == 0)
            {
                return(newRect);
            }

            // Remove the space that was just consumed by the new rectangle.
            {
                var item = freeRectangles[freeNodeIndex];
                SplitFreeRectByHeuristic(ref item, ref newRect, splitMethod);
                freeRectangles[freeNodeIndex] = item;
                freeRectangles.RemoveAt(freeNodeIndex);
            }
            // Perform a Rectangle Merge step if desired.
            if (merge)
            {
                MergeFreeList();
            }

            // Remember the new used rectangle.
            usedRectangles.Add(newRect);


            return(newRect);
        }
Esempio n. 2
0
        /// Computes the placement score for placing the given rectangle with the given method.
        /// @param score1 [out] The primary placement score will be outputted here.
        /// @param score2 [out] The secondary placement score will be outputted here. This isu sed to break ties.
        /// @return This struct identifies where the rectangle would be placed if it were placed.
        private BinRect ScoreRect(int width, int height, bool rot, FreeRectChoiceHeuristic method, ref int score1, ref int score2)
        {
            BinRect newNode = new BinRect();

            score1 = int.MaxValue;
            score2 = int.MinValue;
            switch (method)
            {
            case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(rot, width, height, ref score1, ref score2); break;

            case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(rot, width, height, ref score1, ref score2); break;

            case FreeRectChoiceHeuristic.RectContactPointRule:
                newNode = FindPositionForNewNodeContactPoint(rot, width, height, ref score1);
                score1  = -score1;    // Reverse since we are minimizing, but for contact point score bigger is better.
                break;

            case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(rot, width, height, ref score2, ref score1); break;

            case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(rot, width, height, ref score1, ref score2); break;
            }

            // Cannot fit the current rectangle.
            if (newNode.height == 0)
            {
                score1 = int.MaxValue;
                score2 = int.MaxValue;
            }

            return(newNode);
        }
Esempio n. 3
0
        static int ScoreBestLongSideFit(int width, int height, ref BinRect freeRect)
        {
            int leftoverHoriz = Math.Abs(freeRect.width - width);
            int leftoverVert  = Math.Abs(freeRect.height - height);
            int leftover      = Math.Max(leftoverHoriz, leftoverVert);

            return(leftover);
        }
Esempio n. 4
0
        /// Splits the given L-shaped free rectangle into two new free rectangles after placedRect has been placed into it.
        /// Determines the split axis by using the given heuristic.
        public void SplitFreeRectByHeuristic(ref BinRect freeRect, ref BinRect placedRect, GuillotineSplitHeuristic method)
        {
            // Compute the lengths of the leftover area.
            int w = freeRect.width - placedRect.width;
            int h = freeRect.height - placedRect.height;

            // Placing placedRect into freeRect results in an L-shaped free area, which must be split into
            // two disjoint rectangles. This can be achieved with by splitting the L-shape using a single line.
            // We have two choices: horizontal or vertical.

            // Use the given heuristic to decide which choice to make.

            bool splitHorizontal;

            switch (method)
            {
            case GuillotineSplitHeuristic.SplitShorterLeftoverAxis:
                // Split along the shorter leftover axis.
                splitHorizontal = (w <= h);
                break;

            case GuillotineSplitHeuristic.SplitLongerLeftoverAxis:
                // Split along the longer leftover axis.
                splitHorizontal = (w > h);
                break;

            case GuillotineSplitHeuristic.SplitMinimizeArea:
                // Maximize the larger area == minimize the smaller area.
                // Tries to make the single bigger rectangle.
                splitHorizontal = (placedRect.width * h > w * placedRect.height);
                break;

            case GuillotineSplitHeuristic.SplitMaximizeArea:
                // Maximize the smaller area == minimize the larger area.
                // Tries to make the rectangles more even-sized.
                splitHorizontal = (placedRect.width * h <= w * placedRect.height);
                break;

            case GuillotineSplitHeuristic.SplitShorterAxis:
                // Split along the shorter total axis.
                splitHorizontal = (freeRect.width <= freeRect.height);
                break;

            case GuillotineSplitHeuristic.SplitLongerAxis:
                // Split along the longer total axis.
                splitHorizontal = (freeRect.width > freeRect.height);
                break;

            default:
                splitHorizontal = true;
                break;
            }

            // Perform the actual split.
            SplitFreeRectAlongAxis(ref freeRect, ref placedRect, splitHorizontal);
        }
Esempio n. 5
0
        private BinRect FindPositionForNewNodeBestAreaFit(bool rot, int width, int height, ref int bestAreaFit, ref int bestShortSideFit)
        {
            BinRect bestNode = new BinRect();

            bestAreaFit      = int.MaxValue;
            bestShortSideFit = int.MaxValue;

            for (int i = 0; i < freeRectangles.Count; ++i)
            {
                int areaFit = freeRectangles[i].width * freeRectangles[i].height - width * height;

                // Try to place the rectangle in upright (non-flipped) orientation.
                if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
                {
                    int leftoverHoriz = Math.Abs(freeRectangles[i].width - width);
                    int leftoverVert  = Math.Abs(freeRectangles[i].height - height);
                    int shortSideFit  = Math.Min(leftoverHoriz, leftoverVert);

                    if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
                    {
                        bestNode.x       = freeRectangles[i].x;
                        bestNode.y       = freeRectangles[i].y;
                        bestNode.width   = width;
                        bestNode.height  = height;
                        bestShortSideFit = shortSideFit;
                        bestAreaFit      = areaFit;
                    }
                }

                if (rot)
                {
                    if (freeRectangles[i].width >= height && freeRectangles[i].height >= width)
                    {
                        int leftoverHoriz = Math.Abs(freeRectangles[i].width - height);
                        int leftoverVert  = Math.Abs(freeRectangles[i].height - width);
                        int shortSideFit  = Math.Min(leftoverHoriz, leftoverVert);

                        if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit))
                        {
                            bestNode.x       = freeRectangles[i].x;
                            bestNode.y       = freeRectangles[i].y;
                            bestNode.width   = height;
                            bestNode.height  = width;
                            bestShortSideFit = shortSideFit;
                            bestAreaFit      = areaFit;
                        }
                    }
                }
            }
            return(bestNode);
        }
Esempio n. 6
0
        /// @return True if the free node was split.
        private bool SplitFreeNode(BinRect freeNode, ref BinRect usedNode)
        {
            // Test with SAT if the rectangles even intersect.
            if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x ||
                usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y)
            {
                return(false);
            }

            if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x)
            {
                // New node at the top side of the used node.
                if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height)
                {
                    BinRect newNode = freeNode;
                    newNode.height = usedNode.y - newNode.y;
                    freeRectangles.Add(newNode);
                }

                // New node at the bottom side of the used node.
                if (usedNode.y + usedNode.height < freeNode.y + freeNode.height)
                {
                    BinRect newNode = freeNode;
                    newNode.y      = usedNode.y + usedNode.height;
                    newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
                    freeRectangles.Add(newNode);
                }
            }

            if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y)
            {
                // New node at the left side of the used node.
                if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width)
                {
                    BinRect newNode = freeNode;
                    newNode.width = usedNode.x - newNode.x;
                    freeRectangles.Add(newNode);
                }

                // New node at the right side of the used node.
                if (usedNode.x + usedNode.width < freeNode.x + freeNode.width)
                {
                    BinRect newNode = freeNode;
                    newNode.x     = usedNode.x + usedNode.width;
                    newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
                    freeRectangles.Add(newNode);
                }
            }

            return(true);
        }
Esempio n. 7
0
        /// (Re)initializes the packer to an empty bin of width x height units. Call whenever
        /// you need to restart with a new bin.
        public void Init(int width, int height)
        {
            binWidth       = width;
            binHeight      = height;
            usedRectangles = new List <BinRect>();
            freeRectangles = new List <BinRect>();
            BinRect n = new BinRect();

            n.x      = 0;
            n.y      = 0;
            n.width  = width;
            n.height = height;
            usedRectangles.Clear();
            freeRectangles.Clear();
            freeRectangles.Add(n);
        }
Esempio n. 8
0
        /// Places the given rectangle into the bin.
        private void PlaceRect(ref BinRect node)
        {
            int numRectanglesToProcess = freeRectangles.Count;

            for (int i = 0; i < numRectanglesToProcess; ++i)
            {
                if (SplitFreeNode(freeRectangles[i], ref node))
                {
                    freeRectangles.RemoveAt(i);
                    --i;
                    --numRectanglesToProcess;
                }
            }

            PruneFreeList();

            usedRectangles.Add(node);
        }
Esempio n. 9
0
        static int ScoreByHeuristic(int width, int height, ref BinRect freeRect, FreeRectChoiceHeuristic rectChoice)
        {
            switch (rectChoice)
            {
            case FreeRectChoiceHeuristic.RectBestAreaFit: return(ScoreBestAreaFit(width, height, ref freeRect));

            case FreeRectChoiceHeuristic.RectBestShortSideFit: return(ScoreBestShortSideFit(width, height, ref freeRect));

            case FreeRectChoiceHeuristic.RectBestLongSideFit: return(ScoreBestLongSideFit(width, height, ref freeRect));

            case FreeRectChoiceHeuristic.RectWorstAreaFit: return(ScoreWorstAreaFit(width, height, ref freeRect));

            case FreeRectChoiceHeuristic.RectWorstShortSideFit: return(ScoreWorstShortSideFit(width, height, ref freeRect));

            case FreeRectChoiceHeuristic.RectWorstLongSideFit: return(ScoreWorstLongSideFit(width, height, ref freeRect));

            default: return(int.MaxValue);
            }
        }
Esempio n. 10
0
        private BinRect FindPositionForNewNodeBottomLeft(bool rot, int width, int height, ref int bestY, ref int bestX)
        {
            BinRect bestNode = new BinRect();

            bestY = int.MaxValue;
            bestX = int.MaxValue;

            for (int i = 0; i < freeRectangles.Count; ++i)
            {
                // Try to place the rectangle in upright (non-flipped) orientation.
                if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
                {
                    int topSideY = freeRectangles[i].y + height;
                    if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX))
                    {
                        bestNode.x      = freeRectangles[i].x;
                        bestNode.y      = freeRectangles[i].y;
                        bestNode.width  = width;
                        bestNode.height = height;
                        bestY           = topSideY;
                        bestX           = freeRectangles[i].x;
                    }
                }
                if (rot)
                {
                    if (freeRectangles[i].width >= height && freeRectangles[i].height >= width)
                    {
                        int topSideY = freeRectangles[i].y + width;
                        if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX))
                        {
                            bestNode.x      = freeRectangles[i].x;
                            bestNode.y      = freeRectangles[i].y;
                            bestNode.width  = height;
                            bestNode.height = width;
                            bestY           = topSideY;
                            bestX           = freeRectangles[i].x;
                        }
                    }
                }
            }
            return(bestNode);
        }
Esempio n. 11
0
        /// Inserts a single rectangle into the bin, possibly rotated.
        public BinRect Insert(int width, int height, bool rot, FreeRectChoiceHeuristic method)
        {
            BinRect newNode = new BinRect();
            // Unused in this function. We don't need to know the score after finding the position.
            int score1 = int.MaxValue;
            int score2 = int.MaxValue;

            switch (method)
            {
            case FreeRectChoiceHeuristic.RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(rot, width, height, ref score1, ref score2); break;

            case FreeRectChoiceHeuristic.RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(rot, width, height, ref score1, ref score2); break;

            case FreeRectChoiceHeuristic.RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(rot, width, height, ref score1); break;

            case FreeRectChoiceHeuristic.RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(rot, width, height, ref score2, ref score1); break;

            case FreeRectChoiceHeuristic.RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(rot, width, height, ref score1, ref score2); break;
            }

            if (newNode.height == 0)
            {
                return(newNode);
            }

            int numRectanglesToProcess = freeRectangles.Count;

            for (int i = 0; i < numRectanglesToProcess; ++i)
            {
                if (SplitFreeNode(freeRectangles[i], ref newNode))
                {
                    freeRectangles.RemoveAt(i);
                    --i;
                    --numRectanglesToProcess;
                }
            }

            PruneFreeList();

            usedRectangles.Add(newNode);
            return(newNode);
        }
Esempio n. 12
0
        private BinRect FindPositionForNewNodeContactPoint(bool rot, int width, int height, ref int bestContactScore)
        {
            BinRect bestNode = new BinRect();

            bestContactScore = -1;

            for (int i = 0; i < freeRectangles.Count; ++i)
            {
                // Try to place the rectangle in upright (non-flipped) orientation.
                if (freeRectangles[i].width >= width && freeRectangles[i].height >= height)
                {
                    int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, width, height);
                    if (score > bestContactScore)
                    {
                        bestNode.x       = freeRectangles[i].x;
                        bestNode.y       = freeRectangles[i].y;
                        bestNode.width   = width;
                        bestNode.height  = height;
                        bestContactScore = score;
                    }
                }
                if (rot)
                {
                    if (freeRectangles[i].width >= height && freeRectangles[i].height >= width)
                    {
                        int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, height, width);
                        if (score > bestContactScore)
                        {
                            bestNode.x       = freeRectangles[i].x;
                            bestNode.y       = freeRectangles[i].y;
                            bestNode.width   = height;
                            bestNode.height  = width;
                            bestContactScore = score;
                        }
                    }
                }
            }
            return(bestNode);
        }
Esempio n. 13
0
        /// Inserts the given list of rectangles in an offline/batch mode, possibly rotated.
        /// @param rects The list of rectangles to insert. This vector will be destroyed in the process.
        /// @param dst [out] This list will contain the packed rectangles. The indices will not correspond to that of rects.
        /// @param method The rectangle placement rule to use when packing.
        public void Insert(List <RectSize> rects, List <BinRect> dst, bool rot, FreeRectChoiceHeuristic method)
        {
            dst.Clear();

            while (rects.Count > 0)
            {
                int     bestScore1    = int.MaxValue;
                int     bestScore2    = int.MaxValue;
                int     bestRectIndex = -1;
                BinRect bestNode      = new BinRect();

                for (int i = 0; i < rects.Count; ++i)
                {
                    int     score1  = 0;
                    int     score2  = 0;
                    BinRect newNode = ScoreRect(rects[i].width, rects[i].height, rot, method, ref score1, ref score2);

                    if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2))
                    {
                        bestScore1    = score1;
                        bestScore2    = score2;
                        bestNode      = newNode;
                        bestRectIndex = i;
                    }
                }

                if (bestRectIndex == -1)
                {
                    return;
                }

                PlaceRect(ref bestNode);
                dst.Add(bestNode);
                rects.RemoveAt(bestRectIndex);
            }
        }
Esempio n. 14
0
        /// Splits the given L-shaped free rectangle into two new free rectangles along the given fixed split axis.
        public void SplitFreeRectAlongAxis(ref BinRect freeRect, ref BinRect placedRect, bool splitHorizontal)
        {
            // Form the two new rectangles.
            BinRect bottom = new BinRect();

            bottom.x      = freeRect.x;
            bottom.y      = freeRect.y + placedRect.height;
            bottom.height = freeRect.height - placedRect.height;

            BinRect right = new BinRect();

            right.x     = freeRect.x + placedRect.width;
            right.y     = freeRect.y;
            right.width = freeRect.width - placedRect.width;

            if (splitHorizontal)
            {
                bottom.width = freeRect.width;
                right.height = placedRect.height;
            }
            else // Split vertically
            {
                bottom.width = placedRect.width;
                right.height = freeRect.height;
            }

            // Add the new rectangles into the free rectangle pool if they weren't degenerate.
            if (bottom.width > 0 && bottom.height > 0)
            {
                freeRectangles.Add(bottom);
            }
            if (right.width > 0 && right.height > 0)
            {
                freeRectangles.Add(right);
            }
        }
Esempio n. 15
0
 public bool IsContainedIn(BinRect a, BinRect b)
 {
     return(a.x >= b.x && a.y >= b.y &&
            a.x + a.width <= b.x + b.width &&
            a.y + a.height <= b.y + b.height);
 }
Esempio n. 16
0
        // The following functions compute (penalty) score values if a rect of the given size was placed into the
        // given free rectangle. In these score values, smaller is better.

        static int ScoreBestAreaFit(int width, int height, ref BinRect freeRect)
        {
            return(freeRect.width * freeRect.height - width * height);
        }
Esempio n. 17
0
 static int ScoreWorstLongSideFit(int width, int height, ref BinRect freeRect)
 {
     return(-ScoreBestLongSideFit(width, height, ref freeRect));
 }
Esempio n. 18
0
        public void Pack(List <PackerBitmap> bitmaps, bool verbose, bool unique, bool rotate)
        {
            var packer = new MaxRectsBinPack(Width, Height);
            int ww     = 0;
            int hh     = 0;

            while (bitmaps.Count > 0)
            {
                var bitmap = bitmaps[bitmaps.Count - 1];

                if (verbose)
                {
                    Console.WriteLine($"     { bitmaps.Count - 1 }:  {bitmap.Name}");
                }

                //Check to see if this is a duplicate of an already packed bitmap
                if (unique)
                {
                    if (DupLookup.TryGetValue(bitmap.GetHashCode(), out var di))
                    {
                        Point p = Points[di];
                        p.dupID = di;
                        Points.Add(p);
                        Bitmaps.Add(bitmap);
                        bitmaps.RemoveAt(bitmaps.Count - 1);
                        continue;
                    }
                }

                //If it's not a duplicate, pack it into the atlas
                {
                    BinRect rect = packer.Insert(bitmap.Width + Pad, bitmap.Height + Pad, rotate, MaxRectsBinPack.FreeRectChoiceHeuristic.RectBestShortSideFit);

                    if (rect.width == 0 || rect.height == 0)
                    {
                        break;
                    }

                    if (unique)
                    {
                        DupLookup.Add(bitmap.GetHashCode(), Points.Count);
                    }
                    //Check if we rotated it
                    Point p;
                    p.x     = rect.x;
                    p.y     = rect.y;
                    p.dupID = -1;
                    p.rot   = rotate && (bitmap.Width != (rect.width - Pad));

                    Points.Add(p);
                    Bitmaps.Add(bitmap);
                    bitmaps.RemoveAt(bitmaps.Count - 1);

                    ww = Math.Max(rect.x + rect.width, ww);
                    hh = Math.Max(rect.y + rect.height, hh);
                }
            }

            while (Width / 2 >= ww)
            {
                Width /= 2;
            }
            while (Height / 2 >= hh)
            {
                Height /= 2;
            }
        }
Esempio n. 19
0
        /// Goes through the list of free rectangles and finds the best one to place a rectangle of given size into.
        /// Running time is Theta(|freeRectangles|).
        /// @param nodeIndex [out] The index of the free rectangle in the freeRectangles array into which the new
        ///		rect was placed.
        /// @return A Rect structure that represents the placement of the new rect into the best free rectangle.
        public BinRect FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, ref int nodeIndex)
        {
            BinRect bestNode = new BinRect();

            int bestScore = int.MaxValue;

            /// Try each free rectangle to find the best one for placement.
            for (int i = 0; i < freeRectangles.Count; ++i)
            {
                // If this is a perfect fit upright, choose it immediately.
                if (width == freeRectangles[i].width && height == freeRectangles[i].height)
                {
                    bestNode.x      = freeRectangles[i].x;
                    bestNode.y      = freeRectangles[i].y;
                    bestNode.width  = width;
                    bestNode.height = height;
                    bestScore       = int.MinValue;
                    break;
                }
                // If this is a perfect fit sideways, choose it.
                else if (height == freeRectangles[i].width && width == freeRectangles[i].height)
                {
                    bestNode.x      = freeRectangles[i].x;
                    bestNode.y      = freeRectangles[i].y;
                    bestNode.width  = height;
                    bestNode.height = width;
                    bestScore       = int.MinValue;
                    break;
                }
                // Does the rectangle fit upright?
                else if (width <= freeRectangles[i].width && height <= freeRectangles[i].height)
                {
                    var item  = freeRectangles[i];
                    int score = ScoreByHeuristic(width, height, ref item, rectChoice);
                    freeRectangles[i] = item;
                    if (score < bestScore)
                    {
                        bestNode.x      = freeRectangles[i].x;
                        bestNode.y      = freeRectangles[i].y;
                        bestNode.width  = width;
                        bestNode.height = height;
                        bestScore       = score;
                    }
                }
                // Does the rectangle fit sideways?
                else if (height <= freeRectangles[i].width && width <= freeRectangles[i].height)
                {
                    var item  = freeRectangles[i];
                    int score = ScoreByHeuristic(width, height, ref item, rectChoice);
                    freeRectangles[i] = item;
                    if (score < bestScore)
                    {
                        bestNode.x      = freeRectangles[i].x;
                        bestNode.y      = freeRectangles[i].y;
                        bestNode.width  = height;
                        bestNode.height = width;
                        bestScore       = score;
                    }
                }
            }
            return(bestNode);
        }