Просмотр исходного кода

Modified texture packing utility so I can re-use it for shadow maps

BearishSun 8 лет назад
Родитель
Сommit
2a1db1ce3f

+ 12 - 12
Source/BansheeFontImporter/Source/BsFontImporter.cpp

@@ -4,9 +4,7 @@
 #include "BsFontImportOptions.h"
 #include "BsPixelData.h"
 #include "BsTexture.h"
-#include "BsResources.h"
-#include "BsDebug.h"
-#include "BsTexAtlasGenerator.h"
+#include "BsTextureAtlasLayout.h"
 #include "BsCoreApplication.h"
 #include "BsCoreThread.h"
 
@@ -124,7 +122,7 @@ namespace bs
 			SPtr<FontBitmap> fontData = bs_shared_ptr_new<FontBitmap>();
 
 			// Get all char sizes so we can generate texture layout
-			Vector<TexAtlasElementDesc> atlasElements;
+			Vector<TextureAtlasUtility::Element> atlasElements;
 			Map<UINT32, UINT32> seqIdxToCharIdx;
 			for(auto iter = charIndexRanges.begin(); iter != charIndexRanges.end(); ++iter)
 			{
@@ -142,7 +140,7 @@ namespace bs
 
 					FT_GlyphSlot slot = face->glyph;
 
-					TexAtlasElementDesc atlasElement;
+					TextureAtlasUtility::Element atlasElement;
 					atlasElement.input.width = slot->bitmap.width;
 					atlasElement.input.height = slot->bitmap.rows;
 
@@ -165,7 +163,7 @@ namespace bs
 
 				FT_GlyphSlot slot = face->glyph;
 
-				TexAtlasElementDesc atlasElement;
+				TextureAtlasUtility::Element atlasElement;
 				atlasElement.input.width = slot->bitmap.width;
 				atlasElement.input.height = slot->bitmap.rows;
 
@@ -173,8 +171,8 @@ namespace bs
 			}
 
 			// Create an optimal layout for character bitmaps
-			TexAtlasGenerator texAtlasGen(false, MAXIMUM_TEXTURE_SIZE, MAXIMUM_TEXTURE_SIZE);
-			Vector<TexAtlasPageDesc> pages = texAtlasGen.createAtlasLayout(atlasElements);
+			Vector<TextureAtlasUtility::Page> pages = TextureAtlasUtility::createAtlasLayout(atlasElements, 64, 64,
+				MAXIMUM_TEXTURE_SIZE, MAXIMUM_TEXTURE_SIZE, true);
 
 			INT32 baselineOffset = 0;
 			UINT32 lineHeight = 0;
@@ -192,13 +190,15 @@ namespace bs
 				UINT8* pixelBuffer = pixelData->getData();
 				memset(pixelBuffer, 0, bufferSize);
 
-				for(size_t elementIdx = 0; elementIdx < atlasElements.size(); elementIdx++)
+				for(size_t i = 0; i < atlasElements.size(); i++)
 				{
 					// Copy character bitmap
-					if(atlasElements[elementIdx].output.page != pageIdx)
+					if(atlasElements[i].output.page != pageIdx)
 						continue;
 
-					TexAtlasElementDesc curElement = atlasElements[elementIdx];
+					TextureAtlasUtility::Element curElement = atlasElements[i];
+					UINT32 elementIdx = curElement.output.idx;
+					
 					bool isMissingGlypth = elementIdx == (atlasElements.size() - 1); // It's always the last element
 
 					UINT32 charIdx = 0;
@@ -343,8 +343,8 @@ namespace bs
 				}
 
 				newTex->setName(L"FontPage" + toWString((UINT32)fontData->texturePages.size()));
-				fontData->texturePages.push_back(newTex);
 
+				fontData->texturePages.push_back(newTex);
 				pageIdx++;
 			}
 

+ 2 - 2
Source/BansheeUtility/CMakeSources.cmake

