EditorWhiteBoxComponent.cpp 43 KB

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