// Copyright 2020-2024 CesiumGS, Inc. and Contributors #include "CesiumGlobeAnchorCustomization.h" #include "CesiumCustomization.h" #include "CesiumDegreesMinutesSecondsEditor.h" #include "CesiumGeoreference.h" #include "CesiumGlobeAnchorComponent.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" #include "IDetailGroup.h" #include "Widgets/SToolTip.h" #include "Widgets/Text/STextBlock.h" #define LOCTEXT_NAMESPACE "CesiumGlobeAnchorCustomization" FName FCesiumGlobeAnchorCustomization::RegisteredLayoutName; void FCesiumGlobeAnchorCustomization::Register( FPropertyEditorModule& PropertyEditorModule) { RegisteredLayoutName = UCesiumGlobeAnchorComponent::StaticClass()->GetFName(); PropertyEditorModule.RegisterCustomClassLayout( RegisteredLayoutName, FOnGetDetailCustomizationInstance::CreateStatic( &FCesiumGlobeAnchorCustomization::MakeInstance)); } void FCesiumGlobeAnchorCustomization::Unregister( FPropertyEditorModule& PropertyEditorModule) { PropertyEditorModule.UnregisterCustomClassLayout(RegisteredLayoutName); } TSharedRef FCesiumGlobeAnchorCustomization::MakeInstance() { return MakeShareable(new FCesiumGlobeAnchorCustomization); } void FCesiumGlobeAnchorCustomization::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder) { DetailBuilder.GetObjectsBeingCustomized(this->SelectedObjects); const bool bIsMultiSelect = this->SelectedObjects.Num() > 1; IDetailCategoryBuilder& CesiumCategory = DetailBuilder.EditCategory("Cesium"); TSharedPtr pButtons = CesiumCustomization::CreateButtonGroup(); pButtons->AddButtonForUFunction( UCesiumGlobeAnchorComponent::StaticClass()->FindFunctionByName( GET_FUNCTION_NAME_CHECKED( UCesiumGlobeAnchorComponent, SnapLocalUpToEllipsoidNormal))); pButtons->AddButtonForUFunction( UCesiumGlobeAnchorComponent::StaticClass()->FindFunctionByName( GET_FUNCTION_NAME_CHECKED( UCesiumGlobeAnchorComponent, SnapToEastSouthUp))); pButtons->Finish(DetailBuilder, CesiumCategory); CesiumCategory.AddProperty( GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorComponent, Georeference)); CesiumCategory.AddProperty(GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorComponent, ResolvedGeoreference)); CesiumCategory.AddProperty(GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorComponent, AdjustOrientationForGlobeWhenMoving)); CesiumCategory.AddProperty(GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorComponent, TeleportWhenUpdatingTransform)); if (!bIsMultiSelect) { this->UpdateDerivedProperties(); this->CreatePositionLongitudeLatitudeHeight(DetailBuilder, CesiumCategory); this->CreatePositionEarthCenteredEarthFixed(DetailBuilder, CesiumCategory); this->CreateRotationEastSouthUp(DetailBuilder, CesiumCategory); } else { FDetailWidgetRow& Row = CesiumCategory .AddCustomRow( LOCTEXT("MultipleSelectionFilter", "Multiple Selection")) .FilterString(LOCTEXT( "MultipleSelectionFilters", "Latitude Longitude Height ECEF ESU")); Row.WholeRowContent()[SNew(SBox).Padding(FMargin( 0.f, 4.f))[SNew(STextBlock) .Text(LOCTEXT( "MultiSelectInfo", "Multiple actors selected. Geodetic position (Latitude, Longitude, Height; ECEF) and " "ESU rotation cannot be edited in multi-select. Select a single actor to edit these values.")) .AutoWrapText(true)]]; } } void FCesiumGlobeAnchorCustomization::CreatePositionEarthCenteredEarthFixed( IDetailLayoutBuilder& DetailBuilder, IDetailCategoryBuilder& Category) { IDetailGroup& Group = CesiumCustomization::CreateGroup( Category, "PositionEarthCenteredEarthFixed", FText::FromString("Position (Earth-Centered, Earth-Fixed)"), false, true); TArrayView View = this->DerivedPointers; TSharedPtr XProperty = DetailBuilder.AddObjectPropertyData( View, GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, X)); TSharedPtr YProperty = DetailBuilder.AddObjectPropertyData( View, GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, Y)); TSharedPtr ZProperty = DetailBuilder.AddObjectPropertyData( View, GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, Z)); Group.AddPropertyRow(XProperty.ToSharedRef()); Group.AddPropertyRow(YProperty.ToSharedRef()); Group.AddPropertyRow(ZProperty.ToSharedRef()); } void FCesiumGlobeAnchorCustomization::CreatePositionLongitudeLatitudeHeight( IDetailLayoutBuilder& DetailBuilder, IDetailCategoryBuilder& Category) { IDetailGroup& Group = CesiumCustomization::CreateGroup( Category, "PositionLatitudeLongitudeHeight", FText::FromString("Position (Latitude, Longitude, Height)"), false, true); TArrayView View = this->DerivedPointers; TSharedPtr LatitudeProperty = DetailBuilder.AddObjectPropertyData( View, GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Latitude)); TSharedPtr LongitudeProperty = DetailBuilder.AddObjectPropertyData( View, GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Longitude)); TSharedPtr HeightProperty = DetailBuilder.AddObjectPropertyData( View, GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, Height)); IDetailPropertyRow& LatitudeRow = Group.AddPropertyRow(LatitudeProperty.ToSharedRef()); LatitudeEditor = MakeShared(LatitudeProperty, false); LatitudeEditor->PopulateRow(LatitudeRow); IDetailPropertyRow& LongitudeRow = Group.AddPropertyRow(LongitudeProperty.ToSharedRef()); LongitudeEditor = MakeShared(LongitudeProperty, true); LongitudeEditor->PopulateRow(LongitudeRow); Group.AddPropertyRow(HeightProperty.ToSharedRef()); } void FCesiumGlobeAnchorCustomization::CreateRotationEastSouthUp( IDetailLayoutBuilder& DetailBuilder, IDetailCategoryBuilder& Category) { IDetailGroup& Group = CesiumCustomization::CreateGroup( Category, "RotationEastSouthUp", FText::FromString("Rotation (East-South-Up)"), false, true); this->UpdateDerivedProperties(); TArrayView EastSouthUpPointerView = this->DerivedPointers; TSharedPtr RollProperty = DetailBuilder.AddObjectPropertyData(EastSouthUpPointerView, "Roll"); TSharedPtr PitchProperty = DetailBuilder.AddObjectPropertyData(EastSouthUpPointerView, "Pitch"); TSharedPtr YawProperty = DetailBuilder.AddObjectPropertyData(EastSouthUpPointerView, "Yaw"); Group.AddPropertyRow(RollProperty.ToSharedRef()); Group.AddPropertyRow(PitchProperty.ToSharedRef()); Group.AddPropertyRow(YawProperty.ToSharedRef()); } void FCesiumGlobeAnchorCustomization::UpdateDerivedProperties() { this->DerivedObjects.SetNum(this->SelectedObjects.Num()); this->DerivedPointers.SetNum(DerivedObjects.Num()); for (int i = 0; i < this->SelectedObjects.Num(); ++i) { if (!IsValid(this->DerivedObjects[i].Get())) { this->DerivedObjects[i] = NewObject(); } UCesiumGlobeAnchorComponent* GlobeAnchor = Cast(this->SelectedObjects[i]); this->DerivedObjects[i]->Initialize(GlobeAnchor); this->DerivedPointers[i] = this->DerivedObjects[i].Get(); } } void UCesiumGlobeAnchorDerivedProperties::PostEditChangeProperty( FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); if (!PropertyChangedEvent.Property) { return; } FName propertyName = PropertyChangedEvent.Property->GetFName(); if (propertyName == GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, X) || propertyName == GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, Y) || propertyName == GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, Z)) { this->GlobeAnchor->Modify(); this->GlobeAnchor->MoveToEarthCenteredEarthFixedPosition( FVector(this->X, this->Y, this->Z)); } else if (true) { if (propertyName == GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Longitude) || propertyName == GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Latitude) || propertyName == GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Height)) { this->GlobeAnchor->Modify(); this->GlobeAnchor->MoveToLongitudeLatitudeHeight( FVector(this->Longitude, this->Latitude, this->Height)); } else if ( propertyName == GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Pitch) || propertyName == GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, Yaw) || propertyName == GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Roll)) { this->GlobeAnchor->Modify(); this->GlobeAnchor->SetEastSouthUpRotation( FRotator(this->Pitch, this->Yaw, this->Roll).Quaternion()); } } } bool UCesiumGlobeAnchorDerivedProperties::CanEditChange( const FProperty* InProperty) const { const FName Name = InProperty->GetFName(); // Valid georeference, nothing to disable if (IsValid(this->GlobeAnchor->ResolveGeoreference())) { return true; } return Name != GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Longitude) && Name != GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Latitude) && Name != GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Height) && Name != GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Pitch) && Name != GET_MEMBER_NAME_CHECKED( UCesiumGlobeAnchorDerivedProperties, Yaw) && Name != GET_MEMBER_NAME_CHECKED(UCesiumGlobeAnchorDerivedProperties, Roll); } void UCesiumGlobeAnchorDerivedProperties::Initialize( UCesiumGlobeAnchorComponent* GlobeAnchorComponent) { this->GlobeAnchor = GlobeAnchorComponent; this->Tick(0.0f); } void UCesiumGlobeAnchorDerivedProperties::Tick(float DeltaTime) { if (this->GlobeAnchor) { FVector position = this->GlobeAnchor->GetEarthCenteredEarthFixedPosition(); this->X = position.X; this->Y = position.Y; this->Z = position.Z; // We can't transform the GlobeAnchor's ECEF coordinates back to // cartographic & rotation without a valid georeference to know what // ellipsoid to use. if (IsValid(this->GlobeAnchor->ResolveGeoreference())) { FVector llh = this->GlobeAnchor->GetLongitudeLatitudeHeight(); this->Longitude = llh.X; this->Latitude = llh.Y; this->Height = llh.Z; FQuat rotation = this->GlobeAnchor->GetEastSouthUpRotation(); FRotator rotator = rotation.Rotator(); this->Roll = rotator.Roll; this->Pitch = rotator.Pitch; this->Yaw = rotator.Yaw; } } } TStatId UCesiumGlobeAnchorDerivedProperties::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT( UCesiumGlobeAnchorRotationEastSouthUp, STATGROUP_Tickables); } #undef LOCTEXT_NAMESPACE