@@ -34,7 +34,7 @@ set(BS_BANSHEEUTILITY_SRC_UNIX
 
 set(BS_BANSHEEUTILITY_INC_IMAGE
 	"Include/BsColor.h"
-	"Include/BsTexAtlasGenerator.h"
+	"Include/BsTextureAtlasLayout.h"
 )
 
 set(BS_BANSHEEUTILITY_INC_STRING
@@ -45,7 +45,7 @@ set(BS_BANSHEEUTILITY_INC_STRING
 
 set(BS_BANSHEEUTILITY_SRC_IMAGE
 	"Source/BsColor.cpp"
-	"Source/BsTexAtlasGenerator.cpp"
+	"Source/BsTextureAtlasLayout.cpp"
 )
 
 set(BS_BANSHEEUTILITY_SRC_GENERAL

+ 190 - 190
Source/BansheeUtility/Include/BsBitwise.h

@@ -58,33 +58,33 @@ namespace bs
 		} field;
 	};
 
-    /** Class for manipulating bit patterns. */
-    class Bitwise 
+	/** Class for manipulating bit patterns. */
+	class Bitwise
 	{
-    public:
+	public:
 		/** Returns the most significant bit set in a value. */
 		static UINT32 mostSignificantBitSet(unsigned int value)
-        {
+		{
 			UINT32 result = 0;
-            while (value != 0) {
-                ++result;
-                value >>= 1;
-            }
-            return result-1;
-        }
+			while (value != 0) {
+				++result;
+				value >>= 1;
+			}
+			return result - 1;
+		}
 
 		/** Returns the power-of-two number greater or equal to the provided value. */
-        static UINT32 nextPow2(UINT32 n)
-        {
-            --n;            
-            n |= n >> 16;
-            n |= n >> 8;
-            n |= n >> 4;
-            n |= n >> 2;
-            n |= n >> 1;
-            ++n;
-            return n;
-        }
+		static UINT32 nextPow2(UINT32 n)
+		{
+			--n;
+			n |= n >> 16;
+			n |= n >> 8;
+			n |= n >> 4;
+			n |= n >> 2;
+			n |= n >> 1;
+			++n;
+			return n;
+		}
 
 		/** Returns the power-of-two number closest to the provided value. */
 		static UINT32 closestPow2(UINT32 n)
@@ -99,15 +99,15 @@ namespace bs
 		}
 
 		/** Determines whether the number is power-of-two or not. */
-        template<typename T>
-        static bool isPow2(T n)
-        {
-            return (n & (n-1)) == 0;
-        }
+		template<typename T>
+		static bool isPow2(T n)
+		{
+			return (n & (n - 1)) == 0;
+		}
 
 		/** Returns the number of bits a pattern must be shifted right by to remove right-hand zeros. */
 		template<typename T>
-        static unsigned int getBitShift(T mask)
+		static unsigned int getBitShift(T mask)
 		{
 			if (mask == 0)
 				return 0;
@@ -122,7 +122,7 @@ namespace bs
 
 		/** Takes a value with a given src bit mask, and produces another value with a desired bit mask. */
 		template<typename SrcT, typename DestT>
-        static DestT convertBitPattern(SrcT srcValue, SrcT srcBitMask, DestT destBitMask)
+		static DestT convertBitPattern(SrcT srcValue, SrcT srcBitMask, DestT destBitMask)
 		{
 			// Mask off irrelevant source value bits (if any)
 			srcValue = srcValue & srcBitMask;
@@ -147,191 +147,191 @@ namespace bs
 		 * Convert N bit colour channel value to P bits. It fills P bits with the bit pattern repeated. 
 		 * (this is /((1<<n)-1) in fixed point).
 		 */
-        static unsigned int fixedToFixed(UINT32 value, unsigned int n, unsigned int p) 
-        {
-            if(n > p) 
-            {
-                // Less bits required than available; this is easy
-                value >>= n-p;
-            } 
-            else if(n < p)
-            {
-                // More bits required than are there, do the fill
-                // Use old fashioned division, probably better than a loop
-                if(value == 0)
-                        value = 0;
-                else if(value == (static_cast<unsigned int>(1)<<n)-1)
-                        value = (1<<p)-1;
-                else    value = value*(1<<p)/((1<<n)-1);
-            }
-            return value;    
-        }
+		static unsigned int fixedToFixed(UINT32 value, unsigned int n, unsigned int p)
+		{
+			if (n > p)
+			{
+				// Less bits required than available; this is easy
+				value >>= n - p;
+			}
+			else if (n < p)
+			{
+				// More bits required than are there, do the fill
+				// Use old fashioned division, probably better than a loop
+				if (value == 0)
+					value = 0;
+				else if (value == (static_cast<unsigned int>(1) << n) - 1)
+					value = (1 << p) - 1;
+				else    value = value*(1 << p) / ((1 << n) - 1);
+			}
+			return value;
+		}
 
 		/** 
 		 * Convert floating point color channel value between 0.0 and 1.0 (otherwise clamped) to integer of a certain 
 		 * number of bits. Works for any value of bits between 0 and 31.
 		 */
-        static unsigned int floatToFixed(const float value, const unsigned int bits)
-        {
-            if(value <= 0.0f) return 0;
-            else if (value >= 1.0f) return (1<<bits)-1;
-            else return (unsigned int)(value * (1<<bits));     
-        }
+		static unsigned int floatToFixed(const float value, const unsigned int bits)
+		{
+			if (value <= 0.0f) return 0;
+			else if (value >= 1.0f) return (1 << bits) - 1;
+			else return (unsigned int)(value * (1 << bits));
+		}
 
 		/** Fixed point to float. */
-        static float fixedToFloat(unsigned value, unsigned int bits)
-        {
-            return (float)value/(float)((1<<bits)-1);
-        }
+		static float fixedToFloat(unsigned value, unsigned int bits)
+		{
+			return (float)value / (float)((1 << bits) - 1);
+		}
 
 		/** Write a n*8 bits integer value to memory in native endian. */
-        static void intWrite(void *dest, const int n, const unsigned int value)
-        {
-            switch(n) {
-                case 1:
-                    ((UINT8*)dest)[0] = (UINT8)value;
-                    break;
-                case 2:
-                    ((UINT16*)dest)[0] = (UINT16)value;
-                    break;
-                case 3:
+		static void intWrite(void *dest, const int n, const unsigned int value)
+		{
+			switch(n) {
+				case 1:
+					((UINT8*)dest)[0] = (UINT8)value;
+					break;
+				case 2:
+					((UINT16*)dest)[0] = (UINT16)value;
+					break;
+				case 3:
 #if BS_ENDIAN == BS_ENDIAN_BIG      
-                    ((UINT8*)dest)[0] = (UINT8)((value >> 16) & 0xFF);
-                    ((UINT8*)dest)[1] = (UINT8)((value >> 8) & 0xFF);
-                    ((UINT8*)dest)[2] = (UINT8)(value & 0xFF);
+					((UINT8*)dest)[0] = (UINT8)((value >> 16) & 0xFF);
+					((UINT8*)dest)[1] = (UINT8)((value >> 8) & 0xFF);
+					((UINT8*)dest)[2] = (UINT8)(value & 0xFF);
 #else
-                    ((UINT8*)dest)[2] = (UINT8)((value >> 16) & 0xFF);
-                    ((UINT8*)dest)[1] = (UINT8)((value >> 8) & 0xFF);
-                    ((UINT8*)dest)[0] = (UINT8)(value & 0xFF);
+					((UINT8*)dest)[2] = (UINT8)((value >> 16) & 0xFF);
+					((UINT8*)dest)[1] = (UINT8)((value >> 8) & 0xFF);
+					((UINT8*)dest)[0] = (UINT8)(value & 0xFF);
 #endif
-                    break;
-                case 4:
-                    ((UINT32*)dest)[0] = (UINT32)value;                
-                    break;                
-            }        
-        }
+					break;
+				case 4:
+					((UINT32*)dest)[0] = (UINT32)value;                
+					break;                
+			}        
+		}
 
 		/** Read a n*8 bits integer value to memory in native endian. */
-        static unsigned int intRead(const void *src, int n) {
-            switch(n) {
-                case 1:
-                    return ((UINT8*)src)[0];
-                case 2:
-                    return ((UINT16*)src)[0];
-                case 3:
+		static unsigned int intRead(const void *src, int n) {
+			switch(n) {
+				case 1:
+					return ((UINT8*)src)[0];
+				case 2:
+					return ((UINT16*)src)[0];
+				case 3:
 #if BS_ENDIAN == BS_ENDIAN_BIG      
-                    return ((UINT32)((UINT8*)src)[0]<<16)|
-                            ((UINT32)((UINT8*)src)[1]<<8)|
-                            ((UINT32)((UINT8*)src)[2]);
+					return ((UINT32)((UINT8*)src)[0]<<16)|
+							((UINT32)((UINT8*)src)[1]<<8)|
+							((UINT32)((UINT8*)src)[2]);
 #else
-                    return ((UINT32)((UINT8*)src)[0])|
-                            ((UINT32)((UINT8*)src)[1]<<8)|
-                            ((UINT32)((UINT8*)src)[2]<<16);
+					return ((UINT32)((UINT8*)src)[0])|
+							((UINT32)((UINT8*)src)[1]<<8)|
+							((UINT32)((UINT8*)src)[2]<<16);
 #endif
-                case 4:
-                    return ((UINT32*)src)[0];
-            } 
-            return 0; // ?
-        }
+				case 4:
+					return ((UINT32*)src)[0];
+			} 
+			return 0; // ?
+		}
 
 		/** Convert a float32 to a float16 (NV_half_float). */
-        static UINT16 floatToHalf(float i)
-        {
-            union { float f; UINT32 i; } v;
-            v.f = i;
-            return floatToHalfI(v.i);
-        }
+		static UINT16 floatToHalf(float i)
+		{
+			union { float f; UINT32 i; } v;
+			v.f = i;
+			return floatToHalfI(v.i);
+		}
 
 		/** Converts float in UINT32 format to a a half in UINT16 format. */
-        static UINT16 floatToHalfI(UINT32 i)
-        {
-            int s =  (i >> 16) & 0x00008000;
-            int e = ((i >> 23) & 0x000000ff) - (127 - 15);
-            int m =   i        & 0x007fffff;
-        
-            if (e <= 0)
-            {
-                if (e < -10)
-                {
-                    return 0;
-                }
-                m = (m | 0x00800000) >> (1 - e);
-        
-                return static_cast<UINT16>(s | (m >> 13));
-            }
-            else if (e == 0xff - (127 - 15))
-            {
-                if (m == 0) // Inf
-                {
-                    return static_cast<UINT16>(s | 0x7c00);
-                } 
-                else    // NAN
-                {
-                    m >>= 13;
-                    return static_cast<UINT16>(s | 0x7c00 | m | (m == 0));
-                }
-            }
-            else
-            {
-                if (e > 30) // Overflow
-                {
-                    return static_cast<UINT16>(s | 0x7c00);
-                }
-        
-                return static_cast<UINT16>(s | (e << 10) | (m >> 13));
-            }
-        }
-        
+		static UINT16 floatToHalfI(UINT32 i)
+		{
+			int s =  (i >> 16) & 0x00008000;
+			int e = ((i >> 23) & 0x000000ff) - (127 - 15);
+			int m =   i        & 0x007fffff;
+		
+			if (e <= 0)
+			{
+				if (e < -10)
+				{
+					return 0;
+				}
+				m = (m | 0x00800000) >> (1 - e);
+		
+				return static_cast<UINT16>(s | (m >> 13));
+			}
+			else if (e == 0xff - (127 - 15))
+			{
+				if (m == 0) // Inf
+				{
+					return static_cast<UINT16>(s | 0x7c00);
+				} 
+				else    // NAN
+				{
+					m >>= 13;
+					return static_cast<UINT16>(s | 0x7c00 | m | (m == 0));
+				}
+			}
+			else
+			{
+				if (e > 30) // Overflow
+				{
+					return static_cast<UINT16>(s | 0x7c00);
+				}
+		
+				return static_cast<UINT16>(s | (e << 10) | (m >> 13));
+			}
+		}
+		
 		/** Convert a float16 (NV_half_float) to a float32. */
-        static float halfToFloat(UINT16 y)
-        {
-            union { float f; UINT32 i; } v;
-            v.i = halfToFloatI(y);
-            return v.f;
-        }
+		static float halfToFloat(UINT16 y)
+		{
+			union { float f; UINT32 i; } v;
+			v.i = halfToFloatI(y);
+			return v.f;
+		}
 
 		/** Converts a half in UINT16 format to a float in UINT32 format. */
-        static UINT32 halfToFloatI(UINT16 y)
-        {
-            int s = (y >> 15) & 0x00000001;
-            int e = (y >> 10) & 0x0000001f;
-            int m =  y        & 0x000003ff;
-        
-            if (e == 0)
-            {
-                if (m == 0) // Plus or minus zero
-                {
-                    return s << 31;
-                }
-                else // Denormalized number -- renormalize it
-                {
-                    while (!(m & 0x00000400))
-                    {
-                        m <<= 1;
-                        e -=  1;
-                    }
-        
-                    e += 1;
-                    m &= ~0x00000400;
-                }
-            }
-            else if (e == 31)
-            {
-                if (m == 0) // Inf
-                {
-                    return (s << 31) | 0x7f800000;
-                }
-                else // NaN
-                {
-                    return (s << 31) | 0x7f800000 | (m << 13);
-                }
-            }
-        
-            e = e + (127 - 15);
-            m = m << 13;
-        
-            return (s << 31) | (e << 23) | m;
-        }
+		static UINT32 halfToFloatI(UINT16 y)
+		{
+			int s = (y >> 15) & 0x00000001;
+			int e = (y >> 10) & 0x0000001f;
+			int m =  y        & 0x000003ff;
+		
+			if (e == 0)
+			{
+				if (m == 0) // Plus or minus zero
+				{
+					return s << 31;
+				}
+				else // Denormalized number -- renormalize it
+				{
+					while (!(m & 0x00000400))
+					{
+						m <<= 1;
+						e -=  1;
+					}
+		
+					e += 1;
+					m &= ~0x00000400;
+				}
+			}
+			else if (e == 31)
+			{
+				if (m == 0) // Inf
+				{
+					return (s << 31) | 0x7f800000;
+				}
+				else // NaN
+				{
+					return (s << 31) | 0x7f800000 | (m << 13);
+				}
+			}
+		
+			e = e + (127 - 15);
+			m = m << 13;
+		
+			return (s << 31) | (e << 23) | m;
+		}
 
 		/** Converts a 32-bit float to a 10-bit float according to OpenGL packed_float extension. */
 		static UINT32 floatToFloat10(float v)
@@ -492,7 +492,7 @@ namespace bs
 
 			return *(float*)&output;
 		}
-    };
+	};
 
 	/** @} */
 }

