Files
UnrealEngineUWP/Engine/Source/Editor/DetailCustomizations/Private/CameraDetails.cpp
jon cain 708b978bef Automatic evaluation system for Orthographic Near and Far depth planes.
Uses a units to pixel calculation to determine the necessary precision of the depth buffer wrt 16bit downscaling.
Can be set per camera, and also introduced several CVars for adjusting settings.

Also added debug cameras so all cameras in projects can easily be set to Orthographic for testing.

#jira UE-205435
#rb Jeremy.Moore

[CL 31286794 by jon cain in ue5-main branch]
2024-02-08 06:17:59 -05:00

328 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "CameraDetails.h"
#include "Camera/CameraComponent.h"
#include "Containers/Array.h"
#include "Containers/UnrealString.h"
#include "Delegates/Delegate.h"
#include "DetailCategoryBuilder.h"
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "EditorCategoryUtils.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "HAL/PlatformCrt.h"
#include "IDetailPropertyRow.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Layout/Margin.h"
#include "Math/UnrealMathSSE.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "PropertyEditorModule.h"
#include "PropertyHandle.h"
#include "SlateOptMacros.h"
#include "SlotBase.h"
#include "Styling/AppStyle.h"
#include "Styling/SlateColor.h"
#include "Textures/SlateIcon.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/SBoxPanel.h"
#define LOCTEXT_NAMESPACE "CameraDetails"
const float FCameraDetails::MinAspectRatio = 0.1f;
const float FCameraDetails::MaxAspectRatio = 100.0f;
const float FCameraDetails::LowestCommonAspectRatio = 1.0f;
const float FCameraDetails::HighestCommonAspectRatio = 2.5f;
TSharedRef<IDetailCustomization> FCameraDetails::MakeInstance()
{
return MakeShareable( new FCameraDetails );
}
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void FCameraDetails::CustomizeDetails( IDetailLayoutBuilder& DetailLayout )
{
FSlateFontInfo FontStyle = FAppStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"));
LastParsedAspectRatioValue = -1.0f;
TSharedPtr<IPropertyHandle> bConstrainAspectRatioProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bConstrainAspectRatio));
TSharedPtr<IPropertyHandle> ProjectionModeProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, ProjectionMode));
AspectRatioProperty = DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, AspectRatio));
check(AspectRatioProperty.IsValid());
if (AspectRatioProperty.IsValid() && AspectRatioProperty->IsValidHandle())
{
FSimpleDelegate OnAspectRatioChangedDelegate = FSimpleDelegate::CreateSP(this, &FCameraDetails::OnAspectRatioChanged);
AspectRatioProperty->SetOnPropertyValueChanged(OnAspectRatioChangedDelegate);
}
// see if CameraSettings category should be hidden
bool bCameraSettingsHidden = false;
{
TArray< TWeakObjectPtr<UObject> > Objects;
DetailLayout.GetObjectsBeingCustomized(Objects);
for (TWeakObjectPtr<UObject> ObjWP : Objects)
{
UObject* const Obj = ObjWP.Get();
if (Obj && (FEditorCategoryUtils::IsCategoryHiddenFromClass(Obj->GetClass(), TEXT("CameraSettings"))))
{
bCameraSettingsHidden = true;
break;
}
}
}
DetailLayout.EditCategory("Current Camera Settings", FText::GetEmpty(), ECategoryPriority::Important);
if (bCameraSettingsHidden == false)
{
IDetailCategoryBuilder& CameraCategory = DetailLayout.EditCategory( "CameraSettings", FText::GetEmpty(), ECategoryPriority::Important );
// Organize the properties
CameraCategory.AddProperty(ProjectionModeProperty);
// Perspective-specific properties
IDetailPropertyRow& FieldOfViewRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, FieldOfView)));
FieldOfViewRow.Visibility( TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP( this, &FCameraDetails::ProjectionModeMatches, ProjectionModeProperty, ECameraProjectionMode::Perspective ) ) );
// Orthographic-specific properties
TAttribute<EVisibility> OrthographicVisibility = TAttribute<EVisibility>::Create(TAttribute<EVisibility>::FGetter::CreateSP(this, &FCameraDetails::ProjectionModeMatches, ProjectionModeProperty, ECameraProjectionMode::Orthographic));
IDetailPropertyRow& OrthoWidthRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, OrthoWidth)));
OrthoWidthRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& AutoCalculateOrthoPlanesRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bAutoCalculateOrthoPlanes)));
AutoCalculateOrthoPlanesRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& OrthoNearClipPlaneRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, OrthoNearClipPlane)));
OrthoNearClipPlaneRow.Visibility(OrthographicVisibility);
IDetailPropertyRow& OrthoFarClipPlaneRow = CameraCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, OrthoFarClipPlane)));
OrthoFarClipPlaneRow.Visibility(OrthographicVisibility);
// Aspect ratio
IDetailPropertyRow& AspectRatioRow = CameraCategory.AddProperty(AspectRatioProperty);
// Provide the special aspect ratio row
AspectRatioRow.CustomWidget()
.NameContent()
[
AspectRatioProperty->CreatePropertyNameWidget()
]
.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.Padding(0.0f, 2.0f, 5.0f, 2.0f)
[
SNew(SNumericEntryBox<float>)
.AllowSpin(true)
.Value(this, &FCameraDetails::GetAspectRatio)
.Font(FontStyle)
.MinValue(MinAspectRatio)
.MaxValue(MaxAspectRatio)
.MinSliderValue(LowestCommonAspectRatio)
.MaxSliderValue(HighestCommonAspectRatio)
.OnValueChanged(this, &FCameraDetails::OnAspectRatioSpinnerChanged)
.ToolTipText(LOCTEXT("AspectFloatTooltip", "Aspect Ratio (Width/Height)"))
]
+SHorizontalBox::Slot()
[
SNew(SComboButton)
.OnGetMenuContent( this, &FCameraDetails::OnGetComboContent )
.ContentPadding(0.0f)
.ButtonStyle( FAppStyle::Get(), "ToggleButton" )
.ForegroundColor(FSlateColor::UseForeground())
.VAlign(VAlign_Center)
.ButtonContent()
[
SAssignNew(AspectTextBox, SEditableTextBox)
.HintText( LOCTEXT("AspectTextHint", "width x height") )
.ToolTipText( LOCTEXT("AspectTextTooltip", "Enter a ratio in the form \'width x height\' or \'width:height\'") )
.Font(FontStyle)
.OnTextCommitted(this, &FCameraDetails::OnCommitAspectRatioText)
]
]
];
}
IDetailCategoryBuilder& CameraSettingsCategory = DetailLayout.EditCategory( "CameraOptions", FText::GetEmpty(), ECategoryPriority::Important );
CameraSettingsCategory.AddProperty( bConstrainAspectRatioProperty );
CameraSettingsCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, bUsePawnControlRotation)));
CameraSettingsCategory.AddProperty(DetailLayout.GetProperty(GET_MEMBER_NAME_CHECKED(UCameraComponent, PostProcessBlendWeight)));
UpdateAspectTextFromProperty();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
void FCameraDetails::OnAspectRatioChanged()
{
// Callback set as the SetOnPropertyValueChanged() on the actual aspect ratio property
UpdateAspectTextFromProperty();
}
TOptional<float> FCameraDetails::GetAspectRatio() const
{
// Gets the actual aspect ratio property value
float Value = 0.0f;
if (AspectRatioProperty->GetValue(Value) == FPropertyAccess::Success)
{
return Value;
}
return TOptional<float>();
}
void FCameraDetails::OnAspectRatioSpinnerChanged(float InValue)
{
// Called when the user inputs a new aspect ratio into the spinbox
AspectRatioProperty->SetValue(InValue);
UpdateAspectTextFromProperty();
}
void FCameraDetails::UpdateAspectTextFromProperty()
{
// Called whenever the actual aspect ratio property changes - clears the text box if the value no longer matches the current text
if (AspectTextBox.IsValid())
{
TOptional<float> Value = GetAspectRatio();
if (!Value.IsSet() || Value.GetValue() < LastParsedAspectRatioValue - DELTA || Value.GetValue() > LastParsedAspectRatioValue + DELTA)
{
LastParsedAspectRatioValue = -1.0f;
if (!AspectTextBox->GetText().IsEmpty())
{
AspectTextBox->SetText(FText::GetEmpty());
}
}
}
}
TSharedRef<SWidget> FCameraDetails::OnGetComboContent() const
{
// Fill the combo menu with presets of common screen resolutions
FMenuBuilder MenuBuilder(true, NULL);
TArray<FText> Items;
Items.Add(LOCTEXT("PresetRatio640x480", "640x480 (4:3, 1.33) SDTV"));
Items.Add(LOCTEXT("PresetRatio852x480", "852x480 (16:9, 1.78) SDTV Widescreen"));
Items.Add(LOCTEXT("PresetRatio1280x720", "1280x720 (16:9, 1.78) HDTV 720"));
Items.Add(LOCTEXT("PresetRatio1920x1080", "1920x1080 (16:9, 1.78) HDTV 1080"));
Items.Add(LOCTEXT("PresetRatio960x544", "960x544 (16:9, 1.76) PS Vita"));
Items.Add(LOCTEXT("PresetRatio1024x640", "1024x640 (1.6)"));
Items.Add(LOCTEXT("PresetRatio1024x76", "1024x768 (4:3, 1.33)"));
Items.Add(LOCTEXT("PresetRatio1366x768", "1366x768 (16:9, 1.78)"));
Items.Add(LOCTEXT("PresetRatio2048x1536", "2048x1536 (4:3, 1.33) iPad 3"));
Items.Add(LOCTEXT("PresetRatio4096x2304", "4096x2304 (16:9, 1.78) 4K"));
for (auto ItemIter = Items.CreateConstIterator(); ItemIter; ++ItemIter)
{
FText ItemText = *ItemIter;
FUIAction ItemAction( FExecuteAction::CreateSP( const_cast<FCameraDetails*>(this), &FCameraDetails::CommitAspectRatioText, ItemText ) );
MenuBuilder.AddMenuEntry(ItemText, TAttribute<FText>(), FSlateIcon(), ItemAction);
}
return MenuBuilder.MakeWidget();
}
void FCameraDetails::CommitAspectRatioText(FText ItemText)
{
// placing new text into the box - so set the actual text then run the 'oncommit' handler
AspectTextBox->SetText(ItemText);
OnCommitAspectRatioText(ItemText, ETextCommit::Default);
}
void FCameraDetails::OnCommitAspectRatioText(const FText& ItemFText, ETextCommit::Type CommitInfo)
{
// Parse the text assuming the following format
// <INTEGER><optional whitespace><x or : or /><optional whitespace><INTEGER><optional extra info>
FString ItemText = ItemFText.ToString();
float ParsedRatio = -1.0f;
int32 DelimIdx = INDEX_NONE;
if (!ItemText.FindChar(TCHAR('x'), DelimIdx))
{
if (!ItemText.FindChar(TCHAR('X'), DelimIdx))
{
if (!ItemText.FindChar(TCHAR(':'), DelimIdx))
{
ItemText.FindChar(TCHAR('/'), DelimIdx);
}
}
}
if (DelimIdx != INDEX_NONE)
{
int32 Width;
TTypeFromString<int32>::FromString(Width, *ItemText.Mid(0, DelimIdx).TrimStartAndEnd());
if (Width > 0)
{
int32 WSIdx;
FString RemainingText = ItemText.Mid(DelimIdx + 1).TrimStart();
if (RemainingText.FindChar(TCHAR(' '), WSIdx))
{
RemainingText.LeftInline(WSIdx, EAllowShrinking::No);
}
int32 Height;
TTypeFromString<int32>::FromString(Height, *RemainingText);
if (Height > 0)
{
ParsedRatio = (float)Width / (float)Height;
}
}
}
if (ParsedRatio < 0.0f)
{
// invalid text - value couldn't be read
}
else if (ParsedRatio < MinAspectRatio)
{
// invalid value - too small
}
else if (ParsedRatio > MaxAspectRatio)
{
// invalid value - too large
}
else
{
// valid ratio parsed from text
LastParsedAspectRatioValue = ParsedRatio;
AspectRatioProperty->SetValue(ParsedRatio);
}
}
EVisibility FCameraDetails::ProjectionModeMatches(TSharedPtr<IPropertyHandle> Property, ECameraProjectionMode::Type DesiredMode) const
{
if (Property.IsValid())
{
uint8 ValueAsByte;
FPropertyAccess::Result Result = Property->GetValue(/*out*/ ValueAsByte);
if (Result == FPropertyAccess::Success)
{
return (((ECameraProjectionMode::Type)ValueAsByte) == DesiredMode) ? EVisibility::Visible : EVisibility::Collapsed;
}
}
// If there are multiple values, show all properties
return EVisibility::Visible;
}
#undef LOCTEXT_NAMESPACE