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

Added a bunch more of text rendering and general sprite code. Still WIP though

Marko Pintera 13 лет назад
Родитель
Сommit
6483812436

+ 5 - 1
CamelotCore/CamelotCore.vcxproj

@@ -213,6 +213,7 @@
     <ClInclude Include="Include\CmGpuProgramRTTI.h" />
     <ClInclude Include="Include\CmHardwareBuffer.h" />
     <ClInclude Include="Include\CmHardwareBufferManager.h" />
+    <ClInclude Include="Include\CmImageSprite.h" />
     <ClInclude Include="Include\CmImportOptions.h" />
     <ClInclude Include="Include\CmImportOptionsRTTI.h" />
     <ClInclude Include="Include\CmIndexBuffer.h" />
@@ -221,7 +222,6 @@
     <ClInclude Include="Include\CmOcclusionQuery.h" />
     <ClInclude Include="Include\CmPixelBuffer.h" />
     <ClInclude Include="Include\CmGpuProgIncludeImporter.h" />
-    <ClInclude Include="Include\CmTextRenderer.h" />
     <ClInclude Include="Include\CmTextureView.h" />
     <ClInclude Include="Include\CmVertexBuffer.h" />
     <ClInclude Include="Include\CmHighLevelGpuProgram.h" />
@@ -280,10 +280,12 @@
     <ClInclude Include="Include\CmComponent.h" />
     <ClInclude Include="Include\CmShader.h" />
     <ClInclude Include="Include\CmBlendState.h" />
+    <ClInclude Include="Include\CmSprite.h" />
     <ClInclude Include="Include\stdafx.h" />
     <ClInclude Include="Include\targetver.h" />
     <ClInclude Include="Include\CmVertexDeclarationRTTI.h" />
     <ClInclude Include="Include\CmTechnique.h" />
+    <ClInclude Include="Include\CmTextSprite.h" />
     <ClInclude Include="Include\Win32\CmOSCursorImpl.h" />
     <ClInclude Include="Source\CmMeshRTTI.h" />
   </ItemGroup>
@@ -321,6 +323,8 @@
     <ClCompile Include="Source\CmOcclusionQuery.cpp" />
     <ClCompile Include="Source\CmPixelBuffer.cpp" />
     <ClCompile Include="Source\CmGpuProgIncludeImporter.cpp" />
+    <ClCompile Include="Source\CmSprite.cpp" />
+    <ClCompile Include="Source\CmTextSprite.cpp" />
     <ClCompile Include="Source\CmTextureView.cpp" />
     <ClCompile Include="Source\CmVertexBuffer.cpp" />
     <ClCompile Include="Source\CmHighLevelGpuProgram.cpp" />

+ 21 - 3
CamelotCore/CamelotCore.vcxproj.filters

@@ -88,6 +88,12 @@
     <Filter Include="Source Files\Text">
       <UniqueIdentifier>{96b913ee-4ffb-4c60-9aa9-a51e0faf8060}</UniqueIdentifier>
     </Filter>
+    <Filter Include="Header Files\2D">
+      <UniqueIdentifier>{2d09c863-07a9-4226-a55d-6c5c03d70ac8}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Source Files\2D">
+      <UniqueIdentifier>{500c1648-85c9-4410-86e3-c16d102b4347}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="Include\CmConfigOptionMap.h">
@@ -399,9 +405,6 @@
     <ClInclude Include="Include\CmFontDesc.h">
       <Filter>Header Files\Text</Filter>
     </ClInclude>
-    <ClInclude Include="Include\CmTextRenderer.h">
-      <Filter>Header Files\Text</Filter>
-    </ClInclude>
     <ClInclude Include="Include\CmFontRTTI.h">
       <Filter>Header Files\RTTI</Filter>
     </ClInclude>
@@ -420,6 +423,15 @@
     <ClInclude Include="Include\CmFontImportOptionsRTTI.h">
       <Filter>Header Files\RTTI</Filter>
     </ClInclude>
+    <ClInclude Include="Include\CmSprite.h">
+      <Filter>Header Files\2D</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\CmTextSprite.h">
+      <Filter>Header Files\2D</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\CmImageSprite.h">
+      <Filter>Header Files\2D</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\CamelotRenderer.cpp">
@@ -644,5 +656,11 @@
     <ClCompile Include="Source\CmFontImportOptions.cpp">
       <Filter>Source Files\Text</Filter>
     </ClCompile>