+ 3 - 2
Source/BansheeUtility/Include/BsDebug.h

@@ -44,8 +44,9 @@ namespace bs
 		/** Retrieves the Log used by the Debug instance. */
 		Log& getLog() { return mLog; }
 
-		/** Converts raw pixels into a BMP image. See BitmapWriter for more information. */
-		void writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const Path& filePath, bool overwrite = true) const;
+		/** Converts raw pixels into a BMP image and saves it as a file */
+		void writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const Path& filePath, 
+			bool overwrite = true) const;
 
 		/**
 		 * Saves a log about the current state of the application to the specified location.

+ 0 - 111
Source/BansheeUtility/Include/BsTexAtlasGenerator.h

@@ -1,111 +0,0 @@
-//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
-//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-#pragma once
-
-#include "BsPrerequisitesUtil.h"
-#include "BsVector2.h"
-
-namespace bs
-{
-	/** @addtogroup Image
-	 *  @{
-	 */
-
-	/**
-	 * Represents a single element used as in input to TexAtlasGenerator. Usually represents a single texture.
-	 * 			
-	 * @note	input is required to be filled in before passing it to TexAtlasGenerator.
-	 * @note	output will be filled in by TexAtlasGenerator after a call to TexAtlasGenerator::createAtlasLayout().
-	 */
-	struct TexAtlasElementDesc
-	{
-		struct
-		{
-			UINT32 width, height;
-		} input;
-		
-		struct
-		{
-			UINT32 x, y;
-			INT32 page;
-		} output;
-	};
-
-	/** A single page of the texture atlas. */
-	struct TexAtlasPageDesc
-	{
-		UINT32 width, height;
-	};
-
-	class TexAtlasNode;
-
-	/** Organizes a set of textures into a single larger texture (an atlas) by minimizing empty space. */
-	class BS_UTILITY_EXPORT TexAtlasGenerator
-	{
-	public:
-		/**
-		 * Constructs a new texture atlas generator with the provided parameters.
-		 *
-		 * @param[in]	square			(optional) Should the returned texture always be square. (width == height)
-		 * 								This option is only used if @p fixedSize parameter is set to false.
-		 * @param[in]	maxTexWidth 	(optional) Maximum width of the texture. 
-		 * @param[in]	maxTexHeight	(optional) Maximum height of the texture. 
-		 * @param[in]	fixedSize   	(optional) If this field is false, algorithm will try to reduce the size of the texture
-		 * 								if possible. If it is true, the algorithm will always produce textures of the specified
-		 * 								@p maxTexWidth, @p maxTexHeight size.
-		 */
-		TexAtlasGenerator(bool square = false, UINT32 maxTexWidth = 2048, UINT32 maxTexHeight = 2048, bool fixedSize = false);
-
-		/**
-		 * Creates an optimal texture layout by packing texture elements in order to end up with as little empty space 
-		 * as possible.
-		 *
-		 * @param[in]	elements	Elements to process. They need to have their input structures filled in,
-		 * 							and this method will fill output when it returns.
-		 * @return					One or more descriptors that determine the size of the final atlas textures. 
-		 *							Texture elements will reference these pages with their output.page parameter.
-		 *
-		 * @note	
-		 * Algorithm will split elements over multiple textures if they don't fit in a single texture (Determined by 
-		 * maximum texture size).
-		 */
-		Vector<TexAtlasPageDesc> createAtlasLayout(Vector<TexAtlasElementDesc>& elements) const;
-
-	private:
-		bool mSquare;
-		bool mFixedSize;
-		UINT32 mMaxTexWidth;
-		UINT32 mMaxTexHeight;
-
-		/**
-		 * Organize all of the provide elements and place them into minimum number of pages with the specified width and height.
-		 * 			
-		 * Caller must ensure @p elements array has the page indexes reset to -1 before calling, otherwise it will be assumed
-		 * those elements already have assigned pages.
-		 * 			
-		 * Using @p startPage parameter you may add an offset to the generated page indexes.
-		 *
-		 * @return	Number of pages generated.
-		 */
-		int generatePagesForSize(Vector<TexAtlasElementDesc>& elements, UINT32 width, UINT32 height, UINT32 startPage = 0) const;
-
-		/**
-		 * Finds the largest element without a page that fits within the provided node.
-		 *
-		 * @return	Array index of the found page, or -1 if all textures have a page.
-		 */
-		int addLargestTextureWithoutPageThatFits(Vector<TexAtlasElementDesc>& elements, TexAtlasNode& node) const;
-
-		/**
-		 * Scan all of the provided elements and find the largest one that still doesn't have a page assigned.
-		 * 			
-		 * @return	Array index of the found page, or -1 if all textures have a page.
-		 */
-		int findLargestTextureWithoutPage(const Vector<TexAtlasElementDesc>& elements) const;
-
-		/** Sorts all the texture elements so that larget elements come first. */
-		void sortBySize(Vector<TexAtlasElementDesc>& elements) const;
-	};
-
-	/** @} */
-}

