UiCanvasOnMeshComponent.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  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 "UiCanvasOnMeshComponent.h"
  9. #include <AzCore/Component/TransformBus.h>
  10. #include <AzCore/Serialization/SerializeContext.h>
  11. #include <AzCore/Serialization/EditContext.h>
  12. #include <AzCore/Asset/AssetSerializer.h>
  13. #include <AzCore/Math/IntersectPoint.h>
  14. #include <AzCore/Math/IntersectSegment.h>
  15. #include <AzCore/Name/NameDictionary.h>
  16. #include <AzCore/Component/NonUniformScaleBus.h>
  17. #include <LyShine/Bus/UiCanvasBus.h>
  18. #include <LyShine/Bus/World/UiCanvasRefBus.h>
  19. #include <LyShine/UiSerializeHelpers.h>
  20. #include <Cry_Geo.h>
  21. #include <IIndexedMesh.h>
  22. #include <AzFramework/Render/GeometryIntersectionStructures.h>
  23. #include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
  24. ////////////////////////////////////////////////////////////////////////////////////////////////////
  25. // Anonymous namespace
  26. ////////////////////////////////////////////////////////////////////////////////////////////////////
  27. namespace
  28. {
  29. ////////////////////////////////////////////////////////////////////////////////////////////////////
  30. AZ::Vector2 ConvertBarycentricCoordsToUVCoords(float u, float v, float w, AZ::Vector2 uv0, AZ::Vector2 uv1, AZ::Vector2 uv2)
  31. {
  32. float arrVertWeight[3] = { max(0.f, u), max(0.f, v), max(0.f, w) };
  33. float fDiv = 1.f / (arrVertWeight[0] + arrVertWeight[1] + arrVertWeight[2]);
  34. arrVertWeight[0] *= fDiv;
  35. arrVertWeight[1] *= fDiv;
  36. arrVertWeight[2] *= fDiv;
  37. AZ::Vector2 uvResult = uv0 * arrVertWeight[0] + uv1 * arrVertWeight[1] + uv2 * arrVertWeight[2];
  38. return uvResult;
  39. }
  40. } // Anonymous namespace
  41. ////////////////////////////////////////////////////////////////////////////////////////////////////
  42. // PUBLIC MEMBER FUNCTIONS
  43. ////////////////////////////////////////////////////////////////////////////////////////////////////
  44. ////////////////////////////////////////////////////////////////////////////////////////////////////
  45. UiCanvasOnMeshComponent::UiCanvasOnMeshComponent()
  46. {}
  47. ////////////////////////////////////////////////////////////////////////////////////////////////////
  48. bool UiCanvasOnMeshComponent::ProcessHitInputEvent(
  49. const AzFramework::InputChannel::Snapshot& inputSnapshot,
  50. const AzFramework::RenderGeometry::RayRequest& rayRequest)
  51. {
  52. AZ::EntityId canvasEntityId = GetCanvas();
  53. if (canvasEntityId.IsValid())
  54. {
  55. // Cache bus pointer as it will be used twice
  56. UiCanvasBus::BusPtr uiCanvasInterfacePtr;
  57. UiCanvasBus::Bind(uiCanvasInterfacePtr, canvasEntityId);
  58. if (!uiCanvasInterfacePtr)
  59. {
  60. return false;
  61. }
  62. // Calculate UV texture coordinates of the intersected geometry
  63. AZ::Vector2 uv(0.0f);
  64. if (CalculateUVFromRayIntersection(rayRequest, uv))
  65. {
  66. AZ::Vector2 canvasSize;
  67. UiCanvasBus::EventResult(canvasSize, uiCanvasInterfacePtr, &UiCanvasInterface::GetCanvasSize);
  68. AZ::Vector2 canvasPoint = AZ::Vector2(uv.GetX() * canvasSize.GetX(), uv.GetY() * canvasSize.GetY());
  69. bool handledByCanvas = false;
  70. UiCanvasBus::EventResult(handledByCanvas, uiCanvasInterfacePtr,
  71. &UiCanvasInterface::HandleInputPositionalEvent, inputSnapshot, canvasPoint);
  72. if (handledByCanvas)
  73. {
  74. return true;
  75. }
  76. }
  77. }
  78. return false;
  79. }
  80. ////////////////////////////////////////////////////////////////////////////////////////////////////
  81. void UiCanvasOnMeshComponent::OnCanvasLoadedIntoEntity(AZ::EntityId uiCanvasEntity)
  82. {
  83. if (uiCanvasEntity.IsValid() && m_attachmentImageAssetOverride)
  84. {
  85. UiCanvasBus::Event(uiCanvasEntity, &UiCanvasInterface::SetAttachmentImageAsset, m_attachmentImageAssetOverride);
  86. }
  87. }
  88. ////////////////////////////////////////////////////////////////////////////////////////////////////
  89. void UiCanvasOnMeshComponent::OnCanvasReloaded(AZ::EntityId canvasEntityId)
  90. {
  91. if (canvasEntityId == GetCanvas())
  92. {
  93. // The canvas that we are using has been reloaded, we may need to override the render target
  94. OnCanvasLoadedIntoEntity(canvasEntityId);
  95. }
  96. }
  97. ////////////////////////////////////////////////////////////////////////////////////////////////////
  98. // PUBLIC STATIC MEMBER FUNCTIONS
  99. ////////////////////////////////////////////////////////////////////////////////////////////////////
  100. ////////////////////////////////////////////////////////////////////////////////////////////////////
  101. void UiCanvasOnMeshComponent::Reflect(AZ::ReflectContext* context)
  102. {
  103. AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
  104. if (serializeContext)
  105. {
  106. serializeContext->Class<UiCanvasOnMeshComponent, AZ::Component>()
  107. ->Version(2, &VersionConverter)
  108. ->Field("AttachmentImageAssetOverride", &UiCanvasOnMeshComponent::m_attachmentImageAssetOverride);
  109. AZ::EditContext* editContext = serializeContext->GetEditContext();
  110. if (editContext)
  111. {
  112. auto editInfo = editContext->Class<UiCanvasOnMeshComponent>(
  113. "UI Canvas on Mesh", "The UI Canvas on Mesh component allows you to place a UI Canvas on an entity in the 3D world that a player can interact with via ray casts");
  114. editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  115. ->Attribute(AZ::Edit::Attributes::Category, "UI")
  116. ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/UiCanvasOnMesh.svg")
  117. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/UiCanvasOnMesh.svg")
  118. ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/ui/canvas-on-mesh/")
  119. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c));
  120. editInfo->DataElement(0, &UiCanvasOnMeshComponent::m_attachmentImageAssetOverride,
  121. "Render target override",
  122. "If not empty, this asset overrides the render target set on the UI canvas.\n"
  123. "This is useful if multiple instances of the same UI canvas are rendered in the level.");
  124. }
  125. }
  126. }
  127. ////////////////////////////////////////////////////////////////////////////////////////////////////
  128. // PROTECTED MEMBER FUNCTIONS
  129. ////////////////////////////////////////////////////////////////////////////////////////////////////
  130. ////////////////////////////////////////////////////////////////////////////////////////////////////
  131. void UiCanvasOnMeshComponent::Activate()
  132. {
  133. UiCanvasOnMeshBus::Handler::BusConnect(GetEntityId());
  134. UiCanvasAssetRefNotificationBus::Handler::BusConnect(GetEntityId());
  135. UiCanvasManagerNotificationBus::Handler::BusConnect();
  136. // Check if a UI canvas has already been loaded into the entity
  137. AZ::EntityId canvasEntityId;
  138. UiCanvasRefBus::EventResult(
  139. canvasEntityId, GetEntityId(), &UiCanvasRefBus::Events::GetCanvas);
  140. if (canvasEntityId.IsValid())
  141. {
  142. OnCanvasLoadedIntoEntity(canvasEntityId);
  143. }
  144. }
  145. ////////////////////////////////////////////////////////////////////////////////////////////////////
  146. void UiCanvasOnMeshComponent::Deactivate()
  147. {
  148. UiCanvasAssetRefNotificationBus::Handler::BusDisconnect();
  149. UiCanvasOnMeshBus::Handler::BusDisconnect();
  150. UiCanvasManagerNotificationBus::Handler::BusDisconnect();
  151. }
  152. ////////////////////////////////////////////////////////////////////////////////////////////////////
  153. bool UiCanvasOnMeshComponent::CalculateUVFromRayIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, AZ::Vector2& outUv)
  154. {
  155. outUv = AZ::Vector2(0.0f);
  156. // Make sure we can get the model asset
  157. AZ::Data::Asset<AZ::RPI::ModelAsset> modelAsset;
  158. AZ::Render::MeshComponentRequestBus::EventResult(
  159. modelAsset, GetEntityId(), &AZ::Render::MeshComponentRequestBus::Events::GetModelAsset);
  160. AZ::RPI::ModelAsset* asset = modelAsset.Get();
  161. if (!asset)
  162. {
  163. return false;
  164. }
  165. // Calculate the nearest point of collision
  166. AZ::Transform meshWorldTM;
  167. AZ::TransformBus::EventResult(meshWorldTM, GetEntityId(), &AZ::TransformInterface::GetWorldTM);
  168. AZ::Transform meshWorldTMInverse = meshWorldTM.GetInverse();
  169. AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne();
  170. AZ::NonUniformScaleRequestBus::EventResult(nonUniformScale, GetEntityId(), &AZ::NonUniformScaleRequests::GetScale);
  171. const AZ::Vector3 clampedNonUniformScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale));
  172. AZ::Vector3 rayOrigin = meshWorldTMInverse.TransformPoint(rayRequest.m_startWorldPosition) / clampedNonUniformScale;
  173. AZ::Vector3 rayEnd = meshWorldTMInverse.TransformPoint(rayRequest.m_endWorldPosition) / clampedNonUniformScale;
  174. AZ::Vector3 rayDirection = rayEnd - rayOrigin;
  175. // When a segment intersects a triangle, the returned hit distance will be between [0, 1].
  176. // Initialize min hit distance to be greater than 1 so that the first hit will be the new min
  177. float minResultDistance = 2.0f;
  178. bool foundResult = false;
  179. auto lods = modelAsset->GetLodAssets();
  180. if (lods.empty())
  181. {
  182. return false;
  183. }
  184. auto meshes = lods[0]->GetMeshes();
  185. for (const AZ::RPI::ModelLodAsset::Mesh& mesh : meshes)
  186. {
  187. // Find position and UV semantics
  188. static const AZ::Name positionName = AZ::Name::FromStringLiteral("POSITION", AZ::Interface<AZ::NameDictionary>::Get());
  189. static const AZ::Name uvName = AZ::Name::FromStringLiteral("UV", AZ::Interface<AZ::NameDictionary>::Get());
  190. auto streamBufferList = mesh.GetStreamBufferInfoList();
  191. const AZ::RPI::ModelLodAsset::Mesh::StreamBufferInfo* positionBuffer = nullptr;
  192. const AZ::RPI::ModelLodAsset::Mesh::StreamBufferInfo* uvBuffer = nullptr;
  193. for (const AZ::RPI::ModelLodAsset::Mesh::StreamBufferInfo& bufferInfo : streamBufferList)
  194. {
  195. if (bufferInfo.m_semantic.m_name == positionName)
  196. {
  197. positionBuffer = &bufferInfo;
  198. }
  199. else if ((bufferInfo.m_semantic.m_name == uvName) && (bufferInfo.m_semantic.m_index == 0))
  200. {
  201. uvBuffer = &bufferInfo;
  202. }
  203. }
  204. if (!positionBuffer || !uvBuffer)
  205. {
  206. continue;
  207. }
  208. auto positionBufferAsset = positionBuffer->m_bufferAssetView.GetBufferAsset();
  209. const float* rawPositionBuffer = (const float*)(positionBufferAsset->GetBuffer().begin());
  210. AZ_Assert(
  211. positionBuffer->m_bufferAssetView.GetBufferViewDescriptor().m_elementFormat == AZ::RHI::Format::R32G32B32_FLOAT,
  212. "Unexpected position element format.");
  213. auto uvBufferAsset = uvBuffer->m_bufferAssetView.GetBufferAsset();
  214. const float* rawUvBuffer = (const float*)(uvBufferAsset->GetBuffer().begin());
  215. AZ_Assert(
  216. uvBuffer->m_bufferAssetView.GetBufferViewDescriptor().m_elementFormat == AZ::RHI::Format::R32G32_FLOAT,
  217. "Unexpected UV element format.");
  218. auto indexBuffer = mesh.GetIndexBufferAssetView().GetBufferAsset();
  219. const uint32_t* rawIndexBuffer = (const uint32_t*)(indexBuffer->GetBuffer().begin());
  220. AZ_Assert(
  221. (indexBuffer->GetBufferViewDescriptor().m_elementCount % 3) == 0,
  222. "index buffer not a multiple of 3");
  223. AZ::Intersect::SegmentTriangleHitTester hitTester(rayOrigin, rayEnd);
  224. for (uint32_t index = 0; index < indexBuffer->GetBufferViewDescriptor().m_elementCount; index += 3)
  225. {
  226. uint32_t index1 = rawIndexBuffer[index];
  227. uint32_t index2 = rawIndexBuffer[index + 1];
  228. uint32_t index3 = rawIndexBuffer[index + 2];
  229. AZ::Vector3 vertex1(
  230. rawPositionBuffer[index1 * 3], rawPositionBuffer[(index1 * 3) + 1], rawPositionBuffer[(index1 * 3) + 2]);
  231. AZ::Vector3 vertex2(
  232. rawPositionBuffer[index2 * 3], rawPositionBuffer[(index2 * 3) + 1], rawPositionBuffer[(index2 * 3) + 2]);
  233. AZ::Vector3 vertex3(
  234. rawPositionBuffer[index3 * 3], rawPositionBuffer[(index3 * 3) + 1], rawPositionBuffer[(index3 * 3) + 2]);
  235. AZ::Vector3 resultNormal;
  236. float resultDistance = 0.0f;
  237. if (hitTester.IntersectSegmentTriangle(vertex1, vertex2, vertex3, resultNormal, resultDistance))
  238. {
  239. if (resultDistance < minResultDistance)
  240. {
  241. AZ::Vector3 hitPosition = rayOrigin + (rayDirection * resultDistance);
  242. AZ::Vector3 uvw = AZ::Intersect::Barycentric(vertex1, vertex2, vertex3, hitPosition);
  243. if (uvw.IsGreaterEqualThan(AZ::Vector3::CreateZero()))
  244. {
  245. AZ::Vector3 uv1(rawUvBuffer[index1 * 2], rawUvBuffer[(index1 * 2) + 1], 0.0f);
  246. AZ::Vector3 uv2(rawUvBuffer[index2 * 2], rawUvBuffer[(index2 * 2) + 1], 0.0f);
  247. AZ::Vector3 uv3(rawUvBuffer[index3 * 2], rawUvBuffer[(index3 * 2) + 1], 0.0f);
  248. outUv = ConvertBarycentricCoordsToUVCoords(
  249. uvw.GetX(), uvw.GetY(), uvw.GetZ(), AZ::Vector2(uv1), AZ::Vector2(uv2), AZ::Vector2(uv3));
  250. minResultDistance = resultDistance;
  251. foundResult = true;
  252. }
  253. }
  254. }
  255. }
  256. }
  257. return foundResult;
  258. }
  259. ////////////////////////////////////////////////////////////////////////////////////////////////////
  260. AZ::EntityId UiCanvasOnMeshComponent::GetCanvas()
  261. {
  262. AZ::EntityId result;
  263. UiCanvasRefBus::EventResult(result, GetEntityId(), &UiCanvasRefBus::Events::GetCanvas);
  264. return result;
  265. }
  266. ////////////////////////////////////////////////////////////////////////////////////////////////////
  267. bool UiCanvasOnMeshComponent::VersionConverter(AZ::SerializeContext& context,
  268. AZ::SerializeContext::DataElementNode& classElement)
  269. {
  270. // conversion from version 1 to 2:
  271. // - Need to remove render target name as it was replaced with attachment image asset
  272. if (classElement.GetVersion() < 2)
  273. {
  274. if (!LyShine::RemoveRenderTargetAsString(context, classElement, "RenderTargetOverride"))
  275. {
  276. return false;
  277. }
  278. }
  279. return true;
  280. }