+    <ClCompile Include="Source\CmSprite.cpp">
+      <Filter>Source Files\2D</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\CmTextSprite.cpp">
+      <Filter>Source Files\2D</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 5 - 0
CamelotCore/Include/CmFont.h

@@ -12,6 +12,8 @@ namespace CamelotEngine
 		FONT_DESC fontDesc;
 		vector<TexturePtr>::type texturePages;
 
+		const CHAR_DESC& getCharDesc(UINT32 charId) const;
+
 		/************************************************************************/
 		/* 								SERIALIZATION                      		*/
 		/************************************************************************/
@@ -33,6 +35,9 @@ namespace CamelotEngine
 
 		void initialize(vector<FontData>::type& fontData);
 
+		const FontData* getFontDataForSize(UINT32 size) const;
+		INT32 getClosestAvailableSize(UINT32 size) const;
+
 	protected:
 		friend class FontManager;
 

+ 16 - 15
CamelotCore/Include/CmFontDesc.h

@@ -26,6 +26,10 @@ namespace CamelotEngine
 	struct FONT_DESC
 	{
 		map<UINT32, CHAR_DESC>::type characters;
+		UINT32 baselineOffset;
+		UINT32 lineHeight;
+		CHAR_DESC missingGlyph;
+		UINT32 spaceWidth;
 	};
 
 	// Make CHAR_DESC serializable
@@ -96,13 +100,6 @@ namespace CamelotEngine
 
 			dataSize += sizeof(UINT32);
 
-#if CM_DEBUG_MODE
-			if(dataSize > std::numeric_limits<UINT32>::max())
-			{
-				CM_EXCEPT(InternalErrorException, "Data overflow! Size doesn't fit into 32 bits.");
-			}
-#endif
-
 			return (UINT32)dataSize;
 		}	
 	}; 
@@ -120,6 +117,10 @@ namespace CamelotEngine
 			memory += sizeof(UINT32);
 			
 			RTTIPlainType<std::map<UINT32, CHAR_DESC>>::toMemory(data.characters, memory);
+			rttiWriteElem(data.baselineOffset, memory);
+			rttiWriteElem(data.lineHeight, memory);
+			rttiWriteElem(data.missingGlyph, memory);
+			rttiWriteElem(data.spaceWidth, memory);
 		}
 
 		static UINT32 fromMemory(FONT_DESC& data, char* memory)
@@ -129,6 +130,10 @@ namespace CamelotEngine
 			memory += sizeof(UINT32);
 
 			RTTIPlainType<std::map<UINT32, CHAR_DESC>>::fromMemory(data.characters, memory);
+			rttiReadElem(data.baselineOffset, memory);
+			rttiReadElem(data.lineHeight, memory);
+			rttiReadElem(data.missingGlyph, memory);
+			rttiReadElem(data.spaceWidth, memory);
 
 			return size;
 		}
@@ -137,14 +142,10 @@ namespace CamelotEngine
 		{ 
 			UINT64 dataSize = sizeof(UINT32);
 			dataSize += RTTIPlainType<std::map<UINT32, CHAR_DESC>>::getDynamicSize(data.characters);
-
-
-#if CM_DEBUG_MODE
-			if(dataSize > std::numeric_limits<UINT32>::max())
-			{
-				CM_EXCEPT(InternalErrorException, "Data overflow! Size doesn't fit into 32 bits.");
-			}
-#endif
+			dataSize += rttiGetElemSize(data.baselineOffset);
+			dataSize += rttiGetElemSize(data.lineHeight);
+			dataSize += rttiGetElemSize(data.missingGlyph);
+			dataSize += rttiGetElemSize(data.spaceWidth);
 
 			return (UINT32)dataSize;
 		}	

+ 12 - 0
CamelotCore/Include/CmImageSprite.h

@@ -0,0 +1,12 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+#include "CmSprite.h"
+
+namespace CamelotEngine
+{
+	class CM_EXPORT ImageSprite : public Sprite
+	{
+
+	};
+}

+ 59 - 0
CamelotCore/Include/CmSprite.h