+ 135 - 0
Source/BansheeUtility/Include/BsTextureAtlasLayout.h

@@ -0,0 +1,135 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsPrerequisitesUtil.h"
+#include "BsVector2.h"
+
+namespace bs
+{
+	/** @addtogroup Image
+	 *  @{
+	 */
+
+	/** Organizes a set of textures into a single larger texture (an atlas) by minimizing empty space. */
+	class BS_UTILITY_EXPORT TextureAtlasLayout
+	{
+		/** Represent a single node in the texture atlas binary tree. */
+		class TexAtlasNode
+		{
+		public:
+			TexAtlasNode();
+			TexAtlasNode(UINT32 x, UINT32 y, UINT32 width, UINT32 height);
+
+			UINT32 x, y, width, height;
+			UINT32 children[2];
+			bool nodeFull;
+		};
+
+	public:
+		TextureAtlasLayout();
+
+		/**
+		 * Constructs a new texture atlas layout with the provided parameters.
+		 *
+		 * @param[in]	width 			Initial width of the atlas texture. 
+		 * @param[in]	height			Initial height of the atlas texture.
+		 * @param[in]	maxWidth		Maximum width the atlas texture is allowed to grow to, when elements don't fit.
+		 * @param[in]	maxHeight		Maximum height the atlas texture is allowed to grow to, when elements don't fit.
+		 * @param[in]	pow2			When true the resulting atlas size will always be a power of two.
+		 */
+		TextureAtlasLayout(UINT32 width, UINT32 height, UINT32 maxWidth, UINT32 maxHeight, bool pow2 = false);
+
+		/**
+		 * Attempts to add a new element in the layout.
+		 * 
+		 * @param[in]	width	Width of the new element, in pixels.
+		 * @param[in]	height	Height of the new element, in pixels.
+		 * @param[out]	x		Horizontal position of the new element within the atlas. Only valid if method returns true.
+		 * @param[out]	y		Vertical position of the new element within the atlas. Only valid if method returns true.
+		 * @return				True if the element was added to the atlas, false if the element doesn't fit.
+		 */
+		bool addElement(UINT32 width, UINT32 height, UINT32& x, UINT32& y);
+
+		/** Returns the width of the atlas texture, in pixels. */
+		UINT32 getWidth() const { return mWidth; }
+
+		/** Returns the height of the atlas texture, in pixels. */
+		UINT32 getHeight() const { return mHeight; }
+
+	private:
+		/* 
+		 * Attempts to add a new element to the specified layout node. 
+		 * 
+		 * @param[in]	nodeIdx			Index of the node to which to add the element.
+		 * @param[in]	width			Width of the new element, in pixels.
+		 * @param[in]	height			Height of the new element, in pixels.
+		 * @param[out]	x				Horizontal position of the new element within the atlas. Only valid if method
+		 *								returns true.
+		 * @param[out]	y				Vertical position of the new element within the atlas. Only valid if method returns
+		 *								true.
+		 * @param[in]	allowGrowth		When true, the width/height of the atlas will be allowed to grow to fit the element.
+		 * @return						True if the element was added to the atlas, false if the element doesn't fit.
+		 */
+		bool addToNode(UINT32 nodeIdx, UINT32 width, UINT32 height, UINT32& x, UINT32& y, bool allowGrowth);
+
+		UINT32 mWidth;
+		UINT32 mHeight;
+		UINT32 mMaxWidth;
+		UINT32 mMaxHeight;
+		bool mPow2;
+
+		Vector<TexAtlasNode> mNodes;
+	};
+
+	/** Utility class used for texture atlas layouts. */
+	class BS_UTILITY_EXPORT TextureAtlasUtility
+	{
+	public:
+		/**
+		 * Represents a single element used as in input to TextureAtlasUtility. Usually represents a single texture.
+		 * 			
+		 * @note	input is required to be filled in before passing it to TextureAtlasUtility.
+		 * @note	output will be filled after a call to TextureAtlasUtility::createAtlasLayout().
+		 */
+		struct Element
+		{
+			struct
+			{
+				UINT32 width, height;
+			} input;
+		
+			struct
+			{
+				UINT32 x, y;
+				UINT32 idx;
+				INT32 page;
+			} output;
+		};
+
+		/** Describes a single page of the texture atlas. */
+		struct Page
+		{
+			UINT32 width, height;
+		};
+
+		/**
+		 * Creates an optimal texture layout by packing texture elements in order to end up with as little empty space 
+		 * as possible. Algorithm will split elements over multiple textures if they don't fit in a single texture.
+		 *
+		 * @param[in]	elements	Elements to process. They need to have their input structures filled in,
+		 * 							and this method will fill output when it returns.
+		 * @param[in]	width 		Initial width of the atlas texture.
+		 * @param[in]	height		Initial height of the atlas texture.
+		 * @param[in]	maxWidth	Maximum width the atlas texture is allowed to grow to, when elements don't fit.
+		 * @param[in]	maxHeight	Maximum height the atlas texture is allowed to grow to, when elements don't fit.
+		 * @param[in]	pow2		When true the resulting atlas size will always be a power of two.
+		 * @return					One or more descriptors that determine the size of the final atlas textures. 
+		 *							Texture elements will reference these pages with their output.page parameter.
+		 */
+		static Vector<Page> createAtlasLayout(Vector<Element>& elements, UINT32 width, UINT32 height, UINT32 maxWidth, 
+			UINT32 maxHeight, bool pow2 = false);
+	};
+
+	/** @} */
+}

