Gems/Umbra/Code/Source/UmbraSceneComponent/EditorUmbraSceneComponent.cpp (257 lines of code) (raw):
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*/
#include <AzCore/Math/Matrix4x4.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzFramework/Visibility/VisibleGeometryBus.h>
#include <AzQtComponents/Components/Widgets/FileDialog.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
#include <Umbra/UmbraObjectComponent/UmbraObjectComponentBus.h>
#include <Umbra/UmbraSceneAsset/UmbraSceneAsset.h>
#include <Umbra/UmbraViewVolumeComponent/UmbraViewVolumeComponentBus.h>
#include <UmbraSceneAsset/UmbraSceneDescriptor.h>
#include <UmbraSceneComponent/EditorUmbraSceneComponent.h>
#include <optimizer/umbraScene.hpp>
#include <umbraInfo.hpp>
#include <QMessageBox>
#include <QObject>
namespace Umbra
{
void EditorUmbraSceneComponent::Reflect(AZ::ReflectContext* context)
{
BaseClass::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorUmbraSceneComponent, BaseClass>()
->Version(0)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorUmbraSceneComponent>(
"Umbra Scene", "This level component allows exporting scene geometry and managing occlusion culling requests with Umbra.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Umbra")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Component_Placeholder.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/Component_Placeholder.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Level"))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://umbra3d.com/")
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, AZ::AzTypeInfo<Umbra::UmbraSceneAsset>::Uuid())
->UIElement(AZ::Edit::UIHandlers::Button, ExportUmbraSceneButtonText, ExportUmbraSceneToolTipText)
->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
->Attribute(AZ::Edit::Attributes::ButtonText, ExportUmbraSceneButtonText)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorUmbraSceneComponent::ExportUmbraSceneFromUI)
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->ConstantProperty("EditorUmbraSceneComponentTypeId", BehaviorConstant(AZ::Uuid(EditorUmbraSceneComponentTypeId)))
->Attribute(AZ::Script::Attributes::Module, "umbra")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
;
behaviorContext->EBus<EditorUmbraSceneComponentRequestBus>("EditorUmbraSceneComponentRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Umbra")
->Attribute(AZ::Script::Attributes::Module, "umbra")
->Event("ExportUmbraScene", &EditorUmbraSceneComponentRequestBus::Events::ExportUmbraScene)
->Event("ShouldExportEntity", &EditorUmbraSceneComponentRequestBus::Events::ShouldExportEntity)
;
}
}
EditorUmbraSceneComponent::EditorUmbraSceneComponent(const UmbraSceneComponentConfig& sceneComponentConfig)
: BaseClass(sceneComponentConfig)
{
}
void EditorUmbraSceneComponent::Activate()
{
BaseClass::Activate();
EditorUmbraSceneComponentRequestBus::Handler::BusConnect(GetEntityId());
m_contextId = GetEntityContextId(GetEntityId());
}
void EditorUmbraSceneComponent::Deactivate()
{
EditorUmbraSceneComponentRequestBus::Handler::BusDisconnect();
BaseClass::Deactivate();
}
AZ::u32 EditorUmbraSceneComponent::ExportUmbraSceneFromUI()
{
// Before prompting the user for a scene file path, initialize it with the level name
AZStd::string levelName;
AzToolsFramework::EditorRequestBus::BroadcastResult(levelName, &AzToolsFramework::EditorRequestBus::Events::GetLevelName);
AZStd::to_lower(levelName.begin(), levelName.end());
AZStd::string scenePathSeed = AZ::Utils::GetProjectPath().c_str();
if (!levelName.empty())
{
scenePathSeed += "/";
scenePathSeed += levelName;
scenePathSeed += ".umbrascene";
}
// Prompt the user for a path to save the umbra scene
const AZStd::string scenePath = AzQtComponents::FileDialog::GetSaveFileName(
AzToolsFramework::GetActiveWindow(),
QString("Select Umbra Scene File Path"),
scenePathSeed.c_str(),
QString("Umbra Scene (*.umbrascene)"),
nullptr).toUtf8().constData();
if (!scenePath.empty())
{
if (!ExportUmbraScene(scenePath))
{
QMessageBox::critical(
AzToolsFramework::GetActiveWindow(),
QObject::tr("Umbra Scene Not Saved"),
QObject::tr("Failed to save umbra scene from level entities."));
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
bool sceneAssetFound = false;
AZ::Data::AssetInfo sceneAssetInfo;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
sceneAssetFound,
&AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
scenePath.c_str(),
sceneAssetInfo,
watchFolder);
if (!sceneAssetFound)
{
QMessageBox::critical(
AzToolsFramework::GetActiveWindow(),
QObject::tr("Umbra Scene Asset Not Assigned"),
QObject::tr("Failed to assign the umbra scene asset generated from level entities after saving."));
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
AzToolsFramework::ScopedUndoBatch undoBatch("Setting scene asset.");
SetDirty();
UmbraSceneComponentRequestBus::Event(
GetEntityId(),
&UmbraSceneComponentRequestBus::Events::SetSceneAssetId,
AZ::Data::AssetId(sceneAssetInfo.m_assetId.m_guid, 0));
}
return AZ::Edit::PropertyRefreshLevels::EntireTree;
}
bool EditorUmbraSceneComponent::ExportUmbraScene(const AZStd::string& scenePath) const
{
if (scenePath.empty() || AZ::StringFunc::Path::IsRelative(scenePath.c_str()) ||
!AZ::StringFunc::Path::IsExtension(scenePath.c_str(), "umbrascene"))
{
AZ_Error("Umbra", false, "Scene export path is not valid: %s", scenePath.c_str());
return false;
}
// Create an umbra scene
Umbra::Scene* scene = Umbra::Scene::create();
// Copy the global scene settings from the scene component into the scene descriptor.
UmbraSceneDescriptor sceneDescriptor;
sceneDescriptor.m_sceneSettings = m_controller.GetConfiguration().m_sceneSettings;
// Enumerate every component connected to the visible geometry request bus and add all of the static geometry to the scene.
AzFramework::VisibleGeometryRequestBus::EnumerateHandlers(
[&sceneDescriptor, scene, this](AzFramework::VisibleGeometryRequestBus::Events* handler) -> bool
{
const AZ::EntityId entityId = *(AzFramework::VisibleGeometryRequestBus::GetCurrentBusId());
if (!ShouldExportEntity(entityId))
{
return true;
}
// Override the default scene object settings and flags with values from the umbra object component if one is present.
uint32_t flags = Umbra::SceneObject::OCCLUDER | Umbra::SceneObject::TARGET;
UmbraObjectComponentRequestBus::Event(entityId, [&flags](UmbraObjectComponentRequestBus::Events* handler) {
flags = 0;
flags |= (handler->GetCanOcclude() ? Umbra::SceneObject::OCCLUDER : 0);
flags |= (handler->GetCanBeOccluded() ? Umbra::SceneObject::TARGET : 0);
});
// Enumerate all visible geometry parts from components attached to this entity.
AzFramework::VisibleGeometryContainer geometryContainer;
handler->BuildVisibleGeometry(AZ::Aabb::CreateNull(), geometryContainer);
// Add each visible geometry part from the container to the umbra scene with a unique part ID.
uint32_t partId = 0;
for (const auto& geometry : geometryContainer)
{
const uint32_t objectIndex = aznumeric_cast<uint32_t>(sceneDescriptor.m_objectDescriptors.size());
// Create and umbra model using the triangle list from the visible geometry.
// TODO Implement a mechanism to reuse shared model data if shapes or multiple instances of the same model are included in the level.
const Umbra::SceneModel* model = scene->insertModel(
geometry.m_vertices.data(),
geometry.m_indices.data(),
aznumeric_cast<int>(geometry.m_vertices.size() / 3),
aznumeric_cast<int>(geometry.m_indices.size() / 3));
Umbra::Matrix4x4 transform = {};
geometry.m_transform.StoreToRowMajorFloat16(reinterpret_cast<float*>(transform.m));
// Transparent geometry will automatically be excluded as occluders.
if (geometry.m_transparent)
{
flags &= ~Umbra::SceneObject::OCCLUDER;
}
scene->insertObject(model, transform, objectIndex, flags, Umbra::MF_ROW_MAJOR);
UmbraObjectDescriptor objectDescriptor;
objectDescriptor.m_entityId = entityId;
objectDescriptor.m_partId = partId++;
sceneDescriptor.m_objectDescriptors.emplace_back(AZStd::move(objectDescriptor));
}
return true;
});
// Enumerate all view volumes and add them to the umbra seems to define the area where the camera is expected to move.
UmbraViewVolumeComponentRequestBus::EnumerateHandlers(
[&sceneDescriptor, scene, this]([[maybe_unused]] UmbraViewVolumeComponentRequestBus::Events* handler) -> bool
{
const AZ::EntityId entityId = *(UmbraViewVolumeComponentRequestBus::GetCurrentBusId());
if (!ShouldExportEntity(entityId))
{
return true;
}
// Use the entity world bounds to determine the bounding box for the view volume.
AZ::Aabb bounds = AZ::Aabb::CreateNull();
AzFramework::BoundsRequestBus::EventResult(bounds, entityId, &AzFramework::BoundsRequestBus::Events::GetWorldBounds);
if (bounds.IsValid() && bounds.IsFinite())
{
const uint32_t objectIndex = aznumeric_cast<uint32_t>(sceneDescriptor.m_objectDescriptors.size());
Umbra::Vector3 min = {};
bounds.GetMin().StoreToFloat3(min.v);
Umbra::Vector3 max = {};
bounds.GetMax().StoreToFloat3(max.v);
scene->insertViewVolume(min, max, objectIndex);
// If the view volume overrode scene settings then store them in the scene settings map for the builder.
if (handler->GetOverrideSceneSettings())
{
sceneDescriptor.m_sceneSettingsByView[objectIndex] = handler->GetSceneSettings();
}
UmbraObjectDescriptor objectDescriptor;
objectDescriptor.m_entityId = entityId;
objectDescriptor.m_partId = 0;
sceneDescriptor.m_objectDescriptors.emplace_back(AZStd::move(objectDescriptor));
}
return true;
});
// The scene will be written to a file that can be picked up by the asset processor and converted into an umbra tome.
if (!scene->writeToFile(scenePath.c_str()))
{
AZ_Error("Umbra", false, "Failed to save umbra scene: %s", scenePath.c_str());
scene->release();
return false;
}
scene->release();
AZStd::string sceneDescPath = scenePath;
AZ::StringFunc::Path::ReplaceExtension(sceneDescPath, UmbraSceneDescriptor::Extension);
if (!AZ::JsonSerializationUtils::SaveObjectToFile(&sceneDescriptor, sceneDescPath))
{
AZ_Error("Umbra", false, "Failed to save umbra scene descriptor: %s", sceneDescPath.c_str());
return false;
}
return true;
}
bool EditorUmbraSceneComponent::ShouldExportEntity(const AZ::EntityId& entityId) const
{
if (m_contextId != GetEntityContextId(entityId))
{
return false;
}
if (m_controller.GetConfiguration().m_exportStaticObjectsOnly)
{
// Use the transform bus to determine if the object has a static transform.
bool isStatic = false;
AZ::TransformBus::Event(entityId, [&isStatic](AZ::TransformBus::Events* handler) {
isStatic = handler->IsStaticTransform();
});
if (!isStatic)
{
return false;
}
}
return true;
}
AzFramework::EntityContextId EditorUmbraSceneComponent::GetEntityContextId(const AZ::EntityId& entityId) const
{
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(
contextId, entityId, &AzFramework::EntityIdContextQueryBus::Events::GetOwningContextId);
return contextId;
}
} // namespace Umbra