@@ -0,0 +1,59 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+#include "CmPoint.h"
+#include "CmRect.h"
+
+namespace CamelotEngine
+{
+	enum SpriteAnchor
+	{
+		SA_TopLeft,
+		SA_TopCenter,
+		SA_TopRight,
+		SA_MiddleLeft,
+		SA_MiddleCenter,
+		SA_MiddleRight,
+		SA_BottomLeft,
+		SA_BottomCenter,
+		SA_BottomRight
+	};
+
+	class CM_EXPORT Sprite
+	{
+	public:
+		Sprite();
+		virtual ~Sprite();
+
+		void setOffset(const Point& offset) { mOffset = offset; setDirty(); }
+		void setSize(UINT32 width, UINT32 height) { mWidth = width; mHeight = height; setDirty(); }
+		void setClipRect(const Rect& clipRect) { mClipRect = clipRect; setDirty(); }
+		void setAnchor(SpriteAnchor anchor) { mAnchor = anchor; setDirty(); }
+
+		Point getOffset() const { return mOffset; }
+		UINT32 getWidth() const { return mWidth; }
+		UINT32 getHeight() const { return mHeight; }
+		Rect getClipRect() const { return mClipRect; }
+		SpriteAnchor getAnchor() const { return mAnchor; }
+
+		UINT32 fillBuffer(Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 startingQuad, UINT32 maxNumQuads);
+
+	protected:
+		Point mOffset;
+		UINT32 mWidth, mHeight;
+		Rect mClipRect;
+		SpriteAnchor mAnchor;
+
+		bool mIsDirty;
+		Vector2* mVertices;
+		Vector2* mUVs;
+		UINT32* mIndexes;
+		UINT32 mNumMeshQuads;
+
+		void setDirty() { mIsDirty = true; }
+		Point getAnchorOffset() const;
+
+		virtual void updateMesh() = 0;
+		void clearMesh();
+	};
+}

+ 0 - 0
CamelotCore/Include/CmTextRenderer.h


+ 48 - 0
CamelotCore/Include/CmTextSprite.h

@@ -0,0 +1,48 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+#include "CmSprite.h"
+
+namespace CamelotEngine
+{
+	enum TextHorzAlign
+	{
+		THA_Left, THA_Center, THA_Right
+	};
+
+	enum TextVertAlign
+	{
+		TVA_Top, TVA_Center, TVA_Bottom
+	};
+
+	class CM_EXPORT TextSprite : public Sprite
+	{
+	public:
+		TextSprite(const String& text, FontPtr font, UINT32 fontSize);
+
+		void setText(const String& text) { mText = text; setDirty(); }
+		void setFont(FontPtr font, UINT32 fontSize) { mFont = font; mFontSize = fontSize; setDirty(); }
+		void setWordWrap(bool wordWrap) { mWordWrap = wordWrap; setDirty(); }
+		void setAlignment(TextHorzAlign horzAlign, TextVertAlign vertAlign = TVA_Top) 
+			{ mHorzAlign = horzAlign; mVertAlign = vertAlign; setDirty(); }
+
+		String getText() const { return mText; }
+		FontPtr getFont() const { return mFont; }
+		UINT32 getFontSize() const { return mFontSize; }
+		bool getWordWrap() const { return mWordWrap; }
+		TextHorzAlign getTextHorzAlign() const { return mHorzAlign; }
+		TextVertAlign getTextVertAlign() const { return mVertAlign; }
+
+	protected:
+		String mText;
+		FontPtr mFont;
+		UINT32 mFontSize;
+		bool mWordWrap;
+		TextHorzAlign mHorzAlign;
+		TextVertAlign mVertAlign;
+
+		virtual void updateMesh();
+
+		const FontData* getFontData() const;
+	};
+}

+ 53 - 0
CamelotCore/Source/CmFont.cpp

