| 1 | //************************************ bs::framework - Copyright 2018 Marko Pintera **************************************// |
| 2 | //*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********// |
| 3 | #include "2D/BsTextSprite.h" |
| 4 | #include "Text/BsTextData.h" |
| 5 | #include "Math/BsVector2.h" |
| 6 | #include "2D/BsSpriteManager.h" |
| 7 | #include "String/BsUnicode.h" |
| 8 | |
| 9 | namespace bs |
| 10 | { |
| 11 | TextSprite::~TextSprite() |
| 12 | { |
| 13 | clearMesh(); |
| 14 | } |
| 15 | |
| 16 | void TextSprite::update(const TEXT_SPRITE_DESC& desc, UINT64 groupId) |
| 17 | { |
| 18 | bs_frame_mark(); |
| 19 | { |
| 20 | const U32String utf32text = UTF8::toUTF32(desc.text); |
| 21 | TextData<FrameAlloc> textData(utf32text, desc.font, desc.fontSize, desc.width, desc.height, desc.wordWrap, |
| 22 | desc.wordBreak); |
| 23 | |
| 24 | UINT32 numPages = textData.getNumPages(); |
| 25 | |
| 26 | // Free all previous memory |
| 27 | for (auto& cachedElem : mCachedRenderElements) |
| 28 | { |
| 29 | if (cachedElem.vertices != nullptr) mAlloc.free(cachedElem.vertices); |
| 30 | if (cachedElem.uvs != nullptr) mAlloc.free(cachedElem.uvs); |
| 31 | if (cachedElem.indexes != nullptr) mAlloc.free(cachedElem.indexes); |
| 32 | } |
| 33 | |
| 34 | mAlloc.clear(); |
| 35 | |
| 36 | // Resize cached mesh array to needed size |
| 37 | if (mCachedRenderElements.size() != numPages) |
| 38 | mCachedRenderElements.resize(numPages); |
| 39 | |
| 40 | // Actually generate a mesh |
| 41 | UINT32 texPage = 0; |
| 42 | for (auto& cachedElem : mCachedRenderElements) |
| 43 | { |
| 44 | UINT32 newNumQuads = textData.getNumQuadsForPage(texPage); |
| 45 | |
| 46 | cachedElem.vertices = (Vector2*)mAlloc.alloc(sizeof(Vector2) * newNumQuads * 4); |
| 47 | cachedElem.uvs = (Vector2*)mAlloc.alloc(sizeof(Vector2) * newNumQuads * 4); |
| 48 | cachedElem.indexes = (UINT32*)mAlloc.alloc(sizeof(UINT32) * newNumQuads * 6); |
| 49 | cachedElem.numQuads = newNumQuads; |
| 50 | |
| 51 | const HTexture& tex = textData.getTextureForPage(texPage); |
| 52 | |
| 53 | SpriteMaterialInfo& matInfo = cachedElem.matInfo; |
| 54 | matInfo.groupId = groupId; |
| 55 | matInfo.texture = tex; |
| 56 | matInfo.tint = desc.color; |
| 57 | matInfo.animationStartTime = 0.0f; |
| 58 | |
| 59 | cachedElem.material = SpriteManager::instance().getTextMaterial(); |
| 60 | |
| 61 | texPage++; |
| 62 | } |
| 63 | |
| 64 | // Calc alignment and anchor offsets and set final line positions |
| 65 | for (UINT32 j = 0; j < numPages; j++) |
| 66 | { |
| 67 | SpriteRenderElement& renderElem = mCachedRenderElements[j]; |
| 68 | |
| 69 | genTextQuads(j, textData, desc.width, desc.height, desc.horzAlign, desc.vertAlign, desc.anchor, |
| 70 | renderElem.vertices, renderElem.uvs, renderElem.indexes, renderElem.numQuads); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | bs_frame_clear(); |
| 75 | |
| 76 | updateBounds(); |
| 77 | } |
| 78 | |
| 79 | UINT32 TextSprite::genTextQuads(UINT32 page, const TextDataBase& textData, UINT32 width, UINT32 height, |
| 80 | TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 bufferSizeQuads) |
| 81 | { |
| 82 | UINT32 numLines = textData.getNumLines(); |
| 83 | UINT32 newNumQuads = textData.getNumQuadsForPage(page); |
| 84 | |
| 85 | Vector2I* alignmentOffsets = bs_stack_new<Vector2I>(numLines); |
| 86 | getAlignmentOffsets(textData, width, height, horzAlign, vertAlign, alignmentOffsets); |
| 87 | Vector2I offset = getAnchorOffset(anchor, width, height); |
| 88 | |
| 89 | UINT32 quadOffset = 0; |
| 90 | for(UINT32 i = 0; i < numLines; i++) |
| 91 | { |
| 92 | const TextDataBase::TextLine& line = textData.getLine(i); |
| 93 | UINT32 writtenQuads = line.fillBuffer(page, vertices, uv, indices, quadOffset, bufferSizeQuads); |
| 94 | |
| 95 | Vector2I position = offset + alignmentOffsets[i]; |
| 96 | UINT32 numVertices = writtenQuads * 4; |
| 97 | for(UINT32 j = 0; j < numVertices; j++) |
| 98 | { |
| 99 | vertices[quadOffset * 4 + j].x += (float)position.x; |
| 100 | vertices[quadOffset * 4 + j].y += (float)position.y; |
| 101 | } |
| 102 | |
| 103 | quadOffset += writtenQuads; |
| 104 | } |
| 105 | |
| 106 | bs_stack_delete(alignmentOffsets, numLines); |
| 107 | return newNumQuads; |
| 108 | } |
| 109 | |
| 110 | |
| 111 | UINT32 TextSprite::genTextQuads(const TextDataBase& textData, UINT32 width, UINT32 height, |
| 112 | TextHorzAlign horzAlign, TextVertAlign vertAlign, SpriteAnchor anchor, Vector2* vertices, Vector2* uv, UINT32* indices, UINT32 bufferSizeQuads) |
| 113 | { |
| 114 | UINT32 numLines = textData.getNumLines(); |
| 115 | UINT32 numPages = textData.getNumPages(); |
| 116 | |
| 117 | Vector2I* alignmentOffsets = bs_stack_new<Vector2I>(numLines); |
| 118 | getAlignmentOffsets(textData, width, height, horzAlign, vertAlign, alignmentOffsets); |
| 119 | Vector2I offset = getAnchorOffset(anchor, width, height); |
| 120 | |
| 121 | UINT32 quadOffset = 0; |
| 122 | |
| 123 | for(UINT32 i = 0; i < numLines; i++) |
| 124 | { |
| 125 | const TextDataBase::TextLine& line = textData.getLine(i); |
| 126 | for(UINT32 j = 0; j < numPages; j++) |
| 127 | { |
| 128 | UINT32 writtenQuads = line.fillBuffer(j, vertices, uv, indices, quadOffset, bufferSizeQuads); |
| 129 | |
| 130 | Vector2I position = offset + alignmentOffsets[i]; |
| 131 | |
| 132 | UINT32 numVertices = writtenQuads * 4; |
| 133 | for(UINT32 k = 0; k < numVertices; k++) |
| 134 | { |
| 135 | vertices[quadOffset * 4 + k].x += (float)position.x; |
| 136 | vertices[quadOffset * 4 + k].y += (float)position.y; |
| 137 | } |
| 138 | |
| 139 | quadOffset += writtenQuads; |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | bs_stack_delete(alignmentOffsets, numLines); |
| 144 | return quadOffset; |
| 145 | } |
| 146 | |
| 147 | void TextSprite::getAlignmentOffsets(const TextDataBase& textData, |
| 148 | UINT32 width, UINT32 height, TextHorzAlign horzAlign, TextVertAlign vertAlign, Vector2I* output) |
| 149 | { |
| 150 | UINT32 numLines = textData.getNumLines(); |
| 151 | UINT32 curHeight = 0; |
| 152 | for(UINT32 i = 0; i < numLines; i++) |
| 153 | { |
| 154 | const TextDataBase::TextLine& line = textData.getLine(i); |
| 155 | curHeight += line.getYOffset(); |
| 156 | } |
| 157 | |
| 158 | // Calc vertical alignment offset |
| 159 | UINT32 vertDiff = (UINT32)std::max(0, (INT32)height - (INT32)curHeight); |
| 160 | UINT32 vertOffset = 0; |
| 161 | switch(vertAlign) |
| 162 | { |
| 163 | case TVA_Top: |
| 164 | vertOffset = 0; |
| 165 | break; |
| 166 | case TVA_Bottom: |
| 167 | vertOffset = (UINT32)std::max(0, (INT32)vertDiff); |
| 168 | break; |
| 169 | case TVA_Center: |
| 170 | vertOffset = (UINT32)std::max(0, (INT32)vertDiff) / 2; |
| 171 | break; |
| 172 | } |
| 173 | |
| 174 | // Calc horizontal alignment offset |
| 175 | UINT32 curY = 0; |
| 176 | for(UINT32 i = 0; i < numLines; i++) |
| 177 | { |
| 178 | const TextDataBase::TextLine& line = textData.getLine(i); |
| 179 | |
| 180 | UINT32 horzOffset = 0; |
| 181 | switch(horzAlign) |
| 182 | { |
| 183 | case THA_Left: |
| 184 | horzOffset = 0; |
| 185 | break; |
| 186 | case THA_Right: |
| 187 | horzOffset = (UINT32)std::max(0, (INT32)(width - line.getWidth())); |
| 188 | break; |
| 189 | case THA_Center: |
| 190 | horzOffset = (UINT32)std::max(0, (INT32)(width - line.getWidth())) / 2; |
| 191 | break; |
| 192 | } |
| 193 | |
| 194 | output[i] = Vector2I(horzOffset, vertOffset + curY); |
| 195 | curY += line.getYOffset(); |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | void TextSprite::clearMesh() |
| 200 | { |
| 201 | for (auto& renderElem : mCachedRenderElements) |
| 202 | { |
| 203 | if (renderElem.vertices != nullptr) |
| 204 | { |
| 205 | mAlloc.free(renderElem.vertices); |
| 206 | renderElem.vertices = nullptr; |
| 207 | } |
| 208 | |
| 209 | if (renderElem.uvs != nullptr) |
| 210 | { |
| 211 | mAlloc.free(renderElem.uvs); |
| 212 | renderElem.uvs = nullptr; |
| 213 | } |
| 214 | |
| 215 | if (renderElem.indexes != nullptr) |
| 216 | { |
| 217 | mAlloc.free(renderElem.indexes); |
| 218 | renderElem.indexes = nullptr; |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | mCachedRenderElements.clear(); |
| 223 | mAlloc.clear(); |
| 224 | |
| 225 | updateBounds(); |
| 226 | } |
| 227 | } |
| 228 | |