+ 2 - 1
Source/BansheeUtility/Source/BsDebug.cpp

@@ -52,7 +52,8 @@ namespace bs
 		logToIDEConsole(msg);
 	}
 
-	void Debug::writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const Path& filePath, bool overwrite) const
+	void Debug::writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const Path& filePath, 
+		bool overwrite) const
 	{
 		if(FileSystem::isFile(filePath))
 		{

+ 0 - 332
Source/BansheeUtility/Source/BsTexAtlasGenerator.cpp

@@ -1,332 +0,0 @@
-//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
-//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-#include "BsTexAtlasGenerator.h"
-#include "BsDebug.h"
-
-namespace bs
-{
-	class TexAtlasNode
-	{
-	public:
-		TexAtlasNode()
-			:x(0), y(0), width(0), height(0), children(nullptr), nodeFull(false)
-		{ }
-
-		TexAtlasNode(UINT32 _x, UINT32 _y, UINT32 _width, UINT32 _height)
-			:x(_x), y(_y), width(_width), height(_height), children(nullptr), nodeFull(false)
-		{ }
-
-		~TexAtlasNode()
-		{
-			if(children != nullptr)
-				bs_deleteN(children, 2);
-		}
-
-		UINT32 x, y, width, height;
-		TexAtlasNode* children;
-		bool nodeFull;
-
-		bool insert(TexAtlasElementDesc& element)
-		{
-			float aspect = width / (float)height;
-
-			return insert(element, aspect);
-		}
-
-		bool insert(TexAtlasElementDesc& element, float aspect)
-		{
-			if (children != nullptr)
-			{
-				if (children[0].insert(element))
-					return true;
-
-				return children[1].insert(element);
-			}
-			else
-			{
-				if(nodeFull)
-					return false;
-
-				if (element.input.width > width || element.input.height > height)
-					return false;
-
-				if (element.input.width == width && element.input.height == height)
-				{
-					element.output.x = x;
-					element.output.y = y;
-					nodeFull = true;
-
-					return true;
-				}
-
-				float dw = (float)(width - element.input.width);
-				float dh = (height - element.input.height) * aspect;
-
-				children = bs_newN<TexAtlasNode>(2);
-
-				if (dw > dh)
-				{
-					children[0].x = x;
-					children[0].y = y;
-					children[0].width = element.input.width;
-					children[0].height = height;
-
-					children[1].x = x + element.input.width;
-					children[1].y = y;
-					children[1].width = width - element.input.width;
-					children[1].height = height;
-				}
-				else
-				{
-					children[0].x = x;
-					children[0].y = y;
-					children[0].width = width;
-					children[0].height = element.input.height;
-
-					children[1].x = x;
-					children[1].y = y + element.input.height;
-					children[1].width = width;
-					children[1].height = height - element.input.height;
-				}
-
-				return children[0].insert(element);
-			}
-		}
-	};
-
-	TexAtlasGenerator::TexAtlasGenerator(bool square, UINT32 maxTexWidth, UINT32 maxTexHeight, bool fixedSize)
-		:mSquare(square), mFixedSize(fixedSize), mMaxTexWidth(maxTexWidth), mMaxTexHeight(maxTexHeight)
-	{
-		if(square)
-		{
-			if(maxTexWidth > maxTexHeight)
-				maxTexWidth = maxTexHeight;
-
-			if(maxTexHeight > maxTexWidth)
-				maxTexHeight = maxTexWidth;
-		}
-	}
-
-	Vector<TexAtlasPageDesc> TexAtlasGenerator::createAtlasLayout(Vector<TexAtlasElementDesc>& elements) const
-	{
-		for(size_t i = 0; i < elements.size(); i++)
-			elements[i].output.page = -1;
-
-		//sortBySize(elements);
-		int numPages = generatePagesForSize(elements, mMaxTexWidth, mMaxTexHeight);
-
-		if(numPages == -1)
-		{
-			LOGWRN("Some of the provided elements don't fit in an atlas of provided size. Returning empty array of pages.");
-			return Vector<TexAtlasPageDesc>();
-		}
-
-		if(numPages == 0)
-			return Vector<TexAtlasPageDesc>();
-
-		UINT32 lastPageWidth = mMaxTexWidth;
-		UINT32 lastPageHeight = mMaxTexHeight;
-		INT32 lastPageIdx = numPages - 1;
-
-		// If size isn't fixed, try to reduce the size of the last page
-		if(!mFixedSize)
-		{
-			while (true)
-			{
-				if (lastPageWidth <= 1 || lastPageHeight <= 1)
-					break;
-
-				int newLastPageWidth = lastPageWidth;
-				int newLastPageHeight = lastPageHeight;
-
-				if(mSquare)
-				{
-					if (newLastPageWidth > newLastPageHeight)
-						newLastPageWidth /= 2;
-					else
-						newLastPageHeight /= 2;
-				}
-				else
-				{
-					if (newLastPageWidth > newLastPageHeight)
-						newLastPageWidth /= 2;
-					else
-						newLastPageHeight /= 2;
-				}
-
-				// Clear page indexes so we know which pages to process
-				for(size_t i = 0; i < elements.size(); i++)
-				{
-					if(elements[i].output.page >= lastPageIdx)
-						elements[i].output.page = -1;
-				}
-
-				if(generatePagesForSize(elements, newLastPageWidth, newLastPageHeight, lastPageIdx) == 1)
-				{
-					lastPageWidth = newLastPageWidth;
-					lastPageHeight = newLastPageHeight;
-				}
-				else
-				{
-					// We're done but we need to re-do all the pages with the last valid size
-					for(size_t i = 0; i < elements.size(); i++)
-					{
-						if(elements[i].output.page >= lastPageIdx)
-							elements[i].output.page = -1;
-					}
-
-					generatePagesForSize(elements, lastPageWidth, lastPageHeight, lastPageIdx);
-
-					break;
-				}
-			}
-		}
-
-		// Handle degenerate case
-		for(size_t i = 0; i < elements.size(); i++)
-		{
-			if(elements[i].output.page == -1 && elements[i].input.width == 0 && elements[i].input.height == 0)
-			{
-				elements[i].output.x = 0;
-				elements[i].output.y = 0;
-			}
-		}
-
-		// Create page descriptors and return
-		Vector<TexAtlasPageDesc> pages;
-		for(int i = 0; i < numPages - 1; i++)
-		{
-			TexAtlasPageDesc pageDesc;
-			pageDesc.width = mMaxTexWidth;
-			pageDesc.height = mMaxTexHeight;
-
-			pages.push_back(pageDesc);
-		}
-
-		TexAtlasPageDesc lastPageDesc;
-		lastPageDesc.width = lastPageWidth;
-		lastPageDesc.height = lastPageHeight;
-
-		pages.push_back(lastPageDesc);
-
-		return pages;
-	}
-
-	int TexAtlasGenerator::generatePagesForSize(Vector<TexAtlasElementDesc>& elements, UINT32 width, UINT32 height, UINT32 startPage) const
-	{
-		if(elements.size() == 0)
-			return 0;
-
-		int currentPage = startPage;
-		int numPages = 0;
-		while (true)
-		{
-			int largestTexId = findLargestTextureWithoutPage(elements); // Start with the largest available texture
-
-			if (largestTexId == -1) // No more textures, we're done
-				return numPages;
-
-			TexAtlasElementDesc& currentElem = elements[largestTexId];
-
-			// If the texture is larger than the atlas size then it can never fit
-			while (width < currentElem.input.width || height < currentElem.input.height)
-				return -1;
-
-			TexAtlasNode atlasNode(0, 0, width, height);
-			atlasNode.insert(elements[largestTexId]);
-			elements[largestTexId].output.page = currentPage;
-
-			// Now that the first (largest) texture has been added, do the same for every other texture until atlas is full
-			while (true)
-			{
-				int addedTextureId = addLargestTextureWithoutPageThatFits(elements, atlasNode); // Try to add next largest texture
-				if (addedTextureId == -1)
-					break;
-
-				elements[addedTextureId].output.page = currentPage;
-			}
-
-			currentPage++;
-			numPages++;
-		}
-	}
-
-	int TexAtlasGenerator::addLargestTextureWithoutPageThatFits(Vector<TexAtlasElementDesc>& elements, TexAtlasNode& node) const
-	{
-		UINT32 sizeLimit = std::numeric_limits<UINT32>::max();
-		while (true)
-		{
-			UINT32 largestSize = 0;
-			INT32 largestId = -1;
-
-			for (size_t i = 0; i < elements.size(); i++)
-			{
-				if (elements[i].output.page == -1) // Signifies that we haven't processed this node yet
-				{
-					UINT32 size = elements[i].input.width * elements[i].input.height;
-					if (size > largestSize && size < sizeLimit)
-					{
-						largestSize = size;
-						largestId = (INT32)i;
-					}
-				}
-			}
-
-			if (largestId == -1)
-				return -1;
-
-			if (node.insert(elements[largestId]))
-				return largestId;
-			else
-				sizeLimit = largestSize;
-		}
-	}
-
-	int TexAtlasGenerator::findLargestTextureWithoutPage(const Vector<TexAtlasElementDesc>& elements) const
-	{
-		INT32 largestId = -1;
-		UINT32 largestSize = 0;
-
-		for (size_t i = 0; i < elements.size(); i++)
-		{
-			if (elements[i].output.page == -1) // Signifies that we haven't processed this node yet
-			{
-				UINT32 size = elements[i].input.width * elements[i].input.height;
-				if (size > largestSize)
-				{
-					largestSize = size;
-					largestId = (INT32)i;
-				}
-			}
-		}
-
-		return largestId;
-	}
-
-	void TexAtlasGenerator::sortBySize(Vector<TexAtlasElementDesc>& elements) const
-	{
-		for (size_t i = 0; i < elements.size(); i++)
-		{
-			INT32 largestId = -1;
-			UINT32 largestSize = 0;
-
-			for (size_t j = i; j < elements.size(); j++)
-			{
-				UINT32 size = elements[j].input.width * elements[j].input.height;
-
-				if (size > largestSize)
-				{
-					largestSize = size;
-					largestId = (INT32)j;
-				}
-			}
-
-			if(largestId != -1)
-			{
-				TexAtlasElementDesc temp = elements[i];
-				elements[i] = elements[largestId];
-				elements[largestId] = temp;
-			}
-		}
-	}
-}

+ 187 - 0
Source/BansheeUtility/Source/BsTextureAtlasLayout.cpp

@@ -0,0 +1,187 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "BsTextureAtlasLayout.h"
+#include "BsDebug.h"
+#include "BsBitwise.h"
+
+namespace bs
+{
+	TextureAtlasLayout::TexAtlasNode::TexAtlasNode()
+		:x(0), y(0), width(0), height(0), children{ (UINT32)-1, (UINT32)-1 }, nodeFull(false)
+	{ }
+
+	TextureAtlasLayout::TexAtlasNode::TexAtlasNode(UINT32 x, UINT32 y, UINT32 width, UINT32 height)
+		: x(x), y(y), width(width), height(height), children{ (UINT32)-1, (UINT32)-1 }, nodeFull(false)
+	{ }
+
+	TextureAtlasLayout::TextureAtlasLayout()
+		: mWidth(0), mHeight(0), mMaxWidth(0), mMaxHeight(0), mPow2(false)
+	{ }
+
+	TextureAtlasLayout::TextureAtlasLayout(UINT32 width, UINT32 height, UINT32 maxWidth, UINT32 maxHeight, bool pow2)
+		: mWidth(width), mHeight(height), mMaxWidth(maxWidth), mMaxHeight(maxHeight), mPow2(pow2)
+	{
+		mNodes.push_back(TexAtlasNode(0, 0, maxWidth, maxHeight));
+	}
+
+	bool TextureAtlasLayout::addElement(UINT32 width, UINT32 height, UINT32& x, UINT32& y)
+	{
+		if(width == 0 || height == 0)
+		{
+			x = 0;
+			y = 0;
+			return true;
+		}
+
+		// Try adding without expanding, if that fails try to expand
+		if(!addToNode(0, width, height, x, y, false))
+		{
+			if (!addToNode(0, width, height, x, y, true))
+				return false;
+		}
+
+		// Update size to cover all nodes
+		if(mPow2)
+		{
+			mWidth = std::max(mWidth, Bitwise::nextPow2(x + width));
+			mHeight = std::max(mHeight, Bitwise::nextPow2(y + height));
+		}
+		else
+		{
+			mWidth = std::max(mWidth, x + width);
+			mHeight = std::max(mHeight, y + height);
+		}
+
+		return true;
+	}
+
+	bool TextureAtlasLayout::addToNode(UINT32 nodeIdx, UINT32 width, UINT32 height, UINT32& x, UINT32& y, bool allowGrowth)
+	{
+		TexAtlasNode* node = &mNodes[nodeIdx];
+		float aspect = node->width / (float)node->height;
+
+		if (node->children[0] != -1)
+		{
+			if (addToNode(node->children[0], width, height, x, y, allowGrowth))
+				return true;
+
+			return addToNode(node->children[1], width, height, x, y, allowGrowth);
+		}
+		else
+		{
+			if (node->nodeFull)
+				return false;
+
+			if (width > node->width || height > node->height)
+				return false;
+
+			if(!allowGrowth)
+			{
+				if (node->x + width > mWidth || node->y + height > mHeight)
+					return false;
+			}
+
+			if (width == node->width && height == node->height)
+			{
+				x = node->x;
+				y = node->y;
+				node->nodeFull = true;
+
+				return true;
+			}
+
+			float dw = (float)(node->width - width);
+			float dh = (node->height - height) * aspect;
+
+			UINT32 nextChildIdx = (UINT32)mNodes.size();
+			node->children[0] = nextChildIdx;
+			node->children[1] = nextChildIdx + 1;
+
+			TexAtlasNode nodeCopy = *node;
+			node = nullptr; // Undefined past this point
+			if (dw > dh)
+			{
+				mNodes.emplace_back(nodeCopy.x, nodeCopy.y, width, nodeCopy.height);
+				mNodes.emplace_back(nodeCopy.x + width, nodeCopy.y, nodeCopy.width - width, nodeCopy.height);
+			}
+			else
+			{
+				mNodes.emplace_back(nodeCopy.x, nodeCopy.y, nodeCopy.width, height);
+				mNodes.emplace_back(nodeCopy.x, nodeCopy.y + height, nodeCopy.width, nodeCopy.height - height);
+			}
+
+			return addToNode(nodeCopy.children[0], width, height, x, y, allowGrowth);
+		}
+	}
+
+	Vector<TextureAtlasUtility::Page> TextureAtlasUtility::createAtlasLayout(Vector<Element>& elements, UINT32 width, 
+		UINT32 height, UINT32 maxWidth, UINT32 maxHeight, bool pow2)
+	{
+		for (size_t i = 0; i < elements.size(); i++)
+		{
+			elements[i].output.idx = i; // Preserve original index before sorting
+			elements[i].output.page = -1;
+		}
+
+		std::sort(elements.begin(), elements.end(), 
+			[](const Element& a, const Element& b)
+		{
+			return a.input.width * a.input.height > b.input.width * b.input.height;
+		});
+
+		Vector<TextureAtlasLayout> layouts;
+		UINT32 remainingCount = (UINT32)elements.size();
+		while (remainingCount > 0)
+		{
+			layouts.push_back(TextureAtlasLayout(width, height, maxWidth, maxHeight, pow2));
+			TextureAtlasLayout& curLayout = layouts.back();
+
+			// Find largest unassigned element that fits
+			UINT32 sizeLimit = std::numeric_limits<UINT32>::max();
+			while (true)
+			{
+				UINT32 largestId = -1;
+
+				// Assumes elements are sorted from largest to smallest
+				for (UINT32 i = 0; i < (UINT32)elements.size(); i++)
+				{
+					if (elements[i].output.page == -1)
+					{
+						UINT32 size = elements[i].input.width * elements[i].input.height;
+						if (size < sizeLimit)
+						{
+							largestId = i;
+							break;
+						}
+					}
+				}
+
+				if (largestId == -1)
+					break; // Nothing fits, start a new page
+
+				Element& element = elements[largestId];
+
+				// Check if an element is too large to ever fit
+				if(element.input.width > maxWidth || element.input.height > maxHeight)
+				{
+					LOGWRN("Some of the provided elements don't fit in an atlas of provided size. Returning empty array of pages.");
+					return Vector<Page>();
+				}
+
+				if (curLayout.addElement(element.input.width, element.input.height, element.output.x, element.output.y))
+				{
+					element.output.page = (UINT32)layouts.size() - 1;
+					remainingCount--;
+				}
+				else
+					sizeLimit = element.input.width * element.input.height;
+			}
+		}
+
+		Vector<Page> pages;
+		for (auto& layout : layouts)
+			pages.push_back({ layout.getWidth(), layout.getHeight() });
+
+		return pages;
+	}
+}