@@ -4,6 +4,17 @@
 
 namespace CamelotEngine
 {
+	const CHAR_DESC& FontData::getCharDesc(UINT32 charId) const
+	{
+		auto iterFind = fontDesc.characters.find(charId);
+		if(iterFind != fontDesc.characters.end())
+		{
+			return fontDesc.characters.at(charId);
+		}
+
+		return fontDesc.missingGlyph;
+	}
+
 	RTTITypeBase* FontData::getRTTIStatic()
 	{
 		return FontDataRTTI::instance();
@@ -29,6 +40,48 @@ namespace CamelotEngine
 		Resource::initialize();
 	}
 
+	const FontData* Font::getFontDataForSize(UINT32 size) const
+	{
+		auto iterFind = mFontDataPerSize.find(size);
+
+		if(iterFind == mFontDataPerSize.end())
+			return nullptr;
+
+		return &iterFind->second;
+	}
+
+	INT32 Font::getClosestAvailableSize(UINT32 size) const
+	{
+		UINT32 minDiff = std::numeric_limits<UINT32>::max();
+		UINT32 bestSize = size;
+
+		for(auto iter = mFontDataPerSize.begin(); iter != mFontDataPerSize.end(); ++iter)
+		{
+			if(iter->first == size)
+				return size;
+			else if(iter->first > size)
+			{
+				UINT32 diff = iter->first - size;
+				if(diff < minDiff)
+				{
+					minDiff = diff;
+					bestSize = iter->first;
+				}
+			}
+			else
+			{
+				UINT32 diff = size - iter->first;
+				if(diff < minDiff)
+				{
+					minDiff = diff;
+					bestSize = iter->first;
+				}
+			}
+		}
+
+		return bestSize;
+	}
+
 	FontHandle Font::create(vector<FontData>::type& fontData)
 	{
 		FontPtr newFont = FontManager::instance().create(fontData);

+ 79 - 0
CamelotCore/Source/CmSprite.cpp

@@ -0,0 +1,79 @@
+#include "CmTextSprite.h"
+#include "CmVector2.h"
+
+namespace CamelotEngine
+{
+	Sprite::Sprite()
+		:mWidth(0), mHeight(0), mAnchor(SA_TopLeft), mIsDirty(true),
+		mVertices(nullptr), mUVs(nullptr), mIndexes(nullptr), mNumMeshQuads(0)
+	{
+
+	}
+
+	Sprite::~Sprite()
+	{
+
+	}
+
+	UINT32 Sprite::fillBuffer(Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 startingQuad, UINT32 maxNumQuads)
+	{
+		UINT32 startVert = startingQuad * 4;
+		UINT32 startIndex = startingQuad * 4;
+
+		UINT32 maxVertIdx = maxNumQuads * 4;
+		UINT32 maxIndexIdx = maxNumQuads * 6;
+
+		UINT32 mNumVertices = mNumMeshQuads * 4;
+		UINT32 mNumIndices = mNumMeshQuads * 6;
+
+		assert((startVert + mNumVertices) < maxVertIdx);
+		assert((startIndex + mNumIndices) < maxIndexIdx);
+
+		memcpy(&vertices[startVert], mVertices, mNumVertices * sizeof(Vector2));
+		memcpy(&uv[startVert], mUVs, mNumVertices * sizeof(Vector2));
+		memcpy(&indices[startIndex], mIndexes, mNumVertices * sizeof(UINT32));
+
+		return mNumMeshQuads;
+	}
+
+	Point Sprite::getAnchorOffset() const
+	{
+		switch(mAnchor)
+		{
+		case SA_TopLeft:
+			return -Point(0, 0);
+		case SA_TopCenter:
+			return -Point(mWidth / 2, 0);
+		case SA_TopRight:
+			return -Point(mWidth, 0);
+		case SA_MiddleLeft:
+			return -Point(0, mHeight / 2);
+		case SA_MiddleCenter:
+			return -Point(mWidth / 2, mHeight / 2);
+		case SA_MiddleRight:
+			return -Point(mWidth, mHeight / 2);
+		case SA_BottomLeft:
+			return -Point(0, mHeight);
+		case SA_BottomCenter:
+			return -Point(mWidth / 2, mHeight);
+		case SA_BottomRight:
+			return -Point(mWidth, mHeight);
+		}
+
+		return Point();
+	}
+
+	void Sprite::clearMesh()
+	{
+		if(mVertices != nullptr)
+			delete[] mVertices;
+
+		if(mUVs != nullptr)
+			delete[] mUVs;
+
+		if(mIndexes != nullptr)
+			delete[] mIndexes;
+
+		mNumMeshQuads = 0;
+	}
+}

+ 393 - 0
CamelotCore/Source/CmTextSprite.cpp

@@ -0,0 +1,393 @@
+#include "CmTextSprite.h"
+#include "CmDebug.h"
+#include "CmFontDesc.h"
+#include "CmFont.h"
+#include "CmVector2.h"
+
+namespace CamelotEngine
+{
+	const int SPACE_CHAR = 32;
+
+	class TextWord
+	{
+	public:
+		TextWord(bool spacer)
+			:mWidth(0), mSpacer(spacer)
+		{ }
+
+		void addChar(const CHAR_DESC& desc)
+		{
+			mChars.push_back(desc);
+
+			calculateWidth();
+		}
+
+		void removeLastChar()
+		{
+			if(mChars.size() > 0)
+			{
+				mChars.erase(mChars.end() - 1);
+				calculateWidth();
+			}
+		}
+
+		UINT32 getWidth() const { return mWidth; }
+		bool isSpacer() const { return mSpacer; }
+
+		const vector<CHAR_DESC>::type& getChars() const { return mChars; }
+
+	private:
+		vector<CHAR_DESC>::type mChars;
+		UINT32 mWidth;
+		bool mSpacer;
+
+		void calculateWidth()
+		{
+			if(mChars.size() == 0)
+			{
+				mWidth = 0;
+				return;
+			}
+
+			mWidth = 0;
+			UINT32 kerning = 0;
+			for(size_t i = 0; i < mChars.size() - 1; i++)
+			{
+				mWidth += mChars[i].xAdvance + kerning;
+
+				kerning = 0;
+				for(size_t j = 0; j < mChars[i].kerningPairs.size(); j++)
+				{
+					if(mChars[i].kerningPairs[j].otherCharId == mChars[i + 1].charId)
+					{
+						kerning = mChars[i].kerningPairs[j].amount;
+						break;
+					}
+				}
+			}
+
+			mWidth += mChars[mChars.size() - 1].xAdvance + kerning;
+		}
+	};
+
+	class TextLine
+	{
+	public:
+		TextLine()
+			:mWidth(0), mLastWord(nullptr)
+		{
+
+		}
+
+		~TextLine()
+		{
+			for(auto iter = mWords.begin(); iter != mWords.end(); ++iter)
+				delete *iter;
+		}
+
+		void add(const CHAR_DESC& charDesc)
+		{
+			if(mLastWord == nullptr)
+			{
+				TextWord* newWord = new TextWord(charDesc.charId == SPACE_CHAR);
+				mLastWord = newWord;
+
+				mWords.push_back(mLastWord);
+			}
+			else
+			{
+				if(charDesc.charId != SPACE_CHAR)
+				{
+					if(mLastWord->isSpacer())
+					{
+						TextWord* newWord = new TextWord(false);
+						mLastWord = newWord;
+
+						mWords.push_back(mLastWord);
+					}
+
+					mLastWord->addChar(charDesc);
+				}
+				else
+				{
+					TextWord* newWord = new TextWord(true); // Each space is counted as its own word, to make certain operations easier
+					mLastWord = newWord;
+
+					mWords.push_back(mLastWord);
+
+					mLastWord->addChar(charDesc);
+				}
+			}
+
+			calculateBounds();
+		}
+
+		void addWord(TextWord* word)
+		{
+			mWords.push_back(word);
+			mLastWord = word;
+
+			calculateBounds();
+		}
+
+		TextWord* removeLastWord()
+		{
+			if(mWords.size() == 0)
+				return nullptr;
+
+			TextWord* word = mWords[mWords.size() - 1];
+			mWords.erase(mWords.end() - 1);
+
+			if(mWords.size() > 0)
+				mLastWord = mWords[mWords.size() - 1];
+			else
+				mLastWord = nullptr;
+
+			calculateBounds();
+
+			return word;
+		}
+
+		UINT32 getWidth() const { return mWidth; }
+		Point getPosition() const { return mPosition; }
+		void setPosition(const Point& pos) { mPosition = pos; }
+
+		UINT32 fillBuffer(Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 startingQuad, UINT32 maxNumQuads, const FontData& fontData)
+		{
+			UINT32 curVert = startingQuad * 4;
+			UINT32 curIndex = startingQuad * 6;
+
+			UINT32 maxVertIdx = maxNumQuads * 4;
+			UINT32 maxIndexIdx = maxNumQuads * 6;
+
+			UINT32 penX = mPosition.x;
+			UINT32 baselineY = mPosition.y + fontData.fontDesc.baselineOffset;
+			for(auto wordIter = mWords.begin(); wordIter != mWords.end(); ++wordIter)
+			{
+				if((*wordIter)->isSpacer())
+				{
+					penX += fontData.fontDesc.spaceWidth;
+				}
+				else
+				{
+					const vector<CHAR_DESC>::type& chars = (*wordIter)->getChars();
+					UINT32 kerning = 0;
+					for(auto charIter = chars.begin(); charIter != chars.end(); ++charIter)
+					{
+						assert(curVert < maxVertIdx);
+						assert(curIndex < maxIndexIdx);
+
+						UINT32 curX = penX + charIter->xOffset;
+						UINT32 curY = baselineY - charIter->yOffset;
+
+						vertices[curVert + 0] = Vector2((float)curX, (float)curY);
+						vertices[curVert + 1] = Vector2((float)(curX + charIter->width), (float)curY);
+						vertices[curVert + 2] = Vector2((float)curX, (float)(curY + charIter->height));
+						vertices[curVert + 3] = Vector2((float)(curX + charIter->width), (float)(curY + charIter->height));
+
+						uv[curVert + 0] = Vector2(charIter->uvX, charIter->uvY);
+						uv[curVert + 1] = Vector2(charIter->uvX + charIter->uvWidth, charIter->uvY);
+						uv[curVert + 2] = Vector2(charIter->uvX, charIter->uvY + charIter->uvHeight);
+						uv[curVert + 3] = Vector2(charIter->uvX + charIter->uvWidth, charIter->uvY + charIter->uvHeight);
+
+						indices[curIndex + 0] = 0;
+						indices[curIndex + 1] = 2;
+						indices[curIndex + 2] = 1;
+						indices[curIndex + 3] = 1;
+						indices[curIndex + 4] = 2;
+						indices[curIndex + 5] = 3;
+
+						penX += charIter->xAdvance + kerning;
+						curVert += 4;
+						curIndex += 6;
+						
+						kerning = 0;
+						if((charIter + 1) != chars.end())
+						{
+							for(size_t j = 0; j < charIter->kerningPairs.size(); j++)
+							{
+								if(charIter->kerningPairs[j].otherCharId == (charIter + 1)->charId)
+								{
+									kerning = charIter->kerningPairs[j].amount;
+									break;
+								}
+							}
+						}
+					}
+				}
+			}
+
+			return (curVert / 4) - startingQuad;
+		}
+
+	private:
+		UINT32 mWidth;
+		vector<TextWord*>::type mWords;
+		TextWord* mLastWord;
+		Point mPosition;
+
+		void calculateBounds()
+		{
+			mWidth = 0;
+			for(auto iter = mWords.begin(); iter != mWords.end(); ++iter)
+			{
+				mWidth += (*iter)->getWidth();
+			}
+		}
+	};
+
+	TextSprite::TextSprite(const String& text, FontPtr font, UINT32 fontSize)
+		:mText(text), mFont(font), mFontSize(fontSize), mWordWrap(false), mHorzAlign(THA_Left), mVertAlign(TVA_Top)
+	{
+
+	}
+
+	void TextSprite::updateMesh()
+	{
+		const FontData* fontData = getFontData();
+
+		clearMesh();
+
+		if(fontData == nullptr)
+			return;
+
+		if(fontData->size != mFontSize)
+		{
+			LOGWRN("Unable to find font with specified size (" + toString(mFontSize) + "). Using nearest available size: " + toString(fontData->size));
+		}
+
+		bool heightIsLimited = mHeight > 0;
+
+		TextLine* curLine = new TextLine();
+		vector<TextLine*>::type textLines;
+		textLines.push_back(curLine);
+
+		UINT32 curHeight = fontData->fontDesc.lineHeight;
+		UINT32 charIdx = 0;
+		mNumMeshQuads = 0;
+		while(true)
+		{
+			if(charIdx >= mText.size())
+				break;
+
+			if(mText[charIdx] == '\n')
+			{
+				if(heightIsLimited && (curHeight + fontData->fontDesc.lineHeight * 2) > mHeight)
+					break; // Max height reached
+
+				curLine = new TextLine();
+				textLines.push_back(curLine);
+				curHeight += fontData->fontDesc.lineHeight;
+
+				charIdx++;
+				continue;
+			}
+
+			UINT32 charId = mText[charIdx];
+			const CHAR_DESC& charDesc = fontData->getCharDesc(charId);
+
+			curLine->add(charDesc);
+
+			if(charDesc.charId != SPACE_CHAR)
+				mNumMeshQuads++;
+
+			if(curLine->getWidth() > mWidth)
+			{
+				if(mWordWrap)
+				{
+					TextWord* lastWord = curLine->removeLastWord();
+					if(lastWord->isSpacer())
+						curLine->addWord(lastWord); // Spaces can stay on previous line even if they don't technically fit
+
+					// No more lines fit vertically so we're done
+					if(heightIsLimited && (curHeight + fontData->fontDesc.lineHeight * 2) > mHeight)
+						break;
+
+					curLine = new TextLine();
+					textLines.push_back(curLine);
+					curHeight += fontData->fontDesc.lineHeight;
+
+					if(!lastWord->isSpacer())
+						curLine->addWord(lastWord);
+				}
+				else
+				{
+					// Nothing else we can do, chars don't fit so we are done
+					break;
+				}
+			}
+
+			charIdx++;
+		}
+
+		// Calc vertical alignment offset
+		UINT32 vertDiff = std::max(0U, mHeight - curHeight);
+		UINT32 vertOffset = 0;
+		switch(mVertAlign)
+		{
+		case TVA_Top:
+			vertOffset = 0;
+			break;
+		case TVA_Bottom:
+			vertOffset = std::max(0, (INT32)(mHeight - curHeight));
+			break;
+		case TVA_Center:
+			vertOffset = std::max(0, (INT32)(mHeight - curHeight)) / 2;
+			break;
+		}
+
+		// Calc horizontal alignment offset and set final line positions
+		Point offset = mOffset + getAnchorOffset();
+		UINT32 curY = 0;
+		for(size_t i = 0; i < textLines.size(); i++)
+		{
+			UINT32 horzOffset = 0;
+			switch(mHorzAlign)
+			{
+			case THA_Left:
+				horzOffset = 0;
+				break;
+			case THA_Right:
+				horzOffset = std::max(0, (INT32)(mWidth - textLines[i]->getWidth()));
+				break;
+			case THA_Center:
+				horzOffset = std::max(0, (INT32)(mWidth - textLines[i]->getWidth())) / 2;
+				break;
+			}
+
+			textLines[i]->setPosition(offset + Point(horzOffset, vertOffset + curY));
+
+			curY += fontData->fontDesc.lineHeight;
+		}
+
+		// Actually generate a mesh
+		mVertices = new Vector2[mNumMeshQuads * 4];
+		mUVs = new Vector2[mNumMeshQuads * 4];
+		mIndexes = new UINT32[mNumMeshQuads * 6];
+
+		UINT32 curFace = 0;
+		for(size_t i = 0; i < textLines.size(); i++)
+		{
+			UINT32 newFaces = textLines[i]->fillBuffer(mVertices, mUVs, mIndexes, curFace, mNumMeshQuads, *fontData);
+
+			curFace += newFaces;
+		}
+
+		for(size_t i = 0; i < textLines.size(); i++)
+			delete textLines[i];
+
+		// TODO - Clip the mesh based on mWidth/mHeight
+		
+		// TODO - How do I implement a scrollable text area without a clip rect?
+	}
+
+	const FontData* TextSprite::getFontData() const
+	{
+		if(mFont == nullptr)
+			return nullptr;
+
+		UINT32 nearestSize = mFont->getClosestAvailableSize(mFontSize);
+
+		return mFont->getFontDataForSize(nearestSize);
+	}
+}

+ 72 - 23
CamelotFontImporter/Source/CmFontImporter.cpp

@@ -101,10 +101,30 @@ namespace CamelotEngine
 				}
 			}
 
