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 "GUI/BsGUICanvas.h"
4#include "GUI/BsGUISkin.h"
5#include "Image/BsSpriteTexture.h"
6#include "GUI/BsGUIDimensions.h"
7#include "GUI/BsGUITexture.h"
8#include "Utility/BsShapeMeshes2D.h"
9#include "2D/BsSpriteManager.h"
10#include "2D/BsSpriteMaterials.h"
11#include "Mesh/BsMeshUtility.h"
12#include "Error/BsException.h"
13
14namespace bs
15{
16 const float GUICanvas::LINE_SMOOTH_BORDER_WIDTH = 3.0f;
17
18 const String& GUICanvas::getGUITypeName()
19 {
20 static String name = "Canvas";
21 return name;
22 }
23
24 GUICanvas::GUICanvas(const String& styleName, const GUIDimensions& dimensions)
25 : GUIElement(styleName, dimensions), mNumRenderElements(0), mDepthRange(1), mLastOffset(BsZero)
26 , mForceTriangleBuild(false)
27 {
28
29 }
30
31 GUICanvas::~GUICanvas()
32 {
33 clear();
34 }
35
36 GUICanvas* GUICanvas::create(const GUIOptions& options, const String& styleName)
37 {
38 return new (bs_alloc<GUICanvas>()) GUICanvas(getStyleName<GUICanvas>(styleName), GUIDimensions::create(options));
39 }
40
41 GUICanvas* GUICanvas::create(const String& styleName)
42 {
43 return new (bs_alloc<GUICanvas>()) GUICanvas(getStyleName<GUICanvas>(styleName), GUIDimensions::create());
44 }
45
46 void GUICanvas::drawLine(const Vector2I& a, const Vector2I& b, const Color& color, UINT8 depth)
47 {
48 drawPolyLine({ a, b }, color, depth);
49 }
50
51 void GUICanvas::drawPolyLine(const Vector<Vector2I>& vertices, const Color& color, UINT8 depth)
52 {
53 if(vertices.size() < 2)
54 return;
55
56 mElements.push_back(CanvasElement());
57 CanvasElement& element = mElements.back();
58
59 element.type = CanvasElementType::Line;
60 element.color = color;
61 element.dataId = (UINT32)mTriangleElementData.size();
62 element.vertexStart = (UINT32)mVertexData.size();
63 element.numVertices = (UINT32)vertices.size();
64 element.depth = depth;
65
66 mDepthRange = std::max(mDepthRange, (UINT8)(depth + 1));
67
68 mTriangleElementData.push_back(TriangleElementData());
69 TriangleElementData& elemData = mTriangleElementData.back();
70 elemData.matInfo.groupId = 0;
71 elemData.matInfo.tint = color;
72
73 for (auto& vertex : vertices)
74 {
75 Vector2 point = Vector2((float)vertex.x, (float)vertex.y);
76 point += Vector2(0.5f, 0.5f); // Offset to the middle of the pixel
77
78 mVertexData.push_back(point);
79 }
80
81 mForceTriangleBuild = true;
82 _markContentAsDirty();
83 }
84
85 void GUICanvas::drawTexture(const HSpriteTexture& texture, const Rect2I& area, TextureScaleMode scaleMode,
86 const Color& color, UINT8 depth)
87 {
88 mElements.push_back(CanvasElement());
89 CanvasElement& element = mElements.back();
90
91 element.type = CanvasElementType::Image;
92 element.color = color;
93 element.dataId = (UINT32)mImageData.size();
94 element.scaleMode = scaleMode;
95 element.imageSprite = bs_new<ImageSprite>();
96 element.depth = depth;
97
98 mDepthRange = std::max(mDepthRange, (UINT8)(depth + 1));
99
100 mImageData.push_back({ texture, area });
101 _markContentAsDirty();
102 }
103
104 void GUICanvas::drawTriangleStrip(const Vector<Vector2I>& vertices, const Color& color, UINT8 depth)
105 {
106 if (vertices.size() < 3)
107 {
108 LOGWRN("Invalid number of vertices. Ignoring call.");
109 return;
110 }
111
112 mElements.push_back(CanvasElement());
113 CanvasElement& element = mElements.back();
114
115 element.type = CanvasElementType::Triangle;
116 element.color = color;
117 element.dataId = (UINT32)mTriangleElementData.size();
118 element.vertexStart = (UINT32)mVertexData.size();
119 element.numVertices = (UINT32)(vertices.size() - 2) * 3;
120 element.depth = depth;
121
122 mDepthRange = std::max(mDepthRange, (UINT8)(depth + 1));
123
124 // Convert strip to list
125 for(UINT32 i = 2; i < (UINT32)vertices.size(); i++)
126 {
127 if (i % 2 == 0)
128 {
129 mVertexData.push_back(Vector2((float)vertices[i - 2].x + 0.5f, (float)vertices[i - 2].y + 0.5f));
130 mVertexData.push_back(Vector2((float)vertices[i - 1].x + 0.5f, (float)vertices[i - 1].y + 0.5f));
131 mVertexData.push_back(Vector2((float)vertices[i - 0].x + 0.5f, (float)vertices[i - 0].y + 0.5f));
132 }
133 else
134 {
135 mVertexData.push_back(Vector2((float)vertices[i - 0].x + 0.5f, (float)vertices[i - 0].y + 0.5f));
136 mVertexData.push_back(Vector2((float)vertices[i - 1].x + 0.5f, (float)vertices[i - 1].y + 0.5f));
137 mVertexData.push_back(Vector2((float)vertices[i - 2].x + 0.5f, (float)vertices[i - 2].y + 0.5f));
138 }
139 }
140
141 mTriangleElementData.push_back(TriangleElementData());
142 TriangleElementData& elemData = mTriangleElementData.back();
143 elemData.matInfo.groupId = 0;
144 elemData.matInfo.tint = color;
145
146 mForceTriangleBuild = true;
147 _markContentAsDirty();
148 }
149
150 void GUICanvas::drawTriangleList(const Vector<Vector2I>& vertices, const Color& color, UINT8 depth)
151 {
152 if (vertices.size() < 3 || vertices.size() % 3 != 0)
153 {
154 LOGWRN("Invalid number of vertices. Ignoring call.");
155 return;
156 }
157
158 mElements.push_back(CanvasElement());
159 CanvasElement& element = mElements.back();
160
161 element.type = CanvasElementType::Triangle;
162 element.color = color;
163 element.dataId = (UINT32)mTriangleElementData.size();
164 element.vertexStart = (UINT32)mVertexData.size();
165 element.numVertices = (UINT32)vertices.size();
166 element.depth = depth;
167
168 mDepthRange = std::max(mDepthRange, (UINT8)(depth + 1));
169
170 for (auto& vertex : vertices)
171 mVertexData.push_back(Vector2((float)vertex.x + 0.5f, (float)vertex.y + 0.5f));
172
173 mTriangleElementData.push_back(TriangleElementData());
174 TriangleElementData& elemData = mTriangleElementData.back();
175 elemData.matInfo.groupId = 0;
176 elemData.matInfo.tint = color;
177
178 mForceTriangleBuild = true;
179 _markContentAsDirty();
180 }
181
182 void GUICanvas::drawText(const String& text, const Vector2I& position, const HFont& font, UINT32 size,
183 const Color& color, UINT8 depth)
184 {
185 mElements.push_back(CanvasElement());
186 CanvasElement& element = mElements.back();
187
188 element.type = CanvasElementType::Text;
189 element.color = color;
190 element.dataId = (UINT32)mTextData.size();
191 element.size = size;
192 element.textSprite = bs_new<TextSprite>();
193 element.depth = depth;
194
195 mDepthRange = std::max(mDepthRange, (UINT8)(depth + 1));
196
197 mTextData.push_back({ text, font, position });
198 _markContentAsDirty();
199 }
200
201 void GUICanvas::clear()
202 {
203 for (auto& element : mElements)
204 {
205 if(element.type == CanvasElementType::Image && element.imageSprite != nullptr)
206 bs_delete(element.imageSprite);
207
208 if (element.type == CanvasElementType::Text && element.textSprite != nullptr)
209 bs_delete(element.textSprite);
210 }
211
212 mElements.clear();
213 mNumRenderElements = 0;
214 mDepthRange = 1;
215
216 mVertexData.clear();
217 mImageData.clear();
218 mTextData.clear();
219 mTriangleElementData.clear();
220 mClippedVertices.clear();
221 mClippedLineVertices.clear();
222 mForceTriangleBuild = false;
223 }
224
225 UINT32 GUICanvas::_getNumRenderElements() const
226 {
227 return mNumRenderElements;
228 }
229
230 UINT32 GUICanvas::_getRenderElementDepth(UINT32 renderElementIdx) const
231 {
232 const CanvasElement& element = findElement(renderElementIdx);
233 return _getDepth() + element.depth;
234 }
235
236 const SpriteMaterialInfo& GUICanvas::_getMaterial(UINT32 renderElementIdx, SpriteMaterial** material) const
237 {
238 static const SpriteMaterialInfo defaultMatInfo;
239
240 Vector2 offset((float)mLayoutData.area.x, (float)mLayoutData.area.y);
241 Rect2I clipRect = mLayoutData.getLocalClipRect();
242 buildAllTriangleElementsIfDirty(offset, clipRect);
243
244 const CanvasElement& element = findElement(renderElementIdx);
245 renderElementIdx -= element.renderElemStart;
246
247 switch (element.type)
248 {
249 case CanvasElementType::Line:
250 *material = SpriteManager::instance().getLineMaterial();
251 return mTriangleElementData[element.dataId].matInfo;
252 case CanvasElementType::Image:
253 *material = element.imageSprite->getMaterial(0);
254 return element.imageSprite->getMaterialInfo(0);
255 case CanvasElementType::Text:
256 *material = element.imageSprite->getMaterial(renderElementIdx);
257 return element.textSprite->getMaterialInfo(renderElementIdx);
258 case CanvasElementType::Triangle:
259 *material = SpriteManager::instance().getImageMaterial(true);
260 return mTriangleElementData[element.dataId].matInfo;
261 default:
262 *material = nullptr;
263 return defaultMatInfo;
264 }
265 }
266
267 void GUICanvas::_getMeshInfo(UINT32 renderElementIdx, UINT32& numVertices, UINT32& numIndices, GUIMeshType& type) const
268 {
269 Vector2 offset((float)mLayoutData.area.x, (float)mLayoutData.area.y);
270 Rect2I clipRect = mLayoutData.getLocalClipRect();
271 buildAllTriangleElementsIfDirty(offset, clipRect);
272
273 const CanvasElement& element = findElement(renderElementIdx);
274 renderElementIdx -= element.renderElemStart;
275
276 switch (element.type)
277 {
278 case CanvasElementType::Image:
279 {
280 UINT32 numQuads = element.imageSprite->getNumQuads(renderElementIdx);
281 numVertices = numQuads * 4;
282 numIndices = numQuads * 6;
283 type = GUIMeshType::Triangle;
284 break;
285 }
286 case CanvasElementType::Text:
287 {
288 UINT32 numQuads = element.textSprite->getNumQuads(renderElementIdx);
289 numVertices = numQuads * 4;
290 numIndices = numQuads * 6;
291 type = GUIMeshType::Triangle;
292 break;
293 }
294 case CanvasElementType::Line:
295 numVertices = element.clippedNumVertices;
296 numIndices = element.clippedNumVertices;
297 type = GUIMeshType::Line;
298 break;
299 case CanvasElementType::Triangle:
300 numVertices = element.clippedNumVertices;
301 numIndices = element.clippedNumVertices;
302 type = GUIMeshType::Triangle;
303 break;
304 default:
305 numVertices = 0;
306 numIndices = 0;
307 type = GUIMeshType::Triangle;
308 break;
309 }
310 }
311
312 void GUICanvas::updateRenderElementsInternal()
313 {
314 mNumRenderElements = 0;
315 for(auto& element : mElements)
316 {
317 switch(element.type)
318 {
319 case CanvasElementType::Image:
320 buildImageElement(element);
321 element.renderElemStart = mNumRenderElements;
322 element.renderElemEnd = element.renderElemStart + element.imageSprite->getNumRenderElements();
323 break;
324 case CanvasElementType::Text:
325 buildTextElement(element);
326 element.renderElemStart = mNumRenderElements;
327 element.renderElemEnd = element.renderElemStart + element.textSprite->getNumRenderElements();
328 break;
329 case CanvasElementType::Line:
330 case CanvasElementType::Triangle:
331 element.renderElemStart = mNumRenderElements;
332 element.renderElemEnd = element.renderElemStart + 1;
333
334 mTriangleElementData[element.dataId].matInfo.groupId = (UINT64)_getParentWidget();
335
336 // Actual mesh build happens when reading from it, because the mesh size varies due to clipping rectangle/offset
337 break;
338 }
339
340 mNumRenderElements = element.renderElemEnd;
341 }
342
343 GUIElement::updateRenderElementsInternal();
344 }
345
346 Vector2I GUICanvas::_getOptimalSize() const
347 {
348 return Vector2I(10, 10);
349 }
350
351 void GUICanvas::_fillBuffer(UINT8* vertices, UINT32* indices, UINT32 vertexOffset, UINT32 indexOffset,
352 UINT32 maxNumVerts, UINT32 maxNumIndices, UINT32 renderElementIdx) const
353 {
354 UINT8* uvs = vertices + sizeof(Vector2);
355 UINT32 indexStride = sizeof(UINT32);
356
357 Vector2I offset(mLayoutData.area.x, mLayoutData.area.y);
358 Rect2I clipRect = mLayoutData.getLocalClipRect();
359
360 Vector2 floatOffset((float)offset.x, (float)offset.y);
361 buildAllTriangleElementsIfDirty(floatOffset, clipRect);
362
363 const CanvasElement& element = findElement(renderElementIdx);
364 renderElementIdx -= element.renderElemStart;
365
366 switch(element.type)
367 {
368 case CanvasElementType::Image:
369 {
370 UINT32 vertexStride = sizeof(Vector2) * 2;
371 const Rect2I& area = mImageData[element.dataId].area;
372
373 offset.x += area.x;
374 offset.y += area.y;
375 clipRect.x -= area.x;
376 clipRect.y -= area.y;
377
378 element.imageSprite->fillBuffer(vertices, uvs, indices, vertexOffset, indexOffset, maxNumVerts, maxNumIndices,
379 vertexStride, indexStride, renderElementIdx, offset, clipRect);
380 }
381 break;
382 case CanvasElementType::Text:
383 {
384 UINT32 vertexStride = sizeof(Vector2) * 2;
385 const Vector2I& position = mTextData[element.dataId].position;
386 offset += position;
387 clipRect.x -= position.x;
388 clipRect.y -= position.y;
389
390 element.textSprite->fillBuffer(vertices, uvs, indices, vertexOffset, indexOffset, maxNumVerts, maxNumIndices,
391 vertexStride, indexStride, renderElementIdx, offset, clipRect);
392 }
393 break;
394 case CanvasElementType::Triangle:
395 {
396 UINT32 vertexStride = sizeof(Vector2) * 2;
397
398 UINT32 startVert = vertexOffset;
399 UINT32 startIndex = indexOffset;
400
401 UINT32 maxVertIdx = maxNumVerts;
402 UINT32 maxIndexIdx = maxNumIndices;
403
404 UINT32 numVertices = element.clippedNumVertices;
405 UINT32 numIndices = numVertices;
406
407 assert((startVert + numVertices) <= maxVertIdx);
408 assert((startIndex + numIndices) <= maxIndexIdx);
409
410 UINT8* vertDst = vertices + startVert * vertexStride;
411 UINT8* uvDst = uvs + startVert * vertexStride;
412 UINT32* indexDst = indices + startIndex;
413
414 Vector2 zeroUV(BsZero);
415 for(UINT32 i = 0; i < element.clippedNumVertices; i++)
416 {
417 memcpy(vertDst, &mClippedVertices[element.clippedVertexStart + i], sizeof(Vector2));
418 memcpy(uvDst, &zeroUV, sizeof(Vector2));
419
420 vertDst += vertexStride;
421 uvDst += vertexStride;
422 indexDst[i] = i;
423 }
424 }
425 break;
426 case CanvasElementType::Line:
427 {
428 UINT32 vertexStride = sizeof(Vector2);
429
430 UINT32 startVert = vertexOffset;
431 UINT32 startIndex = indexOffset;
432
433 UINT32 maxVertIdx = maxNumVerts;
434 UINT32 maxIndexIdx = maxNumIndices;
435
436 UINT32 numVertices = element.clippedNumVertices;
437 UINT32 numIndices = numVertices;
438
439 assert((startVert + numVertices) <= maxVertIdx);
440 assert((startIndex + numIndices) <= maxIndexIdx);
441
442 UINT8* vertDst = vertices + startVert * vertexStride;
443 UINT32* indexDst = indices + startIndex;
444
445 for (UINT32 i = 0; i < element.clippedNumVertices; i++)
446 {
447 const Vector2& point = mClippedLineVertices[element.clippedVertexStart + i];
448
449 memcpy(vertDst, &point, sizeof(Vector2));
450
451 vertDst += vertexStride;
452 indexDst[i] = i;
453 }
454 }
455 break;
456 }
457 }
458
459 void GUICanvas::buildImageElement(const CanvasElement& element)
460 {
461 assert(element.type == CanvasElementType::Image);
462
463 const ImageElementData& imageData = mImageData[element.dataId];
464
465 IMAGE_SPRITE_DESC desc;
466 desc.width = imageData.area.width;
467 desc.height = imageData.area.height;
468
469 desc.transparent = true;
470 desc.color = element.color;
471
472 Vector2I textureSize;
473 if (SpriteTexture::checkIsLoaded(imageData.texture))
474 {
475 desc.texture = imageData.texture;
476 textureSize.x = desc.texture->getWidth();
477 textureSize.y = desc.texture->getHeight();
478 }
479
480 Vector2I destSize(mLayoutData.area.width, mLayoutData.area.height);
481 desc.uvScale = ImageSprite::getTextureUVScale(textureSize, destSize, element.scaleMode);
482
483 element.imageSprite->update(desc, (UINT64)_getParentWidget());
484 }
485
486 void GUICanvas::buildTextElement(const CanvasElement& element)
487 {
488 assert(element.type == CanvasElementType::Text);
489
490 const TextElementData& textData = mTextData[element.dataId];
491
492 TEXT_SPRITE_DESC desc;
493 desc.font = textData.font;
494 desc.fontSize = element.size;
495 desc.text = textData.string;
496 desc.color = element.color;
497
498 element.textSprite->update(desc, (UINT64)_getParentWidget());
499 }
500
501 void GUICanvas::buildTriangleElement(const CanvasElement& element, const Vector2& offset, const Rect2I& clipRect) const
502 {
503 assert(element.type == CanvasElementType::Triangle || element.type == CanvasElementType::Line);
504
505 if (element.type == CanvasElementType::Triangle)
506 {
507 UINT8* verticesToClip = (UINT8*)&mVertexData[element.vertexStart];
508 UINT32 trianglesToClip = element.numVertices / 3;
509
510 auto writeCallback = [&](Vector2* vertices, Vector2* uvs, UINT32 count)
511 {
512 for (UINT32 i = 0; i < count; i++)
513 mClippedVertices.push_back(vertices[i] + offset);
514
515 element.clippedNumVertices += count;
516 };
517
518 element.clippedVertexStart = (UINT32)mClippedVertices.size();
519 element.clippedNumVertices = 0;
520
521 ImageSprite::clipTrianglesToRect(verticesToClip, nullptr, trianglesToClip, sizeof(Vector2), clipRect,
522 writeCallback);
523 }
524 else
525 {
526 UINT32 numLines = element.numVertices - 1;
527 const Vector2* linePoints = &mVertexData[element.vertexStart];
528
529 struct Plane2D
530 {
531 Plane2D(const Vector2& normal, float d)
532 :normal(normal), d(d)
533 { }
534
535 Vector2 normal;
536 float d;
537 };
538
539 std::array<Plane2D, 4> clipPlanes =
540 {{
541 Plane2D(Vector2(1.0f, 0.0f), (float)clipRect.x),
542 Plane2D(Vector2(-1.0f, 0.0f), (float)-(clipRect.x + (INT32)clipRect.width)),
543 Plane2D(Vector2(0.0f, 1.0f), (float)clipRect.y),
544 Plane2D(Vector2(0.0f, -1.0f), (float)-(clipRect.y + (INT32)clipRect.height))
545 }};
546
547 element.clippedVertexStart = (UINT32)mClippedLineVertices.size();
548 element.clippedNumVertices = 0;
549
550 for (UINT32 i = 0; i < numLines; i++)
551 {
552 Vector2 a = linePoints[i];
553 Vector2 b = linePoints[i + 1];
554
555 bool isVisible = true;
556 for(UINT32 j = 0; j < (UINT32)clipPlanes.size(); j++)
557 {
558 const Plane2D& plane = clipPlanes[j];
559 float d0 = plane.normal.dot(a) - plane.d;
560 float d1 = plane.normal.dot(b) - plane.d;
561
562 // Line not visible
563 if (d0 <= 0 && d1 <= 0)
564 {
565 isVisible = false;
566 break;
567 }
568
569 // Line visible completely
570 if (d0 >= 0 && d1 >= 0)
571 continue;
572
573 // The line is split by the plane, compute the point of intersection.
574 float t = d0 / (d0 - d1);
575 Vector2 intersectPt = (1 - t)*a + t*b;
576
577 if (d0 > 0)
578 b = intersectPt;
579 else
580 a = intersectPt;
581 }
582
583 if (!isVisible)
584 continue;
585
586 mClippedLineVertices.push_back(a + offset);
587 mClippedLineVertices.push_back(b + offset);
588
589 element.clippedNumVertices += 2;
590 }
591 }
592 }
593
594 void GUICanvas::buildAllTriangleElementsIfDirty(const Vector2& offset, const Rect2I& clipRect) const
595 {
596 // We need to rebuild if new triangle element(s) were added, or if offset or clip rectangle changed
597 bool isDirty = mForceTriangleBuild || (mLastOffset != offset) || (mLastClipRect != clipRect);
598 if (!isDirty)
599 return;
600
601 mClippedVertices.clear();
602 mClippedLineVertices.clear();
603 for(auto& element : mElements)
604 {
605 if (element.type != CanvasElementType::Triangle && element.type != CanvasElementType::Line)
606 continue;
607
608 buildTriangleElement(element, offset, clipRect);
609 }
610
611 mLastOffset = offset;
612 mLastClipRect = clipRect;
613 mForceTriangleBuild = false;
614 }
615
616 const GUICanvas::CanvasElement& GUICanvas::findElement(UINT32 renderElementIdx) const
617 {
618 INT32 start = 0;
619 INT32 end = (INT32)(mElements.size() - 1);
620
621 while (start <= end)
622 {
623 INT32 middle = (start + end) / 2;
624 const CanvasElement& current = mElements[middle];
625
626 if (renderElementIdx >= current.renderElemStart && renderElementIdx < current.renderElemEnd)
627 return current;
628
629 if (renderElementIdx < current.renderElemStart)
630 end = middle - 1;
631 else
632 start = middle + 1;
633 }
634
635 BS_EXCEPT(InvalidParametersException, "Cannot find requested GUI render element.");
636
637 static CanvasElement dummyElement;
638 return dummyElement;
639 }
640}