ClipmapBounds.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <TerrainRenderer/ClipmapBounds.h>
  9. #include <AzCore/Casting/numeric_cast.h>
  10. #include <AzCore/Math/MathUtils.h>
  11. namespace Terrain
  12. {
  13. bool ClipmapBoundsRegion::operator==(const ClipmapBoundsRegion& other) const
  14. {
  15. return m_localAabb == other.m_localAabb && m_worldAabb.IsClose(other.m_worldAabb);
  16. }
  17. bool ClipmapBoundsRegion::operator!=(const ClipmapBoundsRegion& other) const
  18. {
  19. return !(*this == other);
  20. }
  21. ClipmapBounds::ClipmapBounds(const ClipmapBoundsDescriptor& desc)
  22. : m_size(desc.m_size)
  23. , m_halfSize(desc.m_size >> 1)
  24. , m_clipmapUpdateMultiple(AZ::GetMax<uint32_t>(desc.m_clipmapUpdateMultiple, 1))
  25. , m_clipmapToWorldScale(desc.m_clipmapToWorldScale)
  26. , m_worldToClipmapScale(1.0f / desc.m_clipmapToWorldScale)
  27. {
  28. AZ_Assert(m_clipmapToWorldScale > AZ::Constants::FloatEpsilon, "ClipmapBounds should have a scale that is greater than 0.0f.");
  29. // recalculate m_center
  30. m_center = GetSnappedCenter(GetClipSpaceVector(desc.m_worldSpaceCenter));
  31. m_modCenter.m_x = (m_size + (m_center.m_x % m_size)) % m_size;
  32. m_modCenter.m_y = (m_size + (m_center.m_y % m_size)) % m_size;
  33. }
  34. auto ClipmapBounds::UpdateCenter(const AZ::Vector2& newCenter, AZ::Aabb* untouchedRegion) -> ClipmapBoundsRegionList
  35. {
  36. return UpdateCenter(GetClipSpaceVector(newCenter), untouchedRegion);
  37. }
  38. auto ClipmapBounds::UpdateCenter(const Vector2i& newCenter, AZ::Aabb* untouchedRegion) -> ClipmapBoundsRegionList
  39. {
  40. AZStd::vector<Aabb2i> updateRegions;
  41. // If the new snapped center isn't the same as the old, then generate update regions in clipmap space
  42. Vector2i updatedCenter = GetSnappedCenter(newCenter);
  43. int32_t xDiff = updatedCenter.m_x - m_center.m_x;
  44. int32_t updateWidth = AZStd::GetMin<uint32_t>(abs(xDiff), m_size);
  45. /*
  46. Calculate the update regions. In the common case, there will be two update regions that form either
  47. an L or inverted L shape. To avoid double-counting the corner, it is always put in the vertical box:
  48. _
  49. | |
  50. | |____
  51. |_|____|
  52. */
  53. // Calculate the vertical box
  54. if (updatedCenter.m_x != m_center.m_x)
  55. {
  56. Aabb2i& updateRegion = updateRegions.emplace_back();
  57. if (updatedCenter.m_x < m_center.m_x)
  58. {
  59. updateRegion.m_min.m_x = updatedCenter.m_x - m_halfSize;
  60. updateRegion.m_max.m_x = updateRegion.m_min.m_x + updateWidth;
  61. }
  62. else
  63. {
  64. updateRegion.m_max.m_x = updatedCenter.m_x + m_halfSize;
  65. updateRegion.m_min.m_x = updateRegion.m_max.m_x - updateWidth;
  66. }
  67. updateRegion.m_min.m_y = updatedCenter.m_y - m_halfSize;
  68. updateRegion.m_max.m_y = updatedCenter.m_y + m_halfSize;
  69. }
  70. // Calculate the horizontal box
  71. if (updatedCenter.m_y != m_center.m_y && updateWidth < m_size)
  72. {
  73. Aabb2i& updateRegion = updateRegions.emplace_back();
  74. uint32_t updateHeight = AZStd::GetMin<uint32_t>(abs(updatedCenter.m_y - m_center.m_y), m_size);
  75. if (updatedCenter.m_y < m_center.m_y)
  76. {
  77. updateRegion.m_min.m_y = updatedCenter.m_y - m_halfSize;
  78. updateRegion.m_max.m_y = updateRegion.m_min.m_y + updateHeight;
  79. }
  80. else
  81. {
  82. updateRegion.m_max.m_y = updatedCenter.m_y + m_halfSize;
  83. updateRegion.m_min.m_y = updateRegion.m_max.m_y - updateHeight;
  84. }
  85. // If there was a vertical box, then don't double-count the corner of the update.
  86. if (xDiff < 0)
  87. {
  88. updateRegion.m_min.m_x = updatedCenter.m_x - m_halfSize + updateWidth;
  89. updateRegion.m_max.m_x = updatedCenter.m_x + m_halfSize;
  90. }
  91. else if (xDiff > 0)
  92. {
  93. updateRegion.m_min.m_x = updatedCenter.m_x - m_halfSize;
  94. updateRegion.m_max.m_x = updatedCenter.m_x + m_halfSize - updateWidth;
  95. }
  96. }
  97. if (untouchedRegion)
  98. {
  99. // Default to the entire area being untouched.
  100. AZ::Aabb worldBounds = GetWorldBounds();
  101. float maxX = worldBounds.GetMax().GetX();
  102. float minX = worldBounds.GetMin().GetX();
  103. float maxY = worldBounds.GetMax().GetY();
  104. float minY = worldBounds.GetMin().GetY();
  105. if (updatedCenter.m_x < m_center.m_x)
  106. {
  107. maxX = (updatedCenter.m_x + m_halfSize) * m_worldToClipmapScale;
  108. }
  109. else if (updatedCenter.m_x > m_center.m_x)
  110. {
  111. minX = (updatedCenter.m_x - m_halfSize) * m_worldToClipmapScale;
  112. }
  113. if (updatedCenter.m_y < m_center.m_y)
  114. {
  115. maxY = (updatedCenter.m_y + m_halfSize) * m_worldToClipmapScale;
  116. }
  117. else if (updatedCenter.m_y > m_center.m_y)
  118. {
  119. minY = (updatedCenter.m_y - m_halfSize) * m_worldToClipmapScale;
  120. }
  121. if (minX > maxX || minY > maxY)
  122. {
  123. // The center has moved so far, there is no untouched region.
  124. untouchedRegion->SetNull();
  125. }
  126. else
  127. {
  128. untouchedRegion->Set(AZ::Vector3(minX, minY, 0.0f), AZ::Vector3(maxX, maxY, 0.0f));
  129. }
  130. }
  131. m_center = updatedCenter;
  132. m_modCenter.m_x = (m_size + (m_center.m_x % m_size)) % m_size;
  133. m_modCenter.m_y = (m_size + (m_center.m_y % m_size)) % m_size;
  134. ClipmapBoundsRegionList boundsUpdate;
  135. for (Aabb2i& updateRegion : updateRegions)
  136. {
  137. ClipmapBoundsRegionList update = TransformRegion(updateRegion);
  138. boundsUpdate.insert(boundsUpdate.end(), update.begin(), update.end());
  139. }
  140. return boundsUpdate;
  141. }
  142. auto ClipmapBounds::TransformRegion(AZ::Aabb worldSpaceRegion) -> ClipmapBoundsRegionList
  143. {
  144. AZ::Vector2 worldMin = AZ::Vector2(worldSpaceRegion.GetMin());
  145. AZ::Vector2 worldMax = AZ::Vector2(worldSpaceRegion.GetMax());
  146. return TransformRegion(worldMin, worldMax);
  147. }
  148. auto ClipmapBounds::TransformRegion(const AZ::Vector2& worldSpaceMin, const AZ::Vector2& worldSpaceMax) ->ClipmapBoundsRegionList
  149. {
  150. Aabb2i clipSpaceRegion;
  151. clipSpaceRegion.m_min = GetClipSpaceVector(worldSpaceMin, RoundMode::Floor);
  152. clipSpaceRegion.m_max = GetClipSpaceVector(worldSpaceMax, RoundMode::Ceil);
  153. return TransformRegion(clipSpaceRegion);
  154. }
  155. auto ClipmapBounds::TransformRegion(Aabb2i region) -> ClipmapBoundsRegionList
  156. {
  157. ClipmapBoundsRegionList transformedRegions;
  158. Aabb2i clampedRegion = region.GetClamped(GetLocalBounds());
  159. if (!clampedRegion.IsValid())
  160. {
  161. // Early out if the region is outside the bounds
  162. return transformedRegions;
  163. }
  164. Vector2i minCorner = m_center - m_halfSize;
  165. Vector2i minBoundary;
  166. minBoundary.m_x = (minCorner.m_x / m_size - (minCorner.m_x < 0 ? 1 : 0)) * m_size;
  167. minBoundary.m_y = (minCorner.m_y / m_size - (minCorner.m_y < 0 ? 1 : 0)) * m_size;
  168. Aabb2i bottomLeftTile = Aabb2i(minBoundary, minBoundary + m_size);
  169. // For each of the 4 quadrants:
  170. auto calculateQuadrant = [&](Aabb2i tile)
  171. {
  172. Aabb2i regionClampedToTile = clampedRegion.GetClamped(tile);
  173. if (regionClampedToTile.IsValid())
  174. {
  175. transformedRegions.push_back(
  176. ClipmapBoundsRegion({
  177. GetWorldSpaceAabb(regionClampedToTile),
  178. regionClampedToTile - tile.m_min
  179. })
  180. );
  181. }
  182. };
  183. calculateQuadrant(bottomLeftTile);
  184. calculateQuadrant(bottomLeftTile + Vector2i(m_size, 0));
  185. calculateQuadrant(bottomLeftTile + Vector2i(0, m_size));
  186. calculateQuadrant(bottomLeftTile + Vector2i(m_size, m_size));
  187. return transformedRegions;
  188. }
  189. AZ::Aabb ClipmapBounds::GetWorldBounds() const
  190. {
  191. Aabb2i localBounds = GetLocalBounds();
  192. return AZ::Aabb::CreateFromMinMaxValues(
  193. localBounds.m_min.m_x * m_clipmapToWorldScale, localBounds.m_min.m_y * m_clipmapToWorldScale, 0.0f,
  194. localBounds.m_max.m_x * m_clipmapToWorldScale, localBounds.m_max.m_y * m_clipmapToWorldScale, 0.0f);
  195. }
  196. float ClipmapBounds::GetWorldSpaceSafeDistance() const
  197. {
  198. return (m_halfSize - m_clipmapUpdateMultiple) * m_clipmapToWorldScale;
  199. }
  200. Vector2i ClipmapBounds::GetSnappedCenter(const Vector2i& center)
  201. {
  202. Vector2i updatedCenter = m_center;
  203. // Update the snapped center if the new center has drifted beyond the margin
  204. auto UpdateDim = [&](int32_t centerDim, int32_t& snappedCenterDim) -> void
  205. {
  206. int32_t diff = centerDim - snappedCenterDim;
  207. int32_t scaledCenterDim = (centerDim / m_clipmapUpdateMultiple);
  208. if (centerDim < 0)
  209. {
  210. // Force rounding down for negatives
  211. scaledCenterDim--;
  212. }
  213. if (diff >= m_clipmapUpdateMultiple)
  214. {
  215. snappedCenterDim = scaledCenterDim * m_clipmapUpdateMultiple;
  216. }
  217. if (diff < -m_clipmapUpdateMultiple)
  218. {
  219. snappedCenterDim = (scaledCenterDim + 1) * m_clipmapUpdateMultiple;
  220. }
  221. };
  222. UpdateDim(center.m_x, updatedCenter.m_x);
  223. UpdateDim(center.m_y, updatedCenter.m_y);
  224. return updatedCenter;
  225. }
  226. Aabb2i ClipmapBounds::GetLocalBounds() const
  227. {
  228. return Aabb2i(m_center - m_halfSize, m_center + m_halfSize);
  229. }
  230. Vector2i ClipmapBounds::GetClipSpaceVector(const AZ::Vector2& worldSpaceVector, RoundMode roundMode) const
  231. {
  232. // Get rounded integer x/y coords in clipmap space.
  233. AZ::Vector2 clipSpaceCoord = worldSpaceVector * m_worldToClipmapScale;
  234. Vector2i returnValue;
  235. switch (roundMode)
  236. {
  237. case RoundMode::Average:
  238. returnValue = Vector2i(
  239. aznumeric_cast<int32_t>(AZStd::lround(clipSpaceCoord.GetX())),
  240. aznumeric_cast<int32_t>(AZStd::lround(clipSpaceCoord.GetY()))
  241. );
  242. break;
  243. case RoundMode::Floor:
  244. returnValue = Vector2i(
  245. aznumeric_cast<int32_t>(AZStd::floorf(clipSpaceCoord.GetX())),
  246. aznumeric_cast<int32_t>(AZStd::floorf(clipSpaceCoord.GetY()))
  247. );
  248. break;
  249. case RoundMode::Ceil:
  250. returnValue = Vector2i(
  251. aznumeric_cast<int32_t>(AZStd::ceilf(clipSpaceCoord.GetX())),
  252. aznumeric_cast<int32_t>(AZStd::ceilf(clipSpaceCoord.GetY()))
  253. );
  254. break;
  255. }
  256. return returnValue;
  257. }
  258. AZ::Aabb ClipmapBounds::GetWorldSpaceAabb(const Aabb2i& clipSpaceAabb) const
  259. {
  260. return AZ::Aabb::CreateFromMinMaxValues(
  261. clipSpaceAabb.m_min.m_x * m_clipmapToWorldScale, clipSpaceAabb.m_min.m_y * m_clipmapToWorldScale, 0.0f,
  262. clipSpaceAabb.m_max.m_x * m_clipmapToWorldScale, clipSpaceAabb.m_max.m_y * m_clipmapToWorldScale, 0.0f
  263. );
  264. }
  265. Vector2i ClipmapBounds::GetCenterInClipmapSpace() const
  266. {
  267. return m_center;
  268. }
  269. AZ::Vector2 ClipmapBounds::GetCenterInWorldSpace() const
  270. {
  271. AZ::Vector2 worldCenter;
  272. worldCenter.SetX(m_center.m_x * m_clipmapToWorldScale);
  273. worldCenter.SetY(m_center.m_y * m_clipmapToWorldScale);
  274. return worldCenter;
  275. }
  276. Vector2i ClipmapBounds::GetModCenter() const
  277. {
  278. return m_modCenter;
  279. }
  280. }