StretchableSprite2D.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. //
  2. // Copyright (c) 2008-2020 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "../Precompiled.h"
  23. #include "../IO/Log.h"
  24. #include "../Core/Context.h"
  25. #include "../Scene/Node.h"
  26. #include "../Urho2D/Sprite2D.h"
  27. #include "../Urho2D/StretchableSprite2D.h"
  28. #include "../DebugNew.h"
  29. namespace Urho3D
  30. {
  31. namespace
  32. {
  33. void checkBorder(int border, float drawSize)
  34. {
  35. /* not clamping yet, as drawSize may still change and come to accommodate large borders */
  36. if (border < 0 || border * PIXEL_SIZE > drawSize)
  37. URHO3D_LOGWARNINGF("Border out of bounds (%d), may be clamped", border);
  38. }
  39. Rect calcEffectiveBorder(const IntRect& border, const Vector2& drawSize)
  40. {
  41. Vector2 min{Clamp(border.left_ * PIXEL_SIZE, 0.0f, drawSize.x_),
  42. Clamp(border.bottom_ * PIXEL_SIZE, 0.0f, drawSize.y_)};
  43. return Rect{min, {Clamp(border.right_ * PIXEL_SIZE, 0.0f, drawSize.x_ - min.x_),
  44. Clamp(border.top_ * PIXEL_SIZE, 0.0f, drawSize.y_ - min.y_)} /* max*/ };
  45. }
  46. void prepareXYCoords(float coords[4], float low, float high, float lowBorder, float highBorder, float scale)
  47. {
  48. coords[0] = low * scale;
  49. coords[3] = high * scale;
  50. auto scaleSign = Sign(scale);
  51. auto borderSize = lowBorder + highBorder;
  52. if (borderSize > scaleSign * (coords[3] - coords[0]))
  53. {
  54. auto size = high - low;
  55. coords[1] = coords[2] = scale * (low + (lowBorder * size / borderSize));
  56. }
  57. else
  58. {
  59. auto absScale = Abs(scale);
  60. coords[1] = (low * absScale + lowBorder) * scaleSign;
  61. coords[2] = (high * absScale - highBorder) * scaleSign;
  62. }
  63. }
  64. void prepareUVCoords(float coords[4], float low, float high, float lowBorder, float highBorder, float drawSize)
  65. {
  66. coords[0] = low;
  67. coords[1] = low + lowBorder / drawSize;
  68. coords[2] = high - highBorder / drawSize;
  69. coords[3] = high;
  70. }
  71. void prepareVertices(Vertex2D vtx[4][4], const float xs[4], const float ys[4], const float us[4], const float vs[4], unsigned color,
  72. const Vector3& position, const Quaternion& rotation)
  73. {
  74. for (unsigned i = 0; i < 4; ++i)
  75. {
  76. for (unsigned j = 0; j < 4; ++j)
  77. {
  78. vtx[i][j].position_ = position + rotation * Vector3{xs[i], ys[j], 0.0f};
  79. vtx[i][j].color_ = color;
  80. vtx[i][j].uv_ = Vector2{us[i], vs[j]};
  81. }
  82. }
  83. }
  84. void pushVertices(Vector<Vertex2D>& target, const Vertex2D source[4][4])
  85. {
  86. for (unsigned i = 0; i < 3; ++i) // iterate over 3 columns
  87. {
  88. if (!Equals(source[i][0].position_.x_,
  89. source[i + 1][0].position_.x_)) // if width != 0
  90. {
  91. for (unsigned j = 0; j < 3; ++j) // iterate over 3 lines
  92. {
  93. if (!Equals(source[0][j].position_.y_,
  94. source[0][j + 1].position_.y_)) // if height != 0
  95. {
  96. target.Push(source[i][j]); // V0 in V1---V2
  97. target.Push(source[i][j + 1]); // V1 in | / |
  98. target.Push(source[i + 1][j + 1]); // V2 in | / |
  99. target.Push(source[i + 1][j]); // V3 in V0---V3
  100. }
  101. }
  102. }
  103. }
  104. }
  105. } // namespace
  106. extern const char* URHO2D_CATEGORY;
  107. StretchableSprite2D::StretchableSprite2D(Context* context) :
  108. StaticSprite2D{context}
  109. {
  110. }
  111. void StretchableSprite2D::RegisterObject(Context* context)
  112. {
  113. context->RegisterFactory<StretchableSprite2D>(URHO2D_CATEGORY);
  114. URHO3D_COPY_BASE_ATTRIBUTES(StaticSprite2D);
  115. URHO3D_ACCESSOR_ATTRIBUTE("Border", GetBorder, SetBorder, IntRect, IntRect::ZERO, AM_DEFAULT);
  116. }
  117. void StretchableSprite2D::SetBorder(const IntRect& border)
  118. {
  119. border_ = border;
  120. auto drawSize = drawRect_.Size();
  121. checkBorder(border_.left_, drawSize.x_);
  122. checkBorder(border_.right_, drawSize.x_);
  123. checkBorder(border_.left_ + border_.right_, drawSize.x_);
  124. checkBorder(border_.bottom_, drawSize.y_);
  125. checkBorder(border_.top_, drawSize.y_);
  126. checkBorder(border_.bottom_ + border_.top_, drawSize.y_);
  127. }
  128. void StretchableSprite2D::UpdateSourceBatches()
  129. {
  130. /* The general idea is to subdivide the image in the following 9 patches
  131. *---*---*---*
  132. 2 | | | |
  133. *---*---*---*
  134. 1 | | | |
  135. *---*---*---*
  136. 0 | | | |
  137. *---*---*---*
  138. 0 1 2
  139. X scaling works as follow: as long as the scale determines that the total
  140. width is larger than the sum of the widths of columns 0 and 2, only
  141. column 1 is scaled. Otherwise, column 1 is removed and columns 0 and 2 are
  142. scaled, maintaining their relative size. The same principle applies for
  143. Y scaling (but with lines rather than columns). */
  144. if (!sourceBatchesDirty_ || !sprite_ || (!useTextureRect_ && !sprite_->GetTextureRectangle(textureRect_, flipX_, flipY_)))
  145. return;
  146. Vector<Vertex2D>& vertices = sourceBatches_[0].vertices_;
  147. vertices.Clear();
  148. auto effectiveBorder = calcEffectiveBorder(border_, drawRect_.Size());
  149. float xs[4], ys[4], us[4], vs[4]; // prepare all coordinates
  150. const auto signedScale = node_->GetSignedWorldScale();
  151. prepareXYCoords(xs, drawRect_.min_.x_, drawRect_.max_.x_, effectiveBorder.min_.x_, effectiveBorder.max_.x_, signedScale.x_);
  152. prepareXYCoords(ys, drawRect_.min_.y_, drawRect_.max_.y_, effectiveBorder.min_.y_, effectiveBorder.max_.y_, signedScale.y_);
  153. prepareUVCoords(us, textureRect_.min_.x_, textureRect_.max_.x_, effectiveBorder.min_.x_, effectiveBorder.max_.x_,
  154. drawRect_.max_.x_ - drawRect_.min_.x_);
  155. prepareUVCoords(vs, textureRect_.min_.y_, textureRect_.max_.y_, -effectiveBorder.min_.y_,
  156. -effectiveBorder.max_.y_ /* texture y direction inverted*/, drawRect_.max_.y_ - drawRect_.min_.y_);
  157. Vertex2D vtx[4][4]; // prepare all vertices
  158. prepareVertices(vtx, xs, ys, us, vs, color_.ToUInt(), node_->GetWorldPosition(), node_->GetWorldRotation());
  159. pushVertices(vertices, vtx); // push the vertices that make up each patch
  160. sourceBatchesDirty_ = false;
  161. }
  162. }