/// <summary> /// Calculates a width for the packed texture. /// </summary> /// <param name="sprites">Sprites to pack.</param> /// <param name="isPowerOfTwo">Indicates whether the width must be a power of two.</param> /// <returns>Width of the packed texture.</returns> static int CalculateWidth(Sprite[] sprites, bool isPowerOfTwo) { int[] spriteWidths = sprites.Select(sprite => sprite.Width).OrderBy(w => w).ToArray(); int maxWidth = spriteWidths[spriteWidths.Length - 1]; int medianWidth = spriteWidths[spriteWidths.Length / 2]; int width = medianWidth * (int)Math.Round(Math.Sqrt(sprites.Length)); // Ensure the width calculated is not less than the widest sprite. If it is, we use the widest sprite width instead. // width = Math.Max(width, maxWidth); if (isPowerOfTwo) { width = SpritePacker.CalculateNextPowerOfTwo(width); } return(width); }
static void FindSpritePosition(Sprite[] sprites, int index, int textureWidth, out int x, out int y) { x = 0; y = 0; while (true) { if (!SpritePacker.IsIntersectingPreviousSprite(sprites, index, x, y, out int intersectionIndex)) { return; } // Skip to the right of the previously positioned sprite that we collided with. Howver, move down when we // can no longer fit on the current line. // x = sprites[intersectionIndex].X + sprites[intersectionIndex].Width; if (x + sprites[index].Width > textureWidth) { x = 0; y++; } } }
/// <summary> /// Packs many individual sprites into a single sprite and records the individual sprite names and locations inside the large sprite. /// </summary> /// <param name="configuration">Settings controlling how to pack sprites.</param> /// <param name="spritePaths">File paths of sprites to pack.</param> /// <returns>Object containing the packed texture along with the names and locations of the individual sprites inside of it.</returns> public static PackedTexture Pack(PackConfiguration configuration, string[] spritePaths) { if (configuration == null) { throw new ArgumentNullException(nameof(configuration)); } if (spritePaths == null) { throw new ArgumentNullException(nameof(spritePaths)); } if (spritePaths.Length <= 0) { throw new ArgumentException("Must contain at least one element.", nameof(spritePaths)); } Sprite[] sprites = SpritePacker.LoadSprites(spritePaths.Distinct().ToArray(), configuration.Padding); int textureWidth = SpritePacker.CalculateWidth(sprites, configuration.IsPowerOfTwo); int textureHeight = 0; // Find the position of each sprite in the final texture. // Sprite[] orderedSprites = SpritePacker.OrderSprites(sprites); for (int i = 0; i < orderedSprites.Length; i++) { SpritePacker.FindSpritePosition(orderedSprites, i, textureWidth, out int x, out int y); orderedSprites[i].X = x; orderedSprites[i].Y = y; // Update texture height when the just positioned sprite pushes the boundary out. // textureHeight = Math.Max(textureHeight, orderedSprites[i].Y + orderedSprites[i].Height); } // Ensure height dimension is a power of two when the user asked for as much. // if (configuration.IsPowerOfTwo) { textureHeight = SpritePacker.CalculateNextPowerOfTwo(textureHeight); } // Ensure both dimensions are square when user asked for as much. // if (configuration.IsSquare) { int maximum = Math.Max(textureWidth, textureHeight); textureWidth = maximum; textureHeight = maximum; } // Ensure the final dimensions are within the user's limits. // if (textureWidth > configuration.MaximumWidth || textureHeight > configuration.MaximumHeight) { throw new Exception($"Packed texture dimensions ({textureWidth} x {textureHeight}) exceed the maximum requested ({configuration.MaximumWidth} x {configuration.MaximumHeight})."); } return(new PackedTexture { SpriteNamesToIndexMapping = SpritePacker.BuildNameToIndexMap(sprites), SpriteRectangles = SpritePacker.BuildRectangles(sprites, configuration.Padding), Texture = SpritePacker.BuildTexture(sprites, textureWidth, textureHeight, configuration.Padding), }); }