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
8namespace 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}