/******************************************************************************* The content of this file includes portions of the proprietary AUDIOKINETIC Wwise Technology released in source code form as part of the game integration package. The content of this file may not be used without valid licenses to the AUDIOKINETIC Wwise Technology. Note that the use of the game engine is subject to the Unreal(R) Engine End User License Agreement at https://www.unrealengine.com/en-US/eula/unreal License Usage Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use this file in accordance with the end user license agreement provided with the software or, alternatively, in accordance with the terms contained in a written agreement between you and Audiokinetic Inc. Copyright (c) 2023 Audiokinetic Inc. *******************************************************************************/ #include "AkSurfaceReflectorSetDetailsCustomization.h" #include "AkComponent.h" #include "AkSurfaceReflectorSetComponent.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "EditorModeManager.h" #include "EditorSupportDelegates.h" #include "IPropertyUtilities.h" #include "LevelEditorActions.h" #include "Model.h" #include "GameFramework/Volume.h" #include "UI/SAcousticSurfacesController.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Text/STextBlock.h" #include "Builders/ConeBuilder.h" #include "Builders/CubeBuilder.h" #include "Builders/CurvedStairBuilder.h" #include "Builders/CylinderBuilder.h" #include "Builders/LinearStairBuilder.h" #include "Builders/SpiralStairBuilder.h" #include "Builders/TetrahedronBuilder.h" #define LOCTEXT_NAMESPACE "AudiokineticTools" ////////////////////////////////////////////////////////////////////////// // FAkSurfaceReflectorSetDetailsCustomization FAkSurfaceReflectorSetDetailsCustomization::FAkSurfaceReflectorSetDetailsCustomization() { ReflectorSetBeingCustomized = nullptr; FCoreUObjectDelegates::OnObjectModified.AddRaw(this, &FAkSurfaceReflectorSetDetailsCustomization::OnObjectModified); FEditorSupportDelegates::RedrawAllViewports.AddRaw(this, &FAkSurfaceReflectorSetDetailsCustomization::OnRedrawViewports); } FAkSurfaceReflectorSetDetailsCustomization::~FAkSurfaceReflectorSetDetailsCustomization() { FCoreUObjectDelegates::OnObjectModified.RemoveAll(this); FEditorSupportDelegates::RedrawAllViewports.RemoveAll(this); if (ReflectorSetBeingCustomized.IsValid() && ReflectorSetBeingCustomized->GetOnRefreshDetails()) { if (ReflectorSetBeingCustomized->GetOnRefreshDetails()->IsBoundToObject(this)) { ReflectorSetBeingCustomized->ClearOnRefreshDetails(); } } ReflectorSetBeingCustomized.Reset(); } FReply FAkSurfaceReflectorSetDetailsCustomization::OnEnableEditModeClicked() { FLevelEditorActionCallbacks::OnShowOnlySelectedActors(); GLevelEditorModeTools().ActivateMode(FEditorModeID(TEXT("EM_Geometry")), false); return FReply::Handled(); } FReply FAkSurfaceReflectorSetDetailsCustomization::OnDisableEditModeClicked() { GLevelEditorModeTools().DeactivateMode(FEditorModeID(TEXT("EM_Geometry"))); FLevelEditorActionCallbacks::ExecuteExecCommand(FString(TEXT("ACTOR UNHIDE ALL"))); return FReply::Handled(); } void FAkSurfaceReflectorSetDetailsCustomization::OnObjectModified(UObject* Object) { if (!SelectedObjectModifiedThisFrame) { for (TWeakObjectPtr UObjectPtr : ObjectsBeingCustomized) { if (Object == UObjectPtr.Get()) { SelectedObjectModifiedThisFrame = true; return; } } } } void FAkSurfaceReflectorSetDetailsCustomization::OnRedrawViewports() { if (SelectedObjectModifiedThisFrame && DetailBuilder.IsValid()) { // If there is any user interaction going on, we don't want to refresh the details panel. // (This would interrupt the interaction and make sliders unusable) for (TWeakObjectPtr UObjectPtr : ObjectsBeingCustomized) { if (UAkSurfaceReflectorSetComponent* reflectorSet = Cast(UObjectPtr.Get())) { if (reflectorSet->UserInteractionInProgress) { return; } } } IDetailLayoutBuilder* Layout = nullptr; if (auto LockedDetailBuilder = DetailBuilder.Pin()) { Layout = LockedDetailBuilder.Get(); } if (LIKELY(Layout)) { Layout->ForceRefreshDetails(); } SelectedObjectModifiedThisFrame = false; } } TSharedRef FAkSurfaceReflectorSetDetailsCustomization::MakeInstance() { return MakeShareable(new FAkSurfaceReflectorSetDetailsCustomization()); } void FAkSurfaceReflectorSetDetailsCustomization::CustomizeDetails(const TSharedPtr& InDetailBuilder) { InDetailBuilder->EditCategory("EnableComponent", FText::GetEmpty(), ECategoryPriority::Important); InDetailBuilder->EditCategory("SurfaceReflectorSet", FText::GetEmpty(), ECategoryPriority::TypeSpecific); DetailBuilder = InDetailBuilder; CustomizeDetails(*InDetailBuilder); } void FAkSurfaceReflectorSetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) { InDetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized); auto AcousticPolysPropHandle = InDetailBuilder.GetProperty("AcousticPolys"); InDetailBuilder.HideProperty("AcousticPolys"); bool showGeometrySettings = false; for (int i = 0; i < ObjectsBeingCustomized.Num(); ++i) { auto Component = Cast(ObjectsBeingCustomized[i].Get()); if (Component) { ReflectorSetBeingCustomized = TWeakObjectPtr(Component); if (ReflectorSetBeingCustomized->bEnableSurfaceReflectors) { showGeometrySettings = true; break; } } } if (!showGeometrySettings) InDetailBuilder.HideCategory("SurfaceReflectorSet"); IDetailCategoryBuilder& CategoryBuilder = InDetailBuilder.EditCategory("Surface Properties", FText::GetEmpty(), ECategoryPriority::TypeSpecific); FString enableEditSurfacesTooltip(FString("Enable ") + GEOMETRY_EDIT_DISPLAY_NAME + " and show only selected actors"); FString disableEditSurfacesTooltip(FString("Disable ") + GEOMETRY_EDIT_DISPLAY_NAME + " and show all actors"); CategoryBuilder.HeaderContent ( SNew(SHorizontalBox) + SHorizontalBox::Slot() .HAlign(HAlign_Left) .VAlign(VAlign_Center) .AutoWidth() [ SAssignNew(SelectionInfoLabel, STextBlock) // SelectionInfoLabel is passed in to a SAcousticSurfacesController below, and the selection text is set during its construction. .Text(FText::FromString("")) ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SButton) .Text(FText::FromString("Enable Edit Surfaces ")) .ToolTipText(FText::FromString(enableEditSurfacesTooltip)) .OnClicked(this, &FAkSurfaceReflectorSetDetailsCustomization::OnEnableEditModeClicked) ] + SHorizontalBox::Slot() .AutoWidth() .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SButton) .Text(FText::FromString("Disable Edit Surfaces")) .ToolTipText(FText::FromString(disableEditSurfacesTooltip)) .OnClicked(this, &FAkSurfaceReflectorSetDetailsCustomization::OnDisableEditModeClicked) ] ); // Assume Transmission Loss controls will be hidden int surfacePropertiesControlsHeight = 48; for (TWeakObjectPtr ObjectBeingCustomized : ObjectsBeingCustomized) { UAkSurfaceReflectorSetComponent* reflectorSetComponent = Cast(ObjectBeingCustomized.Get()); if (reflectorSetComponent && reflectorSetComponent->bEnableSurfaceReflectors) { // There is a surface reflector set with bEnableSurfaceReflectors enabled - add room for Transmission Loss controls. surfacePropertiesControlsHeight = 72; break; } } if (auto LockedDetailBuilder = DetailBuilder.Pin()) { FDetailWidgetRow& acousticSurfacesRow = CategoryBuilder.AddCustomRow(AcousticPolysPropHandle->GetPropertyDisplayName()); acousticSurfacesRow.NameContent() [ SNew(SBox) .HeightOverride(surfacePropertiesControlsHeight) [ SNew(SAcousticSurfacesLabels, ObjectsBeingCustomized) ] ]; acousticSurfacesRow.ValueContent() [ SNew(SBox) .HeightOverride(surfacePropertiesControlsHeight) [ SNew(SAcousticSurfacesController, ObjectsBeingCustomized, LockedDetailBuilder) ] ]; } if (ObjectsBeingCustomized.Num() == 1) { auto Component = Cast(ObjectsBeingCustomized[0].Get()); if (Component) { ReflectorSetBeingCustomized = TWeakObjectPtr(Component); SetupGeometryModificationHandlers(); } else { ReflectorSetBeingCustomized.Reset(); UE_LOG(LogAkAudio, Log, TEXT("FAkSurfaceReflectorSetDetailsCustomization::CustomizeDetails: Could not get ObjectsBeingCustomized.")); } } } #define REGISTER_PROPERTY_CHANGED(Class, Property, LockedDetailBuilder) \ auto Property ## Handle = LockedDetailBuilder->GetProperty(GET_MEMBER_NAME_CHECKED(Class, Property), Class::StaticClass(), BrushBuilderName); \ if (Property ## Handle->IsValidHandle()) Property ## Handle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FAkSurfaceReflectorSetDetailsCustomization::OnGeometryChanged)) void FAkSurfaceReflectorSetDetailsCustomization::SetupGeometryModificationHandlers() { if (!ReflectorSetBeingCustomized.IsValid()) { return; } static const FName BrushBuilderName(TEXT("BrushBuilder")); auto ParentBrush = ReflectorSetBeingCustomized->ParentBrush; if(!ParentBrush) return; if (auto LockedDetailBuilder = DetailBuilder.Pin()) { auto EnableHandle = LockedDetailBuilder->GetProperty("bEnableSurfaceReflectors"); EnableHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateSP(this, &FAkSurfaceReflectorSetDetailsCustomization::OnEnableValueChanged)); // This is to detect if the BrushBuilder changed. if (ReflectorSetBeingCustomized->AcousticPolys.Num() != ParentBrush->Nodes.Num()) LockedDetailBuilder->GetPropertyUtilities()->EnqueueDeferredAction(FSimpleDelegate::CreateSP(this, &FAkSurfaceReflectorSetDetailsCustomization::OnGeometryChanged)); } // Need to register to a LOT of different properties, because some change the geometry but don't force a refresh of the details panel AVolume* ParentVolume = Cast(ReflectorSetBeingCustomized->GetOwner()); UClass* BrushBuilderClass = nullptr; if (ParentVolume && ParentVolume->BrushBuilder) { BrushBuilderClass = ParentVolume->BrushBuilder->GetClass(); if (BrushBuilderClass == nullptr) { return; } } if (auto LockedDetailBuilder = DetailBuilder.Pin()) { if (BrushBuilderClass == UConeBuilder::StaticClass()) { REGISTER_PROPERTY_CHANGED(UConeBuilder, Sides, LockedDetailBuilder); REGISTER_PROPERTY_CHANGED(UConeBuilder, Hollow, LockedDetailBuilder); } else if (BrushBuilderClass == UCubeBuilder::StaticClass()) { REGISTER_PROPERTY_CHANGED(UCubeBuilder, Hollow, LockedDetailBuilder); REGISTER_PROPERTY_CHANGED(UCubeBuilder, Tessellated, LockedDetailBuilder); } else if (BrushBuilderClass == UCurvedStairBuilder::StaticClass()) { REGISTER_PROPERTY_CHANGED(UCurvedStairBuilder, NumSteps, LockedDetailBuilder); } else if (BrushBuilderClass == UCylinderBuilder::StaticClass()) { REGISTER_PROPERTY_CHANGED(UCylinderBuilder, Sides, LockedDetailBuilder); REGISTER_PROPERTY_CHANGED(UCylinderBuilder, Hollow, LockedDetailBuilder); } else if (BrushBuilderClass == ULinearStairBuilder::StaticClass()) { REGISTER_PROPERTY_CHANGED(ULinearStairBuilder, NumSteps, LockedDetailBuilder); } else if (BrushBuilderClass == USpiralStairBuilder::StaticClass()) { REGISTER_PROPERTY_CHANGED(USpiralStairBuilder, NumSteps, LockedDetailBuilder); } else if (BrushBuilderClass == UTetrahedronBuilder::StaticClass()) { REGISTER_PROPERTY_CHANGED(UTetrahedronBuilder, SphereExtrapolation, LockedDetailBuilder); } } FOnRefreshDetails DetailsChanged = FOnRefreshDetails::CreateRaw(this, &FAkSurfaceReflectorSetDetailsCustomization::OnEnableValueChanged); ReflectorSetBeingCustomized->SetOnRefreshDetails(DetailsChanged); } void FAkSurfaceReflectorSetDetailsCustomization::OnEnableValueChanged() { if (ReflectorSetBeingCustomized.IsValid()) { ReflectorSetBeingCustomized->ClearOnRefreshDetails(); } } void FAkSurfaceReflectorSetDetailsCustomization::OnGeometryChanged() { if (ReflectorSetBeingCustomized.IsValid()) { ReflectorSetBeingCustomized->UpdatePolys(); ReflectorSetBeingCustomized->ClearOnRefreshDetails(); } } ////////////////////////////////////////////////////////////////////////// #undef LOCTEXT_NAMESPACE