3
0

EditorWhiteBoxComponent.cpp 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  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 "Asset/EditorWhiteBoxMeshAsset.h"
  9. #include "Asset/WhiteBoxMeshAssetHandler.h"
  10. #include "EditorWhiteBoxComponent.h"
  11. #include "EditorWhiteBoxComponentMode.h"
  12. #include "EditorWhiteBoxComponentModeBus.h"
  13. #include "Rendering/WhiteBoxNullRenderMesh.h"
  14. #include "Rendering/WhiteBoxRenderMeshInterface.h"
  15. #include "Util/WhiteBoxEditorUtil.h"
  16. #include "WhiteBoxComponent.h"
  17. #include <AzCore/Asset/AssetSerializer.h>
  18. #include <AzCore/Component/TransformBus.h>
  19. #include <AzCore/Console/Console.h>
  20. #include <AzCore/Math/IntersectSegment.h>
  21. #include <AzCore/Memory/Memory.h>
  22. #include <AzCore/Serialization/EditContext.h>
  23. #include <AzCore/Serialization/SerializeContext.h>
  24. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  25. #include <AzCore/std/numeric.h>
  26. #include <AzFramework/StringFunc/StringFunc.h>
  27. #include <AzQtComponents/Components/Widgets/FileDialog.h>
  28. #include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
  29. #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
  30. #include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
  31. #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
  32. #include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
  33. #include <AzToolsFramework/Maths/TransformUtils.h>
  34. #include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
  35. #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
  36. #include <QMessageBox>
  37. #include <WhiteBox/EditorWhiteBoxColliderBus.h>
  38. #include <WhiteBox/WhiteBoxBus.h>
  39. // developer debug properties for the White Box mesh to globally enable/disable
  40. AZ_CVAR(bool, cl_whiteBoxDebugVertexHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display vertex handles");
  41. AZ_CVAR(bool, cl_whiteBoxDebugNormals, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display normals");
  42. AZ_CVAR(
  43. bool, cl_whiteBoxDebugHalfedgeHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display halfedge handles");
  44. AZ_CVAR(bool, cl_whiteBoxDebugEdgeHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display edge handles");
  45. AZ_CVAR(bool, cl_whiteBoxDebugFaceHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display face handles");
  46. AZ_CVAR(bool, cl_whiteBoxDebugAabb, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display Aabb for the White Box");
  47. namespace WhiteBox
  48. {
  49. static const char* const AssetSavedUndoRedoDesc = "White Box Mesh asset saved";
  50. static const char* const ObjExtension = "obj";
  51. static void RefreshProperties()
  52. {
  53. AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
  54. &AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
  55. AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
  56. }
  57. // build intermediate data to be passed to WhiteBoxRenderMeshInterface
  58. // to be used to generate concrete render mesh
  59. static WhiteBoxRenderData CreateWhiteBoxRenderData(const WhiteBoxMesh& whiteBox, const WhiteBoxMaterial& material)
  60. {
  61. AZ_PROFILE_FUNCTION(AzToolsFramework);
  62. WhiteBoxRenderData renderData;
  63. WhiteBoxFaces& faceData = renderData.m_faces;
  64. const auto faceCount = Api::MeshFaceCount(whiteBox);
  65. faceData.reserve(faceCount);
  66. const auto createWhiteBoxFaceFromHandle = [&whiteBox](const Api::FaceHandle& faceHandle) -> WhiteBoxFace
  67. {
  68. const auto copyVertex = [&whiteBox](const Api::HalfedgeHandle& in, WhiteBoxVertex& out)
  69. {
  70. const auto vh = Api::HalfedgeVertexHandleAtTip(whiteBox, in);
  71. out.m_position = Api::VertexPosition(whiteBox, vh);
  72. out.m_uv = Api::HalfedgeUV(whiteBox, in);
  73. };
  74. WhiteBoxFace face;
  75. face.m_normal = Api::FaceNormal(whiteBox, faceHandle);
  76. const auto faceHalfedgeHandles = Api::FaceHalfedgeHandles(whiteBox, faceHandle);
  77. copyVertex(faceHalfedgeHandles[0], face.m_v1);
  78. copyVertex(faceHalfedgeHandles[1], face.m_v2);
  79. copyVertex(faceHalfedgeHandles[2], face.m_v3);
  80. return face;
  81. };
  82. const auto faceHandles = Api::MeshFaceHandles(whiteBox);
  83. for (const auto& faceHandle : faceHandles)
  84. {
  85. faceData.push_back(createWhiteBoxFaceFromHandle(faceHandle));
  86. }
  87. renderData.m_material = material;
  88. return renderData;
  89. }
  90. static bool IsWhiteBoxNullRenderMesh(const AZStd::optional<AZStd::unique_ptr<RenderMeshInterface>>& m_renderMesh)
  91. {
  92. return azrtti_cast<WhiteBoxNullRenderMesh*>((*m_renderMesh).get()) != nullptr;
  93. }
  94. static bool DisplayingAsset(const DefaultShapeType defaultShapeType)
  95. {
  96. // checks if the default shape is set to a custom asset
  97. return defaultShapeType == DefaultShapeType::Asset;
  98. }
  99. // callback for when the default shape field is changed
  100. AZ::Crc32 EditorWhiteBoxComponent::OnDefaultShapeChange()
  101. {
  102. const AZStd::string entityIdStr = AZStd::string::format("%llu", static_cast<AZ::u64>(GetEntityId()));
  103. const AZStd::string componentIdStr = AZStd::string::format("%llu", GetId());
  104. const AZStd::string shapeTypeStr = AZStd::string::format("%d", aznumeric_cast<int>(m_defaultShape));
  105. const AZStd::vector<AZStd::string_view> scriptArgs{entityIdStr, componentIdStr, shapeTypeStr};
  106. // if the shape type has just changed and it is no longer an asset type, check if a mesh asset
  107. // is in use and clear it if so (switch back to using the component serialized White Box mesh)
  108. if (!DisplayingAsset(m_defaultShape) && m_editorMeshAsset->InUse())
  109. {
  110. m_editorMeshAsset->Reset();
  111. }
  112. AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
  113. &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs,
  114. "@gemroot:WhiteBox@/Editor/Scripts/default_shapes.py", scriptArgs);
  115. EditorWhiteBoxComponentNotificationBus::Event(
  116. AZ::EntityComponentIdPair(GetEntityId(), GetId()),
  117. &EditorWhiteBoxComponentNotificationBus::Events::OnDefaultShapeTypeChanged, m_defaultShape);
  118. return AZ::Edit::PropertyRefreshLevels::EntireTree;
  119. }
  120. bool EditorWhiteBoxVersionConverter(
  121. AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
  122. {
  123. if (classElement.GetVersion() <= 1)
  124. {
  125. // find the old WhiteBoxMeshAsset stored directly on the component
  126. AZ::Data::Asset<Pipeline::WhiteBoxMeshAsset> meshAsset;
  127. const int meshAssetIndex = classElement.FindElement(AZ_CRC_CE("MeshAsset"));
  128. if (meshAssetIndex != -1)
  129. {
  130. classElement.GetSubElement(meshAssetIndex).GetData(meshAsset);
  131. classElement.RemoveElement(meshAssetIndex);
  132. }
  133. else
  134. {
  135. return false;
  136. }
  137. // add the new EditorWhiteBoxMeshAsset which will contain the previous WhiteBoxMeshAsset
  138. const int editorMeshAssetIndex =
  139. classElement.AddElement<EditorWhiteBoxMeshAsset>(context, "EditorMeshAsset");
  140. if (editorMeshAssetIndex != -1)
  141. {
  142. // insert the existing WhiteBoxMeshAsset into the new EditorWhiteBoxMeshAsset
  143. classElement.GetSubElement(editorMeshAssetIndex)
  144. .AddElementWithData<AZ::Data::Asset<Pipeline::WhiteBoxMeshAsset>>(context, "MeshAsset", meshAsset);
  145. }
  146. else
  147. {
  148. return false;
  149. }
  150. }
  151. return true;
  152. }
  153. void EditorWhiteBoxComponent::Reflect(AZ::ReflectContext* context)
  154. {
  155. EditorWhiteBoxMeshAsset::Reflect(context);
  156. if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
  157. {
  158. serializeContext->Class<EditorWhiteBoxComponent, EditorComponentBase>()
  159. ->Version(2, &EditorWhiteBoxVersionConverter)
  160. ->Field("WhiteBoxData", &EditorWhiteBoxComponent::m_whiteBoxData)
  161. ->Field("DefaultShape", &EditorWhiteBoxComponent::m_defaultShape)
  162. ->Field("EditorMeshAsset", &EditorWhiteBoxComponent::m_editorMeshAsset)
  163. ->Field("Material", &EditorWhiteBoxComponent::m_material)
  164. ->Field("RenderData", &EditorWhiteBoxComponent::m_renderData)
  165. ->Field("ComponentMode", &EditorWhiteBoxComponent::m_componentModeDelegate)
  166. ->Field("FlipYZForExport", &EditorWhiteBoxComponent::m_flipYZForExport);
  167. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  168. {
  169. editContext->Class<EditorWhiteBoxComponent>("White Box", "White Box level editing")
  170. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  171. ->Attribute(AZ::Edit::Attributes::Category, "Shape")
  172. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/WhiteBox.svg")
  173. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/WhiteBox.svg")
  174. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
  175. ->Attribute(
  176. AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/shape/white-box/")
  177. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  178. ->DataElement(
  179. AZ::Edit::UIHandlers::ComboBox, &EditorWhiteBoxComponent::m_defaultShape, "Default Shape",
  180. "Default shape of the white box mesh.")
  181. ->EnumAttribute(DefaultShapeType::Cube, "Cube")
  182. ->EnumAttribute(DefaultShapeType::Tetrahedron, "Tetrahedron")
  183. ->EnumAttribute(DefaultShapeType::Icosahedron, "Icosahedron")
  184. ->EnumAttribute(DefaultShapeType::Cylinder, "Cylinder")
  185. ->EnumAttribute(DefaultShapeType::Sphere, "Sphere")
  186. ->EnumAttribute(DefaultShapeType::Asset, "Mesh Asset")
  187. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::OnDefaultShapeChange)
  188. ->DataElement(
  189. AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_editorMeshAsset, "Editor Mesh Asset",
  190. "Editor Mesh Asset")
  191. ->Attribute(AZ::Edit::Attributes::Visibility, &EditorWhiteBoxComponent::AssetVisibility)
  192. ->UIElement(AZ::Edit::UIHandlers::Button, "Save as asset", "Save as asset")
  193. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::SaveAsAsset)
  194. ->Attribute(AZ::Edit::Attributes::ButtonText, "Save As ...")
  195. ->DataElement(
  196. AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_material, "White Box Material",
  197. "The properties of the White Box material.")
  198. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::OnMaterialChange)
  199. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  200. ->DataElement(
  201. AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_componentModeDelegate,
  202. "Component Mode", "White Box Tool Component Mode")
  203. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  204. ->UIElement(AZ::Edit::UIHandlers::Button, "", "Export to obj")
  205. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::ExportToFile)
  206. ->Attribute(AZ::Edit::Attributes::ButtonText, "Export")
  207. ->UIElement(AZ::Edit::UIHandlers::Button, "", "Export all whiteboxes on descendant entities as a single obj (excluding this one)")
  208. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::ExportDescendantsToFile)
  209. ->Attribute(AZ::Edit::Attributes::ButtonText, "Export Descendants")
  210. ->DataElement(
  211. AZ::Edit::UIHandlers::Default,
  212. &EditorWhiteBoxComponent::m_flipYZForExport,
  213. "Flip Y and Z for Export",
  214. "Flip the Y and Z axes when exportings so they aren't imported sideways into coord systems where the Y-axis goes up.");
  215. }
  216. }
  217. }
  218. void EditorWhiteBoxComponent::OnMaterialChange()
  219. {
  220. if (m_renderMesh.has_value())
  221. {
  222. (*m_renderMesh)->UpdateMaterial(m_material);
  223. m_renderData.m_material = m_material;
  224. }
  225. }
  226. AZ::Crc32 EditorWhiteBoxComponent::AssetVisibility() const
  227. {
  228. return DisplayingAsset(m_defaultShape) ? AZ::Edit::PropertyVisibility::ShowChildrenOnly
  229. : AZ::Edit::PropertyVisibility::Hide;
  230. }
  231. void EditorWhiteBoxComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  232. {
  233. required.push_back(AZ_CRC_CE("TransformService"));
  234. }
  235. void EditorWhiteBoxComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  236. {
  237. provided.push_back(AZ_CRC_CE("WhiteBoxService"));
  238. }
  239. void EditorWhiteBoxComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  240. {
  241. incompatible.push_back(AZ_CRC_CE("NonUniformScaleService"));
  242. incompatible.push_back(AZ_CRC_CE("MeshService"));
  243. incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
  244. }
  245. EditorWhiteBoxComponent::EditorWhiteBoxComponent() = default;
  246. EditorWhiteBoxComponent::~EditorWhiteBoxComponent()
  247. {
  248. // note: m_editorMeshAsset is (usually) serialized so it is created by the reflection system
  249. // in Reflect (no explicit `new`) - we must still clean-up the resource on destruction though
  250. // to not leak resources.
  251. delete m_editorMeshAsset;
  252. }
  253. void EditorWhiteBoxComponent::Init()
  254. {
  255. if (m_editorMeshAsset)
  256. {
  257. return;
  258. }
  259. // if the m_editorMeshAsset has not been created by the serialization system
  260. // create a new EditorWhiteBoxMeshAsset here
  261. m_editorMeshAsset = aznew EditorWhiteBoxMeshAsset();
  262. }
  263. void EditorWhiteBoxComponent::Activate()
  264. {
  265. const AZ::EntityId entityId = GetEntityId();
  266. const AZ::EntityComponentIdPair entityComponentIdPair{entityId, GetId()};
  267. AzToolsFramework::Components::EditorComponentBase::Activate();
  268. EditorWhiteBoxComponentRequestBus::Handler::BusConnect(entityComponentIdPair);
  269. EditorWhiteBoxComponentNotificationBus::Handler::BusConnect(entityComponentIdPair);
  270. AZ::TransformNotificationBus::Handler::BusConnect(entityId);
  271. AzFramework::BoundsRequestBus::Handler::BusConnect(entityId);
  272. AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(entityId);
  273. AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(entityId);
  274. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusConnect(entityId);
  275. m_componentModeDelegate.ConnectWithSingleComponentMode<EditorWhiteBoxComponent, EditorWhiteBoxComponentMode>(
  276. entityComponentIdPair, this);
  277. m_worldFromLocal = AZ::Transform::CreateIdentity();
  278. AZ::TransformBus::EventResult(m_worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM);
  279. m_editorMeshAsset->Associate(entityComponentIdPair);
  280. // deserialize the white box data into a mesh object or load the serialized asset ref
  281. DeserializeWhiteBox();
  282. if (AzToolsFramework::IsEntityVisible(entityId))
  283. {
  284. ShowRenderMesh();
  285. OnMaterialChange();
  286. }
  287. }
  288. void EditorWhiteBoxComponent::Deactivate()
  289. {
  290. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusDisconnect();
  291. AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusDisconnect();
  292. AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
  293. AzFramework::BoundsRequestBus::Handler::BusDisconnect();
  294. AZ::TransformNotificationBus::Handler::BusDisconnect();
  295. EditorWhiteBoxComponentRequestBus::Handler::BusDisconnect();
  296. EditorWhiteBoxComponentNotificationBus::Handler::BusDisconnect();
  297. AzToolsFramework::Components::EditorComponentBase::Deactivate();
  298. m_componentModeDelegate.Disconnect();
  299. m_editorMeshAsset->Release();
  300. m_renderMesh.reset();
  301. m_whiteBox.reset();
  302. }
  303. void EditorWhiteBoxComponent::DeserializeWhiteBox()
  304. {
  305. // create WhiteBoxMesh object from internal data
  306. m_whiteBox = Api::CreateWhiteBoxMesh();
  307. if (m_editorMeshAsset->InUse())
  308. {
  309. m_editorMeshAsset->Load();
  310. }
  311. else
  312. {
  313. // attempt to load the mesh
  314. const auto result = Api::ReadMesh(*m_whiteBox, m_whiteBoxData);
  315. AZ_Error("EditorWhiteBoxComponent", result != WhiteBox::Api::ReadResult::Error, "Error deserializing white box mesh stream");
  316. // if the read was successful but the byte stream is empty
  317. // (there was nothing to load), create a default mesh
  318. if (result == Api::ReadResult::Empty)
  319. {
  320. Api::InitializeAsUnitCube(*m_whiteBox);
  321. }
  322. }
  323. }
  324. void EditorWhiteBoxComponent::RebuildWhiteBox()
  325. {
  326. RebuildRenderMesh();
  327. RebuildPhysicsMesh();
  328. }
  329. void EditorWhiteBoxComponent::BuildGameEntity(AZ::Entity* gameEntity)
  330. {
  331. if (auto* whiteBoxComponent = gameEntity->CreateComponent<WhiteBoxComponent>())
  332. {
  333. // note: it is important no edit time only functions are called here as BuildGameEntity
  334. // will be called by the Asset Processor when creating dynamic slices
  335. whiteBoxComponent->GenerateWhiteBoxMesh(m_renderData);
  336. }
  337. }
  338. WhiteBoxMesh* EditorWhiteBoxComponent::GetWhiteBoxMesh()
  339. {
  340. if (WhiteBoxMesh* whiteBox = m_editorMeshAsset->GetWhiteBoxMesh())
  341. {
  342. return whiteBox;
  343. }
  344. return m_whiteBox.get();
  345. }
  346. void EditorWhiteBoxComponent::OnWhiteBoxMeshModified()
  347. {
  348. // if using an asset, notify other editor mesh assets using the same id that
  349. // the asset has been modified, this will in turn cause all components to update
  350. // their render and physics meshes
  351. if (m_editorMeshAsset->InUse())
  352. {
  353. WhiteBoxMeshAssetNotificationBus::Event(
  354. m_editorMeshAsset->GetWhiteBoxMeshAssetId(),
  355. &WhiteBoxMeshAssetNotificationBus::Events::OnWhiteBoxMeshAssetModified,
  356. m_editorMeshAsset->GetWhiteBoxMeshAsset());
  357. }
  358. // otherwise, update the render and physics mesh immediately
  359. else
  360. {
  361. RebuildWhiteBox();
  362. }
  363. }
  364. void EditorWhiteBoxComponent::RebuildRenderMesh()
  365. {
  366. AZ_PROFILE_FUNCTION(AzToolsFramework);
  367. // reset caches when the mesh changes
  368. m_worldAabb.reset();
  369. m_localAabb.reset();
  370. m_faces.reset();
  371. AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
  372. // must have been created in Activate or have had the Entity made visible again
  373. if (m_renderMesh.has_value())
  374. {
  375. // cache the white box render data
  376. m_renderData = CreateWhiteBoxRenderData(*GetWhiteBoxMesh(), m_material);
  377. // it's possible the white box mesh data isn't yet ready (for example if it's stored
  378. // in an asset which hasn't finished loading yet) so don't attempt to create a render
  379. // mesh with no data
  380. if (!m_renderData.m_faces.empty())
  381. {
  382. // check if we need to instantiate a concrete render mesh implementation
  383. if (IsWhiteBoxNullRenderMesh(m_renderMesh))
  384. {
  385. // create a concrete implementation of the render mesh
  386. WhiteBoxRequestBus::BroadcastResult(m_renderMesh, &WhiteBoxRequests::CreateRenderMeshInterface, GetEntityId());
  387. }
  388. // generate the mesh
  389. (*m_renderMesh)->BuildMesh(m_renderData, m_worldFromLocal);
  390. OnMaterialChange();
  391. }
  392. }
  393. EditorWhiteBoxComponentModeRequestBus::Event(
  394. AZ::EntityComponentIdPair{GetEntityId(), GetId()},
  395. &EditorWhiteBoxComponentModeRequests::MarkWhiteBoxIntersectionDataDirty);
  396. }
  397. void EditorWhiteBoxComponent::WriteAssetToComponent()
  398. {
  399. if (m_editorMeshAsset->Loaded())
  400. {
  401. Api::WriteMesh(*m_editorMeshAsset->GetWhiteBoxMesh(), m_whiteBoxData);
  402. }
  403. }
  404. void EditorWhiteBoxComponent::SerializeWhiteBox()
  405. {
  406. if (m_editorMeshAsset->Loaded())
  407. {
  408. m_editorMeshAsset->Serialize();
  409. }
  410. else
  411. {
  412. Api::WriteMesh(*m_whiteBox, m_whiteBoxData);
  413. }
  414. }
  415. void EditorWhiteBoxComponent::SetDefaultShape(const DefaultShapeType defaultShape)
  416. {
  417. m_defaultShape = defaultShape;
  418. OnDefaultShapeChange();
  419. }
  420. void EditorWhiteBoxComponent::OnTransformChanged(
  421. [[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world)
  422. {
  423. AZ_PROFILE_FUNCTION(AzToolsFramework);
  424. m_worldAabb.reset();
  425. m_localAabb.reset();
  426. m_worldFromLocal = world;
  427. if (m_renderMesh.has_value())
  428. {
  429. (*m_renderMesh)->UpdateTransform(world);
  430. }
  431. }
  432. void EditorWhiteBoxComponent::RebuildPhysicsMesh()
  433. {
  434. AZ_PROFILE_FUNCTION(AzToolsFramework);
  435. EditorWhiteBoxColliderRequestBus::Event(
  436. GetEntityId(), &EditorWhiteBoxColliderRequests::CreatePhysics, *GetWhiteBoxMesh());
  437. }
  438. static AZStd::string WhiteBoxPathAtProjectRoot(const AZStd::string_view name, const AZStd::string_view extension)
  439. {
  440. AZ::IO::Path whiteBoxPath;
  441. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  442. {
  443. settingsRegistry->Get(whiteBoxPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  444. }
  445. whiteBoxPath /= AZ::IO::FixedMaxPathString::format("%.*s.%.*s", AZ_STRING_ARG(name), AZ_STRING_ARG(extension));
  446. return whiteBoxPath.Native();
  447. }
  448. void EditorWhiteBoxComponent::ExportToFile()
  449. {
  450. const AZStd::string initialAbsolutePathToExport =
  451. WhiteBoxPathAtProjectRoot(GetEntity()->GetName(), ObjExtension);
  452. const QString fileFilter = AZStd::string::format("*.%s", ObjExtension).c_str();
  453. const QString absoluteSaveFilePath = AzQtComponents::FileDialog::GetSaveFileName(
  454. nullptr, "Save As...", QString(initialAbsolutePathToExport.c_str()), fileFilter);
  455. if (m_flipYZForExport)
  456. {
  457. Api::VertexHandles vHandles = Api::MeshVertexHandles(*GetWhiteBoxMesh());
  458. for (auto& handle : vHandles)
  459. {
  460. AZ::Vector3 p = Api::VertexPosition(*GetWhiteBoxMesh(), handle);
  461. float temp = p.GetY();
  462. p.SetY(p.GetZ());
  463. p.SetZ(-temp);
  464. Api::SetVertexPosition(*GetWhiteBoxMesh(), handle, p);
  465. }
  466. }
  467. const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
  468. const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
  469. if (WhiteBox::Api::SaveToObj(*GetWhiteBoxMesh(), absoluteSaveFilePathCstr))
  470. {
  471. AZ_Printf("EditorWhiteBoxComponent", "Exported white box mesh to: %s", absoluteSaveFilePathCstr);
  472. RequestEditSourceControl(absoluteSaveFilePathCstr);
  473. }
  474. else
  475. {
  476. AZ_Warning(
  477. "EditorWhiteBoxComponent", false, "Failed to export white box mesh to: %s", absoluteSaveFilePathCstr);
  478. }
  479. }
  480. void EditorWhiteBoxComponent::ExportDescendantsToFile()
  481. {
  482. // Get all child entities in the viewport
  483. AzToolsFramework::EntityIdList children;
  484. AZ::TransformBus::EventResult(children, GetEntityId(), &AZ::TransformBus::Events::GetAllDescendants);
  485. if (children.empty())
  486. {
  487. AZ_Warning("EditorWhiteBoxComponent", false, "Failed to export descendant whitebox meshes: No descendant entities found.");
  488. return;
  489. }
  490. const AZStd::string initialAbsolutePathToExport = WhiteBoxPathAtProjectRoot(GetEntity()->GetName(), ObjExtension);
  491. const QString fileFilter = AZStd::string::format("*.%s", ObjExtension).c_str();
  492. const QString absoluteSaveFilePath =
  493. AzQtComponents::FileDialog::GetSaveFileName(nullptr, "Save As...", QString(initialAbsolutePathToExport.c_str()), fileFilter);
  494. // Create a new empty white box mesh
  495. Api::WhiteBoxMeshPtr mesh = Api::CreateWhiteBoxMesh();
  496. for (auto& id : children)
  497. {
  498. AZ::Entity* e;
  499. AZ::ComponentApplicationBus::BroadcastResult(e, &AZ::ComponentApplicationRequests::FindEntity, id);
  500. AZ::Transform worldTM = e->GetTransform()->GetWorldTM();
  501. // Add all polys from selected white boxes
  502. for (auto component : e->FindComponents<EditorWhiteBoxComponent>())
  503. {
  504. WhiteBoxMesh* m = component->GetWhiteBoxMesh();
  505. Api::PolygonHandles polys = Api::MeshPolygonHandles(*m);
  506. for (auto& poly : polys)
  507. {
  508. AZStd::vector<AZ::Vector3> verts = Api::PolygonVertexPositions(*m, poly);
  509. if (verts.size() == 4) // if this is in fact a quad
  510. {
  511. Api::VertexHandle vertexHandles[4];
  512. for (unsigned int i = 0; i < 4; i++)
  513. {
  514. AZ::Vector3 worldV = worldTM.TransformPoint(verts[i]);
  515. if (m_flipYZForExport)
  516. {
  517. float temp = worldV.GetY();
  518. worldV.SetY(worldV.GetZ());
  519. worldV.SetZ(-temp);
  520. }
  521. vertexHandles[i] = Api::AddVertex(*mesh.get(), worldV);
  522. }
  523. Api::AddQuadPolygon(*mesh.get(), vertexHandles[0], vertexHandles[1], vertexHandles[2], vertexHandles[3]);
  524. }
  525. }
  526. }
  527. }
  528. Api::CalculateNormals(*mesh.get());
  529. Api::CalculatePlanarUVs(*mesh.get());
  530. const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
  531. const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
  532. if (WhiteBox::Api::SaveToObj(*mesh.get(), absoluteSaveFilePathCstr))
  533. {
  534. AZ_Printf("EditorWhiteBoxComponent", "Exported white box mesh to: %s", absoluteSaveFilePathCstr);
  535. RequestEditSourceControl(absoluteSaveFilePathCstr);
  536. }
  537. else
  538. {
  539. AZ_Warning("EditorWhiteBoxComponent", false, "Failed to export white box mesh to: %s", absoluteSaveFilePathCstr);
  540. }
  541. }
  542. AZStd::optional<WhiteBoxSaveResult> TrySaveAs(
  543. const AZStd::string_view entityName,
  544. const AZStd::function<AZStd::string(const AZStd::string&)>& absoluteSavePathFn,
  545. const AZStd::function<AZStd::optional<AZStd::string>(const AZStd::string&)>& relativePathFn,
  546. const AZStd::function<int()>& saveDecisionFn)
  547. {
  548. const AZStd::string initialAbsolutePathToSave =
  549. WhiteBoxPathAtProjectRoot(entityName, Pipeline::WhiteBoxMeshAssetHandler::AssetFileExtension);
  550. const QString absoluteSaveFilePath = QString(absoluteSavePathFn(initialAbsolutePathToSave).c_str());
  551. // user pressed cancel
  552. if (absoluteSaveFilePath.isEmpty())
  553. {
  554. return AZStd::nullopt;
  555. }
  556. const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
  557. const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
  558. const AZStd::optional<AZStd::string> relativePath =
  559. relativePathFn(AZStd::string(absoluteSaveFilePathCstr, absoluteSaveFilePathUtf8.length()));
  560. if (!relativePath.has_value())
  561. {
  562. int saveDecision = saveDecisionFn();
  563. // save the file but do not attempt to create an asset
  564. if (saveDecision == QMessageBox::Save)
  565. {
  566. return WhiteBoxSaveResult{AZStd::nullopt, AZStd::string(absoluteSaveFilePathCstr)};
  567. }
  568. // the user decided not to save the asset outside the project folder after the prompt
  569. return AZStd::nullopt;
  570. }
  571. return WhiteBoxSaveResult{relativePath, AZStd::string(absoluteSaveFilePathCstr)};
  572. }
  573. AZ::Crc32 EditorWhiteBoxComponent::SaveAsAsset()
  574. {
  575. // let the user select final location of the saved asset
  576. const auto absoluteSavePathFn = [](const AZStd::string& initialAbsolutePath)
  577. {
  578. const QString fileFilter =
  579. AZStd::string::format("WhiteBoxMesh (*.%s)", Pipeline::WhiteBoxMeshAssetHandler::AssetFileExtension).c_str();
  580. const QString absolutePath =
  581. AzQtComponents::FileDialog::GetSaveFileName(nullptr, "Save As Asset...", QString(initialAbsolutePath.c_str()), fileFilter);
  582. return AZStd::string(absolutePath.toUtf8());
  583. };
  584. // ask the asset system to try and convert the absolutePath to a cache relative path
  585. const auto relativePathFn = [](const AZStd::string& absolutePath) -> AZStd::optional<AZStd::string>
  586. {
  587. AZStd::string relativePath;
  588. bool foundRelativePath = false;
  589. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
  590. foundRelativePath,
  591. &AzToolsFramework::AssetSystem::AssetSystemRequest::GetRelativeProductPathFromFullSourceOrProductPath,
  592. absolutePath,
  593. relativePath);
  594. if (foundRelativePath)
  595. {
  596. return relativePath;
  597. }
  598. return AZStd::nullopt;
  599. };
  600. // present the user with the option of accepting saving outside the project folder or allow them to cancel the
  601. // operation
  602. const auto saveDecisionFn = []()
  603. {
  604. return QMessageBox::warning(
  605. AzToolsFramework::GetActiveWindow(),
  606. "Warning",
  607. "Saving a White Box Mesh Asset (.wbm) outside of the project root will not create an Asset for the "
  608. "Component to use. The file will be saved but will not be processed. For live updates to happen the "
  609. "asset must be saved somewhere in the current project folder. Would you like to continue?",
  610. (QMessageBox::Save | QMessageBox::Cancel),
  611. QMessageBox::Cancel);
  612. };
  613. const AZStd::optional<WhiteBoxSaveResult> saveResult =
  614. TrySaveAs(GetEntity()->GetName(), absoluteSavePathFn, relativePathFn, saveDecisionFn);
  615. // user pressed cancel
  616. if (!saveResult.has_value())
  617. {
  618. return AZ::Edit::PropertyRefreshLevels::None;
  619. }
  620. const char* const absoluteSaveFilePath = saveResult.value().m_absoluteFilePath.c_str();
  621. if (saveResult.value().m_relativeAssetPath.has_value())
  622. {
  623. const auto& relativeAssetPath = saveResult.value().m_relativeAssetPath.value();
  624. // notify undo system the entity has been changed (m_meshAsset)
  625. AzToolsFramework::ScopedUndoBatch undoBatch(AssetSavedUndoRedoDesc);
  626. // if there was a previous asset selected, it has to be cloned to a new one
  627. // otherwise the internal mesh can simply be moved into the new asset
  628. m_editorMeshAsset->TakeOwnershipOfWhiteBoxMesh(
  629. relativeAssetPath,
  630. m_editorMeshAsset->InUse() ? Api::CloneMesh(*GetWhiteBoxMesh()) : AZStd::exchange(m_whiteBox, Api::CreateWhiteBoxMesh()));
  631. // change default shape to asset
  632. m_defaultShape = DefaultShapeType::Asset;
  633. // ensure this change gets tracked
  634. undoBatch.MarkEntityDirty(GetEntityId());
  635. RefreshProperties();
  636. m_editorMeshAsset->Save(absoluteSaveFilePath);
  637. }
  638. else
  639. {
  640. // save the asset to disk outside the project folder
  641. if (Api::SaveToWbm(*GetWhiteBoxMesh(), absoluteSaveFilePath))
  642. {
  643. RequestEditSourceControl(absoluteSaveFilePath);
  644. }
  645. }
  646. return AZ::Edit::PropertyRefreshLevels::EntireTree;
  647. }
  648. template<typename TransformFn>
  649. AZ::Aabb CalculateAabb(const WhiteBoxMesh& whiteBox, TransformFn&& transformFn)
  650. {
  651. const auto vertexHandles = Api::MeshVertexHandles(whiteBox);
  652. return AZStd::accumulate(
  653. AZStd::cbegin(vertexHandles), AZStd::cend(vertexHandles), AZ::Aabb::CreateNull(), transformFn);
  654. }
  655. AZ::Aabb EditorWhiteBoxComponent::GetEditorSelectionBoundsViewport(
  656. [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo)
  657. {
  658. return GetWorldBounds();
  659. }
  660. AZ::Aabb EditorWhiteBoxComponent::GetWorldBounds() const
  661. {
  662. AZ_PROFILE_FUNCTION(AzToolsFramework);
  663. if (!m_worldAabb.has_value())
  664. {
  665. m_worldAabb = GetLocalBounds();
  666. m_worldAabb->ApplyTransform(m_worldFromLocal);
  667. }
  668. return m_worldAabb.value();
  669. }
  670. AZ::Aabb EditorWhiteBoxComponent::GetLocalBounds() const
  671. {
  672. AZ_PROFILE_FUNCTION(AzToolsFramework);
  673. if (!m_localAabb.has_value())
  674. {
  675. auto& whiteBoxMesh = *const_cast<EditorWhiteBoxComponent*>(this)->GetWhiteBoxMesh();
  676. m_localAabb = CalculateAabb(
  677. whiteBoxMesh,
  678. [&whiteBox = whiteBoxMesh](AZ::Aabb aabb, const Api::VertexHandle vertexHandle)
  679. {
  680. aabb.AddPoint(Api::VertexPosition(whiteBox, vertexHandle));
  681. return aabb;
  682. });
  683. }
  684. return m_localAabb.value();
  685. }
  686. bool EditorWhiteBoxComponent::EditorSelectionIntersectRayViewport(
  687. [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, const AZ::Vector3& src, const AZ::Vector3& dir,
  688. float& distance)
  689. {
  690. AZ_PROFILE_FUNCTION(AzToolsFramework);
  691. if (!m_faces.has_value())
  692. {
  693. m_faces = Api::MeshFaces(*GetWhiteBoxMesh());
  694. }
  695. // must have at least one triangle
  696. if (m_faces->empty())
  697. {
  698. return false;
  699. }
  700. // transform ray into local space
  701. const AZ::Transform localFromWorld = m_worldFromLocal.GetInverse();
  702. // setup beginning/end of segment
  703. const float rayLength = 1000.0f;
  704. const AZ::Vector3 localRayOrigin = localFromWorld.TransformPoint(src);
  705. const AZ::Vector3 localRayDirection = localFromWorld.TransformVector(dir);
  706. const AZ::Vector3 localRayEnd = localRayOrigin + localRayDirection * rayLength;
  707. bool intersection = false;
  708. AZ::Intersect::SegmentTriangleHitTester hitTester(localRayOrigin, localRayEnd);
  709. for (const auto& face : m_faces.value())
  710. {
  711. float t;
  712. AZ::Vector3 normal;
  713. if (hitTester.IntersectSegmentTriangle(face[0], face[1], face[2], normal, t))
  714. {
  715. intersection = true;
  716. // find closest intersection
  717. const float dist = t * rayLength;
  718. if (dist < distance)
  719. {
  720. distance = dist;
  721. }
  722. }
  723. }
  724. return intersection;
  725. }
  726. void EditorWhiteBoxComponent::OnEntityVisibilityChanged(const bool visibility)
  727. {
  728. if (visibility)
  729. {
  730. ShowRenderMesh();
  731. }
  732. else
  733. {
  734. HideRenderMesh();
  735. }
  736. }
  737. void EditorWhiteBoxComponent::ShowRenderMesh()
  738. {
  739. // if we wish to display the render mesh, set a null render mesh indicating a mesh can exist
  740. // note: if the optional remains empty, no render mesh will be created
  741. m_renderMesh.emplace(AZStd::make_unique<WhiteBoxNullRenderMesh>(AZ::EntityId{}));
  742. RebuildRenderMesh();
  743. }
  744. void EditorWhiteBoxComponent::HideRenderMesh()
  745. {
  746. // clear the optional
  747. m_renderMesh.reset();
  748. }
  749. bool EditorWhiteBoxComponent::AssetInUse() const
  750. {
  751. return m_editorMeshAsset->InUse();
  752. }
  753. bool EditorWhiteBoxComponent::HasRenderMesh() const
  754. {
  755. // if the optional has a value we know a render mesh exists
  756. // note: This implicitly implies that the Entity is visible
  757. return m_renderMesh.has_value();
  758. }
  759. void EditorWhiteBoxComponent::OverrideEditorWhiteBoxMeshAsset(EditorWhiteBoxMeshAsset* editorMeshAsset)
  760. {
  761. // ensure we do not leak resources
  762. delete m_editorMeshAsset;
  763. m_editorMeshAsset = editorMeshAsset;
  764. }
  765. static bool DebugDrawingEnabled()
  766. {
  767. return cl_whiteBoxDebugVertexHandles || cl_whiteBoxDebugNormals || cl_whiteBoxDebugHalfedgeHandles ||
  768. cl_whiteBoxDebugEdgeHandles || cl_whiteBoxDebugFaceHandles || cl_whiteBoxDebugAabb;
  769. }
  770. static void WhiteBoxDebugRendering(
  771. const WhiteBoxMesh& whiteBoxMesh, const AZ::Transform& worldFromLocal,
  772. AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Aabb& editorBounds)
  773. {
  774. const AZ::Quaternion worldOrientationFromLocal = worldFromLocal.GetRotation();
  775. debugDisplay.DepthTestOn();
  776. for (const auto& faceHandle : Api::MeshFaceHandles(whiteBoxMesh))
  777. {
  778. const auto faceHalfedgeHandles = Api::FaceHalfedgeHandles(whiteBoxMesh, faceHandle);
  779. const AZ::Vector3 localFaceCenter =
  780. AZStd::accumulate(
  781. faceHalfedgeHandles.cbegin(), faceHalfedgeHandles.cend(), AZ::Vector3::CreateZero(),
  782. [&whiteBoxMesh](AZ::Vector3 start, const Api::HalfedgeHandle halfedgeHandle)
  783. {
  784. return start +
  785. Api::VertexPosition(
  786. whiteBoxMesh, Api::HalfedgeVertexHandleAtTip(whiteBoxMesh, halfedgeHandle));
  787. }) /
  788. 3.0f;
  789. for (const auto& halfedgeHandle : faceHalfedgeHandles)
  790. {
  791. const Api::VertexHandle vertexHandleAtTip =
  792. Api::HalfedgeVertexHandleAtTip(whiteBoxMesh, halfedgeHandle);
  793. const Api::VertexHandle vertexHandleAtTail =
  794. Api::HalfedgeVertexHandleAtTail(whiteBoxMesh, halfedgeHandle);
  795. const AZ::Vector3 localTailPoint = Api::VertexPosition(whiteBoxMesh, vertexHandleAtTail);
  796. const AZ::Vector3 localTipPoint = Api::VertexPosition(whiteBoxMesh, vertexHandleAtTip);
  797. const AZ::Vector3 localFaceNormal = Api::FaceNormal(whiteBoxMesh, faceHandle);
  798. const AZ::Vector3 localHalfedgeCenter = (localTailPoint + localTipPoint) * 0.5f;
  799. // offset halfedge slightly based on the face it is associated with
  800. const AZ::Vector3 localHalfedgePositionWithOffset =
  801. localHalfedgeCenter + ((localFaceCenter - localHalfedgeCenter).GetNormalized() * 0.1f);
  802. const AZ::Vector3 worldVertexPosition = worldFromLocal.TransformPoint(localTipPoint);
  803. const AZ::Vector3 worldHalfedgePosition =
  804. worldFromLocal.TransformPoint(localHalfedgePositionWithOffset);
  805. const AZ::Vector3 worldNormal =
  806. (worldOrientationFromLocal.TransformVector(localFaceNormal)).GetNormalized();
  807. if (cl_whiteBoxDebugVertexHandles)
  808. {
  809. debugDisplay.SetColor(AZ::Colors::Cyan);
  810. const AZStd::string vertex = AZStd::string::format("%d", vertexHandleAtTip.Index());
  811. debugDisplay.DrawTextLabel(worldVertexPosition, 3.0f, vertex.c_str(), true, 0, 1);
  812. }
  813. if (cl_whiteBoxDebugHalfedgeHandles)
  814. {
  815. debugDisplay.SetColor(AZ::Colors::LawnGreen);
  816. const AZStd::string halfedge = AZStd::string::format("%d", halfedgeHandle.Index());
  817. debugDisplay.DrawTextLabel(worldHalfedgePosition, 2.0f, halfedge.c_str(), true);
  818. }
  819. if (cl_whiteBoxDebugNormals)
  820. {
  821. debugDisplay.SetColor(AZ::Colors::White);
  822. debugDisplay.DrawBall(worldVertexPosition, 0.025f);
  823. debugDisplay.DrawLine(worldVertexPosition, worldVertexPosition + worldNormal * 0.4f);
  824. }
  825. }
  826. if (cl_whiteBoxDebugFaceHandles)
  827. {
  828. debugDisplay.SetColor(AZ::Colors::White);
  829. const AZ::Vector3 worldFacePosition = worldFromLocal.TransformPoint(localFaceCenter);
  830. const AZStd::string face = AZStd::string::format("%d", faceHandle.Index());
  831. debugDisplay.DrawTextLabel(worldFacePosition, 2.0f, face.c_str(), true);
  832. }
  833. }
  834. if (cl_whiteBoxDebugEdgeHandles)
  835. {
  836. for (const auto& edgeHandle : Api::MeshEdgeHandles(whiteBoxMesh))
  837. {
  838. const AZ::Vector3 localEdgeMidpoint = Api::EdgeMidpoint(whiteBoxMesh, edgeHandle);
  839. const AZ::Vector3 worldEdgeMidpoint = worldFromLocal.TransformPoint(localEdgeMidpoint);
  840. debugDisplay.SetColor(AZ::Colors::CornflowerBlue);
  841. const AZStd::string edge = AZStd::string::format("%d", edgeHandle.Index());
  842. debugDisplay.DrawTextLabel(worldEdgeMidpoint, 2.0f, edge.c_str(), true);
  843. }
  844. }
  845. if (cl_whiteBoxDebugAabb)
  846. {
  847. debugDisplay.SetColor(AZ::Colors::Blue);
  848. debugDisplay.DrawWireBox(editorBounds.GetMin(), editorBounds.GetMax());
  849. }
  850. }
  851. void EditorWhiteBoxComponent::DisplayEntityViewport(
  852. [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
  853. {
  854. AZ_PROFILE_FUNCTION(AzToolsFramework);
  855. if (DebugDrawingEnabled())
  856. {
  857. WhiteBoxDebugRendering(
  858. *GetWhiteBoxMesh(), m_worldFromLocal, debugDisplay,
  859. GetEditorSelectionBoundsViewport(AzFramework::ViewportInfo{}));
  860. }
  861. }
  862. } // namespace WhiteBox