3
0

UiImageSequenceComponent.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  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 "UiImageSequenceComponent.h"
  9. #include "Sprite.h"
  10. #include "RenderGraph.h"
  11. #include <LyShine/IDraw2d.h>
  12. #include <LyShine/ISprite.h>
  13. #include <LyShine/IRenderGraph.h>
  14. #include <LyShine/Bus/UiElementBus.h>
  15. #include <LyShine/Bus/UiEditorChangeNotificationBus.h>
  16. #include <LyShine/Bus/UiCanvasBus.h>
  17. #include <LyShine/Bus/Sprite/UiSpriteBus.h>
  18. #include <AzFramework/StringFunc/StringFunc.h>
  19. #include <AzCore/Component/Entity.h>
  20. #include <AzCore/RTTI/BehaviorContext.h>
  21. namespace
  22. {
  23. //! Set the values for an image vertex
  24. //! This helper function is used so that we only have to initialize textIndex and texHasColorChannel in one place
  25. void SetVertex(LyShine::UiPrimitiveVertex& vert, const Vec2& pos, uint32 color, const Vec2& uv)
  26. {
  27. vert.xy = pos;
  28. vert.color.dcolor = color;
  29. vert.st = uv;
  30. vert.texIndex = 0;
  31. vert.texHasColorChannel = 1;
  32. vert.texIndex2 = 0;
  33. vert.pad = 0;
  34. }
  35. //! Set the values for an image vertex
  36. //! This version of the helper function takes AZ vectors
  37. void SetVertex(LyShine::UiPrimitiveVertex& vert, const AZ::Vector2& pos, uint32 color, const AZ::Vector2& uv)
  38. {
  39. SetVertex(vert, Vec2(pos.GetX(), pos.GetY()), color, Vec2(uv.GetX(), uv.GetY()));
  40. }
  41. //! \brief Loads assets from disk and populates the sprite list with loaded sprites.
  42. void PopulateSpriteListFromImageList(UiImageSequenceComponent::SpriteList& spriteList, UiImageSequenceComponent::ImageList& imageList)
  43. {
  44. AZStd::unordered_set<AZStd::string> invalidTextures;
  45. spriteList.clear();
  46. spriteList.reserve(imageList.size());
  47. for (auto& textureAssetRef : imageList)
  48. {
  49. ISprite* sprite = AZ::Interface<ILyShine>::Get()->LoadSprite(textureAssetRef.GetAssetPath().c_str());
  50. if (sprite)
  51. {
  52. spriteList.push_back(sprite);
  53. }
  54. else
  55. {
  56. invalidTextures.insert(textureAssetRef.GetAssetPath());
  57. }
  58. }
  59. auto newEndIter = AZStd::remove_if(imageList.begin(), imageList.end(),
  60. [&invalidTextures](const UiImageSequenceComponent::TextureAssetRef& assetRef)
  61. {
  62. return invalidTextures.find(assetRef.GetAssetPath()) != invalidTextures.end();
  63. });
  64. imageList.erase(newEndIter, imageList.end());
  65. }
  66. }
  67. ////////////////////////////////////////////////////////////////////////////////////////////////////
  68. UiImageSequenceComponent::UiImageSequenceComponent()
  69. {
  70. }
  71. ////////////////////////////////////////////////////////////////////////////////////////////////////
  72. UiImageSequenceComponent::~UiImageSequenceComponent()
  73. {
  74. for (ISprite* sprite : m_spriteList)
  75. {
  76. SAFE_RELEASE(sprite);
  77. }
  78. ClearCachedVertices();
  79. ClearCachedIndices();
  80. }
  81. ////////////////////////////////////////////////////////////////////////////////////////////////////
  82. void UiImageSequenceComponent::Render(LyShine::IRenderGraph* renderGraph)
  83. {
  84. if (m_spriteList.empty())
  85. {
  86. return;
  87. }
  88. ISprite* sprite = m_spriteList[m_sequenceIndex];
  89. // get fade value (tracked by UiRenderer) and compute the desired alpha for the image
  90. float fade = renderGraph->GetAlphaFade();
  91. uint8 desiredPackedAlpha = static_cast<uint8>(fade * 255.0f);
  92. if (m_isRenderCacheDirty)
  93. {
  94. uint32 packedColor = 0xffffffff;
  95. switch (m_imageType)
  96. {
  97. case ImageType::Stretched:
  98. RenderStretchedSprite(sprite, 0, packedColor);
  99. break;
  100. case ImageType::Fixed:
  101. AZ_Assert(sprite, "Should not get here if no sprite path is specified");
  102. RenderFixedSprite(sprite, 0, packedColor);
  103. break;
  104. case ImageType::StretchedToFit:
  105. AZ_Assert(sprite, "Should not get here if no sprite path is specified");
  106. RenderStretchedToFitOrFillSprite(sprite, 0, packedColor, true);
  107. break;
  108. case ImageType::StretchedToFill:
  109. AZ_Assert(sprite, "Should not get here if no sprite path is specified");
  110. RenderStretchedToFitOrFillSprite(sprite, 0, packedColor, false);
  111. break;
  112. }
  113. if (!UiCanvasPixelAlignmentNotificationBus::Handler::BusIsConnected())
  114. {
  115. AZ::EntityId canvasEntityId;
  116. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  117. UiCanvasPixelAlignmentNotificationBus::Handler::BusConnect(canvasEntityId);
  118. }
  119. }
  120. // if desired alpha is zero then no need to do any more
  121. if (desiredPackedAlpha == 0)
  122. {
  123. return;
  124. }
  125. if (m_cachedPrimitive.m_numVertices > 0)
  126. {
  127. // If the fade value has changed we need to update the alpha values in the vertex colors but we do
  128. // not want to touch or recompute the RGB values
  129. if (m_cachedPrimitive.m_vertices[0].color.a != desiredPackedAlpha)
  130. {
  131. // go through all the cached vertices and update the alpha values
  132. LyShine::UCol desiredPackedColor = m_cachedPrimitive.m_vertices[0].color;
  133. desiredPackedColor.a = desiredPackedAlpha;
  134. for (int i = 0; i < m_cachedPrimitive.m_numVertices; ++i)
  135. {
  136. m_cachedPrimitive.m_vertices[i].color = desiredPackedColor;
  137. }
  138. }
  139. AZ::Data::Instance<AZ::RPI::Image> image;
  140. if (sprite)
  141. {
  142. image = sprite->GetImage();
  143. }
  144. bool isClampTextureMode = false;
  145. bool isTextureSRGB = false;
  146. bool isTexturePremultipliedAlpha = false;
  147. LyShine::BlendMode blendMode = LyShine::BlendMode::Normal;
  148. // Add the quad to the render graph
  149. renderGraph->AddPrimitive(&m_cachedPrimitive, image,
  150. isClampTextureMode, isTextureSRGB, isTexturePremultipliedAlpha, blendMode);
  151. }
  152. }
  153. ////////////////////////////////////////////////////////////////////////////////////////////////////
  154. UiImageSequenceComponent::ImageType UiImageSequenceComponent::GetImageType()
  155. {
  156. return m_imageType;
  157. }
  158. ////////////////////////////////////////////////////////////////////////////////////////////////////
  159. void UiImageSequenceComponent::SetImageType(ImageType imageType)
  160. {
  161. if (m_imageType != imageType)
  162. {
  163. m_imageType = imageType;
  164. MarkRenderCacheDirty();
  165. }
  166. }
  167. ////////////////////////////////////////////////////////////////////////////////////////////////////
  168. void UiImageSequenceComponent::Reflect(AZ::ReflectContext* context)
  169. {
  170. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  171. if (serializeContext)
  172. {
  173. serializeContext->Class<UiImageSequenceComponent, AZ::Component>()
  174. ->Version(0, &VersionConverter)
  175. ->Field("ImageType", &UiImageSequenceComponent::m_imageType)
  176. ->Field("ImageList", &UiImageSequenceComponent::m_imageList)
  177. ->Field("ImageSequenceDirectory", &UiImageSequenceComponent::m_imageSequenceDirectory)
  178. ->Field("Index", &UiImageSequenceComponent::m_sequenceIndex);
  179. AZ::EditContext* ec = serializeContext->GetEditContext();
  180. if (ec)
  181. {
  182. auto editInfo = ec->Class<UiImageSequenceComponent>("ImageSequence", "A visual component that displays one of multiple images in a sequence.");
  183. // :TODO: update the icon for image sequence
  184. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  185. ->Attribute(AZ::Edit::Attributes::Category, "UI")
  186. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiImage.png")
  187. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiImage.png")
  188. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0))
  189. ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
  190. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiImageSequenceComponent::m_imageType, "ImageType", "The image type. Affects how the texture/sprite is mapped to the image rectangle.")
  191. ->EnumAttribute(UiImageSequenceInterface::ImageType::Stretched, "Stretched")
  192. ->EnumAttribute(UiImageSequenceInterface::ImageType::Fixed, "Fixed")
  193. ->EnumAttribute(UiImageSequenceInterface::ImageType::StretchedToFit, "Stretched To Fit")
  194. ->EnumAttribute(UiImageSequenceInterface::ImageType::StretchedToFill, "Stretched To Fill")
  195. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiImageSequenceComponent::OnImageTypeChange);
  196. editInfo->DataElement("Directory", &UiImageSequenceComponent::m_imageSequenceDirectory, "Sequence Directory", "A directory containing images of the sequence.")
  197. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiImageSequenceComponent::OnImageSequenceDirectoryChange)
  198. ->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ_CRC("RefreshEntireTree", 0xefbc823c));
  199. editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiImageSequenceComponent::m_sequenceIndex, "Sequence Index", "Image index to display.")
  200. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiImageSequenceComponent::OnImageSequenceIndexChange)
  201. ->Attribute("EnumValues", &UiImageSequenceComponent::PopulateIndexStringList);
  202. }
  203. }
  204. AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
  205. if (behaviorContext)
  206. {
  207. behaviorContext->Enum<(int)UiImageSequenceInterface::ImageType::Stretched>("eUiImageSequenceImageType_Stretched")
  208. ->Enum<(int)UiImageSequenceInterface::ImageType::Fixed>("eUiImageSequenceImageType_Fixed")
  209. ->Enum<(int)UiImageSequenceInterface::ImageType::StretchedToFit>("eUiImageSequenceImageType_StretchedToFit")
  210. ->Enum<(int)UiImageSequenceInterface::ImageType::StretchedToFill>("eUiImageSequenceImageType_StretchedToFill");
  211. behaviorContext->EBus<UiImageSequenceBus>("UiImageSequenceBus")
  212. ->Event("GetImageType", &UiImageSequenceBus::Events::GetImageType)
  213. ->Event("SetImageType", &UiImageSequenceBus::Events::SetImageType);
  214. behaviorContext->Class<UiImageSequenceComponent>()->RequestBus("UiImageSequenceBus");
  215. }
  216. }
  217. ////////////////////////////////////////////////////////////////////////////////////////////////////
  218. void UiImageSequenceComponent::SetImageIndex(AZ::u32 index)
  219. {
  220. if (index < m_spriteList.size())
  221. {
  222. m_sequenceIndex = index;
  223. MarkRenderCacheDirty();
  224. }
  225. }
  226. ////////////////////////////////////////////////////////////////////////////////////////////////////
  227. const AZ::u32 UiImageSequenceComponent::GetImageIndex()
  228. {
  229. return m_sequenceIndex;
  230. }
  231. ////////////////////////////////////////////////////////////////////////////////////////////////////
  232. const AZ::u32 UiImageSequenceComponent::GetImageIndexCount()
  233. {
  234. return static_cast<AZ::u32>(m_spriteList.size());
  235. }
  236. ////////////////////////////////////////////////////////////////////////////////////////////////////
  237. AZStd::string UiImageSequenceComponent::GetImageIndexAlias([[maybe_unused]] AZ::u32 index)
  238. {
  239. return AZStd::string();
  240. }
  241. ////////////////////////////////////////////////////////////////////////////////////////////////////
  242. void UiImageSequenceComponent::SetImageIndexAlias([[maybe_unused]] AZ::u32 index, [[maybe_unused]] const AZStd::string& alias)
  243. {
  244. // Purposefully empty
  245. }
  246. ////////////////////////////////////////////////////////////////////////////////////////////////////
  247. AZ::u32 UiImageSequenceComponent::GetImageIndexFromAlias([[maybe_unused]] const AZStd::string& alias)
  248. {
  249. return 0;
  250. }
  251. ////////////////////////////////////////////////////////////////////////////////////////////////////
  252. void UiImageSequenceComponent::OnCanvasSpaceRectChanged(AZ::EntityId /*entityId*/, const UiTransformInterface::Rect& /*oldRect*/, const UiTransformInterface::Rect& /*newRect*/)
  253. {
  254. MarkRenderCacheDirty();
  255. }
  256. ////////////////////////////////////////////////////////////////////////////////////////////////////
  257. void UiImageSequenceComponent::OnTransformToViewportChanged()
  258. {
  259. MarkRenderCacheDirty();
  260. }
  261. ////////////////////////////////////////////////////////////////////////////////////////////////////
  262. void UiImageSequenceComponent::OnCanvasPixelAlignmentChange()
  263. {
  264. MarkRenderCacheDirty();
  265. }
  266. ////////////////////////////////////////////////////////////////////////////////////////////////////
  267. LyShine::AZu32ComboBoxVec UiImageSequenceComponent::PopulateIndexStringList()
  268. {
  269. const AZ::u32 indexCount = GetImageIndexCount();
  270. return LyShine::GetEnumSpriteIndexList(GetEntityId(), 0, indexCount - 1);
  271. }
  272. ////////////////////////////////////////////////////////////////////////////////////////////////////
  273. void UiImageSequenceComponent::OnImageTypeChange()
  274. {
  275. MarkRenderCacheDirty();
  276. }
  277. ////////////////////////////////////////////////////////////////////////////////////////////////////
  278. void UiImageSequenceComponent::OnImageSequenceDirectoryChange()
  279. {
  280. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  281. if (fileIO)
  282. {
  283. // Add all files in the directory - we'll try to load them all as sprites and toss
  284. // out the ones that don't later.
  285. AZStd::set<AZStd::string> spriteFilepaths;
  286. fileIO->FindFiles(m_imageSequenceDirectory.c_str(), "*", [&spriteFilepaths](const char* pFilename) -> bool
  287. {
  288. spriteFilepaths.insert(pFilename);
  289. return true;
  290. });
  291. // Take all non *.sprite files and look for *.sprite counterparts; if they exist,
  292. // then keep the *.sprite counterpart but removed the other image from the
  293. // list (to prevent loading duplicate images).
  294. for (auto iter = spriteFilepaths.begin(); iter != spriteFilepaths.end(); )
  295. {
  296. AZStd::string filepathCopy = *iter;
  297. AZStd::string fileExtension;
  298. const bool hasExtension = AzFramework::StringFunc::Path::GetExtension(filepathCopy.c_str(), fileExtension);
  299. const bool checkForSpriteFile = hasExtension ? fileExtension != ".sprite" : false;
  300. if (checkForSpriteFile)
  301. {
  302. AzFramework::StringFunc::Path::ReplaceExtension(filepathCopy, "sprite");
  303. const bool spriteFileExists = spriteFilepaths.find(filepathCopy) != spriteFilepaths.end();
  304. if (spriteFileExists)
  305. {
  306. iter = spriteFilepaths.erase(iter);
  307. continue;
  308. }
  309. }
  310. ++iter;
  311. }
  312. // Build list of TextureAssetRefs from list of paths that contain *.sprite
  313. // files (for those images that have them)
  314. m_imageList.clear();
  315. for (const auto& spriteFilepath : spriteFilepaths)
  316. {
  317. TextureAssetRef textureAsset;
  318. textureAsset.SetAssetPath(spriteFilepath.c_str());
  319. m_imageList.push_back(textureAsset);
  320. }
  321. // Finally, load the sprites in the sequence and notify listeners accordingly
  322. PopulateSpriteListFromImageList(m_spriteList, m_imageList);
  323. m_sequenceIndex = 0;
  324. MarkRenderCacheDirty();
  325. UiSpriteSourceNotificationBus::Event(GetEntityId(), &UiSpriteSourceNotificationBus::Events::OnSpriteSourceChanged);
  326. UiEditorChangeNotificationBus::Broadcast(&UiEditorChangeNotificationBus::Events::OnEditorPropertiesRefreshEntireTree);
  327. }
  328. }
  329. ////////////////////////////////////////////////////////////////////////////////////////////////////
  330. void UiImageSequenceComponent::OnImageSequenceIndexChange()
  331. {
  332. MarkRenderCacheDirty();
  333. }
  334. ////////////////////////////////////////////////////////////////////////////////////////////////////
  335. void UiImageSequenceComponent::Init()
  336. {
  337. // If this is called from RC.exe for example these pointers will not be set. In that case
  338. // we only need to be able to load, init and save the component. It will never be
  339. // activated.
  340. if (!AZ::Interface<ILyShine>::Get())
  341. {
  342. return;
  343. }
  344. PopulateSpriteListFromImageList(m_spriteList, m_imageList);
  345. }
  346. ////////////////////////////////////////////////////////////////////////////////////////////////////
  347. void UiImageSequenceComponent::Activate()
  348. {
  349. UiIndexableImageBus::Handler::BusConnect(m_entity->GetId());
  350. UiEditorRefreshDirectoryNotificationBus::Handler::BusConnect();
  351. UiRenderBus::Handler::BusConnect(m_entity->GetId());
  352. UiTransformChangeNotificationBus::Handler::BusConnect(m_entity->GetId());
  353. MarkRenderCacheDirty();
  354. }
  355. ////////////////////////////////////////////////////////////////////////////////////////////////////
  356. void UiImageSequenceComponent::Deactivate()
  357. {
  358. UiIndexableImageBus::Handler::BusDisconnect();
  359. UiEditorRefreshDirectoryNotificationBus::Handler::BusDisconnect();
  360. UiRenderBus::Handler::BusDisconnect();
  361. UiTransformChangeNotificationBus::Handler::BusDisconnect();
  362. if (UiCanvasPixelAlignmentNotificationBus::Handler::BusIsConnected())
  363. {
  364. UiCanvasPixelAlignmentNotificationBus::Handler::BusDisconnect();
  365. }
  366. // reduce memory use on deactivate
  367. ClearCachedVertices();
  368. ClearCachedIndices();
  369. }
  370. ////////////////////////////////////////////////////////////////////////////////////////////////////
  371. void UiImageSequenceComponent::RenderStretchedSprite(ISprite* sprite, int cellIndex, uint32 packedColor)
  372. {
  373. UiTransformInterface::RectPoints points;
  374. UiTransformBus::Event(GetEntityId(), &UiTransformBus::Events::GetViewportSpacePoints, points);
  375. if (sprite)
  376. {
  377. const UiTransformInterface::RectPoints& uvCoords = sprite->GetCellUvCoords(cellIndex);
  378. const AZ::Vector2 uvs[4] =
  379. {
  380. uvCoords.TopLeft(),
  381. uvCoords.TopRight(),
  382. uvCoords.BottomRight(),
  383. uvCoords.BottomLeft(),
  384. };
  385. RenderSingleQuad(points.pt, uvs, packedColor);
  386. }
  387. else
  388. {
  389. // points are a clockwise quad
  390. static const AZ::Vector2 uvs[4] = { AZ::Vector2(0, 0), AZ::Vector2(1, 0), AZ::Vector2(1, 1), AZ::Vector2(0, 1) };
  391. RenderSingleQuad(points.pt, uvs, packedColor);
  392. }
  393. }
  394. ////////////////////////////////////////////////////////////////////////////////////////////////////
  395. void UiImageSequenceComponent::RenderFixedSprite(ISprite* sprite, int cellIndex, uint32 packedColor)
  396. {
  397. AZ::Vector2 textureSize(sprite->GetCellSize(cellIndex));
  398. UiTransformInterface::RectPoints points;
  399. UiTransformBus::Event(GetEntityId(), &UiTransformBus::Events::GetCanvasSpacePointsNoScaleRotate, points);
  400. AZ::Vector2 pivot;
  401. UiTransformBus::EventResult(pivot, GetEntityId(), &UiTransformBus::Events::GetPivot);
  402. // change width and height to match texture
  403. AZ::Vector2 rectSize = points.GetAxisAlignedSize();
  404. AZ::Vector2 sizeDiff = textureSize - rectSize;
  405. AZ::Vector2 topLeftOffset(sizeDiff.GetX() * pivot.GetX(), sizeDiff.GetY() * pivot.GetY());
  406. AZ::Vector2 bottomRightOffset(sizeDiff.GetX() * (1.0f - pivot.GetX()), sizeDiff.GetY() * (1.0f - pivot.GetY()));
  407. points.TopLeft() -= topLeftOffset;
  408. points.BottomRight() += bottomRightOffset;
  409. points.TopRight() = AZ::Vector2(points.BottomRight().GetX(), points.TopLeft().GetY());
  410. points.BottomLeft() = AZ::Vector2(points.TopLeft().GetX(), points.BottomRight().GetY());
  411. // now apply scale and rotation
  412. UiTransformBus::Event(GetEntityId(), &UiTransformBus::Events::RotateAndScalePoints, points);
  413. // now draw the same as Stretched
  414. const UiTransformInterface::RectPoints& uvCoords = sprite->GetCellUvCoords(cellIndex);
  415. const AZ::Vector2 uvs[4] =
  416. {
  417. uvCoords.TopLeft(),
  418. uvCoords.TopRight(),
  419. uvCoords.BottomRight(),
  420. uvCoords.BottomLeft(),
  421. };
  422. RenderSingleQuad(points.pt, uvs, packedColor);
  423. }
  424. ////////////////////////////////////////////////////////////////////////////////////////////////////
  425. void UiImageSequenceComponent::RenderStretchedToFitOrFillSprite(ISprite* sprite, int cellIndex, uint32 packedColor, bool toFit)
  426. {
  427. AZ::Vector2 textureSize = sprite->GetCellSize(cellIndex);
  428. UiTransformInterface::RectPoints points;
  429. UiTransformBus::Event(GetEntityId(), &UiTransformBus::Events::GetCanvasSpacePointsNoScaleRotate, points);
  430. AZ::Vector2 pivot;
  431. UiTransformBus::EventResult(pivot, GetEntityId(), &UiTransformBus::Events::GetPivot);
  432. // scale the texture so it either fits or fills the enclosing rect
  433. AZ::Vector2 rectSize = points.GetAxisAlignedSize();
  434. const float scaleFactorX = rectSize.GetX() / textureSize.GetX();
  435. const float scaleFactorY = rectSize.GetY() / textureSize.GetY();
  436. const float scaleFactor = toFit ?
  437. AZ::GetMin(scaleFactorX, scaleFactorY) :
  438. AZ::GetMax(scaleFactorX, scaleFactorY);
  439. AZ::Vector2 scaledTextureSize = textureSize * scaleFactor;
  440. AZ::Vector2 sizeDiff = scaledTextureSize - rectSize;
  441. AZ::Vector2 topLeftOffset(sizeDiff.GetX() * pivot.GetX(), sizeDiff.GetY() * pivot.GetY());
  442. AZ::Vector2 bottomRightOffset(sizeDiff.GetX() * (1.0f - pivot.GetX()), sizeDiff.GetY() * (1.0f - pivot.GetY()));
  443. points.TopLeft() -= topLeftOffset;
  444. points.BottomRight() += bottomRightOffset;
  445. points.TopRight() = AZ::Vector2(points.BottomRight().GetX(), points.TopLeft().GetY());
  446. points.BottomLeft() = AZ::Vector2(points.TopLeft().GetX(), points.BottomRight().GetY());
  447. // now apply scale and rotation
  448. UiTransformBus::Event(GetEntityId(), &UiTransformBus::Events::RotateAndScalePoints, points);
  449. // now draw the same as Stretched
  450. const UiTransformInterface::RectPoints& uvCoords = sprite->GetCellUvCoords(cellIndex);
  451. const AZ::Vector2 uvs[4] =
  452. {
  453. uvCoords.TopLeft(),
  454. uvCoords.TopRight(),
  455. uvCoords.BottomRight(),
  456. uvCoords.BottomLeft(),
  457. };
  458. RenderSingleQuad(points.pt, uvs, packedColor);
  459. }
  460. ////////////////////////////////////////////////////////////////////////////////////////////////////
  461. void UiImageSequenceComponent::RenderSingleQuad(const AZ::Vector2* positions, const AZ::Vector2* uvs, uint32 packedColor)
  462. {
  463. // points are a clockwise quad
  464. IDraw2d::Rounding pixelRounding = IsPixelAligned() ? IDraw2d::Rounding::Nearest : IDraw2d::Rounding::None;
  465. const uint32 numVertices = 4;
  466. LyShine::UiPrimitiveVertex vertices[numVertices];
  467. for (int i = 0; i < numVertices; ++i)
  468. {
  469. AZ::Vector2 roundedPoint = Draw2dHelper::RoundXY(positions[i], pixelRounding);
  470. SetVertex(vertices[i], roundedPoint, packedColor, uvs[i]);
  471. }
  472. const uint32 numIndices = 6;
  473. uint16 indices[numIndices] = { 0, 1, 2, 2, 3, 0 };
  474. RenderTriangleList(vertices, indices, numVertices, numIndices);
  475. }
  476. ////////////////////////////////////////////////////////////////////////////////////////////////////
  477. void UiImageSequenceComponent::RenderTriangleList(const LyShine::UiPrimitiveVertex* vertices, const uint16* indices, int numVertices, int numIndices)
  478. {
  479. if (numVertices != m_cachedPrimitive.m_numVertices)
  480. {
  481. ClearCachedVertices();
  482. m_cachedPrimitive.m_vertices = new LyShine::UiPrimitiveVertex[numVertices];
  483. m_cachedPrimitive.m_numVertices = numVertices;
  484. }
  485. if (numIndices != m_cachedPrimitive.m_numIndices)
  486. {
  487. ClearCachedIndices();
  488. m_cachedPrimitive.m_indices = new uint16[numIndices];
  489. m_cachedPrimitive.m_numIndices = numIndices;
  490. }
  491. memcpy(m_cachedPrimitive.m_vertices, vertices, sizeof(LyShine::UiPrimitiveVertex) * numVertices);
  492. memcpy(m_cachedPrimitive.m_indices, indices, sizeof(uint16) * numIndices);
  493. m_isRenderCacheDirty = false;
  494. }
  495. ////////////////////////////////////////////////////////////////////////////////////////////////////
  496. void UiImageSequenceComponent::ClearCachedVertices()
  497. {
  498. if (m_cachedPrimitive.m_vertices)
  499. {
  500. delete[] m_cachedPrimitive.m_vertices;
  501. }
  502. m_cachedPrimitive.m_vertices = nullptr;
  503. m_cachedPrimitive.m_numVertices = 0;
  504. }
  505. ////////////////////////////////////////////////////////////////////////////////////////////////////
  506. void UiImageSequenceComponent::ClearCachedIndices()
  507. {
  508. if (m_cachedPrimitive.m_indices)
  509. {
  510. delete[] m_cachedPrimitive.m_indices;
  511. }
  512. m_cachedPrimitive.m_indices = nullptr;
  513. m_cachedPrimitive.m_numIndices = 0;
  514. }
  515. ////////////////////////////////////////////////////////////////////////////////////////////////////
  516. void UiImageSequenceComponent::MarkRenderCacheDirty()
  517. {
  518. m_isRenderCacheDirty = true;
  519. // tell the canvas to invalidate the render graph (never want to do this while rendering)
  520. AZ::EntityId canvasEntityId;
  521. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  522. UiCanvasComponentImplementationBus::Event(canvasEntityId, &UiCanvasComponentImplementationBus::Events::MarkRenderGraphDirty);
  523. }
  524. ////////////////////////////////////////////////////////////////////////////////////////////////////
  525. bool UiImageSequenceComponent::IsPixelAligned()
  526. {
  527. AZ::EntityId canvasEntityId;
  528. UiElementBus::EventResult(canvasEntityId, GetEntityId(), &UiElementBus::Events::GetCanvasEntityId);
  529. bool isPixelAligned = true;
  530. UiCanvasBus::EventResult(isPixelAligned, canvasEntityId, &UiCanvasBus::Events::GetIsPixelAligned);
  531. return isPixelAligned;
  532. }
  533. ////////////////////////////////////////////////////////////////////////////////////////////////////
  534. bool UiImageSequenceComponent::VersionConverter([[maybe_unused]] AZ::SerializeContext& context,
  535. [[maybe_unused]] AZ::SerializeContext::DataElementNode& classElement)
  536. {
  537. return true;
  538. }