EditorWhiteBoxComponent.cpp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  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. if (AZ::EditContext* editContext = serializeContext->GetEditContext())
  167. {
  168. editContext->Class<EditorWhiteBoxComponent>("White Box", "White Box level editing")
  169. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  170. ->Attribute(AZ::Edit::Attributes::Category, "Shape")
  171. ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/WhiteBox.svg")
  172. ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/WhiteBox.svg")
  173. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
  174. ->Attribute(
  175. AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/shape/white-box/")
  176. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  177. ->DataElement(
  178. AZ::Edit::UIHandlers::ComboBox, &EditorWhiteBoxComponent::m_defaultShape, "Default Shape",
  179. "Default shape of the white box mesh.")
  180. ->EnumAttribute(DefaultShapeType::Cube, "Cube")
  181. ->EnumAttribute(DefaultShapeType::Tetrahedron, "Tetrahedron")
  182. ->EnumAttribute(DefaultShapeType::Icosahedron, "Icosahedron")
  183. ->EnumAttribute(DefaultShapeType::Cylinder, "Cylinder")
  184. ->EnumAttribute(DefaultShapeType::Sphere, "Sphere")
  185. ->EnumAttribute(DefaultShapeType::Asset, "Mesh Asset")
  186. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::OnDefaultShapeChange)
  187. ->DataElement(
  188. AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_editorMeshAsset, "Editor Mesh Asset",
  189. "Editor Mesh Asset")
  190. ->Attribute(AZ::Edit::Attributes::Visibility, &EditorWhiteBoxComponent::AssetVisibility)
  191. ->UIElement(AZ::Edit::UIHandlers::Button, "Save as asset", "Save as asset")
  192. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::SaveAsAsset)
  193. ->Attribute(AZ::Edit::Attributes::ButtonText, "Save As ...")
  194. ->DataElement(
  195. AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_material, "White Box Material",
  196. "The properties of the White Box material.")
  197. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::OnMaterialChange)
  198. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  199. ->DataElement(
  200. AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_componentModeDelegate,
  201. "Component Mode", "White Box Tool Component Mode")
  202. ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
  203. ->UIElement(AZ::Edit::UIHandlers::Button, "", "Export to obj")
  204. ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::ExportToFile)
  205. ->Attribute(AZ::Edit::Attributes::ButtonText, "Export");
  206. }
  207. }
  208. }
  209. void EditorWhiteBoxComponent::OnMaterialChange()
  210. {
  211. if (m_renderMesh.has_value())
  212. {
  213. (*m_renderMesh)->UpdateMaterial(m_material);
  214. m_renderData.m_material = m_material;
  215. }
  216. }
  217. AZ::Crc32 EditorWhiteBoxComponent::AssetVisibility() const
  218. {
  219. return DisplayingAsset(m_defaultShape) ? AZ::Edit::PropertyVisibility::ShowChildrenOnly
  220. : AZ::Edit::PropertyVisibility::Hide;
  221. }
  222. void EditorWhiteBoxComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  223. {
  224. required.push_back(AZ_CRC_CE("TransformService"));
  225. }
  226. void EditorWhiteBoxComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  227. {
  228. provided.push_back(AZ_CRC_CE("WhiteBoxService"));
  229. }
  230. void EditorWhiteBoxComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  231. {
  232. incompatible.push_back(AZ_CRC_CE("NonUniformScaleService"));
  233. incompatible.push_back(AZ_CRC_CE("MeshService"));
  234. incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
  235. }
  236. EditorWhiteBoxComponent::EditorWhiteBoxComponent() = default;
  237. EditorWhiteBoxComponent::~EditorWhiteBoxComponent()
  238. {
  239. // note: m_editorMeshAsset is (usually) serialized so it is created by the reflection system
  240. // in Reflect (no explicit `new`) - we must still clean-up the resource on destruction though
  241. // to not leak resources.
  242. delete m_editorMeshAsset;
  243. }
  244. void EditorWhiteBoxComponent::Init()
  245. {
  246. if (m_editorMeshAsset)
  247. {
  248. return;
  249. }
  250. // if the m_editorMeshAsset has not been created by the serialization system
  251. // create a new EditorWhiteBoxMeshAsset here
  252. m_editorMeshAsset = aznew EditorWhiteBoxMeshAsset();
  253. }
  254. void EditorWhiteBoxComponent::Activate()
  255. {
  256. const AZ::EntityId entityId = GetEntityId();
  257. const AZ::EntityComponentIdPair entityComponentIdPair{entityId, GetId()};
  258. AzToolsFramework::Components::EditorComponentBase::Activate();
  259. EditorWhiteBoxComponentRequestBus::Handler::BusConnect(entityComponentIdPair);
  260. EditorWhiteBoxComponentNotificationBus::Handler::BusConnect(entityComponentIdPair);
  261. AZ::TransformNotificationBus::Handler::BusConnect(entityId);
  262. AzFramework::BoundsRequestBus::Handler::BusConnect(entityId);
  263. AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(entityId);
  264. AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(entityId);
  265. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusConnect(entityId);
  266. m_componentModeDelegate.ConnectWithSingleComponentMode<EditorWhiteBoxComponent, EditorWhiteBoxComponentMode>(
  267. entityComponentIdPair, this);
  268. m_worldFromLocal = AZ::Transform::CreateIdentity();
  269. AZ::TransformBus::EventResult(m_worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM);
  270. m_editorMeshAsset->Associate(entityComponentIdPair);
  271. // deserialize the white box data into a mesh object or load the serialized asset ref
  272. DeserializeWhiteBox();
  273. if (AzToolsFramework::IsEntityVisible(entityId))
  274. {
  275. ShowRenderMesh();
  276. OnMaterialChange();
  277. }
  278. }
  279. void EditorWhiteBoxComponent::Deactivate()
  280. {
  281. AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusDisconnect();
  282. AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusDisconnect();
  283. AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
  284. AzFramework::BoundsRequestBus::Handler::BusDisconnect();
  285. AZ::TransformNotificationBus::Handler::BusDisconnect();
  286. EditorWhiteBoxComponentRequestBus::Handler::BusDisconnect();
  287. EditorWhiteBoxComponentNotificationBus::Handler::BusDisconnect();
  288. AzToolsFramework::Components::EditorComponentBase::Deactivate();
  289. m_componentModeDelegate.Disconnect();
  290. m_editorMeshAsset->Release();
  291. m_renderMesh.reset();
  292. m_whiteBox.reset();
  293. }
  294. void EditorWhiteBoxComponent::DeserializeWhiteBox()
  295. {
  296. // create WhiteBoxMesh object from internal data
  297. m_whiteBox = Api::CreateWhiteBoxMesh();
  298. if (m_editorMeshAsset->InUse())
  299. {
  300. m_editorMeshAsset->Load();
  301. }
  302. else
  303. {
  304. // attempt to load the mesh
  305. const auto result = Api::ReadMesh(*m_whiteBox, m_whiteBoxData);
  306. AZ_Error("EditorWhiteBoxComponent", result != WhiteBox::Api::ReadResult::Error, "Error deserializing white box mesh stream");
  307. // if the read was successful but the byte stream is empty
  308. // (there was nothing to load), create a default mesh
  309. if (result == Api::ReadResult::Empty)
  310. {
  311. Api::InitializeAsUnitCube(*m_whiteBox);
  312. }
  313. }
  314. }
  315. void EditorWhiteBoxComponent::RebuildWhiteBox()
  316. {
  317. RebuildRenderMesh();
  318. RebuildPhysicsMesh();
  319. }
  320. void EditorWhiteBoxComponent::BuildGameEntity(AZ::Entity* gameEntity)
  321. {
  322. if (auto* whiteBoxComponent = gameEntity->CreateComponent<WhiteBoxComponent>())
  323. {
  324. // note: it is important no edit time only functions are called here as BuildGameEntity
  325. // will be called by the Asset Processor when creating dynamic slices
  326. whiteBoxComponent->GenerateWhiteBoxMesh(m_renderData);
  327. }
  328. }
  329. WhiteBoxMesh* EditorWhiteBoxComponent::GetWhiteBoxMesh()
  330. {
  331. if (WhiteBoxMesh* whiteBox = m_editorMeshAsset->GetWhiteBoxMesh())
  332. {
  333. return whiteBox;
  334. }
  335. return m_whiteBox.get();
  336. }
  337. void EditorWhiteBoxComponent::OnWhiteBoxMeshModified()
  338. {
  339. // if using an asset, notify other editor mesh assets using the same id that
  340. // the asset has been modified, this will in turn cause all components to update
  341. // their render and physics meshes
  342. if (m_editorMeshAsset->InUse())
  343. {
  344. WhiteBoxMeshAssetNotificationBus::Event(
  345. m_editorMeshAsset->GetWhiteBoxMeshAssetId(),
  346. &WhiteBoxMeshAssetNotificationBus::Events::OnWhiteBoxMeshAssetModified,
  347. m_editorMeshAsset->GetWhiteBoxMeshAsset());
  348. }
  349. // otherwise, update the render and physics mesh immediately
  350. else
  351. {
  352. RebuildWhiteBox();
  353. }
  354. }
  355. void EditorWhiteBoxComponent::RebuildRenderMesh()
  356. {
  357. AZ_PROFILE_FUNCTION(AzToolsFramework);
  358. // reset caches when the mesh changes
  359. m_worldAabb.reset();
  360. m_localAabb.reset();
  361. m_faces.reset();
  362. AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
  363. // must have been created in Activate or have had the Entity made visible again
  364. if (m_renderMesh.has_value())
  365. {
  366. // cache the white box render data
  367. m_renderData = CreateWhiteBoxRenderData(*GetWhiteBoxMesh(), m_material);
  368. // it's possible the white box mesh data isn't yet ready (for example if it's stored
  369. // in an asset which hasn't finished loading yet) so don't attempt to create a render
  370. // mesh with no data
  371. if (!m_renderData.m_faces.empty())
  372. {
  373. // check if we need to instantiate a concrete render mesh implementation
  374. if (IsWhiteBoxNullRenderMesh(m_renderMesh))
  375. {
  376. // create a concrete implementation of the render mesh
  377. WhiteBoxRequestBus::BroadcastResult(m_renderMesh, &WhiteBoxRequests::CreateRenderMeshInterface, GetEntityId());
  378. }
  379. // generate the mesh
  380. (*m_renderMesh)->BuildMesh(m_renderData, m_worldFromLocal);
  381. OnMaterialChange();
  382. }
  383. }
  384. EditorWhiteBoxComponentModeRequestBus::Event(
  385. AZ::EntityComponentIdPair{GetEntityId(), GetId()},
  386. &EditorWhiteBoxComponentModeRequests::MarkWhiteBoxIntersectionDataDirty);
  387. }
  388. void EditorWhiteBoxComponent::WriteAssetToComponent()
  389. {
  390. if (m_editorMeshAsset->Loaded())
  391. {
  392. Api::WriteMesh(*m_editorMeshAsset->GetWhiteBoxMesh(), m_whiteBoxData);
  393. }
  394. }
  395. void EditorWhiteBoxComponent::SerializeWhiteBox()
  396. {
  397. if (m_editorMeshAsset->Loaded())
  398. {
  399. m_editorMeshAsset->Serialize();
  400. }
  401. else
  402. {
  403. Api::WriteMesh(*m_whiteBox, m_whiteBoxData);
  404. }
  405. }
  406. void EditorWhiteBoxComponent::SetDefaultShape(const DefaultShapeType defaultShape)
  407. {
  408. m_defaultShape = defaultShape;
  409. OnDefaultShapeChange();
  410. }
  411. void EditorWhiteBoxComponent::OnTransformChanged(
  412. [[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world)
  413. {
  414. AZ_PROFILE_FUNCTION(AzToolsFramework);
  415. m_worldAabb.reset();
  416. m_localAabb.reset();
  417. m_worldFromLocal = world;
  418. if (m_renderMesh.has_value())
  419. {
  420. (*m_renderMesh)->UpdateTransform(world);
  421. }
  422. }
  423. void EditorWhiteBoxComponent::RebuildPhysicsMesh()
  424. {
  425. AZ_PROFILE_FUNCTION(AzToolsFramework);
  426. EditorWhiteBoxColliderRequestBus::Event(
  427. GetEntityId(), &EditorWhiteBoxColliderRequests::CreatePhysics, *GetWhiteBoxMesh());
  428. }
  429. static AZStd::string WhiteBoxPathAtProjectRoot(const AZStd::string_view name, const AZStd::string_view extension)
  430. {
  431. AZ::IO::Path whiteBoxPath;
  432. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  433. {
  434. settingsRegistry->Get(whiteBoxPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
  435. }
  436. whiteBoxPath /= AZ::IO::FixedMaxPathString::format("%.*s.%.*s", AZ_STRING_ARG(name), AZ_STRING_ARG(extension));
  437. return whiteBoxPath.Native();
  438. }
  439. void EditorWhiteBoxComponent::ExportToFile()
  440. {
  441. const AZStd::string initialAbsolutePathToExport =
  442. WhiteBoxPathAtProjectRoot(GetEntity()->GetName(), ObjExtension);
  443. const QString fileFilter = AZStd::string::format("*.%s", ObjExtension).c_str();
  444. const QString absoluteSaveFilePath = AzQtComponents::FileDialog::GetSaveFileName(
  445. nullptr, "Save As...", QString(initialAbsolutePathToExport.c_str()), fileFilter);
  446. const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
  447. const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
  448. if (WhiteBox::Api::SaveToObj(*GetWhiteBoxMesh(), absoluteSaveFilePathCstr))
  449. {
  450. AZ_Printf("EditorWhiteBoxComponent", "Exported white box mesh to: %s", absoluteSaveFilePathCstr);
  451. RequestEditSourceControl(absoluteSaveFilePathCstr);
  452. }
  453. else
  454. {
  455. AZ_Warning(
  456. "EditorWhiteBoxComponent", false, "Failed to export white box mesh to: %s", absoluteSaveFilePathCstr);
  457. }
  458. }
  459. AZStd::optional<WhiteBoxSaveResult> TrySaveAs(
  460. const AZStd::string_view entityName,
  461. const AZStd::function<AZStd::string(const AZStd::string&)>& absoluteSavePathFn,
  462. const AZStd::function<AZStd::optional<AZStd::string>(const AZStd::string&)>& relativePathFn,
  463. const AZStd::function<int()>& saveDecisionFn)
  464. {
  465. const AZStd::string initialAbsolutePathToSave =
  466. WhiteBoxPathAtProjectRoot(entityName, Pipeline::WhiteBoxMeshAssetHandler::AssetFileExtension);
  467. const QString absoluteSaveFilePath = QString(absoluteSavePathFn(initialAbsolutePathToSave).c_str());
  468. // user pressed cancel
  469. if (absoluteSaveFilePath.isEmpty())
  470. {
  471. return AZStd::nullopt;
  472. }
  473. const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
  474. const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
  475. const AZStd::optional<AZStd::string> relativePath =
  476. relativePathFn(AZStd::string(absoluteSaveFilePathCstr, absoluteSaveFilePathUtf8.length()));
  477. if (!relativePath.has_value())
  478. {
  479. int saveDecision = saveDecisionFn();
  480. // save the file but do not attempt to create an asset
  481. if (saveDecision == QMessageBox::Save)
  482. {
  483. return WhiteBoxSaveResult{AZStd::nullopt, AZStd::string(absoluteSaveFilePathCstr)};
  484. }
  485. // the user decided not to save the asset outside the project folder after the prompt
  486. return AZStd::nullopt;
  487. }
  488. return WhiteBoxSaveResult{relativePath, AZStd::string(absoluteSaveFilePathCstr)};
  489. }
  490. AZ::Crc32 EditorWhiteBoxComponent::SaveAsAsset()
  491. {
  492. // let the user select final location of the saved asset
  493. const auto absoluteSavePathFn = [](const AZStd::string& initialAbsolutePath)
  494. {
  495. const QString fileFilter =
  496. AZStd::string::format("WhiteBoxMesh (*.%s)", Pipeline::WhiteBoxMeshAssetHandler::AssetFileExtension).c_str();
  497. const QString absolutePath = AzQtComponents::FileDialog::GetSaveFileName(
  498. nullptr, "Save As Asset...", QString(initialAbsolutePath.c_str()), fileFilter);
  499. return AZStd::string(absolutePath.toUtf8());
  500. };
  501. // ask the asset system to try and convert the absolutePath to a cache relative path
  502. const auto relativePathFn = [](const AZStd::string& absolutePath) -> AZStd::optional<AZStd::string>
  503. {
  504. AZStd::string relativePath;
  505. bool foundRelativePath = false;
  506. AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
  507. foundRelativePath,
  508. &AzToolsFramework::AssetSystem::AssetSystemRequest::GetRelativeProductPathFromFullSourceOrProductPath,
  509. absolutePath, relativePath);
  510. if (foundRelativePath)
  511. {
  512. return relativePath;
  513. }
  514. return AZStd::nullopt;
  515. };
  516. // present the user with the option of accepting saving outside the project folder or allow them to cancel the
  517. // operation
  518. const auto saveDecisionFn = []()
  519. {
  520. return QMessageBox::warning(
  521. AzToolsFramework::GetActiveWindow(), "Warning",
  522. "Saving a White Box Mesh Asset (.wbm) outside of the project root will not create an Asset for the "
  523. "Component to use. The file will be saved but will not be processed. For live updates to happen the "
  524. "asset must be saved somewhere in the current project folder. Would you like to continue?",
  525. (QMessageBox::Save | QMessageBox::Cancel), QMessageBox::Cancel);
  526. };
  527. const AZStd::optional<WhiteBoxSaveResult> saveResult =
  528. TrySaveAs(GetEntity()->GetName(), absoluteSavePathFn, relativePathFn, saveDecisionFn);
  529. // user pressed cancel
  530. if (!saveResult.has_value())
  531. {
  532. return AZ::Edit::PropertyRefreshLevels::None;
  533. }
  534. const char* const absoluteSaveFilePath = saveResult.value().m_absoluteFilePath.c_str();
  535. if (saveResult.value().m_relativeAssetPath.has_value())
  536. {
  537. const auto& relativeAssetPath = saveResult.value().m_relativeAssetPath.value();
  538. // notify undo system the entity has been changed (m_meshAsset)
  539. AzToolsFramework::ScopedUndoBatch undoBatch(AssetSavedUndoRedoDesc);
  540. // if there was a previous asset selected, it has to be cloned to a new one
  541. // otherwise the internal mesh can simply be moved into the new asset
  542. m_editorMeshAsset->TakeOwnershipOfWhiteBoxMesh(
  543. relativeAssetPath,
  544. m_editorMeshAsset->InUse() ? Api::CloneMesh(*GetWhiteBoxMesh())
  545. : AZStd::exchange(m_whiteBox, Api::CreateWhiteBoxMesh()));
  546. // change default shape to asset
  547. m_defaultShape = DefaultShapeType::Asset;
  548. // ensure this change gets tracked
  549. undoBatch.MarkEntityDirty(GetEntityId());
  550. RefreshProperties();
  551. m_editorMeshAsset->Save(absoluteSaveFilePath);
  552. }
  553. else
  554. {
  555. // save the asset to disk outside the project folder
  556. if (Api::SaveToWbm(*GetWhiteBoxMesh(), absoluteSaveFilePath))
  557. {
  558. RequestEditSourceControl(absoluteSaveFilePath);
  559. }
  560. }
  561. return AZ::Edit::PropertyRefreshLevels::EntireTree;
  562. }
  563. template<typename TransformFn>
  564. AZ::Aabb CalculateAabb(const WhiteBoxMesh& whiteBox, TransformFn&& transformFn)
  565. {
  566. const auto vertexHandles = Api::MeshVertexHandles(whiteBox);
  567. return AZStd::accumulate(
  568. AZStd::cbegin(vertexHandles), AZStd::cend(vertexHandles), AZ::Aabb::CreateNull(), transformFn);
  569. }
  570. AZ::Aabb EditorWhiteBoxComponent::GetEditorSelectionBoundsViewport(
  571. [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo)
  572. {
  573. return GetWorldBounds();
  574. }
  575. AZ::Aabb EditorWhiteBoxComponent::GetWorldBounds()
  576. {
  577. AZ_PROFILE_FUNCTION(AzToolsFramework);
  578. if (!m_worldAabb.has_value())
  579. {
  580. m_worldAabb = GetLocalBounds();
  581. m_worldAabb->ApplyTransform(m_worldFromLocal);
  582. }
  583. return m_worldAabb.value();
  584. }
  585. AZ::Aabb EditorWhiteBoxComponent::GetLocalBounds()
  586. {
  587. AZ_PROFILE_FUNCTION(AzToolsFramework);
  588. if (!m_localAabb.has_value())
  589. {
  590. auto& whiteBoxMesh = *GetWhiteBoxMesh();
  591. m_localAabb = CalculateAabb(
  592. whiteBoxMesh,
  593. [&whiteBox = whiteBoxMesh](AZ::Aabb aabb, const Api::VertexHandle vertexHandle)
  594. {
  595. aabb.AddPoint(Api::VertexPosition(whiteBox, vertexHandle));
  596. return aabb;
  597. });
  598. }
  599. return m_localAabb.value();
  600. }
  601. bool EditorWhiteBoxComponent::EditorSelectionIntersectRayViewport(
  602. [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, const AZ::Vector3& src, const AZ::Vector3& dir,
  603. float& distance)
  604. {
  605. AZ_PROFILE_FUNCTION(AzToolsFramework);
  606. if (!m_faces.has_value())
  607. {
  608. m_faces = Api::MeshFaces(*GetWhiteBoxMesh());
  609. }
  610. // must have at least one triangle
  611. if (m_faces->empty())
  612. {
  613. return false;
  614. }
  615. // transform ray into local space
  616. const AZ::Transform localFromWorld = m_worldFromLocal.GetInverse();
  617. // setup beginning/end of segment
  618. const float rayLength = 1000.0f;
  619. const AZ::Vector3 localRayOrigin = localFromWorld.TransformPoint(src);
  620. const AZ::Vector3 localRayDirection = localFromWorld.TransformVector(dir);
  621. const AZ::Vector3 localRayEnd = localRayOrigin + localRayDirection * rayLength;
  622. bool intersection = false;
  623. AZ::Intersect::SegmentTriangleHitTester hitTester(localRayOrigin, localRayEnd);
  624. for (const auto& face : m_faces.value())
  625. {
  626. float t;
  627. AZ::Vector3 normal;
  628. if (hitTester.IntersectSegmentTriangle(face[0], face[1], face[2], normal, t))
  629. {
  630. intersection = true;
  631. // find closest intersection
  632. const float dist = t * rayLength;
  633. if (dist < distance)
  634. {
  635. distance = dist;
  636. }
  637. }
  638. }
  639. return intersection;
  640. }
  641. void EditorWhiteBoxComponent::OnEntityVisibilityChanged(const bool visibility)
  642. {
  643. if (visibility)
  644. {
  645. ShowRenderMesh();
  646. }
  647. else
  648. {
  649. HideRenderMesh();
  650. }
  651. }
  652. void EditorWhiteBoxComponent::ShowRenderMesh()
  653. {
  654. // if we wish to display the render mesh, set a null render mesh indicating a mesh can exist
  655. // note: if the optional remains empty, no render mesh will be created
  656. m_renderMesh.emplace(AZStd::make_unique<WhiteBoxNullRenderMesh>(AZ::EntityId{}));
  657. RebuildRenderMesh();
  658. }
  659. void EditorWhiteBoxComponent::HideRenderMesh()
  660. {
  661. // clear the optional
  662. m_renderMesh.reset();
  663. }
  664. bool EditorWhiteBoxComponent::AssetInUse() const
  665. {
  666. return m_editorMeshAsset->InUse();
  667. }
  668. bool EditorWhiteBoxComponent::HasRenderMesh() const
  669. {
  670. // if the optional has a value we know a render mesh exists
  671. // note: This implicitly implies that the Entity is visible
  672. return m_renderMesh.has_value();
  673. }
  674. void EditorWhiteBoxComponent::OverrideEditorWhiteBoxMeshAsset(EditorWhiteBoxMeshAsset* editorMeshAsset)
  675. {
  676. // ensure we do not leak resources
  677. delete m_editorMeshAsset;
  678. m_editorMeshAsset = editorMeshAsset;
  679. }
  680. static bool DebugDrawingEnabled()
  681. {
  682. return cl_whiteBoxDebugVertexHandles || cl_whiteBoxDebugNormals || cl_whiteBoxDebugHalfedgeHandles ||
  683. cl_whiteBoxDebugEdgeHandles || cl_whiteBoxDebugFaceHandles || cl_whiteBoxDebugAabb;
  684. }
  685. static void WhiteBoxDebugRendering(
  686. const WhiteBoxMesh& whiteBoxMesh, const AZ::Transform& worldFromLocal,
  687. AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Aabb& editorBounds)
  688. {
  689. const AZ::Quaternion worldOrientationFromLocal = worldFromLocal.GetRotation();
  690. debugDisplay.DepthTestOn();
  691. for (const auto& faceHandle : Api::MeshFaceHandles(whiteBoxMesh))
  692. {
  693. const auto faceHalfedgeHandles = Api::FaceHalfedgeHandles(whiteBoxMesh, faceHandle);
  694. const AZ::Vector3 localFaceCenter =
  695. AZStd::accumulate(
  696. faceHalfedgeHandles.cbegin(), faceHalfedgeHandles.cend(), AZ::Vector3::CreateZero(),
  697. [&whiteBoxMesh](AZ::Vector3 start, const Api::HalfedgeHandle halfedgeHandle)
  698. {
  699. return start +
  700. Api::VertexPosition(
  701. whiteBoxMesh, Api::HalfedgeVertexHandleAtTip(whiteBoxMesh, halfedgeHandle));
  702. }) /
  703. 3.0f;
  704. for (const auto& halfedgeHandle : faceHalfedgeHandles)
  705. {
  706. const Api::VertexHandle vertexHandleAtTip =
  707. Api::HalfedgeVertexHandleAtTip(whiteBoxMesh, halfedgeHandle);
  708. const Api::VertexHandle vertexHandleAtTail =
  709. Api::HalfedgeVertexHandleAtTail(whiteBoxMesh, halfedgeHandle);
  710. const AZ::Vector3 localTailPoint = Api::VertexPosition(whiteBoxMesh, vertexHandleAtTail);
  711. const AZ::Vector3 localTipPoint = Api::VertexPosition(whiteBoxMesh, vertexHandleAtTip);
  712. const AZ::Vector3 localFaceNormal = Api::FaceNormal(whiteBoxMesh, faceHandle);
  713. const AZ::Vector3 localHalfedgeCenter = (localTailPoint + localTipPoint) * 0.5f;
  714. // offset halfedge slightly based on the face it is associated with
  715. const AZ::Vector3 localHalfedgePositionWithOffset =
  716. localHalfedgeCenter + ((localFaceCenter - localHalfedgeCenter).GetNormalized() * 0.1f);
  717. const AZ::Vector3 worldVertexPosition = worldFromLocal.TransformPoint(localTipPoint);
  718. const AZ::Vector3 worldHalfedgePosition =
  719. worldFromLocal.TransformPoint(localHalfedgePositionWithOffset);
  720. const AZ::Vector3 worldNormal =
  721. (worldOrientationFromLocal.TransformVector(localFaceNormal)).GetNormalized();
  722. if (cl_whiteBoxDebugVertexHandles)
  723. {
  724. debugDisplay.SetColor(AZ::Colors::Cyan);
  725. const AZStd::string vertex = AZStd::string::format("%d", vertexHandleAtTip.Index());
  726. debugDisplay.DrawTextLabel(worldVertexPosition, 3.0f, vertex.c_str(), true, 0, 1);
  727. }
  728. if (cl_whiteBoxDebugHalfedgeHandles)
  729. {
  730. debugDisplay.SetColor(AZ::Colors::LawnGreen);
  731. const AZStd::string halfedge = AZStd::string::format("%d", halfedgeHandle.Index());
  732. debugDisplay.DrawTextLabel(worldHalfedgePosition, 2.0f, halfedge.c_str(), true);
  733. }
  734. if (cl_whiteBoxDebugNormals)
  735. {
  736. debugDisplay.SetColor(AZ::Colors::White);
  737. debugDisplay.DrawBall(worldVertexPosition, 0.025f);
  738. debugDisplay.DrawLine(worldVertexPosition, worldVertexPosition + worldNormal * 0.4f);
  739. }
  740. }
  741. if (cl_whiteBoxDebugFaceHandles)
  742. {
  743. debugDisplay.SetColor(AZ::Colors::White);
  744. const AZ::Vector3 worldFacePosition = worldFromLocal.TransformPoint(localFaceCenter);
  745. const AZStd::string face = AZStd::string::format("%d", faceHandle.Index());
  746. debugDisplay.DrawTextLabel(worldFacePosition, 2.0f, face.c_str(), true);
  747. }
  748. }
  749. if (cl_whiteBoxDebugEdgeHandles)
  750. {
  751. for (const auto& edgeHandle : Api::MeshEdgeHandles(whiteBoxMesh))
  752. {
  753. const AZ::Vector3 localEdgeMidpoint = Api::EdgeMidpoint(whiteBoxMesh, edgeHandle);
  754. const AZ::Vector3 worldEdgeMidpoint = worldFromLocal.TransformPoint(localEdgeMidpoint);
  755. debugDisplay.SetColor(AZ::Colors::CornflowerBlue);
  756. const AZStd::string edge = AZStd::string::format("%d", edgeHandle.Index());
  757. debugDisplay.DrawTextLabel(worldEdgeMidpoint, 2.0f, edge.c_str(), true);
  758. }
  759. }
  760. if (cl_whiteBoxDebugAabb)
  761. {
  762. debugDisplay.SetColor(AZ::Colors::Blue);
  763. debugDisplay.DrawWireBox(editorBounds.GetMin(), editorBounds.GetMax());
  764. }
  765. }
  766. void EditorWhiteBoxComponent::DisplayEntityViewport(
  767. [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
  768. {
  769. AZ_PROFILE_FUNCTION(AzToolsFramework);
  770. if (DebugDrawingEnabled())
  771. {
  772. WhiteBoxDebugRendering(
  773. *GetWhiteBoxMesh(), m_worldFromLocal, debugDisplay,
  774. GetEditorSelectionBoundsViewport(AzFramework::ViewportInfo{}));
  775. }
  776. }
  777. } // namespace WhiteBox