示例#1
0
        /// <summary>
        /// Tries to pack the rectangles in the given order into a bin of the specified size.
        /// </summary>
        /// <param name="emptySpaces">The list of empty spaces for reusing.</param>
        /// <param name="unpacked">The unpacked rectangles.</param>
        /// <param name="packed">Where the resulting rectangles will be written.</param>
        /// <param name="binWidth">The width of the bin.</param>
        /// <param name="binHeight">The height of the bin.</param>
        /// <param name="boundsWidth">The width of the resulting bin.</param>
        /// <param name="boundsHeight">The height of the resulting bin.</param>
        /// <returns>Whether the operation succeeded.</returns>
        /// <remarks>The unpacked and packed spans can be the same.</remarks>
        private static bool TryPackAsOrdered(List <PackingRectangle> emptySpaces, Span <PackingRectangle> unpacked,
                                             Span <PackingRectangle> packed, uint binWidth, uint binHeight, out uint boundsWidth, out uint boundsHeight)
        {
            // We clear the empty spaces list and add one space covering the entire bin.
            emptySpaces.Clear();
            emptySpaces.Add(new PackingRectangle(0, 0, binWidth, binHeight));

            // boundsWidth and boundsHeight both start at 0.
            boundsWidth  = 0;
            boundsHeight = 0;

            // We loop through all the rectangles.
            for (int r = 0; r < unpacked.Length; r++)
            {
                // We try to find a space for the rectangle. If we can't, then we return false.
                if (!TryFindBestSpace(unpacked[r], emptySpaces, out int spaceIndex))
                {
                    return(false);
                }

                PackingRectangle oldSpace = emptySpaces[spaceIndex];
                packed[r]    = unpacked[r];
                packed[r].X  = oldSpace.X;
                packed[r].Y  = oldSpace.Y;
                boundsWidth  = Math.Max(boundsWidth, packed[r].Right);
                boundsHeight = Math.Max(boundsHeight, packed[r].Bottom);

                // We calculate the width and height of the rectangles from splitting the empty space
                uint freeWidth  = oldSpace.Width - packed[r].Width;
                uint freeHeight = oldSpace.Height - packed[r].Height;

                if (freeWidth != 0 && freeHeight != 0)
                {
                    emptySpaces.RemoveAt(spaceIndex);
                    // Both freeWidth and freeHeight are different from 0. We need to split the
                    // empty space into two (plus the image). We split it in such a way that the
                    // bigger rectangle will be where there is the most space.
                    if (freeWidth > freeHeight)
                    {
                        emptySpaces.AddSorted(new PackingRectangle(packed[r].Right, oldSpace.Y, freeWidth, oldSpace.Height));
                        emptySpaces.AddSorted(new PackingRectangle(oldSpace.X, packed[r].Bottom, packed[r].Width, freeHeight));
                    }
                    else
                    {
                        emptySpaces.AddSorted(new PackingRectangle(oldSpace.X, packed[r].Bottom, oldSpace.Width, freeHeight));
                        emptySpaces.AddSorted(new PackingRectangle(packed[r].Right, oldSpace.Y, freeWidth, packed[r].Height));
                    }
                }
                else if (freeWidth == 0)
                {
                    // We only need to change the Y and height of the space.
                    oldSpace.Y             += packed[r].Height;
                    oldSpace.Height         = freeHeight;
                    emptySpaces[spaceIndex] = oldSpace;
                    EnsureSorted(emptySpaces, spaceIndex);
                    //emptySpaces.RemoveAt(spaceIndex);
                    //emptySpaces.Add(new PackingRectangle(oldSpace.X, oldSpace.Y + packed[r].Height, oldSpace.Width, freeHeight));
                }
                else if (freeHeight == 0)
                {
                    // We only need to change the X and width of the space.
                    oldSpace.X             += packed[r].Width;
                    oldSpace.Width          = freeWidth;
                    emptySpaces[spaceIndex] = oldSpace;
                    EnsureSorted(emptySpaces, spaceIndex);
                    //emptySpaces.RemoveAt(spaceIndex);
                    //emptySpaces.Add(new PackingRectangle(oldSpace.X + packed[r].Width, oldSpace.Y, freeWidth, oldSpace.Height));
                }
                else // The rectangle uses up the entire empty space.
                {
                    emptySpaces.RemoveAt(spaceIndex);
                }
            }

            return(true);
        }
