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 "Math/BsVector2.h" |
5 | #include "Math/BsPlane.h" |
6 | #include "Mesh/BsMeshUtility.h" |
7 | |
8 | namespace bs |
9 | { |
10 | Rect2I Sprite::getBounds(const Vector2I& offset, const Rect2I& clipRect) const |
11 | { |
12 | Rect2I bounds = mBounds; |
13 | |
14 | if(clipRect.width > 0 && clipRect.height > 0) |
15 | bounds.clip(clipRect); |
16 | |
17 | bounds.x += offset.x; |
18 | bounds.y += offset.y; |
19 | |
20 | return bounds; |
21 | } |
22 | |
23 | UINT32 Sprite::getNumRenderElements() const |
24 | { |
25 | return (UINT32)mCachedRenderElements.size(); |
26 | } |
27 | |
28 | const SpriteMaterialInfo& Sprite::getMaterialInfo(UINT32 renderElementIdx) const |
29 | { |
30 | return mCachedRenderElements.at(renderElementIdx).matInfo; |
31 | } |
32 | |
33 | SpriteMaterial* Sprite::getMaterial(UINT32 renderElementIdx) const |
34 | { |
35 | return mCachedRenderElements.at(renderElementIdx).material; |
36 | } |
37 | |
38 | UINT32 Sprite::getNumQuads(UINT32 renderElementIdx) const |
39 | { |
40 | return mCachedRenderElements.at(renderElementIdx).numQuads; |
41 | } |
42 | |
43 | UINT32 Sprite::fillBuffer(UINT8* vertices, UINT8* uv, UINT32* indices, UINT32 vertexOffset, UINT32 indexOffset, |
44 | UINT32 maxNumVerts, UINT32 maxNumIndices, UINT32 vertexStride, UINT32 indexStride, UINT32 renderElementIdx, |
45 | const Vector2I& offset, const Rect2I& clipRect, bool clip) const |
46 | { |
47 | const auto& renderElem = mCachedRenderElements.at(renderElementIdx); |
48 | |
49 | UINT32 startVert = vertexOffset; |
50 | UINT32 startIndex = indexOffset; |
51 | |
52 | UINT32 maxVertIdx = maxNumVerts; |
53 | UINT32 maxIndexIdx = maxNumIndices; |
54 | |
55 | UINT32 numVertices = renderElem.numQuads * 4; |
56 | UINT32 numIndices = renderElem.numQuads * 6; |
57 | |
58 | assert((startVert + numVertices) <= maxVertIdx); |
59 | assert((startIndex + numIndices) <= maxIndexIdx); |
60 | |
61 | UINT8* vertDst = vertices + startVert * vertexStride; |
62 | UINT8* uvDst = uv + startVert * vertexStride; |
63 | |
64 | // TODO - I'm sure this can be done in a more cache friendly way. Profile it later. |
65 | Vector2 vecOffset((float)offset.x, (float)offset.y); |
66 | if(clip) |
67 | { |
68 | for(UINT32 i = 0; i < renderElem.numQuads; i++) |
69 | { |
70 | UINT8* vecStart = vertDst; |
71 | UINT8* uvStart = uvDst; |
72 | UINT32 vertIdx = i * 4; |
73 | |
74 | memcpy(vertDst, &renderElem.vertices[vertIdx + 0], sizeof(Vector2)); |
75 | memcpy(uvDst, &renderElem.uvs[vertIdx + 0], sizeof(Vector2)); |
76 | |
77 | vertDst += vertexStride; |
78 | uvDst += vertexStride; |
79 | |
80 | memcpy(vertDst, &renderElem.vertices[vertIdx + 1], sizeof(Vector2)); |
81 | memcpy(uvDst, &renderElem.uvs[vertIdx + 1], sizeof(Vector2)); |
82 | |
83 | vertDst += vertexStride; |
84 | uvDst += vertexStride; |
85 | |
86 | memcpy(vertDst, &renderElem.vertices[vertIdx + 2], sizeof(Vector2)); |
87 | memcpy(uvDst, &renderElem.uvs[vertIdx + 2], sizeof(Vector2)); |
88 | |
89 | vertDst += vertexStride; |
90 | uvDst += vertexStride; |
91 | |
92 | memcpy(vertDst, &renderElem.vertices[vertIdx + 3], sizeof(Vector2)); |
93 | memcpy(uvDst, &renderElem.uvs[vertIdx + 3], sizeof(Vector2)); |
94 | |
95 | clipQuadsToRect(vecStart, uvStart, 1, vertexStride, clipRect); |
96 | |
97 | vertDst = vecStart; |
98 | Vector2* curVec = (Vector2*)vertDst; |
99 | *curVec += vecOffset; |
100 | |
101 | vertDst += vertexStride; |
102 | curVec = (Vector2*)vertDst; |
103 | *curVec += vecOffset; |
104 | |
105 | vertDst += vertexStride; |
106 | curVec = (Vector2*)vertDst; |
107 | *curVec += vecOffset; |
108 | |
109 | vertDst += vertexStride; |
110 | curVec = (Vector2*)vertDst; |
111 | *curVec += vecOffset; |
112 | |
113 | vertDst += vertexStride; |
114 | uvDst += vertexStride; |
115 | } |
116 | } |
117 | else |
118 | { |
119 | for(UINT32 i = 0; i < renderElem.numQuads; i++) |
120 | { |
121 | UINT8* vecStart = vertDst; |
122 | UINT32 vertIdx = i * 4; |
123 | |
124 | memcpy(vertDst, &renderElem.vertices[vertIdx + 0], sizeof(Vector2)); |
125 | memcpy(uvDst, &renderElem.uvs[vertIdx + 0], sizeof(Vector2)); |
126 | |
127 | vertDst += vertexStride; |
128 | uvDst += vertexStride; |
129 | |
130 | memcpy(vertDst, &renderElem.vertices[vertIdx + 1], sizeof(Vector2)); |
131 | memcpy(uvDst, &renderElem.uvs[vertIdx + 1], sizeof(Vector2)); |
132 | |
133 | vertDst += vertexStride; |
134 | uvDst += vertexStride; |
135 | |
136 | memcpy(vertDst, &renderElem.vertices[vertIdx + 2], sizeof(Vector2)); |
137 | memcpy(uvDst, &renderElem.uvs[vertIdx + 2], sizeof(Vector2)); |
138 | |
139 | vertDst += vertexStride; |
140 | uvDst += vertexStride; |
141 | |
142 | memcpy(vertDst, &renderElem.vertices[vertIdx + 3], sizeof(Vector2)); |
143 | memcpy(uvDst, &renderElem.uvs[vertIdx + 3], sizeof(Vector2)); |
144 | |
145 | vertDst = vecStart; |
146 | Vector2* curVec = (Vector2*)vertDst; |
147 | *curVec += vecOffset; |
148 | |
149 | vertDst += vertexStride; |
150 | curVec = (Vector2*)vertDst; |
151 | *curVec += vecOffset; |
152 | |
153 | vertDst += vertexStride; |
154 | curVec = (Vector2*)vertDst; |
155 | *curVec += vecOffset; |
156 | |
157 | vertDst += vertexStride; |
158 | curVec = (Vector2*)vertDst; |
159 | *curVec += vecOffset; |
160 | |
161 | vertDst += vertexStride; |
162 | uvDst += vertexStride; |
163 | } |
164 | } |
165 | |
166 | if(indices != nullptr) |
167 | memcpy(&indices[startIndex], renderElem.indexes, numIndices * sizeof(UINT32)); |
168 | |
169 | return renderElem.numQuads; |
170 | } |
171 | |
172 | Vector2I Sprite::getAnchorOffset(SpriteAnchor anchor, UINT32 width, UINT32 height) |
173 | { |
174 | switch(anchor) |
175 | { |
176 | case SA_TopLeft: |
177 | return -Vector2I(0, 0); |
178 | case SA_TopCenter: |
179 | return -Vector2I(width / 2, 0); |
180 | case SA_TopRight: |
181 | return -Vector2I(width, 0); |
182 | case SA_MiddleLeft: |
183 | return -Vector2I(0, height / 2); |
184 | case SA_MiddleCenter: |
185 | return -Vector2I(width / 2, height / 2); |
186 | case SA_MiddleRight: |
187 | return -Vector2I(width, height / 2); |
188 | case SA_BottomLeft: |
189 | return -Vector2I(0, height); |
190 | case SA_BottomCenter: |
191 | return -Vector2I(width / 2, height); |
192 | case SA_BottomRight: |
193 | return -Vector2I(width, height); |
194 | } |
195 | |
196 | return Vector2I(); |
197 | } |
198 | |
199 | void Sprite::updateBounds() const |
200 | { |
201 | Vector2 min; |
202 | Vector2 max; |
203 | |
204 | // Find starting point |
205 | bool foundStartingPoint = false; |
206 | for(auto& renderElem : mCachedRenderElements) |
207 | { |
208 | if(renderElem.vertices != nullptr && renderElem.numQuads > 0) |
209 | { |
210 | min = renderElem.vertices[0]; |
211 | max = renderElem.vertices[0]; |
212 | foundStartingPoint = true; |
213 | break; |
214 | } |
215 | } |
216 | |
217 | if(!foundStartingPoint) |
218 | { |
219 | mBounds = Rect2I(0, 0, 0, 0); |
220 | return; |
221 | } |
222 | |
223 | // Calculate bounds |
224 | for(auto& renderElem : mCachedRenderElements) |
225 | { |
226 | if(renderElem.vertices != nullptr && renderElem.numQuads > 0) |
227 | { |
228 | UINT32 vertexCount = renderElem.numQuads * 4; |
229 | |
230 | for(UINT32 i = 0; i < vertexCount; i++) |
231 | { |
232 | min = Vector2::min(min, renderElem.vertices[i]); |
233 | max = Vector2::max(max, renderElem.vertices[i]); |
234 | } |
235 | } |
236 | } |
237 | |
238 | mBounds = Rect2I((int)min.x, (int)min.y, (int)(max.x - min.x), (int)(max.y - min.y)); |
239 | } |
240 | |
241 | // This will only properly clip an array of quads |
242 | // Vertices in the quad must be in a specific order: top left, top right, bottom left, bottom right |
243 | // (0, 0) represents top left of the screen |
244 | void Sprite::clipQuadsToRect(UINT8* vertices, UINT8* uv, UINT32 numQuads, UINT32 vertStride, const Rect2I& clipRect) |
245 | { |
246 | float left = (float)clipRect.x; |
247 | float right = (float)clipRect.x + clipRect.width; |
248 | float top = (float)clipRect.y; |
249 | float bottom = (float)clipRect.y + clipRect.height; |
250 | |
251 | for(UINT32 i = 0; i < numQuads; i++) |
252 | { |
253 | Vector2* vecA = (Vector2*)(vertices); |
254 | Vector2* vecB = (Vector2*)(vertices + vertStride); |
255 | Vector2* vecC = (Vector2*)(vertices + vertStride * 2); |
256 | Vector2* vecD = (Vector2*)(vertices + vertStride * 3); |
257 | |
258 | Vector2* uvA = (Vector2*)(uv); |
259 | Vector2* uvB = (Vector2*)(uv + vertStride); |
260 | Vector2* uvC = (Vector2*)(uv + vertStride * 2); |
261 | Vector2* uvD = (Vector2*)(uv + vertStride * 3); |
262 | |
263 | // Attempt to skip those that are definitely not clipped |
264 | if(vecA->x >= left && vecB->x <= right && |
265 | vecA->y >= top && vecC->y <= bottom) |
266 | { |
267 | continue; |
268 | } |
269 | |
270 | // TODO - Skip those that are 100% clipped as well |
271 | |
272 | float du = (uvB->x - uvA->x) / (vecB->x - vecA->x); |
273 | float dv = (uvA->y - uvC->y) / (vecA->y - vecD->y); |
274 | |
275 | if(right < left) |
276 | std::swap(left, right); |
277 | |
278 | if(bottom < top) |
279 | std::swap(bottom, top); |
280 | |
281 | // Clip left |
282 | float newLeft = Math::clamp(vecA->x, left, right); |
283 | float uvLeftOffset = (newLeft - vecA->x) * du; |
284 | |
285 | // Clip right |
286 | float newRight = Math::clamp(vecB->x, left, right); |
287 | float uvRightOffset = (vecB->x - newRight) * du; |
288 | |
289 | // Clip top |
290 | float newTop = Math::clamp(vecA->y, top, bottom); |
291 | float uvTopOffset = (newTop - vecA->y) * dv; |
292 | |
293 | // Clip bottom |
294 | float newBottom = Math::clamp(vecC->y, top, bottom); |
295 | float uvBottomOffset = (vecC->y - newBottom) * dv; |
296 | |
297 | vecA->x = newLeft; |
298 | vecC->x = newLeft; |
299 | vecB->x = newRight; |
300 | vecD->x = newRight; |
301 | vecA->y = newTop; |
302 | vecB->y = newTop; |
303 | vecC->y = newBottom; |
304 | vecD->y = newBottom; |
305 | |
306 | uvA->x += uvLeftOffset; |
307 | uvC->x += uvLeftOffset; |
308 | uvB->x -= uvRightOffset; |
309 | uvD->x -= uvRightOffset; |
310 | uvA->y += uvTopOffset; |
311 | uvB->y += uvTopOffset; |
312 | uvC->y -= uvBottomOffset; |
313 | uvD->y -= uvBottomOffset; |
314 | |
315 | vertices += vertStride * 4; |
316 | uv += vertStride * 4; |
317 | } |
318 | } |
319 | |
320 | void Sprite::clipTrianglesToRect(UINT8* vertices, UINT8* uv, UINT32 numTris, UINT32 vertStride, const Rect2I& clipRect, |
321 | const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback) |
322 | { |
323 | Vector<Plane> clipPlanes = |
324 | { |
325 | Plane(Vector3(1.0f, 0.0f, 0.0f), (float)clipRect.x), |
326 | Plane(Vector3(-1.0f, 0.0f, 0.0f), (float)-(clipRect.x + (INT32)clipRect.width)), |
327 | Plane(Vector3(0.0f, 1.0f, 0.0f), (float)clipRect.y), |
328 | Plane(Vector3(0.0f, -1.0f, 0.0f), (float)-(clipRect.y + (INT32)clipRect.height)) |
329 | }; |
330 | |
331 | MeshUtility::clip2D(vertices, uv, numTris, vertStride, clipPlanes, writeCallback); |
332 | } |
333 | } |