/// <summary> /// Packs the provided texture elementsToPack into <see cref="AtlasTextureLayouts"/>, given the provided heuristic algorithm. /// </summary> /// <param name="textureElements">The texture elementsToPack to pack</param> /// <param name="algorithm">Packing algorithm to use</param> /// <returns>True indicates all textures could be packed; False otherwise</returns> private bool PackTextures(List <AtlasTextureElement> textureElements, TexturePackingMethod algorithm) { var elementsToPack = textureElements; if (elementsToPack.Count == 0) // always successful if there is no element to pack (note we do not create a layout) { return(true); } do { // Do not create the atlas if all the elements are "empty". We don't want empty atlas. if (textureElements.All(e => e.SourceRegion.IsEmpty())) { return(true); } List <AtlasTextureElement> remainingElements; var atlasLayout = CreateBestAtlasLayout(elementsToPack, algorithm, out remainingElements); // Check if at least one element could be packed in the texture. if (elementsToPack.Count == remainingElements.Count) { return(false); } elementsToPack = remainingElements; atlasTextureLayouts.Add(atlasLayout); }while (AllowMultipack && elementsToPack.Count > 0); return(elementsToPack.Count == 0); }
/// <summary> /// Packs input elements using the MaxRects algorithm. /// Note that any element that could be packed is removed from the elementsToPack collection. /// </summary> /// <param name="elementsToPack">a list of rectangles to be packed</param> /// <param name="method">MaxRects heuristic method which default value is BestShortSideFit</param> public void PackRectangles(List <AtlasTextureElement> elementsToPack, TexturePackingMethod method = TexturePackingMethod.BestShortSideFit) { var bestRectangle = new RotableRectangle(); // Prune all the empty elements (elements with null region) from the list of elements to pack // Reason: reduce the size of the atlas and wrap/mirror/clamp border mode are undetermined for empty elements. for (int i = elementsToPack.Count - 1; i >= 0; --i) { if (elementsToPack[i].SourceRegion.IsEmpty()) { elementsToPack.RemoveAt(i); } } // Pack the elements. while (elementsToPack.Count > 0) { var bestScore1 = int.MaxValue; var bestScore2 = int.MaxValue; var bestRectangleIndex = -1; for (var i = 0; i < elementsToPack.Count; ++i) { int score1; int score2; var element = elementsToPack[i]; var width = element.SourceRegion.Width + 2 * element.BorderSize; var height = element.SourceRegion.Height + 2 * element.BorderSize; var pickedRectangle = ChooseTargetPosition(width, height, method, out score1, out score2); if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) // Found the new best free region to hold a rectangle { bestScore1 = score1; bestScore2 = score2; bestRectangleIndex = i; bestRectangle = pickedRectangle; } } // Could not find any free region to hold a rectangle, terminate packing process if (bestRectangleIndex == -1) { break; } // Update the free space of the packer TakeSpaceForRectangle(bestRectangle); // Update the packed element var packedElement = elementsToPack[bestRectangleIndex]; packedElement.DestinationRegion = bestRectangle; // Update the packed and remaining element lists packedElements.Add(packedElement); elementsToPack.RemoveAt(bestRectangleIndex); } }
/// <summary> /// Packs input elements using the MaxRects algorithm. /// Note that any element that could be packed is removed from the elementsToPack collection. /// </summary> /// <param name="elementsToPack">a list of rectangles to be packed</param> /// <param name="method">MaxRects heuristic method which default value is BestShortSideFit</param> public void PackRectangles(List<AtlasTextureElement> elementsToPack, TexturePackingMethod method = TexturePackingMethod.BestShortSideFit) { var bestRectangle = new RotableRectangle(); // Prune all the empty elements (elements with null region) from the list of elements to pack // Reason: reduce the size of the atlas and wrap/mirror/clamp border mode are undetermined for empty elements. for (int i = elementsToPack.Count-1; i >= 0; --i) { if (elementsToPack[i].SourceRegion.IsEmpty()) elementsToPack.RemoveAt(i); } // Pack the elements. while (elementsToPack.Count > 0) { var bestScore1 = int.MaxValue; var bestScore2 = int.MaxValue; var bestRectangleIndex = -1; for (var i = 0; i < elementsToPack.Count; ++i) { int score1; int score2; var element = elementsToPack[i]; var width = element.SourceRegion.Width + 2*element.BorderSize; var height = element.SourceRegion.Height+ 2*element.BorderSize; var pickedRectangle = ChooseTargetPosition(width, height, method, out score1, out score2); if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) // Found the new best free region to hold a rectangle { bestScore1 = score1; bestScore2 = score2; bestRectangleIndex = i; bestRectangle = pickedRectangle; } } // Could not find any free region to hold a rectangle, terminate packing process if (bestRectangleIndex == -1) break; // Update the free space of the packer TakeSpaceForRectangle(bestRectangle); // Update the packed element var packedElement = elementsToPack[bestRectangleIndex]; packedElement.DestinationRegion = bestRectangle; // Update the packed and remaining element lists packedElements.Add(packedElement); elementsToPack.RemoveAt(bestRectangleIndex); } }
/// <summary> /// Determines a target position to place a given rectangle by a given heuristic method /// </summary> /// <param name="method">A heuristic placement method</param> /// <param name="score1">First score</param> /// <param name="score2">Second score</param> /// <param name="width">The width of the element to place</param> /// <param name="height">The height of the element to place</param> /// <returns></returns> private RotableRectangle ChooseTargetPosition(int width, int height, TexturePackingMethod method, out int score1, out int score2) { var bestNode = new RotableRectangle(); // null sized rectangle fits everywhere with a perfect score. if (width == 0 || height == 0) { score1 = 0; score2 = 0; return(bestNode); } score1 = int.MaxValue; score2 = int.MaxValue; switch (method) { case TexturePackingMethod.BestShortSideFit: bestNode = FindPositionForNewNodeBestShortSideFit(width, height, out score1, ref score2); break; case TexturePackingMethod.BottomLeftRule: bestNode = FindPositionForNewNodeBottomLeft(width, height, out score1, ref score2); break; case TexturePackingMethod.ContactPointRule: bestNode = FindPositionForNewNodeContactPoint(width, height, out score1); score1 *= -1; break; case TexturePackingMethod.BestLongSideFit: bestNode = FindPositionForNewNodeBestLongSideFit(width, height, ref score2, out score1); break; case TexturePackingMethod.BestAreaFit: bestNode = FindPositionForNewNodeBestAreaFit(width, height, out score1, ref score2); break; default: throw new ArgumentOutOfRangeException("method"); } // there is no available space big enough to fit the rectangle if (bestNode.Height == 0) { score1 = int.MaxValue; score2 = int.MaxValue; } return(bestNode); }
/// <summary> /// Create the best atlas layout possible given the elementsToPack to pack, the algorithm and the atlas maximum size. /// Note: when all the elementsToPack cannot fit into the texture, it tries to pack as much as possible of them. /// </summary> /// <returns>False if</returns> private AtlasTextureLayout CreateBestAtlasLayout(List <AtlasTextureElement> elementsToPack, TexturePackingMethod algorithm, out List <AtlasTextureElement> remainingElements) { remainingElements = elementsToPack; var textureAtlas = new AtlasTextureLayout(); var bestElementPackedCount = int.MaxValue; // Generate sub size array var subSizeArray = CreateSubSizeArray(atlasMaxSize.X, atlasMaxSize.Y, 512, 512); foreach (var subArray in subSizeArray) { var currentRemaingElements = new List <AtlasTextureElement>(elementsToPack); // Reset packer state maxRectPacker.Initialize(subArray.Width, subArray.Height, AllowRotation); // Pack maxRectPacker.PackRectangles(currentRemaingElements, algorithm); // Find true size from packed regions var packedSize = CalculatePackedRectanglesBound(maxRectPacker.PackedElements); // Alter the size of atlas so that it is a power of two if (!AllowNonPowerOfTwo) { packedSize.Width = MathUtil.NextPowerOfTwo(packedSize.Width); packedSize.Height = MathUtil.NextPowerOfTwo(packedSize.Height); if (packedSize.Width > subArray.Width || packedSize.Height > subArray.Height) { continue; } } if (currentRemaingElements.Count >= bestElementPackedCount) { continue; } // Found new best pack, cache it bestElementPackedCount = currentRemaingElements.Count; // Resize texture atlas textureAtlas.Width = packedSize.Width; textureAtlas.Height = packedSize.Height; textureAtlas.Textures.Clear(); // Store all packed regions into Atlas foreach (var element in maxRectPacker.PackedElements) { textureAtlas.Textures.Add(element.Clone()); } remainingElements = currentRemaingElements; } return(textureAtlas); }
/// <summary> /// Create the best atlas layout possible given the elementsToPack to pack, the algorithm and the atlas maximum size. /// Note: when all the elementsToPack cannot fit into the texture, it tries to pack as much as possible of them. /// </summary> /// <returns>False if</returns> private AtlasTextureLayout CreateBestAtlasLayout(List<AtlasTextureElement> elementsToPack, TexturePackingMethod algorithm, out List<AtlasTextureElement> remainingElements) { remainingElements = elementsToPack; var textureAtlas = new AtlasTextureLayout(); var bestElementPackedCount = int.MaxValue; // Generate sub size array var subSizeArray = CreateSubSizeArray(atlasMaxSize.X, atlasMaxSize.Y, 512, 512); foreach (var subArray in subSizeArray) { var currentRemaingElements = new List<AtlasTextureElement>(elementsToPack); // Reset packer state maxRectPacker.Initialize(subArray.Width, subArray.Height, AllowRotation); // Pack maxRectPacker.PackRectangles(currentRemaingElements, algorithm); // Find true size from packed regions var packedSize = CalculatePackedRectanglesBound(maxRectPacker.PackedElements); // Alter the size of atlas so that it is a power of two if (!AllowNonPowerOfTwo) { packedSize.Width = MathUtil.NextPowerOfTwo(packedSize.Width); packedSize.Height = MathUtil.NextPowerOfTwo(packedSize.Height); if (packedSize.Width > subArray.Width || packedSize.Height > subArray.Height) continue; } if (currentRemaingElements.Count >= bestElementPackedCount) continue; // Found new best pack, cache it bestElementPackedCount = currentRemaingElements.Count; // Resize texture atlas textureAtlas.Width = packedSize.Width; textureAtlas.Height = packedSize.Height; textureAtlas.Textures.Clear(); // Store all packed regions into Atlas foreach (var element in maxRectPacker.PackedElements) textureAtlas.Textures.Add(element.Clone()); remainingElements = currentRemaingElements; } return textureAtlas; }
/// <summary> /// Packs the provided texture elementsToPack into <see cref="AtlasTextureLayouts"/>, given the provided heuristic algorithm. /// </summary> /// <param name="textureElements">The texture elementsToPack to pack</param> /// <param name="algorithm">Packing algorithm to use</param> /// <returns>True indicates all textures could be packed; False otherwise</returns> private bool PackTextures(List<AtlasTextureElement> textureElements, TexturePackingMethod algorithm) { var elementsToPack = textureElements; if (elementsToPack.Count == 0) // always successful if there is no element to pack (note we do not create a layout) return true; do { // Do not create the atlas if all the elements are "empty". We don't want empty atlas. if (textureElements.All(e => e.SourceRegion.IsEmpty())) return true; List<AtlasTextureElement> remainingElements; var atlasLayout = CreateBestAtlasLayout(elementsToPack, algorithm, out remainingElements); // Check if at least one element could be packed in the texture. if (elementsToPack.Count == remainingElements.Count) return false; elementsToPack = remainingElements; atlasTextureLayouts.Add(atlasLayout); } while (AllowMultipack && elementsToPack.Count > 0); return elementsToPack.Count == 0; }
/// <summary> /// Determines a target position to place a given rectangle by a given heuristic method /// </summary> /// <param name="method">A heuristic placement method</param> /// <param name="score1">First score</param> /// <param name="score2">Second score</param> /// <param name="width">The width of the element to place</param> /// <param name="height">The height of the element to place</param> /// <returns></returns> private RotableRectangle ChooseTargetPosition(int width, int height, TexturePackingMethod method, out int score1, out int score2) { var bestNode = new RotableRectangle(); // null sized rectangle fits everywhere with a perfect score. if (width == 0 || height == 0) { score1 = 0; score2 = 0; return bestNode; } score1 = int.MaxValue; score2 = int.MaxValue; switch (method) { case TexturePackingMethod.BestShortSideFit: bestNode = FindPositionForNewNodeBestShortSideFit(width, height, out score1, ref score2); break; case TexturePackingMethod.BottomLeftRule: bestNode = FindPositionForNewNodeBottomLeft(width, height, out score1, ref score2); break; case TexturePackingMethod.ContactPointRule: bestNode = FindPositionForNewNodeContactPoint(width, height, out score1); score1 *= -1; break; case TexturePackingMethod.BestLongSideFit: bestNode = FindPositionForNewNodeBestLongSideFit(width, height, ref score2, out score1); break; case TexturePackingMethod.BestAreaFit: bestNode = FindPositionForNewNodeBestAreaFit(width, height, out score1, ref score2); break; default: throw new ArgumentOutOfRangeException("method"); } // there is no available space big enough to fit the rectangle if (bestNode.Height == 0) { score1 = int.MaxValue; score2 = int.MaxValue; } return bestNode; }