+			// Add missing glyph
+			{
+				error = FT_Load_Glyph(face, (FT_ULong)0, FT_LOAD_RENDER);
+
+				if(error)
+					CM_EXCEPT(InternalErrorException, "Failed to load a character");
+
+				FT_GlyphSlot slot = face->glyph;
+
+				TexAtlasElementDesc atlasElement;
+				atlasElement.input.width = slot->bitmap.width;
+				atlasElement.input.height = slot->bitmap.rows;
+
+				atlasElements.push_back(atlasElement);
+			}
+			
+
 			// Create an optimal layout for character bitmaps
 			TexAtlasGenerator texAtlasGen(false, MAXIMUM_TEXTURE_SIZE, MAXIMUM_TEXTURE_SIZE);
 			vector<TexAtlasPageDesc>::type pages = texAtlasGen.createAtlasLayout(atlasElements);
 
+			UINT32 baselineOffset = 0;
+			UINT32 lineHeight = 0;
+
 			// Create char bitmap atlas textures and load character information
 			UINT32 pageIdx = 0;
 			for(auto pageIter = pages.begin(); pageIter != pages.end(); ++pageIter)
@@ -123,10 +143,20 @@ namespace CamelotEngine
 						continue;
 
 					TexAtlasElementDesc curElement = atlasElements[elementIdx];
