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 | |