| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013 |
- /*
- * 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/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::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::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();
- }
- 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
|