+					bool isMissingGlypth = elementIdx == (atlasElements.size() - 1); // It's always the last element
+
+					UINT32 charIdx = 0;
+					if(!isMissingGlypth)
+					{
+						charIdx = seqIdxToCharIdx[(UINT32)elementIdx];
+
+						error = FT_Load_Char(face, charIdx, FT_LOAD_RENDER);
+					}
+					else
+					{
+						error = FT_Load_Glyph(face, 0, FT_LOAD_RENDER);
+					}
 
-					UINT32 charIdx = seqIdxToCharIdx[(UINT32)elementIdx];
-					
-					error = FT_Load_Char(face, charIdx, FT_LOAD_RENDER);
 					if(error)
 						CM_EXCEPT(InternalErrorException, "Failed to load a character");
 
@@ -168,47 +198,66 @@ namespace CamelotEngine
 					charDesc.xAdvance = slot->advance.x >> 6;
 					charDesc.yAdvance = slot->advance.y >> 6;
 
-					// Load kerning
-					FT_Vector resultKerning;
-					for(auto kerningIter = charIndexRanges.begin(); kerningIter != charIndexRanges.end(); ++kerningIter)
+					baselineOffset = std::max(baselineOffset, (UINT32)(slot->metrics.horiBearingY >> 6));
+					lineHeight = std::max(lineHeight, charDesc.height);
+
+					// Load kerning and store char
+					if(!isMissingGlypth)
 					{
-						for(UINT32 kerningCharIdx = kerningIter->first; kerningCharIdx <= kerningIter->second; kerningCharIdx++)
+						FT_Vector resultKerning;
+						for(auto kerningIter = charIndexRanges.begin(); kerningIter != charIndexRanges.end(); ++kerningIter)
 						{
-							if(kerningCharIdx == charIdx)
-								continue;
+							for(UINT32 kerningCharIdx = kerningIter->first; kerningCharIdx <= kerningIter->second; kerningCharIdx++)
+							{
+								if(kerningCharIdx == charIdx)
+									continue;
 
-							error = FT_Get_Kerning(face, charIdx, kerningCharIdx, FT_KERNING_DEFAULT, &resultKerning);
+								error = FT_Get_Kerning(face, charIdx, kerningCharIdx, FT_KERNING_DEFAULT, &resultKerning);
 
-							if(error)
-								CM_EXCEPT(InternalErrorException, "Failed to get kerning information for character: " + toString(charIdx));
+								if(error)
+									CM_EXCEPT(InternalErrorException, "Failed to get kerning information for character: " + toString(charIdx));
 
-							INT32 kerningX = (INT32)(resultKerning.x >> 6); // Y kerning is ignored because it is so rare
-							if(kerningX == 0) // We don't store 0 kerning, this is assumed default
-								continue;
+								INT32 kerningX = (INT32)(resultKerning.x >> 6); // Y kerning is ignored because it is so rare
+								if(kerningX == 0) // We don't store 0 kerning, this is assumed default
+									continue;
 
-							KerningPair pair;
-							pair.amount = kerningX;
-							pair.otherCharId = kerningCharIdx;
+								KerningPair pair;
+								pair.amount = kerningX;
+								pair.otherCharId = kerningCharIdx;
 
-							charDesc.kerningPairs.push_back(pair);
+								charDesc.kerningPairs.push_back(pair);
+							}
 						}
-					}
 
