SpriteBatch.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. // Copyright (c) 2008-2023 the Urho3D project
  2. // License: MIT
  3. #include "SpriteBatch.h"
  4. #include "../UI/FontFace.h"
  5. namespace Urho3D
  6. {
  7. SpriteBatch::SpriteBatch(Context* context) : SpriteBatchBase(context)
  8. {
  9. spriteVS_ = graphics_->GetShader(VS, "Basic", "DIFFMAP VERTEXCOLOR");
  10. spritePS_ = graphics_->GetShader(PS, "Basic", "DIFFMAP VERTEXCOLOR");
  11. ttfTextVS_ = graphics_->GetShader(VS, "Text");
  12. ttfTextPS_ = graphics_->GetShader(PS, "Text", "ALPHAMAP");
  13. spriteTextVS_ = graphics_->GetShader(VS, "Text");
  14. spriteTextPS_ = graphics_->GetShader(PS, "Text");
  15. sdfTextVS_ = graphics_->GetShader(VS, "Text");
  16. sdfTextPS_ = graphics_->GetShader(PS, "Text", "SIGNED_DISTANCE_FIELD");
  17. shapeVS_ = graphics_->GetShader(VS, "Basic", "VERTEXCOLOR");
  18. shapePS_ = graphics_->GetShader(PS, "Basic", "VERTEXCOLOR");
  19. }
  20. static Rect PosToDest(const Vector2& position, Texture2D* texture, const Rect* src)
  21. {
  22. if (src == nullptr)
  23. {
  24. // Проверки не производятся, текстура должна быть корректной
  25. return Rect
  26. (
  27. position.x_,
  28. position.y_,
  29. position.x_ + texture->GetWidth(),
  30. position.y_ + texture->GetHeight()
  31. );
  32. }
  33. else
  34. {
  35. return Rect
  36. (
  37. position.x_,
  38. position.y_,
  39. position.x_ + (src->Right() - src->Left()), // Сперва вычисляем размер, так как там вероятно более близкие
  40. position.y_ + (src->Bottom() - src->Top()) // значения и меньше ошибка вычислений
  41. );
  42. }
  43. }
  44. // Преобразует пиксельные координаты в диапазон [0, 1]
  45. static Rect SrcToUV(const Rect* source, Texture2D* texture)
  46. {
  47. if (source == nullptr)
  48. {
  49. return Rect(Vector2::ZERO, Vector2::ONE);
  50. }
  51. else
  52. {
  53. // Проверки не производятся, текстура должна быть корректной
  54. float invWidth = 1.0f / texture->GetWidth();
  55. float invHeight = 1.0f / texture->GetHeight();
  56. return Rect
  57. (
  58. source->min_.x_ * invWidth,
  59. source->min_.y_ * invHeight,
  60. source->max_.x_ * invWidth,
  61. source->max_.y_ * invHeight
  62. );
  63. }
  64. }
  65. void SpriteBatch::DrawSprite(Texture2D* texture, const Rect& destination, const Rect* source, u32 color,
  66. float rotation, const Vector2& origin, const Vector2& scale, FlipModes flipModes)
  67. {
  68. if (!texture)
  69. return;
  70. sprite_.texture_ = texture;
  71. sprite_.vs_ = spriteVS_;
  72. sprite_.ps_ = spritePS_;
  73. sprite_.destination_ = destination;
  74. sprite_.sourceUV_ = SrcToUV(source, texture);
  75. sprite_.flipModes_ = flipModes;
  76. sprite_.scale_ = scale;
  77. sprite_.rotation_ = rotation;
  78. sprite_.origin_ = origin;
  79. sprite_.color0_ = color;
  80. sprite_.color1_ = color;
  81. sprite_.color2_ = color;
  82. sprite_.color3_ = color;
  83. DrawSpriteInternal();
  84. }
  85. void SpriteBatch::DrawSprite(Texture2D* texture, const Vector2& position, const Rect* source, u32 color,
  86. float rotation, const Vector2 &origin, const Vector2& scale, FlipModes flipModes)
  87. {
  88. if (!texture)
  89. return;
  90. sprite_.texture_ = texture;
  91. sprite_.vs_ = spriteVS_;
  92. sprite_.ps_ = spritePS_;
  93. sprite_.destination_ = PosToDest(position, texture, source);
  94. sprite_.sourceUV_ = SrcToUV(source, texture);
  95. sprite_.flipModes_ = flipModes;
  96. sprite_.scale_ = scale;
  97. sprite_.rotation_ = rotation;
  98. sprite_.origin_ = origin;
  99. sprite_.color0_ = color;
  100. sprite_.color1_ = color;
  101. sprite_.color2_ = color;
  102. sprite_.color3_ = color;
  103. DrawSpriteInternal();
  104. }
  105. void SpriteBatch::DrawSpriteInternal()
  106. {
  107. quad_.vs_ = sprite_.vs_;
  108. quad_.ps_ = sprite_.ps_;
  109. quad_.texture_ = sprite_.texture_;
  110. // Если спрайт не отмасштабирован и не повёрнут, то прорисовка очень проста
  111. if (sprite_.rotation_ == 0.0f && sprite_.scale_ == Vector2::ONE)
  112. {
  113. // Сдвигаем спрайт на -origin
  114. Rect resultDest(sprite_.destination_.min_ - sprite_.origin_, sprite_.destination_.max_ - sprite_.origin_);
  115. // Лицевая грань задаётся по часовой стрелке. Учитываем, что ось Y направлена вниз.
  116. // Но нет большой разницы, так как спрайты двусторонние
  117. quad_.v0_.position_ = Vector3(resultDest.min_.x_, resultDest.min_.y_, 0); // Верхний левый угол спрайта
  118. quad_.v1_.position_ = Vector3(resultDest.max_.x_, resultDest.min_.y_, 0); // Верхний правый угол
  119. quad_.v2_.position_ = Vector3(resultDest.max_.x_, resultDest.max_.y_, 0); // Нижний правый угол
  120. quad_.v3_.position_ = Vector3(resultDest.min_.x_, resultDest.max_.y_, 0); // Нижний левый угол
  121. }
  122. else
  123. {
  124. // Масштабировать и вращать необходимо относительно центра локальных координат:
  125. // 1) При стандартном origin == Vector2::ZERO, который соответствует верхнему левому углу спрайта,
  126. // локальные координаты будут Rect(ноль, размерыСпрайта),
  127. // то есть Rect(Vector2::ZERO, destination.max_ - destination.min_)
  128. // 2) При ненулевом origin нужно сдвинуть на -origin
  129. Rect local(-sprite_.origin_, sprite_.destination_.max_ - sprite_.destination_.min_ - sprite_.origin_);
  130. float sin, cos;
  131. SinCos(sprite_.rotation_, sin, cos);
  132. // Нам нужна матрица, которая масштабирует и поворачивает вершину в локальных координатах, а затем
  133. // смещает ее в требуемые мировые координаты.
  134. // Но в матрице 3x3 последняя строка "0 0 1", умножать на которую бессмысленно.
  135. // Поэтому вычисляем без матрицы для оптимизации
  136. float m11 = cos * sprite_.scale_.x_; float m12 = -sin * sprite_.scale_.y_; float m13 = sprite_.destination_.min_.x_;
  137. float m21 = sin * sprite_.scale_.x_; float m22 = cos * sprite_.scale_.y_; float m23 = sprite_.destination_.min_.y_;
  138. // 0 0 1
  139. float minXm11 = local.min_.x_ * m11;
  140. float minXm21 = local.min_.x_ * m21;
  141. float maxXm11 = local.max_.x_ * m11;
  142. float maxXm21 = local.max_.x_ * m21;
  143. float minYm12 = local.min_.y_ * m12;
  144. float minYm22 = local.min_.y_ * m22;
  145. float maxYm12 = local.max_.y_ * m12;
  146. float maxYm22 = local.max_.y_ * m22;
  147. // transform * Vector3(local.min_.x_, local.min_.y_, 1.0f);
  148. quad_.v0_.position_ = Vector3(minXm11 + minYm12 + m13,
  149. minXm21 + minYm22 + m23,
  150. 0.0f);
  151. // transform * Vector3(local.max_.x_, local.min_.y_, 1.0f).
  152. quad_.v1_.position_ = Vector3(maxXm11 + minYm12 + m13,
  153. maxXm21 + minYm22 + m23,
  154. 0.0f);
  155. // transform * Vector3(local.max_.x_, local.max_.y_, 1.0f).
  156. quad_.v2_.position_ = Vector3(maxXm11 + maxYm12 + m13,
  157. maxXm21 + maxYm22 + m23,
  158. 0.0f);
  159. // transform * Vector3(local.min_.x_, local.max_.y_, 1.0f).
  160. quad_.v3_.position_ = Vector3(minXm11 + maxYm12 + m13,
  161. minXm21 + maxYm22 + m23,
  162. 0.0f);
  163. }
  164. if (!!(sprite_.flipModes_ & FlipModes::Horizontally))
  165. Swap(sprite_.sourceUV_.min_.x_, sprite_.sourceUV_.max_.x_);
  166. if (!!(sprite_.flipModes_ & FlipModes::Vertically))
  167. Swap(sprite_.sourceUV_.min_.y_, sprite_.sourceUV_.max_.y_);
  168. quad_.v0_.color_ = sprite_.color0_;
  169. quad_.v0_.uv_ = sprite_.sourceUV_.min_;
  170. quad_.v1_.color_ = sprite_.color1_;
  171. quad_.v1_.uv_ = Vector2(sprite_.sourceUV_.max_.x_, sprite_.sourceUV_.min_.y_);
  172. quad_.v2_.color_ = sprite_.color2_;
  173. quad_.v2_.uv_ = sprite_.sourceUV_.max_;
  174. quad_.v3_.color_ = sprite_.color3_;
  175. quad_.v3_.uv_ = Vector2(sprite_.sourceUV_.min_.x_, sprite_.sourceUV_.max_.y_);
  176. AddQuad();
  177. }
  178. void SpriteBatch::DrawString(const String& text, Font* font, float fontSize, const Vector2& position, u32 color,
  179. float rotation, const Vector2& origin, const Vector2& scale, FlipModes flipModes)
  180. {
  181. if (text.Length() == 0)
  182. return;
  183. Vector<c32> unicodeText;
  184. for (i32 i = 0; i < text.Length();)
  185. unicodeText.Push(text.NextUTF8Char(i));
  186. if (font->GetFontType() == FONT_FREETYPE)
  187. {
  188. sprite_.vs_ = ttfTextVS_;
  189. sprite_.ps_ = ttfTextPS_;
  190. }
  191. else // FONT_BITMAP
  192. {
  193. if (font->IsSDFFont())
  194. {
  195. sprite_.vs_ = sdfTextVS_;
  196. sprite_.ps_ = sdfTextPS_;
  197. }
  198. else
  199. {
  200. sprite_.vs_ = spriteTextVS_;
  201. sprite_.ps_ = spriteTextPS_;
  202. }
  203. }
  204. sprite_.flipModes_ = flipModes;
  205. sprite_.scale_ = scale;
  206. sprite_.rotation_ = rotation;
  207. sprite_.color0_ = color;
  208. sprite_.color1_ = color;
  209. sprite_.color2_ = color;
  210. sprite_.color3_ = color;
  211. FontFace* face = font->GetFace(fontSize);
  212. const Vector<SharedPtr<Texture2D>>& textures = face->GetTextures();
  213. // По идее все текстуры одинакового размера
  214. float pixelWidth = 1.0f / textures[0]->GetWidth();
  215. float pixelHeight = 1.0f / textures[0]->GetHeight();
  216. Vector2 charPos = position;
  217. Vector2 charOrig = origin;
  218. i32 i = 0;
  219. i32 step = 1;
  220. if (!!(flipModes & FlipModes::Horizontally))
  221. {
  222. i = unicodeText.Size() - 1;
  223. step = -1;
  224. }
  225. for (; i >= 0 && i < unicodeText.Size(); i += step)
  226. {
  227. const FontGlyph* glyph = face->GetGlyph(unicodeText[i]);
  228. float gx = (float)glyph->x_;
  229. float gy = (float)glyph->y_;
  230. float gw = (float)glyph->width_;
  231. float gh = (float)glyph->height_;
  232. float gox = (float)glyph->offsetX_;
  233. float goy = (float)glyph->offsetY_;
  234. sprite_.texture_ = textures[glyph->page_];
  235. sprite_.destination_ = Rect(charPos.x_, charPos.y_, charPos.x_ + gw, charPos.y_ + gh);
  236. sprite_.sourceUV_ = Rect(gx * pixelWidth, gy * pixelHeight, (gx + gw) * pixelWidth, (gy + gh) * pixelHeight);
  237. // Модифицируем origin, а не позицию, чтобы было правильное вращение
  238. sprite_.origin_ = !!(flipModes & FlipModes::Vertically) ? charOrig - Vector2(gox, face->GetRowHeight() - goy - gh) : charOrig - Vector2(gox, goy);
  239. DrawSpriteInternal();
  240. charOrig.x_ -= (float)glyph->advanceX_;
  241. }
  242. }
  243. // В отличие от Sign() никогда не возвращает ноль
  244. template <typename T>
  245. T MySign(T value) { return value >= 0.0f ? 1.0f : -1.0f; }
  246. void SpriteBatch::DrawTriangle(const Vector2& v0, const Vector2& v1, const Vector2& v2)
  247. {
  248. triangle_.v0_.position_ = Vector3(v0);
  249. triangle_.v1_.position_ = Vector3(v1);
  250. triangle_.v2_.position_ = Vector3(v2);
  251. AddTriangle();
  252. }
  253. void SpriteBatch::DrawLine(const Vector2& start, const Vector2&end, float width)
  254. {
  255. float len = (end - start).Length();
  256. if (Equals(len, 0.0f))
  257. return;
  258. // Линия - это прямоугольный полигон. Когда линия не повернута (угол поворота равен нулю), она горизонтальна.
  259. // v0 ┌───────────────┐ v1
  260. //start ├───────────────┤ end
  261. // v3 └───────────────┘ v2
  262. // Пользователь задает координаты точек start и end, а нам нужно определить координаты вершин v0, v1, v2, v3.
  263. // Легче всего вычислить СМЕЩЕНИЯ вершин v0 и v3 от точки start и смещения вершин v1 и v2 от точки end,
  264. // а потом прибавить эти смещения к координатам точек start и end.
  265. // Когда линия горизонтальна, v0 имеет смещение (0, -halfWidth) относительно точки start,
  266. // а вершина v3 имеет смещение (0, halfWidth) относительно той же точки start.
  267. // Аналогично v1 = (0, -halfWidth) и v2 = (0, halfWidth) относительно точки end.
  268. float halfWidth = Abs(width * 0.5f);
  269. // Так как мы оперируем смещениями, то при повороте линии вершины v0 и v3 вращаются вокруг start, а v1 и v2 - вокруг end.
  270. // Итак, вращаем точку v0 с локальными координатами (0, halfWidth).
  271. // {newX = oldX * cos(deltaAngle) - oldY * sin(deltaAngle) = 0 * cos(deltaAngle) - halfWidth * sin(deltaAngle)
  272. // {newY = oldX * sin(deltaAngle) + oldY * cos(deltaAngle) = 0 * sin(deltaAngle) + halfWidth * cos(deltaAngle)
  273. // Так как повернутая линия может оказаться в любом квадранте, при вычислениии синуса и косинуса нам важен знак.
  274. len = len * MySign(end.x_ - start.x_) * MySign(end.y_ - start.y_);
  275. float cos = (end.x_ - start.x_) / len; // Прилежащий катет к гипотенузе.
  276. float sin = (end.y_ - start.y_) / len; // Противолежащий катет к гипотенузе.
  277. Vector2 offset = Vector2(-halfWidth * sin, halfWidth * cos);
  278. // Так как противоположные стороны параллельны, то можно не делать повторных вычислений:
  279. // смещение v0 всегда равно смещению v1, смещение v3 = смещению v2.
  280. // К тому же смещения вершин v0, v1 отличаются от смещений вершин v3, v2 только знаком (противоположны).
  281. Vector2 v0 = Vector2(start.x_ + offset.x_, start.y_ + offset.y_);
  282. Vector2 v1 = Vector2(end.x_ + offset.x_, end.y_ + offset.y_);
  283. Vector2 v2 = Vector2(end.x_ - offset.x_, end.y_ - offset.y_);
  284. Vector2 v3 = Vector2(start.x_ - offset.x_, start.y_ - offset.y_);
  285. DrawTriangle(v0, v1, v2);
  286. DrawTriangle(v2, v3, v0);
  287. }
  288. void SpriteBatch::DrawLine(float startX, float startY, float endX, float endY, float width)
  289. {
  290. DrawLine(Vector2(startX, startY), Vector2(endX, endY), width);
  291. }
  292. void SpriteBatch::DrawAABoxSolid(const Vector2& centerPos, const Vector2& halfSize)
  293. {
  294. DrawAABoxSolid(centerPos.x_, centerPos.y_, halfSize.x_, halfSize.y_);
  295. }
  296. void SpriteBatch::DrawAABBSolid(const Vector2& min, const Vector2& max)
  297. {
  298. Vector2 rightTop = Vector2(max.x_, min.y_); // Правый верхний угол
  299. Vector2 leftBot = Vector2(min.x_, max.y_); // Левый нижний
  300. DrawTriangle(min, rightTop, max);
  301. DrawTriangle(leftBot, min, max);
  302. }
  303. void SpriteBatch::DrawAABoxSolid(float centerX, float centerY, float halfWidth, float halfHeight)
  304. {
  305. if (halfWidth < M_EPSILON || halfHeight < M_EPSILON)
  306. return;
  307. Vector2 v0 = Vector2(centerX - halfWidth, centerY - halfHeight); // Левый верхний угол
  308. Vector2 v1 = Vector2(centerX + halfWidth, centerY - halfHeight); // Правый верхний
  309. Vector2 v2 = Vector2(centerX + halfWidth, centerY + halfHeight); // Правый нижний
  310. Vector2 v3 = Vector2(centerX - halfWidth, centerY + halfHeight); // Левый нижний
  311. DrawTriangle(v0, v1, v2);
  312. DrawTriangle(v2, v3, v0);
  313. }
  314. void SpriteBatch::DrawAABoxBorder(float centerX, float centerY, float halfWidth, float halfHeight, float borderWidth)
  315. {
  316. if (borderWidth < M_EPSILON || halfWidth < M_EPSILON || halfHeight < M_EPSILON)
  317. return;
  318. float halfBorderWidth = borderWidth * 0.5f;
  319. // Тут нужно обработать случай, когда толщина границы больше размера AABB
  320. // Верхняя граница
  321. float y = centerY - halfHeight + halfBorderWidth;
  322. DrawLine(centerX - halfWidth, y, centerX + halfWidth, y, borderWidth);
  323. // Нижняя граница
  324. y = centerY + halfHeight - halfBorderWidth;
  325. DrawLine(centerX - halfWidth, y, centerX + halfWidth, y, borderWidth);
  326. // При отрисовке боковых границ не перекрываем верхнюю и нижнюю, чтобы нормально отрисовывалось в случае полупрозрачного цвета
  327. // Левая граница
  328. float x = centerX - halfWidth + halfBorderWidth;
  329. DrawLine(x, centerY - halfHeight + borderWidth, x, centerY + halfHeight - borderWidth, borderWidth);
  330. // Правая граница
  331. x = centerX + halfWidth - halfBorderWidth;
  332. DrawLine(x, centerY - halfHeight + borderWidth, x, centerY + halfHeight - borderWidth, borderWidth);
  333. }
  334. void SpriteBatch::DrawCircle(const Vector2& centerPos, float radius)
  335. {
  336. const int numPoints = 40;
  337. Vector2 points[numPoints];
  338. for (int i = 0; i < numPoints; ++i)
  339. {
  340. float angle = 360.0f * (float)i / (float)numPoints;
  341. float cos, sin;
  342. SinCos(angle, sin, cos);
  343. points[i] = Vector2(cos, sin) * radius + centerPos;
  344. }
  345. for (int i = 1; i < numPoints; ++i)
  346. DrawTriangle(points[i], points[i - 1], centerPos);
  347. // Рисуем последний сегмент
  348. DrawTriangle(points[0], points[numPoints - 1], centerPos);
  349. }
  350. void SpriteBatch::DrawCircle(float centerX, float centerY, float radius)
  351. {
  352. DrawCircle(Vector2(centerX, centerY), radius);
  353. }
  354. // Поворачивает вектор по часовой стрелке на 90 градусов
  355. static Vector2 RotatePlus90(const Vector2& v)
  356. {
  357. Vector2 result(-v.y_, v.x_);
  358. return result;
  359. }
  360. // Поворачивает вектор по часовой стрелке на -90 градусов
  361. static Vector2 RotateMinus90(const Vector2& v)
  362. {
  363. Vector2 result(v.y_, -v.x_);
  364. return result;
  365. }
  366. void SpriteBatch::DrawArrow(const Vector2& start, const Vector2& end, float width)
  367. {
  368. // TODO: настроить Doxygen на поддержку картинок и тут ссылку на картинку
  369. Vector2 vec = end - start;
  370. float len = vec.Length();
  371. if (len < M_EPSILON)
  372. return;
  373. Vector2 dir = vec.Normalized();
  374. // TODO: Обработать случай, когда вектор короткий
  375. float headLen = width * 2; // Длина наконечника
  376. float shaftLen = len - headLen; // Длина древка
  377. Vector2 headStart = dir * shaftLen + start; // Начало наконечника
  378. Vector2 head = dir * headLen; // Вектор от точки headStart до точки end
  379. Vector2 headTop = RotateMinus90(head) + headStart;
  380. Vector2 headBottom = RotatePlus90(head) + headStart;
  381. DrawLine(start, headStart, width);
  382. DrawTriangle(headStart, headTop, end);
  383. DrawTriangle(headStart, headBottom, end);
  384. }
  385. }