示例#2
0
        /// <summary>
        /// Finds a way to pack all the given rectangles into a single bin. Performance can be traded for
        /// space efficiency by using the optional parameters.
        /// </summary>
        /// <param name="rectangles">The rectangles to pack. The result is saved onto this array.</param>
        /// <param name="bounds">The bounds of the resulting bin. This will always be at X=Y=0.</param>
        /// <param name="packingHint">Specifies hints for optimizing performance.</param>
        /// <param name="acceptableDensity">Searching stops once a bin is found with this density (usedArea/totalArea) or better.</param>
        /// <param name="stepSize">The amount by which to increment/decrement size when trying to pack another bin.</param>
        /// <remarks>
        /// The <see cref="PackingRectangle.Id"/> values are never touched. Use this to identify your rectangles.
        /// </remarks>
        public static void Pack(PackingRectangle[] rectangles, out PackingRectangle bounds,
                                PackingHints packingHint = PackingHints.FindBest, float acceptableDensity = 1, uint stepSize = 1)
        {
            if (rectangles == null)
            {
                throw new ArgumentNullException(nameof(rectangles));
            }

            if (stepSize == 0)
            {
                throw new ArgumentOutOfRangeException(nameof(stepSize), stepSize, nameof(stepSize) + " must be greater than 0.");
            }

            bounds = default;
            if (rectangles.Length == 0)
            {
                return;
            }

            // We separate the value in packingHint into the different options it specifies.
            Span <PackingHints> hints = stackalloc PackingHints[PackingHintExtensions.MaxHintCount];

            PackingHintExtensions.GetFlagsFrom(packingHint, ref hints);

            if (hints.Length == 0)
            {
                throw new ArgumentException("No valid packing hints specified.", nameof(packingHint));
            }

            // We'll try uint.MaxValue as initial bin size. The packing algoritm already tries to
            // use as little space as possible, so this will be QUICKLY cut down closer to the
            // final bin size.
            uint binSize = uint.MaxValue;

            // We turn the acceptableDensity parameter into an acceptableArea value, so we can
            // compare the area directly rather than having to calculate the density each time.
            uint totalArea = CalculateTotalArea(rectangles);

            acceptableDensity = Math.Clamp(acceptableDensity, 0.1f, 1);
            uint acceptableArea = (uint)Math.Ceiling(totalArea / acceptableDensity);

            // We get a list that will be used (and reused) by the packing algorithm.
            List <PackingRectangle> emptySpaces = GetList(rectangles.Length * 2);

            // We'll store the area of the best solution so far here.
            uint currentBestArea = uint.MaxValue;
            bool hasSolution     = false;

            // In one array we'll store the current best solution, and we'll also need two temporary arrays.
            PackingRectangle[] currentBest = rectangles;
            PackingRectangle[] tmpBest     = new PackingRectangle[rectangles.Length];
            PackingRectangle[] tmpArray    = new PackingRectangle[rectangles.Length];

            // For each of the specified hints, we try to pack and see if we can find a better solution.
            for (int i = 0; i < hints.Length && currentBestArea > acceptableArea; i++)
            {
                // We copy the rectangles onto the tmpBest array, then sort them by what the packing hint says.
                currentBest.CopyTo(tmpBest, 0);
                PackingHintExtensions.SortByPackingHint(tmpBest, hints[i]);

                // We try to find the best bin for the rectangles in tmpBest. We give the function as
                // initial bin size the size of the best bin we got so far. The function never tries
                // bigger bin sizes, so if with a specified packingHint it can't pack smaller than
                // with the last solution, it simply stops.
                if (TryFindBestBin(emptySpaces, ref tmpBest, ref tmpArray, binSize - stepSize, stepSize,
                                   acceptableArea, out PackingRectangle boundsTmp))
                {
                    // We have a better solution!
                    // We update the variables tracking the current best solution
                    bounds          = boundsTmp;
                    currentBestArea = boundsTmp.Area;
                    binSize         = bounds.BiggerSide;

                    // We swap tmpBest and currentBest
                    PackingRectangle[] swaptmp = tmpBest;
                    tmpBest     = currentBest;
                    currentBest = swaptmp;
                    hasSolution = true;
                }
            }

            if (!hasSolution)
            {
                throw new Exception("Failed to find a solution. (Do your rectangles have a size close to uint.MaxValue or is your stepSize too high?)");
            }

            // The solution should be in the "rectangles" array passed as parameter.
            if (currentBest != rectangles)
            {
                currentBest.CopyTo(rectangles, 0);
            }

            // We return the list so it can be used in subsequent pack operations.
            ReturnList(emptySpaces);
        }
示例#3
0
        /// <summary>
        /// Tries to find a solution with the smallest bin size possible, packing
        /// the rectangles in the order in which the were provided.
        /// </summary>
        /// <param name="emptySpaces">The list of empty spaces for reusing.</param>
        /// <param name="rectangles">The rectangles to pack. Might get swapped with "tmpArray".</param>
        /// <param name="tmpArray">A temporary array the function needs. Might get swapped with "rectangles".</param>
        /// <param name="binSize">The maximum bin size to try.</param>
        /// <param name="stepSize">The amount by which to increment/decrement size when trying to pack another bin.</param>
        /// <param name="acceptableArea">Stops searching once a bin with this area or less is found.</param>
        /// <param name="bounds">The bounds of the resulting bin (0, 0, width, height).</param>
        /// <returns>Whether a solution was found.</returns>
        private static bool TryFindBestBin(List <PackingRectangle> emptySpaces, ref PackingRectangle[] rectangles,
                                           ref PackingRectangle[] tmpArray, uint binSize, uint stepSize, uint acceptableArea, out PackingRectangle bounds)
        {
            // We set boundsWidth and boundsHeight to these initial
            // values so they're not good enough for acceptableArea.
            uint boundsWidth  = acceptableArea + 1;
            uint boundsHeight = 1;

            bounds = default;

            // We try packing the rectangles until we either fail, or find a solution with acceptable area.
            while (boundsWidth * boundsHeight > acceptableArea &&
                   TryPackAsOrdered(emptySpaces, rectangles, tmpArray, binSize, binSize, out boundsWidth, out boundsHeight))
            {
                bounds.Width  = boundsWidth;
                bounds.Height = boundsHeight;
                PackingRectangle[] swaptmp = rectangles;
                rectangles = tmpArray;
                tmpArray   = swaptmp;

                // When we're getting close to the final result, we'll reduce the bin size by
                // stepSize. But if the final bin size ends up requiring multiple steps to get
                // there, that's very inefficient. So in these cases we just take the bigger
                // side of the bounds rectangle, which makes everything MUCH faster because the
                // packing algorithm already tries to make things as square as possible.
                binSize = Math.Min(binSize - stepSize, Math.Max(boundsWidth, boundsHeight));
            }

            // We return true if we've found any solution. Otherwise, false.
            return(bounds.Width != 0 && bounds.Height != 0);
        }