-					fontData.fontDesc.characters[charIdx] = charDesc;
+						fontData.fontDesc.characters[charIdx] = charDesc;
+					}
+					else
+					{
+						fontData.fontDesc.missingGlyph = charDesc;
+					}
 				}
 
 				TextureHandle newTex = Texture::create(TEX_TYPE_2D, pageIter->width, pageIter->height, 0, PF_R8G8);
 				newTex->waitUntilInitialized();
 				newTex->setRawPixels(pixelData);
 
-				gDebug().writeAsBMP(pixelData, "C:\\FontTex.bmp");
-
 				fontData.texturePages.push_back(newTex.getInternalPtr());
 
 				pageIdx++;
 			}
 
 			fontData.size = fontSizes[i];
+			fontData.fontDesc.baselineOffset = baselineOffset;
+			fontData.fontDesc.lineHeight = lineHeight;
+
+			// Get space size
+			error = FT_Load_Char(face, 32, FT_LOAD_RENDER);
+
+			if(error)
+				CM_EXCEPT(InternalErrorException, "Failed to load a character");
+
+			fontData.fontDesc.spaceWidth = face->glyph->advance.x >> 6;
+
 			dataPerSize.push_back(fontData);
 		}
 

+ 12 - 0
CamelotUtility/Include/CmPoint.h

@@ -15,5 +15,17 @@ namespace CamelotEngine
 		{ }
 
 		int x, y;
+
+		inline friend Point operator + (const Point& lhs, const Point& rhs)
+		{
+			return Point(
+				lhs.x + rhs.x,
+				lhs.y + rhs.y);
+		}
+
+		inline Point operator - () const
+		{
+			return Point(-x, -y);
+		}
 	};
 }