123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "Asset/EditorWhiteBoxMeshAsset.h"
- #include "Asset/WhiteBoxMeshAssetHandler.h"
- #include "EditorWhiteBoxComponent.h"
- #include "EditorWhiteBoxComponentMode.h"
- #include "EditorWhiteBoxComponentModeBus.h"
- #include "Rendering/WhiteBoxNullRenderMesh.h"
- #include "Rendering/WhiteBoxRenderDataUtil.h"
- #include "Rendering/WhiteBoxRenderMeshInterface.h"
- #include "Util/WhiteBoxEditorUtil.h"
- #include "WhiteBoxComponent.h"
- #include <AzCore/Asset/AssetSerializer.h>
- #include <AzCore/Component/TransformBus.h>
- #include <AzCore/Console/Console.h>
- #include <AzCore/Math/IntersectSegment.h>
- #include <AzCore/Memory/Memory.h>
- #include <AzCore/Serialization/EditContext.h>
- #include <AzCore/Serialization/SerializeContext.h>
- #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
- #include <AzCore/std/numeric.h>
- #include <AzFramework/StringFunc/StringFunc.h>
- #include <AzQtComponents/Components/Widgets/FileDialog.h>
- #include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
- #include <AzToolsFramework/API/EditorAssetSystemAPI.h>
- #include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
- #include <AzToolsFramework/Entity/EditorEntityHelpers.h>
- #include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
- #include <AzToolsFramework/Maths/TransformUtils.h>
- #include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
- #include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
- #include <QMessageBox>
- #include <WhiteBox/EditorWhiteBoxColliderBus.h>
- #include <WhiteBox/WhiteBoxBus.h>
- // developer debug properties for the White Box mesh to globally enable/disable
- AZ_CVAR(bool, cl_whiteBoxDebugVertexHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display vertex handles");
- AZ_CVAR(bool, cl_whiteBoxDebugNormals, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display normals");
- AZ_CVAR(
- bool, cl_whiteBoxDebugHalfedgeHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display halfedge handles");
- AZ_CVAR(bool, cl_whiteBoxDebugEdgeHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display edge handles");
- AZ_CVAR(bool, cl_whiteBoxDebugFaceHandles, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display face handles");
- AZ_CVAR(bool, cl_whiteBoxDebugAabb, false, nullptr, AZ::ConsoleFunctorFlags::Null, "Display Aabb for the White Box");
- namespace WhiteBox
- {
- static const char* const AssetSavedUndoRedoDesc = "White Box Mesh asset saved";
- static const char* const ObjExtension = "obj";
- static void RefreshProperties()
- {
- AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
- &AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
- AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
- }
- // build intermediate data to be passed to WhiteBoxRenderMeshInterface
- // to be used to generate concrete render mesh
- static WhiteBoxRenderData CreateWhiteBoxRenderData(const WhiteBoxMesh& whiteBox, const WhiteBoxMaterial& material)
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- WhiteBoxRenderData renderData;
- WhiteBoxFaces& faceData = renderData.m_faces;
- const auto faceCount = Api::MeshFaceCount(whiteBox);
- faceData.reserve(faceCount);
- const auto createWhiteBoxFaceFromHandle = [&whiteBox](const Api::FaceHandle& faceHandle) -> WhiteBoxFace
- {
- const auto copyVertex = [&whiteBox](const Api::HalfedgeHandle& in, WhiteBoxVertex& out)
- {
- const auto vh = Api::HalfedgeVertexHandleAtTip(whiteBox, in);
- out.m_position = Api::VertexPosition(whiteBox, vh);
- out.m_uv = Api::HalfedgeUV(whiteBox, in);
- };
- WhiteBoxFace face;
- face.m_normal = Api::FaceNormal(whiteBox, faceHandle);
- const auto faceHalfedgeHandles = Api::FaceHalfedgeHandles(whiteBox, faceHandle);
- copyVertex(faceHalfedgeHandles[0], face.m_v1);
- copyVertex(faceHalfedgeHandles[1], face.m_v2);
- copyVertex(faceHalfedgeHandles[2], face.m_v3);
- return face;
- };
- const auto faceHandles = Api::MeshFaceHandles(whiteBox);
- for (const auto& faceHandle : faceHandles)
- {
- faceData.push_back(createWhiteBoxFaceFromHandle(faceHandle));
- }
- renderData.m_material = material;
- return renderData;
- }
- static bool IsWhiteBoxNullRenderMesh(const AZStd::optional<AZStd::unique_ptr<RenderMeshInterface>>& m_renderMesh)
- {
- return azrtti_cast<WhiteBoxNullRenderMesh*>((*m_renderMesh).get()) != nullptr;
- }
- static bool DisplayingAsset(const DefaultShapeType defaultShapeType)
- {
- // checks if the default shape is set to a custom asset
- return defaultShapeType == DefaultShapeType::Asset;
- }
- // callback for when the default shape field is changed
- AZ::Crc32 EditorWhiteBoxComponent::OnDefaultShapeChange()
- {
- const AZStd::string entityIdStr = AZStd::string::format("%llu", static_cast<AZ::u64>(GetEntityId()));
- const AZStd::string componentIdStr = AZStd::string::format("%llu", GetId());
- const AZStd::string shapeTypeStr = AZStd::string::format("%d", aznumeric_cast<int>(m_defaultShape));
- const AZStd::vector<AZStd::string_view> scriptArgs{entityIdStr, componentIdStr, shapeTypeStr};
- // if the shape type has just changed and it is no longer an asset type, check if a mesh asset
- // is in use and clear it if so (switch back to using the component serialized White Box mesh)
- if (!DisplayingAsset(m_defaultShape) && m_editorMeshAsset->InUse())
- {
- m_editorMeshAsset->Reset();
- }
- AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
- &AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs,
- "@gemroot:WhiteBox@/Editor/Scripts/default_shapes.py", scriptArgs);
- EditorWhiteBoxComponentNotificationBus::Event(
- AZ::EntityComponentIdPair(GetEntityId(), GetId()),
- &EditorWhiteBoxComponentNotificationBus::Events::OnDefaultShapeTypeChanged, m_defaultShape);
- return AZ::Edit::PropertyRefreshLevels::EntireTree;
- }
- bool EditorWhiteBoxVersionConverter(
- AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
- {
- if (classElement.GetVersion() <= 1)
- {
- // find the old WhiteBoxMeshAsset stored directly on the component
- AZ::Data::Asset<Pipeline::WhiteBoxMeshAsset> meshAsset;
- const int meshAssetIndex = classElement.FindElement(AZ_CRC_CE("MeshAsset"));
- if (meshAssetIndex != -1)
- {
- classElement.GetSubElement(meshAssetIndex).GetData(meshAsset);
- classElement.RemoveElement(meshAssetIndex);
- }
- else
- {
- return false;
- }
- // add the new EditorWhiteBoxMeshAsset which will contain the previous WhiteBoxMeshAsset
- const int editorMeshAssetIndex =
- classElement.AddElement<EditorWhiteBoxMeshAsset>(context, "EditorMeshAsset");
- if (editorMeshAssetIndex != -1)
- {
- // insert the existing WhiteBoxMeshAsset into the new EditorWhiteBoxMeshAsset
- classElement.GetSubElement(editorMeshAssetIndex)
- .AddElementWithData<AZ::Data::Asset<Pipeline::WhiteBoxMeshAsset>>(context, "MeshAsset", meshAsset);
- }
- else
- {
- return false;
- }
- }
- return true;
- }
- void EditorWhiteBoxComponent::Reflect(AZ::ReflectContext* context)
- {
- EditorWhiteBoxMeshAsset::Reflect(context);
- if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
- {
- serializeContext->Class<EditorWhiteBoxComponent, EditorComponentBase>()
- ->Version(2, &EditorWhiteBoxVersionConverter)
- ->Field("WhiteBoxData", &EditorWhiteBoxComponent::m_whiteBoxData)
- ->Field("DefaultShape", &EditorWhiteBoxComponent::m_defaultShape)
- ->Field("EditorMeshAsset", &EditorWhiteBoxComponent::m_editorMeshAsset)
- ->Field("Material", &EditorWhiteBoxComponent::m_material)
- ->Field("RenderData", &EditorWhiteBoxComponent::m_renderData)
- ->Field("ComponentMode", &EditorWhiteBoxComponent::m_componentModeDelegate)
- ->Field("FlipYZForExport", &EditorWhiteBoxComponent::m_flipYZForExport);
- if (AZ::EditContext* editContext = serializeContext->GetEditContext())
- {
- editContext->Class<EditorWhiteBoxComponent>("White Box", "White Box level editing")
- ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
- ->Attribute(AZ::Edit::Attributes::Category, "Shape")
- ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/WhiteBox.svg")
- ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/WhiteBox.svg")
- ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
- ->Attribute(
- AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/shape/white-box/")
- ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
- ->DataElement(
- AZ::Edit::UIHandlers::ComboBox, &EditorWhiteBoxComponent::m_defaultShape, "Default Shape",
- "Default shape of the white box mesh.")
- ->EnumAttribute(DefaultShapeType::Cube, "Cube")
- ->EnumAttribute(DefaultShapeType::Tetrahedron, "Tetrahedron")
- ->EnumAttribute(DefaultShapeType::Icosahedron, "Icosahedron")
- ->EnumAttribute(DefaultShapeType::Cylinder, "Cylinder")
- ->EnumAttribute(DefaultShapeType::Sphere, "Sphere")
- ->EnumAttribute(DefaultShapeType::Asset, "Mesh Asset")
- ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::OnDefaultShapeChange)
- ->DataElement(
- AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_editorMeshAsset, "Editor Mesh Asset",
- "Editor Mesh Asset")
- ->Attribute(AZ::Edit::Attributes::Visibility, &EditorWhiteBoxComponent::AssetVisibility)
- ->UIElement(AZ::Edit::UIHandlers::Button, "Save as asset", "Save as asset")
- ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::SaveAsAsset)
- ->Attribute(AZ::Edit::Attributes::ButtonText, "Save As ...")
- ->DataElement(
- AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_material, "White Box Material",
- "The properties of the White Box material.")
- ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::OnMaterialChange)
- ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
- ->DataElement(
- AZ::Edit::UIHandlers::Default, &EditorWhiteBoxComponent::m_componentModeDelegate,
- "Component Mode", "White Box Tool Component Mode")
- ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
- ->UIElement(AZ::Edit::UIHandlers::Button, "", "Export to obj")
- ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::ExportToFile)
- ->Attribute(AZ::Edit::Attributes::ButtonText, "Export")
- ->UIElement(AZ::Edit::UIHandlers::Button, "", "Export all whiteboxes on descendant entities as a single obj (excluding this one)")
- ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorWhiteBoxComponent::ExportDescendantsToFile)
- ->Attribute(AZ::Edit::Attributes::ButtonText, "Export Descendants")
- ->DataElement(
- AZ::Edit::UIHandlers::Default,
- &EditorWhiteBoxComponent::m_flipYZForExport,
- "Flip Y and Z for Export",
- "Flip the Y and Z axes when exportings so they aren't imported sideways into coord systems where the Y-axis goes up.");
- }
- }
- }
- void EditorWhiteBoxComponent::OnMaterialChange()
- {
- if (m_renderMesh.has_value())
- {
- (*m_renderMesh)->UpdateMaterial(m_material);
- m_renderData.m_material = m_material;
- }
- }
- AZ::Crc32 EditorWhiteBoxComponent::AssetVisibility() const
- {
- return DisplayingAsset(m_defaultShape) ? AZ::Edit::PropertyVisibility::ShowChildrenOnly
- : AZ::Edit::PropertyVisibility::Hide;
- }
- void EditorWhiteBoxComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
- {
- required.push_back(AZ_CRC_CE("TransformService"));
- }
- void EditorWhiteBoxComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
- {
- provided.push_back(AZ_CRC_CE("WhiteBoxService"));
- }
- void EditorWhiteBoxComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
- {
- incompatible.push_back(AZ_CRC_CE("NonUniformScaleService"));
- incompatible.push_back(AZ_CRC_CE("MeshService"));
- incompatible.push_back(AZ_CRC_CE("WhiteBoxService"));
- }
- EditorWhiteBoxComponent::EditorWhiteBoxComponent() = default;
- EditorWhiteBoxComponent::~EditorWhiteBoxComponent()
- {
- // note: m_editorMeshAsset is (usually) serialized so it is created by the reflection system
- // in Reflect (no explicit `new`) - we must still clean-up the resource on destruction though
- // to not leak resources.
- delete m_editorMeshAsset;
- }
- void EditorWhiteBoxComponent::Init()
- {
- if (m_editorMeshAsset)
- {
- return;
- }
- // if the m_editorMeshAsset has not been created by the serialization system
- // create a new EditorWhiteBoxMeshAsset here
- m_editorMeshAsset = aznew EditorWhiteBoxMeshAsset();
- }
- void EditorWhiteBoxComponent::Activate()
- {
- const AZ::EntityId entityId = GetEntityId();
- const AZ::EntityComponentIdPair entityComponentIdPair{entityId, GetId()};
- AzToolsFramework::Components::EditorComponentBase::Activate();
- EditorWhiteBoxComponentRequestBus::Handler::BusConnect(entityComponentIdPair);
- EditorWhiteBoxComponentNotificationBus::Handler::BusConnect(entityComponentIdPair);
- AZ::TransformNotificationBus::Handler::BusConnect(entityId);
- AzFramework::BoundsRequestBus::Handler::BusConnect(entityId);
- AzFramework::VisibleGeometryRequestBus::Handler::BusConnect(entityId);
- AzFramework::EntityDebugDisplayEventBus::Handler::BusConnect(entityId);
- AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(entityId);
- AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusConnect(entityId);
- m_componentModeDelegate.ConnectWithSingleComponentMode<EditorWhiteBoxComponent, EditorWhiteBoxComponentMode>(
- entityComponentIdPair, this);
- m_worldFromLocal = AZ::Transform::CreateIdentity();
- AZ::TransformBus::EventResult(m_worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM);
- m_editorMeshAsset->Associate(entityComponentIdPair);
- // deserialize the white box data into a mesh object or load the serialized asset ref
- DeserializeWhiteBox();
- if (AzToolsFramework::IsEntityVisible(entityId))
- {
- ShowRenderMesh();
- OnMaterialChange();
- }
- }
- void EditorWhiteBoxComponent::Deactivate()
- {
- AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusDisconnect();
- AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusDisconnect();
- AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
- AzFramework::VisibleGeometryRequestBus::Handler::BusDisconnect();
- AzFramework::BoundsRequestBus::Handler::BusDisconnect();
- AZ::TransformNotificationBus::Handler::BusDisconnect();
- EditorWhiteBoxComponentRequestBus::Handler::BusDisconnect();
- EditorWhiteBoxComponentNotificationBus::Handler::BusDisconnect();
- AzToolsFramework::Components::EditorComponentBase::Deactivate();
- m_componentModeDelegate.Disconnect();
- m_editorMeshAsset->Release();
- m_renderMesh.reset();
- m_whiteBox.reset();
- }
- void EditorWhiteBoxComponent::DeserializeWhiteBox()
- {
- // create WhiteBoxMesh object from internal data
- m_whiteBox = Api::CreateWhiteBoxMesh();
- if (m_editorMeshAsset->InUse())
- {
- m_editorMeshAsset->Load();
- }
- else
- {
- // attempt to load the mesh
- const auto result = Api::ReadMesh(*m_whiteBox, m_whiteBoxData);
- AZ_Error("EditorWhiteBoxComponent", result != WhiteBox::Api::ReadResult::Error, "Error deserializing white box mesh stream");
- // if the read was successful but the byte stream is empty
- // (there was nothing to load), create a default mesh
- if (result == Api::ReadResult::Empty)
- {
- Api::InitializeAsUnitCube(*m_whiteBox);
- }
- }
- }
- void EditorWhiteBoxComponent::RebuildWhiteBox()
- {
- RebuildRenderMesh();
- RebuildPhysicsMesh();
- }
- void EditorWhiteBoxComponent::BuildGameEntity(AZ::Entity* gameEntity)
- {
- if (auto* whiteBoxComponent = gameEntity->CreateComponent<WhiteBoxComponent>())
- {
- // note: it is important no edit time only functions are called here as BuildGameEntity
- // will be called by the Asset Processor when creating dynamic slices
- whiteBoxComponent->GenerateWhiteBoxMesh(m_renderData);
- }
- }
- WhiteBoxMesh* EditorWhiteBoxComponent::GetWhiteBoxMesh()
- {
- if (WhiteBoxMesh* whiteBox = m_editorMeshAsset->GetWhiteBoxMesh())
- {
- return whiteBox;
- }
- return m_whiteBox.get();
- }
- void EditorWhiteBoxComponent::OnWhiteBoxMeshModified()
- {
- // if using an asset, notify other editor mesh assets using the same id that
- // the asset has been modified, this will in turn cause all components to update
- // their render and physics meshes
- if (m_editorMeshAsset->InUse())
- {
- WhiteBoxMeshAssetNotificationBus::Event(
- m_editorMeshAsset->GetWhiteBoxMeshAssetId(),
- &WhiteBoxMeshAssetNotificationBus::Events::OnWhiteBoxMeshAssetModified,
- m_editorMeshAsset->GetWhiteBoxMeshAsset());
- }
- // otherwise, update the render and physics mesh immediately
- else
- {
- RebuildWhiteBox();
- }
- }
- void EditorWhiteBoxComponent::RebuildRenderMesh()
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- // reset caches when the mesh changes
- m_worldAabb.reset();
- m_localAabb.reset();
- m_faces.reset();
- AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(GetEntityId());
- // must have been created in Activate or have had the Entity made visible again
- if (m_renderMesh.has_value())
- {
- // cache the white box render data
- m_renderData = CreateWhiteBoxRenderData(*GetWhiteBoxMesh(), m_material);
- // it's possible the white box mesh data isn't yet ready (for example if it's stored
- // in an asset which hasn't finished loading yet) so don't attempt to create a render
- // mesh with no data
- if (!m_renderData.m_faces.empty())
- {
- // check if we need to instantiate a concrete render mesh implementation
- if (IsWhiteBoxNullRenderMesh(m_renderMesh))
- {
- // create a concrete implementation of the render mesh
- WhiteBoxRequestBus::BroadcastResult(m_renderMesh, &WhiteBoxRequests::CreateRenderMeshInterface, GetEntityId());
- }
- // generate the mesh
- (*m_renderMesh)->BuildMesh(m_renderData, m_worldFromLocal);
- OnMaterialChange();
- }
- }
- EditorWhiteBoxComponentModeRequestBus::Event(
- AZ::EntityComponentIdPair{GetEntityId(), GetId()},
- &EditorWhiteBoxComponentModeRequests::MarkWhiteBoxIntersectionDataDirty);
- }
- void EditorWhiteBoxComponent::WriteAssetToComponent()
- {
- if (m_editorMeshAsset->Loaded())
- {
- Api::WriteMesh(*m_editorMeshAsset->GetWhiteBoxMesh(), m_whiteBoxData);
- }
- }
- void EditorWhiteBoxComponent::SerializeWhiteBox()
- {
- if (m_editorMeshAsset->Loaded())
- {
- m_editorMeshAsset->Serialize();
- }
- else
- {
- Api::WriteMesh(*m_whiteBox, m_whiteBoxData);
- }
- }
- void EditorWhiteBoxComponent::SetDefaultShape(const DefaultShapeType defaultShape)
- {
- m_defaultShape = defaultShape;
- OnDefaultShapeChange();
- }
- void EditorWhiteBoxComponent::OnTransformChanged(
- [[maybe_unused]] const AZ::Transform& local, const AZ::Transform& world)
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- m_worldAabb.reset();
- m_localAabb.reset();
- m_worldFromLocal = world;
- if (m_renderMesh.has_value())
- {
- (*m_renderMesh)->UpdateTransform(world);
- }
- }
- void EditorWhiteBoxComponent::RebuildPhysicsMesh()
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- EditorWhiteBoxColliderRequestBus::Event(
- GetEntityId(), &EditorWhiteBoxColliderRequests::CreatePhysics, *GetWhiteBoxMesh());
- }
- static AZStd::string WhiteBoxPathAtProjectRoot(const AZStd::string_view name, const AZStd::string_view extension)
- {
- AZ::IO::Path whiteBoxPath;
- if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
- {
- settingsRegistry->Get(whiteBoxPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath);
- }
- whiteBoxPath /= AZ::IO::FixedMaxPathString::format("%.*s.%.*s", AZ_STRING_ARG(name), AZ_STRING_ARG(extension));
- return whiteBoxPath.Native();
- }
- void EditorWhiteBoxComponent::ExportToFile()
- {
- const AZStd::string initialAbsolutePathToExport =
- WhiteBoxPathAtProjectRoot(GetEntity()->GetName(), ObjExtension);
- const QString fileFilter = AZStd::string::format("*.%s", ObjExtension).c_str();
- const QString absoluteSaveFilePath = AzQtComponents::FileDialog::GetSaveFileName(
- nullptr, "Save As...", QString(initialAbsolutePathToExport.c_str()), fileFilter);
- if (m_flipYZForExport)
- {
- Api::VertexHandles vHandles = Api::MeshVertexHandles(*GetWhiteBoxMesh());
- for (auto& handle : vHandles)
- {
- AZ::Vector3 p = Api::VertexPosition(*GetWhiteBoxMesh(), handle);
- float temp = p.GetY();
- p.SetY(p.GetZ());
- p.SetZ(-temp);
- Api::SetVertexPosition(*GetWhiteBoxMesh(), handle, p);
- }
- }
- const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
- const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
- if (WhiteBox::Api::SaveToObj(*GetWhiteBoxMesh(), absoluteSaveFilePathCstr))
- {
- AZ_Printf("EditorWhiteBoxComponent", "Exported white box mesh to: %s", absoluteSaveFilePathCstr);
- RequestEditSourceControl(absoluteSaveFilePathCstr);
- }
- else
- {
- AZ_Warning(
- "EditorWhiteBoxComponent", false, "Failed to export white box mesh to: %s", absoluteSaveFilePathCstr);
- }
- }
- void EditorWhiteBoxComponent::ExportDescendantsToFile()
- {
- // Get all child entities in the viewport
- AzToolsFramework::EntityIdList children;
- AZ::TransformBus::EventResult(children, GetEntityId(), &AZ::TransformBus::Events::GetAllDescendants);
- if (children.empty())
- {
- AZ_Warning("EditorWhiteBoxComponent", false, "Failed to export descendant whitebox meshes: No descendant entities found.");
- return;
- }
- const AZStd::string initialAbsolutePathToExport = WhiteBoxPathAtProjectRoot(GetEntity()->GetName(), ObjExtension);
- const QString fileFilter = AZStd::string::format("*.%s", ObjExtension).c_str();
- const QString absoluteSaveFilePath =
- AzQtComponents::FileDialog::GetSaveFileName(nullptr, "Save As...", QString(initialAbsolutePathToExport.c_str()), fileFilter);
- // Create a new empty white box mesh
- Api::WhiteBoxMeshPtr mesh = Api::CreateWhiteBoxMesh();
- for (auto& id : children)
- {
- AZ::Entity* e;
- AZ::ComponentApplicationBus::BroadcastResult(e, &AZ::ComponentApplicationRequests::FindEntity, id);
- AZ::Transform worldTM = e->GetTransform()->GetWorldTM();
- // Add all polys from selected white boxes
- for (auto component : e->FindComponents<EditorWhiteBoxComponent>())
- {
- WhiteBoxMesh* m = component->GetWhiteBoxMesh();
- Api::PolygonHandles polys = Api::MeshPolygonHandles(*m);
- for (auto& poly : polys)
- {
- AZStd::vector<AZ::Vector3> verts = Api::PolygonVertexPositions(*m, poly);
- if (verts.size() == 4) // if this is in fact a quad
- {
- Api::VertexHandle vertexHandles[4];
- for (unsigned int i = 0; i < 4; i++)
- {
- AZ::Vector3 worldV = worldTM.TransformPoint(verts[i]);
- if (m_flipYZForExport)
- {
- float temp = worldV.GetY();
- worldV.SetY(worldV.GetZ());
- worldV.SetZ(-temp);
- }
- vertexHandles[i] = Api::AddVertex(*mesh.get(), worldV);
- }
- Api::AddQuadPolygon(*mesh.get(), vertexHandles[0], vertexHandles[1], vertexHandles[2], vertexHandles[3]);
- }
- }
- }
- }
- Api::CalculateNormals(*mesh.get());
- Api::CalculatePlanarUVs(*mesh.get());
- const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
- const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
- if (WhiteBox::Api::SaveToObj(*mesh.get(), absoluteSaveFilePathCstr))
- {
- AZ_Printf("EditorWhiteBoxComponent", "Exported white box mesh to: %s", absoluteSaveFilePathCstr);
- RequestEditSourceControl(absoluteSaveFilePathCstr);
- }
- else
- {
- AZ_Warning("EditorWhiteBoxComponent", false, "Failed to export white box mesh to: %s", absoluteSaveFilePathCstr);
- }
- }
- AZStd::optional<WhiteBoxSaveResult> TrySaveAs(
- const AZStd::string_view entityName,
- const AZStd::function<AZStd::string(const AZStd::string&)>& absoluteSavePathFn,
- const AZStd::function<AZStd::optional<AZStd::string>(const AZStd::string&)>& relativePathFn,
- const AZStd::function<int()>& saveDecisionFn)
- {
- const AZStd::string initialAbsolutePathToSave =
- WhiteBoxPathAtProjectRoot(entityName, Pipeline::WhiteBoxMeshAssetHandler::AssetFileExtension);
- const QString absoluteSaveFilePath = QString(absoluteSavePathFn(initialAbsolutePathToSave).c_str());
- // user pressed cancel
- if (absoluteSaveFilePath.isEmpty())
- {
- return AZStd::nullopt;
- }
- const auto absoluteSaveFilePathUtf8 = absoluteSaveFilePath.toUtf8();
- const auto absoluteSaveFilePathCstr = absoluteSaveFilePathUtf8.constData();
- const AZStd::optional<AZStd::string> relativePath =
- relativePathFn(AZStd::string(absoluteSaveFilePathCstr, absoluteSaveFilePathUtf8.length()));
- if (!relativePath.has_value())
- {
- int saveDecision = saveDecisionFn();
- // save the file but do not attempt to create an asset
- if (saveDecision == QMessageBox::Save)
- {
- return WhiteBoxSaveResult{AZStd::nullopt, AZStd::string(absoluteSaveFilePathCstr)};
- }
- // the user decided not to save the asset outside the project folder after the prompt
- return AZStd::nullopt;
- }
- return WhiteBoxSaveResult{relativePath, AZStd::string(absoluteSaveFilePathCstr)};
- }
- AZ::Crc32 EditorWhiteBoxComponent::SaveAsAsset()
- {
- // let the user select final location of the saved asset
- const auto absoluteSavePathFn = [](const AZStd::string& initialAbsolutePath)
- {
- const QString fileFilter =
- AZStd::string::format("WhiteBoxMesh (*.%s)", Pipeline::WhiteBoxMeshAssetHandler::AssetFileExtension).c_str();
- const QString absolutePath =
- AzQtComponents::FileDialog::GetSaveFileName(nullptr, "Save As Asset...", QString(initialAbsolutePath.c_str()), fileFilter);
- return AZStd::string(absolutePath.toUtf8());
- };
- // ask the asset system to try and convert the absolutePath to a cache relative path
- const auto relativePathFn = [](const AZStd::string& absolutePath) -> AZStd::optional<AZStd::string>
- {
- AZStd::string relativePath;
- bool foundRelativePath = false;
- AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
- foundRelativePath,
- &AzToolsFramework::AssetSystem::AssetSystemRequest::GetRelativeProductPathFromFullSourceOrProductPath,
- absolutePath,
- relativePath);
- if (foundRelativePath)
- {
- return relativePath;
- }
- return AZStd::nullopt;
- };
- // present the user with the option of accepting saving outside the project folder or allow them to cancel the
- // operation
- const auto saveDecisionFn = []()
- {
- return QMessageBox::warning(
- AzToolsFramework::GetActiveWindow(),
- "Warning",
- "Saving a White Box Mesh Asset (.wbm) outside of the project root will not create an Asset for the "
- "Component to use. The file will be saved but will not be processed. For live updates to happen the "
- "asset must be saved somewhere in the current project folder. Would you like to continue?",
- (QMessageBox::Save | QMessageBox::Cancel),
- QMessageBox::Cancel);
- };
- const AZStd::optional<WhiteBoxSaveResult> saveResult =
- TrySaveAs(GetEntity()->GetName(), absoluteSavePathFn, relativePathFn, saveDecisionFn);
- // user pressed cancel
- if (!saveResult.has_value())
- {
- return AZ::Edit::PropertyRefreshLevels::None;
- }
- const char* const absoluteSaveFilePath = saveResult.value().m_absoluteFilePath.c_str();
- if (saveResult.value().m_relativeAssetPath.has_value())
- {
- const auto& relativeAssetPath = saveResult.value().m_relativeAssetPath.value();
- // notify undo system the entity has been changed (m_meshAsset)
- AzToolsFramework::ScopedUndoBatch undoBatch(AssetSavedUndoRedoDesc);
- // if there was a previous asset selected, it has to be cloned to a new one
- // otherwise the internal mesh can simply be moved into the new asset
- m_editorMeshAsset->TakeOwnershipOfWhiteBoxMesh(
- relativeAssetPath,
- m_editorMeshAsset->InUse() ? Api::CloneMesh(*GetWhiteBoxMesh()) : AZStd::exchange(m_whiteBox, Api::CreateWhiteBoxMesh()));
- // change default shape to asset
- m_defaultShape = DefaultShapeType::Asset;
- // ensure this change gets tracked
- undoBatch.MarkEntityDirty(GetEntityId());
- RefreshProperties();
- m_editorMeshAsset->Save(absoluteSaveFilePath);
- }
- else
- {
- // save the asset to disk outside the project folder
- if (Api::SaveToWbm(*GetWhiteBoxMesh(), absoluteSaveFilePath))
- {
- RequestEditSourceControl(absoluteSaveFilePath);
- }
- }
- return AZ::Edit::PropertyRefreshLevels::EntireTree;
- }
- template<typename TransformFn>
- AZ::Aabb CalculateAabb(const WhiteBoxMesh& whiteBox, TransformFn&& transformFn)
- {
- const auto vertexHandles = Api::MeshVertexHandles(whiteBox);
- return AZStd::accumulate(
- AZStd::cbegin(vertexHandles), AZStd::cend(vertexHandles), AZ::Aabb::CreateNull(), transformFn);
- }
- AZ::Aabb EditorWhiteBoxComponent::GetEditorSelectionBoundsViewport(
- [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo)
- {
- return GetWorldBounds();
- }
- AZ::Aabb EditorWhiteBoxComponent::GetWorldBounds() const
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- if (!m_worldAabb.has_value())
- {
- m_worldAabb = GetLocalBounds();
- m_worldAabb->ApplyTransform(m_worldFromLocal);
- }
- return m_worldAabb.value();
- }
- AZ::Aabb EditorWhiteBoxComponent::GetLocalBounds() const
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- if (!m_localAabb.has_value())
- {
- auto& whiteBoxMesh = *const_cast<EditorWhiteBoxComponent*>(this)->GetWhiteBoxMesh();
- m_localAabb = CalculateAabb(
- whiteBoxMesh,
- [&whiteBox = whiteBoxMesh](AZ::Aabb aabb, const Api::VertexHandle vertexHandle)
- {
- aabb.AddPoint(Api::VertexPosition(whiteBox, vertexHandle));
- return aabb;
- });
- }
- return m_localAabb.value();
- }
- void EditorWhiteBoxComponent::BuildVisibleGeometry(const AZ::Aabb& bounds, AzFramework::VisibleGeometryContainer& geometryContainer) const
- {
- // Only add the white box geometry if its bounds overlap the input bounds
- if (bounds.IsValid() && !bounds.Overlaps(GetWorldBounds()))
- {
- return;
- }
- // Extract white box geometry data to convert to visible geometry vertices and indices
- const WhiteBoxRenderData renderData =
- CreateWhiteBoxRenderData(*const_cast<EditorWhiteBoxComponent*>(this)->GetWhiteBoxMesh(), m_material);
- // Convert the white box render data into visible geometry data
- const AzFramework::VisibleGeometry geometry = BuildVisibleGeometryFromWhiteBoxRenderData(GetEntityId(), renderData);
- if (!geometry.m_indices.empty() && !geometry.m_vertices.empty())
- {
- geometryContainer.push_back(geometry);
- }
- }
- bool EditorWhiteBoxComponent::EditorSelectionIntersectRayViewport(
- [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, const AZ::Vector3& src, const AZ::Vector3& dir,
- float& distance)
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- if (!m_faces.has_value())
- {
- m_faces = Api::MeshFaces(*GetWhiteBoxMesh());
- }
- // must have at least one triangle
- if (m_faces->empty())
- {
- return false;
- }
- // transform ray into local space
- const AZ::Transform localFromWorld = m_worldFromLocal.GetInverse();
- // setup beginning/end of segment
- const float rayLength = 1000.0f;
- const AZ::Vector3 localRayOrigin = localFromWorld.TransformPoint(src);
- const AZ::Vector3 localRayDirection = localFromWorld.TransformVector(dir);
- const AZ::Vector3 localRayEnd = localRayOrigin + localRayDirection * rayLength;
- bool intersection = false;
- AZ::Intersect::SegmentTriangleHitTester hitTester(localRayOrigin, localRayEnd);
- for (const auto& face : m_faces.value())
- {
- float t;
- AZ::Vector3 normal;
- if (hitTester.IntersectSegmentTriangle(face[0], face[1], face[2], normal, t))
- {
- intersection = true;
- // find closest intersection
- const float dist = t * rayLength;
- if (dist < distance)
- {
- distance = dist;
- }
- }
- }
- return intersection;
- }
- void EditorWhiteBoxComponent::OnEntityVisibilityChanged(const bool visibility)
- {
- if (visibility)
- {
- ShowRenderMesh();
- }
- else
- {
- HideRenderMesh();
- }
- }
- void EditorWhiteBoxComponent::ShowRenderMesh()
- {
- // if we wish to display the render mesh, set a null render mesh indicating a mesh can exist
- // note: if the optional remains empty, no render mesh will be created
- m_renderMesh.emplace(AZStd::make_unique<WhiteBoxNullRenderMesh>(AZ::EntityId{}));
- RebuildRenderMesh();
- }
- void EditorWhiteBoxComponent::HideRenderMesh()
- {
- // clear the optional
- m_renderMesh.reset();
- }
- bool EditorWhiteBoxComponent::AssetInUse() const
- {
- return m_editorMeshAsset->InUse();
- }
- bool EditorWhiteBoxComponent::HasRenderMesh() const
- {
- // if the optional has a value we know a render mesh exists
- // note: This implicitly implies that the Entity is visible
- return m_renderMesh.has_value();
- }
- void EditorWhiteBoxComponent::OverrideEditorWhiteBoxMeshAsset(EditorWhiteBoxMeshAsset* editorMeshAsset)
- {
- // ensure we do not leak resources
- delete m_editorMeshAsset;
- m_editorMeshAsset = editorMeshAsset;
- }
- static bool DebugDrawingEnabled()
- {
- return cl_whiteBoxDebugVertexHandles || cl_whiteBoxDebugNormals || cl_whiteBoxDebugHalfedgeHandles ||
- cl_whiteBoxDebugEdgeHandles || cl_whiteBoxDebugFaceHandles || cl_whiteBoxDebugAabb;
- }
- static void WhiteBoxDebugRendering(
- const WhiteBoxMesh& whiteBoxMesh, const AZ::Transform& worldFromLocal,
- AzFramework::DebugDisplayRequests& debugDisplay, const AZ::Aabb& editorBounds)
- {
- const AZ::Quaternion worldOrientationFromLocal = worldFromLocal.GetRotation();
- debugDisplay.DepthTestOn();
- for (const auto& faceHandle : Api::MeshFaceHandles(whiteBoxMesh))
- {
- const auto faceHalfedgeHandles = Api::FaceHalfedgeHandles(whiteBoxMesh, faceHandle);
- const AZ::Vector3 localFaceCenter =
- AZStd::accumulate(
- faceHalfedgeHandles.cbegin(), faceHalfedgeHandles.cend(), AZ::Vector3::CreateZero(),
- [&whiteBoxMesh](AZ::Vector3 start, const Api::HalfedgeHandle halfedgeHandle)
- {
- return start +
- Api::VertexPosition(
- whiteBoxMesh, Api::HalfedgeVertexHandleAtTip(whiteBoxMesh, halfedgeHandle));
- }) /
- 3.0f;
- for (const auto& halfedgeHandle : faceHalfedgeHandles)
- {
- const Api::VertexHandle vertexHandleAtTip =
- Api::HalfedgeVertexHandleAtTip(whiteBoxMesh, halfedgeHandle);
- const Api::VertexHandle vertexHandleAtTail =
- Api::HalfedgeVertexHandleAtTail(whiteBoxMesh, halfedgeHandle);
- const AZ::Vector3 localTailPoint = Api::VertexPosition(whiteBoxMesh, vertexHandleAtTail);
- const AZ::Vector3 localTipPoint = Api::VertexPosition(whiteBoxMesh, vertexHandleAtTip);
- const AZ::Vector3 localFaceNormal = Api::FaceNormal(whiteBoxMesh, faceHandle);
- const AZ::Vector3 localHalfedgeCenter = (localTailPoint + localTipPoint) * 0.5f;
- // offset halfedge slightly based on the face it is associated with
- const AZ::Vector3 localHalfedgePositionWithOffset =
- localHalfedgeCenter + ((localFaceCenter - localHalfedgeCenter).GetNormalized() * 0.1f);
- const AZ::Vector3 worldVertexPosition = worldFromLocal.TransformPoint(localTipPoint);
- const AZ::Vector3 worldHalfedgePosition =
- worldFromLocal.TransformPoint(localHalfedgePositionWithOffset);
- const AZ::Vector3 worldNormal =
- (worldOrientationFromLocal.TransformVector(localFaceNormal)).GetNormalized();
- if (cl_whiteBoxDebugVertexHandles)
- {
- debugDisplay.SetColor(AZ::Colors::Cyan);
- const AZStd::string vertex = AZStd::string::format("%d", vertexHandleAtTip.Index());
- debugDisplay.DrawTextLabel(worldVertexPosition, 3.0f, vertex.c_str(), true, 0, 1);
- }
- if (cl_whiteBoxDebugHalfedgeHandles)
- {
- debugDisplay.SetColor(AZ::Colors::LawnGreen);
- const AZStd::string halfedge = AZStd::string::format("%d", halfedgeHandle.Index());
- debugDisplay.DrawTextLabel(worldHalfedgePosition, 2.0f, halfedge.c_str(), true);
- }
- if (cl_whiteBoxDebugNormals)
- {
- debugDisplay.SetColor(AZ::Colors::White);
- debugDisplay.DrawBall(worldVertexPosition, 0.025f);
- debugDisplay.DrawLine(worldVertexPosition, worldVertexPosition + worldNormal * 0.4f);
- }
- }
- if (cl_whiteBoxDebugFaceHandles)
- {
- debugDisplay.SetColor(AZ::Colors::White);
- const AZ::Vector3 worldFacePosition = worldFromLocal.TransformPoint(localFaceCenter);
- const AZStd::string face = AZStd::string::format("%d", faceHandle.Index());
- debugDisplay.DrawTextLabel(worldFacePosition, 2.0f, face.c_str(), true);
- }
- }
- if (cl_whiteBoxDebugEdgeHandles)
- {
- for (const auto& edgeHandle : Api::MeshEdgeHandles(whiteBoxMesh))
- {
- const AZ::Vector3 localEdgeMidpoint = Api::EdgeMidpoint(whiteBoxMesh, edgeHandle);
- const AZ::Vector3 worldEdgeMidpoint = worldFromLocal.TransformPoint(localEdgeMidpoint);
- debugDisplay.SetColor(AZ::Colors::CornflowerBlue);
- const AZStd::string edge = AZStd::string::format("%d", edgeHandle.Index());
- debugDisplay.DrawTextLabel(worldEdgeMidpoint, 2.0f, edge.c_str(), true);
- }
- }
- if (cl_whiteBoxDebugAabb)
- {
- debugDisplay.SetColor(AZ::Colors::Blue);
- debugDisplay.DrawWireBox(editorBounds.GetMin(), editorBounds.GetMax());
- }
- }
- void EditorWhiteBoxComponent::DisplayEntityViewport(
- [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
- {
- AZ_PROFILE_FUNCTION(AzToolsFramework);
- if (DebugDrawingEnabled())
- {
- WhiteBoxDebugRendering(
- *GetWhiteBoxMesh(), m_worldFromLocal, debugDisplay,
- GetEditorSelectionBoundsViewport(AzFramework::ViewportInfo{}));
- }
- }
- } // namespace WhiteBox
|