UiCanvasOnMeshComponent.cpp 14 KB

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