UiCanvasOnMeshComponent.cpp 17 KB

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