123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- #region License
- // Copyright 2021 Kastellanos Nikolaos
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #endregion
- using System;
- using System.Collections.Generic;
- using Microsoft.Xna.Framework;
- namespace nkast.Aether.Content.Pipeline
- {
- internal static class TilePacker
- {
- internal static IList<TileContent> ArrangeGlyphs(IList<TileContent> sourceTiles,
- int tileWidth, int tileHeight,
- bool requirePOT, bool requireSquare)
- {
- // copy tiles to destTiles
- List<TileContent> destTiles = new List<TileContent>();
- for (int i = 0; i < sourceTiles.Count; i++)
- {
- TileContent srcTile = sourceTiles[i];
- TileContent dstTile = new TileContent(srcTile);
- destTiles.Add(dstTile);
- }
-
- for (int i = 0; i < destTiles.Count; i++)
- {
- TileContent dstTile = destTiles[i];
- dstTile.DstBounds.Width = tileWidth;
- dstTile.DstBounds.Height = tileHeight;
- }
-
- // Sort so the largest glyphs get arranged first.
- destTiles.Sort(CompareTileSizes);
- // Work out how big the output bitmap should be.
- int outputWidth = EstimateOutputWidth(destTiles);
- outputWidth = MakeValidTextureSize(outputWidth, requirePOT);
- int outputHeight = 0;
- // Choose positions for each glyph, one at a time.
- for (int i = 0; i < destTiles.Count; i++)
- {
- PositionGlyph(destTiles, i, outputWidth);
- outputHeight = Math.Max(outputHeight, destTiles[i].DstBounds.Y + destTiles[i].DstBounds.Height);
- }
- // Create the merged output bitmap.
- outputHeight = MakeValidTextureSize(outputHeight, requirePOT);
- if (requireSquare)
- {
- outputHeight = Math.Max(outputWidth, outputHeight);
- outputWidth = outputHeight;
- }
- return destTiles;
- }
-
- static void PositionGlyph(List<TileContent> glyphs, int index, int outputWidth)
- {
- int x = 0;
- int y = 0;
- while (true)
- {
- // Is this position free for us to use?
- int intersects = FindIntersectingTile(glyphs, index, x, y);
- if (intersects < 0)
- {
- glyphs[index].DstBounds.X = x;
- glyphs[index].DstBounds.Y = y;
- return;
- }
- // Skip past the existing glyph that we collided with.
- x = glyphs[intersects].DstBounds.X + glyphs[intersects].DstBounds.Width;
- // If we ran out of room to move to the right, try the next line down instead.
- //if (x + glyphs[index].SrcBounds.Width > outputWidth)
- if (x + glyphs[index].DstBounds.Width > outputWidth)
- {
- x = 0;
- y++;
- }
- }
- }
-
- // Checks if a proposed glyph position collides with anything that we already arranged.
- static int FindIntersectingTile(List<TileContent> glyphs, int index, int x, int y)
- {
- Rectangle bounds = glyphs[index].DstBounds;
- for (int i = 0; i < index; i++)
- {
- Rectangle other = glyphs[i].DstBounds;
- if (other.X >= x + bounds.Width)
- continue;
- if (other.X + other.Width <= x)
- continue;
- if (other.Y >= y + bounds.Height)
- continue;
- if (other.Y + other.Height <= y)
- continue;
- return i;
- }
- return -1;
- }
- static int CompareTileSizes(TileContent a, TileContent b)
- {
- int res = b.DstBounds.Height.CompareTo(a.DstBounds.Height);
- if (res == 0)
- res = b.DstBounds.Width.CompareTo(a.DstBounds.Width);
- return res;
- }
- static int EstimateOutputWidth(IList<TileContent> sourceGlyphs)
- {
- int maxWidth = 0;
- int totalSize = 0;
- foreach (TileContent sourceGlyph in sourceGlyphs)
- {
- maxWidth = Math.Max(maxWidth, sourceGlyph.DstBounds.Width);
- totalSize += sourceGlyph.DstBounds.Width * sourceGlyph.DstBounds.Height;
- }
- int width = Math.Max((int)Math.Sqrt(totalSize), maxWidth);
- return width;
- }
- // Rounds a value up to the next larger valid texture size.
- static int MakeValidTextureSize(int value, bool requirePowerOfTwo)
- {
- // In case we want to compress the texture, make sure the size is a multiple of 4.
- const int blockSize = 4;
- if (requirePowerOfTwo)
- {
- // Round up to a power of two.
- int powerOfTwo = blockSize;
- while (powerOfTwo < value)
- powerOfTwo <<= 1;
- return powerOfTwo;
- }
- else
- {
- // Round up to the specified block size.
- return (value + blockSize - 1) & ~(blockSize - 1);
- }
- }
- }
- }
|